SLING-2624 : Improving Jackrabbit integration with OSGi Service Registry. Apply patch from Chetan Mehrotra
git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1401220 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/pom.xml b/pom.xml
new file mode 100644
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
index 0000000..f55cd45
--- /dev/null
+++ b/src/main/java/org/apache/sling/jcr/jackrabbit/base/config/OsgiBeanFactory.java
@@ -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
index 0000000..dfa7754
--- /dev/null
+++ b/src/main/java/org/apache/sling/jcr/jackrabbit/base/security/DelegatingLoginModule.java
@@ -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
index 0000000..19abd52
--- /dev/null
+++ b/src/main/java/org/apache/sling/jcr/jackrabbit/base/security/DelegatingPrincipalProviderRegistry.java
@@ -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
index 0000000..5a2fd3d
--- /dev/null
+++ b/src/main/java/org/apache/sling/jcr/jackrabbit/base/security/MultiplexingAuthorizableAction.java
@@ -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
index 0000000..3953da8
--- /dev/null
+++ b/src/main/java/org/apache/sling/jcr/jackrabbit/base/security/PrincipalProviderTracker.java
@@ -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
index 0000000..36bb8cb
--- /dev/null
+++ b/src/main/java/org/apache/sling/jcr/jackrabbit/base/security/package-info.java
@@ -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