2 * Copyright (C) 2010 Mathieu Baudier <mbaudier@argeo.org>
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package org
.argeo
.jcr
;
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
;
27 import java
.util
.StringTokenizer
;
28 import java
.util
.TreeMap
;
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
;
41 import org
.apache
.commons
.logging
.Log
;
42 import org
.apache
.commons
.logging
.LogFactory
;
43 import org
.argeo
.ArgeoException
;
45 /** Utility methods to simplify common JCR operations. */
46 public class JcrUtils
{
47 private final static Log log
= LogFactory
.getLog(JcrUtils
.class);
50 * Queries one single node.
52 * @return one single node or null if none was found
53 * @throws ArgeoException
54 * if more than one node was found
56 public static Node
querySingleNode(Query query
) {
57 NodeIterator nodeIterator
;
59 QueryResult queryResult
= query
.execute();
60 nodeIterator
= queryResult
.getNodes();
61 } catch (RepositoryException e
) {
62 throw new ArgeoException("Cannot execute query " + query
, e
);
65 if (nodeIterator
.hasNext())
66 node
= nodeIterator
.nextNode();
70 if (nodeIterator
.hasNext())
71 throw new ArgeoException("Query returned more than one node.");
75 /** Removes forbidden characters from a path, replacing them with '_' */
76 public static String
removeForbiddenCharacters(String str
) {
77 return str
.replace('[', '_').replace(']', '_').replace('/', '_')
82 /** Retrieves the parent path of the provided path */
83 public static String
parentPath(String path
) {
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 '/'");
89 if (pathT
.charAt(pathT
.length() - 1) == '/')
90 pathT
= pathT
.substring(0, pathT
.length() - 2);
92 int index
= pathT
.lastIndexOf('/');
93 return pathT
.substring(0, index
);
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);
102 * The provided data as a path ('/' at the end, not the beginning)
107 * whether to add hour as well
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
113 int month
= cal
.get(Calendar
.MONTH
) + 1;
117 buf
.append(month
);// 3
119 int day
= cal
.get(Calendar
.DAY_OF_MONTH
);
122 buf
.append('D').append(day
);// 3
125 int hour
= cal
.get(Calendar
.HOUR_OF_DAY
);
128 buf
.append('H').append(hour
);// 3
131 return buf
.toString();
135 /** Converts in one call a string into a gregorian calendar. */
136 public static Calendar
parseCalendar(DateFormat dateFormat
, String value
) {
138 Date date
= dateFormat
.parse(value
);
139 Calendar calendar
= new GregorianCalendar();
140 calendar
.setTime(date
);
142 } catch (ParseException e
) {
143 throw new ArgeoException("Cannot parse " + value
144 + " with date format " + dateFormat
, e
);
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
153 return host
.replace('.', '/');
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('/');
162 throw new ArgeoException("Cannot find last path element for "
164 return path
.substring(index
+ 1);
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);
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
) {
176 if (path
.equals('/'))
177 return session
.getRootNode();
179 if (session
.itemExists(path
)) {
180 Node node
= session
.getNode(path
);
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
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())) {
200 currentNode
= currentNode
.addNode(part
, type
);
202 currentNode
= currentNode
.addNode(part
);
204 currentNode
.addMixin(ArgeoJcrConstants
.MIX_VERSIONABLE
);
205 if (log
.isTraceEnabled())
206 log
.debug("Added folder " + part
+ " as " + current
);
208 currentNode
= (Node
) session
.getItem(current
.toString());
213 } catch (RepositoryException e
) {
214 throw new ArgeoException("Cannot mkdirs " + path
, e
);
219 * Safe and repository implementation independent registration of a
222 public static void registerNamespaceSafely(Session session
, String prefix
,
225 registerNamespaceSafely(session
.getWorkspace()
226 .getNamespaceRegistry(), prefix
, uri
);
227 } catch (RepositoryException e
) {
228 throw new ArgeoException("Cannot find namespace registry", e
);
233 * Safe and repository implementation independent registration of a
236 public static void registerNamespaceSafely(NamespaceRegistry nr
,
237 String prefix
, String uri
) {
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 "
247 + " which is different from provided URI "
252 nr
.registerNamespace(prefix
, uri
);
253 } catch (RepositoryException e
) {
254 throw new ArgeoException("Cannot register namespace " + uri
255 + " under prefix " + prefix
, e
);
259 /** Recursively outputs the contents of the given node. */
260 public static void debug(Node node
) {
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
)) {
269 // Then the children nodes (recursive)
270 NodeIterator it
= node
.getNodes();
271 while (it
.hasNext()) {
272 Node childNode
= it
.nextNode();
276 // Then output the properties
277 PropertyIterator properties
= node
.getProperties();
278 // log.debug("Property are : ");
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());
290 // A single-valued property
291 log
.debug(property
.getPath() + "=" + property
.getString());
294 } catch (Exception e
) {
295 log
.error("Could not debug " + node
, e
);
301 * Copies recursively the content of a node to another one. Mixin are NOT
304 public static void copy(Node fromNode
, Node toNode
) {
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()
315 toNode
.setProperty(fromProperty
.getName(),
316 fromProperty
.getValue());
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
+ "]";
325 if (toNode
.hasNode(nodeRelPath
))
326 toChild
= toNode
.getNode(nodeRelPath
);
328 toChild
= toNode
.addNode(fromChild
.getName(), fromChild
329 .getPrimaryNodeType().getName());
330 copy(fromChild
, toChild
);
332 } catch (RepositoryException e
) {
333 throw new ArgeoException("Cannot copy " + fromNode
+ " to "
339 * Check whether all first-level properties (except jcr:* properties) are
340 * equal. Skip jcr:* properties
342 public static Boolean
allPropertiesEquals(Node reference
, Node observed
,
343 Boolean onlyCommonProperties
) {
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:"))
352 if (!observed
.hasProperty(propName
))
353 if (onlyCommonProperties
)
357 // TODO: deal with multiple property values?
358 if (!observed
.getProperty(propName
).getValue()
359 .equals(propReference
.getValue()))
363 } catch (RepositoryException e
) {
364 throw new ArgeoException("Cannot check all properties equals of "
365 + reference
+ " and " + observed
, e
);
369 public static Map
<String
, PropertyDiff
> diffProperties(Node reference
,
371 Map
<String
, PropertyDiff
> diffs
= new TreeMap
<String
, PropertyDiff
>();
372 diffPropertiesLevel(diffs
, null, reference
, observed
);
377 * Compare the properties of two nodes. Recursivity to child nodes is not
378 * yet supported. Skip jcr:* properties.
380 static void diffPropertiesLevel(Map
<String
, PropertyDiff
> diffs
,
381 String baseRelPath
, Node reference
, Node observed
) {
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:"))
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
);
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
,
406 diffs
.put(relPath
, pDiff
);
411 pit
= observed
.getProperties();
412 props
: while (pit
.hasNext()) {
413 Property p
= pit
.nextProperty();
414 String name
= p
.getName();
415 if (name
.startsWith("jcr:"))
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
);
424 } catch (RepositoryException e
) {
425 throw new ArgeoException("Cannot diff " + reference
+ " and "
431 * Compare only a restricted list of properties of two nodes. No
435 public static Map
<String
, PropertyDiff
> diffProperties(Node reference
,
436 Node observed
, List
<String
> properties
) {
437 Map
<String
, PropertyDiff
> diffs
= new TreeMap
<String
, PropertyDiff
>();
439 Iterator
<String
> pit
= properties
.iterator();
441 props
: while (pit
.hasNext()) {
442 String name
= pit
.next();
443 if (!reference
.hasProperty(name
)) {
444 if (!observed
.hasProperty(name
))
446 Value val
= observed
.getProperty(name
).getValue();
448 // empty String but not null
449 if ("".equals(val
.getString()))
451 } catch (Exception e
) {
452 // not parseable as String, silent
454 PropertyDiff pDiff
= new PropertyDiff(PropertyDiff
.ADDED
,
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
);
462 Value referenceValue
= reference
.getProperty(name
)
464 Value newValue
= observed
.getProperty(name
).getValue();
465 if (!referenceValue
.equals(newValue
)) {
466 PropertyDiff pDiff
= new PropertyDiff(
467 PropertyDiff
.MODIFIED
, name
, referenceValue
,
469 diffs
.put(name
, pDiff
);
473 } catch (RepositoryException e
) {
474 throw new ArgeoException("Cannot diff " + reference
+ " and "
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)
486 return baseRelPath
+ '/' + propertyName
;