blob: 59de5a8bb53dc042aca4f4026caa07dbcb346656 [file] [log] [blame]
/*
* Copyright 2004-2005 The Apache Software Foundation or its licensors,
* as applicable.
*
* Licensed 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.base;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.AccessControlException;
import java.util.regex.Pattern;
import javax.jcr.Credentials;
import javax.jcr.Item;
import javax.jcr.ItemNotFoundException;
import javax.jcr.ItemVisitor;
import javax.jcr.NamespaceException;
import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.PathNotFoundException;
import javax.jcr.Repository;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.UnsupportedRepositoryOperationException;
import javax.jcr.ValueFactory;
import javax.jcr.Workspace;
import javax.jcr.query.Query;
import javax.jcr.query.QueryResult;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.sax.SAXResult;
import javax.xml.transform.sax.SAXTransformerFactory;
import javax.xml.transform.sax.TransformerHandler;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import org.apache.jackrabbit.name.QName;
import org.apache.jackrabbit.xml.DocumentViewExportVisitor;
import org.apache.jackrabbit.xml.SystemViewExportVisitor;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;
/**
* Session base class. The dummy session implemented by this class
* attempts to act like a read-only anonymous session. Subclasses must
* override at least the {@link #getRepository() getRepository()},
* {@link #getWorkspace() getWorkspace()},
* {@link #getRootNode() getRootNode()}, and
* {@link #getValueFactory() getValueFactory()} methods to make this class
* somewhat useful. See the method javadocs for full details of which
* methods to override for each required feature.
*/
public class BaseSession implements Session {
/** The pattern used to match UUID strings. */
private static final Pattern UUID_PATTERN = Pattern.compile(
"[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}");
/**
* Unsupported operation. Subclasses should override this method to
* return the repository to which this session is attached.
* Overiding this method is required for the default implementation of the
* {@link #impersonate(Credentials) impersonate(Credentials)} method.
*
* @return nothing, throws a {@link UnsupportedOperationException}
* @see Session#getRepository()
*/
public Repository getRepository() {
throw new UnsupportedOperationException();
}
/**
* Returns <code>null</code> to indicate that this session is associated
* with the "anonymous" user. Subclasses should override this method to
* return the actual user identity associated with the session.
*
* @return always <code>null</code>
* @see Session#getUserID()
*/
public String getUserID() {
return null;
}
/**
* Returns <code>null</code> to indicate that the named attribute does
* not exist. Subclasses should override this method to return the
* available attribute values.
*
* @param name attribute name
* @return always <code>null</code>
* @see Session#getAttribute(String)
*/
public Object getAttribute(String name) {
return null;
}
/**
* Returns an empty string array to indicate that no attributes are
* available. Subclasses should override this method to return the
* available attribute names.
*
* @return empty array
* @see Session#getAttributeNames()
*/
public String[] getAttributeNames() {
return new String[0];
}
/**
* Unsupported operation. Subclasses should override this method to
* return the workspace attached to this session. Overiding this method
* is required for the default implementations of the
* {@link #impersonate(Credentials) impersonate(Credentials)} and
* {@link #getNodeByUUID(String) getNodeByUUID(String)} methods.
*
* @return nothing, throws a {@link UnsupportedOperationException}
* @see Session#getWorkspace()
*/
public Workspace getWorkspace() {
throw new UnsupportedOperationException();
}
/**
* Implemented by calling the
* {@link Repository#login(Credentials, String) login(Credentials, String)}
* method of the {@link Repository} instance returned by the
* {@link #getRepository() getRepository()} method. The method is invoked
* with the given login credentials and the workspace name returned by
* the {@link Workspace#getName() getName()} method of the
* {@link Workspace} instance returned by the
* {@link #getWorkspace() getWorkspace()} method.
* <p>
* There should normally be little need for subclasses to override this
* method unless the underlying repository implementation suggests a
* more straightforward implementation.
*
* @param credentials login credentials
* @return impersonated session
* @see Session#impersonate(Credentials)
*/
public Session impersonate(Credentials credentials)
throws RepositoryException {
return getRepository().login(credentials, getWorkspace().getName());
}
/**
* Unsupported operation. Subclasses should override this method to
* return the root node in the workspace attached to this session.
* Overriding this method is required for the default implementation of
* the {@link #getItem(String) getItem(String)} method.
*
* @return nothing, throws a {@link UnsupportedRepositoryOperationException}
* @see Session#getRootNode()
*/
public Node getRootNode() throws RepositoryException {
throw new UnsupportedRepositoryOperationException();
}
/**
* Implemented by making the XPath query <code>//*[@jcr:uuid='...']</code>
* and returning the node that matches the query. Subclasses may want to
* override this method if an UUID index or another more efficient
* lookup mechanism can be used directly.
*
* @return the identified node
* @throws ItemNotFoundException if the identified node does not exist
* @see Session#getNodeByUUID(String)
*/
public Node getNodeByUUID(String uuid) throws RepositoryException {
if (UUID_PATTERN.matcher(uuid).matches()) {
String xpath;
String jcr = getNamespacePrefix(QName.NS_JCR_URI);
if (jcr.length() > 0) {
xpath = "//*[@" + jcr + ":uuid='" + uuid + "']";
} else {
xpath = "//*[@uuid='" + uuid + "']";
}
Query query =
getWorkspace().getQueryManager().createQuery(Query.XPATH, xpath);
QueryResult result = query.execute();
NodeIterator nodes = result.getNodes();
if (nodes.hasNext()) {
return nodes.nextNode();
}
}
throw new ItemNotFoundException(uuid);
}
/**
* Implemented by invoking the {@link Node#getNode(String) getNode(String)}
* (or {@link Node#getProperty(String) getProperty(String)} if getNode
* fails) method on the root node returned by the
* {@link #getRootNode() getRootNode()} method. The path given to the
* getNode or getProperty method is the given path without the leading "/".
* If the given path is "/" then the root node is returned directly.
* <p>
* Subclasses should not normally need to override this method as long as
* the referenced methods have been implemented. For performance reasons
* it might make sense to override this method because this implementation
* can cause the item path to be traversed twice.
*
* @param absPath absolute item path
* @return identified item
* @see Session#getItem(String)
*/
public Item getItem(String absPath) throws PathNotFoundException,
RepositoryException {
if (absPath == null || !absPath.startsWith("/")) {
throw new PathNotFoundException("Invalid item path: " + absPath);
}
Node node = getRootNode();
if (absPath.equals("/")) {
return node;
} else {
String relPath = absPath.substring(1);
try {
return node.getNode(relPath);
} catch (PathNotFoundException e) {
return node.getProperty(relPath);
}
}
}
/**
* Implemented by trying to retrieve the identified item using the
* {@link #getItem(String) getItem(String)} method. Subclasses may
* want to override this method for performance as there is no real
* need for instantiating the identified item.
*
* @param absPath absolute item path
* @return <code>true</code> if the identified item exists,
* <code>false</code> otherwise
* @see Session#itemExists(String)
*/
public boolean itemExists(String absPath) throws RepositoryException {
try {
getItem(absPath);
return true;
} catch (PathNotFoundException e) {
return false;
}
}
/**
* Unsupported operation. Subclasses should override this method to
* make it possible to move and rename items.
*
* @param srcAbsPath source item path
* @param destAbsPath destination item path
* @see Session#move(String, String)
*/
public void move(String srcAbsPath, String destAbsPath)
throws RepositoryException {
throw new UnsupportedRepositoryOperationException();
}
/**
* Unsupported operation. Subclasses should override this method to
* allow modifications to the content repository.
*
* @see Session#save()
*/
public void save() throws RepositoryException {
throw new UnsupportedRepositoryOperationException();
}
/**
* Does nothing. Subclasses should override this method to correctly
* manage the transient state and underlying repository changes.
*
* @param keepChanges whether to keep transient changes (ignored)
* @see Session#refresh(boolean)
*/
public void refresh(boolean keepChanges) throws RepositoryException {
}
/**
* Returns <code>false</code> to indicate that there are no pending
* changes. Subclasses should override this method to correctly manage
* the transient state.
*
* @return always <code>false</code>
* @see Session#hasPendingChanges()
*/
public boolean hasPendingChanges() throws RepositoryException {
return false;
}
/**
* Throws an {@link AccessControlException} for anything else than
* <code>read</code> actions to indicate that only read access is
* permitted. Subclasses should override this method to correctly
* report access controls settings.
*
* @param absPath item path
* @param actions action strings
* @throws AccessControlException if other than <code>read</code> actions
* are requested
* @see Session#checkPermission(String, String)
*/
public void checkPermission(String absPath, String actions)
throws AccessControlException {
String[] parts = actions.split(",");
for (int i = 0; i < parts.length; i++) {
if (!"read".equals(parts[i])) {
throw new AccessControlException(
"No " + actions + " permission for " + absPath);
}
}
}
/**
* Unsupported operation. Subclasses should override this method to
* support XML imports. Overriding this method is required for the
* default implementation of the
* {@link #importXML(String, InputStream, int) importXML(String, InputStream, int)}
* method.
*
* @param parentAbsPath path of the parent node
* @param uuidBehaviour UUID behaviour flag
* @return nothing, throws an {@link UnsupportedRepositoryOperationException}
* @see Session#getImportContentHandler(String, int)
*/
public ContentHandler getImportContentHandler(
String parentAbsPath, int uuidBehaviour)
throws RepositoryException {
throw new UnsupportedRepositoryOperationException();
}
/**
* Parses the XML input stream and feeds the SAX events to the content
* handler returned by the
* {@link #getImportContentHandler(String, int) getImportContentHandler(String, int)}
* method.
*
* @param parentAbsPath path of the parent node
* @param in XML input stream
* @param uuidBehaviour UUID behaviour flag
* @see Session#importXML(String, InputStream, int)
*/
public void importXML(
String parentAbsPath, InputStream in, int uuidBehaviour)
throws IOException, RepositoryException {
try {
ContentHandler handler =
getImportContentHandler(parentAbsPath, uuidBehaviour);
TransformerFactory factory = TransformerFactory.newInstance();
Transformer transformer = factory.newTransformer();
transformer.transform(new StreamSource(in), new SAXResult(handler));
} catch (TransformerConfigurationException e) {
throw new IOException(
"Unable to configure a SAX transformer: " + e.getMessage());
} catch (TransformerException e) {
throw new IOException(
"Unable to deserialize a SAX stream: " + e.getMessage());
}
}
/**
* Creates a {@link SystemViewExportVisitor} instance and passes it
* to the node identified by the given path. The export visitor traverses
* the entire content tree and generates the system view SAX events for
* the given content handler. Subclasses may override this method for
* better performance or custom processing, but the default implementation
* should be good enough for general use.
*
* @param absPath node path
* @param contentHandler SAX content handler
* @param skipBinary binary property skip flag
* @param noRecurse subtree recursion flag
* @see Session#exportSystemView(String, ContentHandler, boolean, boolean)
*/
public void exportSystemView(
String absPath, ContentHandler contentHandler,
boolean skipBinary, boolean noRecurse)
throws SAXException, RepositoryException {
Item item = getItem(absPath);
if (item.isNode()) {
ItemVisitor visitor = new SystemViewExportVisitor(
contentHandler, skipBinary, noRecurse);
item.accept(visitor);
} else {
throw new PathNotFoundException("Invalid node path: " + absPath);
}
}
/**
* Creates a SAX serializer for the given output stream and passes it to the
* {@link #exportSystemView(String, ContentHandler, boolean, boolean) exportSystemView(String, ContentHandler, boolean, boolean)}
* method along with the other parameters.
*
* @param absPath node path
* @param out XML output stream
* @param skipBinary binary property skip flag
* @param noRecurse subtree recursion flag
* @see Session#exportSystemView(String, OutputStream, boolean, boolean)
*/
public void exportSystemView(
String absPath, OutputStream out,
boolean skipBinary, boolean noRecurse)
throws IOException, RepositoryException {
try {
SAXTransformerFactory factory = (SAXTransformerFactory)
SAXTransformerFactory.newInstance();
TransformerHandler handler = factory.newTransformerHandler();
handler.setResult(new StreamResult(out));
exportSystemView(absPath, handler, skipBinary, noRecurse);
} catch (TransformerConfigurationException e) {
throw new IOException(
"Unable to configure a SAX transformer: " + e.getMessage());
} catch (SAXException e) {
throw new IOException(
"Unable to serialize a SAX stream: " + e.getMessage());
}
}
/**
* Creates a {@link DocumentViewExportVisitor} instance and passes it
* to the node identified by the given path. The export visitor traverses
* the entire content tree and generates the document view SAX events for
* the given content handler. Subclasses may override this method for
* better performance or custom processing, but the default implementation
* should be good enough for general use.
*
* @param absPath node path
* @param contentHandler SAX content handler
* @param skipBinary binary property skip flag
* @param noRecurse subtree recursion flag
* @see Session#exportDocumentView(String, ContentHandler, boolean, boolean)
*/
public void exportDocumentView(
String absPath, ContentHandler contentHandler,
boolean skipBinary, boolean noRecurse)
throws SAXException, RepositoryException {
Item item = getItem(absPath);
if (item.isNode()) {
ItemVisitor visitor = new DocumentViewExportVisitor(
contentHandler, skipBinary, noRecurse);
item.accept(visitor);
} else {
throw new PathNotFoundException("Invalid node path: " + absPath);
}
}
/**
* Creates a SAX serializer for the given output stream and passes it to the
* {@link #exportDocumentView(String, ContentHandler, boolean, boolean) exportDocumentView(String, ContentHandler, boolean, boolean)}
* method along with the other parameters.
*
* @param absPath node path
* @param out XML output stream
* @param skipBinary binary property skip flag
* @param noRecurse subtree recursion flag
* @see Session#exportDocumentView(String, OutputStream, boolean, boolean)
*/
public void exportDocumentView(
String absPath, OutputStream out,
boolean skipBinary, boolean noRecurse)
throws IOException, RepositoryException {
try {
SAXTransformerFactory factory = (SAXTransformerFactory)
SAXTransformerFactory.newInstance();
TransformerHandler handler = factory.newTransformerHandler();
handler.setResult(new StreamResult(out));
exportDocumentView(absPath, handler, skipBinary, noRecurse);
} catch (TransformerConfigurationException e) {
throw new IOException(
"Unable to configure a SAX transformer: " + e.getMessage());
} catch (SAXException e) {
throw new IOException(
"Unable to serialize a SAX stream: " + e.getMessage());
}
}
/**
* Unsupported operation. Subclasses should override this method to
* support namespace remapping.
*
* @param prefix namespace prefix
* @param uri namespace uri
* @see Session#setNamespacePrefix(String, String)
*/
public void setNamespacePrefix(String prefix, String uri)
throws NamespaceException, RepositoryException {
throw new UnsupportedRepositoryOperationException();
}
/**
* Returns the namespace prefixes registered in the
* {@link javax.jcr.NamespaceRegistry} associated with the workspace of
* this session. Subclasses should override this method to support
* namespace remapping.
*
* @return namespace prefixes
* @see Session#getNamespacePrefixes()
*/
public String[] getNamespacePrefixes() throws RepositoryException {
return getWorkspace().getNamespaceRegistry().getPrefixes();
}
/**
* Returns the namespace URI registered for the given prefix in the
* {@link javax.jcr.NamespaceRegistry} associated with the workspace of
* this session. Subclasses should override this method to support
* namespace remapping.
*
* @param prefix namespace prefix
* @return namespace URI
* @see Session#getNamespaceURI(String)
*/
public String getNamespaceURI(String prefix) throws RepositoryException {
return getWorkspace().getNamespaceRegistry().getURI(prefix);
}
/**
* Returns the namespace prefix registered for the given URI in the
* {@link javax.jcr.NamespaceRegistry} associated with the workspace of
* this session. Subclasses should override this method to support
* namespace remapping.
*
* @param uri namespace URI
* @return namespace prefix
* @see Session#getNamespacePrefix(String)
*/
public String getNamespacePrefix(String uri) throws RepositoryException {
return getWorkspace().getNamespaceRegistry().getPrefix(uri);
}
/**
* Does nothing. Subclasses should override this method to actually
* close this session.
*
* @see Session#logout()
*/
public void logout() {
}
/**
* Unsupported operation. Subclasses should override this method to support
* lock token management.
*
* @param lock token
* @see Session#addLockToken(String)
*/
public void addLockToken(String lt) {
throw new UnsupportedOperationException();
}
/**
* Returns an empty string array to indicate that no lock tokens are held
* by this session. Subclasses should override this method to return the
* actual lock tokens held by this session.
*
* @return empty array
* @see Session#getLockTokens()
*/
public String[] getLockTokens() {
return new String[0];
}
/**
* Unsupported operation. Subclasses should override this method to support
* lock token management.
*
* @param lock token
* @see Session#removeLockToken(String)
*/
public void removeLockToken(String lt) {
throw new UnsupportedOperationException();
}
/**
* Unsupported operation. Subclasses should override this method to
* allow the creation of new {@link javax.jcr.Value Value} instances.
*
* @param lock token
* @see Session#removeLockToken(String)
*/
public ValueFactory getValueFactory()
throws UnsupportedRepositoryOperationException, RepositoryException {
throw new UnsupportedRepositoryOperationException();
}
/**
* Returns <code>true</code> to indicate that the session has not been
* closed. Subclasses should override this method to correctly report
* the state of the session.
*
* @return always <code>true</code>
* @see Session#isLive()
*/
public boolean isLive() {
return true;
}
}