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
.net
.MalformedURLException
;
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
.HashMap
;
27 import java
.util
.Iterator
;
28 import java
.util
.List
;
30 import java
.util
.StringTokenizer
;
31 import java
.util
.TreeMap
;
33 import javax
.jcr
.Binary
;
34 import javax
.jcr
.NamespaceRegistry
;
35 import javax
.jcr
.Node
;
36 import javax
.jcr
.NodeIterator
;
37 import javax
.jcr
.Property
;
38 import javax
.jcr
.PropertyIterator
;
39 import javax
.jcr
.Repository
;
40 import javax
.jcr
.RepositoryException
;
41 import javax
.jcr
.RepositoryFactory
;
42 import javax
.jcr
.Session
;
43 import javax
.jcr
.Value
;
44 import javax
.jcr
.Workspace
;
45 import javax
.jcr
.nodetype
.NodeType
;
46 import javax
.jcr
.observation
.EventListener
;
47 import javax
.jcr
.query
.Query
;
48 import javax
.jcr
.query
.QueryResult
;
49 import javax
.jcr
.query
.qom
.Constraint
;
50 import javax
.jcr
.query
.qom
.DynamicOperand
;
51 import javax
.jcr
.query
.qom
.QueryObjectModelFactory
;
52 import javax
.jcr
.query
.qom
.Selector
;
53 import javax
.jcr
.query
.qom
.StaticOperand
;
55 import org
.apache
.commons
.logging
.Log
;
56 import org
.apache
.commons
.logging
.LogFactory
;
57 import org
.argeo
.ArgeoException
;
59 /** Utility methods to simplify common JCR operations. */
60 public class JcrUtils
implements ArgeoJcrConstants
{
61 private final static Log log
= LogFactory
.getLog(JcrUtils
.class);
64 * Not complete yet. See
65 * http://www.day.com/specs/jcr/2.0/3_Repository_Model.html#3.2.2%20Local
68 public final static char[] INVALID_NAME_CHARACTERS
= { '/', ':', '[', ']',
74 /** Prevents instantiation */
79 * Queries one single node.
81 * @return one single node or null if none was found
82 * @throws ArgeoException
83 * if more than one node was found
85 public static Node
querySingleNode(Query query
) {
86 NodeIterator nodeIterator
;
88 QueryResult queryResult
= query
.execute();
89 nodeIterator
= queryResult
.getNodes();
90 } catch (RepositoryException e
) {
91 throw new ArgeoException("Cannot execute query " + query
, e
);
94 if (nodeIterator
.hasNext())
95 node
= nodeIterator
.nextNode();
99 if (nodeIterator
.hasNext())
100 throw new ArgeoException("Query returned more than one node.");
104 /** Retrieves the parent path of the provided path */
105 public static String
parentPath(String path
) {
106 if (path
.equals("/"))
107 throw new ArgeoException("Root path '/' has no parent path");
108 if (path
.charAt(0) != '/')
109 throw new ArgeoException("Path " + path
+ " must start with a '/'");
111 if (pathT
.charAt(pathT
.length() - 1) == '/')
112 pathT
= pathT
.substring(0, pathT
.length() - 2);
114 int index
= pathT
.lastIndexOf('/');
115 return pathT
.substring(0, index
);
118 /** The provided data as a path ('/' at the end, not the beginning) */
119 public static String
dateAsPath(Calendar cal
) {
120 return dateAsPath(cal
, false);
124 * Creates a deep path based on a URL:
125 * http://subdomain.example.com/to/content?args =>
126 * com/example/subdomain/to/content
128 public static String
urlAsPath(String url
) {
130 URL u
= new URL(url
);
131 StringBuffer path
= new StringBuffer(url
.length());
133 path
.append(hostAsPath(u
.getHost()));
134 // we don't put port since it may not always be there and may change
135 path
.append(u
.getPath());
136 return path
.toString();
137 } catch (MalformedURLException e
) {
138 throw new ArgeoException("Cannot generate URL path for " + url
, e
);
143 * Creates a path from a FQDN, inverting the order of the component:
144 * www.argeo.org => org.argeo.www
146 public static String
hostAsPath(String host
) {
147 StringBuffer path
= new StringBuffer(host
.length());
148 String
[] hostTokens
= host
.split("\\.");
149 for (int i
= hostTokens
.length
- 1; i
>= 0; i
--) {
150 path
.append(hostTokens
[i
]);
154 return path
.toString();
158 * The provided data as a path ('/' at the end, not the beginning)
163 * whether to add hour as well
165 public static String
dateAsPath(Calendar cal
, Boolean addHour
) {
166 StringBuffer buf
= new StringBuffer(14);
168 buf
.append(cal
.get(Calendar
.YEAR
));
171 int month
= cal
.get(Calendar
.MONTH
) + 1;
178 int day
= cal
.get(Calendar
.DAY_OF_MONTH
);
186 int hour
= cal
.get(Calendar
.HOUR_OF_DAY
);
193 return buf
.toString();
197 /** Converts in one call a string into a gregorian calendar. */
198 public static Calendar
parseCalendar(DateFormat dateFormat
, String value
) {
200 Date date
= dateFormat
.parse(value
);
201 Calendar calendar
= new GregorianCalendar();
202 calendar
.setTime(date
);
204 } catch (ParseException e
) {
205 throw new ArgeoException("Cannot parse " + value
206 + " with date format " + dateFormat
, e
);
211 /** The last element of a path. */
212 public static String
lastPathElement(String path
) {
213 if (path
.charAt(path
.length() - 1) == '/')
214 throw new ArgeoException("Path " + path
+ " cannot end with '/'");
215 int index
= path
.lastIndexOf('/');
217 throw new ArgeoException("Cannot find last path element for "
219 return path
.substring(index
+ 1);
222 /** Creates the nodes making path, if they don't exist. */
223 public static Node
mkdirs(Session session
, String path
) {
224 return mkdirs(session
, path
, null, null, false);
228 * @deprecated use {@link #mkdirs(Session, String, String, String, Boolean)}
232 public static Node
mkdirs(Session session
, String path
, String type
,
233 Boolean versioning
) {
234 return mkdirs(session
, path
, type
, type
, false);
239 * the type of the leaf node
241 public static Node
mkdirs(Session session
, String path
, String type
) {
242 return mkdirs(session
, path
, type
, null, false);
246 * Creates the nodes making path, if they don't exist. This is up to the
247 * caller to save the session.
249 public static Node
mkdirs(Session session
, String path
, String type
,
250 String intermediaryNodeType
, Boolean versioning
) {
252 if (path
.equals('/'))
253 return session
.getRootNode();
255 if (session
.itemExists(path
)) {
256 Node node
= session
.getNode(path
);
259 && !type
.equals(node
.getPrimaryNodeType().getName()))
260 throw new ArgeoException("Node " + node
261 + " exists but is of type "
262 + node
.getPrimaryNodeType().getName()
263 + " not of type " + type
);
264 // TODO: check versioning
268 StringTokenizer st
= new StringTokenizer(path
, "/");
269 StringBuffer current
= new StringBuffer("/");
270 Node currentNode
= session
.getRootNode();
271 while (st
.hasMoreTokens()) {
272 String part
= st
.nextToken();
273 current
.append(part
).append('/');
274 if (!session
.itemExists(current
.toString())) {
275 if (!st
.hasMoreTokens() && type
!= null)
276 currentNode
= currentNode
.addNode(part
, type
);
277 else if (st
.hasMoreTokens() && intermediaryNodeType
!= null)
278 currentNode
= currentNode
.addNode(part
,
279 intermediaryNodeType
);
281 currentNode
= currentNode
.addNode(part
);
283 currentNode
.addMixin(NodeType
.MIX_VERSIONABLE
);
284 if (log
.isTraceEnabled())
285 log
.debug("Added folder " + part
+ " as " + current
);
287 currentNode
= (Node
) session
.getItem(current
.toString());
292 } catch (RepositoryException e
) {
293 throw new ArgeoException("Cannot mkdirs " + path
, e
);
298 * Safe and repository implementation independent registration of a
301 public static void registerNamespaceSafely(Session session
, String prefix
,
304 registerNamespaceSafely(session
.getWorkspace()
305 .getNamespaceRegistry(), prefix
, uri
);
306 } catch (RepositoryException e
) {
307 throw new ArgeoException("Cannot find namespace registry", e
);
312 * Safe and repository implementation independent registration of a
315 public static void registerNamespaceSafely(NamespaceRegistry nr
,
316 String prefix
, String uri
) {
318 String
[] prefixes
= nr
.getPrefixes();
319 for (String pref
: prefixes
)
320 if (pref
.equals(prefix
)) {
321 String registeredUri
= nr
.getURI(pref
);
322 if (!registeredUri
.equals(uri
))
323 throw new ArgeoException("Prefix " + pref
324 + " already registered for URI "
326 + " which is different from provided URI "
331 nr
.registerNamespace(prefix
, uri
);
332 } catch (RepositoryException e
) {
333 throw new ArgeoException("Cannot register namespace " + uri
334 + " under prefix " + prefix
, e
);
338 /** Recursively outputs the contents of the given node. */
339 public static void debug(Node node
) {
343 /** Recursively outputs the contents of the given node. */
344 public static void debug(Node node
, Log log
) {
346 // First output the node path
347 log
.debug(node
.getPath());
348 // Skip the virtual (and large!) jcr:system subtree
349 if (node
.getName().equals("jcr:system")) {
353 // Then the children nodes (recursive)
354 NodeIterator it
= node
.getNodes();
355 while (it
.hasNext()) {
356 Node childNode
= it
.nextNode();
360 // Then output the properties
361 PropertyIterator properties
= node
.getProperties();
362 // log.debug("Property are : ");
364 while (properties
.hasNext()) {
365 Property property
= properties
.nextProperty();
366 if (property
.getDefinition().isMultiple()) {
367 // A multi-valued property, print all values
368 Value
[] values
= property
.getValues();
369 for (int i
= 0; i
< values
.length
; i
++) {
370 log
.debug(property
.getPath() + "="
371 + values
[i
].getString());
374 // A single-valued property
375 log
.debug(property
.getPath() + "=" + property
.getString());
378 } catch (Exception e
) {
379 log
.error("Could not debug " + node
, e
);
385 * Copies recursively the content of a node to another one. Do NOT copy the
386 * property values of {@link NodeType#MIX_CREATED} and
387 * {@link NodeType#MIX_LAST_MODIFIED}, but update the
388 * {@link Property#JCR_LAST_MODIFIED} and
389 * {@link Property#JCR_LAST_MODIFIED_BY} properties if the target node has
390 * the {@link NodeType#MIX_LAST_MODIFIED} mixin.
392 public static void copy(Node fromNode
, Node toNode
) {
394 // process properties
395 PropertyIterator pit
= fromNode
.getProperties();
396 properties
: while (pit
.hasNext()) {
397 Property fromProperty
= pit
.nextProperty();
398 String propertyName
= fromProperty
.getName();
399 if (toNode
.hasProperty(propertyName
)
400 && toNode
.getProperty(propertyName
).getDefinition()
404 if (fromProperty
.getDefinition().isProtected())
407 if (propertyName
.equals("jcr:created")
408 || propertyName
.equals("jcr:createdBy")
409 || propertyName
.equals("jcr:lastModified")
410 || propertyName
.equals("jcr:lastModifiedBy"))
413 if (fromProperty
.isMultiple()) {
414 toNode
.setProperty(propertyName
, fromProperty
.getValues());
416 toNode
.setProperty(propertyName
, fromProperty
.getValue());
420 // update jcr:lastModified and jcr:lastModifiedBy in toNode in case
421 // they existed, before adding the mixins
422 updateLastModified(toNode
);
425 for (NodeType mixinType
: fromNode
.getMixinNodeTypes()) {
426 toNode
.addMixin(mixinType
.getName());
429 // process children nodes
430 NodeIterator nit
= fromNode
.getNodes();
431 while (nit
.hasNext()) {
432 Node fromChild
= nit
.nextNode();
433 Integer index
= fromChild
.getIndex();
434 String nodeRelPath
= fromChild
.getName() + "[" + index
+ "]";
436 if (toNode
.hasNode(nodeRelPath
))
437 toChild
= toNode
.getNode(nodeRelPath
);
439 toChild
= toNode
.addNode(fromChild
.getName(), fromChild
440 .getPrimaryNodeType().getName());
441 copy(fromChild
, toChild
);
443 } catch (RepositoryException e
) {
444 throw new ArgeoException("Cannot copy " + fromNode
+ " to "
450 * Check whether all first-level properties (except jcr:* properties) are
451 * equal. Skip jcr:* properties
453 public static Boolean
allPropertiesEquals(Node reference
, Node observed
,
454 Boolean onlyCommonProperties
) {
456 PropertyIterator pit
= reference
.getProperties();
457 props
: while (pit
.hasNext()) {
458 Property propReference
= pit
.nextProperty();
459 String propName
= propReference
.getName();
460 if (propName
.startsWith("jcr:"))
463 if (!observed
.hasProperty(propName
))
464 if (onlyCommonProperties
)
468 // TODO: deal with multiple property values?
469 if (!observed
.getProperty(propName
).getValue()
470 .equals(propReference
.getValue()))
474 } catch (RepositoryException e
) {
475 throw new ArgeoException("Cannot check all properties equals of "
476 + reference
+ " and " + observed
, e
);
480 public static Map
<String
, PropertyDiff
> diffProperties(Node reference
,
482 Map
<String
, PropertyDiff
> diffs
= new TreeMap
<String
, PropertyDiff
>();
483 diffPropertiesLevel(diffs
, null, reference
, observed
);
488 * Compare the properties of two nodes. Recursivity to child nodes is not
489 * yet supported. Skip jcr:* properties.
491 static void diffPropertiesLevel(Map
<String
, PropertyDiff
> diffs
,
492 String baseRelPath
, Node reference
, Node observed
) {
494 // check removed and modified
495 PropertyIterator pit
= reference
.getProperties();
496 props
: while (pit
.hasNext()) {
497 Property p
= pit
.nextProperty();
498 String name
= p
.getName();
499 if (name
.startsWith("jcr:"))
502 if (!observed
.hasProperty(name
)) {
503 String relPath
= propertyRelPath(baseRelPath
, name
);
504 PropertyDiff pDiff
= new PropertyDiff(PropertyDiff
.REMOVED
,
505 relPath
, p
.getValue(), null);
506 diffs
.put(relPath
, pDiff
);
510 Value referenceValue
= p
.getValue();
511 Value newValue
= observed
.getProperty(name
).getValue();
512 if (!referenceValue
.equals(newValue
)) {
513 String relPath
= propertyRelPath(baseRelPath
, name
);
514 PropertyDiff pDiff
= new PropertyDiff(
515 PropertyDiff
.MODIFIED
, relPath
, referenceValue
,
517 diffs
.put(relPath
, pDiff
);
522 pit
= observed
.getProperties();
523 props
: while (pit
.hasNext()) {
524 Property p
= pit
.nextProperty();
525 String name
= p
.getName();
526 if (name
.startsWith("jcr:"))
528 if (!reference
.hasProperty(name
)) {
529 String relPath
= propertyRelPath(baseRelPath
, name
);
530 PropertyDiff pDiff
= new PropertyDiff(PropertyDiff
.ADDED
,
531 relPath
, null, p
.getValue());
532 diffs
.put(relPath
, pDiff
);
535 } catch (RepositoryException e
) {
536 throw new ArgeoException("Cannot diff " + reference
+ " and "
542 * Compare only a restricted list of properties of two nodes. No
546 public static Map
<String
, PropertyDiff
> diffProperties(Node reference
,
547 Node observed
, List
<String
> properties
) {
548 Map
<String
, PropertyDiff
> diffs
= new TreeMap
<String
, PropertyDiff
>();
550 Iterator
<String
> pit
= properties
.iterator();
552 props
: while (pit
.hasNext()) {
553 String name
= pit
.next();
554 if (!reference
.hasProperty(name
)) {
555 if (!observed
.hasProperty(name
))
557 Value val
= observed
.getProperty(name
).getValue();
559 // empty String but not null
560 if ("".equals(val
.getString()))
562 } catch (Exception e
) {
563 // not parseable as String, silent
565 PropertyDiff pDiff
= new PropertyDiff(PropertyDiff
.ADDED
,
567 diffs
.put(name
, pDiff
);
568 } else if (!observed
.hasProperty(name
)) {
569 PropertyDiff pDiff
= new PropertyDiff(PropertyDiff
.REMOVED
,
570 name
, reference
.getProperty(name
).getValue(), null);
571 diffs
.put(name
, pDiff
);
573 Value referenceValue
= reference
.getProperty(name
)
575 Value newValue
= observed
.getProperty(name
).getValue();
576 if (!referenceValue
.equals(newValue
)) {
577 PropertyDiff pDiff
= new PropertyDiff(
578 PropertyDiff
.MODIFIED
, name
, referenceValue
,
580 diffs
.put(name
, pDiff
);
584 } catch (RepositoryException e
) {
585 throw new ArgeoException("Cannot diff " + reference
+ " and "
591 /** Builds a property relPath to be used in the diff. */
592 private static String
propertyRelPath(String baseRelPath
,
593 String propertyName
) {
594 if (baseRelPath
== null)
597 return baseRelPath
+ '/' + propertyName
;
601 * Normalizes a name so that it can be stored in contexts not supporting
602 * names with ':' (typically databases). Replaces ':' by '_'.
604 public static String
normalize(String name
) {
605 return name
.replace(':', '_');
609 * Replaces characters which are invalid in a JCR name by '_'. Currently not
612 * @see JcrUtils#INVALID_NAME_CHARACTERS
614 public static String
replaceInvalidChars(String name
) {
615 return replaceInvalidChars(name
, '_');
619 * Replaces characters which are invalid in a JCR name. Currently not
622 * @see JcrUtils#INVALID_NAME_CHARACTERS
624 public static String
replaceInvalidChars(String name
, char replacement
) {
625 boolean modified
= false;
626 char[] arr
= name
.toCharArray();
627 for (int i
= 0; i
< arr
.length
; i
++) {
629 invalid
: for (char invalid
: INVALID_NAME_CHARACTERS
) {
631 arr
[i
] = replacement
;
638 return new String(arr
);
640 // do not create new object if unnecessary
645 * Removes forbidden characters from a path, replacing them with '_'
647 * @deprecated use {@link #replaceInvalidChars(String)} instead
649 public static String
removeForbiddenCharacters(String str
) {
650 return str
.replace('[', '_').replace(']', '_').replace('/', '_')
655 /** Cleanly disposes a {@link Binary} even if it is null. */
656 public static void closeQuietly(Binary binary
) {
663 * Creates depth from a string (typically a username) by adding levels based
664 * on its first characters: "aBcD",2 => a/aB
666 public static String
firstCharsToPath(String str
, Integer nbrOfChars
) {
667 if (str
.length() < nbrOfChars
)
668 throw new ArgeoException("String " + str
669 + " length must be greater or equal than " + nbrOfChars
);
670 StringBuffer path
= new StringBuffer("");
671 StringBuffer curr
= new StringBuffer("");
672 for (int i
= 0; i
< nbrOfChars
; i
++) {
673 curr
.append(str
.charAt(i
));
675 if (i
< nbrOfChars
- 1)
678 return path
.toString();
682 * Wraps the call to the repository factory based on parameter
683 * {@link ArgeoJcrConstants#JCR_REPOSITORY_ALIAS} in order to simplify it
684 * and protect against future API changes.
686 public static Repository
getRepositoryByAlias(
687 RepositoryFactory repositoryFactory
, String alias
) {
689 Map
<String
, String
> parameters
= new HashMap
<String
, String
>();
690 parameters
.put(JCR_REPOSITORY_ALIAS
, alias
);
691 return repositoryFactory
.getRepository(parameters
);
692 } catch (RepositoryException e
) {
693 throw new ArgeoException(
694 "Unexpected exception when trying to retrieve repository with alias "
700 * Wraps the call to the repository factory based on parameter
701 * {@link ArgeoJcrConstants#JCR_REPOSITORY_URI} in order to simplify it and
702 * protect against future API changes.
704 public static Repository
getRepositoryByUri(
705 RepositoryFactory repositoryFactory
, String uri
) {
707 Map
<String
, String
> parameters
= new HashMap
<String
, String
>();
708 parameters
.put(JCR_REPOSITORY_URI
, uri
);
709 return repositoryFactory
.getRepository(parameters
);
710 } catch (RepositoryException e
) {
711 throw new ArgeoException(
712 "Unexpected exception when trying to retrieve repository with uri "
718 * Discards the current changes in the session attached to this node. To be
719 * used typically in a catch block.
721 * @see #discardQuietly(Session)
723 public static void discardUnderlyingSessionQuietly(Node node
) {
725 discardQuietly(node
.getSession());
726 } catch (RepositoryException e
) {
727 log
.warn("Cannot quietly discard session of node " + node
+ ": "
733 * Discards the current changes in a session by calling
734 * {@link Session#refresh(boolean)} with <code>false</code>, only logging
735 * potential errors when doing so. To be used typically in a catch block.
737 public static void discardQuietly(Session session
) {
740 session
.refresh(false);
741 } catch (RepositoryException e
) {
742 log
.warn("Cannot quietly discard session " + session
+ ": "
747 /** Logs out the session, not throwing any exception, even if it is null. */
748 public static void logoutQuietly(Session session
) {
751 if (session
.isLive())
753 } catch (Exception e
) {
758 /** Returns the home node of the session user or null if none was found. */
759 public static Node
getUserHome(Session session
) {
760 String userID
= session
.getUserID();
761 return getUserHome(session
, userID
);
765 * Returns user home has path, embedding exceptions. Contrary to
766 * {@link #getUserHome(Session)}, it never returns null but throws and
767 * exception if not found.
769 public static String
getUserHomePath(Session session
) {
770 String userID
= session
.getUserID();
772 Node userHome
= getUserHome(session
, userID
);
773 if (userHome
!= null)
774 return userHome
.getPath();
776 throw new ArgeoException("No home registered for " + userID
);
777 } catch (RepositoryException e
) {
778 throw new ArgeoException("Cannot find user home path", e
);
782 /** Get the profile of the user attached to this session. */
783 public static Node
getUserProfile(Session session
) {
784 String userID
= session
.getUserID();
785 return getUserProfile(session
, userID
);
789 * Returns the home node of the session user or null if none was found.
792 * the session to use in order to perform the search, this can be
793 * a session with a different user ID than the one searched,
794 * typically when a system or admin session is used.
796 * the username of the user
798 public static Node
getUserHome(Session session
, String username
) {
800 QueryObjectModelFactory qomf
= session
.getWorkspace()
801 .getQueryManager().getQOMFactory();
803 // query the user home for this user id
804 Selector userHomeSel
= qomf
.selector(ArgeoTypes
.ARGEO_USER_HOME
,
806 DynamicOperand userIdDop
= qomf
.propertyValue("userHome",
807 ArgeoNames
.ARGEO_USER_ID
);
808 StaticOperand userIdSop
= qomf
.literal(session
.getValueFactory()
809 .createValue(username
));
810 Constraint constraint
= qomf
.comparison(userIdDop
,
811 QueryObjectModelFactory
.JCR_OPERATOR_EQUAL_TO
, userIdSop
);
812 Query query
= qomf
.createQuery(userHomeSel
, constraint
, null, null);
813 Node userHome
= JcrUtils
.querySingleNode(query
);
815 } catch (RepositoryException e
) {
816 throw new ArgeoException("Cannot find home for user " + username
, e
);
820 public static Node
getUserProfile(Session session
, String username
) {
822 QueryObjectModelFactory qomf
= session
.getWorkspace()
823 .getQueryManager().getQOMFactory();
824 Selector sel
= qomf
.selector(ArgeoTypes
.ARGEO_USER_PROFILE
,
826 DynamicOperand userIdDop
= qomf
.propertyValue("userProfile",
827 ArgeoNames
.ARGEO_USER_ID
);
828 StaticOperand userIdSop
= qomf
.literal(session
.getValueFactory()
829 .createValue(username
));
830 Constraint constraint
= qomf
.comparison(userIdDop
,
831 QueryObjectModelFactory
.JCR_OPERATOR_EQUAL_TO
, userIdSop
);
832 Query query
= qomf
.createQuery(sel
, constraint
, null, null);
833 Node userHome
= JcrUtils
.querySingleNode(query
);
835 } catch (RepositoryException e
) {
836 throw new ArgeoException(
837 "Cannot find profile for user " + username
, e
);
841 /** Creates an Argeo user home. */
842 public static Node
createUserHome(Session session
, String homeBasePath
,
846 throw new ArgeoException("Session is null");
847 if (session
.hasPendingChanges())
848 throw new ArgeoException(
849 "Session has pending changes, save them first");
851 String homePath
= homeBasePath
+ '/'
852 + firstCharsToPath(username
, 2) + '/' + username
;
854 if (session
.itemExists(homePath
)) {
856 throw new ArgeoException(
857 "Trying to create a user home that already exists");
858 } catch (Exception e
) {
859 // we use this workaround to be sure to get the stack trace
860 // to identify the sink of the bug.
861 log
.warn("trying to create an already existing userHome at path:"
862 + homePath
+ ". Stack trace : ");
867 Node userHome
= JcrUtils
.mkdirs(session
, homePath
);
869 if (userHome
.hasNode(ArgeoNames
.ARGEO_PROFILE
)) {
870 log
.warn("userProfile node already exists for userHome path: "
871 + homePath
+ ". We do not add a new one");
873 userProfile
= userHome
.addNode(ArgeoNames
.ARGEO_PROFILE
);
874 userProfile
.addMixin(ArgeoTypes
.ARGEO_USER_PROFILE
);
875 userProfile
.setProperty(ArgeoNames
.ARGEO_USER_ID
, username
);
877 // we need to save the profile before adding the user home type
879 userHome
.addMixin(ArgeoTypes
.ARGEO_USER_HOME
);
881 // http://jackrabbit.510166.n4.nabble.com/Jackrabbit-2-0-beta-6-Problem-adding-a-Mixin-type-with-mandatory-properties-after-setting-propertiesn-td1290332.html
882 userHome
.setProperty(ArgeoNames
.ARGEO_USER_ID
, username
);
885 } catch (RepositoryException e
) {
886 discardQuietly(session
);
887 throw new ArgeoException("Cannot create home node for user "
893 * Quietly unregisters an {@link EventListener} from the udnerlying
894 * workspace of this node.
896 public static void unregisterQuietly(Node node
, EventListener eventListener
) {
898 unregisterQuietly(node
.getSession().getWorkspace(), eventListener
);
899 } catch (RepositoryException e
) {
901 if (log
.isTraceEnabled())
902 log
.trace("Could not unregister event listener "
907 /** Quietly unregisters an {@link EventListener} from this workspace */
908 public static void unregisterQuietly(Workspace workspace
,
909 EventListener eventListener
) {
910 if (eventListener
== null)
913 workspace
.getObservationManager()
914 .removeEventListener(eventListener
);
915 } catch (RepositoryException e
) {
917 if (log
.isTraceEnabled())
918 log
.trace("Could not unregister event listener "
924 * If this node is has the {@link NodeType#MIX_LAST_MODIFIED} mixin, it
925 * updates the {@link Property#JCR_LAST_MODIFIED} property with the current
926 * time and the {@link Property#JCR_LAST_MODIFIED_BY} property with the
927 * underlying session user id. In Jackrabbit 2.x, <a
928 * href="https://issues.apache.org/jira/browse/JCR-2233">these properties
929 * are not automatically updated</a>, hence the need for manual update. The
930 * session is not saved.
932 public static void updateLastModified(Node node
) {
934 if (node
.isNodeType(NodeType
.MIX_LAST_MODIFIED
)) {
935 node
.setProperty(Property
.JCR_LAST_MODIFIED
,
936 new GregorianCalendar());
937 node
.setProperty(Property
.JCR_LAST_MODIFIED_BY
, node
938 .getSession().getUserID());
940 } catch (RepositoryException e
) {
941 throw new ArgeoException("Cannot update last modified", e
);