org.apache.jackrabbit.commons.AbstractSession Maven / Gradle / Ivy
Show all versions of jackrabbit-jcr-commons Show documentation
/*
* 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.commons;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javax.jcr.Credentials;
import javax.jcr.InvalidSerializedDataException;
import javax.jcr.Item;
import javax.jcr.NamespaceException;
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.Workspace;
import org.apache.jackrabbit.commons.xml.DocumentViewExporter;
import org.apache.jackrabbit.commons.xml.Exporter;
import org.apache.jackrabbit.commons.xml.ParsingContentHandler;
import org.apache.jackrabbit.commons.xml.SystemViewExporter;
import org.apache.jackrabbit.commons.xml.ToXmlContentHandler;
import org.apache.jackrabbit.util.XMLChar;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;
/**
* Abstract base class for implementing the JCR {@link Session} interface.
*/
public abstract class AbstractSession implements Session {
/**
* Local namespace mappings. Prefixes as keys and namespace URIs as values.
*
* This map is only accessed from synchronized methods (see
* JCR-1793).
*/
private final Map namespaces =
new HashMap();
/**
* Clears the local namespace mappings. Subclasses that for example
* want to participate in a session pools should remember to call
* super.logout()
when overriding this method to avoid
* namespace mappings to be carried over to a new session.
*/
public void logout() {
synchronized (namespaces) {
namespaces.clear();
}
}
//------------------------------------------------< Namespace handling >--
/**
* Returns the namespace prefix mapped to the given URI. The mapping is
* added to the set of session-local namespace mappings unless it already
* exists there.
*
* This behaviour is based on JSR 283 (JCR 2.0), but remains backwards
* compatible with JCR 1.0.
*
* @param uri namespace URI
* @return namespace prefix
* @throws NamespaceException if the namespace is not found
* @throws RepositoryException if a repository error occurs
*/
public String getNamespacePrefix(String uri)
throws NamespaceException, RepositoryException {
synchronized (namespaces) {
for (Map.Entry entry : namespaces.entrySet()) {
if (entry.getValue().equals(uri)) {
return entry.getKey();
}
}
// The following throws an exception if the URI is not found, that's OK
String prefix = getWorkspace().getNamespaceRegistry().getPrefix(uri);
// Generate a new prefix if the global mapping is already taken
String base = prefix;
for (int i = 2; namespaces.containsKey(prefix); i++) {
prefix = base + i;
}
namespaces.put(prefix, uri);
return prefix;
}
}
/**
* Returns the namespace URI mapped to the given prefix. The mapping is
* added to the set of session-local namespace mappings unless it already
* exists there.
*
* This behaviour is based on JSR 283 (JCR 2.0), but remains backwards
* compatible with JCR 1.0.
*
* @param prefix namespace prefix
* @return namespace URI
* @throws NamespaceException if the namespace is not found
* @throws RepositoryException if a repository error occurs
*/
public String getNamespaceURI(String prefix)
throws NamespaceException, RepositoryException {
synchronized (namespaces) {
String uri = namespaces.get(prefix);
if (uri == null) {
// Not in local mappings, try the global ones
uri = getWorkspace().getNamespaceRegistry().getURI(prefix);
if (namespaces.containsValue(uri)) {
// The global URI is locally mapped to some other prefix,
// so there are no mappings for this prefix
throw new NamespaceException("Namespace not found: " + prefix);
}
// Add the mapping to the local set, we already know that
// the prefix is not taken
namespaces.put(prefix, uri);
}
return uri;
}
}
/**
* Returns the prefixes of all known namespace mappings. All global
* mappings not already included in the local set of namespace mappings
* are added there.
*
* This behaviour is based on JSR 283 (JCR 2.0), but remains backwards
* compatible with JCR 1.0.
*
* @return namespace prefixes
* @throws RepositoryException if a repository error occurs
*/
public String[] getNamespacePrefixes()
throws RepositoryException {
for (String uri : getWorkspace().getNamespaceRegistry().getURIs()) {
getNamespacePrefix(uri);
}
synchronized (namespaces) {
return namespaces.keySet().toArray(new String[namespaces.size()]);
}
}
/**
* Modifies the session local namespace mappings to contain the given
* prefix to URI mapping.
*
* This behaviour is based on JSR 283 (JCR 2.0), but remains backwards
* compatible with JCR 1.0.
*
* @param prefix namespace prefix
* @param uri namespace URI
* @throws NamespaceException if the mapping is illegal
* @throws RepositoryException if a repository error occurs
*/
public void setNamespacePrefix(String prefix, String uri)
throws NamespaceException, RepositoryException {
if (prefix == null) {
throw new IllegalArgumentException("Prefix must not be null");
} else if (uri == null) {
throw new IllegalArgumentException("Namespace must not be null");
} else if (prefix.length() == 0) {
throw new NamespaceException(
"Empty prefix is reserved and can not be remapped");
} else if (uri.length() == 0) {
throw new NamespaceException(
"Default namespace is reserved and can not be remapped");
} else if (prefix.toLowerCase().startsWith("xml")) {
throw new NamespaceException(
"XML prefixes are reserved: " + prefix);
} else if (!XMLChar.isValidNCName(prefix)) {
throw new NamespaceException(
"Prefix is not a valid XML NCName: " + prefix);
}
synchronized (namespaces) {
// Remove existing mapping for the given prefix
namespaces.remove(prefix);
// Remove existing mapping(s) for the given URI
Set prefixes = new HashSet();
for (Map.Entry entry : namespaces.entrySet()) {
if (entry.getValue().equals(uri)) {
prefixes.add(entry.getKey());
}
}
namespaces.keySet().removeAll(prefixes);
// Add the new mapping
namespaces.put(prefix, uri);
}
}
//---------------------------------------------< XML export and import >--
/**
* Generates a document view export using a {@link DocumentViewExporter}
* instance.
*
* @param path of the node to be exported
* @param handler handler for the SAX events of the export
* @param skipBinary whether binary values should be skipped
* @param noRecurse whether to export just the identified node
* @throws PathNotFoundException if a node at the given path does not exist
* @throws SAXException if the SAX event handler failed
* @throws RepositoryException if another error occurs
*/
public void exportDocumentView(
String path, ContentHandler handler,
boolean skipBinary, boolean noRecurse)
throws PathNotFoundException, SAXException, RepositoryException {
export(path, new DocumentViewExporter(
this, handler, !noRecurse, !skipBinary));
}
/**
* Generates a system view export using a {@link SystemViewExporter}
* instance.
*
* @param path of the node to be exported
* @param handler handler for the SAX events of the export
* @param skipBinary whether binary values should be skipped
* @param noRecurse whether to export just the identified node
* @throws PathNotFoundException if a node at the given path does not exist
* @throws SAXException if the SAX event handler failed
* @throws RepositoryException if another error occurs
*/
public void exportSystemView(
String path, ContentHandler handler,
boolean skipBinary, boolean noRecurse)
throws PathNotFoundException, SAXException, RepositoryException {
export(path, new SystemViewExporter(
this, handler, !noRecurse, !skipBinary));
}
/**
* Calls {@link Session#exportDocumentView(String, ContentHandler, boolean, boolean)}
* with the given arguments and a {@link ContentHandler} that serializes
* SAX events to the given output stream.
*
* @param absPath passed through
* @param out output stream to which the SAX events are serialized
* @param skipBinary passed through
* @param noRecurse passed through
* @throws IOException if the SAX serialization failed
* @throws RepositoryException if another error occurs
*/
public void exportDocumentView(
String absPath, OutputStream out,
boolean skipBinary, boolean noRecurse)
throws IOException, RepositoryException {
try {
ContentHandler handler = new ToXmlContentHandler(out);
exportDocumentView(absPath, handler, skipBinary, noRecurse);
} catch (SAXException e) {
Exception exception = e.getException();
if (exception instanceof RepositoryException) {
throw (RepositoryException) exception;
} else if (exception instanceof IOException) {
throw (IOException) exception;
} else {
throw new RepositoryException(
"Error serializing document view XML", e);
}
}
}
/**
* Calls {@link Session#exportSystemView(String, ContentHandler, boolean, boolean)}
* with the given arguments and a {@link ContentHandler} that serializes
* SAX events to the given output stream.
*
* @param absPath passed through
* @param out output stream to which the SAX events are serialized
* @param skipBinary passed through
* @param noRecurse passed through
* @throws IOException if the SAX serialization failed
* @throws RepositoryException if another error occurs
*/
public void exportSystemView(
String absPath, OutputStream out,
boolean skipBinary, boolean noRecurse)
throws IOException, RepositoryException {
try {
ContentHandler handler = new ToXmlContentHandler(out);
exportSystemView(absPath, handler, skipBinary, noRecurse);
} catch (SAXException e) {
Exception exception = e.getException();
if (exception instanceof RepositoryException) {
throw (RepositoryException) exception;
} else if (exception instanceof IOException) {
throw (IOException) exception;
} else {
throw new RepositoryException(
"Error serializing system view XML", e);
}
}
}
/**
* Parses the given input stream as an XML document and processes the
* SAX events using the {@link ContentHandler} returned by
* {@link Session#getImportContentHandler(String, int)}.
*
* @param parentAbsPath passed through
* @param in input stream to be parsed as XML and imported
* @param uuidBehavior passed through
* @throws IOException if an I/O error occurs
* @throws InvalidSerializedDataException if an XML parsing error occurs
* @throws RepositoryException if a repository error occurs
*/
public void importXML(
String parentAbsPath, InputStream in, int uuidBehavior)
throws IOException, InvalidSerializedDataException,
RepositoryException {
try {
ContentHandler handler =
getImportContentHandler(parentAbsPath, uuidBehavior);
new ParsingContentHandler(handler).parse(in);
} catch (SAXException e) {
Throwable exception = e.getException();
if (exception instanceof RepositoryException) {
throw (RepositoryException) exception;
} else if (exception instanceof IOException) {
throw (IOException) exception;
} else {
throw new InvalidSerializedDataException("XML parse error", e);
}
} finally {
// JCR-2903
if (in != null) {
try { in.close(); } catch (IOException ignore) {}
}
}
}
//-----------------------------------------------------< Item handling >--
private String toRelativePath(String absPath) throws PathNotFoundException {
if (absPath.startsWith("/") && absPath.length() > 1) {
return absPath.substring(1);
} else {
throw new PathNotFoundException("Not an absolute path: " + absPath);
}
}
/**
* Returns the node or property at the given path.
*
* The default implementation:
*
* - Returns the root node if the given path is "/"
*
- Delegates to {@link Session#getNodeByIdentifier(String)} for identifier
* paths
*
- Throws a {@link PathNotFoundException} if the given path does not
* start with a slash.
*
- Calls {@link Node#getNode(String)} on the root node with the part of
* the given path after the first slash
*
- Calls {@link Node#getProperty(String)} similarly in case the above
* call fails with a {@link PathNotFoundException}
*
*
* @see Session#getItem(String)
* @param absPath
* absolute path
* @return the node or property with the given path
* @throws PathNotFoundException
* if the given path is invalid or not found
* @throws RepositoryException
* if another error occurs
*/
public Item getItem(String absPath) throws PathNotFoundException, RepositoryException {
Node root = getRootNode();
if (absPath.equals("/")) {
return root;
} else if (absPath.startsWith("[") && absPath.endsWith("]")) {
return getNodeByIdentifier(absPath.substring(1, absPath.length() - 1));
} else {
String relPath = toRelativePath(absPath);
if (root.hasNode(relPath)) {
return root.getNode(relPath);
} else {
return root.getProperty(relPath);
}
}
}
/**
* Calls {@link #getItem(String)} with the given path and returns
* true
if the call succeeds. Returns false
* if a {@link PathNotFoundException} was thrown. Other exceptions are
* passed through.
*
* @see Session#itemExists(String)
* @param absPath absolute path
* @return true
if an item exists at the given path,
* false
otherwise
* @throws RepositoryException if an error occurs
*/
public boolean itemExists(String absPath) throws RepositoryException {
if (absPath.equals("/")) {
return true;
} else {
Node root = getRootNode();
String relPath = toRelativePath(absPath);
return root.hasNode(relPath) || root.hasProperty(relPath);
}
}
/**
* Removes the identified item. Implemented by calling
* {@link Item#remove()} on the item removed by {@link #getItem(String)}.
*
* @see Session#removeItem(String)
* @param absPath An absolute path of the item to be removed
* @throws RepositoryException if the item can not be removed
*/
public void removeItem(String absPath) throws RepositoryException {
getItem(absPath).remove();
}
/**
* Returns the node with the given absolute path.
*
* @see Session#getNode(String)
* @param absPath absolute path
* @return node at the given path
* @throws RepositoryException if the node can not be accessed
*/
public Node getNode(String absPath) throws RepositoryException {
Node root = getRootNode();
if (absPath.equals("/")) {
return root;
} else {
return root.getNode(toRelativePath(absPath));
}
}
/**
* Checks whether a node with the given absolute path exists.
*
* @see Session#nodeExists(String)
* @param absPath absolute path
* @return true
if a node with the given path exists,
* false
otherwise
* @throws RepositoryException if the path is invalid
*/
public boolean nodeExists(String absPath) throws RepositoryException {
if (absPath.equals("/")) {
return true;
} else {
return getRootNode().hasNode(toRelativePath(absPath));
}
}
/**
* Returns the property with the given absolute path.
*
* @see Session#getProperty(String)
* @param absPath absolute path
* @return node at the given path
* @throws RepositoryException if the property can not be accessed
*/
public Property getProperty(String absPath) throws RepositoryException {
if (absPath.equals("/")) {
throw new RepositoryException("The root node is not a property");
} else {
return getRootNode().getProperty(toRelativePath(absPath));
}
}
/**
* Checks whether a property with the given absolute path exists.
*
* @see Session#propertyExists(String)
* @param absPath absolute path
* @return true
if a property with the given path exists,
* false
otherwise
* @throws RepositoryException if the path is invalid
*/
public boolean propertyExists(String absPath) throws RepositoryException {
if (absPath.equals("/")) {
return false;
} else {
return getRootNode().hasProperty(toRelativePath(absPath));
}
}
//--------------------------------------------------< Session handling >--
/**
* Logs in the same workspace with the given credentials.
*
* The default implementation:
*
* - Retrieves the {@link Repository} instance using
* {@link Session#getRepository()}
*
- Retrieves the current workspace using {@link Session#getWorkspace()}
*
- Retrieves the name of the current workspace using
* {@link Workspace#getName()}
*
- Calls {@link Repository#login(Credentials, String)} on the
* retrieved repository with the given credentials and the retrieved
* workspace name.
*
*
* @param credentials login credentials
* @return logged in session
* @throws RepositoryException if an error occurs
*/
public Session impersonate(Credentials credentials)
throws RepositoryException {
return getRepository().login(credentials, getWorkspace().getName());
}
//-------------------------------------------------------------< private >
/**
* Exports content at the given path using the given exporter.
*
* @param path of the node to be exported
* @param exporter document or system view exporter
* @throws SAXException if the SAX event handler failed
* @throws RepositoryException if another error occurs
*/
private synchronized void export(String path, Exporter exporter)
throws PathNotFoundException, SAXException, RepositoryException {
Item item = getItem(path);
if (item.isNode()) {
exporter.export((Node) item);
} else {
throw new PathNotFoundException(
"XML export is not defined for properties: " + path);
}
}
}