| /* |
| * 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.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 Properties()); |
| 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); |
| } |
| } |
| } |
| } |