You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by ro...@apache.org on 2017/11/07 10:14:49 UTC

[sling-org-apache-sling-serviceusermapper] annotated tag org.apache.sling.serviceusermapper-1.3.0 created (now a18b683)

This is an automated email from the ASF dual-hosted git repository.

rombert pushed a change to annotated tag org.apache.sling.serviceusermapper-1.3.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-serviceusermapper.git.


      at a18b683  (tag)
 tagging fd703e7dee4ca607fbdebfb7bf02bb47df82ca76 (commit)
      by Carsten Ziegeler
      on Fri Apr 28 13:30:45 2017 +0000

- Log -----------------------------------------------------------------
org.apache.sling.serviceusermapper-1.3.0
-----------------------------------------------------------------------

This annotated tag includes the following new commits:

     new e689b69  Working on a prototype to deprecate loginAdministrative
     new c5e0963  change api: use ServiceFactory approach and service bundle is actually the using bundle
     new 7d98561  Switch back to using a regular ServiceUserName service tacking the service bundle as an argument.
     new 1b03026  Implement support for service based ResourceResolver and Session access
     new 3ecf9df  Implement support for service based ResourceResolver and Session access
     new f39b2fc  SLING-2944 Implement Service User Mapper
     new 0a5259b  SLING-2944 :  Replace administrative login by service-based login
     new e9b4e3e  Update to parent pom 18
     new 145bf95  SLING-3286 - Remove plugin version overrides from poms
     new e1a1537  Add svn info
     new ce124d6  [maven-release-plugin] prepare release org.apache.sling.serviceusermapper-1.0.0
     new b1c7389  [maven-release-plugin] prepare for next development iteration
     new e40493e  Update to parent pom v19
     new 90f2a66  SLING-3578 : Support mapping amendments
     new fe71e7f  [maven-release-plugin] prepare release org.apache.sling.serviceusermapper-1.0.2
     new 0394164  [maven-release-plugin] prepare for next development iteration
     new dad1f70  SLING-3465 : Wrong description for "user.mapping" property of ServiceUserMapperImpl. Apply patch from Tobias Bocanegra and also fix the copied info in the new MappingConfigAmendment
     new a8f6248  SLING-3757 : cannot add more than 1 service user with the OSGI console
     new 3c2449b  [maven-release-plugin] prepare release org.apache.sling.serviceusermapper-1.0.4
     new d6a1293  [maven-release-plugin] prepare for next development iteration
     new fdec4ca  Updated to parent version 20
     new a151b5a  Update to Sling Parent POM 22 with baselining enabled
     new 3d65ad7  SLING-3854 - Add configuration option to restrict service user mapper to system users
     new eb2a1ce  SLING-4312: Register an osgi service for each available service user
     new 84260c0  SLING-4312: revert revision 1661081 due to a compile error on java 1.6
     new 79cfa85  [maven-release-plugin] prepare release org.apache.sling.serviceusermapper-1.1.0
     new 23de558  [maven-release-plugin] prepare for next development iteration
     new d3e3149  SLING-4312: Register an osgi service for each available service user (+ change tests to mockito)
     new 6bc2582  SLING-4312: changed name from ServiceUserMapping to ServiceUserMapped and removed all metohds
     new dd3c265  SLING-4495 : Improve ServiceUserMapperImpl#isValidUser
     new ff10f6a  SLING-4312 : update javadocs
     new 8f7e8ab  [maven-release-plugin] prepare release org.apache.sling.serviceusermapper-1.2.0
     new ddeb68b  [maven-release-plugin] prepare for next development iteration
     new 8f8ddd7  Add missing licence header
     new 87a8eef  SLING-4742 Service User Mapper: Web Console Name Hints for Mapping Configurations
     new d10ff51  Update to Sling Parent 23
     new 470edb7  set parent version to 24 and add empty relativePath where missing
     new 9780c5b  SLING-4930 - InventoryPrinter for service user mappings
     new d45db0b  SLING-4930 - keep only the 'mappings by user' section, I think the raw mappings are redundant
     new 1029fe5  SLING-4930 : InventoryPrinter for Service User Mappings
     new 6934258  SLING-4930 : InventoryPrinter for Service User Mappings
     new ccf117e  SLING-4895: Service registry should not be called from within synchronized block
     new b1a6848  SLING-5019 : ServiceUserMappedBundleFilter should be immediate
     new bebc3e4  SLING-5006 restart ServiceUserMapped services in case a ServiceUserValidator has been changed.
     new c904ef4  Update the main reactor to parent 25
     new ff12b1f  SLING-5006: do service registrations asyncronously and outside syncronized area
     new c98618d  SLING-5006: set executorService to null on deactivate
     new 07e1e1b  [maven-release-plugin] prepare release org.apache.sling.serviceusermapper-1.2.2
     new ae5f528  [maven-release-plugin] prepare for next development iteration
     new 54e156d  [maven-release-plugin] prepare release org.apache.sling.serviceusermapper-1.2.4
     new cb28970  [maven-release-plugin] prepare for next development iteration
     new aed1e64  Switch back to 1.2.3-SNAPSHOT
     new 5d8bd40  Switch to parent pom 26
     new 8013242  SLING-5484 - more logging for ServiceUserMapperImpl.getServiceUserID and related methods
     new 87c737f  SLING-5484 - add debug log for default user
     new f2dda72  SLING-5484 - typo
     new c32106c  SLING-5484 - log the active mappings update as well
     new ece541c  SLING-5484 - tweak debug logs
     new 8a5761a  SLING-6397 : Error logged by ServiceUserMapperImpl while unregistering an already unregistered service
     new c3f5d9d  [maven-release-plugin] prepare release org.apache.sling.serviceusermapper-1.2.4
     new ce974aa  [maven-release-plugin] prepare for next development iteration
     new dfbde94  SLING-6555 : Migrate to R6 annotations, clean up dependencies
     new 847de08  SLING-6555 : Migrate to R6 annotations, clean up dependencies
     new ece807e  use Sling Parent 30
     new f217eff  add missing Felix SCR annotations
     new 0baa5ba  Remove unused SCR plugin, fix javadoc
     new 743c855  [maven-release-plugin] prepare release org.apache.sling.serviceusermapper-1.2.6
     new 5248f77  [maven-release-plugin] prepare for next development iteration
     new 13fa883  SLING-6772 : Provide default mapping for service users
     new 8667bd8  [maven-release-plugin] prepare release org.apache.sling.serviceusermapper-1.3.0
     new a249752  [maven-release-plugin] copy for tag org.apache.sling.serviceusermapper-1.3.0
     new fd703e7  [maven-release-plugin] copy for tag org.apache.sling.serviceusermapper-1.3.0

The 72 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


-- 
To stop receiving notification emails like this one, please contact
['"commits@sling.apache.org" <co...@sling.apache.org>'].

[sling-org-apache-sling-serviceusermapper] 03/05: [maven-release-plugin] prepare release org.apache.sling.serviceusermapper-1.3.0

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.serviceusermapper-1.3.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-serviceusermapper.git

commit 8667bd8aed2cbc04b5f0375d0bbabc86b0d16daf
Author: Carsten Ziegeler <cz...@apache.org>
AuthorDate: Thu Apr 6 07:39:21 2017 +0000

    [maven-release-plugin] prepare release org.apache.sling.serviceusermapper-1.3.0
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/serviceusermapper@1790353 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/pom.xml b/pom.xml
index fd7648f..a52b6bb 100644
--- a/pom.xml
+++ b/pom.xml
@@ -29,7 +29,7 @@
 
     <artifactId>org.apache.sling.serviceusermapper</artifactId>
     <packaging>bundle</packaging>
-    <version>1.2.7-SNAPSHOT</version>
+    <version>1.3.0</version>
 
     <name>Apache Sling Service User Mapper</name>
     <description>
@@ -40,9 +40,9 @@
     </description>
 
     <scm>
-        <connection>scm:svn:http://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/serviceusermapper</connection>
-        <developerConnection>scm:svn:https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/serviceusermapper</developerConnection>
-        <url>http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/serviceusermapper</url>
+        <connection>scm:svn:http://svn.apache.org/repos/asf/sling/tags/org.apache.sling.serviceusermapper-1.3.0</connection>
+        <developerConnection>scm:svn:https://svn.apache.org/repos/asf/sling/tags/org.apache.sling.serviceusermapper-1.3.0</developerConnection>
+        <url>http://svn.apache.org/viewvc/sling/tags/org.apache.sling.serviceusermapper-1.3.0</url>
     </scm>
 
     <build>

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-serviceusermapper] 01/05: [maven-release-plugin] prepare for next development iteration

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.serviceusermapper-1.3.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-serviceusermapper.git

commit 5248f773e41b8605b733aaa8eea3c98987c689fd
Author: Carsten Ziegeler <cz...@apache.org>
AuthorDate: Mon Mar 27 08:29:15 2017 +0000

    [maven-release-plugin] prepare for next development iteration
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/serviceusermapper@1788849 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/pom.xml b/pom.xml
index add0a18..fd7648f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -29,7 +29,7 @@
 
     <artifactId>org.apache.sling.serviceusermapper</artifactId>
     <packaging>bundle</packaging>
-    <version>1.2.6</version>
+    <version>1.2.7-SNAPSHOT</version>
 
     <name>Apache Sling Service User Mapper</name>
     <description>
@@ -40,9 +40,9 @@
     </description>
 
     <scm>
-        <connection>scm:svn:http://svn.apache.org/repos/asf/sling/tags/org.apache.sling.serviceusermapper-1.2.6</connection>
-        <developerConnection>scm:svn:https://svn.apache.org/repos/asf/sling/tags/org.apache.sling.serviceusermapper-1.2.6</developerConnection>
-        <url>http://svn.apache.org/viewvc/sling/tags/org.apache.sling.serviceusermapper-1.2.6</url>
+        <connection>scm:svn:http://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/serviceusermapper</connection>
+        <developerConnection>scm:svn:https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/serviceusermapper</developerConnection>
+        <url>http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/serviceusermapper</url>
     </scm>
 
     <build>

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-serviceusermapper] 05/05: [maven-release-plugin] copy for tag org.apache.sling.serviceusermapper-1.3.0

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.serviceusermapper-1.3.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-serviceusermapper.git

commit fd703e7dee4ca607fbdebfb7bf02bb47df82ca76
Author: Carsten Ziegeler <cz...@apache.org>
AuthorDate: Fri Apr 28 13:30:45 2017 +0000

    [maven-release-plugin] copy for tag org.apache.sling.serviceusermapper-1.3.0
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/tags/org.apache.sling.serviceusermapper-1.3.0@1793085 13f79535-47bb-0310-9956-ffa450edef68
---
 serviceusermapper/pom.xml                          | 137 +++++++
 .../serviceusermapping/ServiceUserMapped.java      |  43 ++
 .../serviceusermapping/ServiceUserMapper.java      |  74 ++++
 .../serviceusermapping/ServiceUserValidator.java   |  37 ++
 .../sling/serviceusermapping/impl/Mapping.java     | 139 +++++++
 .../impl/MappingConfigAmendment.java               | 107 +++++
 .../impl/MappingInventoryPrinter.java              | 142 +++++++
 .../impl/ServiceUserMappedBundleFilter.java        |  93 +++++
 .../impl/ServiceUserMappedImpl.java                |  31 ++
 .../impl/ServiceUserMapperImpl.java                | 441 +++++++++++++++++++++
 .../sling/serviceusermapping/package-info.java     |  22 +
 .../sling/serviceusermapping/impl/MappingTest.java | 141 +++++++
 .../impl/ServiceUserMappedBundleFilterTest.java    | 115 ++++++
 .../impl/ServiceUserMapperImplTest.java            | 380 ++++++++++++++++++
 14 files changed, 1902 insertions(+)

diff --git a/serviceusermapper/pom.xml b/serviceusermapper/pom.xml
new file mode 100644
index 0000000..a52b6bb
--- /dev/null
+++ b/serviceusermapper/pom.xml
@@ -0,0 +1,137 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!--
+    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>30</version>
+        <relativePath />
+    </parent>
+
+    <artifactId>org.apache.sling.serviceusermapper</artifactId>
+    <packaging>bundle</packaging>
+    <version>1.3.0</version>
+
+    <name>Apache Sling Service User Mapper</name>
+    <description>
+        Provides a service to map service names with
+        optional service information to user names to
+        be used to access repositories such as the JCR
+        repository or the Sling ResourceResolver.
+    </description>
+
+    <scm>
+        <connection>scm:svn:http://svn.apache.org/repos/asf/sling/tags/org.apache.sling.serviceusermapper-1.3.0</connection>
+        <developerConnection>scm:svn:https://svn.apache.org/repos/asf/sling/tags/org.apache.sling.serviceusermapper-1.3.0</developerConnection>
+        <url>http://svn.apache.org/viewvc/sling/tags/org.apache.sling.serviceusermapper-1.3.0</url>
+    </scm>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <extensions>true</extensions>
+                <configuration>
+                    <instructions>
+                        <Import-Package>
+                            org.apache.felix.inventory;resolution:=optional,
+                            *
+                        </Import-Package>
+                        <DynamicImport-Package>
+                            org.apache.felix.inventory
+                        </DynamicImport-Package>
+                        <Embed-Dependency>
+                             org.apache.felix.utils;inline=org/apache/felix/utils/json/JSONWriter**
+                        </Embed-Dependency>
+                    </instructions>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-javadoc-plugin</artifactId>
+                <configuration>
+                    <excludePackageNames>
+                        org.apache.sling.serviceusermapping.impl
+                    </excludePackageNames>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.api</artifactId>
+            <version>2.3.0</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.utils</artifactId>
+            <version>1.9.0</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.inventory</artifactId>
+            <version>1.0.0</version>
+            <scope>provided</scope>
+        </dependency>
+  
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>osgi.core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.service.event</artifactId>
+            <version>1.3.1</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>javax.servlet-api</artifactId>
+        </dependency>
+
+        <!-- Testing -->
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-simple</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-all</artifactId>
+            <version>1.9.5</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+</project>
diff --git a/serviceusermapper/src/main/java/org/apache/sling/serviceusermapping/ServiceUserMapped.java b/serviceusermapper/src/main/java/org/apache/sling/serviceusermapping/ServiceUserMapped.java
new file mode 100644
index 0000000..921dffb
--- /dev/null
+++ b/serviceusermapper/src/main/java/org/apache/sling/serviceusermapping/ServiceUserMapped.java
@@ -0,0 +1,43 @@
+/*
+ * 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.serviceusermapping;
+
+import org.osgi.annotation.versioning.ProviderType;
+
+/**
+ * The <code>ServiceUserMapped</code> is a marker service that can be used to ensure that there is an already registered mapping for a certain service/subService.
+ * A service reference targeting a <code>ServiceUserMapped</code> will be satisfied only if <code>ServiceUserMapper.getServiceUserID</code>
+ * will resolve the subService to an userID.
+ * For example setting the reference target to "(subServiceName=mySubService)" ensures that your component only starts when the subService is available.
+ * The subServiceName will not be set for mappings that do not have one, and those can be referenced with a negating target "(!(subServiceName=*))".
+ * Trying to reference a sub service from a bundle for which it was not registered for will not work.
+ *
+ * As the service user mapper implementation is using a fallback, it is usually best to use a reference target that includes both
+ * options, the sub service name and the fallback, therefore a target like "(|((subServiceName=mySubService)(!(subServiceName=*))))" should be used.
+ */
+@ProviderType
+public interface ServiceUserMapped {
+
+
+    /**
+     * The name of the osgi property holding the sub service name.
+     */
+    static String SUBSERVICENAME = "subServiceName";
+
+}
diff --git a/serviceusermapper/src/main/java/org/apache/sling/serviceusermapping/ServiceUserMapper.java b/serviceusermapper/src/main/java/org/apache/sling/serviceusermapping/ServiceUserMapper.java
new file mode 100644
index 0000000..d8e6701
--- /dev/null
+++ b/serviceusermapper/src/main/java/org/apache/sling/serviceusermapping/ServiceUserMapper.java
@@ -0,0 +1,74 @@
+/*
+ * 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.serviceusermapping;
+
+import org.osgi.annotation.versioning.ProviderType;
+import org.osgi.framework.Bundle;
+
+/**
+ * The <code>ServiceUserMapper</code> service can be used to map a service
+ * provided by a bundle to the ID of a user account used to access the
+ * ResourceResolver used by the service to access its data.
+ * <p>
+ * The goal of this service is to allow services to be implemented accessing the
+ * storage with service-specific accounts which are tailored to allow the
+ * service appropriate access without requiring administrative level access to
+ * the storage.
+ * <p>
+ * In general a service is implement in a single bundle such as the JSP compiler
+ * bundle. Other services may be implemented in multiple bundles. In certain
+ * cases there may be sub-services requiring different access levels. For
+ * example a couple of bundles may implement a "mail" service where each bundle
+ * implements a part of the service such as the "smtp", "queuing", and
+ * "delivery" sub services. Such sub services are identified with the
+ * {@code subServiceName} parameter on the method calls.
+ * <p>
+ * In addition to allowing to phase out the use of
+ * {@code ResourceResolver.getAdministrativeResourceResolver} and
+ * {@code SlingRepository.loginAdministrative} it also allows to better account
+ * for changes to the storage by the different services.
+ * <p>
+ * This service is not intended to be used by the general user but by
+ * implementations of the {@code ResourceResolverFactory} and
+ * {@code SlingRepository} services.
+ * <p>
+ * This service is not intended to be implemented by clients.
+ *
+ * @see <a href=
+ *      "http://sling.apache.org/documentation/the-sling-engine/service-authentication.html"
+ *      >Service Authentication</a>
+ */
+@ProviderType
+public interface ServiceUserMapper {
+
+    /**
+     * Returns the ID of a user to access the data store on behalf of the
+     * service.
+     *
+     * @param bundle The bundle implementing the service request access to
+     *            resources.
+     * @param subServiceName Name of the sub service. This parameter is optional and
+     *            may be an empty string or {@code null}.
+     * @return The ID of the user to use to provide access to the resources for
+     *         the service. This may be {@code null} if no particular user can
+     *         be derived for the service identified by the bundle and the
+     *         optional {@code serviceInfo}.
+     */
+    String getServiceUserID(Bundle bundle, String subServiceName);
+}
diff --git a/serviceusermapper/src/main/java/org/apache/sling/serviceusermapping/ServiceUserValidator.java b/serviceusermapper/src/main/java/org/apache/sling/serviceusermapping/ServiceUserValidator.java
new file mode 100644
index 0000000..72faa34
--- /dev/null
+++ b/serviceusermapper/src/main/java/org/apache/sling/serviceusermapping/ServiceUserValidator.java
@@ -0,0 +1,37 @@
+/*
+ * 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.serviceusermapping;
+
+import org.osgi.annotation.versioning.ConsumerType;
+
+/**
+ * The {@code ServiceUserValidator} allows to implement validation of configured
+ * service user mappings.
+ */
+@ConsumerType
+public interface ServiceUserValidator {
+
+    /**
+     * Validates the configured service user ID.
+     *
+     * @param serviceUserId The ID of the configured service user.
+     * @param serviceName The name of the service
+     * @param subServiceName The optional sub service name.
+     * @return {@code true} if the configured service user is valid; {@code false} otherwise.
+     */
+    boolean isValid(String serviceUserId, String serviceName, String subServiceName);
+}
\ No newline at end of file
diff --git a/serviceusermapper/src/main/java/org/apache/sling/serviceusermapping/impl/Mapping.java b/serviceusermapper/src/main/java/org/apache/sling/serviceusermapping/impl/Mapping.java
new file mode 100644
index 0000000..b4650c2
--- /dev/null
+++ b/serviceusermapper/src/main/java/org/apache/sling/serviceusermapping/impl/Mapping.java
@@ -0,0 +1,139 @@
+/*
+ * 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.serviceusermapping.impl;
+
+/**
+ * The <code>Mapping</code> class defines the mapping of a service's name and
+ * optional service information to a user name.
+ */
+class Mapping implements Comparable<Mapping> {
+
+
+    /**
+     * The name of the osgi property holding the service name.
+     */
+    static String SERVICENAME = ".serviceName";
+
+    private final String serviceName;
+
+    private final String subServiceName;
+
+    private final String userName;
+
+    /**
+     * Creates a mapping entry for the entry specification of the form:
+     *
+     * <pre>
+     * spec = serviceName [ ":" subServiceName ] "=" userName .
+     * </pre>
+     *
+     * @param spec The mapping specification.
+     * @throws NullPointerException if {@code spec} is {@code null}.
+     * @throws IllegalArgumentException if {@code spec} does not match the
+     *             expected pattern.
+     */
+    Mapping(final String spec) {
+
+        final int colon = spec.indexOf(':');
+        final int equals = spec.indexOf('=');
+
+        if (colon == 0 || equals <= 0) {
+            throw new IllegalArgumentException("serviceName is required");
+        } else if (equals == spec.length() - 1) {
+            throw new IllegalArgumentException("userName is required");
+        } else if (colon + 1 == equals) {
+            throw new IllegalArgumentException("serviceInfo must not be empty");
+        }
+
+        if (colon < 0 || colon > equals) {
+            this.serviceName = spec.substring(0, equals);
+            this.subServiceName = null;
+        } else {
+            this.serviceName = spec.substring(0, colon);
+            this.subServiceName = spec.substring(colon + 1, equals);
+        }
+
+        this.userName = spec.substring(equals + 1);
+    }
+
+    /**
+     * Returns the user name if the {@code serviceName} and the
+     * {@code serviceInfo} match. Otherwise {@code null} is returned.
+     *
+     * @param serviceName The name of the service to match. If this is
+     *            {@code null} this mapping will not match.
+     * @param subServiceName The Subservice Name to match. This may be
+     *            {@code null}.
+     * @return The user name if this mapping matches or {@code null} otherwise.
+     */
+    String map(final String serviceName, final String subServiceName) {
+        if (this.serviceName.equals(serviceName) && equals(this.subServiceName, subServiceName)) {
+            return userName;
+        }
+
+        return null;
+    }
+
+    private boolean equals(String str1, String str2) {
+        return ((str1 == null) ? str2 == null : str1.equals(str2));
+    }
+
+    @Override
+    public String toString() {
+        return "Mapping [serviceName=" + serviceName + ", subServiceName="
+                + subServiceName + ", userName=" + userName + "]";
+    }
+
+    public String getServiceName() {
+        return serviceName;
+    }
+
+    public String getSubServiceName() {
+        return subServiceName;
+    }
+
+
+    public int compareTo(Mapping o) {
+        if (o == null) {
+            return -1;
+        }
+
+        int result = compare(this.serviceName, o.serviceName);
+        if (result == 0) {
+            result = compare(this.subServiceName, o.subServiceName);
+        }
+        return result;
+    }
+
+    private int compare(String str1, String str2) {
+        if (str1 == str2) {
+            return 0;
+        }
+
+        if (str1 == null) {
+            return -1;
+        }
+
+        if (str2 == null) {
+            return 1;
+        }
+
+        return str1.hashCode() - str2.hashCode();
+    }
+}
diff --git a/serviceusermapper/src/main/java/org/apache/sling/serviceusermapping/impl/MappingConfigAmendment.java b/serviceusermapper/src/main/java/org/apache/sling/serviceusermapping/impl/MappingConfigAmendment.java
new file mode 100644
index 0000000..ff631cf
--- /dev/null
+++ b/serviceusermapper/src/main/java/org/apache/sling/serviceusermapping/impl/MappingConfigAmendment.java
@@ -0,0 +1,107 @@
+/*
+ * 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.serviceusermapping.impl;
+
+import java.util.ArrayList;
+
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.ConfigurationPolicy;
+import org.osgi.service.component.annotations.Modified;
+import org.osgi.service.metatype.annotations.AttributeDefinition;
+import org.osgi.service.metatype.annotations.Designate;
+import org.osgi.service.metatype.annotations.ObjectClassDefinition;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Designate(factory=true, ocd=MappingConfigAmendment.Config.class)
+@Component(name = "org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.amended",
+           configurationPolicy=ConfigurationPolicy.REQUIRE,
+           service={MappingConfigAmendment.class},
+           property= {
+                   "webconsole.configurationFactory.nameHint=Mapping: {user.mapping}",
+           })
+public class MappingConfigAmendment implements Comparable<MappingConfigAmendment> {
+
+    @ObjectClassDefinition(name ="Apache Sling Service User Mapper Service Amendment",
+            description="An amendment mapping for the user mapping service.")
+    public @interface Config {
+
+        @AttributeDefinition(name = "Ranking",
+              description="Amendments are processed in order of their ranking, an amendment with a higher ranking has" +
+                          " precedence over a mapping with a lower ranking.")
+        int service_ranking() default 0;
+
+        @AttributeDefinition(name = "Service Mappings",
+            description = "Provides mappings from service name to user names. "
+                + "Each entry is of the form 'bundleId [ \":\" subServiceName ] \"=\" userName' "
+                + "where bundleId and subServiceName identify the service and userName "
+                + "defines the name of the user to provide to the service. Invalid entries are logged and ignored.")
+        String[] user_mapping() default {};
+    }
+
+    /** default logger */
+    private final Logger logger = LoggerFactory.getLogger(getClass());
+
+    private Mapping[] serviceUserMappings;
+
+    private int serviceRanking;
+
+    @Activate
+    @Modified
+    void configure(final Config config) {
+        final String[] props = config.user_mapping();
+
+        if ( props != null ) {
+            final ArrayList<Mapping> mappings = new ArrayList<Mapping>(props.length);
+            for (final String prop : props) {
+                if (prop != null && prop.trim().length() > 0 ) {
+                    try {
+                        final Mapping mapping = new Mapping(prop.trim());
+                        mappings.add(mapping);
+                    } catch (final IllegalArgumentException iae) {
+                        logger.info("configure: Ignoring '{}': {}", prop, iae.getMessage());
+                    }
+                }
+            }
+
+            this.serviceUserMappings = mappings.toArray(new Mapping[mappings.size()]);
+        } else {
+            this.serviceUserMappings = new Mapping[0];
+        }
+        this.serviceRanking = config.service_ranking();
+    }
+
+    public Mapping[] getServiceUserMappings() {
+        return this.serviceUserMappings;
+    }
+
+    @Override
+    public int compareTo(final MappingConfigAmendment o) {
+        // Sort by rank in descending order.
+        if ( this.serviceRanking > o.serviceRanking ) {
+            return -1; // lower rank
+        } else if (this.serviceRanking < o.serviceRanking) {
+            return 1; // higher rank
+        }
+
+        // If ranks are equal, then sort by hash code
+        return this.hashCode() < o.hashCode() ? -1 : 1;
+    }
+}
diff --git a/serviceusermapper/src/main/java/org/apache/sling/serviceusermapping/impl/MappingInventoryPrinter.java b/serviceusermapper/src/main/java/org/apache/sling/serviceusermapping/impl/MappingInventoryPrinter.java
new file mode 100644
index 0000000..e3488f5
--- /dev/null
+++ b/serviceusermapper/src/main/java/org/apache/sling/serviceusermapping/impl/MappingInventoryPrinter.java
@@ -0,0 +1,142 @@
+/*
+ * 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.serviceusermapping.impl;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+import org.apache.felix.inventory.Format;
+import org.apache.felix.inventory.InventoryPrinter;
+import org.apache.felix.utils.json.JSONWriter;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+
+/** InventoryPrinter for service user mappings */
+@Component(service = InventoryPrinter.class,
+           property = {
+                   InventoryPrinter.FORMAT + "=JSON",
+                   InventoryPrinter.FORMAT + "=TEXT",
+                   InventoryPrinter.TITLE + "=Sling Service User Mappings",
+                   InventoryPrinter.WEBCONSOLE + ":Boolean=true"
+           })
+public class MappingInventoryPrinter implements InventoryPrinter {
+
+    @Reference
+    private ServiceUserMapperImpl mapper;
+
+    @Override
+    public void print(PrintWriter out, Format format, boolean isZip) {
+        try {
+            if(format.equals(Format.JSON)) {
+                renderJson(out);
+            } else if(format.equals(Format.TEXT)) {
+                renderText(out);
+            }
+        } catch(Exception e) {
+            e.printStackTrace(out);
+        }
+    }
+
+    private String getMappedUser(Mapping m) {
+        return m.map(m.getServiceName(), m.getSubServiceName());
+    }
+
+    private SortedMap<String, List<Mapping>> getMappingsByUser(List<Mapping> mappings) {
+        SortedMap<String, List<Mapping>> result = new TreeMap<String, List<Mapping>>();
+        for(Mapping m : mappings) {
+            final String user = getMappedUser(m);
+            List<Mapping> list = result.get(user);
+            if(list == null) {
+                list = new ArrayList<Mapping>();
+                result.put(user, list);
+            }
+            list.add(m);
+        }
+        return result;
+    }
+
+    private void asJSON(JSONWriter w, Mapping m) throws IOException {
+        w.object();
+        w.key("serviceName").value(m.getServiceName());
+        w.key("subServiceName").value(m.getSubServiceName());
+        w.key("user").value(getMappedUser(m));
+        w.endObject();
+    }
+
+    private void renderJson(PrintWriter out) throws IOException {
+        final List<Mapping> data = mapper.getActiveMappings();
+        final Map<String, List<Mapping>> byUser = getMappingsByUser(data);
+
+        final JSONWriter w = new JSONWriter(out);
+        w.object();
+        w.key("title").value("Service User Mappings");
+        w.key("mappingsCount").value(data.size());
+        w.key("uniqueUsersCount").value(byUser.keySet().size());
+
+        w.key("mappingsByUser");
+        w.object();
+        for(Map.Entry<String, List<Mapping>> e : byUser.entrySet()) {
+            w.key(e.getKey());
+            w.array();
+            for(Mapping m : e.getValue()) {
+                asJSON(w,m);
+            }
+            w.endArray();
+        }
+        w.endObject();
+
+        w.endObject();
+    }
+
+    private void asText(PrintWriter w, Mapping m, String indent) {
+        final String SEP = " / ";
+        w.print(indent);
+        w.print(m.getServiceName());
+        w.print(SEP);
+        final String sub = m.getSubServiceName();
+        w.print(sub == null ? "" : sub);
+        w.print(SEP);
+        w.println(getMappedUser(m));
+    }
+
+    private void renderText(PrintWriter out) {
+        final List<Mapping> data = mapper.getActiveMappings();
+        final Map<String, List<Mapping>> byUser = getMappingsByUser(data);
+
+        final String formatInfo = " (format: service name / sub service name / user)";
+
+        out.print("*** Mappings by user (");
+        out.print(byUser.keySet().size());
+        out.print(" users):");
+        out.println(formatInfo);
+
+        for(Map.Entry<String, List<Mapping>> e : byUser.entrySet()) {
+            out.print("  ");
+            out.println(e.getKey());
+            for(Mapping m : e.getValue()) {
+                asText(out, m, "    ");
+            }
+        }
+   }
+}
\ No newline at end of file
diff --git a/serviceusermapper/src/main/java/org/apache/sling/serviceusermapping/impl/ServiceUserMappedBundleFilter.java b/serviceusermapper/src/main/java/org/apache/sling/serviceusermapping/impl/ServiceUserMappedBundleFilter.java
new file mode 100644
index 0000000..a8b0719
--- /dev/null
+++ b/serviceusermapper/src/main/java/org/apache/sling/serviceusermapping/impl/ServiceUserMappedBundleFilter.java
@@ -0,0 +1,93 @@
+/*
+ * 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.serviceusermapping.impl;
+
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.Map;
+
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceEvent;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.hooks.service.EventListenerHook;
+import org.osgi.framework.hooks.service.FindHook;
+import org.osgi.framework.hooks.service.ListenerHook;
+import org.osgi.service.component.annotations.Component;
+
+/**
+ * The <code>ServiceUserMappingBundleFilter</code> only allows the bundle for which the service mapping is available to see it.
+ */
+@Component(immediate=true, // framework gets/ungets hooks each time
+           service = {EventListenerHook.class, FindHook.class} )
+public class ServiceUserMappedBundleFilter implements EventListenerHook, FindHook {
+
+    @Override
+    public void event(ServiceEvent serviceEvent, Map map) {
+
+        ServiceReference serviceReference = serviceEvent.getServiceReference();
+        if (isServiceMappingReference(serviceReference)) {
+            Object serviceName = serviceReference.getProperty(Mapping.SERVICENAME);
+
+            if (serviceName != null && serviceName instanceof String) {
+                Iterator<Map.Entry<BundleContext, Collection<ListenerHook.ListenerInfo>>> it = map.entrySet().iterator();
+                while (it.hasNext()) {
+                    BundleContext ctx = it.next().getKey();
+
+                    String bundleServiceName = ServiceUserMapperImpl.getServiceName(ctx.getBundle());
+                    if (!serviceName.equals(bundleServiceName)) {
+                        it.remove();
+                    }
+                }
+            }
+        }
+    }
+
+    @Override
+    public void find(BundleContext bundleContext, String name, String filter, boolean allServices,
+                     Collection references) {
+        String bundleServiceName = ServiceUserMapperImpl.getServiceName(bundleContext.getBundle());
+
+        Iterator<ServiceReference> it = references.iterator();
+        while (it.hasNext()) {
+            ServiceReference serviceReference = it.next();
+            if (isServiceMappingReference(serviceReference)) {
+                Object serviceName = serviceReference.getProperty(Mapping.SERVICENAME);
+
+                if (serviceName != null && !serviceName.equals(bundleServiceName)) {
+                    it.remove();
+                }
+            }
+        }
+    }
+
+    private static boolean isServiceMappingReference(ServiceReference serviceReference) {
+        Object objectClass = serviceReference.getProperty(Constants.OBJECTCLASS);
+        for (Object o :  (Object[]) objectClass) {
+            if (ServiceUserMappedImpl.SERVICEUSERMAPPED.equals(o)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+
+
+}
diff --git a/serviceusermapper/src/main/java/org/apache/sling/serviceusermapping/impl/ServiceUserMappedImpl.java b/serviceusermapper/src/main/java/org/apache/sling/serviceusermapping/impl/ServiceUserMappedImpl.java
new file mode 100644
index 0000000..3a7d2d7
--- /dev/null
+++ b/serviceusermapper/src/main/java/org/apache/sling/serviceusermapping/impl/ServiceUserMappedImpl.java
@@ -0,0 +1,31 @@
+/*
+ * 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.serviceusermapping.impl;
+
+import org.apache.sling.serviceusermapping.ServiceUserMapped;
+
+/**
+ * This is a trivial implementation of the marker interface <code>ServiceUserMapped</code>
+ */
+public class ServiceUserMappedImpl implements ServiceUserMapped {
+
+    static String SERVICEUSERMAPPED = ServiceUserMapped.class.getName();
+
+}
diff --git a/serviceusermapper/src/main/java/org/apache/sling/serviceusermapping/impl/ServiceUserMapperImpl.java b/serviceusermapper/src/main/java/org/apache/sling/serviceusermapping/impl/ServiceUserMapperImpl.java
new file mode 100644
index 0000000..a7b8c48
--- /dev/null
+++ b/serviceusermapper/src/main/java/org/apache/sling/serviceusermapping/impl/ServiceUserMapperImpl.java
@@ -0,0 +1,441 @@
+/*
+ * 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.serviceusermapping.impl;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Dictionary;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.SortedSet;
+import java.util.TreeMap;
+import java.util.TreeSet;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import org.apache.sling.serviceusermapping.ServiceUserMapped;
+import org.apache.sling.serviceusermapping.ServiceUserMapper;
+import org.apache.sling.serviceusermapping.ServiceUserValidator;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Deactivate;
+import org.osgi.service.component.annotations.Modified;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.component.annotations.ReferenceCardinality;
+import org.osgi.service.component.annotations.ReferencePolicy;
+import org.osgi.service.metatype.annotations.AttributeDefinition;
+import org.osgi.service.metatype.annotations.Designate;
+import org.osgi.service.metatype.annotations.ObjectClassDefinition;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Designate(ocd = ServiceUserMapperImpl.Config.class)
+@Component(service = {ServiceUserMapper.class, ServiceUserMapperImpl.class})
+public class ServiceUserMapperImpl implements ServiceUserMapper {
+
+    @ObjectClassDefinition(name = "Apache Sling Service User Mapper Service",
+        description = "Configuration for the service mapping service names to names of users.")
+    public @interface Config {
+
+        @AttributeDefinition(name = "Service Mappings",
+            description = "Provides mappings from service name to user names. "
+                + "Each entry is of the form 'bundleId [ \":\" subServiceName ] \"=\" userName' "
+                + "where bundleId and subServiceName identify the service and userName "
+                + "defines the name of the user to provide to the service. Invalid entries are logged and ignored.")
+        String[] user_mapping() default {};
+
+        @AttributeDefinition(name = "Default User",
+            description = "The name of the user to use as the default if no service mapping"
+                + " applies. If this property is missing or empty no default user is defined.")
+        String user_default();
+
+        @AttributeDefinition(name = "Default Mapping",
+                description = "If enabled and no mapping for a requested service user exists and no " +
+                      " default user is defined, a " +
+                     "default mapping is applied which uses the service user \"serviceuser@\" + {bundleId} + [\":\" + subServiceName]")
+        boolean user_enable_default_mapping() default true;
+    }
+
+    /** default log */
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    private Mapping[] globalServiceUserMappings = new Mapping[0];
+
+    private String defaultUser;
+
+    private boolean useDefaultMapping;
+
+    private Map<Long, MappingConfigAmendment> amendments = new HashMap<>();
+
+    private Mapping[] activeMappings = new Mapping[0];
+
+    private final List<ServiceUserValidator> validators = new CopyOnWriteArrayList<>();
+
+    private SortedMap<Mapping, Registration> activeRegistrations = new TreeMap<>();
+
+    private BundleContext bundleContext;
+
+    private ExecutorService executorService;
+
+    public boolean registerAsync = true;
+
+    @Activate
+    @Modified
+    synchronized void configure(BundleContext bundleContext, final Config config) {
+        if (registerAsync && executorService == null) {
+            executorService = Executors.newSingleThreadExecutor();
+        }
+
+        final String[] props = config.user_mapping();
+
+        if ( props != null ) {
+            final ArrayList<Mapping> mappings = new ArrayList<>(props.length);
+            for (final String prop : props) {
+                if (prop != null && prop.trim().length() > 0 ) {
+                    try {
+                        final Mapping mapping = new Mapping(prop.trim());
+                        mappings.add(mapping);
+                    } catch (final IllegalArgumentException iae) {
+                        log.error("configure: Ignoring '{}': {}", prop, iae.getMessage());
+                    }
+                }
+            }
+
+            this.globalServiceUserMappings = mappings.toArray(new Mapping[mappings.size()]);
+        } else {
+            this.globalServiceUserMappings = new Mapping[0];
+        }
+        this.defaultUser = config.user_default();
+        this.useDefaultMapping = config.user_enable_default_mapping();
+
+        RegistrationSet registrationSet = null;
+        this.bundleContext = bundleContext;
+        registrationSet = this.updateMappings();
+
+        this.executeServiceRegistrationsAsync(registrationSet);
+    }
+
+    @Deactivate
+    synchronized void deactivate() {
+        // this call does not unregister the mappings, but they should be unbound
+        // through the unbind methods anyway
+        updateServiceRegistrations(new Mapping[0]);
+        bundleContext = null;
+        if (executorService != null) {
+            executorService.shutdown();
+            executorService = null;
+        }
+    }
+
+    private void restartAllActiveServiceUserMappedServices() {
+        RegistrationSet registrationSet = new RegistrationSet();
+        registrationSet.removed = activeRegistrations.values();
+        registrationSet.added = activeRegistrations.values();
+        executeServiceRegistrationsAsync(registrationSet);
+    }
+
+    /**
+     * bind the serviceUserValidator
+     * @param serviceUserValidator
+     */
+    @Reference(cardinality=ReferenceCardinality.MULTIPLE, policy= ReferencePolicy.DYNAMIC)
+    protected synchronized void bindServiceUserValidator(final ServiceUserValidator serviceUserValidator) {
+        validators.add(serviceUserValidator);
+        restartAllActiveServiceUserMappedServices();
+    }
+
+    /**
+     * unbind the serviceUserValidator
+     * @param serviceUserValidator
+     */
+    protected synchronized void unbindServiceUserValidator(final ServiceUserValidator serviceUserValidator) {
+        validators.remove(serviceUserValidator);
+        restartAllActiveServiceUserMappedServices();
+    }
+
+    /**
+     * @see org.apache.sling.serviceusermapping.ServiceUserMapper#getServiceUserID(org.osgi.framework.Bundle, java.lang.String)
+     */
+    @Override
+    public String getServiceUserID(final Bundle bundle, final String subServiceName) {
+        final String serviceName = getServiceName(bundle);
+        final String userId = internalGetUserId(serviceName, subServiceName);
+        final boolean valid = isValidUser(userId, serviceName, subServiceName);
+        final String result = valid ? userId : null;
+        log.debug(
+                "getServiceUserID(bundle {}, subServiceName {}) returns [{}] (raw userId={}, valid={})",
+                new Object[] { bundle, subServiceName, result, userId, valid });
+        return result;
+    }
+
+    @Reference(cardinality=ReferenceCardinality.MULTIPLE,policy=ReferencePolicy.DYNAMIC,updated="updateAmendment")
+    protected synchronized void bindAmendment(final MappingConfigAmendment amendment, final Map<String, Object> props) {
+        final Long key = (Long) props.get(Constants.SERVICE_ID);
+        RegistrationSet registrationSet = null;
+        amendments.put(key, amendment);
+        registrationSet = this.updateMappings();
+        executeServiceRegistrationsAsync(registrationSet);
+    }
+
+    protected synchronized void unbindAmendment(final MappingConfigAmendment amendment, final Map<String, Object> props) {
+        final Long key = (Long) props.get(Constants.SERVICE_ID);
+        RegistrationSet registrationSet = null;
+        if ( amendments.remove(key) != null ) {
+             registrationSet = this.updateMappings();
+        }
+        executeServiceRegistrationsAsync(registrationSet);
+    }
+
+    protected void updateAmendment(final MappingConfigAmendment amendment, final Map<String, Object> props) {
+        this.bindAmendment(amendment, props);
+    }
+
+    protected RegistrationSet updateMappings() {
+        final List<MappingConfigAmendment> sortedMappings = new ArrayList<>();
+        for(final MappingConfigAmendment amendment : this.amendments.values() ) {
+            sortedMappings.add(amendment);
+        }
+        Collections.sort(sortedMappings);
+
+        final List<Mapping> mappings = new ArrayList<>();
+        for(final Mapping m : this.globalServiceUserMappings) {
+            mappings.add(m);
+        }
+        for(final MappingConfigAmendment mca : sortedMappings) {
+            for(final Mapping m : mca.getServiceUserMappings()) {
+                mappings.add(m);
+            }
+        }
+
+        activeMappings = mappings.toArray(new Mapping[mappings.size()]);
+        log.debug("Active mappings updated: {} mappings active", mappings.size());
+
+        RegistrationSet registrationSet = updateServiceRegistrations(activeMappings);
+
+        return registrationSet;
+
+    }
+
+
+    RegistrationSet updateServiceRegistrations(final Mapping[] newMappings) {
+
+        RegistrationSet result = new RegistrationSet();
+        // do not do anything if not activated
+        if (bundleContext == null) {
+            return result;
+        }
+
+        final SortedSet<Mapping> orderedNewMappings = new TreeSet<>(Arrays.asList(newMappings));
+        final SortedMap<Mapping, Registration> newRegistrations = new TreeMap<>();
+
+        // keep those that are still mapped
+        for (Map.Entry<Mapping, Registration> registrationEntry: activeRegistrations.entrySet()) {
+            boolean keepEntry = true;
+
+            if (!orderedNewMappings.contains(registrationEntry.getKey())) {
+                Registration registration = registrationEntry.getValue();
+
+                result.removed.add(registration);
+                keepEntry = false;
+            }
+
+            if (keepEntry) {
+                newRegistrations.put(registrationEntry.getKey(), registrationEntry.getValue());
+            }
+        }
+
+        // add those that are new
+        for (final Mapping mapping: orderedNewMappings) {
+            if (!newRegistrations.containsKey(mapping)) {
+                Registration registration = new Registration(mapping);
+                newRegistrations.put(mapping, registration);
+                result.added.add(registration);
+            }
+        }
+
+        activeRegistrations = newRegistrations;
+
+        return result;
+    }
+
+    private void executeServiceRegistrationsAsync(final RegistrationSet registrationSet) {
+
+        if (executorService == null) {
+            executeServiceRegistrations(registrationSet);
+        } else {
+            executorService.submit(new Runnable() {
+                @Override
+                public void run() {
+                    executeServiceRegistrations(registrationSet);
+                }
+            });
+        }
+    }
+
+
+    private void executeServiceRegistrations(final RegistrationSet registrationSet) {
+
+        if (registrationSet == null) {
+            return;
+        }
+
+        for (final Registration registration : registrationSet.removed) {
+
+
+            ServiceRegistration serviceRegistration = registration.setService(null);
+
+            if (serviceRegistration != null) {
+                try {
+                    serviceRegistration.unregister();
+                    log.debug("Unregistered ServiceUserMapped {}", registration.mapping);
+                } catch (final IllegalStateException e) {
+                    // this can happen on shutdown, therefore we just ignore it and don't log
+                }
+            }
+        }
+
+        BundleContext savedBundleContext = bundleContext;
+
+        if (savedBundleContext == null) {
+            return;
+        }
+
+        for (final Registration registration : registrationSet.added) {
+            Mapping mapping = registration.mapping;
+            final Dictionary<String, Object> properties = new Hashtable<>();
+            if (mapping.getSubServiceName() != null) {
+                properties.put(ServiceUserMapped.SUBSERVICENAME, mapping.getSubServiceName());
+            }
+
+            properties.put(Mapping.SERVICENAME, mapping.getServiceName());
+            final ServiceRegistration serviceRegistration = savedBundleContext.registerService(ServiceUserMappedImpl.SERVICEUSERMAPPED,
+                    new ServiceUserMappedImpl(), properties);
+
+            ServiceRegistration oldServiceRegistration = registration.setService(serviceRegistration);
+            log.debug("Activated ServiceUserMapped {}", registration.mapping);
+
+            if (oldServiceRegistration != null) {
+                try {
+                    oldServiceRegistration.unregister();
+                } catch (final IllegalStateException e) {
+                    // this can happen on shutdown, therefore we just ignore it and don't log
+                }
+            }
+        }
+
+    }
+
+    private String internalGetUserId(final String serviceName, final String subServiceName) {
+        log.debug(
+                "internalGetUserId: {} active mappings, looking for mapping for {}/{}",
+                new Object[] { this.activeMappings.length, serviceName, subServiceName });
+
+        for (final Mapping mapping : this.activeMappings) {
+            final String userId = mapping.map(serviceName, subServiceName);
+            if (userId != null) {
+                log.debug("Got userId [{}] from {}/{}", new Object[] { userId, serviceName, subServiceName });
+                return userId;
+            }
+        }
+
+        // second round without serviceInfo
+        log.debug(
+                "internalGetUserId: {} active mappings, looking for mapping for {}/<no subServiceName>",
+                this.activeMappings.length, serviceName);
+
+        for (Mapping mapping : this.activeMappings) {
+            final String userId = mapping.map(serviceName, null);
+            if (userId != null) {
+                log.debug("Got userId [{}] from {}/<no subServiceName>", userId, serviceName);
+                return userId;
+            }
+        }
+
+        // use default mapping if configured and no default user
+        if ( this.defaultUser == null || this.defaultUser.isEmpty() ) {
+            final String userName = "serviceuser--" + serviceName + (subServiceName == null ? "" : "--" + subServiceName);
+            log.debug("internalGetUserId: no mapping found, using default mapping [{}]", userName);
+            return userName;
+
+        }
+        log.debug("internalGetUserId: no mapping found, fallback to default user [{}]", this.defaultUser);
+        return this.defaultUser;
+    }
+
+    private boolean isValidUser(final String userId, final String serviceName, final String subServiceName) {
+        if (userId == null) {
+            log.debug("isValidUser: userId is null -> invalid");
+            return false;
+        }
+        if ( !validators.isEmpty() ) {
+            for (final ServiceUserValidator validator : validators) {
+                if ( validator.isValid(userId, serviceName, subServiceName) ) {
+                    log.debug("isValidUser: Validator {} accepts userId [{}] -> valid", validator, userId);
+                    return true;
+                }
+            }
+            log.debug("isValidUser: No validator accepted userId [{}] -> invalid", userId);
+            return false;
+        } else {
+            log.debug("isValidUser: No active validators for userId [{}] -> valid", userId);
+            return true;
+        }
+    }
+
+    static String getServiceName(final Bundle bundle) {
+        return bundle.getSymbolicName();
+    }
+
+    List<Mapping> getActiveMappings() {
+        return Collections.unmodifiableList(Arrays.asList(activeMappings));
+    }
+
+    class Registration {
+        private Mapping mapping;
+        private ServiceRegistration serviceRegistration;
+
+
+        Registration(Mapping mapping) {
+            this.mapping = mapping;
+            this.serviceRegistration = null;
+        }
+
+        synchronized ServiceRegistration setService(ServiceRegistration serviceRegistration) {
+            ServiceRegistration oldServiceRegistration = this.serviceRegistration;
+            this.serviceRegistration = serviceRegistration;
+            return oldServiceRegistration;
+        }
+    }
+
+    class RegistrationSet {
+        Collection<Registration> added = new ArrayList<>();
+        Collection<Registration> removed = new ArrayList<>();
+    }
+}
+
diff --git a/serviceusermapper/src/main/java/org/apache/sling/serviceusermapping/package-info.java b/serviceusermapper/src/main/java/org/apache/sling/serviceusermapping/package-info.java
new file mode 100644
index 0000000..69a2cdd
--- /dev/null
+++ b/serviceusermapper/src/main/java/org/apache/sling/serviceusermapping/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * 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.
+ */
+
+@org.osgi.annotation.versioning.Version("1.2.1")
+package org.apache.sling.serviceusermapping;
+
diff --git a/serviceusermapper/src/test/java/org/apache/sling/serviceusermapping/impl/MappingTest.java b/serviceusermapper/src/test/java/org/apache/sling/serviceusermapping/impl/MappingTest.java
new file mode 100644
index 0000000..7764d79
--- /dev/null
+++ b/serviceusermapper/src/test/java/org/apache/sling/serviceusermapping/impl/MappingTest.java
@@ -0,0 +1,141 @@
+/*
+ * 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.serviceusermapping.impl;
+
+import java.lang.reflect.Field;
+
+import junit.framework.TestCase;
+
+import org.apache.sling.serviceusermapping.impl.Mapping;
+import org.junit.Test;
+
+public class MappingTest {
+
+    @Test
+    public void test_constructor_null() {
+        try {
+            new Mapping(null);
+            TestCase.fail("NullPointerException expected");
+        } catch (NullPointerException npe) {
+            // expected
+        }
+    }
+
+    @Test
+    public void test_constructor_empty() {
+        try {
+            new Mapping("");
+            TestCase.fail("IllegalArgumentException expected");
+        } catch (IllegalArgumentException iae) {
+            // expected
+        }
+    }
+
+    @Test
+    public void test_constructor_missing_user_name() {
+        try {
+            new Mapping("serviceName");
+            TestCase.fail("IllegalArgumentException expected");
+        } catch (IllegalArgumentException iae) {
+            // expected
+        }
+
+        try {
+            new Mapping("serviceName=");
+            TestCase.fail("IllegalArgumentException expected");
+        } catch (IllegalArgumentException iae) {
+            // expected
+        }
+    }
+
+    @Test
+    public void test_constructor_missing_service_name() {
+        try {
+            new Mapping("=user");
+            TestCase.fail("IllegalArgumentException expected");
+        } catch (IllegalArgumentException iae) {
+            // expected
+        }
+    }
+
+    @Test
+    public void test_constructor_empty_service_info() {
+        try {
+            new Mapping("srv:=user");
+            TestCase.fail("IllegalArgumentException expected");
+        } catch (IllegalArgumentException iae) {
+            // expected
+        }
+    }
+
+    @Test
+    public void test_constructor_user_with_colon() {
+        new Mapping("srv=jcr:user");
+    }
+
+    @Test
+    public void test_constructor_and_map() {
+        assertMapping("service", null, "user");
+        assertMapping("service", "subServiceName", "user");
+    }
+
+    private void assertMapping(final String serviceName, final String subServiceName, final String userName) {
+        StringBuilder spec = new StringBuilder();
+        spec.append(serviceName);
+        if (subServiceName != null) {
+            spec.append(':').append(subServiceName);
+        }
+        spec.append('=').append(userName);
+
+        // spec analysis
+        final Mapping mapping = new Mapping(spec.toString());
+        TestCase.assertEquals(getField(mapping, "serviceName"), serviceName);
+        TestCase.assertEquals(getField(mapping, "subServiceName"), subServiceName);
+        TestCase.assertEquals(getField(mapping, "userName"), userName);
+
+        // mapping
+        TestCase.assertEquals(userName, mapping.map(serviceName, subServiceName));
+        if (subServiceName == null) {
+            // Mapping without subServiceName must not match request with any
+            // subServiceName
+            TestCase.assertNull(mapping.map(serviceName, subServiceName + "-garbage"));
+        } else {
+            // Mapping with subServiceName must not match request without
+            // subServiceName
+            TestCase.assertNull(mapping.map(serviceName, null));
+        }
+
+        // no match for different service name
+        TestCase.assertNull(mapping.map(serviceName + "-garbage", subServiceName));
+
+        // no match for null service name
+        TestCase.assertNull(mapping.map(null, subServiceName));
+    }
+
+    private String getField(final Object object, final String fieldName) {
+        try {
+            Field f = object.getClass().getDeclaredField(fieldName);
+            f.setAccessible(true);
+            return (String) f.get(object);
+        } catch (Exception e) {
+            TestCase.fail("Cannot get field " + fieldName + ": " + e.toString());
+            return null; // will not get here, quiesce compiler
+        }
+    }
+}
diff --git a/serviceusermapper/src/test/java/org/apache/sling/serviceusermapping/impl/ServiceUserMappedBundleFilterTest.java b/serviceusermapper/src/test/java/org/apache/sling/serviceusermapping/impl/ServiceUserMappedBundleFilterTest.java
new file mode 100644
index 0000000..9f12426
--- /dev/null
+++ b/serviceusermapper/src/test/java/org/apache/sling/serviceusermapping/impl/ServiceUserMappedBundleFilterTest.java
@@ -0,0 +1,115 @@
+/*
+ * 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.serviceusermapping.impl;
+
+
+import junit.framework.TestCase;
+import org.junit.Test;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceEvent;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.hooks.service.EventListenerHook;
+import org.osgi.framework.hooks.service.FindHook;
+import org.osgi.framework.hooks.service.ListenerHook;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+/**
+ * Test reference and bundle filtering based on <code>Mapping.SERVICENAME</code>
+ */
+public class ServiceUserMappedBundleFilterTest {
+
+    final static String BUNDLE1 = "bundle1";
+    final static String BUNDLE2 = "bundle2";
+
+
+    final static BundleContext bundleContext1;
+    final static BundleContext bundleContext2;
+
+    static  {
+        bundleContext1 = mock(BundleContext.class);
+        Bundle bundle1 = mock(Bundle.class);
+        when(bundleContext1.getBundle()).thenReturn(bundle1);
+        when(bundle1.getSymbolicName()).thenReturn(BUNDLE1);
+
+
+        bundleContext2 = mock(BundleContext.class);
+        Bundle bundle2 = mock(Bundle.class);
+        when(bundleContext2.getBundle()).thenReturn(bundle2);
+        when(bundle2.getSymbolicName()).thenReturn(BUNDLE2);
+
+    }
+
+
+
+
+    @Test
+    public void testEvent() {
+        Map<BundleContext, Collection<ListenerHook.ListenerInfo>> map = new HashMap<BundleContext, Collection<ListenerHook.ListenerInfo>>();
+
+        map.put(bundleContext1, new ArrayList<ListenerHook.ListenerInfo>());
+        map.put(bundleContext2, new ArrayList<ListenerHook.ListenerInfo>());
+
+        ServiceEvent serviceEvent = mock(ServiceEvent.class);
+        ServiceReference serviceReference = mock(ServiceReference.class);
+        when(serviceEvent.getServiceReference()).thenReturn(serviceReference);
+        when(serviceReference.getProperty(Constants.OBJECTCLASS)).thenReturn(new String[]{ServiceUserMappedImpl.SERVICEUSERMAPPED});
+        when(serviceReference.getProperty(Mapping.SERVICENAME)).thenReturn(BUNDLE1);
+
+
+        EventListenerHook eventListenerHook = new ServiceUserMappedBundleFilter();
+        eventListenerHook.event(serviceEvent, map);
+
+        TestCase.assertEquals(1, map.size());
+        TestCase.assertTrue(map.containsKey(bundleContext1));
+
+    }
+
+    @Test
+    public void testFind() {
+        List collection = new ArrayList<ServiceReference>();
+
+        ServiceReference serviceReference1 = mock(ServiceReference.class);
+        ServiceReference serviceReference2 = mock(ServiceReference.class);
+        collection.add(serviceReference1);
+        collection.add(serviceReference2);
+
+        when(serviceReference1.getProperty(Mapping.SERVICENAME)).thenReturn(BUNDLE1);
+        when(serviceReference1.getProperty(Constants.OBJECTCLASS)).thenReturn(new String[]{ServiceUserMappedImpl.SERVICEUSERMAPPED});
+
+        when(serviceReference2.getProperty(Mapping.SERVICENAME)).thenReturn(BUNDLE2);
+        when(serviceReference2.getProperty(Constants.OBJECTCLASS)).thenReturn(new String[]{ServiceUserMappedImpl.SERVICEUSERMAPPED});
+
+        FindHook findHook = new ServiceUserMappedBundleFilter();
+        findHook.find(bundleContext1, null, null, false, collection);
+
+        TestCase.assertEquals(1, collection.size());
+        TestCase.assertTrue(collection.contains(serviceReference1));
+    }
+}
diff --git a/serviceusermapper/src/test/java/org/apache/sling/serviceusermapping/impl/ServiceUserMapperImplTest.java b/serviceusermapper/src/test/java/org/apache/sling/serviceusermapping/impl/ServiceUserMapperImplTest.java
new file mode 100644
index 0000000..8d0b719
--- /dev/null
+++ b/serviceusermapper/src/test/java/org/apache/sling/serviceusermapping/impl/ServiceUserMapperImplTest.java
@@ -0,0 +1,380 @@
+/*
+ * 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.serviceusermapping.impl;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.util.Dictionary;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.sling.serviceusermapping.ServiceUserValidator;
+import org.junit.Test;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
+
+import junit.framework.TestCase;
+
+public class ServiceUserMapperImplTest {
+    private static final String BUNDLE_SYMBOLIC1 = "bundle1";
+
+    private static final String BUNDLE_SYMBOLIC2 = "bundle2";
+
+    private static final String BUNDLE_SYMBOLIC3 = "bundle3";
+
+    private static final String SUB = "sub";
+
+    private static final String NONE = "none";
+
+    private static final String SAMPLE = "sample";
+
+    private static final String ANOTHER = "another";
+
+    private static final String SAMPLE_SUB = "sample_sub";
+
+    private static final String ANOTHER_SUB = "another_sub";
+
+    private static final Bundle BUNDLE1;
+
+    private static final Bundle BUNDLE2;
+
+    private static final Bundle BUNDLE3;
+
+    static {
+        BUNDLE1 = mock(Bundle.class);
+        when(BUNDLE1.getSymbolicName()).thenReturn(BUNDLE_SYMBOLIC1);
+
+        BUNDLE2 = mock(Bundle.class);
+        when(BUNDLE2.getSymbolicName()).thenReturn(BUNDLE_SYMBOLIC2);
+
+        BUNDLE3 = mock(Bundle.class);
+        when(BUNDLE3.getSymbolicName()).thenReturn(BUNDLE_SYMBOLIC3);
+    }
+
+    @Test
+    public void test_getServiceUserID() {
+        ServiceUserMapperImpl.Config config = mock(ServiceUserMapperImpl.Config.class);
+        when(config.user_mapping()).thenReturn(new String[] {
+            BUNDLE_SYMBOLIC1 + "=" + SAMPLE, //
+            BUNDLE_SYMBOLIC2 + "=" + ANOTHER, //
+            BUNDLE_SYMBOLIC1 + ":" + SUB + "=" + SAMPLE_SUB, //
+            BUNDLE_SYMBOLIC2 + ":" + SUB + "=" + ANOTHER_SUB //
+        });
+        when(config.user_default()).thenReturn(NONE);
+        when(config.user_enable_default_mapping()).thenReturn(false);
+
+        final ServiceUserMapperImpl sum = new ServiceUserMapperImpl();
+        sum.configure(null, config);
+
+        TestCase.assertEquals(SAMPLE, sum.getServiceUserID(BUNDLE1, null));
+        TestCase.assertEquals(ANOTHER, sum.getServiceUserID(BUNDLE2, null));
+        TestCase.assertEquals(SAMPLE, sum.getServiceUserID(BUNDLE1, ""));
+        TestCase.assertEquals(ANOTHER, sum.getServiceUserID(BUNDLE2, ""));
+        TestCase.assertEquals(SAMPLE_SUB, sum.getServiceUserID(BUNDLE1, SUB));
+        TestCase.assertEquals(ANOTHER_SUB, sum.getServiceUserID(BUNDLE2, SUB));
+        TestCase.assertEquals(NONE, sum.getServiceUserID(BUNDLE3, null));
+        TestCase.assertEquals(NONE, sum.getServiceUserID(BUNDLE3, SUB));
+    }
+
+    @Test
+    public void test_getServiceUserIDwithDefaultMappingEnabledAndDefaultUser() {
+        ServiceUserMapperImpl.Config config = mock(ServiceUserMapperImpl.Config.class);
+        when(config.user_mapping()).thenReturn(new String[] {
+            BUNDLE_SYMBOLIC1 + "=" + SAMPLE, //
+            BUNDLE_SYMBOLIC2 + "=" + ANOTHER, //
+            BUNDLE_SYMBOLIC1 + ":" + SUB + "=" + SAMPLE_SUB, //
+            BUNDLE_SYMBOLIC2 + ":" + SUB + "=" + ANOTHER_SUB //
+        });
+        when(config.user_default()).thenReturn(NONE);
+        when(config.user_enable_default_mapping()).thenReturn(true);
+
+        final ServiceUserMapperImpl sum = new ServiceUserMapperImpl();
+        sum.configure(null, config);
+
+        TestCase.assertEquals(SAMPLE, sum.getServiceUserID(BUNDLE1, null));
+        TestCase.assertEquals(ANOTHER, sum.getServiceUserID(BUNDLE2, null));
+        TestCase.assertEquals(SAMPLE, sum.getServiceUserID(BUNDLE1, ""));
+        TestCase.assertEquals(ANOTHER, sum.getServiceUserID(BUNDLE2, ""));
+        TestCase.assertEquals(SAMPLE_SUB, sum.getServiceUserID(BUNDLE1, SUB));
+        TestCase.assertEquals(ANOTHER_SUB, sum.getServiceUserID(BUNDLE2, SUB));
+        TestCase.assertEquals(NONE, sum.getServiceUserID(BUNDLE3, null));
+        TestCase.assertEquals(NONE, sum.getServiceUserID(BUNDLE3, SUB));
+    }
+
+    @Test
+    public void test_getServiceUserIDwithDefaultMappingEnabledAndNoDefaultUser() {
+        ServiceUserMapperImpl.Config config = mock(ServiceUserMapperImpl.Config.class);
+        when(config.user_mapping()).thenReturn(new String[] {
+            BUNDLE_SYMBOLIC1 + "=" + SAMPLE, //
+            BUNDLE_SYMBOLIC2 + "=" + ANOTHER, //
+            BUNDLE_SYMBOLIC1 + ":" + SUB + "=" + SAMPLE_SUB, //
+            BUNDLE_SYMBOLIC2 + ":" + SUB + "=" + ANOTHER_SUB //
+        });
+        when(config.user_default()).thenReturn(null);
+        when(config.user_enable_default_mapping()).thenReturn(true);
+
+        final ServiceUserMapperImpl sum = new ServiceUserMapperImpl();
+        sum.configure(null, config);
+
+        TestCase.assertEquals(SAMPLE, sum.getServiceUserID(BUNDLE1, null));
+        TestCase.assertEquals(ANOTHER, sum.getServiceUserID(BUNDLE2, null));
+        TestCase.assertEquals(SAMPLE, sum.getServiceUserID(BUNDLE1, ""));
+        TestCase.assertEquals(ANOTHER, sum.getServiceUserID(BUNDLE2, ""));
+        TestCase.assertEquals(SAMPLE_SUB, sum.getServiceUserID(BUNDLE1, SUB));
+        TestCase.assertEquals(ANOTHER_SUB, sum.getServiceUserID(BUNDLE2, SUB));
+        TestCase.assertEquals("serviceuser--" + BUNDLE_SYMBOLIC3, sum.getServiceUserID(BUNDLE3, null));
+        TestCase.assertEquals("serviceuser--" + BUNDLE_SYMBOLIC3 + "--" + SUB, sum.getServiceUserID(BUNDLE3, SUB));
+    }
+
+    @Test
+    public void test_getServiceUserID_WithServiceUserValidator() {
+        ServiceUserMapperImpl.Config config = mock(ServiceUserMapperImpl.Config.class);
+        when(config.user_mapping()).thenReturn(new String[] {
+                BUNDLE_SYMBOLIC1 + "=" + SAMPLE, //
+                BUNDLE_SYMBOLIC2 + "=" + ANOTHER, //
+                BUNDLE_SYMBOLIC1 + ":" + SUB + "=" + SAMPLE_SUB, //
+                BUNDLE_SYMBOLIC2 + ":" + SUB + "=" + ANOTHER_SUB //
+        });
+        when(config.user_default()).thenReturn(NONE);
+        when(config.user_enable_default_mapping()).thenReturn(false);
+
+        final ServiceUserMapperImpl sum = new ServiceUserMapperImpl();
+        sum.configure(null, config);
+        ServiceUserValidator serviceUserValidator = new ServiceUserValidator() {
+
+            @Override
+            public boolean isValid(String serviceUserId, String serviceName,
+                    String subServiceName) {
+                if (SAMPLE.equals(serviceUserId)) {
+                    return false;
+                }
+                return true;
+            }
+        };
+        sum.bindServiceUserValidator(serviceUserValidator);
+
+        TestCase.assertEquals(null, sum.getServiceUserID(BUNDLE1, null));
+        TestCase.assertEquals(ANOTHER, sum.getServiceUserID(BUNDLE2, null));
+        TestCase.assertEquals(null, sum.getServiceUserID(BUNDLE1, ""));
+        TestCase.assertEquals(ANOTHER, sum.getServiceUserID(BUNDLE2, ""));
+        TestCase.assertEquals(SAMPLE_SUB, sum.getServiceUserID(BUNDLE1, SUB));
+        TestCase.assertEquals(ANOTHER_SUB, sum.getServiceUserID(BUNDLE2, SUB));
+    }
+
+    @Test
+    public void test_amendment() {
+        ServiceUserMapperImpl.Config config = mock(ServiceUserMapperImpl.Config.class);
+        when(config.user_mapping()).thenReturn(new String[] {
+                BUNDLE_SYMBOLIC1 + "=" + SAMPLE, //
+                BUNDLE_SYMBOLIC1 + ":" + SUB + "=" + SAMPLE_SUB, //
+        });
+        when(config.user_default()).thenReturn(NONE);
+        when(config.user_enable_default_mapping()).thenReturn(false);
+
+        final ServiceUserMapperImpl sum = new ServiceUserMapperImpl();
+        sum.configure(null, config);
+        final MappingConfigAmendment mca1 = new MappingConfigAmendment();
+
+        MappingConfigAmendment.Config mca1Config = mock(MappingConfigAmendment.Config.class);
+        when(mca1Config.user_mapping()).thenReturn(new String[] {BUNDLE_SYMBOLIC2 + "=" + ANOTHER});
+        when(mca1Config.service_ranking()).thenReturn(100);
+        Map<String, Object> mca1ConfigMap = new HashMap<>();
+        mca1ConfigMap.put("user.mapping", mca1Config.user_mapping());
+        mca1ConfigMap.put("service.ranking", mca1Config.service_ranking());
+        mca1ConfigMap.put("service.id", 1L);
+
+        mca1.configure(mca1Config);
+        sum.bindAmendment(mca1, mca1ConfigMap);
+        final MappingConfigAmendment mca2 = new MappingConfigAmendment();
+
+        MappingConfigAmendment.Config mca2Config = mock(MappingConfigAmendment.Config.class);
+        when(mca2Config.user_mapping()).thenReturn(new String[] {BUNDLE_SYMBOLIC2 + ":" + SUB + "=" + ANOTHER_SUB});
+        when(mca2Config.service_ranking()).thenReturn(200);
+        Map<String, Object> mca2ConfigMap = new HashMap<>();
+        mca2ConfigMap.put("user.mapping", mca2Config.user_mapping());
+        mca2ConfigMap.put("service.ranking", mca2Config.service_ranking());
+        mca2ConfigMap.put("service.id", 2L);
+
+        mca2.configure(mca2Config);
+        sum.bindAmendment(mca2, mca2ConfigMap);
+
+        TestCase.assertEquals(SAMPLE, sum.getServiceUserID(BUNDLE1, null));
+        TestCase.assertEquals(ANOTHER, sum.getServiceUserID(BUNDLE2, null));
+        TestCase.assertEquals(SAMPLE, sum.getServiceUserID(BUNDLE1, ""));
+        TestCase.assertEquals(ANOTHER, sum.getServiceUserID(BUNDLE2, ""));
+        TestCase.assertEquals(SAMPLE_SUB, sum.getServiceUserID(BUNDLE1, SUB));
+        TestCase.assertEquals(ANOTHER_SUB, sum.getServiceUserID(BUNDLE2, SUB));
+    }
+
+    @Test
+    public void test_amendmentOverlap() {
+        ServiceUserMapperImpl.Config config = mock(ServiceUserMapperImpl.Config.class);
+        when(config.user_mapping()).thenReturn(new String[] {});
+        when(config.user_default()).thenReturn(NONE);
+        when(config.user_enable_default_mapping()).thenReturn(false);
+
+        final ServiceUserMapperImpl sum = new ServiceUserMapperImpl();
+        sum.configure(null, config);
+
+        final MappingConfigAmendment mca1 = new MappingConfigAmendment();
+
+        MappingConfigAmendment.Config mca1Config = mock(MappingConfigAmendment.Config.class);
+        when(mca1Config.user_mapping()).thenReturn(new String[] {BUNDLE_SYMBOLIC2 + "=" + ANOTHER});
+        when(mca1Config.service_ranking()).thenReturn(100);
+        Map<String, Object> mca1ConfigMap = new HashMap<>();
+        mca1ConfigMap.put("user.mapping", mca1Config.user_mapping());
+        mca1ConfigMap.put("service.ranking", mca1Config.service_ranking());
+
+        mca1.configure(mca1Config);
+        final MappingConfigAmendment mca2 = new MappingConfigAmendment();
+
+        MappingConfigAmendment.Config mca2Config = mock(MappingConfigAmendment.Config.class);
+        when(mca2Config.user_mapping()).thenReturn(new String[] {BUNDLE_SYMBOLIC2 + "=" + ANOTHER_SUB});
+        when(mca2Config.service_ranking()).thenReturn(200);
+        Map<String, Object> mca2ConfigMap = new HashMap<>();
+        mca2ConfigMap.put("user.mapping", mca2Config.user_mapping());
+        mca2ConfigMap.put("service.ranking", mca2Config.service_ranking());
+
+        mca2.configure(mca2Config);
+
+        sum.bindAmendment(mca1, mca1ConfigMap);
+        sum.bindAmendment(mca2, mca2ConfigMap);
+
+        TestCase.assertEquals(ANOTHER_SUB, sum.getServiceUserID(BUNDLE2, ""));
+    }
+
+
+
+    @Test
+    public void test_amendmentServiceUserMapping() {
+
+        ServiceUserMapperImpl.Config config = mock(ServiceUserMapperImpl.Config.class);
+        when(config.user_mapping()).thenReturn(new String[] {
+                BUNDLE_SYMBOLIC1 + "=" + SAMPLE, //
+                BUNDLE_SYMBOLIC1 + ":" + SUB + "=" + SAMPLE_SUB, //
+                });
+        when(config.user_default()).thenReturn(NONE);
+        when(config.user_enable_default_mapping()).thenReturn(false);
+
+        final ServiceUserMapperImpl sum = new ServiceUserMapperImpl();
+        sum.registerAsync = false;
+        final ServiceRegistrationContextHelper context = new ServiceRegistrationContextHelper();
+        sum.configure(context.getBundleContext(), config);
+
+        TestCase.assertEquals(2, context.getRegistrations(ServiceUserMappedImpl.SERVICEUSERMAPPED).size());
+
+        final MappingConfigAmendment mca1 = new MappingConfigAmendment();
+
+        MappingConfigAmendment.Config mca1Config = mock(MappingConfigAmendment.Config.class);
+        when(mca1Config.user_mapping()).thenReturn(new String[] {BUNDLE_SYMBOLIC2 + "=" + ANOTHER});
+        when(mca1Config.service_ranking()).thenReturn(100);
+        Map<String, Object> mca1ConfigMap = new HashMap<>();
+        mca1ConfigMap.put("user.mapping", mca1Config.user_mapping());
+        mca1ConfigMap.put("service.ranking", mca1Config.service_ranking());
+        mca1ConfigMap.put("service.id", 1L);
+
+        mca1.configure(mca1Config);
+        sum.bindAmendment(mca1, mca1ConfigMap);
+
+        TestCase.assertEquals(3, context.getRegistrations(ServiceUserMappedImpl.SERVICEUSERMAPPED).size());
+
+        final MappingConfigAmendment mca2 = new MappingConfigAmendment();
+
+        MappingConfigAmendment.Config mca2Config = mock(MappingConfigAmendment.Config.class);
+        when(mca2Config.user_mapping()).thenReturn(new String[] {BUNDLE_SYMBOLIC2 + ":" + SUB + "=" + ANOTHER_SUB});
+        when(mca2Config.service_ranking()).thenReturn(200);
+        Map<String, Object> mca2ConfigMap = new HashMap<>();
+        mca2ConfigMap.put("user.mapping", mca2Config.user_mapping());
+        mca2ConfigMap.put("service.ranking", mca2Config.service_ranking());
+        mca2ConfigMap.put("service.id", 2L);
+
+        mca2.configure(mca2Config);
+        sum.bindAmendment(mca2, mca2ConfigMap);
+
+        TestCase.assertEquals(4, context.getRegistrations(ServiceUserMappedImpl.SERVICEUSERMAPPED).size());
+
+        sum.unbindAmendment(mca1, mca1ConfigMap);
+
+        TestCase.assertEquals(3, context.getRegistrations(ServiceUserMappedImpl.SERVICEUSERMAPPED).size());
+    }
+
+
+    private class ServiceRegistrationContextHelper {
+
+
+        final BundleContext bundleContext = mock(BundleContext.class);
+        final Map<String, Map<Object, Dictionary>> registrations = new HashMap<>();
+
+        public ServiceRegistrationContextHelper() {
+            when(bundleContext.registerService(any(String.class), any(Object.class), any(Dictionary.class)))
+                    .then(new Answer<ServiceRegistration>() {
+                        @Override
+                        public ServiceRegistration answer(InvocationOnMock invocationOnMock) throws Throwable {
+
+                            Object[] arguments = invocationOnMock.getArguments();
+                            return registerService((String) arguments[0], arguments[1], (Dictionary) arguments[2]);
+                        }
+                    });
+        }
+
+        private ServiceRegistration registerService(String string, Object o, Dictionary dictionary) {
+            if (!registrations.containsKey(string)) {
+                registrations.put(string, new HashMap<Object, Dictionary>());
+            }
+            final Map<Object, Dictionary> serviceRegistrations = registrations.get(string);
+            serviceRegistrations.put(o, dictionary);
+
+            final Object registeredObject = o;
+
+
+            return new ServiceRegistration() {
+                @Override
+                public ServiceReference getReference() {
+                    return null;
+                }
+
+                @Override
+                public void setProperties(Dictionary dictionary) {
+
+                }
+
+                @Override
+                public void unregister() {
+                    serviceRegistrations.remove(registeredObject);
+                }
+            };
+        }
+
+        public Map<Object, Dictionary> getRegistrations(String name) {
+            return registrations.get(name);
+        }
+
+        public BundleContext getBundleContext() {
+            return bundleContext;
+        }
+
+    }
+
+}

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-serviceusermapper] 04/05: [maven-release-plugin] copy for tag org.apache.sling.serviceusermapper-1.3.0

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.serviceusermapper-1.3.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-serviceusermapper.git

commit a2497524dd3f0a56db06f4b49776423f884b22d1
Author: Carsten Ziegeler <cz...@apache.org>
AuthorDate: Thu Apr 6 07:39:34 2017 +0000

    [maven-release-plugin] copy for tag org.apache.sling.serviceusermapper-1.3.0
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/tags/org.apache.sling.serviceusermapper-1.3.0@1790354 13f79535-47bb-0310-9956-ffa450edef68

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-serviceusermapper] 02/05: SLING-6772 : Provide default mapping for service users

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.serviceusermapper-1.3.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-serviceusermapper.git

commit 13fa883a1f9885705d344ab702cf8f544360480d
Author: Carsten Ziegeler <cz...@apache.org>
AuthorDate: Thu Apr 6 06:03:13 2017 +0000

    SLING-6772 : Provide default mapping for service users
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/serviceusermapper@1790344 13f79535-47bb-0310-9956-ffa450edef68
---
 .../impl/ServiceUserMapperImpl.java                | 40 +++++++++----
 .../impl/ServiceUserMapperImplTest.java            | 65 +++++++++++++++++++++-
 2 files changed, 92 insertions(+), 13 deletions(-)

diff --git a/src/main/java/org/apache/sling/serviceusermapping/impl/ServiceUserMapperImpl.java b/src/main/java/org/apache/sling/serviceusermapping/impl/ServiceUserMapperImpl.java
index 75c8599..46f266e 100644
--- a/src/main/java/org/apache/sling/serviceusermapping/impl/ServiceUserMapperImpl.java
+++ b/src/main/java/org/apache/sling/serviceusermapping/impl/ServiceUserMapperImpl.java
@@ -72,8 +72,14 @@ public class ServiceUserMapperImpl implements ServiceUserMapper {
 
         @AttributeDefinition(name = "Default User",
             description = "The name of the user to use as the default if no service mapping"
-                + "applies. If this property is missing or empty no default user is defined.")
+                + " applies. If this property is missing or empty no default user is defined.")
         String user_default();
+
+        @AttributeDefinition(name = "Default Mapping",
+                description = "If enabled and no mapping for a requested service user exists and no " +
+                      " default user is defined, a " +
+                     "default mapping is applied which uses the service user \"serviceuser@\" + {bundleId} + [\":\" + subServiceName]")
+        boolean user_default_mapping() default true;
     }
 
     /** default log */
@@ -83,13 +89,15 @@ public class ServiceUserMapperImpl implements ServiceUserMapper {
 
     private String defaultUser;
 
-    private Map<Long, MappingConfigAmendment> amendments = new HashMap<Long, MappingConfigAmendment>();
+    private boolean useDefaultMapping;
+
+    private Map<Long, MappingConfigAmendment> amendments = new HashMap<>();
 
     private Mapping[] activeMappings = new Mapping[0];
 
-    private final List<ServiceUserValidator> validators = new CopyOnWriteArrayList<ServiceUserValidator>();
+    private final List<ServiceUserValidator> validators = new CopyOnWriteArrayList<>();
 
-    private SortedMap<Mapping, Registration> activeRegistrations = new TreeMap<Mapping, Registration>();
+    private SortedMap<Mapping, Registration> activeRegistrations = new TreeMap<>();
 
     private BundleContext bundleContext;
 
@@ -107,7 +115,7 @@ public class ServiceUserMapperImpl implements ServiceUserMapper {
         final String[] props = config.user_mapping();
 
         if ( props != null ) {
-            final ArrayList<Mapping> mappings = new ArrayList<Mapping>(props.length);
+            final ArrayList<Mapping> mappings = new ArrayList<>(props.length);
             for (final String prop : props) {
                 if (prop != null && prop.trim().length() > 0 ) {
                     try {
@@ -124,6 +132,7 @@ public class ServiceUserMapperImpl implements ServiceUserMapper {
             this.globalServiceUserMappings = new Mapping[0];
         }
         this.defaultUser = config.user_default();
+        this.useDefaultMapping = config.user_default_mapping();
 
         RegistrationSet registrationSet = null;
         this.bundleContext = bundleContext;
@@ -208,13 +217,13 @@ public class ServiceUserMapperImpl implements ServiceUserMapper {
     }
 
     protected RegistrationSet updateMappings() {
-        final List<MappingConfigAmendment> sortedMappings = new ArrayList<MappingConfigAmendment>();
+        final List<MappingConfigAmendment> sortedMappings = new ArrayList<>();
         for(final MappingConfigAmendment amendment : this.amendments.values() ) {
             sortedMappings.add(amendment);
         }
         Collections.sort(sortedMappings);
 
-        final List<Mapping> mappings = new ArrayList<Mapping>();
+        final List<Mapping> mappings = new ArrayList<>();
         for(final Mapping m : this.globalServiceUserMappings) {
             mappings.add(m);
         }
@@ -242,8 +251,8 @@ public class ServiceUserMapperImpl implements ServiceUserMapper {
             return result;
         }
 
-        final SortedSet<Mapping> orderedNewMappings = new TreeSet<Mapping>(Arrays.asList(newMappings));
-        final SortedMap<Mapping, Registration> newRegistrations = new TreeMap<Mapping, Registration>();
+        final SortedSet<Mapping> orderedNewMappings = new TreeSet<>(Arrays.asList(newMappings));
+        final SortedMap<Mapping, Registration> newRegistrations = new TreeMap<>();
 
         // keep those that are still mapped
         for (Map.Entry<Mapping, Registration> registrationEntry: activeRegistrations.entrySet()) {
@@ -319,7 +328,7 @@ public class ServiceUserMapperImpl implements ServiceUserMapper {
 
         for (final Registration registration : registrationSet.added) {
             Mapping mapping = registration.mapping;
-            final Dictionary<String, Object> properties = new Hashtable<String, Object>();
+            final Dictionary<String, Object> properties = new Hashtable<>();
             if (mapping.getSubServiceName() != null) {
                 properties.put(ServiceUserMapped.SUBSERVICENAME, mapping.getSubServiceName());
             }
@@ -368,6 +377,13 @@ public class ServiceUserMapperImpl implements ServiceUserMapper {
             }
         }
 
+        // use default mapping if configured and no default user
+        if ( this.defaultUser == null || this.defaultUser.isEmpty() ) {
+            final String userName = "serviceuser@" + serviceName + (subServiceName == null ? "" : ":" + subServiceName);
+            log.debug("internalGetUserId: no mapping found, using default mapping [{}]", userName);
+            return userName;
+
+        }
         log.debug("internalGetUserId: no mapping found, fallback to default user [{}]", this.defaultUser);
         return this.defaultUser;
     }
@@ -418,8 +434,8 @@ public class ServiceUserMapperImpl implements ServiceUserMapper {
     }
 
     class RegistrationSet {
-        Collection<Registration> added = new ArrayList<Registration>();
-        Collection<Registration> removed = new ArrayList<Registration>();
+        Collection<Registration> added = new ArrayList<>();
+        Collection<Registration> removed = new ArrayList<>();
     }
 }
 
diff --git a/src/test/java/org/apache/sling/serviceusermapping/impl/ServiceUserMapperImplTest.java b/src/test/java/org/apache/sling/serviceusermapping/impl/ServiceUserMapperImplTest.java
index 199f6f7..97917a4 100644
--- a/src/test/java/org/apache/sling/serviceusermapping/impl/ServiceUserMapperImplTest.java
+++ b/src/test/java/org/apache/sling/serviceusermapping/impl/ServiceUserMapperImplTest.java
@@ -42,6 +42,8 @@ public class ServiceUserMapperImplTest {
 
     private static final String BUNDLE_SYMBOLIC2 = "bundle2";
 
+    private static final String BUNDLE_SYMBOLIC3 = "bundle3";
+
     private static final String SUB = "sub";
 
     private static final String NONE = "none";
@@ -58,6 +60,7 @@ public class ServiceUserMapperImplTest {
 
     private static final Bundle BUNDLE2;
 
+    private static final Bundle BUNDLE3;
 
     static {
         BUNDLE1 = mock(Bundle.class);
@@ -65,6 +68,9 @@ public class ServiceUserMapperImplTest {
 
         BUNDLE2 = mock(Bundle.class);
         when(BUNDLE2.getSymbolicName()).thenReturn(BUNDLE_SYMBOLIC2);
+
+        BUNDLE3 = mock(Bundle.class);
+        when(BUNDLE3.getSymbolicName()).thenReturn(BUNDLE_SYMBOLIC3);
     }
 
     @Test
@@ -77,6 +83,57 @@ public class ServiceUserMapperImplTest {
             BUNDLE_SYMBOLIC2 + ":" + SUB + "=" + ANOTHER_SUB //
         });
         when(config.user_default()).thenReturn(NONE);
+        when(config.user_default_mapping()).thenReturn(false);
+
+        final ServiceUserMapperImpl sum = new ServiceUserMapperImpl();
+        sum.configure(null, config);
+
+        TestCase.assertEquals(SAMPLE, sum.getServiceUserID(BUNDLE1, null));
+        TestCase.assertEquals(ANOTHER, sum.getServiceUserID(BUNDLE2, null));
+        TestCase.assertEquals(SAMPLE, sum.getServiceUserID(BUNDLE1, ""));
+        TestCase.assertEquals(ANOTHER, sum.getServiceUserID(BUNDLE2, ""));
+        TestCase.assertEquals(SAMPLE_SUB, sum.getServiceUserID(BUNDLE1, SUB));
+        TestCase.assertEquals(ANOTHER_SUB, sum.getServiceUserID(BUNDLE2, SUB));
+        TestCase.assertEquals(NONE, sum.getServiceUserID(BUNDLE3, null));
+        TestCase.assertEquals(NONE, sum.getServiceUserID(BUNDLE3, SUB));
+    }
+
+    @Test
+    public void test_getServiceUserIDwithDefaultMappingEnabledAndDefaultUser() {
+        ServiceUserMapperImpl.Config config = mock(ServiceUserMapperImpl.Config.class);
+        when(config.user_mapping()).thenReturn(new String[] {
+            BUNDLE_SYMBOLIC1 + "=" + SAMPLE, //
+            BUNDLE_SYMBOLIC2 + "=" + ANOTHER, //
+            BUNDLE_SYMBOLIC1 + ":" + SUB + "=" + SAMPLE_SUB, //
+            BUNDLE_SYMBOLIC2 + ":" + SUB + "=" + ANOTHER_SUB //
+        });
+        when(config.user_default()).thenReturn(NONE);
+        when(config.user_default_mapping()).thenReturn(true);
+
+        final ServiceUserMapperImpl sum = new ServiceUserMapperImpl();
+        sum.configure(null, config);
+
+        TestCase.assertEquals(SAMPLE, sum.getServiceUserID(BUNDLE1, null));
+        TestCase.assertEquals(ANOTHER, sum.getServiceUserID(BUNDLE2, null));
+        TestCase.assertEquals(SAMPLE, sum.getServiceUserID(BUNDLE1, ""));
+        TestCase.assertEquals(ANOTHER, sum.getServiceUserID(BUNDLE2, ""));
+        TestCase.assertEquals(SAMPLE_SUB, sum.getServiceUserID(BUNDLE1, SUB));
+        TestCase.assertEquals(ANOTHER_SUB, sum.getServiceUserID(BUNDLE2, SUB));
+        TestCase.assertEquals(NONE, sum.getServiceUserID(BUNDLE3, null));
+        TestCase.assertEquals(NONE, sum.getServiceUserID(BUNDLE3, SUB));
+    }
+
+    @Test
+    public void test_getServiceUserIDwithDefaultMappingEnabledAndNoDefaultUser() {
+        ServiceUserMapperImpl.Config config = mock(ServiceUserMapperImpl.Config.class);
+        when(config.user_mapping()).thenReturn(new String[] {
+            BUNDLE_SYMBOLIC1 + "=" + SAMPLE, //
+            BUNDLE_SYMBOLIC2 + "=" + ANOTHER, //
+            BUNDLE_SYMBOLIC1 + ":" + SUB + "=" + SAMPLE_SUB, //
+            BUNDLE_SYMBOLIC2 + ":" + SUB + "=" + ANOTHER_SUB //
+        });
+        when(config.user_default()).thenReturn(null);
+        when(config.user_default_mapping()).thenReturn(true);
 
         final ServiceUserMapperImpl sum = new ServiceUserMapperImpl();
         sum.configure(null, config);
@@ -87,6 +144,8 @@ public class ServiceUserMapperImplTest {
         TestCase.assertEquals(ANOTHER, sum.getServiceUserID(BUNDLE2, ""));
         TestCase.assertEquals(SAMPLE_SUB, sum.getServiceUserID(BUNDLE1, SUB));
         TestCase.assertEquals(ANOTHER_SUB, sum.getServiceUserID(BUNDLE2, SUB));
+        TestCase.assertEquals("serviceuser@" + BUNDLE_SYMBOLIC3, sum.getServiceUserID(BUNDLE3, null));
+        TestCase.assertEquals("serviceuser@" + BUNDLE_SYMBOLIC3 + ":" + SUB, sum.getServiceUserID(BUNDLE3, SUB));
     }
 
     @Test
@@ -99,6 +158,7 @@ public class ServiceUserMapperImplTest {
                 BUNDLE_SYMBOLIC2 + ":" + SUB + "=" + ANOTHER_SUB //
         });
         when(config.user_default()).thenReturn(NONE);
+        when(config.user_default_mapping()).thenReturn(false);
 
         final ServiceUserMapperImpl sum = new ServiceUserMapperImpl();
         sum.configure(null, config);
@@ -131,6 +191,7 @@ public class ServiceUserMapperImplTest {
                 BUNDLE_SYMBOLIC1 + ":" + SUB + "=" + SAMPLE_SUB, //
         });
         when(config.user_default()).thenReturn(NONE);
+        when(config.user_default_mapping()).thenReturn(false);
 
         final ServiceUserMapperImpl sum = new ServiceUserMapperImpl();
         sum.configure(null, config);
@@ -172,6 +233,7 @@ public class ServiceUserMapperImplTest {
         ServiceUserMapperImpl.Config config = mock(ServiceUserMapperImpl.Config.class);
         when(config.user_mapping()).thenReturn(new String[] {});
         when(config.user_default()).thenReturn(NONE);
+        when(config.user_default_mapping()).thenReturn(false);
 
         final ServiceUserMapperImpl sum = new ServiceUserMapperImpl();
         sum.configure(null, config);
@@ -214,6 +276,7 @@ public class ServiceUserMapperImplTest {
                 BUNDLE_SYMBOLIC1 + ":" + SUB + "=" + SAMPLE_SUB, //
                 });
         when(config.user_default()).thenReturn(NONE);
+        when(config.user_default_mapping()).thenReturn(false);
 
         final ServiceUserMapperImpl sum = new ServiceUserMapperImpl();
         sum.registerAsync = false;
@@ -262,7 +325,7 @@ public class ServiceUserMapperImplTest {
 
 
         final BundleContext bundleContext = mock(BundleContext.class);
-        final Map<String, Map<Object, Dictionary>> registrations = new HashMap<String, Map<Object, Dictionary>>();
+        final Map<String, Map<Object, Dictionary>> registrations = new HashMap<>();
 
         public ServiceRegistrationContextHelper() {
             when(bundleContext.registerService(any(String.class), any(Object.class), any(Dictionary.class)))

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.