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