SLING-3384 Create AbstractSlingRepositoryManager and AbstractSlingRepository2
- Also create new NamespaceMappingSupport base class allowing for better
integrations going forward
- AbstractSlingRepository and AbstractNamespaceMappingRepository are
deprecated
git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1566547 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/src/main/java/org/apache/sling/jcr/base/AbstractNamespaceMappingRepository.java b/src/main/java/org/apache/sling/jcr/base/AbstractNamespaceMappingRepository.java
index cf27d1b..4a8add3 100644
--- a/src/main/java/org/apache/sling/jcr/base/AbstractNamespaceMappingRepository.java
+++ b/src/main/java/org/apache/sling/jcr/base/AbstractNamespaceMappingRepository.java
@@ -18,35 +18,46 @@
*/
package org.apache.sling.jcr.base;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-
import org.apache.sling.jcr.api.NamespaceMapper;
import org.apache.sling.jcr.api.SlingRepository;
-import org.apache.sling.jcr.base.internal.loader.Loader;
import org.osgi.framework.BundleContext;
import org.osgi.util.tracker.ServiceTracker;
+import aQute.bnd.annotation.ProviderType;
+
/**
- * The <code>AbstractNamespaceMappingRepository</code> is an abstract implementation of
- * the {@link SlingRepository} interface which provides default support for
- * namespace mapping.
+ * The <code>AbstractNamespaceMappingRepository</code> is an abstract
+ * implementation of the {@link SlingRepository} interface which provides
+ * default support for namespace mapping.
+ *
+ * @deprecated as of API version 2.3 (bundle version 2.3). Use
+ * {@link NamespaceMappingSupport} or
+ * {@link AbstractSlingRepositoryManager} and
+ * {@link AbstractSlingRepository2} instead.
*/
-public abstract class AbstractNamespaceMappingRepository implements SlingRepository {
-
- /** Namespace handler. */
- private Loader namespaceHandler;
-
- /** Session proxy handler. */
- private SessionProxyHandler sessionProxyHandler;
+@Deprecated
+@ProviderType
+public abstract class AbstractNamespaceMappingRepository extends NamespaceMappingSupport implements SlingRepository {
private ServiceTracker namespaceMapperTracker;
+ protected final NamespaceMapper[] getNamespaceMapperServices() {
+ if (namespaceMapperTracker != null) {
+ // call namespace mappers
+ final Object[] nsMappers = namespaceMapperTracker.getServices();
+ if (nsMappers != null) {
+ NamespaceMapper[] mappers = new NamespaceMapper[nsMappers.length];
+ System.arraycopy(nsMappers, 0, mappers, 0, nsMappers.length);
+ return mappers;
+ }
+ }
+ return null;
+ }
+
protected void setup(final BundleContext bundleContext) {
+ super.setup(bundleContext, this);
this.namespaceMapperTracker = new ServiceTracker(bundleContext, NamespaceMapper.class.getName(), null);
this.namespaceMapperTracker.open();
- this.namespaceHandler = new Loader(this, bundleContext);
- this.sessionProxyHandler = new SessionProxyHandler(this);
}
protected void tearDown() {
@@ -54,46 +65,7 @@
this.namespaceMapperTracker.close();
this.namespaceMapperTracker = null;
}
- if (this.namespaceHandler != null) {
- this.namespaceHandler.dispose();
- this.namespaceHandler = null;
- }
- this.sessionProxyHandler = null;
+ super.tearDown();
}
- void defineNamespacePrefixes(final Session session) throws RepositoryException {
- final Loader localHandler = this.namespaceHandler;
- if (localHandler != null) {
- // apply namespace mapping
- localHandler.defineNamespacePrefixes(session);
- }
-
- if (namespaceMapperTracker != null) {
- // call namespace mappers
- final Object[] nsMappers = namespaceMapperTracker.getServices();
- if (nsMappers != null) {
- for (int i = 0; i < nsMappers.length; i++) {
- ((NamespaceMapper) nsMappers[i]).defineNamespacePrefixes(session);
- }
- }
- }
- }
-
- /**
- * Return a namespace aware session.
- */
- protected Session getNamespaceAwareSession(final Session session) throws RepositoryException {
- if ( session == null ) { // sanity check
- return null;
- }
- defineNamespacePrefixes(session);
-
- // to support namespace prefixes if session.impersonate is called
- // we have to use a proxy
- final SessionProxyHandler localHandler = this.sessionProxyHandler;
- if ( localHandler != null ) {
- return localHandler.createProxy(session);
- }
- return session;
- }
}
diff --git a/src/main/java/org/apache/sling/jcr/base/AbstractSlingRepository.java b/src/main/java/org/apache/sling/jcr/base/AbstractSlingRepository.java
index 5768eeb..5cf64c8 100644
--- a/src/main/java/org/apache/sling/jcr/base/AbstractSlingRepository.java
+++ b/src/main/java/org/apache/sling/jcr/base/AbstractSlingRepository.java
@@ -47,6 +47,8 @@
import org.osgi.service.log.LogService;
import org.slf4j.LoggerFactory;
+import aQute.bnd.annotation.ProviderType;
+
/**
* The <code>AbstractSlingRepository</code> is an abstract implementation of the
* {@link SlingRepository} interface which provides default support for attached
@@ -79,7 +81,13 @@
* method must replace this overwritten method with overwriting the new
* {@link #getServiceRegistrationInterfaces()} and/or
* {@link #getServiceRegistrationProperties()} methods.
+ *
+ * @deprecated as of API version 2.3 (bundle version 2.3). Use
+ * {@link AbstractSlingRepositoryManager} and
+ * {@link AbstractSlingRepository2} instead.
*/
+@Deprecated
+@ProviderType
@Component(componentAbstract=true)
public abstract class AbstractSlingRepository
extends AbstractNamespaceMappingRepository
diff --git a/src/main/java/org/apache/sling/jcr/base/AbstractSlingRepository2.java b/src/main/java/org/apache/sling/jcr/base/AbstractSlingRepository2.java
new file mode 100644
index 0000000..1f76446
--- /dev/null
+++ b/src/main/java/org/apache/sling/jcr/base/AbstractSlingRepository2.java
@@ -0,0 +1,428 @@
+/*
+ * 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.sling.jcr.base;
+
+import javax.jcr.Credentials;
+import javax.jcr.GuestCredentials;
+import javax.jcr.LoginException;
+import javax.jcr.NoSuchWorkspaceException;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.SimpleCredentials;
+import javax.jcr.Value;
+
+import org.apache.sling.jcr.api.SlingRepository;
+import org.apache.sling.serviceusermapping.ServiceUserMapper;
+import org.osgi.framework.Bundle;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import aQute.bnd.annotation.ProviderType;
+
+/**
+ * The <code>AbstractSlingRepository2</code> is an abstract implementation of
+ * the {@link SlingRepository} version 2.2 interface (phasing
+ * {@link #loginAdministrative(String)} out in favor of
+ * {@link #loginService(String, String)}) which provides default support for
+ * attached repositories as well as namespace mapping support by wrapping
+ * sessions with namespace support (see
+ * {@link #getNamespaceAwareSession(Session)}).
+ * <p>
+ * Implementations of the <code>SlingRepository</code> interface may wish to
+ * extend this class to benefit from default implementations of most methods.
+ * <p/>
+ * To be able to know the calling bundle to implement the
+ * {@link #loginService(String, String)} method the bundle using the repository
+ * service has to be provided in the
+ * {@link #AbstractSlingRepository2(AbstractSlingRepositoryManager, Bundle)
+ * constructor}.
+ * <p>
+ * The premise of this abstract class is that an instance of an implementation
+ * of this abstract class is handed out to each consumer of the
+ * {@code SlingRepository} service. Each instance is generally based on the same
+ * {@link #getRepository() delegated repository} instance and also makes use of
+ * a single system wide {@link #getNamespaceAwareSession(Session) namespace
+ * session support}.
+ *
+ * @see AbstractSlingRepositoryManager
+ * @since API version 2.3 (bundle version 2.3)
+ */
+@ProviderType
+public abstract class AbstractSlingRepository2 implements SlingRepository {
+
+ private final Logger log = LoggerFactory.getLogger(getClass());
+
+ // the repository manager
+ private final AbstractSlingRepositoryManager manager;
+
+ // the bundle using this repository instance
+ private final Bundle usingBundle;
+
+ /**
+ * Sets up this abstract SlingRepository implementation.
+ *
+ * @param manager The {@link AbstractSlingRepositoryManager} controlling
+ * this instance as well as the actual JCR repository instance
+ * used by this.
+ * @param usingBundle The bundle using this instance. This is used by the
+ * {@link #loginService(String, String)} method, which will not
+ * be able to login if this parameter is {@code null}
+ */
+ protected AbstractSlingRepository2(final AbstractSlingRepositoryManager manager, final Bundle usingBundle) {
+ this.manager = manager;
+ this.usingBundle = usingBundle;
+ }
+
+ /**
+ * @return The {@link AbstractSlingRepositoryManager} controlling this
+ * instances
+ */
+ protected final AbstractSlingRepositoryManager getSlingRepositoryManager() {
+ return this.manager;
+ }
+
+ /**
+ * Returns the actual repository to which all JCR Repository interface
+ * methods implemented by this class are delegated.
+ *
+ * @return The delegated repository.
+ */
+ protected final Repository getRepository() {
+ return this.getSlingRepositoryManager().getRepository();
+ }
+
+ /**
+ * Returns the default workspace to login to if any of the {@code login} and
+ * {@code createSession} methods is called without an explicit workspace
+ * name.
+ * <p>
+ * This method may return {@code null} in which case the actual default
+ * workspace used depends on the underlying JCR Repository implementation.
+ */
+ public final String getDefaultWorkspace() {
+ return this.getSlingRepositoryManager().getDefaultWorkspace();
+ }
+
+ /**
+ * Wraps the given session with support for name spaces defined by bundles
+ * deployed in the OSGi framework. See {@link NamespaceMappingSupport} for
+ * details about namespace support in Sling.
+ * <p>
+ * To fully support namespaces, this method must be called from each
+ * implementation of any of the {@code login} methods implemented by this
+ * class or its extensions.
+ *
+ * @param session The {@code Session} to wrap. This must not be {@code null}
+ * @return The wrapped session
+ * @throws RepositoryException If an error occurrs wrapping the session
+ * @throws NullPointerException If {@code session} is {@code null}
+ */
+ protected final Session getNamespaceAwareSession(Session session) throws RepositoryException {
+ return this.getSlingRepositoryManager().getNamespaceAwareSession(session);
+ }
+
+ /**
+ * Creates an administrative session to access the indicated workspace.
+ * <p>
+ * This method is called by the {@link #loginAdministrative(String)} and
+ * {@link #createServiceSession(String, String)} methods.
+ * <p>
+ * Implementations of this method must not call
+ * {@link #getNamespaceAwareSession(Session)} as this is handled by the
+ * calling method.
+ *
+ * @param workspace The workspace to access or {@code null} to access the
+ * {@link #getDefaultWorkspace() default workspace}
+ * @return An administrative session
+ * @throws RepositoryException If a general error occurs during login
+ * @see #createServiceSession(String, String)
+ * @see #loginAdministrative(String)
+ */
+ protected abstract Session createAdministrativeSession(String workspace) throws RepositoryException;
+
+ /**
+ * Creates a service-session for the service's {@code serviceUserName} by
+ * impersonating the user from an administrative session.
+ * <p>
+ * The administrative session is created calling
+ * {@link #createAdministrativeSession(String)
+ * createAdministrativeSession(workspace)} and is logged out before this
+ * method returns.
+ * <p>
+ * Implementations of this class may overwrite this method with a better
+ * implementation, notably one which does not involve a temporary creation
+ * of an administrative session.
+ * <p>
+ * This method must not call {@link #getNamespaceAwareSession(Session)} as
+ * this is handled by the calling method.
+ *
+ * @param serviceUserName The name of the user to create the session for
+ * @param workspace The workspace to access or {@code null} to access the
+ * {@link #getDefaultWorkspace() default workspace}
+ * @return A session for the given user
+ * @throws RepositoryException If a general error occurs while creating the
+ * session
+ */
+ protected Session createServiceSession(String serviceUserName, String workspace) throws RepositoryException {
+ Session admin = null;
+ try {
+ admin = this.createAdministrativeSession(workspace);
+ return admin.impersonate(new SimpleCredentials(serviceUserName, new char[0]));
+ } finally {
+ if (admin != null) {
+ admin.logout();
+ }
+ }
+ }
+
+ // login implementations (may be overwritten)
+
+ /**
+ * Same as calling {@code login(null, null)}.
+ * <p>
+ * This method may be overwritten. Care must be taken to call
+ * {@link #getNamespaceAwareSession(Session)} before return to the caller.
+ *
+ * @return the result of calling {@link #login(Credentials, String)
+ * login(null, null)}.
+ * @throws LoginException If login is not possible
+ * @throws RepositoryException If another error occurrs during login
+ * @see #login(Credentials, String)
+ * @see #getNamespaceAwareSession(Session)
+ */
+ public Session login() throws LoginException, RepositoryException {
+ return this.login(null, null);
+ }
+
+ /**
+ * Same as calling {@code login(credentials, null)}.
+ * <p>
+ * This method may be overwritten. Care must be taken to call
+ * {@link #getNamespaceAwareSession(Session)} before return to the caller.
+ *
+ * @param credentials The {@code Credentials} to use to login.
+ * @return the result of calling {@link #login(Credentials, String)
+ * login(credentials, null)}.
+ * @throws LoginException If login is not possible
+ * @throws RepositoryException If another error occurrs during login
+ * @see #login(Credentials, String)
+ * @see #getNamespaceAwareSession(Session)
+ */
+ public Session login(Credentials credentials) throws LoginException, RepositoryException {
+ return this.login(credentials, null);
+ }
+
+ /**
+ * Same as calling {@code login(null, workspace)}.
+ * <p>
+ * This method may be overwritten. Care must be taken to call
+ * {@link #getNamespaceAwareSession(Session)} before return to the caller.
+ *
+ * @param workspace The workspace to access or {@code null} to access the
+ * {@link #getDefaultWorkspace() default workspace}
+ * @return the result of calling {@link #login(Credentials, String)
+ * login(null, workspace)}.
+ * @throws LoginException If login is not possible
+ * @throws RepositoryException If another error occurrs during login
+ * @see #login(Credentials, String)
+ * @see #getNamespaceAwareSession(Session)
+ */
+ public Session login(String workspace) throws LoginException, NoSuchWorkspaceException, RepositoryException {
+ return this.login(null, workspace);
+ }
+
+ /**
+ * Logs into the repository at the given {@code workspace} with the given
+ * {@code credentials} and returns a namespace aware session by calling
+ * {@link #getNamespaceAwareSession(Session)} with the session returned from
+ * the repository..
+ * <p>
+ * This method logs in as a guest if {@code null} credentials are provided.
+ * The method may be overwritten to implement a different behaviour as
+ * indicated by the JCR specification for this method to use external
+ * mechanisms to login instead of leveraging provided credentials. An
+ * implementation overwriting this method must also call
+ * {@link #getNamespaceAwareSession(Session)} before returning the session
+ * to the caller.
+ * <p>
+ * This method may be overwritten. Care must be taken to call
+ * {@link #getNamespaceAwareSession(Session)} before return to the caller.
+ *
+ * @param credentials The {@code Credentials} to use to login. If this is
+ * {@code null} JCR {@code GuestCredentials} are used to login.
+ * @param workspace The workspace to access or {@code null} to access the
+ * {@link #getDefaultWorkspace() default workspace}
+ * @throws LoginException If login is not possible
+ * @throws NoSuchWorkspaceException if the desired workspace is not
+ * available
+ * @throws RepositoryException If another error occurrs during login
+ * @see #getNamespaceAwareSession(Session)
+ */
+ public Session login(Credentials credentials, String workspace) throws LoginException, NoSuchWorkspaceException,
+ RepositoryException {
+
+ if (credentials == null) {
+ credentials = new GuestCredentials();
+ }
+
+ // check the workspace
+ if (workspace == null) {
+ workspace = this.getDefaultWorkspace();
+ }
+
+ try {
+ log.debug("login: Logging in to workspace '" + workspace + "'");
+ final Repository repository = this.getRepository();
+ if (this.getRepository() == null) {
+ throw new RepositoryException("Sling Repository not ready");
+ }
+
+ Session session = repository.login(credentials, workspace);
+ return this.getNamespaceAwareSession(session);
+
+ } catch (RuntimeException re) {
+ // SLING-702: Jackrabbit throws IllegalStateException if the
+ // repository has already been shut down ...
+ throw new RepositoryException(re.getMessage(), re);
+ }
+ }
+
+ // service login
+
+ /**
+ * Actual implementation of the {@link #loginService(String, String)} method
+ * taking into account the bundle calling this method.
+ * <p/>
+ * This method uses the
+ * {@link AbstractSlingRepositoryManager#getServiceUserMapper()
+ * ServiceUserMapper} service to map the named service to a user and then
+ * calls the {@link #createServiceSession(String, String)} method actually
+ * create a session for that user.
+ *
+ * @param subServiceName An optional subService identifier (may be
+ * {@code null})
+ * @param workspace The workspace to access or {@code null} to access the
+ * {@link #getDefaultWorkspace() default workspace}
+ * @return A session authenticated with the service user
+ * @throws LoginException if the service name cannot be derived or if
+ * logging is as the user to which the service name maps is not
+ * allowed
+ * @throws RepositoryException If a general error occurs while creating the
+ * session
+ */
+ public final Session loginService(String subServiceName, String workspace) throws LoginException,
+ RepositoryException {
+ final ServiceUserMapper serviceUserMapper = this.getSlingRepositoryManager().getServiceUserMapper();
+ final String userName = (serviceUserMapper != null) ? serviceUserMapper.getServiceUserID(this.usingBundle,
+ subServiceName) : null;
+ if (userName == null) {
+ throw new LoginException("Cannot derive user name for bundle " + usingBundle + " and sub service "
+ + subServiceName);
+ }
+ return getNamespaceAwareSession(createServiceSession(userName, workspace));
+ }
+
+ /**
+ * Login as an administrative user. This method is deprecated and its use
+ * can be completely disabled by setting {@code disableLoginAdministrative}
+ * to {@code true}.
+ * <p>
+ * This implementation cannot be overwritten but, unless disabled, forwards
+ * to the {@link #createAdministrativeSession(String)} method.
+ *
+ * @param workspace The workspace to access or {@code null} to access the
+ * {@link #getDefaultWorkspace() default workspace}
+ * @return An administrative session
+ * @throws RepositoryException If the login fails or has been disabled
+ */
+ public final Session loginAdministrative(String workspace) throws RepositoryException {
+ if (this.getSlingRepositoryManager().isDisableLoginAdministrative()) {
+ log.error("SlingRepository.loginAdministrative is disabled. Please use SlingRepository.loginService.");
+ throw new LoginException();
+ }
+
+ log.debug("SlingRepository.loginAdministrative is deprecated. Please use SlingRepository.loginService.");
+ return getNamespaceAwareSession(createAdministrativeSession(workspace));
+ }
+
+ // Remaining Repository service methods all backed by the actual
+ // repository instance. They may be overwritten, but generally are not.
+
+ public String getDescriptor(String name) {
+ Repository repo = getRepository();
+ if (repo != null) {
+ return repo.getDescriptor(name);
+ }
+
+ log.error("getDescriptor: Repository not available");
+ return null;
+ }
+
+ public String[] getDescriptorKeys() {
+ Repository repo = getRepository();
+ if (repo != null) {
+ return repo.getDescriptorKeys();
+ }
+
+ log.error("getDescriptorKeys: Repository not available");
+ return new String[0];
+ }
+
+ public Value getDescriptorValue(String key) {
+ Repository repo = getRepository();
+ if (repo != null) {
+ return repo.getDescriptorValue(key);
+ }
+
+ log.error("getDescriptorValue: Repository not available");
+ return null;
+ }
+
+ public Value[] getDescriptorValues(String key) {
+ Repository repo = getRepository();
+ if (repo != null) {
+ return repo.getDescriptorValues(key);
+ }
+
+ log.error("getDescriptorValues: Repository not available");
+ return null;
+ }
+
+ public boolean isSingleValueDescriptor(String key) {
+ Repository repo = getRepository();
+ if (repo != null) {
+ return repo.isSingleValueDescriptor(key);
+ }
+
+ log.error("isSingleValueDescriptor: Repository not available");
+ return false;
+ }
+
+ public boolean isStandardDescriptor(String key) {
+ Repository repo = getRepository();
+ if (repo != null) {
+ return repo.isStandardDescriptor(key);
+ }
+
+ log.error("isStandardDescriptor: Repository not available");
+ return false;
+ }
+
+}
diff --git a/src/main/java/org/apache/sling/jcr/base/AbstractSlingRepositoryManager.java b/src/main/java/org/apache/sling/jcr/base/AbstractSlingRepositoryManager.java
new file mode 100644
index 0000000..429d7f4
--- /dev/null
+++ b/src/main/java/org/apache/sling/jcr/base/AbstractSlingRepositoryManager.java
@@ -0,0 +1,376 @@
+/*
+ * 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.sling.jcr.base;
+
+import java.util.Dictionary;
+
+import javax.jcr.Repository;
+
+import org.apache.sling.jcr.api.SlingRepository;
+import org.apache.sling.serviceusermapping.ServiceUserMapper;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceFactory;
+import org.osgi.framework.ServiceRegistration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import aQute.bnd.annotation.ProviderType;
+
+/**
+ * The <code>AbstractSlingRepositoryManager</code> is the basis for controlling
+ * the JCR repository instances used by Sling. As a manager it starts and stops
+ * the actual repository instance, manages service registration and hands out
+ * {@code SlingRepository} instances to be used by the consumers.
+ * <p>
+ * This base class controls the livecycle of repository instance whereas
+ * implementations of this class provide actual integration into the runtime
+ * context. The livecycle of the repository instance is defined as follows:
+ * <p>
+ * To start the repository instance, the implemetation calls the
+ * {@link #start(BundleContext, String, boolean)}method which goes through the
+ * steps of instantiating the repository, setting things up, and registering the
+ * repository as an OSGi service:
+ * <ol>
+ * <li>{@link #acquireRepository()}</li>
+ * <li>{@link #create(Bundle)}</li>
+ * <li>{@link #setup(BundleContext, SlingRepository)}</li>
+ * <li>{@link #registerService()}</li>
+ * </ol>
+ * <p>
+ * To stop the repository instance, the implementation calls the {@link #stop()}
+ * method which goes through the setps of unregistering the OSGi service,
+ * tearing all special settings down and finally shutting down the repository:
+ * <ol>
+ * <li>{@link #unregisterService(ServiceRegistration)}</li>
+ * <li>{@link #tearDown()}</li>
+ * <li>{@link #destroy(AbstractSlingRepository2)}</li>
+ * <li>{@link #disposeRepository(Repository)}</li>
+ * </ol>
+ * <p>
+ * Instances of this class manage a single repository instance backing the OSGi
+ * service instances. Each consuming bundle, though, gets its own service
+ * instance backed by the single actual repository instance managed by this
+ * class.
+ *
+ * @see AbstractSlingRepository2
+ * @since API version 2.3 (bundle version 2.3)
+ */
+@ProviderType
+public abstract class AbstractSlingRepositoryManager extends NamespaceMappingSupport {
+
+ /** default log */
+ private final Logger log = LoggerFactory.getLogger(getClass());
+
+ private BundleContext bundleContext;
+
+ private Repository repository;
+
+ // the SlingRepository instance used to setup basic stuff
+ // see setup and tearDown
+ private AbstractSlingRepository2 masterSlingRepository;
+
+ private ServiceRegistration repositoryService;
+
+ private String defaultWorkspace;
+
+ private boolean disableLoginAdministrative;
+
+ /**
+ * Returns the default workspace, which may be <code>null</code> meaning to
+ * use the repository provided default workspace.
+ *
+ * @return the default workspace or {@code null} indicating the repository's
+ * default workspace is actually used.
+ */
+ public final String getDefaultWorkspace() {
+ return defaultWorkspace;
+ }
+
+ /**
+ * Returns whether to disable the
+ * {@code SlingRepository.loginAdministrative} method or not.
+ *
+ * @return {@code true} if {@code SlingRepository.loginAdministrative} is
+ * disabled.
+ */
+ public final boolean isDisableLoginAdministrative() {
+ return disableLoginAdministrative;
+ }
+
+ /**
+ * Returns the {@code ServiceUserMapper} service to map the service name to
+ * a service user name.
+ * <p>
+ * The {@code ServiceUserMapper} is used to implement the
+ * {@link AbstractSlingRepository2#loginService(String, String)} method used
+ * to replace the
+ * {@link AbstractSlingRepository2#loginAdministrative(String)} method. If
+ * this method returns {@code null} and hence the
+ * {@code ServiceUserMapperService} is not available, the
+ * {@code loginService} method is not able to login.
+ *
+ * @return The {@code ServiceUserMapper} service or {@code null} if not
+ * available.
+ * @see AbstractSlingRepository2#loginService(String, String)
+ */
+ protected abstract ServiceUserMapper getServiceUserMapper();
+
+ /**
+ * Creates the backing JCR repository instances. It is expected for this
+ * method to just start the repository.
+ * <p>
+ * This method does not throw any <code>Throwable</code> but instead just
+ * returns <code>null</code> if not repository is available. Any problems
+ * trying to acquire the repository must be caught and logged as
+ * appropriate.
+ *
+ * @return The acquired JCR <code>Repository</code> or <code>null</code> if
+ * not repository can be acquired.
+ * @see #start(BundleContext, String, boolean)
+ */
+ protected abstract Repository acquireRepository();
+
+ /**
+ * Registers this component as an OSGi service with the types provided by
+ * the {@link #getServiceRegistrationInterfaces()} method and properties
+ * provided by the {@link #getServiceRegistrationProperties()} method.
+ * <p>
+ * The repository is actually registered as an OSGi {@code ServiceFactory}
+ * where the {@link #create(Bundle)} method is called to create an actual
+ * {@link AbstractSlingRepository2} repository instance for a calling
+ * (using) bundle. When the bundle is done using the repository instance,
+ * the {@link #destroy(AbstractSlingRepository2)} method is called to clean
+ * up.
+ *
+ * @return The OSGi <code>ServiceRegistration</code> object representing the
+ * registered service.
+ * @see #start(BundleContext, String, boolean)
+ * @see #getServiceRegistrationInterfaces()
+ * @see #getServiceRegistrationProperties()
+ * @see #create(Bundle)
+ * @see #destroy(AbstractSlingRepository2)
+ */
+ protected final ServiceRegistration registerService() {
+ final Dictionary<String, Object> props = getServiceRegistrationProperties();
+ final String[] interfaces = getServiceRegistrationInterfaces();
+
+ return bundleContext.registerService(interfaces, new ServiceFactory() {
+ public Object getService(Bundle bundle, ServiceRegistration registration) {
+ return AbstractSlingRepositoryManager.this.create(bundle);
+ }
+
+ public void ungetService(Bundle bundle, ServiceRegistration registration, Object service) {
+ AbstractSlingRepositoryManager.this.destroy((AbstractSlingRepository2) service);
+ }
+ }, props);
+ }
+
+ /**
+ * Return the service registration properties to be used to register the
+ * repository service in {@link #registerService()}.
+ *
+ * @return The service registration properties to be used to register the
+ * repository service in {@link #registerService()}
+ * @see #registerService()
+ */
+ protected abstract Dictionary<String, Object> getServiceRegistrationProperties();
+
+ /**
+ * Returns the service types to be used to register the repository service
+ * in {@link #registerService()}. All interfaces returned must be accessible
+ * to the class loader of the class of this instance.
+ * <p>
+ * This method may be overwritten to return additional types but the types
+ * returned from this base implementation, {@code SlingRepository} and
+ * {@code Repository}, must always be included.
+ *
+ * @return The service types to be used to register the repository service
+ * in {@link #registerService()}
+ * @see #registerService()
+ */
+ protected String[] getServiceRegistrationInterfaces() {
+ return new String[] {
+ SlingRepository.class.getName(), Repository.class.getName()
+ };
+ }
+
+ /**
+ * Creates an instance of the {@link AbstractSlingRepository2}
+ * implementation for use by the given {@code usingBundle}.
+ * <p>
+ * This method is called when the repository service is requested from
+ * within the using bundle for the first time.
+ * <p>
+ * This method is expected to return a new instance on every call.
+ *
+ * @param usingBundle The bundle providing from which the repository is
+ * requested.
+ * @return The {@link AbstractSlingRepository2} implementation instance to
+ * be used by the {@code usingBundle}.
+ * @see #registerService()
+ */
+ protected abstract AbstractSlingRepository2 create(Bundle usingBundle);
+
+ /**
+ * Cleans up the given {@link AbstractSlingRepository2} instance previously
+ * created by the {@link #create(Bundle)} method.
+ *
+ * @param repositoryServiceInstance The {@link AbstractSlingRepository2}
+ * istance to cleanup.
+ * @see #registerService()
+ */
+ protected abstract void destroy(AbstractSlingRepository2 repositoryServiceInstance);
+
+ /**
+ * Returns the repository underlying this instance or <code>null</code> if
+ * no repository is currently being available.
+ *
+ * @return The repository
+ */
+ protected final Repository getRepository() {
+ return repository;
+ }
+
+ /**
+ * Unregisters the service represented by the
+ * <code>serviceRegistration</code>.
+ *
+ * @param serviceRegistration The service to unregister
+ */
+ protected final void unregisterService(ServiceRegistration serviceRegistration) {
+ serviceRegistration.unregister();
+ }
+
+ /**
+ * Disposes off the given <code>repository</code>.
+ *
+ * @param repository The repository to be disposed off which is the same as
+ * the one returned from {@link #acquireRepository()}.
+ */
+ protected abstract void disposeRepository(Repository repository);
+
+ // --------- SCR integration -----------------------------------------------
+
+ /**
+ * This method actually starts the backing repository instannce and
+ * registeres the repository service.
+ * <p>
+ * Multiple subsequent calls to this method without calling {@link #stop()}
+ * first have no effect.
+ *
+ * @param bundleContext The {@code BundleContext} to register the repository
+ * service (and optionally more services required to operate the
+ * repository)
+ * @param defaultWorkspace The name of the default workspace to use to
+ * login. This may be {@code null} to have the actual repository
+ * instance define its own default
+ * @param disableLoginAdministrative Whether to disable the
+ * {@code SlingRepository.loginAdministrative} method or not.
+ * @return {@code true} if the repository has been started and the service
+ * is registered.
+ */
+ protected final boolean start(final BundleContext bundleContext, final String defaultWorkspace,
+ final boolean disableLoginAdministrative) {
+
+ // already setup ?
+ if (this.bundleContext != null) {
+ log.debug("start: Repository already started and registered");
+ return true;
+ }
+
+ this.bundleContext = bundleContext;
+ this.defaultWorkspace = defaultWorkspace;
+ this.disableLoginAdministrative = disableLoginAdministrative;
+
+ try {
+ log.debug("start: calling acquireRepository()");
+ Repository newRepo = this.acquireRepository();
+ if (newRepo != null) {
+
+ // ensure we really have the repository
+ log.debug("start: got a Repository");
+ this.repository = newRepo;
+ this.masterSlingRepository = this.create(this.bundleContext.getBundle());
+
+ log.debug("start: setting up NamespaceMapping support");
+ this.setup(this.bundleContext, this.masterSlingRepository);
+
+ log.debug("start: calling registerService()");
+ this.repositoryService = registerService();
+
+ log.debug("start: registerService() successful, registration=" + repositoryService);
+ return true;
+ }
+ } catch (Throwable t) {
+ // consider an uncaught problem an error
+ log.error("start: Uncaught Throwable trying to access Repository, calling stopRepository()", t);
+
+ // repository might be partially started, stop anything left
+ stop();
+ }
+
+ // fallback to failure to start the repository
+ return false;
+ }
+
+ /**
+ * This method must be called if overwritten by implementations !!
+ */
+ protected final void stop() {
+ // ensure the repository is really disposed off
+ if (repository != null || repositoryService != null) {
+ log.info("stop: Repository still running, forcing shutdown");
+
+ try {
+ if (repositoryService != null) {
+ try {
+ log.debug("stop: Unregistering SlingRepository service, registration=" + repositoryService);
+ unregisterService(repositoryService);
+ } catch (Throwable t) {
+ log.info("stop: Uncaught problem unregistering the repository service", t);
+ }
+ repositoryService = null;
+ }
+
+ if (repository != null) {
+ Repository oldRepo = repository;
+ repository = null;
+
+ // stop namespace support
+ this.tearDown();
+ this.destroy(this.masterSlingRepository);
+
+ try {
+ disposeRepository(oldRepo);
+ } catch (Throwable t) {
+ log.info("stop: Uncaught problem disposing the repository", t);
+ }
+ }
+ } catch (Throwable t) {
+ log.warn("stop: Unexpected problem stopping repository", t);
+ }
+ }
+
+ this.repositoryService = null;
+ this.repository = null;
+ this.defaultWorkspace = null;
+ this.bundleContext = null;
+ }
+}
diff --git a/src/main/java/org/apache/sling/jcr/base/NamespaceMappingSupport.java b/src/main/java/org/apache/sling/jcr/base/NamespaceMappingSupport.java
new file mode 100644
index 0000000..f649d2d
--- /dev/null
+++ b/src/main/java/org/apache/sling/jcr/base/NamespaceMappingSupport.java
@@ -0,0 +1,152 @@
+/*
+ * 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.sling.jcr.base;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.apache.sling.jcr.api.NamespaceMapper;
+import org.apache.sling.jcr.api.SlingRepository;
+import org.apache.sling.jcr.base.SessionProxyHandler.SessionProxyInvocationHandler;
+import org.apache.sling.jcr.base.internal.loader.Loader;
+import org.osgi.framework.BundleContext;
+
+import aQute.bnd.annotation.ProviderType;
+
+/**
+ * The <code>NamespaceMappingSupport</code> is an abstract base class for
+ * implementing support for dynamic namespace mapping in {@code SlingRepository}
+ * instances.
+ *
+ * @see AbstractSlingRepositoryManager
+ * @since API version 2.3 (bundle version 2.3)
+ */
+@ProviderType
+public abstract class NamespaceMappingSupport {
+
+ /** Namespace handler. */
+ private Loader namespaceHandler;
+
+ /** Session proxy handler. */
+ private SessionProxyHandler sessionProxyHandler;
+
+ /**
+ * Returns the {@code NamespaceMapper} services used by the
+ * {@link #getNamespaceAwareSession(Session)} method to define custom
+ * namespaces on sessions.
+ *
+ * @return the {@code NamespaceMapper} services or {@code null} if there are
+ * none.
+ */
+ protected abstract NamespaceMapper[] getNamespaceMapperServices();
+
+ private SessionProxyHandler getSessionProxyHandler() {
+ return this.sessionProxyHandler;
+ }
+
+ private Loader getLoader() {
+ return this.namespaceHandler;
+ }
+
+ /**
+ * Sets up the namespace mapping support. This method is called by
+ * implementations of this class after having started (or acquired) the
+ * backing JCR repository instance.
+ * <p>
+ * This method may be overwritten but must be called from overwriting
+ * methods.
+ *
+ * @param bundleContext The OSGi {@code BundleContext} to access the
+ * framework for namespacing setup
+ * @param repository The {@code SlingRepository} to register namespaces on
+ */
+ protected void setup(final BundleContext bundleContext, final SlingRepository repository) {
+ this.sessionProxyHandler = new SessionProxyHandler(this);
+ this.namespaceHandler = new Loader(repository, bundleContext);
+ }
+
+ /**
+ * Terminates namespace mapping support. This method is called by the
+ * implementations of this class before stopping (or letting go of) the
+ * backing JCR repository instance.
+ * <p>
+ * This method may be overwritten but must be called from overwriting
+ * methods.
+ */
+ protected void tearDown() {
+ if (this.namespaceHandler != null) {
+ this.namespaceHandler.dispose();
+ this.namespaceHandler = null;
+ }
+ this.sessionProxyHandler = null;
+ }
+
+ /**
+ * Helper method to dynamically define namespaces on the given session
+ * <p>
+ * This method is package private to allow to be accessed from the
+ * {@link SessionProxyInvocationHandler#invoke(Object, java.lang.reflect.Method, Object[])}
+ * method.
+ *
+ * @param session The JCR {@code Session} to define name spaces on
+ * @throws RepositoryException if an error occurrs defining the name spaces
+ */
+ final void defineNamespacePrefixes(final Session session) throws RepositoryException {
+ final Loader localHandler = this.getLoader();
+ if (localHandler != null) {
+ // apply namespace mapping
+ localHandler.defineNamespacePrefixes(session);
+ }
+
+ // call namespace mappers
+ final NamespaceMapper[] nsMappers = getNamespaceMapperServices();
+ if (nsMappers != null) {
+ for (int i = 0; i < nsMappers.length; i++) {
+ nsMappers[i].defineNamespacePrefixes(session);
+ }
+ }
+ }
+
+ /**
+ * Return a namespace aware session.
+ * <p>
+ * This method must be called for each JCR {@code Session} to be returned
+ * from any of the repository {@code login} methods which are expected to
+ * support dynamically mapped namespaces.
+ *
+ * @param session The session convert into a namespace aware session
+ * @return The namespace aware session
+ * @throws RepositoryException If an error occurrs making the session
+ * namespace aware
+ */
+ protected final Session getNamespaceAwareSession(final Session session) throws RepositoryException {
+ if (session == null) { // sanity check
+ return null;
+ }
+ defineNamespacePrefixes(session);
+
+ // to support namespace prefixes if session.impersonate is called
+ // we have to use a proxy
+ final SessionProxyHandler localHandler = this.getSessionProxyHandler();
+ if (localHandler != null) {
+ return localHandler.createProxy(session);
+ }
+ return session;
+ }
+}
diff --git a/src/main/java/org/apache/sling/jcr/base/SessionProxyHandler.java b/src/main/java/org/apache/sling/jcr/base/SessionProxyHandler.java
index 8e66675..294ae90 100644
--- a/src/main/java/org/apache/sling/jcr/base/SessionProxyHandler.java
+++ b/src/main/java/org/apache/sling/jcr/base/SessionProxyHandler.java
@@ -32,21 +32,25 @@
* The session proxy handler creates session proxies to handle
* the namespace mapping support if impersonate is called on
* the session.
+ * <p>
+ * This class is not really part of the API is not intended to be used
+ * directly by consumers or implementors of this API. It is used internally
+ * to support namespace mapping.
*/
public class SessionProxyHandler {
/** The array of proxied interfaces. */
private Class<?>[] interfaces;
- /** The repository */
- private final AbstractNamespaceMappingRepository repository;
+ /** The namespaceSupport */
+ private final NamespaceMappingSupport namespaceSupport;
- public SessionProxyHandler(final AbstractNamespaceMappingRepository repo) {
- this.repository = repo;
+ public SessionProxyHandler(final NamespaceMappingSupport namespaceSupport) {
+ this.namespaceSupport = namespaceSupport;
}
/** Calculate the interfaces.
- * This is done only once - we simply assume that the same repository is
+ * This is done only once - we simply assume that the same namespaceSupport is
* emitting session from the same class.
*/
private Class<?>[] getInterfaces(final Class<?> sessionClass) {
@@ -74,21 +78,21 @@
final Class<?>[] interfaces = getInterfaces(sessionClass);
return (Session)Proxy.newProxyInstance(sessionClass.getClassLoader(),
interfaces,
- new SessionProxyInvocationHandler(session, this.repository, interfaces));
+ new SessionProxyInvocationHandler(session, this.namespaceSupport, interfaces));
}
public static final class SessionProxyInvocationHandler implements InvocationHandler {
private final Session delegatee;
- private final AbstractNamespaceMappingRepository repository;
+ private final NamespaceMappingSupport namespaceSupport;
private final Class<?>[] interfaces;
public SessionProxyInvocationHandler(final Session delegatee,
- final AbstractNamespaceMappingRepository repo,
+ final NamespaceMappingSupport namespaceSupport,
final Class<?>[] interfaces) {
this.delegatee = delegatee;
- this.repository = repo;
+ this.namespaceSupport = namespaceSupport;
this.interfaces = interfaces;
}
@@ -99,11 +103,11 @@
throws Throwable {
if ( method.getName().equals("impersonate") && args != null && args.length == 1) {
final Session session = this.delegatee.impersonate((Credentials)args[0]);
- this.repository.defineNamespacePrefixes(session);
+ this.namespaceSupport.defineNamespacePrefixes(session);
final Class<?> sessionClass = session.getClass();
return Proxy.newProxyInstance(sessionClass.getClassLoader(),
interfaces,
- new SessionProxyInvocationHandler(session, this.repository, interfaces));
+ new SessionProxyInvocationHandler(session, this.namespaceSupport, interfaces));
}
try {
return method.invoke(this.delegatee, args);
diff --git a/src/main/java/org/apache/sling/jcr/base/package-info.java b/src/main/java/org/apache/sling/jcr/base/package-info.java
index 9eb29d3..f2dfcd7 100644
--- a/src/main/java/org/apache/sling/jcr/base/package-info.java
+++ b/src/main/java/org/apache/sling/jcr/base/package-info.java
@@ -17,7 +17,23 @@
* under the License.
*/
-@Version("2.2")
+/**
+ * The {@code org.apache.sling.jcr.base} package provides basic support
+ * to expose JCR repositories in Sling. The primary classes to implement are
+ * {@code org.apache.sling.jcr.base.AbstractSlingRepositoryManager} to
+ * manage the actual JCR repository instance and
+ * {@link org.apache.sling.jcr.base.AbstractSlingRepository2} being the
+ * basis for the repository service instance handed to using bundles.
+ * <p>
+ * The old {@link org.apache.sling.jcr.base.AbstractSlingRepository}
+ * class is being deprecated in favor of the new method of providing access
+ * to JCR repositories. Likewise the
+ * {@link org.apache.sling.jcr.base.AbstractNamespaceMappingRepository} is
+ * deprecated in favor of the new
+ * {@link org.apache.sling.jcr.base.NamespaceMappingSupport} abstract class
+ * and said repository manager.
+ */
+@Version("2.3")
package org.apache.sling.jcr.base;
import aQute.bnd.annotation.Version;