SLING-2944 Implement Service User Mapper
* Add ServiceUserMapper service and implementation bundle
* Add service login methods to ResourceResolverFactory
and SlingRepository
* Add implementations of new methods
git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1510413 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/pom.xml b/pom.xml
index c013b0e..3d5eb85 100644
--- a/pom.xml
+++ b/pom.xml
@@ -57,13 +57,6 @@
<Bundle-Category>
sling,jcr,jackrabbit
</Bundle-Category>
- <Export-Package>
- org.apache.sling.jcr.base;
- org.apache.sling.jcr.base.util;version=2.1.0
- </Export-Package>
- <Private-Package>
- org.apache.sling.jcr.base.internal.*
- </Private-Package>
</instructions>
</configuration>
</plugin>
@@ -85,13 +78,9 @@
<dependencies>
<dependency>
- <groupId>org.apache.felix</groupId>
- <artifactId>org.apache.felix.scr.annotations</artifactId>
- </dependency>
- <dependency>
<groupId>org.apache.sling</groupId>
<artifactId>org.apache.sling.jcr.api</artifactId>
- <version>2.1.0</version>
+ <version>2.1.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
@@ -117,6 +106,13 @@
<version>2.0.0</version>
<scope>provided</scope>
</dependency>
+ <dependency>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>org.apache.sling.serviceusermapper</artifactId>
+ <version>0.0.1-SNAPSHOT</version>
+ <scope>provided</scope>
+ </dependency>
+
<!-- OSGi Libraries -->
<dependency>
<groupId>org.osgi</groupId>
@@ -129,6 +125,15 @@
</dependency>
<dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.scr.annotations</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>biz.aQute</groupId>
+ <artifactId>bndlib</artifactId>
+ </dependency>
+
+ <dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
diff --git a/src/main/java/org/apache/sling/jcr/base/AbstractSlingRepository.java b/src/main/java/org/apache/sling/jcr/base/AbstractSlingRepository.java
index ce6e68e..e890079 100644
--- a/src/main/java/org/apache/sling/jcr/base/AbstractSlingRepository.java
+++ b/src/main/java/org/apache/sling/jcr/base/AbstractSlingRepository.java
@@ -18,6 +18,10 @@
*/
package org.apache.sling.jcr.base;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
import java.util.Dictionary;
import javax.jcr.Credentials;
@@ -35,18 +39,46 @@
import org.apache.felix.scr.annotations.Reference;
import org.apache.sling.jcr.api.SlingRepository;
import org.apache.sling.jcr.base.util.RepositoryAccessor;
+import org.apache.sling.serviceusermapping.ServiceUserMapper;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.ServiceFactory;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.component.ComponentContext;
import org.osgi.service.log.LogService;
+import org.slf4j.LoggerFactory;
/**
- * The <code>AbstractSlingRepository</code> is an abstract implementation of
- * the {@link SlingRepository} interface which provides default support for
- * attached repositories as well as ensuring live repository connection,
- * reconnecting if needed. Implementations of the <code>SlingRepository</code>
- * interface may wish to extend this class to benefit from a default
- * implementation.
- *
+ * The <code>AbstractSlingRepository</code> is an abstract implementation of the
+ * {@link SlingRepository} interface which provides default support for attached
+ * repositories as well as ensuring live repository connection, reconnecting if
+ * needed. Implementations of the <code>SlingRepository</code> interface may
+ * wish to extend this class to benefit from a default implementation.
+ * <p>
+ * As of version 2.2 (bundle version 2.2.0) the registration of repository
+ * services based on this abstract base class works differently. To be able to
+ * know the calling bundle to implement the
+ * {@link #loginService(String, String)} method the service is registered as a
+ * service factory. Yet this component is registered as a non-service component
+ * with Declarative Services handling its registration itself so the the
+ * {@code ServiceFactory} cannot simply create instances of this class. The
+ * solution is for the service factory to create a proxy to the actual component
+ * object. All method calls are just routed through with the exception of the
+ * {@link #loginService(String, String)} method which is routed to a new
+ * internal method taking the calling bundle as an additional argument.
+ * <p>
+ * The changes to support this new registration mechanism are as follows:
+ * <ul>
+ * <li>The {@link #registerService()} method is now final.</li>
+ * <li>The {@link #getServiceRegistrationInterfaces()} and
+ * {@link #getServiceRegistrationProperties()} methods have been added and can
+ * be overwritten by implementations of this class. The
+ * {@link #registerService()} method calls these new methods to get the
+ * interfaces and properties for the service registration.</li>
+ * </ul>
+ * Implementations of this class overwriting the {@link #registerService()}
+ * method must replace this overwritten method with overwriting the new
+ * {@link #getServiceRegistrationInterfaces()} and/or
+ * {@link #getServiceRegistrationProperties()} methods.
*/
@Component(componentAbstract=true)
public abstract class AbstractSlingRepository
@@ -61,6 +93,10 @@
public static final String DEFAULT_ADMIN_PASS = "admin";
+ // For backwards compatibility loginAdministrative is still enabled
+ // In future releases, this default may change to false.
+ public static final boolean DEFAULT_LOGIN_ADMIN_ENABLED = true;
+
@Property
public static final String PROPERTY_DEFAULT_WORKSPACE = "defaultWorkspace";
@@ -76,6 +112,9 @@
@Property(value=DEFAULT_ADMIN_PASS)
public static final String PROPERTY_ADMIN_PASS = "admin.password";
+ @Property(boolValue = DEFAULT_LOGIN_ADMIN_ENABLED)
+ public static final String PROPERTY_LOGIN_ADMIN_ENABLED = "admin.login.enabled";
+
/**
* The default value for the number of seconds to wait between two
* consecutive checks while the repository is active (value is 10).
@@ -100,6 +139,9 @@
@Reference
private LogService log;
+ @Reference()
+ private ServiceUserMapper serviceUserMapper;
+
private ComponentContext componentContext;
private Repository repository;
@@ -116,6 +158,8 @@
private char[] adminPass;
+ private boolean disableLoginAdministrative;
+
// the poll interval used while the repository is not active
private long pollTimeInActiveSeconds;
@@ -165,10 +209,32 @@
return this.login(null, null);
}
- public Session loginAdministrative(String workspace)
- throws RepositoryException {
- Credentials sc = getAdministrativeCredentials(this.adminUser);
- return this.login(sc, workspace);
+ public final Session loginAdministrative(String workspace) throws RepositoryException {
+ if (this.disableLoginAdministrative) {
+ log(LogService.LOG_ERROR, "SlingRepository.loginAdministrative is disabled. Please use SlingRepository.loginService.");
+ throw new LoginException();
+ }
+
+ log(LogService.LOG_WARNING,
+ "SlingRepository.loginAdministrative is deprecated. Please use SlingRepository.loginService.");
+ return loginAdministrativeInternal(workspace);
+ }
+
+ /**
+ * This method always throws {@code LoginException} because it does
+ * not directly have the calling bundle at its disposition to decide
+ * on the required service name.
+ * <p>
+ * This method is final and cannot be overwritten by extensions. See the
+ * class comments for full details on how this works.
+ *
+ * @since 2.2 (bundle version 2.2.0)
+ */
+ public final Session loginService(String subServiceName, String workspace) throws LoginException,
+ RepositoryException {
+ log(LogService.LOG_ERROR,
+ "loginService: Cannot get using Bundle because this SlingRepository service is not a ServiceFactory");
+ throw new LoginException();
}
public Session login(Credentials credentials) throws LoginException,
@@ -236,6 +302,55 @@
}
/**
+ * Actual implementation of the {@link #loginService(String, String)} method
+ * taking into account the bundle calling this method.
+ * <p>
+ * This method is final and cannot be overwritten by extensions. See the
+ * class comments for full details on how this works.
+ *
+ * @param usingBundle The bundle requesting access
+ * @param subServiceName Subservice name (may be {@code null})
+ * @param workspace The workspace to access
+ * @return The session authenticated with the service user
+ * @throws LoginException If authentication fails or if no user is defined
+ * for the requesting service (bundle)
+ * @throws RepositoryException If a general error occurrs creating the
+ * session
+ *
+ * @since 2.2 (bundle version 2.2.0)
+ */
+ final Session loginService(final Bundle usingBundle, final String subServiceName, final String workspace)
+ throws LoginException, RepositoryException {
+ final String userName = this.serviceUserMapper.getServiceUserID(usingBundle, subServiceName);
+ final SimpleCredentials creds = new SimpleCredentials(userName, new char[0]);
+
+ Session admin = null;
+ try {
+ admin = this.loginAdministrativeInternal(workspace);
+ return admin.impersonate(creds);
+ } finally {
+ if (admin != null) {
+ admin.logout();
+ }
+ }
+ }
+
+ /**
+ * Actual (unprotected) implementation of administrative login.
+ * <p>
+ * This methods is internally used to administratively login.
+ *
+ * @param workspace The workspace to login to (or {@code null} to use the
+ * {@link #getDefaultWorkspace() default workspace}.
+ * @return The administrative session
+ * @throws RepositoryException if an error occurrs.
+ */
+ protected Session loginAdministrativeInternal(String workspace) throws RepositoryException {
+ Credentials sc = getAdministrativeCredentials(this.adminUser);
+ return this.login(sc, workspace);
+ }
+
+ /**
* @param anonUser the user name of the anon user.
* @return a Credentials implementation that represents the anon user.
*/
@@ -427,25 +542,69 @@
}
/**
- * Registers this component as an OSGi service with type
- * <code>javax.jcr.Repository</code> and
- * <code>org.apache.sling.jcr.api.SlingRepository</code> using the
- * component properties as service registration properties.
+ * Registers this component as an OSGi service with the types provided by
+ * the {@link #getServiceRegistrationInterfaces()} method and properties
+ * provided by the {@link #getServiceRegistrationProperties()} method.
* <p>
- * This method may be overwritten to register the component with different
- * types.
+ * As of version 2.2 (bundle version 2.2.0) this method is final and cannot
+ * be overwritten because the mechanism of service registration using a
+ * service factory is required to fully implement the
+ * {@link #loginService(String, String)} method. See the class comments for
+ * full details on how this works.
*
- * @return The OSGi <code>ServiceRegistration</code> object representing
- * the registered service.
+ * @return The OSGi <code>ServiceRegistration</code> object representing the
+ * registered service.
*/
- protected ServiceRegistration registerService() {
- @SuppressWarnings("unchecked")
- Dictionary<String, Object> props = componentContext.getProperties();
- String[] interfaces = new String[] { SlingRepository.class.getName(),
- Repository.class.getName() };
+ protected final ServiceRegistration registerService() {
+ final Dictionary<String, Object> props = getServiceRegistrationProperties();
+ final String[] interfaces = getServiceRegistrationInterfaces();
- return componentContext.getBundleContext().registerService(interfaces,
- this, props);
+ return componentContext.getBundleContext().registerService(interfaces, new ServiceFactory() {
+ public Object getService(Bundle bundle, ServiceRegistration registration) {
+ return SlingRepositoryProxyHandler.createProxy(interfaces, AbstractSlingRepository.this, bundle);
+ }
+
+ public void ungetService(Bundle bundle, ServiceRegistration registration, Object service) {
+ // nothing to do (GC does the work for us)
+ }
+ }, props);
+ }
+
+ /**
+ * Return the service registration properties to be used to register the
+ * repository service in {@link #registerService()}.
+ * <p>
+ * This method may be overwritten to return additional service registration
+ * properties. But it is strongly recommended to always include the
+ * properties returned from this method.
+ *
+ * @return The service registration properties to be used to register the
+ * repository service in {@link #registerService()}
+ *
+ * @since 2.2 (bundle version 2.2.0)
+ */
+ @SuppressWarnings("unchecked")
+ protected Dictionary<String, Object> getServiceRegistrationProperties() {
+ return componentContext.getProperties();
+ }
+
+ /**
+ * Returns the service types to be used to register the repository service
+ * in {@link #registerService()}. All interfaces returned must be accessible
+ * to the class loader of the class of this instance.
+ * <p>
+ * This method may be overwritten to return additional types but the types
+ * returned from this base implementation must always be included.
+ *
+ * @return The service types to be used to register the repository service
+ * in {@link #registerService()}
+ *
+ * @since 2.2 (bundle version 2.2.0)
+ */
+ protected String[] getServiceRegistrationInterfaces() {
+ return new String[] {
+ SlingRepository.class.getName(), Repository.class.getName()
+ };
}
/**
@@ -496,7 +655,7 @@
if(pingRepository(repository)) {
try {
- final Session s = loginAdministrative(getDefaultWorkspace());
+ final Session s = loginAdministrativeInternal(getDefaultWorkspace());
s.logout();
result = true;
} catch(RepositoryException re) {
@@ -572,6 +731,9 @@
this.adminPass = this.getProperty(properties, PROPERTY_ADMIN_PASS,
DEFAULT_ADMIN_PASS).toCharArray();
+ this.disableLoginAdministrative = !this.getProperty(properties, PROPERTY_LOGIN_ADMIN_ENABLED,
+ DEFAULT_LOGIN_ADMIN_ENABLED);
+
setPollTimeActive(getIntProperty(properties, PROPERTY_POLL_ACTIVE));
setPollTimeInActive(getIntProperty(properties, PROPERTY_POLL_INACTIVE));
@@ -652,6 +814,17 @@
return -1;
}
+ private boolean getProperty(Dictionary<String, Object> properties, String name, boolean defaultValue) {
+ Object prop = properties.get(name);
+ if (prop instanceof Boolean) {
+ return ((Boolean) prop).booleanValue();
+ } else if (prop instanceof String) {
+ return Boolean.valueOf((String) prop);
+ }
+
+ return defaultValue;
+ }
+
private boolean createWorkspace(String workspace) {
this.log(LogService.LOG_INFO, "createWorkspace: Requested workspace "
+ workspace + " does not exist, trying to create");
@@ -920,4 +1093,79 @@
}
}
+ /**
+ * The <code>SlingRepositoryProxyHandler</code> class implements a proxy for all
+ * service interfaces under which the {@link AbstractSlingRepository}
+ * implementation is registered.
+ * <p>
+ * All calls a directly handed through to the object except for the
+ * {@code loginService} call which is routed through
+ * {@code AbstractSlingRepository.loginService(Bundle, String, String)} method
+ * to influence logging in by the calling bundle.
+ *
+ * @since 2.2 (bundle version 2.2.0)
+ */
+ private static class SlingRepositoryProxyHandler implements InvocationHandler {
+
+ // The name of the method to re-route
+ private static final String LOGIN_SERVICE_NAME = "loginService";
+
+ // The delegatee object to which all calls are routed
+ private final AbstractSlingRepository delegatee;
+
+ // The bundle using this proxy service instance
+ private final Bundle usingBundle;
+
+ /**
+ * Creates a new proxy instance for the given {@code delegatee} object. The
+ * proxy is handled by a new instance of this
+ * {@code SlingRepositoryProxyHandler} handler.
+ *
+ * @param interfaceNames The list of interfaces to implement and expose in
+ * the proxy
+ * @param delegatee The object to which to route all method calls
+ * @param usingBundle The bundle making use of the proxy
+ * @return The proxy to be used by client code or {@code null} if not all
+ * service interfaces can be loaded by the class loader of the
+ * {@code delegatee} object.
+ */
+ static Object createProxy(final String[] interfaceNames, final AbstractSlingRepository delegatee,
+ final Bundle usingBundle) {
+
+ // get the interface classes to create the proxy
+ final ClassLoader cl = delegatee.getClass().getClassLoader();
+ final Class<?>[] interfaces = new Class<?>[interfaceNames.length];
+ for (int i = 0; i < interfaces.length; i++) {
+ try {
+ interfaces[i] = cl.loadClass(interfaceNames[i]);
+ } catch (ClassNotFoundException e) {
+ LoggerFactory.getLogger(SlingRepositoryProxyHandler.class).error(
+ "createProxy: Cannot load interface class " + interfaceNames[i], e);
+ return null;
+ }
+ }
+
+ // create the proxy
+ final InvocationHandler handler = new SlingRepositoryProxyHandler(delegatee, usingBundle);
+ return Proxy.newProxyInstance(cl, interfaces, handler);
+ }
+
+ private SlingRepositoryProxyHandler(final AbstractSlingRepository delegatee, final Bundle usingBundle) {
+ this.delegatee = delegatee;
+ this.usingBundle = usingBundle;
+ }
+
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+ if (SlingRepositoryProxyHandler.LOGIN_SERVICE_NAME.equals(method.getName()) && args != null && args.length == 2) {
+ return this.delegatee.loginService(this.usingBundle, (String) args[0], (String) args[1]);
+ }
+
+ // otherwise forward to the AbstractSlingRepository implementation
+ try {
+ return method.invoke(this.delegatee, args);
+ } catch (InvocationTargetException ite) {
+ throw ite.getTargetException();
+ }
+ }
+ }
}
diff --git a/src/main/java/org/apache/sling/jcr/base/internal/loader/Loader.java b/src/main/java/org/apache/sling/jcr/base/internal/loader/Loader.java
index 91460c9..809a02e 100644
--- a/src/main/java/org/apache/sling/jcr/base/internal/loader/Loader.java
+++ b/src/main/java/org/apache/sling/jcr/base/internal/loader/Loader.java
@@ -333,6 +333,7 @@
}
private Session getSession() throws RepositoryException {
+ // TODO: Should really use loginService !!
return this.slingRepository.loginAdministrative(null);
}
diff --git a/src/main/java/org/apache/sling/jcr/base/package-info.java b/src/main/java/org/apache/sling/jcr/base/package-info.java
new file mode 100644
index 0000000..9eb29d3
--- /dev/null
+++ b/src/main/java/org/apache/sling/jcr/base/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+
+@Version("2.2")
+package org.apache.sling.jcr.base;
+
+import aQute.bnd.annotation.Version;
+
diff --git a/src/main/java/org/apache/sling/jcr/base/util/package-info.java b/src/main/java/org/apache/sling/jcr/base/util/package-info.java
new file mode 100644
index 0000000..7963a9b
--- /dev/null
+++ b/src/main/java/org/apache/sling/jcr/base/util/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+
+@Version("2.1")
+package org.apache.sling.jcr.base.util;
+
+import aQute.bnd.annotation.Version;
+