package org.springmodules.jcr;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import javax.jcr.Credentials;
import javax.jcr.NamespaceRegistry;
import javax.jcr.Repository;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.Workspace;
import javax.jcr.observation.ObservationManager;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springmodules.jcr.support.GenericSessionHolderProvider;
/**
* Jcr Session Factory. This class is just a simple wrapper around the
* repository which facilitates session retrieval through a central point. No
* exception conversion from Jcr Repository exceptions to Spring DAO exceptions
* is done.
*
* <p/> The session factory is able to add event listener definitions for each
* session and some utility methods.<br/> Note that for added functionality
* (like JackRabbit SessionListener) you can use the decorators package
* (available from JackRabbit).
*
* <p/> This factory beans allows registration for namespaces. By default, newly
* registered namespaces will <strong>not</strong> be unregistered once the
* FactoryBean is destroyed and existing namespaces registered under the same
* suffix will <strong>not</strong> be overwritten since most JCR
* implementations do not support these features.
*
* One can change this behavior using the
* {@link #setForceNamespacesRegistration(boolean)},
* {@link #setKeepNewNamespaces(boolean) and
* {@link #skipRegisteredNamespace(boolean)} methods.
*
* If 'forceNamespacesRegistration' is true and 'keepNewNamespaces' false, the
* overwritten namespaces are registered back when the factory is destroyed.
*
* @author Costin Leau
* @author Brian Moseley <bcm@osafoundation.org>
*
*/
public class JcrSessionFactory implements InitializingBean, DisposableBean, SessionFactory {
private static final Log log = LogFactory.getLog(JcrSessionFactory.class);
private Repository repository;
private String workspaceName;
private Credentials credentials;
private EventListenerDefinition eventListeners[] = new EventListenerDefinition[] {};
private Properties namespaces;
private Map overwrittenNamespaces;
private boolean forceNamespacesRegistration = false;
private boolean keepNewNamespaces = true;
private boolean skipExistingNamespaces = true;
/**
* session holder provider manager - optional.
*/
private SessionHolderProviderManager sessionHolderProviderManager;
/**
* session holder provider - determined and used internally.
*/
private SessionHolderProvider sessionHolderProvider;
/**
* Constructor with all the required fields.
*
* @param repository
* @param workspaceName
* @param credentials
*/
public JcrSessionFactory(Repository repository, String workspaceName, Credentials credentials) {
this(repository, workspaceName, credentials, null);
}
/**
* Constructor containing all the fields available.
*
* @param repository
* @param workspaceName
* @param credentials
* @param sessionHolderProviderManager
*/
public JcrSessionFactory(Repository repository, String workspaceName, Credentials credentials,
SessionHolderProviderManager sessionHolderProviderManager) {
this.repository = repository;
this.workspaceName = workspaceName;
this.credentials = credentials;
this.sessionHolderProviderManager = sessionHolderProviderManager;
}
/**
* Empty constructor.
*/
public JcrSessionFactory() {
}
public void afterPropertiesSet() throws Exception {
Assert.notNull(getRepository(), "repository is required");
if (eventListeners != null && eventListeners.length > 0 && !JcrUtils.supportsObservation(getRepository()))
throw new IllegalArgumentException("repository " + getRepositoryInfo()
+ " does NOT support Observation; remove Listener definitions");
registerNodeTypes();
registerNamespaces();
// determine the session holder provider
if (sessionHolderProviderManager == null) {
if (log.isDebugEnabled())
log.debug("no session holder provider manager set; using the default one");
sessionHolderProvider = new GenericSessionHolderProvider();
}
else
sessionHolderProvider = sessionHolderProviderManager.getSessionProvider(getRepository());
}
/**
* Hook for registering node types on the underlying repository. Since this
* process is not covered by the spec, each implementation requires its own
* subclass.
*
* By default, this method doesn't do anything.
*/
protected void registerNodeTypes() throws Exception {
// do nothing
}
/**
* Hook for un-registering node types on the underlying repository. Since
* this process is not covered by the spec, each implementation requires its
* own subclass.
*
* By default, this method doesn't do anything.
*/
protected void unregisterNodeTypes() throws Exception {
// do nothing
}
/**
* Register the namespaces.
*
* @param session
* @throws RepositoryException
*/
protected void registerNamespaces() throws Exception {
if (namespaces == null || namespaces.isEmpty())
return;
if (log.isDebugEnabled())
log.debug("registering custom namespaces " + namespaces);
NamespaceRegistry registry = getSession().getWorkspace().getNamespaceRegistry();
// do the lookup, so we avoid exceptions
String[] prefixes = registry.getPrefixes();
// sort the array
Arrays.sort(prefixes);
// unregister namespaces if told so
if (forceNamespacesRegistration) {
// save the old namespace only if it makes sense
if (!keepNewNamespaces)
overwrittenNamespaces = new HashMap(namespaces.size());
// search occurences
for (Iterator iter = namespaces.keySet().iterator(); iter.hasNext();) {
String prefix = (String) iter.next();
int position = Arrays.binarySearch(prefixes, prefix);
if (position >= 0) {
if (log.isDebugEnabled()) {
log.debug("prefix " + prefix + " was already registered; unregistering it");
}
if (!keepNewNamespaces) {
// save old namespace
overwrittenNamespaces.put(prefix, registry.getURI(prefix));
}
registry.unregisterNamespace(prefix);
// postpone registration for later
}
}
}
// do the registration
for (Iterator iter = namespaces.entrySet().iterator(); iter.hasNext();) {
Map.Entry namespace = (Map.Entry) iter.next();
String prefix = (String) namespace.getKey();
String ns = (String) namespace.getValue();
int position = Arrays.binarySearch(prefixes, prefix);
if (skipExistingNamespaces && position >= 0) {
log.debug("namespace already registered under [" + prefix + "]; skipping registration");
}
else {
log.debug("registering namespace [" + ns + "] under [" + prefix + "]");
registry.registerNamespace(prefix, ns);
}
}
}
/**
* @see org.springframework.beans.factory.DisposableBean#destroy()
*/
public void destroy() throws Exception {
unregisterNamespaces();
unregisterNodeTypes();
}
/**
* Removes the namespaces.
*
* @param session
*/
protected void unregisterNamespaces() throws Exception {
if (namespaces == null || namespaces.isEmpty() || keepNewNamespaces)
return;
if (log.isDebugEnabled())
log.debug("unregistering custom namespaces " + namespaces);
NamespaceRegistry registry = getSession().getWorkspace().getNamespaceRegistry();
for (Iterator iter = namespaces.keySet().iterator(); iter.hasNext();) {
String prefix = (String) iter.next();
registry.unregisterNamespace(prefix);
}
if (forceNamespacesRegistration) {
if (log.isDebugEnabled())
log.debug("reverting back overwritten namespaces " + overwrittenNamespaces);
if (overwrittenNamespaces != null)
for (Iterator iter = overwrittenNamespaces.entrySet().iterator(); iter.hasNext();) {
Map.Entry entry = (Map.Entry) iter.next();
registry.registerNamespace((String) entry.getKey(), (String) entry.getValue());
}
}
}
/**
* @see org.springmodules.jcr.SessionFactory#getSession()
*/
public Session getSession() throws RepositoryException {
return addListeners(repository.login(credentials, workspaceName));
}
/**
* @see org.springmodules.jcr.SessionFactory#getSessionHolder(javax.jcr.Session)
*/
public SessionHolder getSessionHolder(Session session) {
return sessionHolderProvider.createSessionHolder(session);
}
/**
* Hook for adding listeners to the newly returned session. We have to treat
* exceptions manually and can't reply on the template.
*
* @param session JCR session
* @return the listened session
*/
protected Session addListeners(Session session) throws RepositoryException {
if (eventListeners != null && eventListeners.length > 0) {
Workspace ws = session.getWorkspace();
ObservationManager manager = ws.getObservationManager();
if (log.isDebugEnabled())
log.debug("adding listeners " + Arrays.asList(eventListeners).toString() + " for session " + session);
for (int i = 0; i < eventListeners.length; i++) {
manager.addEventListener(eventListeners[i].getListener(), eventListeners[i].getEventTypes(),
eventListeners[i].getAbsPath(), eventListeners[i].isDeep(), eventListeners[i].getUuid(),
eventListeners[i].getNodeTypeName(), eventListeners[i].isNoLocal());
}
}
return session;
}
/**
* @return Returns the repository.
*/
public Repository getRepository() {
return repository;
}
/**
* @param repository The repository to set.
*/
public void setRepository(Repository repository) {
this.repository = repository;
}
/**
* @param workspaceName The workspaceName to set.
*/
public void setWorkspaceName(String workspaceName) {
this.workspaceName = workspaceName;
}
/**
* @param credentials The credentials to set.
*/
public void setCredentials(Credentials credentials) {
this.credentials = credentials;
}
/**
* @see java.lang.Object#equals(java.lang.Object)
*/
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj instanceof JcrSessionFactory)
return (this.hashCode() == obj.hashCode());
return false;
}
/**
* @see java.lang.Object#hashCode()
*/
public int hashCode() {
int result = 17;
result = 37 * result + repository.hashCode();
// add the optional params (can be null)
if (credentials != null)
result = 37 * result + credentials.hashCode();
if (workspaceName != null)
result = 37 * result + workspaceName.hashCode();
return result;
}
/**
* @see java.lang.Object#toString()
*/
public String toString() {
StringBuffer buffer = new StringBuffer();
buffer.append("SessionFactory for ");
buffer.append(getRepositoryInfo());
buffer.append("|workspace=");
buffer.append(workspaceName);
return buffer.toString();
}
/**
* @return Returns the eventListenerDefinitions.
*/
public EventListenerDefinition[] getEventListeners() {
return eventListeners;
}
/**
* @param eventListenerDefinitions The eventListenerDefinitions to set.
*/
public void setEventListeners(EventListenerDefinition[] eventListenerDefinitions) {
this.eventListeners = eventListenerDefinitions;
}
/**
* A toString representation of the Repository.
*
* @return
*/
private String getRepositoryInfo() {
// in case toString() is called before afterPropertiesSet()
if (getRepository() == null)
return "<N/A>";
StringBuffer buffer = new StringBuffer();
buffer.append(getRepository().getDescriptor(Repository.REP_NAME_DESC));
buffer.append(" ");
buffer.append(getRepository().getDescriptor(Repository.REP_VERSION_DESC));
return buffer.toString();
}
/**
* @return Returns the namespaces.
*/
public Properties getNamespaces() {
return namespaces;
}
/**
* @param namespaces The namespaces to set.
*/
public void setNamespaces(Properties namespaces) {
this.namespaces = namespaces;
}
/**
* Used internally.
*
* @return Returns the sessionHolderProvider.
*/
protected SessionHolderProvider getSessionHolderProvider() {
return sessionHolderProvider;
}
/**
* Used internally.
*
* @param sessionHolderProvider The sessionHolderProvider to set.
*/
protected void setSessionHolderProvider(SessionHolderProvider sessionHolderProvider) {
this.sessionHolderProvider = sessionHolderProvider;
}
/**
* @return Returns the sessionHolderProviderManager.
*/
public SessionHolderProviderManager getSessionHolderProviderManager() {
return sessionHolderProviderManager;
}
/**
* @param sessionHolderProviderManager The sessionHolderProviderManager to
* set.
*/
public void setSessionHolderProviderManager(SessionHolderProviderManager sessionHolderProviderManager) {
this.sessionHolderProviderManager = sessionHolderProviderManager;
}
/**
* Indicate if the given namespace registrations will be kept (the default)
* when the application context closes down or if they will be unregistered.
*
* If unregistered, the namespace mappings that were overriden are
* registered back to the repository.
*
* @see #forceNamespacesRegistration
* @param keepNamespaces The keepNamespaces to set.
*/
public void setKeepNewNamespaces(boolean keepNamespaces) {
this.keepNewNamespaces = keepNamespaces;
}
/**
* Indicate if the given namespace registrations will override the namespace
* already registered in the repository under the same prefix. This will
* cause unregistration for the namespaces that will be modified.
*
* <p/> However, depending on the {@link #setKeepNewNamespaces(boolean)}
* setting, the old namespaces can be registered back once the application
* context is destroyed.
*
* False by default.
*
* @param forceNamespacesRegistration The forceNamespacesRegistration to
* set.
*/
public void setForceNamespacesRegistration(boolean forceNamespacesRegistration) {
this.forceNamespacesRegistration = forceNamespacesRegistration;
}
/**
* Indicate if the given namespace registrations will skip already
* registered namespaces or not. If true (default), the new namespace will
* not be registered and the old namespace kept in place. If not skipped,
* registration of new namespaces will fail if there are already namespace
* registered under the same prefix.
*
* <p/> This flag is required for JCR implementations which do not support
* namespace unregistration which render the
* {@link #setForceNamespacesRegistration(boolean)} method useless (as
* namespace registration cannot be forced).
*
* @param skipRegisteredNamespace The skipRegisteredNamespace to set.
*/
public void setSkipExistingNamespaces(boolean skipRegisteredNamespace) {
this.skipExistingNamespaces = skipRegisteredNamespace;
}
/**
* @return Returns the forceNamespacesRegistration.
*/
public boolean isForceNamespacesRegistration() {
return forceNamespacesRegistration;
}
/**
* @return Returns the keepNewNamespaces.
*/
public boolean isKeepNewNamespaces() {
return keepNewNamespaces;
}
/**
* @return Returns the skipExistingNamespaces.
*/
public boolean isSkipExistingNamespaces() {
return skipExistingNamespaces;
}
/**
* @return Returns the credentials.
*/
public Credentials getCredentials() {
return credentials;
}
/**
* @return Returns the workspaceName.
*/
public String getWorkspaceName() {
return workspaceName;
}
}