+
+ /**
+ * Wraps the call to the repository factory based on parameter
+ * {@link ArgeoJcrConstants#JCR_REPOSITORY_ALIAS} in order to simplify it
+ * and protect against future API changes.
+ */
+ public static Repository getRepositoryByAlias(
+ RepositoryFactory repositoryFactory, String alias) {
+ try {
+ Map<String, String> parameters = new HashMap<String, String>();
+ parameters.put(JCR_REPOSITORY_ALIAS, alias);
+ return repositoryFactory.getRepository(parameters);
+ } catch (RepositoryException e) {
+ throw new ArgeoException(
+ "Unexpected exception when trying to retrieve repository with alias "
+ + alias, e);
+ }
+ }
+
+ /**
+ * Wraps the call to the repository factory based on parameter
+ * {@link ArgeoJcrConstants#JCR_REPOSITORY_URI} in order to simplify it and
+ * protect against future API changes.
+ */
+ public static Repository getRepositoryByUri(
+ RepositoryFactory repositoryFactory, String uri) {
+ try {
+ Map<String, String> parameters = new HashMap<String, String>();
+ parameters.put(JCR_REPOSITORY_URI, uri);
+ return repositoryFactory.getRepository(parameters);
+ } catch (RepositoryException e) {
+ throw new ArgeoException(
+ "Unexpected exception when trying to retrieve repository with uri "
+ + uri, e);
+ }
+ }
+
+ /**
+ * Discards the current changes in the session attached to this node. To be
+ * used typically in a catch block.
+ *
+ * @see #discardQuietly(Session)
+ */
+ public static void discardUnderlyingSessionQuietly(Node node) {
+ try {
+ discardQuietly(node.getSession());
+ } catch (RepositoryException e) {
+ log.warn("Cannot quietly discard session of node " + node + ": "
+ + e.getMessage());
+ }
+ }
+
+ /**
+ * Discards the current changes in a session by calling
+ * {@link Session#refresh(boolean)} with <code>false</code>, only logging
+ * potential errors when doing so. To be used typically in a catch block.
+ */
+ public static void discardQuietly(Session session) {
+ try {
+ if (session != null)
+ session.refresh(false);
+ } catch (RepositoryException e) {
+ log.warn("Cannot quietly discard session " + session + ": "
+ + e.getMessage());
+ }
+ }
+
+ /** Logs out the session, not throwing any exception, even if it is null. */
+ public static void logoutQuietly(Session session) {
+ try {
+ if (session != null)
+ if (session.isLive())
+ session.logout();
+ } catch (Exception e) {
+ // silent
+ }
+ }
+
+ /**
+ * Convenient method to add a listener. uuids passed as null, deep=true,
+ * local=true, only one node type
+ */
+ public static void addListener(Session session, EventListener listener,
+ int eventTypes, String basePath, String nodeType) {
+ try {
+ session.getWorkspace()
+ .getObservationManager()
+ .addEventListener(listener, eventTypes, basePath, true,
+ null, new String[] { nodeType }, true);
+ } catch (RepositoryException e) {
+ throw new ArgeoException("Cannot add JCR listener " + listener
+ + " to session " + session, e);
+ }
+ }
+
+ /** Removes a listener without throwing exception */
+ public static void removeListenerQuietly(Session session,
+ EventListener listener) {
+ if (session == null || !session.isLive())
+ return;
+ try {
+ session.getWorkspace().getObservationManager()
+ .removeEventListener(listener);
+ } catch (RepositoryException e) {
+ // silent
+ }
+ }
+
+ /** Returns the home node of the session user or null if none was found. */
+ public static Node getUserHome(Session session) {
+ String userID = session.getUserID();
+ return getUserHome(session, userID);
+ }
+
+ /** User home path is NOT configurable */
+ public static String getUserHomePath(String username) {
+ String homeBasePath = DEFAULT_HOME_BASE_PATH;
+ return homeBasePath + '/' + firstCharsToPath(username, 2) + '/'
+ + username;
+ }
+
+ /**
+ * Returns the home node of the session user or null if none was found.
+ *
+ * @param session
+ * the session to use in order to perform the search, this can be
+ * a session with a different user ID than the one searched,
+ * typically when a system or admin session is used.
+ * @param username
+ * the username of the user
+ */
+ public static Node getUserHome(Session session, String username) {
+ try {
+ String homePath = getUserHomePath(username);
+ return session.itemExists(homePath) ? session.getNode(homePath)
+ : null;
+ // kept for example of QOM queries
+ // QueryObjectModelFactory qomf = session.getWorkspace()
+ // .getQueryManager().getQOMFactory();
+ // Selector userHomeSel = qomf.selector(ArgeoTypes.ARGEO_USER_HOME,
+ // "userHome");
+ // DynamicOperand userIdDop = qomf.propertyValue("userHome",
+ // ArgeoNames.ARGEO_USER_ID);
+ // StaticOperand userIdSop = qomf.literal(session.getValueFactory()
+ // .createValue(username));
+ // Constraint constraint = qomf.comparison(userIdDop,
+ // QueryObjectModelFactory.JCR_OPERATOR_EQUAL_TO, userIdSop);
+ // Query query = qomf.createQuery(userHomeSel, constraint, null,
+ // null);
+ // Node userHome = JcrUtils.querySingleNode(query);
+ } catch (RepositoryException e) {
+ throw new ArgeoException("Cannot find home for user " + username, e);
+ }
+ }
+
+ /**
+ * Creates an Argeo user home, does nothing if it already exists. Session is
+ * NOT saved.
+ */
+ public static Node createUserHomeIfNeeded(Session session, String username) {
+ try {
+ String homePath = getUserHomePath(username);
+ if (session.itemExists(homePath))
+ return session.getNode(homePath);
+ else {
+ Node userHome = JcrUtils.mkdirs(session, homePath);
+ userHome.addMixin(ArgeoTypes.ARGEO_USER_HOME);
+ userHome.setProperty(ArgeoNames.ARGEO_USER_ID, username);
+ return userHome;
+ }
+ } catch (RepositoryException e) {
+ discardQuietly(session);
+ throw new ArgeoException("Cannot create home for " + username
+ + " in workspace " + session.getWorkspace().getName(), e);
+ }
+ }
+
+ /**
+ * Creates a user profile in the home of this user. Creates the home if
+ * needed, but throw an exception if a profile already exists. The session
+ * is not saved and the node is in a checkedOut state (that is, it requires
+ * a subsequent checkin after saving the session).
+ */
+ public static Node createUserProfile(Session session, String username) {
+ try {
+ Node userHome = createUserHomeIfNeeded(session, username);
+ if (userHome.hasNode(ArgeoNames.ARGEO_PROFILE))
+ throw new ArgeoException(
+ "There is already a user profile under " + userHome);
+ Node userProfile = userHome.addNode(ArgeoNames.ARGEO_PROFILE);
+ userProfile.addMixin(ArgeoTypes.ARGEO_USER_PROFILE);
+ userProfile.setProperty(ArgeoNames.ARGEO_USER_ID, username);
+ userProfile.setProperty(ArgeoNames.ARGEO_ENABLED, true);
+ userProfile.setProperty(ArgeoNames.ARGEO_ACCOUNT_NON_EXPIRED, true);
+ userProfile.setProperty(ArgeoNames.ARGEO_ACCOUNT_NON_LOCKED, true);
+ userProfile.setProperty(ArgeoNames.ARGEO_CREDENTIALS_NON_EXPIRED,
+ true);
+ return userProfile;
+ } catch (RepositoryException e) {
+ discardQuietly(session);
+ throw new ArgeoException("Cannot create user profile for "
+ + username + " in workspace "
+ + session.getWorkspace().getName(), e);
+ }
+ }
+
+ /**
+ * Create user profile if needed, the session IS saved.
+ *
+ * @return the user profile
+ */
+ public static Node createUserProfileIfNeeded(Session securitySession,
+ String username) {
+ try {
+ Node userHome = JcrUtils.createUserHomeIfNeeded(securitySession,
+ username);
+ Node userProfile = userHome.hasNode(ArgeoNames.ARGEO_PROFILE) ? userHome
+ .getNode(ArgeoNames.ARGEO_PROFILE) : JcrUtils
+ .createUserProfile(securitySession, username);
+ if (securitySession.hasPendingChanges())
+ securitySession.save();
+ VersionManager versionManager = securitySession.getWorkspace()
+ .getVersionManager();
+ if (versionManager.isCheckedOut(userProfile.getPath()))
+ versionManager.checkin(userProfile.getPath());
+ return userProfile;
+ } catch (RepositoryException e) {
+ discardQuietly(securitySession);
+ throw new ArgeoException("Cannot create user profile for "
+ + username + " in workspace "
+ + securitySession.getWorkspace().getName(), e);
+ }
+ }
+
+ /** Creates an Argeo user home. */
+ // public static Node createUserHome(Session session, String homeBasePath,
+ // String username) {
+ // try {
+ // if (session == null)
+ // throw new ArgeoException("Session is null");
+ // if (session.hasPendingChanges())
+ // throw new ArgeoException(
+ // "Session has pending changes, save them first");
+ //
+ // String homePath = getUserHomePath(username);
+ //
+ // if (session.itemExists(homePath)) {
+ // try {
+ // throw new ArgeoException(
+ // "Trying to create a user home that already exists");
+ // } catch (Exception e) {
+ // // we use this workaround to be sure to get the stack trace
+ // // to identify the sink of the bug.
+ // log.warn("trying to create an already existing userHome at path:"
+ // + homePath + ". Stack trace : ");
+ // e.printStackTrace();
+ // }
+ // }
+ //
+ // Node userHome = JcrUtils.mkdirs(session, homePath);
+ // Node userProfile;
+ // if (userHome.hasNode(ArgeoNames.ARGEO_PROFILE)) {
+ // log.warn("userProfile node already exists for userHome path: "
+ // + homePath + ". We do not add a new one");
+ // } else {
+ // userProfile = userHome.addNode(ArgeoNames.ARGEO_PROFILE);
+ // userProfile.addMixin(ArgeoTypes.ARGEO_USER_PROFILE);
+ // // session.getWorkspace().getVersionManager()
+ // // .checkout(userProfile.getPath());
+ // userProfile.setProperty(ArgeoNames.ARGEO_USER_ID, username);
+ // session.save();
+ // session.getWorkspace().getVersionManager()
+ // .checkin(userProfile.getPath());
+ // // we need to save the profile before adding the user home type
+ // }
+ // userHome.addMixin(ArgeoTypes.ARGEO_USER_HOME);
+ // // see
+ // //
+ // http://jackrabbit.510166.n4.nabble.com/Jackrabbit-2-0-beta-6-Problem-adding-a-Mixin-type-with-mandatory-properties-after-setting-propertiesn-td1290332.html
+ // userHome.setProperty(ArgeoNames.ARGEO_USER_ID, username);
+ // session.save();
+ // return userHome;
+ // } catch (RepositoryException e) {
+ // discardQuietly(session);
+ // throw new ArgeoException("Cannot create home node for user "
+ // + username, e);
+ // }
+ // }
+
+ /**
+ * Returns user home has path, embedding exceptions. Contrary to
+ * {@link #getUserHome(Session)}, it never returns null but throws and
+ * exception if not found.
+ *
+ * @deprecated use getUserHome() instead, throwing an exception if it
+ * returns null
+ */
+ @Deprecated
+ public static String getUserHomePath(Session session) {
+ String userID = session.getUserID();
+ try {
+ String homePath = getUserHomePath(userID);
+ if (session.itemExists(homePath))
+ return homePath;
+ else
+ throw new ArgeoException("No home registered for " + userID);
+ } catch (RepositoryException e) {
+ throw new ArgeoException("Cannot find user home path", e);
+ }
+ }
+
+ /**
+ * @return null if not found *
+ */
+ public static Node getUserProfile(Session session, String username) {
+ try {
+ Node userHome = getUserHome(session, username);
+ if (userHome == null)
+ return null;
+ if (userHome.hasNode(ArgeoNames.ARGEO_PROFILE))
+ return userHome.getNode(ArgeoNames.ARGEO_PROFILE);
+ else
+ return null;
+ } catch (RepositoryException e) {
+ throw new ArgeoException(
+ "Cannot find profile for user " + username, e);
+ }
+ }
+
+ /**
+ * Get the profile of the user attached to this session.
+ */
+ public static Node getUserProfile(Session session) {
+ String userID = session.getUserID();
+ return getUserProfile(session, userID);
+ }
+
+ /**
+ * Quietly unregisters an {@link EventListener} from the udnerlying
+ * workspace of this node.
+ */
+ public static void unregisterQuietly(Node node, EventListener eventListener) {
+ try {
+ unregisterQuietly(node.getSession().getWorkspace(), eventListener);
+ } catch (RepositoryException e) {
+ // silent
+ if (log.isTraceEnabled())
+ log.trace("Could not unregister event listener "
+ + eventListener);
+ }
+ }
+
+ /** Quietly unregisters an {@link EventListener} from this workspace */
+ public static void unregisterQuietly(Workspace workspace,
+ EventListener eventListener) {
+ if (eventListener == null)
+ return;
+ try {
+ workspace.getObservationManager()
+ .removeEventListener(eventListener);
+ } catch (RepositoryException e) {
+ // silent
+ if (log.isTraceEnabled())
+ log.trace("Could not unregister event listener "
+ + eventListener);
+ }
+ }
+
+ /**
+ * If this node is has the {@link NodeType#MIX_LAST_MODIFIED} mixin, it
+ * updates the {@link Property#JCR_LAST_MODIFIED} property with the current
+ * time and the {@link Property#JCR_LAST_MODIFIED_BY} property with the
+ * underlying session user id. In Jackrabbit 2.x, <a
+ * href="https://issues.apache.org/jira/browse/JCR-2233">these properties
+ * are not automatically updated</a>, hence the need for manual update. The
+ * session is not saved.
+ */
+ public static void updateLastModified(Node node) {
+ try {
+ if (!node.isNodeType(NodeType.MIX_LAST_MODIFIED))
+ node.addMixin(NodeType.MIX_LAST_MODIFIED);
+ node.setProperty(Property.JCR_LAST_MODIFIED,
+ new GregorianCalendar());
+ node.setProperty(Property.JCR_LAST_MODIFIED_BY, node.getSession()
+ .getUserID());
+ } catch (RepositoryException e) {
+ throw new ArgeoException("Cannot update last modified on " + node,
+ e);
+ }
+ }
+
+ /** Update lastModified recursively until this parent. */
+ public static void updateLastModifiedAndParents(Node node, String untilPath) {
+ try {
+ if (!node.getPath().startsWith(untilPath))
+ throw new ArgeoException(node + " is not under " + untilPath);
+ updateLastModified(node);
+ if (!node.getPath().equals(untilPath))
+ updateLastModifiedAndParents(node.getParent(), untilPath);
+ } catch (RepositoryException e) {
+ throw new ArgeoException("Cannot update lastModified from " + node
+ + " until " + untilPath, e);
+ }
+ }
+
+ /**
+ * Returns a String representing the short version (see <a
+ * href="http://jackrabbit.apache.org/node-type-notation.html"> Node type
+ * Notation </a> attributes grammar) of the main business attributes of this
+ * property definition
+ *
+ * @param prop
+ */
+ public static String getPropertyDefinitionAsString(Property prop) {
+ StringBuffer sbuf = new StringBuffer();
+ try {
+ if (prop.getDefinition().isAutoCreated())
+ sbuf.append("a");
+ if (prop.getDefinition().isMandatory())
+ sbuf.append("m");
+ if (prop.getDefinition().isProtected())
+ sbuf.append("p");
+ if (prop.getDefinition().isMultiple())
+ sbuf.append("*");
+ } catch (RepositoryException re) {
+ throw new ArgeoException(
+ "unexpected error while getting property definition as String",
+ re);
+ }
+ return sbuf.toString();
+ }
+
+ /**
+ * Estimate the sub tree size from current node. Computation is based on the
+ * Jcr {@link Property.getLength()} method. Note : it is not the exact size
+ * used on the disk by the current part of the JCR Tree.
+ */
+
+ public static long getNodeApproxSize(Node node) {
+ long curNodeSize = 0;
+ try {
+ PropertyIterator pi = node.getProperties();
+ while (pi.hasNext()) {
+ Property prop = pi.nextProperty();
+ if (prop.isMultiple()) {
+ int nb = prop.getLengths().length;
+ for (int i = 0; i < nb; i++) {
+ curNodeSize += (prop.getLengths()[i] > 0 ? prop
+ .getLengths()[i] : 0);
+ }
+ } else
+ curNodeSize += (prop.getLength() > 0 ? prop.getLength() : 0);
+ }
+
+ NodeIterator ni = node.getNodes();
+ while (ni.hasNext())
+ curNodeSize += getNodeApproxSize(ni.nextNode());
+ return curNodeSize;
+ } catch (RepositoryException re) {
+ throw new ArgeoException(
+ "Unexpected error while recursively determining node size.",
+ re);
+ }
+ }