/*
- * Copyright (C) 2010 Mathieu Baudier <mbaudier@argeo.org>
+ * Copyright (C) 2007-2012 Mathieu Baudier
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package org.argeo.jcr;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
+import java.security.Principal;
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;
import javax.jcr.NamespaceRegistry;
+import javax.jcr.NoSuchWorkspaceException;
import javax.jcr.Node;
import javax.jcr.NodeIterator;
+import javax.jcr.PathNotFoundException;
import javax.jcr.Property;
import javax.jcr.PropertyIterator;
+import javax.jcr.PropertyType;
import javax.jcr.Repository;
import javax.jcr.RepositoryException;
import javax.jcr.RepositoryFactory;
import javax.jcr.Session;
import javax.jcr.Value;
+import javax.jcr.ValueFormatException;
import javax.jcr.Workspace;
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 javax.jcr.security.AccessControlEntry;
+import javax.jcr.security.AccessControlList;
+import javax.jcr.security.AccessControlManager;
+import javax.jcr.security.AccessControlPolicy;
+import javax.jcr.security.AccessControlPolicyIterator;
+import javax.jcr.security.Privilege;
+import javax.jcr.version.VersionManager;
+
+import org.apache.commons.io.IOUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.argeo.ArgeoException;
+import org.argeo.util.security.SimplePrincipal;
/** Utility methods to simplify common JCR operations. */
public class JcrUtils implements ArgeoJcrConstants {
+
private final static Log log = LogFactory.getLog(JcrUtils.class);
/**
}
}
+ /** 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<String> 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
buf.append('Y');
buf.append(cal.get(Calendar.YEAR));
buf.append('/');
-
+
int month = cal.get(Calendar.MONTH) + 1;
buf.append('M');
if (month < 10)
return path.substring(index + 1);
}
+ /**
+ * Routine that get the child with this name, adding id it does not already
+ * exist
+ */
+ public static Node getOrAdd(Node parent, String childName,
+ String childPrimaryNodeType) throws RepositoryException {
+ return parent.hasNode(childName) ? parent.getNode(childName) : parent
+ .addNode(childName, childPrimaryNodeType);
+ }
+
+ /**
+ * Routine that get the child with this name, adding id it does not already
+ * exist
+ */
+ public static Node getOrAdd(Node parent, String childName)
+ throws RepositoryException {
+ return parent.hasNode(childName) ? parent.getNode(childName) : parent
+ .addNode(childName);
+ }
+
+ /** Convert a {@link NodeIterator} to a list of {@link Node} */
+ public static List<Node> nodeIteratorToList(NodeIterator nodeIterator) {
+ List<Node> nodes = new ArrayList<Node>();
+ while (nodeIterator.hasNext()) {
+ nodes.add(nodeIterator.nextNode());
+ }
+ return nodes;
+ }
+
+ /*
+ * PROPERTIES
+ */
+
+ /** Concisely get the string value of a property */
+ public static String get(Node node, String propertyName) {
+ try {
+ return node.getProperty(propertyName).getString();
+ } catch (RepositoryException e) {
+ throw new ArgeoException("Cannot get property " + propertyName
+ + " of " + node, e);
+ }
+ }
+
+ /** Concisely get the boolean value of a property */
+ public static Boolean check(Node node, String propertyName) {
+ try {
+ return node.getProperty(propertyName).getBoolean();
+ } catch (RepositoryException e) {
+ throw new ArgeoException("Cannot get property " + propertyName
+ + " of " + node, e);
+ }
+ }
+
+ /** Concisely get the bytes array value of a property */
+ public static byte[] getBytes(Node node, String propertyName) {
+ try {
+ return getBinaryAsBytes(node.getProperty(propertyName));
+ } catch (RepositoryException e) {
+ throw new ArgeoException("Cannot get property " + propertyName
+ + " of " + node, e);
+ }
+ }
+
/** Creates the nodes making path, if they don't exist. */
public static Node mkdirs(Session session, String path) {
return mkdirs(session, path, null, null, false);
}
/**
- * @deprecated use {@link #mkdirs(Session, String, String, String, Boolean)}
- * instead.
+ * use {@link #mkdirs(Session, String, String, String, Boolean)} instead.
+ *
+ * @deprecated
*/
@Deprecated
public static Node mkdirs(Session session, String path, String type,
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 the path as {@link NodeType#NT_FOLDER}
+ */
+ public static Node mkfolders(Session session, String path) {
+ return mkdirs(session, path, NodeType.NT_FOLDER, NodeType.NT_FOLDER,
+ false);
+ }
+
/**
* 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) {
if (session.itemExists(path)) {
Node node = session.getNode(path);
// check type
- if (type != null
- && !type.equals(node.getPrimaryNodeType().getName()))
+ if (type != null && !node.isNodeType(type)
+ && !node.getPath().equals("/"))
throw new ArgeoException("Node " + node
+ " exists but is of type "
+ node.getPrimaryNodeType().getName()
return node;
}
- StringTokenizer st = new StringTokenizer(path, "/");
StringBuffer current = new StringBuffer("/");
Node currentNode = session.getRootNode();
- while (st.hasMoreTokens()) {
- String part = st.nextToken();
+ Iterator<String> 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
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<String> tokenize(String path) {
+ List<String> tokens = new ArrayList<String>();
+ 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);
}
/**
NodeIterator it = node.getNodes();
while (it.hasNext()) {
Node childNode = it.nextNode();
- debug(childNode);
+ debug(childNode, log);
}
// Then output the properties
PropertyIterator properties = node.getProperties();
// log.debug("Property are : ");
- while (properties.hasNext()) {
+ properties: while (properties.hasNext()) {
Property property = properties.nextProperty();
+ if (property.getType() == PropertyType.BINARY)
+ continue properties;// skip
if (property.getDefinition().isMultiple()) {
// A multi-valued property, print all values
Value[] values = property.getValues();
}
+ /** Logs the effective access control policies */
+ public static void logEffectiveAccessPolicies(Node node) {
+ try {
+ logEffectiveAccessPolicies(node.getSession(), node.getPath());
+ } catch (RepositoryException e) {
+ log.error("Cannot log effective access policies of " + node, e);
+ }
+ }
+
+ /** Logs the effective access control policies */
+ public static void logEffectiveAccessPolicies(Session session, String path) {
+ if (!log.isDebugEnabled())
+ return;
+
+ try {
+ AccessControlPolicy[] effectivePolicies = session
+ .getAccessControlManager().getEffectivePolicies(path);
+ if (effectivePolicies.length > 0) {
+ for (AccessControlPolicy policy : effectivePolicies) {
+ if (policy instanceof AccessControlList) {
+ AccessControlList acl = (AccessControlList) policy;
+ log.debug("Access control list for " + path + "\n"
+ + accessControlListSummary(acl));
+ }
+ }
+ } else {
+ log.debug("No effective access control policy for " + path);
+ }
+ } catch (RepositoryException e) {
+ log.error("Cannot log effective access policies of " + path, e);
+ }
+ }
+
+ /** Returns a human-readable summary of this access control list. */
+ public static String accessControlListSummary(AccessControlList acl) {
+ StringBuffer buf = new StringBuffer("");
+ try {
+ for (AccessControlEntry ace : acl.getAccessControlEntries()) {
+ buf.append('\t').append(ace.getPrincipal().getName())
+ .append('\n');
+ for (Privilege priv : ace.getPrivileges())
+ buf.append("\t\t").append(priv.getName()).append('\n');
+ }
+ return buf.toString();
+ } catch (RepositoryException e) {
+ throw new ArgeoException("Cannot write summary of " + acl, e);
+ }
+ }
+
/**
* Copies recursively the content of a node to another one. Do NOT copy the
* property values of {@link NodeType#MIX_CREATED} and
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);
+ }
}
}
}
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) {
binary.dispose();
}
+ /** Retrieve a {@link Binary} as a byte array */
+ public static byte[] getBinaryAsBytes(Property property) {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ InputStream in = null;
+ Binary binary = null;
+ try {
+ binary = property.getBinary();
+ in = binary.getStream();
+ IOUtils.copy(in, out);
+ return out.toByteArray();
+ } catch (Exception e) {
+ throw new ArgeoException("Cannot read binary " + property
+ + " as bytes", e);
+ } finally {
+ IOUtils.closeQuietly(out);
+ IOUtils.closeQuietly(in);
+ closeQuietly(binary);
+ }
+ }
+
+ /** Writes a {@link Binary} from a byte array */
+ public static void setBinaryAsBytes(Node node, String property, byte[] bytes) {
+ InputStream in = null;
+ Binary binary = null;
+ try {
+ in = new ByteArrayInputStream(bytes);
+ binary = node.getSession().getValueFactory().createBinary(in);
+ node.setProperty(property, binary);
+ } catch (Exception e) {
+ throw new ArgeoException("Cannot read binary " + property
+ + " as bytes", e);
+ } finally {
+ IOUtils.closeQuietly(in);
+ closeQuietly(binary);
+ }
+ }
+
+ /**
+ * Copy a file as an nt:file, assuming an nt:folder hierarchy. The session
+ * is NOT saved.
+ *
+ * @return the created file node
+ */
+ public static Node copyFile(Node folderNode, File file) {
+ InputStream in = null;
+ try {
+ in = new FileInputStream(file);
+ return copyStreamAsFile(folderNode, file.getName(), in);
+ } catch (IOException e) {
+ throw new ArgeoException("Cannot copy file " + file + " under "
+ + folderNode, e);
+ } finally {
+ IOUtils.closeQuietly(in);
+ }
+ }
+
+ /** Copy bytes as an nt:file */
+ public static Node copyBytesAsFile(Node folderNode, String fileName,
+ byte[] bytes) {
+ InputStream in = null;
+ try {
+ in = new ByteArrayInputStream(bytes);
+ return copyStreamAsFile(folderNode, fileName, in);
+ } catch (Exception e) {
+ throw new ArgeoException("Cannot copy file " + fileName + " under "
+ + folderNode, e);
+ } finally {
+ IOUtils.closeQuietly(in);
+ }
+ }
+
+ /**
+ * Copy a stream as an nt:file, assuming an nt:folder hierarchy. The session
+ * is NOT saved.
+ *
+ * @return the created file node
+ */
+ public static Node copyStreamAsFile(Node folderNode, String fileName,
+ InputStream in) {
+ Binary binary = null;
+ try {
+ Node fileNode;
+ Node contentNode;
+ if (folderNode.hasNode(fileName)) {
+ fileNode = folderNode.getNode(fileName);
+ // we assume that the content node is already there
+ contentNode = fileNode.getNode(Node.JCR_CONTENT);
+ } else {
+ fileNode = folderNode.addNode(fileName, NodeType.NT_FILE);
+ contentNode = fileNode.addNode(Node.JCR_CONTENT,
+ NodeType.NT_RESOURCE);
+ }
+ binary = contentNode.getSession().getValueFactory()
+ .createBinary(in);
+ contentNode.setProperty(Property.JCR_DATA, binary);
+ return fileNode;
+ } catch (Exception e) {
+ throw new ArgeoException("Cannot create file node " + fileName
+ + " under " + folderNode, e);
+ } finally {
+ closeQuietly(binary);
+ }
+ }
+
/**
* Creates depth from a string (typically a username) by adding levels based
* on its first characters: "aBcD",2 => a/aB
}
}
- /** Logs out the session, not throwing any exception, even if it is null. */
- public static void logoutQuietly(Session session) {
- if (session != null)
- session.logout();
+ /**
+ * Login to a workspace with implicit credentials, creates the workspace
+ * with these credentials if it does not already exist.
+ */
+ public static Session loginOrCreateWorkspace(Repository repository,
+ String workspaceName) throws RepositoryException {
+ Session workspaceSession = null;
+ Session defaultSession = null;
+ try {
+ try {
+ workspaceSession = repository.login(workspaceName);
+ } catch (NoSuchWorkspaceException e) {
+ // try to create workspace
+ defaultSession = repository.login();
+ defaultSession.getWorkspace().createWorkspace(workspaceName);
+ workspaceSession = repository.login(workspaceName);
+ }
+ return workspaceSession;
+ } finally {
+ logoutQuietly(defaultSession);
+ }
}
- /** 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);
+ /** 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
+ }
}
/**
- * Returns user home has path, embedding exceptions. Contrary to
- * {@link #getUserHome(Session)}, it never returns null but throws and
- * exception if not found.
+ * Convenient method to add a listener. uuids passed as null, deep=true,
+ * local=true, only one node type
*/
- public static String getUserHomePath(Session session) {
- String userID = session.getUserID();
+ public static void addListener(Session session, EventListener listener,
+ int eventTypes, String basePath, String nodeType) {
try {
- Node userHome = getUserHome(session, userID);
- if (userHome != null)
- return userHome.getPath();
- else
- throw new ArgeoException("No home registered for " + userID);
+ session.getWorkspace()
+ .getObservationManager()
+ .addEventListener(listener, eventTypes, basePath, true,
+ null, new String[] { nodeType }, true);
} catch (RepositoryException e) {
- throw new ArgeoException("Cannot find user home path", e);
+ throw new ArgeoException("Cannot add JCR listener " + listener
+ + " to session " + session, e);
}
}
- /** Get the profile of the user attached to this session. */
- public static Node getUserProfile(Session session) {
+ /** 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 getUserProfile(session, userID);
+ 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;
}
/**
*/
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;
- Node userHome = JcrUtils.mkdirs(session, homePath);
-
+ "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);
- 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;
+ 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 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 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);
}
}
+
+ /**
+ * 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);
+ }
+ }
+
+ /*
+ * SECURITY
+ */
+
+ /**
+ * Convenience method for adding a single privilege to a principal (user or
+ * role), typically jcr:all
+ */
+ public static void addPrivilege(Session session, String path,
+ String principal, String privilege) throws RepositoryException {
+ List<Privilege> privileges = new ArrayList<Privilege>();
+ privileges.add(session.getAccessControlManager().privilegeFromName(
+ privilege));
+ addPrivileges(session, path, new SimplePrincipal(principal), privileges);
+ }
+
+ /**
+ * Add privileges on a path to a {@link Principal}. The path must already
+ * exist.
+ */
+ public static void addPrivileges(Session session, String path,
+ Principal principal, List<Privilege> privs)
+ throws RepositoryException {
+ AccessControlManager acm = session.getAccessControlManager();
+ // search for an access control list
+ AccessControlList acl = null;
+ AccessControlPolicyIterator policyIterator = acm
+ .getApplicablePolicies(path);
+ if (policyIterator.hasNext()) {
+ while (policyIterator.hasNext()) {
+ AccessControlPolicy acp = policyIterator
+ .nextAccessControlPolicy();
+ if (acp instanceof AccessControlList)
+ acl = ((AccessControlList) acp);
+ }
+ } else {
+ AccessControlPolicy[] existingPolicies = acm.getPolicies(path);
+ for (AccessControlPolicy acp : existingPolicies) {
+ if (acp instanceof AccessControlList)
+ acl = ((AccessControlList) acp);
+ }
+ }
+
+ if (acl != null) {
+ acl.addAccessControlEntry(principal,
+ privs.toArray(new Privilege[privs.size()]));
+ acm.setPolicy(path, acl);
+ if (log.isDebugEnabled())
+ log.debug("Added privileges " + privs + " to " + principal
+ + " on " + path);
+ } else {
+ throw new ArgeoException("Don't know how to apply privileges "
+ + privs + " to " + principal + " on " + path);
+ }
+ }
+
}