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 <[email protected]> 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. + */ + [email protected]("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 "[email protected]" <[email protected]>.
