You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by ju...@apache.org on 2009/09/08 18:09:45 UTC
svn commit: r812570 [14/24] - in /jackrabbit/sandbox/JCR-1456: ./
jackrabbit-api/ jackrabbit-api/src/main/appended-resources/
jackrabbit-api/src/main/appended-resources/META-INF/
jackrabbit-api/src/main/java/org/apache/jackrabbit/api/security/ jackrabb...
Modified: jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserManagerImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserManagerImpl.java?rev=812570&r1=812569&r2=812570&view=diff
==============================================================================
--- jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserManagerImpl.java (original)
+++ jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserManagerImpl.java Tue Sep 8 16:09:28 2009
@@ -21,17 +21,16 @@
import org.apache.jackrabbit.api.security.user.Group;
import org.apache.jackrabbit.api.security.user.User;
import org.apache.jackrabbit.api.security.user.UserManager;
+import org.apache.jackrabbit.api.security.principal.ItemBasedPrincipal;
import org.apache.jackrabbit.core.ItemImpl;
import org.apache.jackrabbit.core.NodeImpl;
import org.apache.jackrabbit.core.ProtectedItemModifier;
import org.apache.jackrabbit.core.SessionImpl;
import org.apache.jackrabbit.core.SessionListener;
-import org.apache.jackrabbit.core.security.principal.ItemBasedPrincipal;
import org.apache.jackrabbit.core.security.principal.PrincipalImpl;
import org.apache.jackrabbit.spi.Name;
import org.apache.jackrabbit.spi.commons.name.NameConstants;
import org.apache.jackrabbit.util.Text;
-import org.apache.commons.collections.map.LRUMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -50,29 +49,185 @@
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Set;
-import java.util.Map;
+import java.util.Properties;
/**
- * UserManagerImpl
+ * Default implementation of the <code>UserManager</code> interface with the
+ * following characteristics:
+ *
+ * <ul>
+ * <li>Users and Groups are stored in the repository as JCR nodes.</li>
+ * <li>Users are created below {@link UserConstants#USERS_PATH},<br>Groups are
+ * created below {@link UserConstants#GROUPS_PATH}.</li>
+ * <li>In order to structure the users and groups tree and void creating a flat
+ * hierarchy, additional hierarchy nodes of type "rep:AuthorizableFolder" are
+ * introduced.</li>
+ * <li>The names of the hierarchy folders is determined from ID of the
+ * authorizable to be created, consisting of the leading N chars where N is
+ * the relative depth starting from the node at {@link UserConstants#USERS_PATH}
+ * or {@link UserConstants#GROUPS_PATH}.</li>
+ * <li>By default 2 levels (depth == 2) are created.</li>
+ * <li>Searching authorizables by ID always starts looking at that specific
+ * hierarchy level. Parent nodes are expected to consist of folder structure only.</li>
+ * <li>If the ID contains invalid JCR chars that would prevent the creation of
+ * a Node with that name, the names of authorizable node and the intermediate
+ * hierarchy nodes are {@link Text#escapeIllegalJcrChars(String) escaped}.</li>
+ * <li>Any intermediate path passed to either
+ * {@link #createUser(String, String, Principal, String) createUser} or
+ * {@link #createGroup(Principal, String) createGroup} are ignored. This allows
+ * to directly find authorizables by ID without having to search or traverse
+ * the complete tree.<br>
+ * See also {@link #PARAM_COMPATIBILE_JR16}.
+ * </li>
+ * </ul>
+ * Example: Creating an non-existing authorizable with ID 'aSmith' would result
+ * in the following structure:
+ *
+ * <pre>
+ * + rep:security [nt:unstructured]
+ * + rep:authorizables [rep:AuthorizableFolder]
+ * + rep:users [rep:AuthorizableFolder]
+ * + a [rep:AuthorizableFolder]
+ * + aS [rep:AuthorizableFolder]
+ * + aSmith [rep:User]
+ * </pre>
+ *
+ * This <code>UserManager</code> is able to handle the following configuration
+ * options:
+ *
+ * <ul>
+ * <li>{@link #PARAM_COMPATIBILE_JR16}: If the param is present and its
+ * value is <code>true</code> looking up authorizables by ID will use the
+ * <code>NodeResolver</code> if not found otherwise.<br>
+ * If the parameter is missing (or false) users and groups created
+ * with a Jackrabbit repository < v2.0 will not be found any more.<br>
+ * By default this option is disabled.</li>
+ * <li>{@link #PARAM_DEFAULT_DEPTH}: Parameter used to change the number of
+ * levels that are used by default store authorizable nodes.<br>The value is
+ * expected to be a positive integer greater than zero. The default
+ * number of levels is 2.
+ * <p/>
+ * <strong>NOTE:</strong> Changing the default depth once users and groups
+ * have been created in the repository will cause inconsistencies, due to
+ * the fact that the resolution of ID to an authorizable relies on the
+ * structure defined by the default depth.<br>
+ * It is recommended to remove all authorizable nodes that will not be
+ * reachable any more, before this config option is changed.
+ * <ul>
+ * <li>If default depth is increased:<br>
+ * All authorizables on levels < default depth are not reachable any more.</li>
+ * <li>If default depth is decreased:<br>
+ * All authorizables on levels > default depth aren't reachable any more
+ * unless the {@link #PARAM_AUTO_EXPAND_TREE} flag is set to <code>true</code>.</li>
+ * </ul>
+ * </li>
+ * <li>{@link #PARAM_AUTO_EXPAND_TREE}: If this parameter is present and its
+ * value is <code>true</code>, the trees containing user and group nodes will
+ * automatically created additional hierarchy levels if the number of nodes
+ * on a given level exceeds the maximal allowed {@link #PARAM_AUTO_EXPAND_SIZE size}.
+ * <br>By default this option is disabled.</li>
+ * <li>{@link #PARAM_AUTO_EXPAND_SIZE}: This parameter only takes effect
+ * if {@link #PARAM_AUTO_EXPAND_TREE} is enabled.<br>The value is expected to be
+ * a positive long greater than zero. The default value is 1000.</li>
+ * </ul>
*/
-public class UserManagerImpl extends ProtectedItemModifier implements UserManager, UserConstants, SessionListener {
+public class UserManagerImpl extends ProtectedItemModifier
+ implements UserManager, UserConstants, SessionListener {
+
+ /**
+ * Flag to enable a minimal backwards compatibility with Jackrabbit <
+ * v2.0<br>
+ * If the param is present and its value is <code>true</code> looking up
+ * authorizables by ID will use the <code>NodeResolver</code> if not found
+ * otherwise.<br>
+ * If the parameter is missing (or false) users and groups created
+ * with a Jackrabbit repository < v2.0 will not be found any more.<br>
+ * By default this option is disabled.
+ */
+ public static final String PARAM_COMPATIBILE_JR16 = "compatibleJR16";
+
+ /**
+ * Parameter used to change the number of levels that are used by default
+ * store authorizable nodes.<br>The default number of levels is 2.
+ * <p/>
+ * <strong>NOTE:</strong> Changing the default depth once users and groups
+ * have been created in the repository will cause inconsistencies, due to
+ * the fact that the resolution of ID to an authorizable relies on the
+ * structure defined by the default depth.<br>
+ * It is recommended to remove all authorizable nodes that will not be
+ * reachable any more, before this config option is changed.
+ * <ul>
+ * <li>If default depth is increased:<br>
+ * All authorizables on levels < default depth are not reachable any more.</li>
+ * <li>If default depth is decreased:<br>
+ * All authorizables on levels > default depth aren't reachable any more
+ * unless the {@link #PARAM_AUTO_EXPAND_TREE} flag is set to <code>true</code>.</li>
+ * </ul>
+ */
+ public static final String PARAM_DEFAULT_DEPTH = "defaultDepth";
+
+ /**
+ * If this parameter is present and its value is <code>true</code>, the trees
+ * containing user and group nodes will automatically created additional
+ * hierarchy levels if the number of nodes on a given level exceeds the
+ * maximal allowed {@link #PARAM_AUTO_EXPAND_SIZE size}.
+ * <br>By default this option is disabled.
+ */
+ public static final String PARAM_AUTO_EXPAND_TREE = "autoExpandTree";
+
+ /**
+ * This parameter only takes effect if {@link #PARAM_AUTO_EXPAND_TREE} is
+ * enabled.<br>The default value is 1000.
+ */
+ public static final String PARAM_AUTO_EXPAND_SIZE = "autoExpandSize";
private static final Logger log = LoggerFactory.getLogger(UserManagerImpl.class);
private final SessionImpl session;
private final String adminId;
private final NodeResolver authResolver;
+ private final IdResolver idResolver;
/**
- * Simple unmanaged map from authorizableID to nodePath (node representing
- * the authorizable) used limit the number of calls to the
- * <code>NodeResolver</code> in order to find authorizable nodes by the
- * authorizable id.
+ * Flag indicating if {@link #getAuthorizable(String)} should find users or
+ * groups created with Jackrabbit < 2.0.<br>
+ * As of 2.0 authorizables are created using a defined logic that allows
+ * to retrieve them without searching/traversing. If this flag is
+ * <code>true</code> this method will try to find authorizables using the
+ * <code>authResolver</code> if not found otherwise.
*/
- private final Map idPathMap = new LRUMap(1000);
+ private final boolean compatibleJR16;
+ /**
+ * Create a new <code>UserManager</code> with the default configuration.
+ *
+ * @param session
+ * @param adminId
+ * @throws RepositoryException
+ */
public UserManagerImpl(SessionImpl session, String adminId) throws RepositoryException {
- super();
+ this(session, adminId, null);
+ }
+
+ /**
+ * Create a new <code>UserManager</code> for the given <code>session</code>.
+ * Currently the following configuration options are respected:
+ *
+ * <ul>
+ * <li>{@link #PARAM_COMPATIBILE_JR16}. By default this option is disabled.</li>
+ * <li>{@link #PARAM_DEFAULT_DEPTH}. The default number of levels is 2.</li>
+ * <li>{@link #PARAM_AUTO_EXPAND_TREE}. By default this option is disabled.</li>
+ * <li>{@link #PARAM_AUTO_EXPAND_SIZE}. The default value is 1000.</li>
+ * </ul>
+ *
+ * See the overall {@link UserManagerImpl introduction} for details.
+ *
+ * @param session
+ * @param adminId
+ * @param config
+ * @throws RepositoryException
+ */
+ public UserManagerImpl(SessionImpl session, String adminId, Properties config) throws RepositoryException {
this.session = session;
this.adminId = adminId;
@@ -80,10 +235,17 @@
try {
nr = new IndexNodeResolver(session, session);
} catch (RepositoryException e) {
- log.debug("UserManger: no QueryManager available for workspace '" + session.getWorkspace().getName() + "' -> Use traversing node resolver.");
+ log.debug("UserManager: no QueryManager available for workspace '" + session.getWorkspace().getName() + "' -> Use traversing node resolver.");
nr = new TraversingNodeResolver(session, session);
}
authResolver = nr;
+
+ idResolver = new IdResolver(config);
+ boolean compatMode = false;
+ if (config != null && config.containsKey(PARAM_COMPATIBILE_JR16)) {
+ compatMode = Boolean.parseBoolean(config.get(PARAM_COMPATIBILE_JR16).toString());
+ }
+ compatibleJR16 = compatMode;
}
//--------------------------------------------------------< UserManager >---
@@ -133,7 +295,7 @@
} else if (n.isNodeType(NT_REP_GROUP)) {
return createGroup(n);
} else {
- log.warn("Unexpected user nodetype " + n.getPrimaryNodeType().getName());
+ log.debug("Unexpected user nodetype " + n.getPrimaryNodeType().getName());
}
}
return null;
@@ -181,7 +343,6 @@
* @param userID
* @param password
* @see UserManager#createUser(String,String)
- * @inheritDoc
*/
public User createUser(String userID, String password) throws RepositoryException {
return createUser(userID, password, new PrincipalImpl(userID), null);
@@ -192,7 +353,7 @@
* @param userID
* @param password
* @param principal
- * @param intermediatePath
+ * @param intermediatePath Is always ignored.
* @return
* @throws AuthorizableExistsException
* @throws RepositoryException
@@ -215,53 +376,54 @@
if (hasAuthorizableOrReferee(principal)) {
throw new AuthorizableExistsException("Authorizable for '" + principal.getName() + "' already exists");
}
+ if (intermediatePath != null) {
+ log.debug("Intermediate path param " + intermediatePath + " is ignored.");
+ }
NodeImpl parent = null;
try {
- String parentPath = getParentPath(intermediatePath, getCurrentUserPath());
- parent = createParentNode(parentPath);
-
- Name nodeName = session.getQName(Text.escapeIllegalJcrChars(userID));
- NodeImpl userNode = addNode(parent, nodeName, NT_REP_USER);
+ NodeImpl userNode = (NodeImpl) idResolver.createUserNode(userID);
setProperty(userNode, P_USERID, getValue(userID), true);
setProperty(userNode, P_PASSWORD, getValue(UserImpl.buildPasswordValue(password)), true);
setProperty(userNode, P_PRINCIPAL_NAME, getValue(principal.getName()), true);
- parent.save();
+ session.save();
log.debug("User created: " + userID + "; " + userNode.getPath());
return createUser(userNode);
} catch (RepositoryException e) {
// something went wrong -> revert changes and rethrow
- if (parent != null) {
- parent.refresh(false);
- log.debug("Failed to create new User, reverting changes.");
- }
+ session.refresh(false);
+ log.debug("Failed to create new User, reverting changes.");
throw e;
}
}
/**
- * Create a new <code>Group</code> with the given <code>groupName</code>.
- * It will be created below the this UserManager's root Path.<br>
- * If non-existant elements of the Path will be created as <code>Nodes</code>
- * of type {@link #NT_REP_AUTHORIZABLE_FOLDER rep:AuthorizableFolder}
- *
- * @param principal
- * @see UserManager#createGroup(Principal);
- * @inheritDoc
+ * Same as {@link #createGroup(java.security.Principal, String )} where the
+ * intermediate path is <code>null</code>.
+ * @see UserManager#createGroup(Principal)
*/
public Group createGroup(Principal principal) throws RepositoryException {
return createGroup(principal, null);
}
/**
+ * Create a new <code>Group</code> from the given <code>principal</code>.
+ * It will be created below the this {@link #GROUPS_PATH group path}.<br>
+ * Non-existant elements of the Path will be created as nodes
+ * of type {@link #NT_REP_AUTHORIZABLE_FOLDER rep:AuthorizableFolder}.
+ * The group ID will be generated from the principal name. If the name
+ * conflicts with an existing authorizable ID (may happen in cases where
+ * principal name != ID) the principal name is expanded by a suffix;
+ * otherwise the resulting group ID equals the principal name.
*
* @param principal
- * @param intermediatePath
- * @return
+ * @param intermediatePath Is always ignored.
+ * @return A new group.
* @throws AuthorizableExistsException
* @throws RepositoryException
+ * @see UserManager#createGroup(java.security.Principal, String)
*/
public Group createGroup(Principal principal, String intermediatePath) throws AuthorizableExistsException, RepositoryException {
if (!isValidPrincipal(principal)) {
@@ -270,25 +432,24 @@
if (hasAuthorizableOrReferee(principal)) {
throw new AuthorizableExistsException("Authorizable for '" + principal.getName() + "' already exists: ");
}
-
+ if (intermediatePath != null) {
+ log.debug("Intermediate path param " + intermediatePath + " is ignored.");
+ }
+
NodeImpl parent = null;
try {
- String parentPath = getParentPath(intermediatePath, GROUPS_PATH);
- parent = createParentNode(parentPath);
- Name groupID = getGroupId(principal.getName());
+ String groupID = getGroupId(principal.getName());
+ NodeImpl groupNode = (NodeImpl) idResolver.createGroupNode(groupID);
- NodeImpl groupNode = addNode(parent, groupID, NT_REP_GROUP);
setProperty(groupNode, P_PRINCIPAL_NAME, getValue(principal.getName()));
- parent.save();
+ session.save();
log.debug("Group created: " + groupID + "; " + groupNode.getPath());
return createGroup(groupNode);
} catch (RepositoryException e) {
- if (parent != null) {
- parent.refresh(false);
- log.debug("newInstance new Group failed, revert changes on parent");
- }
+ session.refresh(false);
+ log.debug("newInstance new Group failed, revert changes on parent");
throw e;
}
}
@@ -301,7 +462,7 @@
* @throws RepositoryException
*/
boolean hasAuthorizableOrReferee(Principal principal) throws RepositoryException {
- Set s = new HashSet(2);
+ Set<Name> s = new HashSet<Name>(2);
s.add(P_PRINCIPAL_NAME);
s.add(P_REFEREES);
NodeIterator res = authResolver.findNodes(s, principal.getName(), NT_REP_AUTHORIZABLE, true, 1);
@@ -318,29 +479,34 @@
node.save();
}
+ void setProtectedProperty(NodeImpl node, Name propName, Value[] values, int type) throws RepositoryException, LockException, ConstraintViolationException, ItemExistsException, VersionException {
+ setProperty(node, propName, values, type);
+ node.save();
+ }
+
void removeProtectedItem(ItemImpl item, Node parent) throws RepositoryException, AccessDeniedException, VersionException {
removeItem(item);
parent.save();
}
/**
- * Escape illegal JCR characters and test if a user exists that has the
- * principals name as userId, which might happen if userID != principal-name.
+ * Test if a user or group exists that has the given principals name as ID,
+ * which might happen if userID != principal-name.
* In this case: generate another ID for the group to be created.
*
* @param principalName to be used as hint for the groupid.
* @return a group id.
* @throws RepositoryException
*/
- private Name getGroupId(String principalName) throws RepositoryException {
- String escHint = Text.escapeIllegalJcrChars(principalName);
+ private String getGroupId(String principalName) throws RepositoryException {
+ String escHint = principalName;
String groupID = escHint;
int i = 0;
while (getAuthorizable(groupID) != null) {
groupID = escHint + "_" + i;
i++;
}
- return session.getQName(groupID);
+ return groupID;
}
private Value getValue(String strValue) throws RepositoryException {
@@ -348,10 +514,11 @@
}
/**
+ * @param userID
* @return true if the given userID belongs to the administrator user.
*/
boolean isAdminId(String userID) {
- return (adminId == null) ? false : adminId.equals(userID);
+ return (adminId != null) && adminId.equals(userID);
}
/**
@@ -369,7 +536,6 @@
throw new IllegalArgumentException("User has to be within the User Path");
}
User user = doCreateUser(userNode);
- idPathMap.put(user.getID(), userNode.getPath());
return user;
}
@@ -395,141 +561,53 @@
*/
Group createGroup(NodeImpl groupNode) throws RepositoryException {
Group group = GroupImpl.create(groupNode, this);
- idPathMap.put(group.getID(), groupNode.getPath());
return group;
}
/**
- * @param userID
+ * Resolve the given <code>userID</code> to an rep:user node in the repository.
+ *
+ * @param userID A valid userID.
* @return the node associated with the given userID or <code>null</code>.
+ * @throws RepositoryException If an error occurs.
*/
private NodeImpl getUserNode(String userID) throws RepositoryException {
- NodeImpl n = null;
- if (idPathMap.containsKey(userID)) {
- String path = idPathMap.get(userID).toString();
- if (session.itemExists(path)) {
- Item itm = session.getItem(path);
- // make sure the item really represents the node associated with
- // the given userID. if not the search below is execute.
- if (itm.isNode()) {
- NodeImpl tmp = (NodeImpl) itm;
- if (tmp.isNodeType(NT_REP_USER) && userID.equals(((NodeImpl) itm).getProperty(P_USERID).getString())) {
- n = (NodeImpl) itm;
- }
- }
- }
- }
-
- if (n == null) {
- // clear eventual previous entry
- idPathMap.remove(userID);
+ NodeImpl n = (NodeImpl) idResolver.findNode(userID, false);
+ if (n == null && compatibleJR16) {
+ // backwards-compatibility with JR < 2.0 user structure that doesn't
+ // allow to determine the auth-path from the id directly.
// search for it the node belonging to that userID
n = (NodeImpl) authResolver.findNode(P_USERID, userID, NT_REP_USER);
- }
+ } // else: no such user -> return null.
return n;
}
+ /**
+ * Resolve the given <code>groupID</code> to an rep:group node in the repository.
+ *
+ * @param groupID A valid groupID.
+ * @return the node associated with the given userID or <code>null</code>.
+ * @throws RepositoryException If an error occurs.
+ */
private NodeImpl getGroupNode(String groupID) throws RepositoryException {
- NodeImpl n = null;
- if (idPathMap.containsKey(groupID)) {
- String path = idPathMap.get(groupID).toString();
- if (session.itemExists(path)) {
- Item itm = session.getItem(path);
- // make sure the item really represents the node associated with
- // the given userID. if not the search below is execute.
- if (itm.isNode()) {
- NodeImpl tmp = (NodeImpl) itm;
- if (tmp.isNodeType(NT_REP_GROUP) && groupID.equals(tmp.getName())) {
- n = (NodeImpl) itm;
- }
- }
- }
- }
- if (n == null) {
- // clear eventual previous entry
- idPathMap.remove(groupID);
- // search for it the node belonging to that groupID
- Name nodeName = session.getQName(groupID);
+ NodeImpl n = (NodeImpl) idResolver.findNode(groupID, true);
+ if (n == null && compatibleJR16) {
+ // backwards-compatibility with JR < 2.0 group structure that doesn't
+ // allow to determine the auth-path from the id directly
+ // search for it the node belonging to that groupID.
+ // NOTE: JR < 2.0 always returned groupIDs that didn't contain any
+ // illegal JCR chars. Since Group.getID() now unescapes the node
+ // name additional escaping is required.
+ Name nodeName = session.getQName(Text.escapeIllegalJcrChars(groupID));
n = (NodeImpl) authResolver.findNode(nodeName, NT_REP_GROUP);
- }
+ } // else: no such group -> return null.
return n;
}
- /**
- * @return the path refering to the node associated with the user this
- * <code>UserManager</code> has been built for.
- */
- private String getCurrentUserPath() {
- // fallback: default user-path
- String currentUserPath = USERS_PATH;
- String userId = session.getUserID();
-
- if (idPathMap.containsKey(userId)) {
- currentUserPath = idPathMap.get(userId).toString();
- } else {
- try {
- Node n = getUserNode(userId);
- if (n != null) {
- currentUserPath = n.getPath();
- }
- } catch (RepositoryException e) {
- // should never get here
- log.error("Internal error: unable to build current user path.", e.getMessage());
- }
- }
- return currentUserPath;
- }
-
private static boolean isValidPrincipal(Principal principal) {
return principal != null && principal.getName() != null && principal.getName().length() > 0;
}
- private static String getParentPath(String hint, String root) {
- StringBuffer b = new StringBuffer();
- if (hint == null || !hint.startsWith(root)) {
- b.append(root);
- }
- if (hint != null && hint.length() > 1) {
- if (!hint.startsWith("/")) {
- b.append("/");
- }
- b.append(hint);
- }
- return b.toString();
- }
-
- /**
- * @param path to the authorizable node to be created
- * @return
- * @throws RepositoryException
- */
- private NodeImpl createParentNode(String path) throws RepositoryException {
- NodeImpl parent = (NodeImpl) session.getRootNode();
- String[] elem = path.split("/");
- for (int i = 0; i < elem.length; i++) {
- String name = elem[i];
- if (name.length() < 1) {
- continue;
- }
- Name nName = session.getQName(name);
- if (!parent.hasNode(nName)) {
- Name ntName;
- if (i == 0) {
- // rep:security node
- ntName = NameConstants.NT_UNSTRUCTURED;
- } else {
- ntName = NT_REP_AUTHORIZABLE_FOLDER;
- }
- NodeImpl added = addNode(parent, nName, ntName);
- parent.save();
- parent = added;
- } else {
- parent = parent.getNode(nName);
- }
- }
- return parent;
- }
-
//----------------------------------------------------< SessionListener >---
/**
* @see SessionListener#loggingOut(org.apache.jackrabbit.core.SessionImpl)
@@ -542,8 +620,6 @@
* @see SessionListener#loggedOut(org.apache.jackrabbit.core.SessionImpl)
*/
public void loggedOut(SessionImpl session) {
- // clear the map
- idPathMap.clear();
// and logout the session unless it is the loggedout session itself.
if (session != this.session) {
this.session.logout();
@@ -556,7 +632,7 @@
*/
private final class AuthorizableIterator implements Iterator {
- private final Set served = new HashSet();
+ private final Set<String> served = new HashSet<String>();
private Authorizable next;
private NodeIterator authNodeIter;
@@ -621,4 +697,409 @@
return null;
}
}
+
+ //--------------------------------------------------------------------------
+ /**
+ * Inner class creating and finding the JCR nodes corresponding the a given
+ * authorizable ID with the following behavior:
+ * <ul>
+ * <li>Users are created below /rep:security/rep:authorizables/rep:users</li>
+ * <li>Groups are created below /rep:security/rep:authorizables/rep:users</li>
+ * <li>Below each category authorizables are created within a human readable
+ * structure, whose depth is defined by the <code>defaultDepth</code> config
+ * option.<br>
+ * E.g. creating a user node for an ID 'aSmith' would result in the following
+ * structure assuming defaultDepth == 2 is used:
+ * <pre>
+ * + rep:security [nt:unstructured]
+ * + rep:authorizables [rep:AuthorizableFolder]
+ * + rep:users [rep:AuthorizableFolder]
+ * + a [rep:AuthorizableFolder]
+ * + aS [rep:AuthorizableFolder]
+ * -> + aSmith [rep:User]
+ * </pre>
+ * </li>
+ * <li>In case of a user the node name is calculated from the specified UserID
+ * {@link Text#escapeIllegalJcrChars(String) escaping} any illegal JCR chars.
+ * In case of a Group the node name is calculated from the specified principal
+ * name circumventing any conflicts with existing ids and escaping illegal chars.</li>
+ * <li>The names of the intermediate folders are caculated from the leading
+ * chars of the escaped node name.</li>
+ * <li>If the escaped node name is shorter than the <code>defaultDepth</code>
+ * the last char is repeated.<br>
+ * E.g. creating a user node for an ID 'a' would result in the following
+ * structure assuming defaultDepth == 2 is used:
+ * <pre>
+ * + rep:security [nt:unstructured]
+ * + rep:authorizables [rep:AuthorizableFolder]
+ * + rep:users [rep:AuthorizableFolder]
+ * + a [rep:AuthorizableFolder]
+ * + aa [rep:AuthorizableFolder]
+ * -> + a [rep:User]
+ * </pre>
+ * </li>
+ * <li>If the <code>autoExpandTree</code> option is <code>true</code> the
+ * user tree will be automatically expanded using additional levels if
+ * <code>autoExpandSize</code> is exceeded within a given level.</li>
+ * </ul>
+ *
+ * The auto-expansion of the authorizable tree is defined by the following
+ * steps and exceptional cases:
+ * <ul>
+ * <li>As long as <code>autoExpandSize</code> isn't reached authorizable
+ * nodes are created within the structure defined by the
+ * <code>defaultDepth</code>. (see above)</li>
+ * <li>If <code>autoExpandSize</code> is reached additional intermediate
+ * folders will be created.<br>
+ * E.g. creating a user node for an ID 'aSmith1001' would result in the
+ * following structure:
+ * <pre>
+ * + rep:security [nt:unstructured]
+ * + rep:authorizables [rep:AuthorizableFolder]
+ * + rep:users [rep:AuthorizableFolder]
+ * + a [rep:AuthorizableFolder]
+ * + aS [rep:AuthorizableFolder]
+ * + aSmith1 [rep:User]
+ * + aSmith2 [rep:User]
+ * [...]
+ * + aSmith1000 [rep:User]
+ * -> + aSm [rep:AuthorizableFolder]
+ * -> + aSmith1001 [rep:User]
+ * </pre>
+ * </li>
+ * <li>Conflicts: In order to prevent any conflicts that would arise from
+ * creating a authorizable node that upon later expansion could conflict
+ * with an authorizable folder, intermediate levels are always created if
+ * the node name equals any of the names reserved for the next level of
+ * folders.<br>
+ * In the example above any attempt to create a user with ID 'aSm' would
+ * result in an intermediate level irrespective if max-size has been
+ * reached or not:
+ * <pre>
+ * + rep:security [nt:unstructured]
+ * + rep:authorizables [rep:AuthorizableFolder]
+ * + rep:users [rep:AuthorizableFolder]
+ * + a [rep:AuthorizableFolder]
+ * + aS [rep:AuthorizableFolder]
+ * -> + aSm [rep:AuthorizableFolder]
+ * -> + aSm [rep:User]
+ * </pre>
+ * </li>
+ * <li>Special case: If the name of the authorizable node to be created is
+ * shorter or equal to the length of the folder at level N, the authorizable
+ * node is created even if max-size has been reached before.<br>
+ * An attempt to create the users 'aS' and 'aSm' in a structure containing
+ * tons of 'aSmith' users will therefore result in:
+ * <pre>
+ * + rep:security [nt:unstructured]
+ * + rep:authorizables [rep:AuthorizableFolder]
+ * + rep:users [rep:AuthorizableFolder]
+ * + a [rep:AuthorizableFolder]
+ * + aS [rep:AuthorizableFolder]
+ * + aSmith1 [rep:User]
+ * + aSmith2 [rep:User]
+ * [...]
+ * + aSmith1000 [rep:User]
+ * -> + aS [rep:User]
+ * + aSm [rep:AuthorizableFolder]
+ * + aSmith1001 [rep:User]
+ * -> + aSm [rep:User]
+ * </pre>
+ * </li>
+ * <li>Special case: If <code>autoExpandTree</code> is enabled later on
+ * AND any of the existing authorizable nodes collides with an intermediate
+ * folder to be created the auto-expansion is aborted and the new
+ * authorizable is inserted at the last valid level irrespective of
+ * max-size being reached.
+ * </li>
+ * </ul>
+ *
+ * The configuration options:
+ * <ul>
+ * <li><strong>defaultDepth</strong>:<br>
+ * A positive <code>integer</code> greater than zero defining the depth of
+ * the default structure that is always created.<br>
+ * Default value: 2</li>
+ * <li><strong>autoExpandTree</strong>:<br>
+ * <code>boolean</code> defining if the tree gets automatically expanded
+ * if within a level the maximum number of child nodes is reached.<br>
+ * Default value: <code>false</code></li>
+ * <li><strong>autoExpandSize</strong>:<br>
+ * A positive <code>long</code> greater than zero defining the maximum
+ * number of child nodes that are allowed at a given level.<br>
+ * Default value: 1000<br>
+ * NOTE: that total number of child nodes may still be greater that
+ * autoExpandSize.</li>
+ * </ul>
+ */
+ private class IdResolver {
+
+ private static final String DELIMITER = "/";
+ private static final int DEFAULT_DEPTH = 2;
+ private static final long DEFAULT_SIZE = 1000;
+
+ private final int defaultDepth;
+ private final boolean autoExpandTree;
+ // best effort max-size of authorizables per folder. there may be
+ // more nodes created if the editing session isn't allowed to see
+ // all child nodes.
+ private final long autoExpandSize;
+
+ private IdResolver(Properties config) {
+ int d = DEFAULT_DEPTH;
+ boolean expand = false;
+ long size = DEFAULT_SIZE;
+
+ if (config != null) {
+ if (config.containsKey(PARAM_DEFAULT_DEPTH)) {
+ try {
+ d = Integer.parseInt(config.get(PARAM_DEFAULT_DEPTH).toString());
+ if (d <= 0) {
+ log.warn("Invalid defaultDepth '" + d + "' -> using default.");
+ d = DEFAULT_DEPTH;
+ }
+ } catch (NumberFormatException e) {
+ log.warn("Unable to parse defaultDepth config parameter -> using default.", e);
+ }
+ }
+ if (config.containsKey(PARAM_AUTO_EXPAND_TREE)) {
+ expand = Boolean.parseBoolean(config.get(PARAM_AUTO_EXPAND_TREE).toString());
+ }
+ if (config.containsKey(PARAM_AUTO_EXPAND_SIZE)) {
+ try {
+ size = Integer.parseInt(config.get(PARAM_AUTO_EXPAND_SIZE).toString());
+ if (expand && size <= 0) {
+ log.warn("Invalid autoExpandSize '" + size + "' -> using default.");
+ size = DEFAULT_SIZE;
+ }
+ } catch (NumberFormatException e) {
+ log.warn("Unable to parse autoExpandSize config parameter -> using default.", e);
+ }
+ }
+ }
+
+ defaultDepth = d;
+ autoExpandTree = expand;
+ autoExpandSize = size;
+ }
+
+ public Node createUserNode(String userID) throws RepositoryException {
+ return createAuthorizableNode(userID, false);
+ }
+
+ public Node createGroupNode(String groupID) throws RepositoryException {
+ return createAuthorizableNode(groupID, true);
+ }
+
+ public Node findNode(String id, boolean isGroup) throws RepositoryException {
+ String defaultFolderPath = getDefaultFolderPath(id, isGroup);
+ String escapedId = Text.escapeIllegalJcrChars(id);
+
+ if (session.nodeExists(defaultFolderPath)) {
+ Node folder = session.getNode(defaultFolderPath);
+ Name expectedNt = (isGroup) ? NT_REP_GROUP : NT_REP_USER;
+
+ // traverse the potentially existing hierarchy looking for the
+ // authorizable node.
+ int segmLength = defaultDepth +1;
+ while (folder != null) {
+ if (folder.hasNode(escapedId)) {
+ NodeImpl aNode = (NodeImpl) folder.getNode(escapedId);
+ if (aNode.isNodeType(expectedNt)) {
+ // done. found the right auth-node
+ return aNode;
+ } else {
+ folder = aNode;
+ }
+ } else {
+ // no child node with name 'escapedId' -> look for
+ // additional levels that may exist.
+ Node parent = folder;
+ folder = null;
+ if (id.length() >= segmLength) {
+ String folderName = Text.escapeIllegalJcrChars(id.substring(0, segmLength));
+ if (parent.hasNode(folderName)) {
+ NodeImpl f = (NodeImpl) parent.getNode(folderName);
+ if (f.isNodeType(NT_REP_AUTHORIZABLE_FOLDER)) {
+ folder = f;
+ } // else: matching node isn't an authorizable-folder
+ } // else: failed to find a suitable next level
+ } // else: id is shorter than required length at the current level.
+ }
+ segmLength++;
+ }
+ } // else: no node at default-path
+
+ // no matching node found -> authorizable doesn't exist.
+ return null;
+ }
+
+ private Node createAuthorizableNode(String id, boolean isGroup) throws RepositoryException {
+ String escapedId = Text.escapeIllegalJcrChars(id);
+
+ // first create the default folder nodes, that are always present.
+ Node folder = createDefaultFolderNodes(id, escapedId, isGroup);
+ // eventually create additional intermediate folders.
+ folder = createIntermediateFolderNodes(id, escapedId, folder);
+
+ // finally create the authorizable node
+ Name nodeName = session.getQName(escapedId);
+ Name ntName = (isGroup) ? NT_REP_GROUP : NT_REP_USER;
+ Node authNode = addNode((NodeImpl) folder, nodeName, ntName);
+
+ return authNode;
+ }
+
+ private Node createDefaultFolderNodes(String id, String escapedId, boolean isGroup) throws RepositoryException {
+ NodeImpl folder;
+ // first create the levels that are always present -> see #getDefaultFolderPath
+ String defaultPath = getDefaultFolderPath(id, isGroup);
+ if (session.nodeExists(defaultPath)) {
+ folder = (NodeImpl) session.getNode(defaultPath);
+ } else {
+ String[] segmts = defaultPath.split("/");
+ folder = (NodeImpl) session.getRootNode();
+ String repSecurity = SECURITY_ROOT_PATH.substring(1);
+
+ for (String segment : segmts) {
+ if (segment.length() < 1) {
+ continue;
+ }
+ if (folder.hasNode(segment)) {
+ folder = (NodeImpl) folder.getNode(segment);
+ } else {
+ Name ntName;
+ if (repSecurity.equals(segment)) {
+ // rep:security node
+ ntName = NameConstants.NT_UNSTRUCTURED;
+ } else {
+ ntName = NT_REP_AUTHORIZABLE_FOLDER;
+ }
+ NodeImpl added = addNode(folder, session.getQName(segment), ntName);
+ folder = added;
+ }
+ }
+ }
+
+ // validation check if authorizable to be create doesn't conflict.
+ checkAuthorizableNodeExists(escapedId, folder);
+ return folder;
+ }
+
+ private String getDefaultFolderPath(String id, boolean isGroup) {
+ StringBuilder bld = new StringBuilder();
+ if (isGroup) {
+ bld.append(GROUPS_PATH);
+ } else {
+ bld.append(USERS_PATH);
+ }
+ StringBuilder lastSegment = new StringBuilder(defaultDepth);
+ int idLength = id.length();
+ for (int i = 0; i < defaultDepth; i++) {
+ if (idLength > i) {
+ lastSegment.append(id.charAt(i));
+ } else {
+ // escapedID is too short -> append the last char again
+ lastSegment.append(id.charAt(idLength-1));
+ }
+ bld.append(DELIMITER).append(Text.escapeIllegalJcrChars(lastSegment.toString()));
+ }
+ return bld.toString();
+ }
+
+ private Node createIntermediateFolderNodes(String id, String escapedId, Node folder) throws RepositoryException {
+ if (!autoExpandTree) {
+ // additional folders are never created
+ return folder;
+ }
+
+ // additional folders needs be created if
+ // - the maximal size of child nodes is reached
+ // - if the auth-node to be created potentially collides with any
+ // of the intermediate nodes.
+ int segmLength = defaultDepth +1;
+ int idLength = id.length();
+
+ while (intermediateFolderNeeded(escapedId, folder)) {
+ String folderName = Text.escapeIllegalJcrChars(id.substring(0, segmLength));
+ if (folder.hasNode(folderName)) {
+ NodeImpl n = (NodeImpl) folder.getNode(folderName);
+ // validation check: folder must be of type rep:AuthorizableFolder
+ // and not an authorizable node.
+ if (n.isNodeType(NT_REP_AUTHORIZABLE_FOLDER)) {
+ // expected nodetype -> no violation
+ folder = n;
+ } else if (n.isNodeType(NT_REP_AUTHORIZABLE)){
+ /*
+ an authorizable node has been created before with the
+ name of the intermediate folder to be created.
+ this may only occur if the 'autoExpandTree' option has
+ been enabled later on.
+ Resolution:
+ - abort auto-expanding and create the authorizable
+ at the current level, ignoring that max-size is reached.
+ - note, that this behavior has been preferred over tmp.
+ removing and recreating the colliding authorizable node.
+ */
+ log.warn("Auto-expanding aborted. An existing authorizable node '" + n.getName() +"'conflicts with intermediate folder to be created.");
+ break;
+ } else {
+ // should never get here: some other, unexpected node type
+ String msg = "Failed to create authorizable node: Detected conflict with node of unexpected nodetype '" + n.getPrimaryNodeType().getName() + "'.";
+ log.error(msg);
+ throw new RepositoryException(msg);
+ }
+ } else {
+ // folder doesn't exist nor does another colliding child node.
+ folder = addNode((NodeImpl) folder, session.getQName(folderName), NT_REP_AUTHORIZABLE_FOLDER);
+ }
+ segmLength++;
+ }
+
+ // final validation check if authorizable to be created doesn't conflict.
+ checkAuthorizableNodeExists(escapedId, folder);
+ return folder;
+ }
+
+ private void checkAuthorizableNodeExists(String nodeName, Node folder) throws AuthorizableExistsException, RepositoryException {
+ if (folder.hasNode(nodeName) &&
+ ((NodeImpl) folder.getNode(nodeName)).isNodeType(NT_REP_AUTHORIZABLE)) {
+ throw new AuthorizableExistsException("Unable to create Group/User: Collision with existing authorizable.");
+ }
+ }
+
+ private boolean intermediateFolderNeeded(String nodeName, Node folder) throws RepositoryException {
+ // don't create additional intermediate folders for ids that are
+ // shorter or equally long as the folder name. In this case the
+ // MAX_SIZE flag is ignored.
+ if (nodeName.length() <= folder.getName().length()) {
+ return false;
+ }
+
+ // test for potential (or existing) collision in which case the
+ // intermediate node is created irrespective of the MAX_SIZE and the
+ // existing number of children.
+ if (nodeName.length() == folder.getName().length()+1) {
+ // max-size may not yet be reached yet on folder but the node to
+ // be created potentially collides with an intermediate folder.
+ // e.g.:
+ // existing folder structure: a/ab
+ // authID to be created : abt
+ // OR
+ // existing collition that would result from
+ // existing folder structure: a/ab/abt
+ // authID to be create : abt
+ return true;
+ }
+
+ // last possibility: max-size is reached.
+ if (folder.getNodes().getSize() >= autoExpandSize) {
+ return true;
+ }
+
+ // no collision and no need to create an additional intermediate
+ // folder due to max-size reached
+ return false;
+ }
+ }
}
Modified: jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/NodeStateMerger.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/NodeStateMerger.java?rev=812570&r1=812569&r2=812570&view=diff
==============================================================================
--- jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/NodeStateMerger.java (original)
+++ jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/NodeStateMerger.java Tue Sep 8 16:09:28 2009
@@ -69,8 +69,8 @@
* - either session moved node
*/
- // compare current transient state with externally modified
- // overlayed state and determine what has been changed by whom
+ // compare current state with externally modified overlayed
+ // state and determine what has been changed by whom
// child node entries order
if (!state.getReorderedChildNodeEntries().isEmpty()) {
Modified: jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/SessionItemStateManager.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/SessionItemStateManager.java?rev=812570&r1=812569&r2=812570&view=diff
==============================================================================
--- jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/SessionItemStateManager.java (original)
+++ jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/SessionItemStateManager.java Tue Sep 8 16:09:28 2009
@@ -93,14 +93,13 @@
* @param stateMgr the local item state manager
* @param ntReg node type registry
*/
- public SessionItemStateManager(NodeId rootNodeId,
+ protected SessionItemStateManager(NodeId rootNodeId,
LocalItemStateManager stateMgr,
NodeTypeRegistry ntReg) {
transientStore = new ItemStateMap();
atticStore = new ItemStateMap();
this.stateMgr = stateMgr;
- stateMgr.addListener(this);
// create hierarchy manager that uses both transient and persistent state
hierMgr = new CachingHierarchyManager(rootNodeId, this);
@@ -110,6 +109,24 @@
}
/**
+ * Creates a new <code>SessionItemStateManager</code> instance.
+ *
+ * @param rootNodeId the root node id
+ * @param stateMgr the local item state manager
+ * @param ntReg node type registry
+ * @return the session item state manager.
+ */
+ public static SessionItemStateManager createInstance(
+ NodeId rootNodeId,
+ LocalItemStateManager stateMgr,
+ NodeTypeRegistry ntReg) {
+ SessionItemStateManager mgr = new SessionItemStateManager(
+ rootNodeId, stateMgr, ntReg);
+ stateMgr.addListener(mgr);
+ return mgr;
+ }
+
+ /**
* Returns the hierarchy manager
*
* @return the hierarchy manager
@@ -313,6 +330,9 @@
* {@inheritDoc}
*/
public void dispose() {
+ // remove hierarchy manager as listener to avoid
+ // unnecessary work during stateMgr.dispose()
+ removeListener(hierMgr);
// discard all transient changes
disposeAllTransientItemStates();
// dispose our (i.e. 'local') state manager
@@ -393,13 +413,14 @@
// the depth is used as array index
List[] la = new List[10];
try {
+ HierarchyManager atticAware = getAtticAwareHierarchyMgr();
Iterator iter = transientStore.values().iterator();
while (iter.hasNext()) {
ItemState state = (ItemState) iter.next();
// determine relative depth: > 0 means it's a descendant
int depth;
try {
- depth = hierMgr.getShareRelativeDepth(parentId, state.getId());
+ depth = atticAware.getShareRelativeDepth(parentId, state.getId());
} catch (ItemNotFoundException infe) {
/**
* one of the parents of the specified item has been
Modified: jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/SharedItemStateManager.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/SharedItemStateManager.java?rev=812570&r1=812569&r2=812570&view=diff
==============================================================================
--- jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/SharedItemStateManager.java (original)
+++ jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/SharedItemStateManager.java Tue Sep 8 16:09:28 2009
@@ -148,7 +148,7 @@
* for referential integrity.
* Should be changed very carefully by experienced developers only.
*
- * @see https://issues.apache.org/jira/browse/JCR-954
+ * @see "https://issues.apache.org/jira/browse/JCR-954"
*/
private boolean checkReferences = true;
@@ -202,13 +202,14 @@
if (!hasNonVirtualItemState(rootNodeId)) {
createRootNodeState(rootNodeId, ntReg);
}
+ ensureActivitiesNode();
}
/**
* Enables or disables the referential integrity checking, this
* should be used very carefully by experienced developers only.
*
- * @see https://issues.apache.org/jira/browse/JCR-954
+ * @see "https://issues.apache.org/jira/browse/JCR-954"
* @param checkReferences whether to do referential integrity checks
*/
public void setCheckReferences(boolean checkReferences) {
@@ -1295,6 +1296,9 @@
// add child node entry for virtual jcr:versionStorage
jcrSystemState.addChildNodeEntry(NameConstants.JCR_VERSIONSTORAGE, RepositoryImpl.VERSION_STORAGE_NODE_ID);
+ // add child node entry for virtual jcr:activities
+ jcrSystemState.addChildNodeEntry(NameConstants.JCR_ACTIVITIES, RepositoryImpl.ACTIVITIES_NODE_ID);
+
// add child node entry for virtual jcr:nodeTypes
jcrSystemState.addChildNodeEntry(NameConstants.JCR_NODETYPES, RepositoryImpl.NODETYPES_NODE_ID);
@@ -1312,6 +1316,28 @@
}
/**
+ * Makes sure child node entry for mandatory jcr:activities exist.
+ * Repositories upgraded from 1.x do not have it.
+ * <p/>
+ * This method assumes that the jcr:system node already exists.
+ *
+ * @throws ItemStateException if an error occurs while reading or writing to
+ * the persistence manager.
+ */
+ private void ensureActivitiesNode() throws ItemStateException {
+ NodeState jcrSystemState = (NodeState) getNonVirtualItemState(RepositoryImpl.SYSTEM_ROOT_NODE_ID);
+ if (!jcrSystemState.hasChildNodeEntry(RepositoryImpl.ACTIVITIES_NODE_ID)) {
+ jcrSystemState.addChildNodeEntry(NameConstants.JCR_ACTIVITIES, RepositoryImpl.ACTIVITIES_NODE_ID);
+
+ ChangeLog changeLog = new ChangeLog();
+ changeLog.modified(jcrSystemState);
+
+ persistMgr.store(changeLog);
+ changeLog.persisted();
+ }
+ }
+
+ /**
* Returns the item state for the given id without considering virtual
* item state providers.
*/
Modified: jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/util/CooperativeFileLock.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/util/CooperativeFileLock.java?rev=812570&r1=812569&r2=812570&view=diff
==============================================================================
--- jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/util/CooperativeFileLock.java (original)
+++ jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/util/CooperativeFileLock.java Tue Sep 8 16:09:28 2009
@@ -16,7 +16,6 @@
*/
package org.apache.jackrabbit.core.util;
-import org.apache.jackrabbit.uuid.UUID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -27,6 +26,7 @@
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Properties;
+import java.util.UUID;
import javax.jcr.RepositoryException;
Modified: jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/value/BLOBFileValue.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/value/BLOBFileValue.java?rev=812570&r1=812569&r2=812570&view=diff
==============================================================================
--- jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/value/BLOBFileValue.java (original)
+++ jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/value/BLOBFileValue.java Tue Sep 8 16:09:28 2009
@@ -18,8 +18,6 @@
import java.io.IOException;
import java.io.InputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.UnsupportedEncodingException;
import javax.jcr.RepositoryException;
import javax.jcr.Binary;
@@ -39,48 +37,6 @@
abstract class BLOBFileValue implements Binary {
/**
- * Returns a String representation of this value.
- *
- * @return String representation of this value.
- * @throws RepositoryException
- */
- String getString() throws RepositoryException {
- // TODO: review again. currently the getString method of the JCR Value is delegated to the QValue.
- InputStream stream = getStream();
- try {
- ByteArrayOutputStream out = new ByteArrayOutputStream();
- byte[] buffer = new byte[8192];
- int read;
- while ((read = stream.read(buffer)) > 0) {
- out.write(buffer, 0, read);
- }
- byte[] data = out.toByteArray();
- return new String(data, "UTF-8");
- } catch (UnsupportedEncodingException e) {
- throw new RepositoryException("UTF-8 not supported on this platform", e);
- } catch (IOException e) {
- throw new RepositoryException("conversion from stream to string failed", e);
- } finally {
- try {
- if (stream != null) {
- stream.close();
- }
- } catch (IOException e) {
- // ignore
- }
- }
- }
-
- /**
- * Frees temporarily allocated resources such as temporary file, buffer, etc.
- * If this <code>BLOBFileValue</code> is backed by a persistent resource
- * calling this method will have no effect.
- *
- * @see #delete(boolean)
- */
- abstract void discard();
-
- /**
* Deletes the persistent resource backing this <code>BLOBFileValue</code>.
*
* @param pruneEmptyParentDirs if <code>true</code>, empty parent directories
@@ -89,12 +45,18 @@
abstract void delete(boolean pruneEmptyParentDirs);
/**
- * Checks if this object is immutable.
- * Immutable objects can not change and can safely copied.
+ * Returns a copy of this BLOB file value. The returned copy may also be
+ * this object. However an implementation must guarantee that the returned
+ * value has state that is independent from this value. Immutable values
+ * can savely return the same value (this object).
+ * <p/>
+ * Specifically, {@link #dispose()} on the returned value must not have an
+ * effect on this value!
*
- * @return true if the object is immutable
+ * @return a value that can be used independently from this value.
+ * @throws RepositoryException if an error occur while copying this value.
*/
- abstract boolean isImmutable();
+ abstract BLOBFileValue copy() throws RepositoryException;
public abstract boolean equals(Object obj);
@@ -112,9 +74,6 @@
}
//-----------------------------------------------------< javax.jcr.Binary >
- public abstract long getSize();
-
- public abstract InputStream getStream() throws RepositoryException;
public int read(byte[] b, long position) throws IOException, RepositoryException {
InputStream in = getStream();
@@ -125,9 +84,4 @@
in.close();
}
}
-
- public void dispose() {
- discard();
- }
-
}
Modified: jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/value/BLOBInDataStore.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/value/BLOBInDataStore.java?rev=812570&r1=812569&r2=812570&view=diff
==============================================================================
--- jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/value/BLOBInDataStore.java (original)
+++ jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/value/BLOBInDataStore.java Tue Sep 8 16:09:28 2009
@@ -57,7 +57,7 @@
// do nothing
}
- void discard() {
+ public void dispose() {
// do nothing
}
@@ -65,8 +65,8 @@
return identifier;
}
- boolean isImmutable() {
- return true;
+ BLOBFileValue copy() throws RepositoryException {
+ return this;
}
public boolean equals(Object obj) {
Modified: jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/value/BLOBInMemory.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/value/BLOBInMemory.java?rev=812570&r1=812569&r2=812570&view=diff
==============================================================================
--- jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/value/BLOBInMemory.java (original)
+++ jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/value/BLOBInMemory.java Tue Sep 8 16:09:28 2009
@@ -16,14 +16,12 @@
*/
package org.apache.jackrabbit.core.value;
-import org.apache.jackrabbit.uuid.Constants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.jcr.RepositoryException;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
-import java.io.UnsupportedEncodingException;
import java.util.Arrays;
/**
@@ -123,14 +121,14 @@
// the data will be garbage collected
}
- void discard() {
+ public void dispose() {
// do nothing
// this object could still be referenced
// the data will be garbage collected
}
- boolean isImmutable() {
- return true;
+ BLOBFileValue copy() throws RepositoryException {
+ return this;
}
public long getSize() {
@@ -141,22 +139,13 @@
return new ByteArrayInputStream(data);
}
- String getString() throws RepositoryException {
- try {
- return new String(data, "UTF-8");
- } catch (UnsupportedEncodingException e) {
- throw new RepositoryException("UTF-8 not supported on this platform", e);
- }
- }
-
public String toString() {
StringBuilder buff = new StringBuilder(PREFIX.length() + 2 * data.length);
buff.append(PREFIX);
- char[] hex = Constants.hexDigits;
for (int i = 0; i < data.length; i++) {
int c = data[i] & 0xff;
- buff.append(hex[c >> 4]);
- buff.append(hex[c & 0xf]);
+ buff.append(Integer.toHexString(c >> 4));
+ buff.append(Integer.toHexString(c & 0xf));
}
return buff.toString();
}
Modified: jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/value/BLOBInResource.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/value/BLOBInResource.java?rev=812570&r1=812569&r2=812570&view=diff
==============================================================================
--- jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/value/BLOBInResource.java (original)
+++ jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/value/BLOBInResource.java Tue Sep 8 16:09:28 2009
@@ -92,13 +92,12 @@
}
- void discard() {
+ public void dispose() {
// this instance is not backed by temporarily allocated resource/buffer
}
- boolean isImmutable() {
- // delete will modify the state.
- return false;
+ BLOBFileValue copy() throws RepositoryException {
+ return BLOBInTempFile.getInstance(getStream(), true);
}
public long getSize() {
Modified: jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/value/BLOBInTempFile.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/value/BLOBInTempFile.java?rev=812570&r1=812569&r2=812570&view=diff
==============================================================================
--- jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/value/BLOBInTempFile.java (original)
+++ jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/value/BLOBInTempFile.java Tue Sep 8 16:09:28 2009
@@ -92,8 +92,12 @@
* @param in the stream
* @param temp
*/
- static BLOBInTempFile getInstance(InputStream in, boolean temp) throws RepositoryException {
- return new BLOBInTempFile(in, temp);
+ static BLOBFileValue getInstance(InputStream in, boolean temp) throws RepositoryException {
+ if (temp) {
+ return new RefCountingBLOBFileValue(new BLOBInTempFile(in, temp));
+ } else {
+ return new BLOBInTempFile(in, temp);
+ }
}
/**
@@ -101,7 +105,7 @@
*
* @param file the file
*/
- static BLOBInTempFile getInstance(File file, boolean temp) throws IOException {
+ static BLOBInTempFile getInstance(File file, boolean temp) {
return new BLOBInTempFile(file, temp);
}
@@ -111,15 +115,18 @@
file = null;
}
- void discard() {
+ public void dispose() {
if (temp) {
delete(true);
}
}
- boolean isImmutable() {
- // discard and delete can modify the state.
- return false;
+ BLOBFileValue copy() throws RepositoryException {
+ if (temp) {
+ return BLOBInTempFile.getInstance(getStream(), temp);
+ } else {
+ return BLOBInTempFile.getInstance(file, temp);
+ }
}
public long getSize() {
Modified: jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/value/InternalValue.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/value/InternalValue.java?rev=812570&r1=812569&r2=812570&view=diff
==============================================================================
--- jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/value/InternalValue.java (original)
+++ jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/value/InternalValue.java Tue Sep 8 16:09:28 2009
@@ -49,6 +49,7 @@
import org.apache.jackrabbit.spi.commons.value.AbstractQValueFactory;
import org.apache.jackrabbit.spi.commons.value.QValueValue;
import org.apache.jackrabbit.util.ISO8601;
+import org.apache.commons.io.IOUtils;
/**
* <code>InternalValue</code> represents the internal format of a property value.
@@ -115,7 +116,6 @@
throws ValueFormatException, RepositoryException {
switch (value.getType()) {
case PropertyType.BINARY:
- InternalValue result;
BLOBFileValue blob = null;
if (value instanceof BinaryValueImpl) {
BinaryValueImpl bin = (BinaryValueImpl) value;
@@ -131,10 +131,24 @@
}
}
if (blob == null) {
- blob = getBLOBFileValue(store, value.getBinary().getStream(), true);
+ Binary b = value.getBinary();
+ boolean dispose = false;
+ try {
+ if (b instanceof BLOBFileValue) {
+ // use as is
+ blob = (BLOBFileValue) b;
+ } else {
+ // create a copy from the stream
+ dispose = true;
+ blob = getBLOBFileValue(store, b.getStream(), true);
+ }
+ } finally {
+ if (dispose) {
+ b.dispose();
+ }
+ }
}
- result = new InternalValue(blob);
- return result;
+ return new InternalValue(blob);
case PropertyType.BOOLEAN:
return create(value.getBoolean());
case PropertyType.DATE:
@@ -323,7 +337,7 @@
* @return the internal value
* @throws RepositoryException
*/
- static InternalValue create(InputStream value, DataStore store) throws RepositoryException {
+ public static InternalValue create(InputStream value, DataStore store) throws RepositoryException {
return new InternalValue(getBLOBFileValue(store, value, false));
}
@@ -334,7 +348,7 @@
* @throws RepositoryException
*/
public static InternalValue create(InputStream value) throws RepositoryException {
- return new InternalValue(getBLOBFileValue(null, value, false));
+ return create(value, null);
}
/**
@@ -432,21 +446,8 @@
// wrapped value is immutable (and therefore this instance as well)
return this;
}
- BLOBFileValue v = (BLOBFileValue) val;
- if (v.isImmutable()) {
- return this;
- }
- // return a copy since the wrapped BLOBFileValue instance is mutable
- InputStream stream = v.getStream();
- try {
- return createTemporary(stream);
- } finally {
- try {
- stream.close();
- } catch (IOException e) {
- // ignore
- }
- }
+ // return a copy of the wrapped BLOBFileValue
+ return new InternalValue(((BLOBFileValue) val).copy());
}
/**
@@ -639,7 +640,7 @@
*/
public long getLength() throws RepositoryException {
if (PropertyType.BINARY == type) {
- return ((BLOBFileValue) val).getSize();
+ return ((Binary) val).getSize();
} else {
return super.getLength();
}
@@ -650,7 +651,14 @@
*/
public String getString() throws RepositoryException {
if (type == PropertyType.BINARY) {
- return ((BLOBFileValue) val).getString();
+ InputStream stream = getStream();
+ try {
+ return IOUtils.toString(stream, "UTF-8");
+ } catch (IOException e) {
+ throw new RepositoryException("conversion from stream to string failed", e);
+ } finally {
+ IOUtils.closeQuietly(stream);
+ }
} else if (type == PropertyType.DATE) {
return ISO8601.format(((Calendar) val));
} else {
@@ -663,7 +671,7 @@
*/
public InputStream getStream() throws RepositoryException {
if (type == PropertyType.BINARY) {
- return ((BLOBFileValue) val).getStream();
+ return ((Binary) val).getStream();
} else {
try {
// convert via string
@@ -679,7 +687,9 @@
*/
public Binary getBinary() throws RepositoryException {
if (type == PropertyType.BINARY) {
- return (BLOBFileValue) val;
+ // return an independent copy that can be disposed without
+ // affecting this value
+ return ((BLOBFileValue) val).copy();
} else {
try {
// convert via string
@@ -697,7 +707,7 @@
public void discard() {
if (type == PropertyType.BINARY) {
BLOBFileValue bfv = (BLOBFileValue) val;
- bfv.discard();
+ bfv.dispose();
} else {
super.discard();
}
Modified: jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/value/InternalValueFactory.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/value/InternalValueFactory.java?rev=812570&r1=812569&r2=812570&view=diff
==============================================================================
--- jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/value/InternalValueFactory.java (original)
+++ jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/value/InternalValueFactory.java Tue Sep 8 16:09:28 2009
@@ -18,39 +18,27 @@
import org.apache.jackrabbit.core.data.DataStore;
import org.apache.jackrabbit.core.id.NodeId;
-import org.apache.jackrabbit.spi.Name;
-import org.apache.jackrabbit.spi.Path;
import org.apache.jackrabbit.spi.QValue;
import org.apache.jackrabbit.spi.QValueFactory;
-import org.apache.jackrabbit.spi.commons.name.NameFactoryImpl;
-import org.apache.jackrabbit.spi.commons.name.PathFactoryImpl;
+import org.apache.jackrabbit.spi.Name;
+import org.apache.jackrabbit.spi.Path;
import org.apache.jackrabbit.spi.commons.value.AbstractQValueFactory;
-import org.apache.jackrabbit.util.ISO8601;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import javax.jcr.PropertyType;
import javax.jcr.RepositoryException;
-import javax.jcr.ValueFormatException;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
-import java.io.UnsupportedEncodingException;
import java.io.FileInputStream;
import java.util.Calendar;
-import java.math.BigDecimal;
import java.net.URI;
+import java.math.BigDecimal;
/**
- * <code>InternalValueFactory</code>...
+ * <code>InternalValueFactory</code> implements a {@link QValueFactory} that
+ * creates {@link InternalValue} instances for binary values.
*/
public final class InternalValueFactory extends AbstractQValueFactory {
- /**
- * logger instance
- */
- private static final Logger log = LoggerFactory.getLogger(InternalValueFactory.class);
-
private static final QValueFactory INSTANCE = new InternalValueFactory(null);
private final DataStore store;
@@ -63,56 +51,6 @@
return INSTANCE;
}
- public QValue create(String value, int type) throws ValueFormatException, RepositoryException {
- if (value == null) {
- throw new IllegalArgumentException("Cannot create QValue from null value.");
- }
- try {
- switch (type) {
- case PropertyType.BOOLEAN:
- return InternalValue.create(Boolean.valueOf(value).booleanValue());
- case PropertyType.DATE: {
- Calendar cal = ISO8601.parse(value);
- if (cal == null) {
- throw new ValueFormatException("not a valid date: " + value);
- }
- return InternalValue.create(cal);
- }
- case PropertyType.DOUBLE:
- return InternalValue.create(Double.parseDouble(value));
- case PropertyType.LONG:
- return InternalValue.create(Long.parseLong(value));
- case PropertyType.DECIMAL:
- return InternalValue.create(new BigDecimal(value));
- case PropertyType.PATH:
- return InternalValue.create(PathFactoryImpl.getInstance().create(value));
- case PropertyType.NAME:
- return InternalValue.create(NameFactoryImpl.getInstance().create(value));
- case PropertyType.STRING:
- return InternalValue.create(value);
- case PropertyType.URI:
- return InternalValue.create(URI.create(value));
- case PropertyType.REFERENCE:
- return InternalValue.create(new NodeId(value));
- case PropertyType.WEAKREFERENCE:
- return InternalValue.create(new NodeId(value), true);
- case PropertyType.BINARY:
- return InternalValue.create(value.getBytes("UTF-8"));
- // default: invalid type specified -> see below.
- }
- } catch (NumberFormatException ex) {
- // given String value cannot be converted to Decimal
- throw new ValueFormatException(ex);
- } catch (IllegalArgumentException ex) {
- // given String value cannot be converted to Long/Double/Path/Name
- throw new ValueFormatException(ex);
- } catch (UnsupportedEncodingException ex) {
- throw new RepositoryException(ex);
- }
-
- // invalid type specified:
- throw new IllegalArgumentException("illegal type " + type); }
-
public QValue create(Calendar value) throws RepositoryException {
return InternalValue.create(value);
}
@@ -150,11 +88,23 @@
}
public QValue create(InputStream value) throws RepositoryException, IOException {
- return InternalValue.create(value, store);
+ if (store == null) {
+ return InternalValue.createTemporary(value);
+ } else {
+ return InternalValue.create(value, store);
+ }
}
public QValue create(File value) throws RepositoryException, IOException {
InputStream in = new FileInputStream(value);
return InternalValue.createTemporary(in);
}
+
+ protected QValue createReference(String ref, boolean weak) {
+ return InternalValue.create(new NodeId(ref), weak);
+ }
+
+ protected QValue createString(String value) {
+ return InternalValue.create(value);
+ }
}
\ No newline at end of file
Modified: jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/value/ValueFactoryImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/value/ValueFactoryImpl.java?rev=812570&r1=812569&r2=812570&view=diff
==============================================================================
--- jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/value/ValueFactoryImpl.java (original)
+++ jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/value/ValueFactoryImpl.java Tue Sep 8 16:09:28 2009
@@ -21,6 +21,7 @@
import org.apache.jackrabbit.spi.commons.conversion.NamePathResolver;
import org.apache.jackrabbit.spi.commons.value.ValueFactoryQImpl;
import org.apache.jackrabbit.spi.QValue;
+import org.apache.jackrabbit.value.BinaryImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -59,7 +60,7 @@
public Value createValue(QValue qvalue) {
if (qvalue instanceof InternalValue && PropertyType.BINARY == qvalue.getType()) {
try {
- return new BinaryValueImpl(((InternalValue) qvalue).getBLOBFileValue());
+ return new BinaryValueImpl(((InternalValue) qvalue).getBLOBFileValue().copy());
} catch (RepositoryException e) {
// should not get here
log.error(e.getMessage(), e);
@@ -68,6 +69,19 @@
return super.createValue(qvalue);
}
+ public Binary createBinary(InputStream stream) throws RepositoryException {
+ try {
+ QValue value = getQValueFactory().create(stream);
+ if (value instanceof InternalValue) {
+ return ((InternalValue) value).getBLOBFileValue();
+ } else {
+ return new BinaryImpl(stream);
+ }
+ } catch (IOException e) {
+ throw new RepositoryException(e);
+ }
+ }
+
public Value createValue(Binary binary) {
try {
if (binary instanceof BLOBInDataStore) {
@@ -77,6 +91,8 @@
// if the value is already in this data store
return new BinaryValueImpl(value.getBLOBFileValue());
}
+ } else if (binary instanceof BLOBFileValue) {
+ return new BinaryValueImpl(((BLOBFileValue) binary).copy());
}
return createValue(binary.getStream());
} catch (RepositoryException e) {
Modified: jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/DateVersionSelector.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/DateVersionSelector.java?rev=812570&r1=812569&r2=812570&view=diff
==============================================================================
--- jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/DateVersionSelector.java (original)
+++ jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/DateVersionSelector.java Tue Sep 8 16:09:28 2009
@@ -16,12 +16,12 @@
*/
package org.apache.jackrabbit.core.version;
-import javax.jcr.RepositoryException;
-import javax.jcr.version.Version;
-import javax.jcr.version.VersionHistory;
-import javax.jcr.version.VersionIterator;
import java.util.Calendar;
+import javax.jcr.RepositoryException;
+
+import org.apache.jackrabbit.spi.Name;
+
/**
* This Class implements a version selector that selects a version by creation
* date. The selected version is the latest that is older or equal than the
@@ -57,7 +57,7 @@
* Creates a <code>DateVersionSelector</code> that will select the latest
* version of all those that are older than the given date.
*
- * @param date
+ * @param date reference date
*/
public DateVersionSelector(Calendar date) {
this.date = date;
@@ -67,8 +67,8 @@
* Creates a <code>DateVersionSelector</code> that will select the latest
* version of all those that are older than the given date.
*
- * @param date
- * @param returnLatest
+ * @param date reference date
+ * @param returnLatest if <code>true</code> latest is selected
*/
public DateVersionSelector(Calendar date, boolean returnLatest) {
this.date = date;
@@ -87,7 +87,7 @@
/**
* Sets the date hint
*
- * @param date
+ * @param date reference date
*/
public void setDate(Calendar date) {
this.date = date;
@@ -107,22 +107,20 @@
* Sets the flag, if the latest version should be selected, if no
* version can be found using the given hint.
*
- * @param returnLatest
+ * @param returnLatest the <ocde>returnLatest</code> flag
*/
public void setReturnLatest(boolean returnLatest) {
this.returnLatest = returnLatest;
}
/**
+ * {@inheritDoc}
+ *
* Selects a version from the given version history using the previously
* assigned hint in the following order: name, label, date, latest.
- *
- * @param versionHistory
- * @return
- * @throws RepositoryException
*/
- public Version select(VersionHistory versionHistory) throws RepositoryException {
- Version selected = null;
+ public InternalVersion select(InternalVersionHistory versionHistory) throws RepositoryException {
+ InternalVersion selected = null;
if (date != null) {
selected = DateVersionSelector.selectByDate(versionHistory, date);
}
@@ -135,21 +133,20 @@
/**
* Selects a version by date.
*
- * @param history
- * @param date
+ * @param history history to select from
+ * @param date reference date
* @return the latest version that is older than the given date date or
* <code>null</code>
- * @throws RepositoryException
+ * @throws RepositoryException if an error occurs
*/
- public static Version selectByDate(VersionHistory history, Calendar date)
+ public static InternalVersion selectByDate(InternalVersionHistory history, Calendar date)
throws RepositoryException {
long time = (date != null) ? date.getTimeInMillis() : Long.MAX_VALUE;
long latestDate = Long.MIN_VALUE;
- Version latestVersion = null;
- VersionIterator iter = history.getAllVersions();
- while (iter.hasNext()) {
- Version v = iter.nextVersion();
- if (v.getPredecessors().length == 0) {
+ InternalVersion latestVersion = null;
+ for (Name name: history.getVersionNames()) {
+ InternalVersion v = history.getVersion(name);
+ if (v.isRootVersion()) {
// ignore root version
continue;
}
Modified: jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/InternalActivity.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/InternalActivity.java?rev=812570&r1=812569&r2=812570&view=diff
==============================================================================
--- jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/InternalActivity.java (original)
+++ jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/InternalActivity.java Tue Sep 8 16:09:28 2009
@@ -16,12 +16,8 @@
*/
package org.apache.jackrabbit.core.version;
-import java.util.Map;
-
import javax.jcr.RepositoryException;
-import org.apache.jackrabbit.core.id.NodeId;
-
/**
* This interface defines the internal activity.
*/
@@ -33,17 +29,17 @@
* @return the version
* @throws RepositoryException if an error occurs
*/
- public InternalVersion getLatestVersion(InternalVersionHistory history)
+ InternalVersion getLatestVersion(InternalVersionHistory history)
throws RepositoryException;
/**
* Returns the changeset of this activity.
* This is the set of versions that are the latest members of this activity
- * in their respective version histories. the changeset is a map grouped by
- * the nodeid of the respective histories.
+ * in their respective version histories.
+ *
* @return the changeset
* @throws RepositoryException if an error occurs
*/
- public Map<NodeId, InternalVersion> getChangeSet() throws RepositoryException;
+ VersionSet getChangeSet() throws RepositoryException;
}
\ No newline at end of file
Modified: jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/InternalActivityImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/InternalActivityImpl.java?rev=812570&r1=812569&r2=812570&view=diff
==============================================================================
--- jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/InternalActivityImpl.java (original)
+++ jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/InternalActivityImpl.java Tue Sep 8 16:09:28 2009
@@ -33,7 +33,7 @@
/**
* Implements a internal representation of an activity node.
- * this is only for the {@link XAVersionManager}.
+ * this is only for the {@link InternalXAVersionManager}.
*/
class InternalActivityImpl extends InternalVersionItemImpl implements InternalActivity {
@@ -43,7 +43,7 @@
* @param node version history node state
* @throws RepositoryException if an error occurs
*/
- public InternalActivityImpl(AbstractVersionManager vMgr, NodeStateEx node)
+ public InternalActivityImpl(InternalVersionManagerBase vMgr, NodeStateEx node)
throws RepositoryException {
super(vMgr, node);
}
@@ -156,7 +156,7 @@
/**
* {@inheritDoc}
*/
- public Map<NodeId, InternalVersion> getChangeSet() throws RepositoryException {
+ public VersionSet getChangeSet() throws RepositoryException {
Map<NodeId, InternalVersion> changeset = new HashMap<NodeId, InternalVersion>();
if (node.hasProperty(NameConstants.REP_VERSIONS)) {
for (InternalValue ref: node.getPropertyValues(NameConstants.REP_VERSIONS)) {
@@ -164,6 +164,6 @@
changeset.put(v.getVersionHistory().getId(), v);
}
}
- return changeset;
+ return new VersionSet(changeset);
}
}
\ No newline at end of file