Java tutorial
/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.jackrabbit.core; import org.apache.commons.collections.IteratorUtils; import org.apache.commons.collections.map.ReferenceMap; import org.apache.jackrabbit.commons.AbstractSession; import org.apache.jackrabbit.core.RepositoryImpl.WorkspaceInfo; import org.apache.jackrabbit.core.config.WorkspaceConfig; import org.apache.jackrabbit.core.data.GarbageCollector; import org.apache.jackrabbit.core.lock.LockManager; import org.apache.jackrabbit.core.nodetype.NodeDefinitionImpl; import org.apache.jackrabbit.core.nodetype.NodeTypeImpl; import org.apache.jackrabbit.core.nodetype.NodeTypeManagerImpl; import org.apache.jackrabbit.core.persistence.IterablePersistenceManager; import org.apache.jackrabbit.core.persistence.PersistenceManager; import org.apache.jackrabbit.core.security.AMContext; import org.apache.jackrabbit.core.security.AccessManager; import org.apache.jackrabbit.core.security.SecurityConstants; import org.apache.jackrabbit.core.security.authorization.Permission; import org.apache.jackrabbit.api.security.principal.PrincipalManager; import org.apache.jackrabbit.api.security.user.UserManager; import org.apache.jackrabbit.api.JackrabbitSession; import org.apache.jackrabbit.api.jsr283.security.AccessControlManager; import org.apache.jackrabbit.api.jsr283.retention.RetentionManager; import org.apache.jackrabbit.core.security.authentication.AuthContext; import org.apache.jackrabbit.core.state.LocalItemStateManager; import org.apache.jackrabbit.core.state.NodeState; import org.apache.jackrabbit.core.state.SessionItemStateManager; import org.apache.jackrabbit.core.state.SharedItemStateManager; import org.apache.jackrabbit.core.util.Dumpable; import org.apache.jackrabbit.core.version.VersionManager; import org.apache.jackrabbit.core.version.VersionManagerImpl; import org.apache.jackrabbit.core.xml.ImportHandler; import org.apache.jackrabbit.core.xml.SessionImporter; import org.apache.jackrabbit.core.retention.RetentionManagerImpl; import org.apache.jackrabbit.core.retention.RetentionRegistry; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.Path; import org.apache.jackrabbit.spi.commons.conversion.DefaultNamePathResolver; import org.apache.jackrabbit.spi.commons.conversion.IllegalNameException; import org.apache.jackrabbit.spi.commons.conversion.MalformedPathException; import org.apache.jackrabbit.spi.commons.conversion.NameException; import org.apache.jackrabbit.spi.commons.conversion.NamePathResolver; import org.apache.jackrabbit.spi.commons.namespace.NamespaceResolver; import org.apache.jackrabbit.uuid.UUID; import org.apache.jackrabbit.value.ValueFactoryImpl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xml.sax.ContentHandler; import org.xml.sax.InputSource; import javax.jcr.AccessDeniedException; import javax.jcr.Credentials; import javax.jcr.InvalidItemStateException; import javax.jcr.Item; import javax.jcr.ItemExistsException; import javax.jcr.ItemNotFoundException; import javax.jcr.LoginException; import javax.jcr.NamespaceException; import javax.jcr.NoSuchWorkspaceException; import javax.jcr.Node; import javax.jcr.PathNotFoundException; import javax.jcr.Property; import javax.jcr.Repository; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.SimpleCredentials; import javax.jcr.UnsupportedRepositoryOperationException; import javax.jcr.ValueFactory; import javax.jcr.Workspace; import javax.jcr.lock.LockException; import javax.jcr.lock.Lock; import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.nodetype.NoSuchNodeTypeException; import javax.jcr.observation.EventListener; import javax.jcr.observation.ObservationManager; import javax.jcr.version.VersionException; import javax.security.auth.Subject; import java.io.File; import java.io.PrintStream; import java.security.AccessControlException; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.Arrays; /** * A <code>SessionImpl</code> ... */ public class SessionImpl extends AbstractSession implements org.apache.jackrabbit.api.jsr283.Session, JackrabbitSession, NamespaceResolver, NamePathResolver, Dumpable { private static Logger log = LoggerFactory.getLogger(SessionImpl.class); /** * @deprecated Use {@link org.apache.jackrabbit.api.jsr283.Session#ACTION_READ} instead. */ public static final String READ_ACTION = org.apache.jackrabbit.api.jsr283.Session.ACTION_READ; /** * @deprecated Use {@link org.apache.jackrabbit.api.jsr283.Session#ACTION_REMOVE} instead. */ public static final String REMOVE_ACTION = org.apache.jackrabbit.api.jsr283.Session.ACTION_REMOVE; /** * @deprecated Use {@link org.apache.jackrabbit.api.jsr283.Session#ACTION_ADD_NODE} instead. */ public static final String ADD_NODE_ACTION = org.apache.jackrabbit.api.jsr283.Session.ACTION_ADD_NODE; /** * @deprecated Use {@link org.apache.jackrabbit.api.jsr283.Session#ACTION_SET_PROPERTY} instead. */ public static final String SET_PROPERTY_ACTION = org.apache.jackrabbit.api.jsr283.Session.ACTION_SET_PROPERTY; /** * flag indicating whether this session is alive */ protected boolean alive; /** * the repository that issued this session */ protected final RepositoryImpl rep; /** * the AuthContext of this session (can be null if this * session was not instantiated through a login process) */ protected AuthContext loginContext; /** * the Subject of this session */ protected final Subject subject; /** * the user ID that was used to acquire this session */ protected final String userId; /** * the attributes of this session */ protected final HashMap attributes = new HashMap(); /** * the node type manager */ protected final NodeTypeManagerImpl ntMgr; /** * the AccessManager associated with this session */ protected AccessManager accessMgr; /** * the item state mgr associated with this session */ protected final SessionItemStateManager itemStateMgr; /** * the HierarchyManager associated with this session */ protected final HierarchyManager hierMgr; /** * the item mgr associated with this session */ protected final ItemManager itemMgr; /** * the Workspace associated with this session */ protected final WorkspaceImpl wsp; /** * Name and Path resolver */ protected NamePathResolver namePathResolver; /** * The version manager for this session */ protected final VersionManager versionMgr; /** * Listeners (weak references) */ protected final Map listeners = new ReferenceMap(ReferenceMap.WEAK, ReferenceMap.WEAK); /** * value factory */ protected ValueFactory valueFactory; /** * Principal Manager */ private PrincipalManager principalManager; /** * User Manager */ private UserManager userManager; /** * Retention and Hold Manager */ private RetentionManager retentionManager; /** * The stack trace knows who opened this session. It is logged * if the session is finalized, but Session.logout() was never called. */ private Exception openStackTrace = new Exception("Stack Trace"); /** * Internal helper class for common validation checks (lock status, checkout * status, protection etc. etc.) */ private ItemValidator validator; /** * Protected constructor. * * @param rep * @param loginContext * @param wspConfig * @throws AccessDeniedException if the subject of the given login context * is not granted access to the specified * workspace * @throws RepositoryException if another error occurs */ protected SessionImpl(RepositoryImpl rep, AuthContext loginContext, WorkspaceConfig wspConfig) throws AccessDeniedException, RepositoryException { this(rep, loginContext.getSubject(), wspConfig); this.loginContext = loginContext; } /** * Protected constructor. * * @param rep * @param subject * @param wspConfig * @throws AccessDeniedException if the given subject is not granted access * to the specified workspace * @throws RepositoryException if another error occurs */ protected SessionImpl(RepositoryImpl rep, Subject subject, WorkspaceConfig wspConfig) throws AccessDeniedException, RepositoryException { alive = true; this.rep = rep; this.subject = subject; userId = retrieveUserId(subject); namePathResolver = new DefaultNamePathResolver(this, true); ntMgr = new NodeTypeManagerImpl(rep.getNodeTypeRegistry(), this, rep.getDataStore()); String wspName = wspConfig.getName(); wsp = createWorkspaceInstance(wspConfig, rep.getWorkspaceStateManager(wspName), rep, this); itemStateMgr = createSessionItemStateManager(wsp.getItemStateManager()); hierMgr = itemStateMgr.getHierarchyMgr(); itemMgr = createItemManager(itemStateMgr, hierMgr); accessMgr = createAccessManager(subject, itemStateMgr.getHierarchyMgr()); versionMgr = createVersionManager(rep); } /** * Retrieve the userID from the specified subject. * * @return the userID. */ protected String retrieveUserId(Subject subject) throws RepositoryException { return rep.getSecurityManager().getUserID(subject); } /** * Create the session item state manager. * * @return session item state manager */ protected SessionItemStateManager createSessionItemStateManager(LocalItemStateManager manager) { return new SessionItemStateManager(rep.getRootNodeId(), manager, rep.getNodeTypeRegistry()); } /** * Creates the workspace instance backing this session. * * @param wspConfig The workspace configuration * @param stateMgr The shared item state manager * @param rep The repository * @param session The session * @return An instance of the {@link WorkspaceImpl} class or an extension * thereof. */ protected WorkspaceImpl createWorkspaceInstance(WorkspaceConfig wspConfig, SharedItemStateManager stateMgr, RepositoryImpl rep, SessionImpl session) { return new WorkspaceImpl(wspConfig, stateMgr, rep, session); } /** * Create the item manager. * @return item manager */ protected ItemManager createItemManager(SessionItemStateManager itemStateMgr, HierarchyManager hierMgr) { return new ItemManager(itemStateMgr, hierMgr, this, ntMgr.getRootNodeDefinition(), rep.getRootNodeId()); } /** * Create the version manager. If we are not using XA, we may safely use * the repository version manager. * @return version manager */ protected VersionManager createVersionManager(RepositoryImpl rep) throws RepositoryException { return rep.getVersionManager(); } /** * Create the access manager. * * @param subject * @param hierarchyManager * @return access manager * @throws AccessDeniedException if the current subject is not granted access * to the current workspace * @throws RepositoryException if the access manager cannot be instantiated */ protected AccessManager createAccessManager(Subject subject, HierarchyManager hierarchyManager) throws AccessDeniedException, RepositoryException { String wspName = getWorkspace().getName(); AMContext ctx = new AMContext(new File(rep.getConfig().getHomeDir()), rep.getFileSystem(), this, getSubject(), hierarchyManager, this, wspName); return rep.getSecurityManager().getAccessManager(this, ctx); } /** * Performs a sanity check on this session. * * @throws RepositoryException if this session has been rendered invalid * for some reason (e.g. if this session has * been closed explicitly or if it has expired) */ protected void sanityCheck() throws RepositoryException { // check session status if (!alive) { throw new RepositoryException("this session has been closed"); } } /** * @return ItemValidator instance for this session. * @throws RepositoryException If an error occurs. */ public synchronized ItemValidator getValidator() throws RepositoryException { if (validator == null) { validator = new ItemValidator(rep.getNodeTypeRegistry(), getHierarchyManager(), this); } return validator; } /** * Returns the <code>Subject</code> associated with this session. * * @return the <code>Subject</code> associated with this session */ public Subject getSubject() { return subject; } /** * Creates a new session with the same subject as this sessions but to a * different workspace. The returned session is a newly logged in session, * with the same subject but a different workspace. Even if the given * workspace is the same as this sessions one, the implementation must * return a new session object. * * @param workspaceName name of the workspace to acquire a session for. * @return A session to the requested workspace for the same authenticated * subject. * @throws AccessDeniedException in case the current Subject is not allowed * to access the requested Workspace * @throws NoSuchWorkspaceException If the named workspace does not exist. * @throws RepositoryException in any other exceptional state */ public Session createSession(String workspaceName) throws AccessDeniedException, NoSuchWorkspaceException, RepositoryException { if (workspaceName == null) { workspaceName = rep.getConfig().getDefaultWorkspaceName(); } if (loginContext != null) { return rep.createSession(loginContext, workspaceName); } else { return rep.createSession(getSubject(), workspaceName); } } /** * Returns the <code>AccessManager</code> associated with this session. * * @return the <code>AccessManager</code> associated with this session */ public AccessManager getAccessManager() { return accessMgr; } /** * Returns the <code>NodeTypeManager</code>. * * @return the <code>NodeTypeManager</code> */ public NodeTypeManagerImpl getNodeTypeManager() { return ntMgr; } /** * Returns the <code>ItemManager</code> of this session. * * @return the <code>ItemManager</code> */ public ItemManager getItemManager() { return itemMgr; } /** * Returns the <code>SessionItemStateManager</code> associated with this session. * * @return the <code>SessionItemStateManager</code> associated with this session */ protected SessionItemStateManager getItemStateManager() { return itemStateMgr; } /** * Returns the <code>HierarchyManager</code> associated with this session. * * @return the <code>HierarchyManager</code> associated with this session */ public HierarchyManager getHierarchyManager() { return hierMgr; } /** * Returns the <code>VersionManager</code> associated with this session. * * @return the <code>VersionManager</code> associated with this session */ public VersionManager getVersionManager() { return versionMgr; } /** * Returns the internal retention manager used for evaluation of effective * retention policies and holds. * * @return internal retention manager * @throws RepositoryException */ protected RetentionRegistry getRetentionRegistry() throws RepositoryException { return wsp.getRetentionRegistry(); } /** * Sets the named attribute. If the value is <code>null</code>, then * the named attribute is removed. * * @see <a href="https://issues.apache.org/jira/browse/JCR-1932">JCR-1932</a> * @param name attribute name * @param value attribute value * @since Apache Jackrabbit 1.6 */ protected void setAttribute(String name, Object value) { if (value != null) { attributes.put(name, value); } else { attributes.remove(name); } } /** * Retrieves the referenceable node with the given <code>UUID</code>. * * @param uuid uuid of the node to be retrieved * @return referenceable node with the given uuid * @throws ItemNotFoundException if no node exists with the given uuid or * if the existing node is not referenceable. * @throws RepositoryException if another error occurs. * @see #getNodeByUUID(String) * @see #getNodeById(NodeId) */ public Node getNodeByUUID(UUID uuid) throws ItemNotFoundException, RepositoryException { NodeImpl node = getNodeById(new NodeId(uuid)); // since the uuid of a node is only exposed through jcr:uuid declared // by mix:referenceable it's rather unlikely that a client can possibly // know the internal uuid of a non-referenceable node; omitting the // check for mix:referenceable seems therefore to be a reasonable // compromise in order to improve performance. /* if (node.isNodeType(Name.MIX_REFERENCEABLE)) { return node; } else { // there is a node with that uuid but the node does not expose it throw new ItemNotFoundException(uuid.toString()); } */ return node; } /** * Retrieves the <code>Node</code> with the given id. * * @param id id of node to be retrieved * @return node with the given <code>NodeId</code>. * @throws ItemNotFoundException if no such node exists or if this * <code>Session</code> does not have permission to access the node. * @throws RepositoryException if another error occurs. */ public NodeImpl getNodeById(NodeId id) throws ItemNotFoundException, RepositoryException { // check sanity of this session sanityCheck(); try { return (NodeImpl) getItemManager().getItem(id); } catch (AccessDeniedException ade) { throw new ItemNotFoundException(id.toString()); } } /** * Returns the names of all workspaces of this repository with respect of the * access rights of this session. * * @return the names of all accessible workspaces * @throws RepositoryException if an error occurs */ protected String[] getWorkspaceNames() throws RepositoryException { // filter workspaces according to access rights ArrayList list = new ArrayList(); String[] names = rep.getWorkspaceNames(); for (int i = 0; i < names.length; i++) { try { if (getAccessManager().canAccess(names[i])) { list.add(names[i]); } } catch (NoSuchWorkspaceException nswe) { // should never happen, ignore... } } return (String[]) list.toArray(new String[list.size()]); } /** * Creates a workspace with the given name. * * @param workspaceName name of the new workspace * @throws AccessDeniedException if the current session is not allowed to * create the workspace * @throws RepositoryException if a workspace with the given name * already exists or if another error occurs */ protected void createWorkspace(String workspaceName) throws AccessDeniedException, RepositoryException { // @todo verify that this session has the right privileges for this operation rep.createWorkspace(workspaceName); } /** * Creates a workspace with the given name and a workspace configuration * template. * * @param workspaceName name of the new workspace * @param configTemplate the configuration template of the new workspace * @throws AccessDeniedException if the current session is not allowed to * create the workspace * @throws RepositoryException if a workspace with the given name already * exists or if another error occurs */ protected void createWorkspace(String workspaceName, InputSource configTemplate) throws AccessDeniedException, RepositoryException { // @todo verify that this session has the right privileges for this operation rep.createWorkspace(workspaceName, configTemplate); } /** * Notify the listeners that this session is about to be closed. */ protected void notifyLoggingOut() { // copy listeners to array to avoid ConcurrentModificationException SessionListener[] la = (SessionListener[]) listeners.values() .toArray(new SessionListener[listeners.size()]); for (int i = 0; i < la.length; i++) { if (la[i] != null) { la[i].loggingOut(this); } } } /** * Notify the listeners that this session has been closed. */ protected void notifyLoggedOut() { // copy listeners to array to avoid ConcurrentModificationException SessionListener[] la = (SessionListener[]) listeners.values() .toArray(new SessionListener[listeners.size()]); for (int i = 0; i < la.length; i++) { if (la[i] != null) { la[i].loggedOut(this); } } } /** * Add a <code>SessionListener</code> * * @param listener the new listener to be informed on modifications */ public void addListener(SessionListener listener) { if (!listeners.containsKey(listener)) { listeners.put(listener, listener); } } /** * Remove a <code>SessionListener</code> * * @param listener an existing listener */ public void removeListener(SessionListener listener) { listeners.remove(listener); } /** * Create a data store garbage collector for this repository. * * @throws RepositoryException */ public GarbageCollector createDataStoreGarbageCollector() throws RepositoryException { ArrayList pmList = new ArrayList(); VersionManagerImpl vm = (VersionManagerImpl) rep.getVersionManager(); PersistenceManager pm = vm.getPersistenceManager(); pmList.add(pm); String[] wspNames = rep.getWorkspaceNames(); Session[] sessions = new Session[wspNames.length]; for (int i = 0; i < wspNames.length; i++) { String wspName = wspNames[i]; WorkspaceInfo wspInfo = rep.getWorkspaceInfo(wspName); // this will initialize the workspace if required wspInfo.getSystemSession(); SessionImpl session = SystemSession.create(rep, wspInfo.getConfig()); // mark this session as 'active' so the workspace does not get disposed // by the workspace-janitor until the garbage collector is done rep.onSessionCreated(session); sessions[i] = session; pm = wspInfo.getPersistenceManager(); pmList.add(pm); } IterablePersistenceManager[] ipmList = new IterablePersistenceManager[pmList.size()]; for (int i = 0; i < pmList.size(); i++) { pm = (PersistenceManager) pmList.get(i); if (!(pm instanceof IterablePersistenceManager)) { ipmList = null; break; } ipmList[i] = (IterablePersistenceManager) pm; } GarbageCollector gc = new GarbageCollector(this, ipmList, sessions); return gc; } //---------------------------------------------------< NamespaceResolver > public String getPrefix(String uri) throws NamespaceException { try { return getNamespacePrefix(uri); } catch (NamespaceException e) { throw e; } catch (RepositoryException e) { throw new NamespaceException("Namespace not found: " + uri, e); } } public String getURI(String prefix) throws NamespaceException { try { return getNamespaceURI(prefix); } catch (NamespaceException e) { throw e; } catch (RepositoryException e) { throw new NamespaceException("Namespace not found: " + prefix, e); } } //--------------------------------------------------------< NameResolver > public String getJCRName(Name name) throws NamespaceException { return namePathResolver.getJCRName(name); } public Name getQName(String name) throws IllegalNameException, NamespaceException { return namePathResolver.getQName(name); } //--------------------------------------------------------< PathResolver > public String getJCRPath(Path path) throws NamespaceException { return namePathResolver.getJCRPath(path); } public Path getQPath(String path) throws MalformedPathException, IllegalNameException, NamespaceException { return namePathResolver.getQPath(path); } //----------------------------------------------------< JackrabbitSession > /** * @see JackrabbitSession#getPrincipalManager() */ public PrincipalManager getPrincipalManager() throws RepositoryException, AccessDeniedException { if (principalManager == null) { principalManager = rep.getSecurityManager().getPrincipalManager(this); } return principalManager; } /** * @see JackrabbitSession#getUserManager() */ public UserManager getUserManager() throws AccessDeniedException, RepositoryException { if (userManager == null) { userManager = rep.getSecurityManager().getUserManager(this); } return userManager; } //--------------------------------------------------------------< Session > /** * {@inheritDoc} */ public void checkPermission(String absPath, String actions) throws AccessControlException, RepositoryException { if (!hasPermission(absPath, actions)) { throw new AccessControlException(actions); } } /** * {@inheritDoc} */ public Workspace getWorkspace() { return wsp; } /** * {@inheritDoc} */ public Session impersonate(Credentials otherCredentials) throws LoginException, RepositoryException { // check sanity of this session sanityCheck(); if (!(otherCredentials instanceof SimpleCredentials)) { String msg = "impersonate failed: incompatible credentials, SimpleCredentials expected"; log.debug(msg); throw new RepositoryException(msg); } // set IMPERSONATOR_ATTRIBUTE attribute of given credentials // with subject of current session SimpleCredentials creds = (SimpleCredentials) otherCredentials; creds.setAttribute(SecurityConstants.IMPERSONATOR_ATTRIBUTE, subject); try { return rep.login(otherCredentials, getWorkspace().getName()); } catch (NoSuchWorkspaceException nswe) { // should never get here... String msg = "impersonate failed"; log.error(msg, nswe); throw new RepositoryException(msg, nswe); } finally { // make sure IMPERSONATOR_ATTRIBUTE is removed creds.removeAttribute(SecurityConstants.IMPERSONATOR_ATTRIBUTE); } } /** * {@inheritDoc} */ public Node getRootNode() throws RepositoryException { // check sanity of this session sanityCheck(); return getItemManager().getRootNode(); } /** * {@inheritDoc} */ public Node getNodeByUUID(String uuid) throws ItemNotFoundException, RepositoryException { try { return getNodeByUUID(UUID.fromString(uuid)); } catch (IllegalArgumentException e) { // Assuming the exception is from UUID.fromString() throw new RepositoryException("Invalid UUID: " + uuid, e); } } /** * {@inheritDoc} */ public Item getItem(String absPath) throws PathNotFoundException, RepositoryException { // check sanity of this session sanityCheck(); try { Path p = getQPath(absPath).getNormalizedPath(); if (!p.isAbsolute()) { throw new RepositoryException("not an absolute path: " + absPath); } return getItemManager().getItem(p); } catch (AccessDeniedException ade) { throw new PathNotFoundException(absPath); } catch (NameException e) { String msg = "invalid path:" + absPath; log.debug(msg); throw new RepositoryException(msg, e); } } /** * {@inheritDoc} */ public boolean itemExists(String absPath) throws RepositoryException { // check sanity of this session sanityCheck(); try { Path p = getQPath(absPath).getNormalizedPath(); if (!p.isAbsolute()) { throw new RepositoryException("not an absolute path: " + absPath); } return getItemManager().itemExists(p); } catch (NameException e) { String msg = "invalid path:" + absPath; log.debug(msg); throw new RepositoryException(msg, e); } } /** * {@inheritDoc} */ public void save() throws AccessDeniedException, ItemExistsException, ConstraintViolationException, InvalidItemStateException, VersionException, LockException, NoSuchNodeTypeException, RepositoryException { // check sanity of this session sanityCheck(); getItemManager().getRootNode().save(); } /** * {@inheritDoc} */ public void refresh(boolean keepChanges) throws RepositoryException { // check sanity of this session sanityCheck(); if (!keepChanges) { // optimization itemStateMgr.disposeAllTransientItemStates(); return; } getItemManager().getRootNode().refresh(keepChanges); } /** * {@inheritDoc} */ public boolean hasPendingChanges() throws RepositoryException { // check sanity of this session sanityCheck(); return itemStateMgr.hasAnyTransientItemStates(); } /** * {@inheritDoc} */ public void move(String srcAbsPath, String destAbsPath) throws ItemExistsException, PathNotFoundException, VersionException, ConstraintViolationException, LockException, RepositoryException { // check sanity of this session sanityCheck(); // check paths & get node instances Path srcPath; Path.Element srcName; Path srcParentPath; NodeImpl targetNode; NodeImpl srcParentNode; try { srcPath = getQPath(srcAbsPath).getNormalizedPath(); if (!srcPath.isAbsolute()) { throw new RepositoryException("not an absolute path: " + srcAbsPath); } srcName = srcPath.getNameElement(); srcParentPath = srcPath.getAncestor(1); targetNode = getItemManager().getNode(srcPath); srcParentNode = getItemManager().getNode(srcParentPath); } catch (AccessDeniedException ade) { throw new PathNotFoundException(srcAbsPath); } catch (NameException e) { String msg = srcAbsPath + ": invalid path"; log.debug(msg); throw new RepositoryException(msg, e); } Path destPath; Path.Element destName; Path destParentPath; NodeImpl destParentNode; try { destPath = getQPath(destAbsPath).getNormalizedPath(); if (!destPath.isAbsolute()) { throw new RepositoryException("not an absolute path: " + destAbsPath); } if (srcPath.isAncestorOf(destPath)) { String msg = destAbsPath + ": invalid destination path (cannot be descendant of source path)"; log.debug(msg); throw new RepositoryException(msg); } destName = destPath.getNameElement(); destParentPath = destPath.getAncestor(1); destParentNode = getItemManager().getNode(destParentPath); } catch (AccessDeniedException ade) { throw new PathNotFoundException(destAbsPath); } catch (NameException e) { String msg = destAbsPath + ": invalid path"; log.debug(msg); throw new RepositoryException(msg, e); } if (hierMgr.isShareAncestor(targetNode.getNodeId(), destParentNode.getNodeId())) { String msg = destAbsPath + ": invalid destination path (share cycle detected)"; log.debug(msg); throw new RepositoryException(msg); } int ind = destName.getIndex(); if (ind > 0) { // subscript in name element String msg = destAbsPath + ": invalid destination path (subscript in name element is not allowed)"; log.debug(msg); throw new RepositoryException(msg); } // check for name collisions NodeImpl existing = null; try { existing = getItemManager().getNode(destPath); // there's already a node with that name: // check same-name sibling setting of existing node if (!existing.getDefinition().allowsSameNameSiblings()) { throw new ItemExistsException("Same name siblings are not allowed: " + existing); } } catch (AccessDeniedException ade) { // FIXME by throwing ItemExistsException we're disclosing too much information throw new ItemExistsException(destAbsPath); } catch (PathNotFoundException pnfe) { // no name collision, fall through } // verify for both source and destination parent nodes that // - they are checked-out // - are not protected neither by node type constraints nor by retention/hold int options = ItemValidator.CHECK_VERSIONING | ItemValidator.CHECK_LOCK | ItemValidator.CHECK_CONSTRAINTS | ItemValidator.CHECK_HOLD | ItemValidator.CHECK_RETENTION; getValidator().checkRemove(srcParentNode, options, Permission.NONE); getValidator().checkModify(destParentNode, options, Permission.NONE); // check constraints // get applicable definition of target node at new location NodeTypeImpl nt = (NodeTypeImpl) targetNode.getPrimaryNodeType(); NodeDefinitionImpl newTargetDef; try { newTargetDef = destParentNode.getApplicableChildNodeDefinition(destName.getName(), nt.getQName()); } catch (RepositoryException re) { String msg = destAbsPath + ": no definition found in parent node's node type for new node"; log.debug(msg); throw new ConstraintViolationException(msg, re); } // if there's already a node with that name also check same-name sibling // setting of new node; just checking same-name sibling setting on // existing node is not sufficient since same-name sibling nodes don't // necessarily have identical definitions if (existing != null && !newTargetDef.allowsSameNameSiblings()) { throw new ItemExistsException("Same name siblings not allowed: " + existing); } NodeId targetId = targetNode.getNodeId(); int index = srcName.getIndex(); if (index == 0) { index = 1; } // check permissions AccessManager acMgr = getAccessManager(); if (!(acMgr.isGranted(srcPath, Permission.REMOVE_NODE) && acMgr.isGranted(destPath, Permission.ADD_NODE | Permission.NODE_TYPE_MNGMT))) { String msg = "Not allowed to move node " + srcAbsPath + " to " + destAbsPath; log.debug(msg); throw new AccessDeniedException(msg); } if (srcParentNode.isSame(destParentNode)) { // do rename destParentNode.renameChildNode(srcName.getName(), index, targetId, destName.getName()); } else { // check shareable case if (((NodeState) targetNode.getItemState()).isShareable()) { String msg = "Moving a shareable node is not supported."; log.debug(msg); throw new UnsupportedRepositoryOperationException(msg); } // do move: // 1. remove child node entry from old parent NodeState srcParentState = (NodeState) srcParentNode.getOrCreateTransientItemState(); srcParentState.removeChildNodeEntry(srcName.getName(), index); // 2. re-parent target node NodeState targetState = (NodeState) targetNode.getOrCreateTransientItemState(); targetState.setParentId(destParentNode.getNodeId()); // 3. add child node entry to new parent NodeState destParentState = (NodeState) destParentNode.getOrCreateTransientItemState(); destParentState.addChildNodeEntry(destName.getName(), targetId); } // change definition of target targetNode.onRedefine(newTargetDef.unwrap().getId()); } /** * {@inheritDoc} */ public ContentHandler getImportContentHandler(String parentAbsPath, int uuidBehavior) throws PathNotFoundException, ConstraintViolationException, VersionException, LockException, RepositoryException { // check sanity of this session sanityCheck(); NodeImpl parent; try { Path p = getQPath(parentAbsPath).getNormalizedPath(); if (!p.isAbsolute()) { throw new RepositoryException("not an absolute path: " + parentAbsPath); } parent = getItemManager().getNode(p); } catch (NameException e) { String msg = parentAbsPath + ": invalid path"; log.debug(msg); throw new RepositoryException(msg, e); } catch (AccessDeniedException ade) { throw new PathNotFoundException(parentAbsPath); } // verify that parent node is checked-out, not locked and not protected // by either node type constraints nor by some retention or hold. int options = ItemValidator.CHECK_LOCK | ItemValidator.CHECK_VERSIONING | ItemValidator.CHECK_CONSTRAINTS | ItemValidator.CHECK_HOLD | ItemValidator.CHECK_RETENTION; getValidator().checkModify(parent, options, Permission.NONE); SessionImporter importer = new SessionImporter(parent, this, uuidBehavior); return new ImportHandler(importer, this); } /** * {@inheritDoc} */ public boolean isLive() { return alive; } /** * Utility method that removes all registered event listeners. */ private void removeRegisteredEventListeners() { try { ObservationManager manager = getWorkspace().getObservationManager(); // Use a copy to avoid modifying the set of registered listeners // while iterating over it Collection listeners = IteratorUtils.toList(manager.getRegisteredEventListeners()); Iterator iterator = listeners.iterator(); while (iterator.hasNext()) { EventListener listener = (EventListener) iterator.next(); try { manager.removeEventListener(listener); } catch (RepositoryException e) { log.warn("Error removing event listener: " + listener, e); } } } catch (RepositoryException e) { log.warn("Error removing event listeners", e); } } /** * {@inheritDoc} */ public synchronized void logout() { if (!alive) { // ignore return; } // JCR-798: Remove all registered event listeners to avoid concurrent // access to session internals by the event delivery or even listeners removeRegisteredEventListeners(); // discard any pending changes first as those might // interfere with subsequent operations itemStateMgr.disposeAllTransientItemStates(); // notify listeners that session is about to be closed notifyLoggingOut(); // dispose session item state manager itemStateMgr.dispose(); // dispose item manager itemMgr.dispose(); // dispose workspace wsp.dispose(); // invalidate session alive = false; // logout JAAS subject if (loginContext != null) { try { loginContext.logout(); } catch (javax.security.auth.login.LoginException le) { log.warn("failed to logout current subject: " + le.getMessage()); } loginContext = null; } try { accessMgr.close(); } catch (Exception e) { log.warn("error while closing AccessManager", e); } // finally notify listeners that session has been closed notifyLoggedOut(); } /** * {@inheritDoc} */ public Repository getRepository() { return rep; } /** * {@inheritDoc} */ public ValueFactory getValueFactory() throws UnsupportedRepositoryOperationException, RepositoryException { if (valueFactory == null) { valueFactory = ValueFactoryImpl.getInstance(); } return valueFactory; } /** * {@inheritDoc} */ public String getUserID() { return userId; } /** * {@inheritDoc} */ public Object getAttribute(String name) { return attributes.get(name); } /** * {@inheritDoc} */ public String[] getAttributeNames() { return (String[]) attributes.keySet().toArray(new String[attributes.size()]); } /** * {@inheritDoc} */ public void setNamespacePrefix(String prefix, String uri) throws NamespaceException, RepositoryException { super.setNamespacePrefix(prefix, uri); // Clear name and path caches namePathResolver = new DefaultNamePathResolver(this, true); } //------------------------------------------------------< locking support > /** * {@inheritDoc} */ public void addLockToken(String lt) { try { wsp.getLockManager().addLockToken(lt); } catch (RepositoryException e) { log.debug("Error while adding lock token."); } } /** * {@inheritDoc} */ public String[] getLockTokens() { try { return wsp.getLockManager().getLockTokens(); } catch (RepositoryException e) { log.debug("Error while accessing lock tokens."); return new String[0]; } } /** * {@inheritDoc} */ public void removeLockToken(String lt) { try { wsp.getLockManager().removeLockToken(lt); } catch (RepositoryException e) { log.debug("Error while removing lock token."); } } /** * Return the lock manager for this session. * @return lock manager for this session */ public LockManager getLockManager() throws RepositoryException { return wsp.getInternalLockManager(); } /** * Returns all locks owned by this session. * * @return an array of <code>Lock</code>s */ public Lock[] getLocks() { // check sanity of this session //sanityCheck(); if (!alive) { log.error("failed to retrieve locks: session has been closed"); return new Lock[0]; } try { return getLockManager().getLocks(this); } catch (RepositoryException e) { log.error("Lock manager not available.", e); return new Lock[0]; } } //--------------------------------------------------< new JSR 283 methods > /** * @see org.apache.jackrabbit.api.jsr283.Session#getNodeByIdentifier(String) * @since JCR 2.0 */ public Node getNodeByIdentifier(String id) throws ItemNotFoundException, RepositoryException { NodeId nodeId; try { nodeId = NodeId.valueOf(id); } catch (IllegalArgumentException iae) { throw new RepositoryException("invalid identifier: " + id); } return getNodeById(nodeId); } /** * @see org.apache.jackrabbit.api.jsr283.Session#getNode(String) * @since JCR 2.0 */ public Node getNode(String absPath) throws PathNotFoundException, RepositoryException { // check sanity of this session sanityCheck(); try { Path p = getQPath(absPath).getNormalizedPath(); if (!p.isAbsolute()) { throw new RepositoryException("not an absolute path: " + absPath); } return getItemManager().getNode(p); } catch (AccessDeniedException ade) { throw new PathNotFoundException(absPath); } catch (NameException e) { String msg = "invalid path:" + absPath; log.debug(msg); throw new RepositoryException(msg, e); } } /** * @see org.apache.jackrabbit.api.jsr283.Session#getProperty(String) * @since JCR 2.0 */ public Property getProperty(String absPath) throws PathNotFoundException, RepositoryException { // check sanity of this session sanityCheck(); try { Path p = getQPath(absPath).getNormalizedPath(); if (!p.isAbsolute()) { throw new RepositoryException("not an absolute path: " + absPath); } return getItemManager().getProperty(p); } catch (AccessDeniedException ade) { throw new PathNotFoundException(absPath); } catch (NameException e) { String msg = "invalid path:" + absPath; log.debug(msg); throw new RepositoryException(msg, e); } } /** * @see org.apache.jackrabbit.api.jsr283.Session#nodeExists(String) * @since JCR 2.0 */ public boolean nodeExists(String absPath) throws RepositoryException { // check sanity of this session sanityCheck(); try { Path p = getQPath(absPath).getNormalizedPath(); if (!p.isAbsolute()) { throw new RepositoryException("not an absolute path: " + absPath); } return getItemManager().nodeExists(p); } catch (NameException e) { String msg = "invalid path:" + absPath; log.debug(msg); throw new RepositoryException(msg, e); } } /** * @see org.apache.jackrabbit.api.jsr283.Session#propertyExists(String) * @since JCR 2.0 */ public boolean propertyExists(String absPath) throws RepositoryException { // check sanity of this session sanityCheck(); try { Path p = getQPath(absPath).getNormalizedPath(); if (!p.isAbsolute()) { throw new RepositoryException("not an absolute path: " + absPath); } return getItemManager().propertyExists(p); } catch (NameException e) { String msg = "invalid path:" + absPath; log.debug(msg); throw new RepositoryException(msg, e); } } /** * @see org.apache.jackrabbit.api.jsr283.Session#removeItem(String) * @since JCR 2.0 */ public void removeItem(String absPath) throws VersionException, LockException, ConstraintViolationException, RepositoryException { // check sanity of this session sanityCheck(); Item item; try { Path p = getQPath(absPath).getNormalizedPath(); if (!p.isAbsolute()) { throw new RepositoryException("not an absolute path: " + absPath); } item = getItemManager().getItem(p); } catch (AccessDeniedException e) { throw new PathNotFoundException(absPath); } catch (NameException e) { String msg = "invalid path:" + absPath; log.debug(msg); throw new RepositoryException(msg, e); } item.remove(); } /** * @see org.apache.jackrabbit.api.jsr283.Session#hasPermission(String, String) * @since 2.0 */ public boolean hasPermission(String absPath, String actions) throws RepositoryException { // check sanity of this session sanityCheck(); Path path = getQPath(absPath).getNormalizedPath(); // test if path is absolute if (!path.isAbsolute()) { throw new RepositoryException("Absolute path expected. Was:" + absPath); } Set s = new HashSet(Arrays.asList(actions.split(","))); int permissions = 0; if (s.remove(ACTION_READ)) { permissions |= Permission.READ; } if (s.remove(ACTION_ADD_NODE)) { permissions |= Permission.ADD_NODE; } if (s.remove(ACTION_SET_PROPERTY)) { permissions |= Permission.SET_PROPERTY; } if (s.remove(ACTION_REMOVE)) { if (nodeExists(absPath)) { permissions |= (propertyExists(absPath)) ? (Permission.REMOVE_NODE | Permission.REMOVE_PROPERTY) : Permission.REMOVE_NODE; } else if (propertyExists(absPath)) { permissions |= Permission.REMOVE_PROPERTY; } else { // item doesn't exist -> check both permissions permissions = Permission.REMOVE_NODE | Permission.REMOVE_PROPERTY; } } if (!s.isEmpty()) { StringBuffer sb = new StringBuffer(); for (Iterator it = s.iterator(); it.hasNext();) { sb.append(it.next()); } throw new IllegalArgumentException("Unknown actions: " + sb.toString()); } try { return getAccessManager().isGranted(path, permissions); } catch (AccessDeniedException e) { return false; } } /** * @see org.apache.jackrabbit.api.jsr283.Session#hasCapability(String, Object, Map) * @since JCR 2.0 */ public boolean hasCapability(String methodType, Object target, Map arguments) throws RepositoryException { //TODO throw new UnsupportedRepositoryOperationException("Not yet implemented"); } /** * @see org.apache.jackrabbit.api.jsr283.Session#getAccessControlManager() * @since JCR 2.0 */ public AccessControlManager getAccessControlManager() throws UnsupportedRepositoryOperationException, RepositoryException { if (accessMgr instanceof AccessControlManager) { return (AccessControlManager) accessMgr; } else { throw new UnsupportedRepositoryOperationException("Access control discovery is not supported."); } } /** * @see org.apache.jackrabbit.api.jsr283.Session#getRetentionManager() * @since JCR 2.0 */ public RetentionManager getRetentionManager() throws UnsupportedRepositoryOperationException, RepositoryException { // check sanity of this session sanityCheck(); if (retentionManager == null) { // make sure the internal retention manager exists. getRetentionRegistry(); // create the api level retention manager. retentionManager = new RetentionManagerImpl(this); } return retentionManager; } //-------------------------------------------------------------< Dumpable > /** * {@inheritDoc} */ public void dump(PrintStream ps) { ps.print("Session: "); if (userId == null) { ps.print("unknown"); } else { ps.print(userId); } ps.println(" (" + this + ")"); ps.println(); itemMgr.dump(ps); ps.println(); itemStateMgr.dump(ps); } /** * Finalize the session. If the application doesn't close Session.logout(), * the session is closed automatically; however a warning is written to the log file, * together with the stack trace of where the session was opened. */ public void finalize() { if (alive) { log.warn("Unclosed session detected. The session was opened here: ", openStackTrace); logout(); } } }