/* * 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.registration; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import javax.jcr.Repository; import org.osgi.framework.Constants; import org.osgi.framework.ServiceReference; import org.osgi.service.component.ComponentConstants; import org.osgi.service.component.ComponentContext; import org.osgi.service.log.LogService; /** * The <code>AbstractRegistrationSupport</code> class is the base class for * registration purposes of embedded repositories. * <p> * This base class cares for synchronization issues of the * {@link #activate(ComponentContext)}, {@link #deactivate(ComponentContext)}, * {@link #bindRepository(ServiceReference)} and * {@link #unbindRepository(ServiceReference)} methods. Implementations of the * abstract API may safely assume to run thread-safe. * <p> * To ensure this thread-safeness, said methods should not be overwritten. */ public abstract class AbstractRegistrationSupport { /** * The JCR Repository service registration property used to create * the registration name. If this service registration property * (assumed to be a single string) does not exist, the repository is * not registered. */ public static final String REPOSITORY_REGISTRATION_NAME = "name"; /** * The LogService for logging. Extensions of this class must declare the log * service as a reference or call the {@link #bindLog(LogService)} to enable * logging correctly. */ private volatile LogService log; /** * The OSGi ComponentContext. */ private ComponentContext componentContext; /** * The (possibly empty) map of repositories which have been bound to the * registry before the registry has been activated. */ private final Map<String, ServiceReference> repositoryRegistrationBacklog = new HashMap<String, ServiceReference>(); /** * The map of repositories which have been bound to the registry component * and which are actually registered with the registry. */ private final Map<String, Object> registeredRepositories = new HashMap<String, Object>(); /** * A lock to serialize access to the registry management in this class. */ protected final Object registryLock = new Object(); // ---------- API to be implemented by extensions -------------------------- /** * Performs additional activation tasks. This method is called by the * {@link #activate(ComponentContext)} method and is intended for internal * setup, such as acquiring the registry. * * @return Whether the activation succeeded or not. If <code>true</code> * is returned, activation succeeded and any repositories which have * been bound before the component was activated are now actually * registered. If <code>false</code> is returned, activation * failed and this component is disabled and receives no further * repository bind and unbound events (apart for unbind events for * repositories which have already been bound). */ protected abstract boolean doActivate(); /** * Performs additional deactivation tasks. This method is called by the * {@link #deactivate(ComponentContext)} method and is intended for internal * cleanup of setup done by the {@link #doActivate()} method. * <p> * This method is always called, regardless of whether {@link #doActivate()} * succeeded or not. */ protected abstract void doDeactivate(); /** * Called to actually register a repository with the registry. This method * is called by {@link #activate(ComponentContext)} for any repositories * bound before the component was activated and by * {@link #bindRepository(ServiceReference)} for any repositories bound * after the component was activated. * <p> * If actual registration fails, this method is expected to return * <code>null</code> to indicate this fact. In this case, the * {@link #unbindRepository(String, Object)} will NOT be called for the * named repository. * <p> * This method may safely assume that it is only called on or after * activation of this component on or before the component deactivation. * * @param name The name under which the repository is to be registered. * @param repository The <code>javax.jcr.Repository</code> to register. * @return Returns an object which is later given as the <code>data</code> * parameter to the {@link #unbindRepository(String, Object)} method * to unregister the repository of the given name. This may be * <code>null</code> if actual registration failed. */ protected abstract Object bindRepository(String name, Repository repository); /** * Called to actually unregister a repository with the registry. This method * is called by {@link #unbindRepository(ServiceReference)} for any * repositories unbound before the component is deactivated and by * {@link #deactivate(ComponentContext)} for any repositories not unbound * before the component is deactivated. * <p> * If the {@link #bindRepository(String, Repository)} returned * <code>null</code> for when the named repository was registered, this * method is not called. * <p> * This method may safely assume that it is only called on or after * activation of this component on or before the component deactivation. * * @param name The name under which the repository is to be registered. * @param data The data object returned by the * {@link #bindRepositoryInternal(String, ServiceReference)} * method. */ protected abstract void unbindRepository(String name, Object data); // ---------- Implementation support methods ------------------------------- /** * Returns the OSGi <code>ComponentContext</code> of this component. This * method returns <code>null</code> before the {@link #doActivate()} * method is called and after the {@link #doDeactivate()} method has been * called. That is, this method does not return <code>null</code> if it is * fully operational. */ protected ComponentContext getComponentContext() { return this.componentContext; } /** * Logs a message with optional <code>Throwable</code> stack trace to the * log service or <code>stderr</code> if no log service is available. * * @param level The <code>LogService</code> level at which to log the * message. * @param message The message to log, this should of course not be * <code>null</code>. * @param t The <code>Throwable</code> to log along with the message. This * may be <code>null</code>. */ protected void log(int level, String message, Throwable t) { LogService log = this.log; if (log != null) { log.log(level, message, t); } else { System.err.print(level + " - " + message); if (t != null) { t.printStackTrace(System.err); } } } /** * Returns the <code>name</code> property from the service properties or * <code>null</code> if no such property exists or the property is an * empty string. * * @param reference The <code>ServiceReference</code> whose * <code>name</code> property is to be returned. * @return The non-empty name property or <code>null</code>. */ protected String getName(ServiceReference reference) { String name = (String) reference.getProperty(REPOSITORY_REGISTRATION_NAME); if (name == null || name.length() == 0) { log(LogService.LOG_DEBUG, "registerRepository: Repository not to be registered", null); return null; } return name; } // ---------- SCR intergration --------------------------------------------- /** * Activates this component thread-safely as follows: * <ol> * <li>Set the OSGi ComponentContext field * <li>Call {@link #doActivate()} * <li>Register repositores bound before activation calling * {@link #bindRepository(String, Repository)} for each such repository. * </ol> * <p> * If {@link #doActivate()} returns <code>false</code>, the repositories * already bound are not actually registered, but this component is * disabled. * * @param componentContext The OSGi <code>ComponentContext</code> of this * component. */ protected void activate(ComponentContext componentContext) { synchronized (this.registryLock) { this.componentContext = componentContext; if (this.doActivate()) { // register all repositories in the tmp map for (Iterator<Map.Entry<String, ServiceReference>> ri = this.repositoryRegistrationBacklog.entrySet().iterator(); ri.hasNext();) { Map.Entry<String, ServiceReference> entry = ri.next(); this.bindRepositoryInternal(entry.getKey(), entry.getValue()); ri.remove(); } } else { // disable this component String name = (String) componentContext.getProperties().get( ComponentConstants.COMPONENT_NAME); this.getComponentContext().disableComponent(name); } } } /** * Deactivates this component thread-safely as follows: * <ol> * <li>Unregister repositores still bound calling * {@link #unbindRepository(String, Object)} for each such repository. * <li>Call {@link #doDeactivate()} * <li>Clear the OSGi ComponentContext field * </ol> * * @param componentContext The OSGi <code>ComponentContext</code> of this * component. */ protected void deactivate(ComponentContext context) { synchronized (this.registryLock) { // unregister all repositories in the tmp map for (Iterator<Map.Entry<String, Object>> ri = this.registeredRepositories.entrySet().iterator(); ri.hasNext();) { Map.Entry<String, Object> entry = ri.next(); this.unbindRepository(entry.getKey(), entry.getValue()); ri.remove(); } this.doDeactivate(); this.componentContext = null; } } /** * Registers the repository identified by the OSGi service reference under * the name set as service property. If the repository service has not * name property, the repository is not registered. * * @param reference The <code>ServiceReference</code> representing the * repository to register. */ protected void bindRepository(ServiceReference reference) { String name = this.getName(reference); if (name != null) { synchronized (this.registryLock) { if (this.componentContext == null) { // no context to register with, delay ?? this.repositoryRegistrationBacklog.put(name, reference); } else { this.bindRepositoryInternal(name, reference); } } } else { this.log(LogService.LOG_INFO, "Service " + reference.getProperty(Constants.SERVICE_ID) + " has no name property, not registering", null); } } /** * Unregisters the repository identified by the OSGi service reference under * the name set as service property. If the repository service has no * name property, the repository is assumed not be registered and nothing * needs to be done. * * @param reference The <code>ServiceReference</code> representing the * repository to unregister. */ protected void unbindRepository(ServiceReference reference) { String name = this.getName(reference); if (name != null) { synchronized (this.registryLock) { // unbind the repository Object data = this.registeredRepositories.remove(name); if (data != null) { this.unbindRepository(name, data); } // ensure unregistered from internal and temporary map this.repositoryRegistrationBacklog.remove(name); // make sure we have no reference to the service if (this.componentContext != null) { this.componentContext.getBundleContext().ungetService(reference); } } } else { this.log(LogService.LOG_DEBUG, "Service " + reference.getProperty(Constants.SERVICE_ID) + " has no name property, nothing to unregister", null); } } /** Binds the LogService */ protected void bindLog(LogService log) { this.log = log; } /** Unbinds the LogService */ protected void unbindLog(LogService log) { if ( this.log == log ) { this.log = null; } } //---------- internal ----------------------------------------------------- /** * Internal bind method called by {@link #activate(ComponentContext)} and * {@link #bindRepository(ServiceReference)} to actually control the * registration process by retrieving the repository and calling the * {@link #bindRepository(String, Repository)} method. */ private void bindRepositoryInternal(String name, ServiceReference reference) { Repository repository = (Repository) this.getComponentContext().getBundleContext().getService( reference); Object data = this.bindRepository(name, repository); if (data != null) { this.registeredRepositories.put(name, data); } } }