blob: 484bf927e5ac54cdbf5b97752ffdc8fb99428f82 [file] [log] [blame]
/*
* 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.Arrays;
import java.util.Dictionary;
import javax.jcr.Repository;
import org.apache.sling.jcr.api.SlingRepository;
import org.apache.sling.jcr.api.SlingRepositoryInitializer;
import org.apache.sling.jcr.base.internal.loader.Loader;
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.ServiceReference;
import org.osgi.framework.ServiceRegistration;
import org.osgi.util.tracker.ServiceTracker;
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 implementation 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 #registerService()}</li>
* </ol>
* Earlier versions of this class had an additional <code>setup</code> method,
* whatever code was there can be moved to the <core>create</code> method.
* <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 #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.2.2)
*/
@ProviderType
public abstract class AbstractSlingRepositoryManager {
/** default log */
private final Logger log = LoggerFactory.getLogger(getClass());
private volatile BundleContext bundleContext;
private volatile Repository repository;
// the SlingRepository instance used to setup basic stuff
// see setup and tearDown
private volatile AbstractSlingRepository2 masterSlingRepository;
private volatile ServiceRegistration repositoryService;
private volatile String defaultWorkspace;
private volatile boolean disableLoginAdministrative;
private volatile ServiceTracker repoInitializerTracker;
private volatile Loader loader;
/**
* 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() {
@Override
public Object getService(Bundle bundle, ServiceRegistration registration) {
return AbstractSlingRepositoryManager.this.create(bundle);
}
@Override
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;
this.repoInitializerTracker = new ServiceTracker(bundleContext, SlingRepositoryInitializer.class.getName(), null);
this.repoInitializerTracker.open();
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 Loader");
this.loader = new Loader(this.masterSlingRepository, this.bundleContext);
log.debug("start: calling SlingRepositoryInitializer");
Throwable t = null;
try {
executeRepositoryInitializers(this.masterSlingRepository);
} catch(Exception e) {
t = e;
} catch(Error e) {
t = e;
}
if(t != null) {
log.error("Exception in a SlingRepositoryInitializer, SlingRepository service registration aborted", t);
return false;
}
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;
}
private void executeRepositoryInitializers(SlingRepository repo) throws Exception {
final ServiceReference [] refs = repoInitializerTracker.getServiceReferences();
if(refs == null || refs.length == 0) {
log.debug("No SlingRepositoryInitializer services found");
return;
}
Arrays.sort(refs);
for(ServiceReference ref : refs) {
final SlingRepositoryInitializer sri = (SlingRepositoryInitializer)bundleContext.getService(ref);
log.debug("Executing {}", sri);
try {
sri.processRepository(repo);
} finally {
bundleContext.ungetService(ref);
}
}
}
/**
* This method must be called if overwritten by implementations !!
*/
protected final void stop() {
if(repoInitializerTracker != null) {
repoInitializerTracker.close();
repoInitializerTracker = null;
}
// 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 loader
if ( this.loader != null ) {
this.loader.dispose();
this.loader = null;
}
// destroy repository
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;
}
}