SLING-2624 : Improving Jackrabbit integration with OSGi Service Registry. Apply patch...
authorCarsten Ziegeler <cziegeler@apache.org>
Tue, 23 Oct 2012 09:40:15 +0000 (09:40 +0000)
committerCarsten Ziegeler <cziegeler@apache.org>
Tue, 23 Oct 2012 09:40:15 +0000 (09:40 +0000)
git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1401220 13f79535-47bb-0310-9956-ffa450edef68

pom.xml [new file with mode: 0644]
src/main/java/org/apache/sling/jcr/jackrabbit/base/config/OsgiBeanFactory.java [new file with mode: 0644]
src/main/java/org/apache/sling/jcr/jackrabbit/base/security/DelegatingLoginModule.java [new file with mode: 0644]
src/main/java/org/apache/sling/jcr/jackrabbit/base/security/DelegatingPrincipalProviderRegistry.java [new file with mode: 0644]
src/main/java/org/apache/sling/jcr/jackrabbit/base/security/MultiplexingAuthorizableAction.java [new file with mode: 0644]
src/main/java/org/apache/sling/jcr/jackrabbit/base/security/PrincipalProviderTracker.java [new file with mode: 0644]
src/main/java/org/apache/sling/jcr/jackrabbit/base/security/package-info.java [new file with mode: 0644]

diff --git a/pom.xml b/pom.xml
new file mode 100644 (file)
index 0000000..f6dae28
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,109 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.apache.sling</groupId>
+        <artifactId>sling</artifactId>
+        <version>13</version>
+        <relativePath>../../../parent/pom.xml</relativePath>
+    </parent>
+
+    <artifactId>org.apache.sling.jcr.jackrabbit.base</artifactId>
+    <packaging>bundle</packaging>
+    <version>0.0.1-SNAPSHOT</version>
+
+    <name>Apache Sling JCR Jackrabbit Base</name>
+    <description>
+               The JCR base bundle provides Jackrabbit utility classes
+    </description>
+
+    <scm>
+        <connection>scm:svn:http://svn.apache.org/repos/asf/sling/trunk/bundles/jcr/jackrabbit-base</connection>
+        <developerConnection>scm:svn:https://svn.apache.org/repos/asf/sling/trunk/bundles/jcr/jackrabbit-base</developerConnection>
+        <url>http://svn.apache.org/viewvc/sling/trunk/bundles/jcr/jackrabbit-base</url>
+    </scm>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <extensions>true</extensions>
+            </plugin>
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>animal-sniffer-maven-plugin</artifactId>
+                <configuration>
+                    <!-- Skip the check for JDK 5 API -->
+                    <skip>true</skip>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>javax.jcr</groupId>
+            <artifactId>jcr</artifactId>
+            <version>2.0</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.jackrabbit</groupId>
+            <artifactId>jackrabbit-core</artifactId>
+            <version>2.5.2</version>
+            <scope>provided</scope>
+        </dependency>
+        <!-- OSGi Libraries -->
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.core</artifactId>
+            <version>4.2.0</version>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.compendium</artifactId>
+            <version>4.2.0</version>
+        </dependency>
+        <dependency>
+            <groupId>biz.aQute</groupId>
+            <artifactId>bndlib</artifactId>
+            <version>1.50.0</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-simple</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+</project>
diff --git a/src/main/java/org/apache/sling/jcr/jackrabbit/base/config/OsgiBeanFactory.java b/src/main/java/org/apache/sling/jcr/jackrabbit/base/config/OsgiBeanFactory.java
new file mode 100644 (file)
index 0000000..f55cd45
--- /dev/null
@@ -0,0 +1,264 @@
+/*
+ * 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.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(InputSource configSource) throws IOException, 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(InputSource source) throws ConfigurationException, IOException {
+        Properties p = new Properties();
+        p.putAll(System.getProperties());
+        p.setProperty(RepositoryConfigurationParser.REPOSITORY_HOME_VARIABLE, "/fake/path");
+        RepositoryConfigurationParser parser = new RepositoryConfigurationParser(p);
+        parser.setConfigVisitor(new DepFinderBeanConfigVisitor());
+
+        try {
+            parser.parseRepositoryConfig(source);
+        } finally {
+            InputStream is = source.getByteStream();
+            if (is != null) {
+                is.close();
+            }
+        }
+
+        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);
+            }
+        }
+    }
+}
diff --git a/src/main/java/org/apache/sling/jcr/jackrabbit/base/security/DelegatingLoginModule.java b/src/main/java/org/apache/sling/jcr/jackrabbit/base/security/DelegatingLoginModule.java
new file mode 100644 (file)
index 0000000..dfa7754
--- /dev/null
@@ -0,0 +1,156 @@
+/*
+ * 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.security;
+
+
+import org.apache.jackrabbit.core.config.BeanConfig;
+import org.apache.jackrabbit.core.config.ConfigurationException;
+import org.apache.jackrabbit.core.config.LoginModuleConfig;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.security.auth.Subject;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.login.Configuration;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+import javax.security.auth.spi.LoginModule;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.util.Map;
+import java.util.Properties;
+
+public class DelegatingLoginModule implements LoginModule {
+    private static Logger logger = LoggerFactory.getLogger(DelegatingLoginModule.class);
+    public static final String JAAS_CONFIG_ALGO_NAME = "JavaLoginConfig";
+    private LoginModule delegate;
+    private LoginContext loginContext;
+    private boolean loginSucceeded;
+
+    private String appName;
+    private String delegateLoginModuleClass;
+    private String providerName;
+
+    private LoginException loginException;
+
+
+    public void initialize(Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState,
+                           Map<String, ?> options) {
+        Configuration config = null;
+        try{
+            config = Configuration.getInstance(JAAS_CONFIG_ALGO_NAME, null, providerName);
+        }catch (NoSuchProviderException e){
+            logger.debug("No provider "+providerName+"found so far",e);
+        } catch (NoSuchAlgorithmException e) {
+            logger.debug("No provider "+providerName+"found so far for fetching JAAS " +
+                    "config with algorithm name "+JAAS_CONFIG_ALGO_NAME,e);
+        }
+
+        if(config != null){
+            final Thread current = Thread.currentThread();
+            final ClassLoader orig = current.getContextClassLoader();
+            try {
+                current.setContextClassLoader(DelegatingLoginModule.class.getClassLoader());
+                loginContext = new LoginContext(appName, subject,callbackHandler, config);
+            } catch (LoginException e) {
+                loginException = e;
+            } finally{
+                current.setContextClassLoader(orig);
+            }
+        }else{
+            //No support so far from OSGi so would use default logic used by Jackrabbit
+            //to construct the LoginModule
+            Properties p = new Properties();
+            p.putAll(options);
+            BeanConfig bc = new BeanConfig(delegateLoginModuleClass,p);
+            LoginModuleConfig lmc = new LoginModuleConfig(bc);
+            try {
+                delegate = lmc.getLoginModule();
+                delegate.initialize(subject,callbackHandler,sharedState,options);
+                logger.info("No JAAS Configuration provider found would be directly invoking LoginModule {}",delegateLoginModuleClass);
+            } catch (ConfigurationException e) {
+                //Behaviour is same as org.apache.jackrabbit.core.security.authentication.LocalAuthContext.login()
+                loginException = new LoginException(e.getMessage());
+            }
+        }
+    }
+
+    public boolean login() throws LoginException {
+        assertState();
+
+        if (loginContext == null) {
+            return delegate.login();
+        } else {
+            loginContext.login();
+            loginSucceeded = true;
+            return true;
+        }
+    }
+
+    public boolean commit() throws LoginException {
+        assertState();
+
+        if (loginContext == null) {
+            return delegate.commit();
+        } else {
+            return loginSucceeded;
+        }
+    }
+
+    public boolean abort() throws LoginException {
+        assertState();
+
+        if (loginContext == null) {
+            return delegate.abort();
+        } else {
+            return loginSucceeded;
+        }
+    }
+
+    public boolean logout() throws LoginException {
+        assertState();
+
+        if (loginContext == null) {
+            return delegate.logout();
+        } else {
+            loginContext.logout();
+            return true;
+        }
+    }
+
+    private void assertState() throws LoginException {
+        if(loginException != null){
+            throw loginException;
+        }
+    }
+
+    public void setAppName(String appName) {
+        this.appName = appName;
+    }
+
+    public void setDelegateLoginModuleClass(String delegateLoginModuleClass) {
+        this.delegateLoginModuleClass = delegateLoginModuleClass;
+    }
+
+    public void setProviderName(String providerName) {
+        this.providerName = providerName;
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/jcr/jackrabbit/base/security/DelegatingPrincipalProviderRegistry.java b/src/main/java/org/apache/sling/jcr/jackrabbit/base/security/DelegatingPrincipalProviderRegistry.java
new file mode 100644 (file)
index 0000000..19abd52
--- /dev/null
@@ -0,0 +1,70 @@
+/*
+ * 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.security;
+
+import java.util.Properties;
+
+import javax.jcr.RepositoryException;
+
+import org.apache.jackrabbit.core.security.principal.PrincipalProvider;
+import org.apache.jackrabbit.core.security.principal.PrincipalProviderRegistry;
+
+public class DelegatingPrincipalProviderRegistry implements PrincipalProviderRegistry {
+    private final PrincipalProviderRegistry defaultRegistry;
+    private final PrincipalProviderRegistry osgiRegistry;
+
+    public DelegatingPrincipalProviderRegistry(PrincipalProviderRegistry defaultRegistry,
+                                                PrincipalProviderRegistry osgiRegistry) {
+        this.defaultRegistry = defaultRegistry;
+        this.osgiRegistry = osgiRegistry;
+    }
+
+    public PrincipalProvider registerProvider(Properties properties) throws RepositoryException {
+        return defaultRegistry.registerProvider(properties);
+    }
+
+    public PrincipalProvider getDefault() {
+        return defaultRegistry.getDefault();
+    }
+
+    public PrincipalProvider getProvider(String name) {
+        PrincipalProvider p = defaultRegistry.getProvider(name);
+        if (p == null) {
+            p = osgiRegistry.getProvider(name);
+        }
+        return p;
+    }
+
+    public PrincipalProvider[] getProviders() {
+        PrincipalProvider[] defaultProviders = defaultRegistry.getProviders();
+        PrincipalProvider[] extraProviders = osgiRegistry.getProviders();
+
+        //Quick check
+        if(extraProviders.length == 0){
+            return defaultProviders;
+        }
+
+        PrincipalProvider[] mergedResult = new PrincipalProvider[defaultProviders.length + extraProviders.length];
+        System.arraycopy(defaultProviders, 0, mergedResult, 0, defaultProviders.length);
+        System.arraycopy(extraProviders, 0, mergedResult, defaultProviders.length, extraProviders.length);
+        return mergedResult;
+    }
+
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/jcr/jackrabbit/base/security/MultiplexingAuthorizableAction.java b/src/main/java/org/apache/sling/jcr/jackrabbit/base/security/MultiplexingAuthorizableAction.java
new file mode 100644 (file)
index 0000000..5a2fd3d
--- /dev/null
@@ -0,0 +1,144 @@
+/*
+ * 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.security;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Properties;
+import java.util.concurrent.ConcurrentSkipListMap;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.apache.jackrabbit.api.security.user.Authorizable;
+import org.apache.jackrabbit.api.security.user.Group;
+import org.apache.jackrabbit.api.security.user.User;
+import org.apache.jackrabbit.core.security.user.action.AbstractAuthorizableAction;
+import org.apache.jackrabbit.core.security.user.action.AuthorizableAction;
+import org.osgi.framework.BundleContext;
+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;
+
+
+
+public class MultiplexingAuthorizableAction extends AbstractAuthorizableAction implements ServiceTrackerCustomizer{
+    private Logger log = LoggerFactory.getLogger(getClass());
+
+    private Map<Comparable,AuthorizableAction> actionMap =
+            new ConcurrentSkipListMap<Comparable, AuthorizableAction>(Collections.reverseOrder());
+    private final ServiceTracker tracker;
+    private final BundleContext context;
+    private final ServiceRegistration reg;
+
+    public MultiplexingAuthorizableAction(BundleContext context){
+        this.context = context;
+        this.tracker = new ServiceTracker(context, createFilter(context),this);
+        this.tracker.open();
+
+        Properties p = new Properties();
+        p.setProperty("jackrabbit.extension","true");
+        reg = context.registerService(AuthorizableAction.class.getName(),this,p);
+    }
+
+    //~----------------------------------------<AuthorizableAction>
+
+    public void onCreate(User user, String password, Session session) throws RepositoryException {
+        log.debug("Created user {}", user.getID());
+        for(AuthorizableAction a : getActions()){
+            a.onCreate(user,password,session);
+        }
+    }
+
+    @Override
+    public void onCreate(Group group, Session session) throws RepositoryException {
+        log.debug("Created group {}", group.getID());
+        for(AuthorizableAction a : getActions()){
+            a.onCreate(group,session);
+        }
+    }
+
+    @Override
+    public void onRemove(Authorizable authorizable, Session session) throws RepositoryException {
+        log.debug("Removed authorizable {}", authorizable.getID());
+        for(AuthorizableAction a : getActions()){
+            a.onRemove(authorizable,session);
+        }
+    }
+
+    @Override
+    public void onPasswordChange(User user, String newPassword, Session session) throws RepositoryException {
+        log.debug("Password changed for user {}", user.getID());
+        for(AuthorizableAction a : getActions()){
+            a.onPasswordChange(user,newPassword,session);
+        }
+    }
+
+    //~----------------------------------------<LifeCycle methods>
+
+    public void open(){
+        tracker.open();
+    }
+
+    public void close(){
+        if(reg != null){
+            reg.unregister();
+        }
+        tracker.close();
+        actionMap.clear();
+    }
+
+    //~----------------------------------------- < ServiceTrackerCustomizer >
+
+    public Object addingService(ServiceReference reference) {
+        AuthorizableAction action = (AuthorizableAction) context.getService(reference);
+        actionMap.put(reference,action);
+        return action;
+    }
+
+    public void modifiedService(ServiceReference reference, Object service) {
+        actionMap.put(reference, (AuthorizableAction) service);
+    }
+
+    public void removedService(ServiceReference reference, Object service) {
+        actionMap.remove(reference);
+    }
+
+    private Collection<AuthorizableAction> getActions() {
+        return actionMap.values();
+    }
+
+    private Filter createFilter(BundleContext context) {
+        try {
+            //Create a filter such that we track all service excluding ourselves
+            return context.createFilter("(&(!(jackrabbit.extension=true))" +
+                    "(objectClass=org.apache.jackrabbit.core.security.user.action.AuthorizableAction))");
+        } catch (InvalidSyntaxException e) {
+            //Should not happen as Filter is hardcoded and should work
+            throw new RuntimeException(e);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/sling/jcr/jackrabbit/base/security/PrincipalProviderTracker.java b/src/main/java/org/apache/sling/jcr/jackrabbit/base/security/PrincipalProviderTracker.java
new file mode 100644 (file)
index 0000000..3953da8
--- /dev/null
@@ -0,0 +1,153 @@
+/*
+ * 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.security;
+
+import java.util.Map;
+import java.util.Properties;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.jcr.RepositoryException;
+
+import org.apache.jackrabbit.core.security.principal.PrincipalProvider;
+import org.apache.jackrabbit.core.security.principal.PrincipalProviderRegistry;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.util.tracker.ServiceTracker;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class PrincipalProviderTracker extends ServiceTracker implements PrincipalProviderRegistry{
+    /**
+     * Property-Key if the <code>PrincipalProvider</code> configured with
+     * {@link LoginModuleConfig#PARAM_PRINCIPAL_PROVIDER_CLASS} be registered using the
+     * specified name instead of the class name which is used by default if the
+     * name parameter is missing.
+     *
+     * @see <a href="https://issues.apache.org/jira/browse/JCR-2629">JCR-2629</a>
+     */
+    private static final String COMPAT_PRINCIPAL_PROVIDER_NAME = "principal_provider.name";
+    public static final PrincipalProvider[] EMPTY_ARRAY = new PrincipalProvider[0];
+
+    private Logger log = LoggerFactory.getLogger(getClass());
+
+    private final Map<String, PrincipalProvider> providers = new ConcurrentHashMap<String, PrincipalProvider>();
+    private final Map<ServiceReference, String> refToNameMapping = new ConcurrentHashMap<ServiceReference, String>();
+    private PrincipalProvider[] providerArray = EMPTY_ARRAY;
+
+    public PrincipalProviderTracker(BundleContext context) {
+        super(context, PrincipalProvider.class.getName(), null);
+    }
+
+    //~-------------------------------------< ServiceTracker >
+
+    @Override
+    public Object addingService(ServiceReference reference) {
+        PrincipalProvider provider = (PrincipalProvider) super.addingService(reference);
+        addProvider(provider,reference);
+        reloadProviders();
+        return provider;
+    }
+
+    @Override
+    public void modifiedService(ServiceReference reference, Object service) {
+        //Check if the name has changed then re-register the provider
+        String newName = getProviderName((PrincipalProvider) service,reference);
+        String oldName = refToNameMapping.get(reference);
+        if(!equals(newName,oldName)){
+            if(oldName != null){
+                providers.remove(oldName);
+            }
+            addProvider((PrincipalProvider) service, reference);
+            reloadProviders();
+        }
+    }
+
+    @Override
+    public void removedService(ServiceReference reference, Object service) {
+        String name = refToNameMapping.remove(reference);
+        if(name != null){
+            providers.remove(name);
+        }
+        reloadProviders();
+    }
+
+    @Override
+    public void close() {
+        super.close();
+        providers.clear();
+        refToNameMapping.clear();
+        providerArray = EMPTY_ARRAY;
+    }
+
+    //~-------------------------------------< PrincipalProviderRegistry >
+
+    public PrincipalProvider registerProvider(Properties configuration) throws RepositoryException {
+        throw new UnsupportedOperationException("The PrincipalProvider are only registered as OSGi services");
+    }
+
+    public PrincipalProvider getDefault() {
+        throw new UnsupportedOperationException("Default provider is handled via WorkspaceBasedPrincipalProviderRegistry");
+    }
+
+    public PrincipalProvider getProvider(String className) {
+        return providers.get(className);
+    }
+
+    public PrincipalProvider[] getProviders() {
+        return providerArray;
+    }
+
+    private void addProvider(PrincipalProvider provider,ServiceReference reference){
+        String providerName = getProviderName(provider,reference);
+        if(providers.containsKey(providerName)){
+            log.warn("Provider with name {} is already registered. PrincipalProvider {} " +
+                    "would not be registered",providerName,reference);
+            return;
+        }
+        providers.put(providerName,provider);
+        refToNameMapping.put(reference,providerName);
+    }
+
+    private void reloadProviders() {
+        PrincipalProvider[] providerArray = providers.values().toArray(new PrincipalProvider[providers.size()]);
+        synchronized (this){
+            this.providerArray = providerArray;
+        }
+    }
+
+    private static String getProviderName(PrincipalProvider provider,ServiceReference ref){
+        String providerName = (String) ref.getProperty(COMPAT_PRINCIPAL_PROVIDER_NAME);
+        if(providerName == null){
+            providerName = provider.getClass().getName();
+        }
+        return providerName;
+    }
+
+    private static boolean equals(Object object1, Object object2) {
+        if (object1 == object2) {
+            return true;
+        }
+        if ((object1 == null) || (object2 == null)) {
+            return false;
+        }
+        return object1.equals(object2);
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/jcr/jackrabbit/base/security/package-info.java b/src/main/java/org/apache/sling/jcr/jackrabbit/base/security/package-info.java
new file mode 100644 (file)
index 0000000..36bb8cb
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+
+/**
+ * Provides support for performing JAAS based authentication in OSGi
+ *
+ * @version 1.0
+ */
+@Version("1.0")
+@Export(optional = "provide:=true")
+package org.apache.sling.jcr.jackrabbit.base.security;
+
+import aQute.bnd.annotation.Export;
+import aQute.bnd.annotation.Version;
\ No newline at end of file