blob: d150b048c97ba1c947964622044890a1a0072271 [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.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 context 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);
}
}
}