]> git.argeo.org Git - lgpl/argeo-commons.git/blob - server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/JcrUtils.java
GIS does not expose perspectives and views
[lgpl/argeo-commons.git] / server / runtime / org.argeo.server.jcr / src / main / java / org / argeo / jcr / JcrUtils.java
1 /*
2 * Copyright (C) 2010 Mathieu Baudier <mbaudier@argeo.org>
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package org.argeo.jcr;
18
19 import java.net.MalformedURLException;
20 import java.net.URL;
21 import java.text.DateFormat;
22 import java.text.ParseException;
23 import java.util.Calendar;
24 import java.util.Date;
25 import java.util.GregorianCalendar;
26 import java.util.Iterator;
27 import java.util.List;
28 import java.util.Map;
29 import java.util.StringTokenizer;
30 import java.util.TreeMap;
31
32 import javax.jcr.Binary;
33 import javax.jcr.NamespaceRegistry;
34 import javax.jcr.Node;
35 import javax.jcr.NodeIterator;
36 import javax.jcr.Property;
37 import javax.jcr.PropertyIterator;
38 import javax.jcr.RepositoryException;
39 import javax.jcr.Session;
40 import javax.jcr.Value;
41 import javax.jcr.nodetype.NodeType;
42 import javax.jcr.query.Query;
43 import javax.jcr.query.QueryResult;
44
45 import org.apache.commons.logging.Log;
46 import org.apache.commons.logging.LogFactory;
47 import org.argeo.ArgeoException;
48
49 /** Utility methods to simplify common JCR operations. */
50 public class JcrUtils {
51 private final static Log log = LogFactory.getLog(JcrUtils.class);
52
53 /**
54 * Queries one single node.
55 *
56 * @return one single node or null if none was found
57 * @throws ArgeoException
58 * if more than one node was found
59 */
60 public static Node querySingleNode(Query query) {
61 NodeIterator nodeIterator;
62 try {
63 QueryResult queryResult = query.execute();
64 nodeIterator = queryResult.getNodes();
65 } catch (RepositoryException e) {
66 throw new ArgeoException("Cannot execute query " + query, e);
67 }
68 Node node;
69 if (nodeIterator.hasNext())
70 node = nodeIterator.nextNode();
71 else
72 return null;
73
74 if (nodeIterator.hasNext())
75 throw new ArgeoException("Query returned more than one node.");
76 return node;
77 }
78
79 /** Removes forbidden characters from a path, replacing them with '_' */
80 public static String removeForbiddenCharacters(String str) {
81 return str.replace('[', '_').replace(']', '_').replace('/', '_')
82 .replace('*', '_');
83
84 }
85
86 /** Retrieves the parent path of the provided path */
87 public static String parentPath(String path) {
88 if (path.equals("/"))
89 throw new ArgeoException("Root path '/' has no parent path");
90 if (path.charAt(0) != '/')
91 throw new ArgeoException("Path " + path + " must start with a '/'");
92 String pathT = path;
93 if (pathT.charAt(pathT.length() - 1) == '/')
94 pathT = pathT.substring(0, pathT.length() - 2);
95
96 int index = pathT.lastIndexOf('/');
97 return pathT.substring(0, index);
98 }
99
100 /** The provided data as a path ('/' at the end, not the beginning) */
101 public static String dateAsPath(Calendar cal) {
102 return dateAsPath(cal, false);
103 }
104
105 /**
106 * Creates a deep path based on a URL:
107 * http://subdomain.example.com/to/content?args =>
108 * com/example/subdomain/to/content
109 */
110 public static String urlAsPath(String url) {
111 try {
112 URL u = new URL(url);
113 StringBuffer path = new StringBuffer(url.length());
114 // invert host
115 String[] hostTokens = u.getHost().split("\\.");
116 for (int i = hostTokens.length - 1; i >= 0; i--)
117 path.append(hostTokens[i]).append('/');
118 // we don't put port since it may not always be there and may change
119 path.append(u.getPath());
120 return path.toString();
121 } catch (MalformedURLException e) {
122 throw new ArgeoException("Cannot generate URL path for " + url, e);
123 }
124 }
125
126 /**
127 * The provided data as a path ('/' at the end, not the beginning)
128 *
129 * @param cal
130 * the date
131 * @param addHour
132 * whether to add hour as well
133 */
134 public static String dateAsPath(Calendar cal, Boolean addHour) {
135 StringBuffer buf = new StringBuffer(14);
136 buf.append('Y').append(cal.get(Calendar.YEAR));// 5
137 buf.append('/');// 1
138 int month = cal.get(Calendar.MONTH) + 1;
139 buf.append('M');
140 if (month < 10)
141 buf.append(0);
142 buf.append(month);// 3
143 buf.append('/');// 1
144 int day = cal.get(Calendar.DAY_OF_MONTH);
145 if (day < 10)
146 buf.append(0);
147 buf.append('D').append(day);// 3
148 buf.append('/');// 1
149 if (addHour) {
150 int hour = cal.get(Calendar.HOUR_OF_DAY);
151 if (hour < 10)
152 buf.append(0);
153 buf.append('H').append(hour);// 3
154 buf.append('/');// 1
155 }
156 return buf.toString();
157
158 }
159
160 /** Converts in one call a string into a gregorian calendar. */
161 public static Calendar parseCalendar(DateFormat dateFormat, String value) {
162 try {
163 Date date = dateFormat.parse(value);
164 Calendar calendar = new GregorianCalendar();
165 calendar.setTime(date);
166 return calendar;
167 } catch (ParseException e) {
168 throw new ArgeoException("Cannot parse " + value
169 + " with date format " + dateFormat, e);
170 }
171
172 }
173
174 /** Converts the FQDN of an host into a path (converts '.' into '/'). */
175 public static String hostAsPath(String host) {
176 // TODO : inverse order of the elements (to have org/argeo/test IO
177 // test/argeo/org
178 return host.replace('.', '/');
179 }
180
181 /** The last element of a path. */
182 public static String lastPathElement(String path) {
183 if (path.charAt(path.length() - 1) == '/')
184 throw new ArgeoException("Path " + path + " cannot end with '/'");
185 int index = path.lastIndexOf('/');
186 if (index < 0)
187 throw new ArgeoException("Cannot find last path element for "
188 + path);
189 return path.substring(index + 1);
190 }
191
192 /** Creates the nodes making path, if they don't exist. */
193 public static Node mkdirs(Session session, String path) {
194 return mkdirs(session, path, null, null, false);
195 }
196
197 /**
198 * @deprecated use {@link #mkdirs(Session, String, String, String, Boolean)}
199 * instead.
200 */
201 @Deprecated
202 public static Node mkdirs(Session session, String path, String type,
203 Boolean versioning) {
204 return mkdirs(session, path, type, type, false);
205 }
206
207 /**
208 * @param type
209 * the type of the leaf node
210 */
211 public static Node mkdirs(Session session, String path, String type) {
212 return mkdirs(session, path, type, null, false);
213 }
214
215 /** Creates the nodes making path, if they don't exist. */
216 public static Node mkdirs(Session session, String path, String type,
217 String intermediaryNodeType, Boolean versioning) {
218 try {
219 if (path.equals('/'))
220 return session.getRootNode();
221
222 if (session.itemExists(path)) {
223 Node node = session.getNode(path);
224 // check type
225 if (type != null
226 && !type.equals(node.getPrimaryNodeType().getName()))
227 throw new ArgeoException("Node " + node
228 + " exists but is of type "
229 + node.getPrimaryNodeType().getName()
230 + " not of type " + type);
231 // TODO: check versioning
232 return node;
233 }
234
235 StringTokenizer st = new StringTokenizer(path, "/");
236 StringBuffer current = new StringBuffer("/");
237 Node currentNode = session.getRootNode();
238 while (st.hasMoreTokens()) {
239 String part = st.nextToken();
240 current.append(part).append('/');
241 if (!session.itemExists(current.toString())) {
242 if (!st.hasMoreTokens() && type != null)
243 currentNode = currentNode.addNode(part, type);
244 else if (st.hasMoreTokens() && intermediaryNodeType != null)
245 currentNode = currentNode.addNode(part,
246 intermediaryNodeType);
247 else
248 currentNode = currentNode.addNode(part);
249 if (versioning)
250 currentNode.addMixin(NodeType.MIX_VERSIONABLE);
251 if (log.isTraceEnabled())
252 log.debug("Added folder " + part + " as " + current);
253 } else {
254 currentNode = (Node) session.getItem(current.toString());
255 }
256 }
257 session.save();
258 return currentNode;
259 } catch (RepositoryException e) {
260 throw new ArgeoException("Cannot mkdirs " + path, e);
261 }
262 }
263
264 /**
265 * Safe and repository implementation independent registration of a
266 * namespace.
267 */
268 public static void registerNamespaceSafely(Session session, String prefix,
269 String uri) {
270 try {
271 registerNamespaceSafely(session.getWorkspace()
272 .getNamespaceRegistry(), prefix, uri);
273 } catch (RepositoryException e) {
274 throw new ArgeoException("Cannot find namespace registry", e);
275 }
276 }
277
278 /**
279 * Safe and repository implementation independent registration of a
280 * namespace.
281 */
282 public static void registerNamespaceSafely(NamespaceRegistry nr,
283 String prefix, String uri) {
284 try {
285 String[] prefixes = nr.getPrefixes();
286 for (String pref : prefixes)
287 if (pref.equals(prefix)) {
288 String registeredUri = nr.getURI(pref);
289 if (!registeredUri.equals(uri))
290 throw new ArgeoException("Prefix " + pref
291 + " already registered for URI "
292 + registeredUri
293 + " which is different from provided URI "
294 + uri);
295 else
296 return;// skip
297 }
298 nr.registerNamespace(prefix, uri);
299 } catch (RepositoryException e) {
300 throw new ArgeoException("Cannot register namespace " + uri
301 + " under prefix " + prefix, e);
302 }
303 }
304
305 /** Recursively outputs the contents of the given node. */
306 public static void debug(Node node) {
307 try {
308 // First output the node path
309 log.debug(node.getPath());
310 // Skip the virtual (and large!) jcr:system subtree
311 if (node.getName().equals("jcr:system")) {
312 return;
313 }
314
315 // Then the children nodes (recursive)
316 NodeIterator it = node.getNodes();
317 while (it.hasNext()) {
318 Node childNode = it.nextNode();
319 debug(childNode);
320 }
321
322 // Then output the properties
323 PropertyIterator properties = node.getProperties();
324 // log.debug("Property are : ");
325
326 while (properties.hasNext()) {
327 Property property = properties.nextProperty();
328 if (property.getDefinition().isMultiple()) {
329 // A multi-valued property, print all values
330 Value[] values = property.getValues();
331 for (int i = 0; i < values.length; i++) {
332 log.debug(property.getPath() + "="
333 + values[i].getString());
334 }
335 } else {
336 // A single-valued property
337 log.debug(property.getPath() + "=" + property.getString());
338 }
339 }
340 } catch (Exception e) {
341 log.error("Could not debug " + node, e);
342 }
343
344 }
345
346 /**
347 * Copies recursively the content of a node to another one. Mixin are NOT
348 * copied.
349 */
350 public static void copy(Node fromNode, Node toNode) {
351 try {
352 PropertyIterator pit = fromNode.getProperties();
353 properties: while (pit.hasNext()) {
354 Property fromProperty = pit.nextProperty();
355 String propertyName = fromProperty.getName();
356 if (toNode.hasProperty(propertyName)
357 && toNode.getProperty(propertyName).getDefinition()
358 .isProtected())
359 continue properties;
360
361 toNode.setProperty(fromProperty.getName(),
362 fromProperty.getValue());
363 }
364
365 NodeIterator nit = fromNode.getNodes();
366 while (nit.hasNext()) {
367 Node fromChild = nit.nextNode();
368 Integer index = fromChild.getIndex();
369 String nodeRelPath = fromChild.getName() + "[" + index + "]";
370 Node toChild;
371 if (toNode.hasNode(nodeRelPath))
372 toChild = toNode.getNode(nodeRelPath);
373 else
374 toChild = toNode.addNode(fromChild.getName(), fromChild
375 .getPrimaryNodeType().getName());
376 copy(fromChild, toChild);
377 }
378 } catch (RepositoryException e) {
379 throw new ArgeoException("Cannot copy " + fromNode + " to "
380 + toNode, e);
381 }
382 }
383
384 /**
385 * Check whether all first-level properties (except jcr:* properties) are
386 * equal. Skip jcr:* properties
387 */
388 public static Boolean allPropertiesEquals(Node reference, Node observed,
389 Boolean onlyCommonProperties) {
390 try {
391 PropertyIterator pit = reference.getProperties();
392 props: while (pit.hasNext()) {
393 Property propReference = pit.nextProperty();
394 String propName = propReference.getName();
395 if (propName.startsWith("jcr:"))
396 continue props;
397
398 if (!observed.hasProperty(propName))
399 if (onlyCommonProperties)
400 continue props;
401 else
402 return false;
403 // TODO: deal with multiple property values?
404 if (!observed.getProperty(propName).getValue()
405 .equals(propReference.getValue()))
406 return false;
407 }
408 return true;
409 } catch (RepositoryException e) {
410 throw new ArgeoException("Cannot check all properties equals of "
411 + reference + " and " + observed, e);
412 }
413 }
414
415 public static Map<String, PropertyDiff> diffProperties(Node reference,
416 Node observed) {
417 Map<String, PropertyDiff> diffs = new TreeMap<String, PropertyDiff>();
418 diffPropertiesLevel(diffs, null, reference, observed);
419 return diffs;
420 }
421
422 /**
423 * Compare the properties of two nodes. Recursivity to child nodes is not
424 * yet supported. Skip jcr:* properties.
425 */
426 static void diffPropertiesLevel(Map<String, PropertyDiff> diffs,
427 String baseRelPath, Node reference, Node observed) {
428 try {
429 // check removed and modified
430 PropertyIterator pit = reference.getProperties();
431 props: while (pit.hasNext()) {
432 Property p = pit.nextProperty();
433 String name = p.getName();
434 if (name.startsWith("jcr:"))
435 continue props;
436
437 if (!observed.hasProperty(name)) {
438 String relPath = propertyRelPath(baseRelPath, name);
439 PropertyDiff pDiff = new PropertyDiff(PropertyDiff.REMOVED,
440 relPath, p.getValue(), null);
441 diffs.put(relPath, pDiff);
442 } else {
443 if (p.isMultiple())
444 continue props;
445 Value referenceValue = p.getValue();
446 Value newValue = observed.getProperty(name).getValue();
447 if (!referenceValue.equals(newValue)) {
448 String relPath = propertyRelPath(baseRelPath, name);
449 PropertyDiff pDiff = new PropertyDiff(
450 PropertyDiff.MODIFIED, relPath, referenceValue,
451 newValue);
452 diffs.put(relPath, pDiff);
453 }
454 }
455 }
456 // check added
457 pit = observed.getProperties();
458 props: while (pit.hasNext()) {
459 Property p = pit.nextProperty();
460 String name = p.getName();
461 if (name.startsWith("jcr:"))
462 continue props;
463 if (!reference.hasProperty(name)) {
464 String relPath = propertyRelPath(baseRelPath, name);
465 PropertyDiff pDiff = new PropertyDiff(PropertyDiff.ADDED,
466 relPath, null, p.getValue());
467 diffs.put(relPath, pDiff);
468 }
469 }
470 } catch (RepositoryException e) {
471 throw new ArgeoException("Cannot diff " + reference + " and "
472 + observed, e);
473 }
474 }
475
476 /**
477 * Compare only a restricted list of properties of two nodes. No
478 * recursivity.
479 *
480 */
481 public static Map<String, PropertyDiff> diffProperties(Node reference,
482 Node observed, List<String> properties) {
483 Map<String, PropertyDiff> diffs = new TreeMap<String, PropertyDiff>();
484 try {
485 Iterator<String> pit = properties.iterator();
486
487 props: while (pit.hasNext()) {
488 String name = pit.next();
489 if (!reference.hasProperty(name)) {
490 if (!observed.hasProperty(name))
491 continue props;
492 Value val = observed.getProperty(name).getValue();
493 try {
494 // empty String but not null
495 if ("".equals(val.getString()))
496 continue props;
497 } catch (Exception e) {
498 // not parseable as String, silent
499 }
500 PropertyDiff pDiff = new PropertyDiff(PropertyDiff.ADDED,
501 name, null, val);
502 diffs.put(name, pDiff);
503 } else if (!observed.hasProperty(name)) {
504 PropertyDiff pDiff = new PropertyDiff(PropertyDiff.REMOVED,
505 name, reference.getProperty(name).getValue(), null);
506 diffs.put(name, pDiff);
507 } else {
508 Value referenceValue = reference.getProperty(name)
509 .getValue();
510 Value newValue = observed.getProperty(name).getValue();
511 if (!referenceValue.equals(newValue)) {
512 PropertyDiff pDiff = new PropertyDiff(
513 PropertyDiff.MODIFIED, name, referenceValue,
514 newValue);
515 diffs.put(name, pDiff);
516 }
517 }
518 }
519 } catch (RepositoryException e) {
520 throw new ArgeoException("Cannot diff " + reference + " and "
521 + observed, e);
522 }
523 return diffs;
524 }
525
526 /** Builds a property relPath to be used in the diff. */
527 private static String propertyRelPath(String baseRelPath,
528 String propertyName) {
529 if (baseRelPath == null)
530 return propertyName;
531 else
532 return baseRelPath + '/' + propertyName;
533 }
534
535 /**
536 * Normalize a name so taht it can be stores in contexts not supporting
537 * names with ':' (typically databases). Replaces ':' by '_'.
538 */
539 public static String normalize(String name) {
540 return name.replace(':', '_');
541 }
542
543 public static void closeQuietly(Binary binary) {
544 if (binary == null)
545 return;
546 binary.dispose();
547 }
548 }