X-Git-Url: https://git.argeo.org/?a=blobdiff_plain;f=server%2Fruntime%2Forg.argeo.server.jcr%2Fsrc%2Fmain%2Fjava%2Forg%2Fargeo%2Fjcr%2FJcrUtils.java;h=66c6a9388f1f6c1df93e79de91beb6e5a117e832;hb=484dcb1507e4e35cc282e50522ea7eac7e99a7f9;hp=eeaff3a924f505cee38124e3ac85a090dbc5295e;hpb=1c8303618e6865a4b3a8715ee217ed557736d9ae;p=lgpl%2Fargeo-commons.git diff --git a/server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/JcrUtils.java b/server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/JcrUtils.java index eeaff3a92..66c6a9388 100644 --- a/server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/JcrUtils.java +++ b/server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/JcrUtils.java @@ -23,14 +23,15 @@ import java.net.MalformedURLException; import java.net.URL; import java.text.DateFormat; import java.text.ParseException; +import java.util.ArrayList; import java.util.Calendar; +import java.util.Collections; import java.util.Date; import java.util.GregorianCalendar; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.StringTokenizer; import java.util.TreeMap; import javax.jcr.Binary; @@ -50,11 +51,6 @@ import javax.jcr.nodetype.NodeType; import javax.jcr.observation.EventListener; import javax.jcr.query.Query; import javax.jcr.query.QueryResult; -import javax.jcr.query.qom.Constraint; -import javax.jcr.query.qom.DynamicOperand; -import javax.jcr.query.qom.QueryObjectModelFactory; -import javax.jcr.query.qom.Selector; -import javax.jcr.query.qom.StaticOperand; import org.apache.commons.io.IOUtils; import org.apache.commons.logging.Log; @@ -144,6 +140,46 @@ public class JcrUtils implements ArgeoJcrConstants { } } + /** Set the {@link NodeType#NT_ADDRESS} properties based on this URL. */ + public static void urlToAddressProperties(Node node, String url) { + try { + URL u = new URL(url); + node.setProperty(Property.JCR_PROTOCOL, u.getProtocol()); + node.setProperty(Property.JCR_HOST, u.getHost()); + node.setProperty(Property.JCR_PORT, Integer.toString(u.getPort())); + node.setProperty(Property.JCR_PATH, normalizePath(u.getPath())); + } catch (Exception e) { + throw new ArgeoException("Cannot set URL " + url + + " as nt:address properties", e); + } + } + + /** Build URL based on the {@link NodeType#NT_ADDRESS} properties. */ + public static String urlFromAddressProperties(Node node) { + try { + URL u = new URL( + node.getProperty(Property.JCR_PROTOCOL).getString(), node + .getProperty(Property.JCR_HOST).getString(), + (int) node.getProperty(Property.JCR_PORT).getLong(), node + .getProperty(Property.JCR_PATH).getString()); + return u.toString(); + } catch (Exception e) { + throw new ArgeoException( + "Cannot get URL from nt:address properties of " + node, e); + } + } + + /** Make sure that: starts with '/', do not end with '/', do not have '//' */ + public static String normalizePath(String path) { + List tokens = tokenize(path); + StringBuffer buf = new StringBuffer(path.length()); + for (String token : tokens) { + buf.append('/'); + buf.append(token); + } + return buf.toString(); + } + /** * Creates a path from a FQDN, inverting the order of the component: * www.argeo.org => org.argeo.www @@ -268,9 +304,33 @@ public class JcrUtils implements ArgeoJcrConstants { return mkdirs(session, path, type, null, false); } + /** + * Synchronized and save is performed, to avoid race conditions in + * initializers leading to duplicate nodes. + */ + public synchronized static Node mkdirsSafe(Session session, String path, + String type) { + try { + if (session.hasPendingChanges()) + throw new ArgeoException( + "Session has pending changes, save them first."); + Node node = mkdirs(session, path, type); + session.save(); + return node; + } catch (RepositoryException e) { + discardQuietly(session); + throw new ArgeoException("Cannot safely make directories", e); + } + } + + public synchronized static Node mkdirsSafe(Session session, String path) { + return mkdirsSafe(session, path, null); + } + /** * Creates the nodes making path, if they don't exist. This is up to the - * caller to save the session. + * caller to save the session. Use with caution since it can create + * duplicate nodes if used concurrently. */ public static Node mkdirs(Session session, String path, String type, String intermediaryNodeType, Boolean versioning) { @@ -291,16 +351,16 @@ public class JcrUtils implements ArgeoJcrConstants { return node; } - StringTokenizer st = new StringTokenizer(path, "/"); StringBuffer current = new StringBuffer("/"); Node currentNode = session.getRootNode(); - while (st.hasMoreTokens()) { - String part = st.nextToken(); + Iterator it = tokenize(path).iterator(); + while (it.hasNext()) { + String part = it.next(); current.append(part).append('/'); if (!session.itemExists(current.toString())) { - if (!st.hasMoreTokens() && type != null) + if (!it.hasNext() && type != null) currentNode = currentNode.addNode(part, type); - else if (st.hasMoreTokens() && intermediaryNodeType != null) + else if (it.hasNext() && intermediaryNodeType != null) currentNode = currentNode.addNode(part, intermediaryNodeType); else @@ -313,13 +373,47 @@ public class JcrUtils implements ArgeoJcrConstants { currentNode = (Node) session.getItem(current.toString()); } } - // session.save(); return currentNode; } catch (RepositoryException e) { + discardQuietly(session); throw new ArgeoException("Cannot mkdirs " + path, e); + } finally { } } + /** Convert a path to the list of its tokens */ + public static List tokenize(String path) { + List tokens = new ArrayList(); + boolean optimized = false; + if (!optimized) { + String[] rawTokens = path.split("/"); + for (String token : rawTokens) { + if (!token.equals("")) + tokens.add(token); + } + } else { + StringBuffer curr = new StringBuffer(); + char[] arr = path.toCharArray(); + chars: for (int i = 0; i < arr.length; i++) { + char c = arr[i]; + if (c == '/') { + if (i == 0 || (i == arr.length - 1)) + continue chars; + if (curr.length() > 0) { + tokens.add(curr.toString()); + curr = new StringBuffer(); + } + } else + curr.append(c); + } + if (curr.length() > 0) { + tokens.add(curr.toString()); + curr = new StringBuffer(); + } + } + return Collections.unmodifiableList(tokens); + } + /** * Safe and repository implementation independent registration of a * namespace. @@ -533,16 +627,18 @@ public class JcrUtils implements ArgeoJcrConstants { relPath, p.getValue(), null); diffs.put(relPath, pDiff); } else { - if (p.isMultiple()) - continue props; - Value referenceValue = p.getValue(); - Value newValue = observed.getProperty(name).getValue(); - if (!referenceValue.equals(newValue)) { - String relPath = propertyRelPath(baseRelPath, name); - PropertyDiff pDiff = new PropertyDiff( - PropertyDiff.MODIFIED, relPath, referenceValue, - newValue); - diffs.put(relPath, pDiff); + if (p.isMultiple()) { + // FIXME implement multiple + } else { + Value referenceValue = p.getValue(); + Value newValue = observed.getProperty(name).getValue(); + if (!referenceValue.equals(newValue)) { + String relPath = propertyRelPath(baseRelPath, name); + PropertyDiff pDiff = new PropertyDiff( + PropertyDiff.MODIFIED, relPath, + referenceValue, newValue); + diffs.put(relPath, pDiff); + } } } } @@ -554,10 +650,14 @@ public class JcrUtils implements ArgeoJcrConstants { if (name.startsWith("jcr:")) continue props; if (!reference.hasProperty(name)) { - String relPath = propertyRelPath(baseRelPath, name); - PropertyDiff pDiff = new PropertyDiff(PropertyDiff.ADDED, - relPath, null, p.getValue()); - diffs.put(relPath, pDiff); + if (p.isMultiple()) { + // FIXME implement multiple + } else { + String relPath = propertyRelPath(baseRelPath, name); + PropertyDiff pDiff = new PropertyDiff( + PropertyDiff.ADDED, relPath, null, p.getValue()); + diffs.put(relPath, pDiff); + } } } } catch (RepositoryException e) { @@ -820,34 +920,30 @@ public class JcrUtils implements ArgeoJcrConstants { } } - /** 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); - } - - /** - * Returns user home has path, embedding exceptions. Contrary to - * {@link #getUserHome(Session)}, it never returns null but throws and - * exception if not found. - */ - public static String getUserHomePath(Session session) { - String userID = session.getUserID(); + /** Removes a listener without throwing exception */ + public static void removeListenerQuietly(Session session, + EventListener listener) { + if (session == null || !session.isLive()) + return; try { - Node userHome = getUserHome(session, userID); - if (userHome != null) - return userHome.getPath(); - else - throw new ArgeoException("No home registered for " + userID); + session.getWorkspace().getObservationManager() + .removeEventListener(listener); } catch (RepositoryException e) { - throw new ArgeoException("Cannot find user home path", e); + // silent } } - /** Get the profile of the user attached to this session. */ - public static Node getUserProfile(Session session) { + /** 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 getUserProfile(session, userID); + return getUserHome(session, userID); + } + + /** User home path is NOT configurable */ + public static String getUserHomePath(String username) { + String homeBasePath = "/home"; + return homeBasePath + '/' + firstCharsToPath(username, 2) + '/' + + username; } /** @@ -862,98 +958,190 @@ public class JcrUtils implements ArgeoJcrConstants { */ public static Node getUserHome(Session session, String username) { try { - QueryObjectModelFactory qomf = session.getWorkspace() - .getQueryManager().getQOMFactory(); - - // query the user home for this user id - 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); - return userHome; + 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); } } - public static Node getUserProfile(Session session, String username) { + /** + * Creates an Argeo user home, does nothing if it already exists. Session is + * NOT saved. + */ + public static Node createUserHomeIfNeeded(Session session, String username) { try { - QueryObjectModelFactory qomf = session.getWorkspace() - .getQueryManager().getQOMFactory(); - Selector sel = qomf.selector(ArgeoTypes.ARGEO_USER_PROFILE, - "userProfile"); - DynamicOperand userIdDop = qomf.propertyValue("userProfile", - 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(sel, constraint, null, null); - Node userHome = JcrUtils.querySingleNode(query); - return userHome; + 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) { - throw new ArgeoException( - "Cannot find profile for user " + username, e); + discardQuietly(session); + throw new ArgeoException("Cannot create home for " + username + + " in workspace " + session.getWorkspace().getName(), e); } } - /** Creates an Argeo user home. */ - public static Node createUserHome(Session session, String homeBasePath, - String username) { + /** + * 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 { - if (session == null) - throw new ArgeoException("Session is null"); - if (session.hasPendingChanges()) + Node userHome = createUserHomeIfNeeded(session, username); + if (userHome.hasNode(ArgeoNames.ARGEO_PROFILE)) throw new ArgeoException( - "Session has pending changes, save them first"); - - String homePath = homeBasePath + '/' - + firstCharsToPath(username, 2) + '/' + 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); - userProfile.setProperty(ArgeoNames.ARGEO_USER_ID, username); - session.save(); - // 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; + "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 home node for user " - + username, e); + throw new ArgeoException("Cannot create home for " + username + + " in workspace " + session.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 * + * @deprecated will soon be removed. Call instead + * getUserHome().getNode(ARGEO_PROFILE) on the security + * workspace. + */ + @Deprecated + 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. + * + * @deprecated will soon be removed. Call instead + * getUserHome().getNode(ARGEO_PROFILE) on the security + * workspace. + */ + @Deprecated + 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. @@ -996,14 +1184,29 @@ public class JcrUtils implements ArgeoJcrConstants { */ public static void updateLastModified(Node node) { try { - if (node.isNodeType(NodeType.MIX_LAST_MODIFIED)) { - node.setProperty(Property.JCR_LAST_MODIFIED, - new GregorianCalendar()); - node.setProperty(Property.JCR_LAST_MODIFIED_BY, node - .getSession().getUserID()); - } + 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 last modified", e); + throw new ArgeoException("Cannot update lastModified from " + node + + " until " + untilPath, e); } }