/*
* 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 static org.apache.jackrabbit.core.ItemValidator.CHECK_CHECKED_OUT;
import static org.apache.jackrabbit.core.ItemValidator.CHECK_CONSTRAINTS;
import static org.apache.jackrabbit.core.ItemValidator.CHECK_HOLD;
import static org.apache.jackrabbit.core.ItemValidator.CHECK_LOCK;
import static org.apache.jackrabbit.core.ItemValidator.CHECK_RETENTION;
import java.io.File;
import java.security.AccessControlException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;
import javax.jcr.AccessDeniedException;
import javax.jcr.Credentials;
import javax.jcr.Item;
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.Lock;
import javax.jcr.lock.LockException;
import javax.jcr.nodetype.ConstraintViolationException;
import javax.jcr.observation.EventListener;
import javax.jcr.observation.ObservationManager;
import javax.jcr.retention.RetentionManager;
import javax.jcr.security.AccessControlManager;
import javax.jcr.version.VersionException;
import javax.security.auth.Subject;
import org.apache.commons.collections.IteratorUtils;
import org.apache.commons.collections.map.ReferenceMap;
import org.apache.jackrabbit.api.JackrabbitSession;
import org.apache.jackrabbit.api.security.principal.PrincipalManager;
import org.apache.jackrabbit.api.security.user.Authorizable;
import org.apache.jackrabbit.api.security.user.User;
import org.apache.jackrabbit.api.security.user.UserManager;
import org.apache.jackrabbit.commons.AbstractSession;
import org.apache.jackrabbit.core.config.WorkspaceConfig;
import org.apache.jackrabbit.core.data.GarbageCollector;
import org.apache.jackrabbit.core.id.NodeId;
import org.apache.jackrabbit.core.nodetype.NodeTypeManagerImpl;
import org.apache.jackrabbit.core.observation.ObservationManagerImpl;
import org.apache.jackrabbit.core.retention.RetentionManagerImpl;
import org.apache.jackrabbit.core.retention.RetentionRegistry;
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.SystemPrincipal;
import org.apache.jackrabbit.core.security.authentication.AuthContext;
import org.apache.jackrabbit.core.security.authorization.Permission;
import org.apache.jackrabbit.core.security.principal.AdminPrincipal;
import org.apache.jackrabbit.core.session.SessionContext;
import org.apache.jackrabbit.core.session.SessionItemOperation;
import org.apache.jackrabbit.core.session.SessionOperation;
import org.apache.jackrabbit.core.session.SessionRefreshOperation;
import org.apache.jackrabbit.core.session.SessionSaveOperation;
import org.apache.jackrabbit.core.state.SessionItemStateManager;
import org.apache.jackrabbit.core.version.InternalVersionManager;
import org.apache.jackrabbit.core.xml.ImportHandler;
import org.apache.jackrabbit.core.xml.SessionImporter;
import org.apache.jackrabbit.spi.Name;
import org.apache.jackrabbit.spi.Path;
import org.apache.jackrabbit.spi.commons.SessionExtensions;
import org.apache.jackrabbit.spi.commons.conversion.DefaultNamePathResolver;
import org.apache.jackrabbit.spi.commons.conversion.IdentifierResolver;
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.name.NameConstants;
import org.apache.jackrabbit.spi.commons.namespace.NamespaceResolver;
import org.apache.jackrabbit.util.Text;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.ContentHandler;
/**
* A <code>SessionImpl</code> ...
*/
public class SessionImpl extends AbstractSession
implements JackrabbitSession, SessionExtensions, NamespaceResolver, NamePathResolver, IdentifierResolver {
/**
* Name of the session attribute that controls whether the
* {@link #refresh(boolean)} method will cause the repository to
* synchronize itself to changes in other cluster nodes. This cluster
* synchronization is enabled by default, unless an attribute with this
* name is set (any non-null value) for this session.
*
* @since Apache Jackrabbit 1.6
* @see <a href="https://issues.apache.org/jira/browse/JCR-1753">JCR-1753</a>
*/
public static final String DISABLE_CLUSTER_SYNC_ON_REFRESH =
"org.apache.jackrabbit.disableClusterSyncOnRefresh";
/**
* Name of the session attribute that controls whether repository
* inconsistencies should be automatically fixed when traversing over child
* nodes, when trying to add a child node, or removing a child node.
*
* @since Apache Jackrabbit 2.2
* @see <a href="https://issues.apache.org/jira/browse/JCR-2740">JCR-2740</a>
*/
public static final String AUTO_FIX_CORRUPTIONS =
"org.apache.jackrabbit.autoFixCorruptions";
private static Logger log = LoggerFactory.getLogger(SessionImpl.class);
/**
* Session counter. Used to generate unique internal session names.
*/
private static final AtomicLong SESSION_COUNTER = new AtomicLong();
/**
* The component context of this session.
*/
protected final SessionContext context;
/**
* The component context of the repository that issued this session.
*/
protected final RepositoryContext repositoryContext;
/**
* 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;
/**
* Unique internal name of this session. Returned by the
* {@link #toString()} method for use in logging and debugging.
*/
private final String sessionName;
/**
* the attributes of this session
*/
protected final Map<String, Object> attributes =
new HashMap<String, Object>();
/**
* Name and Path resolver
*/
protected NamePathResolver namePathResolver;
/**
* The version manager for this session
*/
protected final InternalVersionManager versionMgr;
/**
* Listeners (weak references)
*/
@SuppressWarnings("unchecked")
protected final Map<SessionListener, SessionListener> listeners =
new ReferenceMap(ReferenceMap.WEAK, ReferenceMap.WEAK);
/**
* 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");
/**
* Protected constructor.
*
* @param repositoryContext repository context
* @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(
RepositoryContext repositoryContext, AuthContext loginContext,
WorkspaceConfig wspConfig)
throws AccessDeniedException, RepositoryException {
this(repositoryContext, loginContext.getSubject(), wspConfig);
this.loginContext = loginContext;
}
/**
* Protected constructor.
*
* @param repositoryContext repository context
* @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(
RepositoryContext repositoryContext, Subject subject,
WorkspaceConfig wspConfig)
throws AccessDeniedException, RepositoryException {
this.context = new SessionContext(repositoryContext, this, wspConfig);
this.repositoryContext = repositoryContext;
this.subject = subject;
this.userId = retrieveUserId(subject, wspConfig.getName());
long count = SESSION_COUNTER.incrementAndGet();
if (userId != null) {
String user = Text.escapeIllegalJcrChars(userId);
this.sessionName = "session-" + user + "-" + count;
} else {
this.sessionName = "session-" + count;
}
namePathResolver = new DefaultNamePathResolver(this, this, true);
context.setItemStateManager(createSessionItemStateManager());
context.setItemManager(createItemManager());
context.setAccessManager(createAccessManager(subject));
context.setObservationManager(
createObservationManager(wspConfig.getName()));
versionMgr = createVersionManager();
}
/**
* Retrieve the userID from the specified subject.
*
* @return the userID.
*/
protected String retrieveUserId(Subject subject, String workspaceName) throws RepositoryException {
return repositoryContext.getSecurityManager().getUserID(
subject, workspaceName);
}
/**
* Create the session item state manager.
*
* @return session item state manager
*/
protected SessionItemStateManager createSessionItemStateManager() {
SessionItemStateManager mgr = new SessionItemStateManager(
context.getRootNodeId(),
context.getWorkspace().getItemStateManager());
context.getWorkspace().getItemStateManager().addListener(mgr);
return mgr;
}
/**
* Create the item manager.
* @return item manager
*/
protected ItemManager createItemManager() {
ItemManager mgr = new ItemManager(context);
context.getItemStateManager().addListener(mgr);
return mgr;
}
protected ObservationManagerImpl createObservationManager(String wspName)
throws RepositoryException {
try {
return new ObservationManagerImpl(
context.getRepository().getObservationDispatcher(wspName),
this, context.getRepositoryContext().getClusterNode());
} catch (NoSuchWorkspaceException e) {
// should never get here
throw new RepositoryException(
"Internal error: failed to create observation manager", e);
}
}
/**
* Create the version manager. If we are not using XA, we may safely use
* the repository version manager.
* @return version manager
*/
protected InternalVersionManager createVersionManager()
throws RepositoryException {
return context.getRepositoryContext().getInternalVersionManager();
}
/**
* Create the access manager.
*
* @param subject
* @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)
throws AccessDeniedException, RepositoryException {
String wspName = getWorkspace().getName();
AMContext ctx = new AMContext(
new File(context.getRepository().getConfig().getHomeDir()),
context.getRepositoryContext().getFileSystem(),
this,
subject,
context.getHierarchyManager(),
context.getPrivilegeManager(),
this,
wspName);
return repositoryContext.getSecurityManager().getAccessManager(this, ctx);
}
private <T> T perform(SessionOperation<T> operation)
throws RepositoryException {
return context.getSessionState().perform(operation);
}
/**
* 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)
*/
private void sanityCheck() throws RepositoryException {
context.getSessionState().checkAlive();
}
/**
* Returns a read only copy of the <code>Subject</code> associated with this
* session.
*
* @return a read only copy of <code>Subject</code> associated with this session
*/
public Subject getSubject() {
Subject readOnly = new Subject(true, subject.getPrincipals(), subject.getPublicCredentials(), subject.getPrivateCredentials());
return readOnly;
}
/**
* Returns <code>true</code> if the subject contains a
* <code>SystemPrincipal</code>; <code>false</code> otherwise.
*
* @return <code>true</code> if this is an system session.
*/
public boolean isSystem() {
// NOTE: for backwards compatibility evaluate subject for containing SystemPrincipal
// TODO: Q: shouldn't 'isSystem' rather be covered by instances of SystemSession only?
return (subject != null && !subject.getPrincipals(SystemPrincipal.class).isEmpty());
}
/**
* Returns <code>true</code> if this session has been created for the
* administrator. <code>False</code> otherwise.
*
* @return <code>true</code> if this is an admin session.
*/
public boolean isAdmin() {
// NOTE: don't replace by getUserManager()
if (userManager != null) {
try {
Authorizable a = userManager.getAuthorizable(userId);
if (a != null && !a.isGroup()) {
return ((User) a).isAdmin();
}
} catch (RepositoryException e) {
// no user management -> use fallback
}
}
// fallback: user manager not yet initialized or user mgt not supported
// -> check for AdminPrincipal being present in the subject.
return (subject != null && !subject.getPrincipals(AdminPrincipal.class).isEmpty());
}
/**
* 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 =
repositoryContext.getWorkspaceManager().getDefaultWorkspaceName();
}
Subject newSubject = new Subject(subject.isReadOnly(), subject.getPrincipals(), subject.getPublicCredentials(), subject.getPrivateCredentials());
return repositoryContext.getWorkspaceManager().createSession(
newSubject, workspaceName);
}
/**
* Returns the <code>AccessManager</code> associated with this session.
*
* @return the <code>AccessManager</code> associated with this session
*/
public AccessManager getAccessManager() {
return context.getAccessManager();
}
/**
* Returns the <code>NodeTypeManager</code>.
*
* @return the <code>NodeTypeManager</code>
*/
public NodeTypeManagerImpl getNodeTypeManager() {
return context.getNodeTypeManager();
}
/**
* Returns the <code>ItemManager</code> of this session.
*
* @return the <code>ItemManager</code>
*/
public ItemManager getItemManager() {
return context.getItemManager();
}
/**
* Returns the <code>HierarchyManager</code> associated with this session.
*
* @return the <code>HierarchyManager</code> associated with this session
*/
public HierarchyManager getHierarchyManager() {
return context.getHierarchyManager();
}
/**
* Returns the <code>InternalVersionManager</code> associated with this session.
*
* @return the <code>InternalVersionManager</code> associated with this session
*/
public InternalVersionManager getInternalVersionManager() {
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 context.getWorkspace().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
*/
public void setAttribute(String name, Object value) {
if (value != null) {
attributes.put(name, value);
} else {
attributes.remove(name);
}
}
/**
* 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());
}
}
/**
* Notify the listeners that this session is about to be closed.
*/
protected void notifyLoggingOut() {
// copy listeners to array to avoid ConcurrentModificationException
List<SessionListener> copy =
new ArrayList<SessionListener>(listeners.values());
for (SessionListener listener : copy) {
if (listener != null) {
listener.loggingOut(this);
}
}
}
/**
* Notify the listeners that this session has been closed.
*/
protected void notifyLoggedOut() {
// copy listeners to array to avoid ConcurrentModificationException
List<SessionListener> copy =
new ArrayList<SessionListener>(listeners.values());
for (SessionListener listener : copy) {
if (listener != null) {
listener.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 {
final GarbageCollector gc =
repositoryContext.getRepository().createDataStoreGarbageCollector();
// Auto-close if the main session logs out
addListener(new SessionListener() {
public void loggedOut(SessionImpl session) {
}
public void loggingOut(SessionImpl session) {
gc.close();
}
});
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);
}
public Path getQPath(String path, boolean normalizeIdentifier) throws MalformedPathException, IllegalNameException, NamespaceException {
return namePathResolver.getQPath(path, normalizeIdentifier);
}
//---------------------------------------------------< IdentifierResolver >
/**
* @see IdentifierResolver#getPath(String)
*/
public Path getPath(String identifier) throws MalformedPathException {
try {
return context.getHierarchyManager().getPath(NodeId.valueOf(identifier));
} catch (RepositoryException e) {
throw new MalformedPathException("Identifier '" + identifier + "' cannot be resolved.");
}
}
/**
* @see IdentifierResolver#checkFormat(String)
*/
public void checkFormat(String identifier) throws MalformedPathException {
try {
NodeId.valueOf(identifier);
} catch (IllegalArgumentException e) {
throw new MalformedPathException("Invalid identifier: " + identifier);
}
}
//----------------------------------------------------< JackrabbitSession >
/**
* @see JackrabbitSession#getPrincipalManager()
*/
public PrincipalManager getPrincipalManager() throws RepositoryException, AccessDeniedException {
if (principalManager == null) {
principalManager =
repositoryContext.getSecurityManager().getPrincipalManager(this);
}
return principalManager;
}
/**
* @see JackrabbitSession#getUserManager()
*/
public UserManager getUserManager() throws AccessDeniedException, RepositoryException {
if (userManager == null) {
userManager =
repositoryContext.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 context.getWorkspace();
}
/**
* {@inheritDoc}
*/
@Override
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 getRepository().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 {
NodeImpl node = getNodeById(new NodeId(uuid));
if (node.isNodeType(NameConstants.MIX_REFERENCEABLE)) {
return node;
} else {
// there is a node with that uuid but the node does not expose it
throw new ItemNotFoundException(uuid);
}
} catch (IllegalArgumentException e) {
// Assuming the exception is from UUID.fromString()
throw new RepositoryException("Invalid UUID: " + uuid, e);
}
}
/**
* {@inheritDoc}
*/
@Override
public Item getItem(String absPath) throws RepositoryException {
return perform(SessionItemOperation.getItem(absPath));
}
/**
* {@inheritDoc}
*/
@Override
public boolean itemExists(String absPath) throws RepositoryException {
if (absPath != null && absPath.startsWith("[") && absPath.endsWith("]")) {
// an identifier segment has been specified (JCR-3014)
try {
NodeId id = NodeId.valueOf(absPath.substring(1, absPath.length() - 1));
return getItemManager().itemExists(id);
} catch (IllegalArgumentException e) {
throw new MalformedPathException(absPath);
}
}
return perform(SessionItemOperation.itemExists(absPath));
}
/**
* {@inheritDoc}
*/
public void save() throws RepositoryException {
// JCR-3131: no need to perform save op when there's nothing to save...
if (context.getItemStateManager().hasAnyTransientItemStates()) {
perform(new SessionSaveOperation());
}
}
/**
* {@inheritDoc}
*/
public void refresh(boolean keepChanges) throws RepositoryException {
perform(new SessionRefreshOperation(
keepChanges, clusterSyncOnRefresh()));
}
/**
* Checks whether the {@link #refresh(boolean)} method should cause
* cluster synchronization.
* <p>
* Subclasses can override this method to implement alternative
* rules on when cluster synchronization should be done.
*
* @return <code>true</code> if the {@link #DISABLE_CLUSTER_SYNC_ON_REFRESH}
* attribute is <em>not</em> set, <code>false</code> otherwise
* @since Apache Jackrabbit 1.6
* @see <a href="https://issues.apache.org/jira/browse/JCR-1753">JCR-1753</a>
*/
protected boolean clusterSyncOnRefresh() {
return getAttribute(DISABLE_CLUSTER_SYNC_ON_REFRESH) == null;
}
/**
* Checks whether repository inconsistencies should be automatically fixed
* when traversing over child nodes, when trying to add a child node, or
* when removing a child node.
*
* @return <code>true</code> if the {@link #AUTO_FIX_CORRUPTIONS}
* attribute is set, <code>false</code> otherwise
* @since Apache Jackrabbit 2.2
* @see <a href="https://issues.apache.org/jira/browse/JCR-2740">JCR-2740</a>
*/
protected boolean autoFixCorruptions() {
return getAttribute(AUTO_FIX_CORRUPTIONS) != null;
}
/**
* {@inheritDoc}
*/
public boolean hasPendingChanges() throws RepositoryException {
// check sanity of this session
sanityCheck();
return context.getItemStateManager().hasAnyTransientItemStates();
}
/**
* {@inheritDoc}
*/
public void move(String srcAbsPath, String destAbsPath)
throws RepositoryException {
perform(new SessionMoveOperation(this, srcAbsPath, destAbsPath));
}
/**
* {@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_CHECKED_OUT |
ItemValidator.CHECK_CONSTRAINTS | ItemValidator.CHECK_HOLD | ItemValidator.CHECK_RETENTION;
context.getItemValidator().checkModify(parent, options, Permission.NONE);
SessionImporter importer = new SessionImporter(
parent, this, uuidBehavior,
context.getWorkspace().getConfig().getImportConfig());
return new ImportHandler(importer, this);
}
/**
* {@inheritDoc}
*/
public boolean isLive() {
return context.getSessionState().isAlive();
}
/**
* 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<EventListener> listeners =
IteratorUtils.toList(manager.getRegisteredEventListeners());
for (EventListener listener : listeners) {
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);
}
}
/**
* Invalidates this session and releases all associated resources.
*/
@Override
public void logout() {
if (context.getSessionState().close()) {
// 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
context.getItemStateManager().disposeAllTransientItemStates();
// notify listeners that session is about to be closed
notifyLoggingOut();
context.getPrivilegeManager().dispose();
context.getNodeTypeManager().dispose();
// dispose session item state manager
context.getItemStateManager().dispose();
// dispose item manager
context.getItemManager().dispose();
// dispose workspace
context.getWorkspace().dispose();
// 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 {
context.getAccessManager().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 repositoryContext.getRepository();
}
/**
* {@inheritDoc}
*/
public ValueFactory getValueFactory() {
return context.getValueFactory();
}
/**
* {@inheritDoc}
*/
public String getUserID() {
return userId;
}
/**
* {@inheritDoc}
*/
public Object getAttribute(String name) {
return attributes.get(name);
}
/**
* {@inheritDoc}
*/
public String[] getAttributeNames() {
return attributes.keySet().toArray(new String[attributes.size()]);
}
/**
* {@inheritDoc}
*/
@Override
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 {
getWorkspace().getLockManager().addLockToken(lt);
} catch (RepositoryException e) {
log.debug("Error while adding lock token.");
}
}
/**
* {@inheritDoc}
*/
public String[] getLockTokens() {
try {
return getWorkspace().getLockManager().getLockTokens();
} catch (RepositoryException e) {
log.debug("Error while accessing lock tokens.");
return new String[0];
}
}
/**
* {@inheritDoc}
*/
public void removeLockToken(String lt) {
try {
getWorkspace().getLockManager().removeLockToken(lt);
} catch (RepositoryException e) {
log.debug("Error while removing lock token.");
}
}
/**
* 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 (!isLive()) {
log.error("failed to retrieve locks: session has been closed");
return new Lock[0];
}
try {
return context.getWorkspace().getInternalLockManager().getLocks(this);
} catch (RepositoryException e) {
log.error("Lock manager not available.", e);
return new Lock[0];
}
}
//--------------------------------------------------< new JSR 283 methods >
/**
* @see javax.jcr.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,iae);
}
return getNodeById(nodeId);
}
/**
* @see javax.jcr.Session#getNode(String)
* @since JCR 2.0
*/
@Override
public Node getNode(String absPath) throws RepositoryException {
return perform(SessionItemOperation.getNode(absPath));
}
/**
* @see javax.jcr.Session#getProperty(String)
* @since JCR 2.0
*/
@Override
public Property getProperty(String absPath) throws RepositoryException {
return perform(SessionItemOperation.getProperty(absPath));
}
/**
* @see javax.jcr.Session#nodeExists(String)
* @since JCR 2.0
*/
@Override
public boolean nodeExists(String absPath) throws RepositoryException {
if (absPath != null && absPath.startsWith("[") && absPath.endsWith("]")) {
// an identifier segment has been specified (JCR-3014)
try {
NodeId id = NodeId.valueOf(absPath.substring(1, absPath.length() - 1));
return getItemManager().itemExists(id);
} catch (IllegalArgumentException e) {
throw new MalformedPathException(absPath);
}
}
return perform(SessionItemOperation.nodeExists(absPath));
}
/**
* @see javax.jcr.Session#propertyExists(String)
* @since JCR 2.0
*/
@Override
public boolean propertyExists(String absPath) throws RepositoryException {
return perform(SessionItemOperation.propertyExists(absPath));
}
/**
* @see javax.jcr.Session#removeItem(String)
* @since JCR 2.0
*/
@Override
public void removeItem(String absPath) throws RepositoryException {
perform(SessionItemOperation.remove(absPath));
}
/**
* @see javax.jcr.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<String> s = new HashSet<String>(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()) {
throw new IllegalArgumentException("Unknown actions: " + s);
}
try {
return context.getAccessManager().isGranted(path, permissions);
} catch (AccessDeniedException e) {
return false;
}
}
/**
* @see javax.jcr.Session#hasCapability(String, Object, Object[])
* @since JCR 2.0
*/
public boolean hasCapability(String methodName, Object target, Object[] arguments)
throws RepositoryException {
// value of this method (as currently spec'ed) to jcr api clients
// is rather limited...
// here's therefore a minimal rather than best effort implementation;
// returning true is always fine according to the spec...
ItemValidator validator = context.getItemValidator();
int options =
CHECK_CHECKED_OUT | CHECK_LOCK | CHECK_CONSTRAINTS
| CHECK_HOLD | CHECK_RETENTION;
if (target instanceof Node) {
if (methodName.equals("addNode")
|| methodName.equals("addMixin")
|| methodName.equals("orderBefore")
|| methodName.equals("removeMixin")
|| methodName.equals("removeShare")
|| methodName.equals("removeSharedSet")
|| methodName.equals("setPrimaryType")
|| methodName.equals("setProperty")
|| methodName.equals("update")) {
return validator.canModify((ItemImpl) target, options, Permission.NONE);
} else if (methodName.equals("remove")) {
try {
validator.checkRemove((ItemImpl) target, options, Permission.NONE);
} catch (RepositoryException e) {
return false;
}
}
} else if (target instanceof Property) {
if (methodName.equals("setValue")
|| methodName.equals("save")) {
return validator.canModify((ItemImpl) target, options, Permission.NONE);
} else if (methodName.equals("remove")) {
try {
validator.checkRemove((ItemImpl) target, options, Permission.NONE);
} catch (RepositoryException e) {
return false;
}
}
// TODO: Add minimal, best effort checks for Workspace and Session operations
// } else if (target instanceof Workspace) {
// if (methodName.equals("clone")
// || methodName.equals("copy")
// || methodName.equals("createWorkspace")
// || methodName.equals("deleteWorkspace")
// || methodName.equals("getImportContentHandler")
// || methodName.equals("importXML")
// || methodName.equals("move")) {
// // TODO minimal, best effort checks (e.g. permissions for write methods etc)
// }
// } else if (target instanceof Session) {
// if (methodName.equals("clone")
// || methodName.equals("removeItem")
// || methodName.equals("getImportContentHandler")
// || methodName.equals("importXML")
// || methodName.equals("save")) {
// // TODO minimal, best effort checks (e.g. permissions for write methods etc)
// }
}
// we're unable to evaluate capability, return true (staying on the safe side)
return true;
}
/**
* @see javax.jcr.Session#getAccessControlManager()
* @since JCR 2.0
*/
public AccessControlManager getAccessControlManager()
throws UnsupportedRepositoryOperationException, RepositoryException {
AccessManager accessMgr = context.getAccessManager();
if (accessMgr instanceof AccessControlManager) {
return (AccessControlManager) accessMgr;
} else {
throw new UnsupportedRepositoryOperationException(
"Access control discovery is not supported.");
}
}
/**
* @see javax.jcr.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;
}
//--------------------------------------------------------------< Object >
/**
* Returns the unique internal name of this session. The returned name
* is especially useful for debugging and logging purposes.
*
* @see #sessionName
* @return session name
*/
@Override
public String toString() {
return sessionName;
}
/**
* Finalize the session. If the application doesn't call 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.
*/
@Override
public void finalize() {
if (isLive()) {
log.warn("Unclosed session detected. The session was opened here: ", openStackTrace);
logout();
}
}
}