blob: f04002edcdafaa70d70683107e5fd8f73e22a62e [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.jackrabbit.base.config;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.jackrabbit.core.config.BeanConfig;
import org.apache.jackrabbit.core.config.BeanConfigVisitor;
import org.apache.jackrabbit.core.config.BeanFactory;
import org.apache.jackrabbit.core.config.ConfigurationException;
import org.apache.jackrabbit.core.config.RepositoryConfigurationParser;
import org.apache.jackrabbit.core.config.SimpleBeanFactory;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.Filter;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.ServiceRegistration;
import org.osgi.util.tracker.ServiceTracker;
import org.osgi.util.tracker.ServiceTrackerCustomizer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.InputSource;
public class OsgiBeanFactory implements BeanFactory, ServiceTrackerCustomizer {
private final Logger log = LoggerFactory.getLogger(getClass());
private final BeanFactory delegate = new SimpleBeanFactory();
private final BundleContext bundleContext;
/**
* Tracker to track all services which are possible Jackrabbit extensions
*/
private final ServiceTracker tracker;
/**
* Set of all interface class instances for which actual instances need to
* be lookedup from OSGi Service Registry
*/
private final Set<Class> dependencies = new HashSet<Class>();
/**
* Map of className to class instances
*/
private final Map<String, Class> classNameMapping = new HashMap<String, Class>();
/**
* Map of the interface name -> instance where the instance provides an implementation
* of the given interface
*/
private final Map<Class, Object> instanceMap = new ConcurrentHashMap<Class, Object>();
private ServiceRegistration beanFactoryReg;
public OsgiBeanFactory(BundleContext bundleContext) {
this.bundleContext = bundleContext;
Filter filter = null;
try {
filter = bundleContext.createFilter("(jackrabbit.extension=true)");
} catch (InvalidSyntaxException e) {
//Should not happen
throw new RuntimeException("Invalid filter", e);
}
this.tracker = new ServiceTracker(bundleContext, filter, this);
}
public void initialize(final InputSource configSource) throws ConfigurationException {
determineDependencies(configSource);
createClassNameMappings();
tracker.open();
checkState();
}
public void close() {
if (beanFactoryReg != null) {
beanFactoryReg.unregister();
beanFactoryReg = null;
}
tracker.close();
dependencies.clear();
instanceMap.clear();
classNameMapping.clear();
}
//-----------------------------------------------< BeanFactory >
public Object newInstance(Class<?> clazz, BeanConfig config) throws ConfigurationException {
Class targetClass = getClassFromConfig(config);
if (targetClass.isInterface()) {
Object o = instanceMap.get(targetClass);
if (o == null) {
throw new ConfigurationException("No instance registered for type " + targetClass.getName());
}
return o;
}
return delegate.newInstance(clazz, config);
}
//-----------------------------------------------< ServiceTrackerCustomizer >
public Object addingService(ServiceReference reference) {
Object instance = bundleContext.getService(reference);
Class[] depsProvided = determineProvidedDependencies(reference);
registerInstance(depsProvided, instance);
checkState();
return depsProvided;
}
public void modifiedService(ServiceReference serviceReference, Object o) {
}
public void removedService(ServiceReference reference, Object o) {
deregisterInstance((Class[]) o);
checkState();
bundleContext.ungetService(reference);
}
//------------------------- Callback methods
/**
* Callback method invoked when all services required by Jackrabbit are available. Implementing class
* can use this to manage repository lifecycle. Default implementation registers the BeanFactory
* instance which would be used by the Repository creator to create the repository
*/
protected void dependenciesSatisfied() {
//TODO Review the thread safety aspect
ServiceRegistration reg = beanFactoryReg;
if (reg == null) {
beanFactoryReg = bundleContext.registerService(BeanFactory.class.getName(), this, new Hashtable<>());
log.info("All dependencies are satisfied. Registering the BeanFactory instance");
}
}
/**
* Callback method invoked when any of the service required by Jackrabbit goes away. Implementing class
* can use this to manage repository lifecycle. Default implementation de-registers the BeanFactory
* instance. And repository creator service which then depends on BeanFactory reference would then be notified and
* can react accordingly
*/
protected void dependenciesUnSatisfied() {
ServiceRegistration reg = beanFactoryReg;
if (reg != null) {
reg.unregister();
beanFactoryReg = null;
log.info("Dependencies unsatisfied. Deregistering the BeanFactory instance");
}
}
private Class getClassFromConfig(BeanConfig config) {
String cname = config.getClassName();
try {
return config.getClassLoader().loadClass(cname);
} catch (ClassNotFoundException e) {
throw new RuntimeException("Could not load class for " + cname, e);
}
}
private void checkState() {
if (instanceMap.size() == dependencies.size()) {
dependenciesSatisfied();
} else {
dependenciesUnSatisfied();
}
}
private void determineDependencies(final InputSource source) throws ConfigurationException {
final Properties p = new Properties();
p.putAll(System.getProperties());
p.setProperty(RepositoryConfigurationParser.REPOSITORY_HOME_VARIABLE, "/fake/path");
final RepositoryConfigurationParser parser = new RepositoryConfigurationParser(p);
parser.setConfigVisitor(new DepFinderBeanConfigVisitor());
try {
parser.parseRepositoryConfig(source);
} finally {
// close source
final InputStream is = source.getByteStream();
if (is != null) {
try { is.close(); } catch (final IOException ignore) {}
} else {
final Reader r = source.getCharacterStream();
if ( r != null ) {
try { r.close(); } catch (final IOException ignore) {}
}
}
}
if (dependencies.isEmpty()) {
log.info("No dependencies configured. Repository would be created without any OSGi dependency getting injected");
return;
}
log.info("Following dependencies have been determined for the repository {}. Repository would be started " +
"once all these dependencies have been satisfied", dependencies);
}
private void registerInstance(Class[] depsProvided, Object o) {
for (Class c : depsProvided) {
instanceMap.put(c, o);
}
}
private void deregisterInstance(Class[] depsProvided) {
for (Class c : depsProvided) {
instanceMap.remove(c);
}
}
/**
* Determines all the dependencies which this ServiceReference can satisfy
*/
private Class[] determineProvidedDependencies(ServiceReference ref) {
//Use OBJECTCLASS property from SR as that determines under what classes
//a given service instance is published
//Class[] interfaces = o.getClass().getInterfaces();
String[] interfaces = (String[]) ref.getProperty(Constants.OBJECTCLASS);
List<Class> depsProvided = new ArrayList<Class>(interfaces.length);
for (String intf : interfaces) {
if (classNameMapping.containsKey(intf)) {
depsProvided.add(classNameMapping.get(intf));
}
}
return depsProvided.toArray(new Class[depsProvided.size()]);
}
private void createClassNameMappings() {
for (Class clazz : dependencies) {
classNameMapping.put(clazz.getName(), clazz);
}
}
private class DepFinderBeanConfigVisitor implements BeanConfigVisitor {
public void visit(BeanConfig config) {
Class clazz = getClassFromConfig(config);
if (clazz.isInterface()) {
dependencies.add(clazz);
}
}
}
}