You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@aries.apache.org by cs...@apache.org on 2016/03/11 20:43:25 UTC

[26/50] [abbrv] aries-rsa git commit: Switch project setup to Aries

http://git-wip-us.apache.org/repos/asf/aries-rsa/blob/d73a3a7f/rsa/src/main/java/org/apache/cxf/dosgi/dsw/service/RemoteServiceAdminCore.java
----------------------------------------------------------------------
diff --git a/rsa/src/main/java/org/apache/cxf/dosgi/dsw/service/RemoteServiceAdminCore.java b/rsa/src/main/java/org/apache/cxf/dosgi/dsw/service/RemoteServiceAdminCore.java
new file mode 100644
index 0000000..356de5e
--- /dev/null
+++ b/rsa/src/main/java/org/apache/cxf/dosgi/dsw/service/RemoteServiceAdminCore.java
@@ -0,0 +1,587 @@
+/**
+ * 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.cxf.dosgi.dsw.service;
+
+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.HashSet;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.cxf.dosgi.dsw.api.DistributionProvider;
+import org.apache.cxf.dosgi.dsw.api.Endpoint;
+import org.apache.cxf.dosgi.dsw.api.EndpointHelper;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceEvent;
+import org.osgi.framework.ServiceListener;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.remoteserviceadmin.EndpointDescription;
+import org.osgi.service.remoteserviceadmin.ExportReference;
+import org.osgi.service.remoteserviceadmin.ExportRegistration;
+import org.osgi.service.remoteserviceadmin.ImportReference;
+import org.osgi.service.remoteserviceadmin.ImportRegistration;
+import org.osgi.service.remoteserviceadmin.RemoteConstants;
+import org.osgi.service.remoteserviceadmin.RemoteServiceAdmin;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class RemoteServiceAdminCore implements RemoteServiceAdmin {
+
+    private static final Logger LOG = LoggerFactory.getLogger(RemoteServiceAdminCore.class);
+
+    private final Map<Map<String, Object>, Collection<ExportRegistration>> exportedServices
+        = new LinkedHashMap<Map<String, Object>, Collection<ExportRegistration>>();
+    private final Map<EndpointDescription, Collection<ImportRegistrationImpl>> importedServices
+        = new LinkedHashMap<EndpointDescription, Collection<ImportRegistrationImpl>>();
+
+    // Is stored in exportedServices while the export is in progress as a marker
+    private final List<ExportRegistration> exportInProgress = Collections.emptyList();
+
+    private final BundleContext bctx;
+    private final EventProducer eventProducer;
+    private final ServiceListener exportedServiceListener;
+    private DistributionProvider provider;
+    private BundleContext apictx;
+
+    public RemoteServiceAdminCore(BundleContext context, BundleContext apiContext, DistributionProvider provider) {
+        this.bctx = context;
+        this.apictx = apiContext;
+        this.eventProducer = new EventProducer(bctx);
+        this.provider = provider;
+        // listen for exported services being unregistered so we can close the export
+        this.exportedServiceListener = new ServiceListener() {
+            public void serviceChanged(ServiceEvent event) {
+                if (event.getType() == ServiceEvent.UNREGISTERING) {
+                    removeServiceExports(event.getServiceReference());
+                }
+            }
+        };
+        try {
+            String filter = "(" + RemoteConstants.SERVICE_EXPORTED_INTERFACES + "=*)";
+            context.addServiceListener(exportedServiceListener, filter);
+        } catch (InvalidSyntaxException ise) {
+            throw new RuntimeException(ise); // can never happen
+        }
+    }
+
+    @Override
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    public List<ExportRegistration> exportService(ServiceReference serviceReference, Map additionalProperties)
+        throws IllegalArgumentException, UnsupportedOperationException {
+        Map<String, Object> serviceProperties = getProperties(serviceReference);
+        if (additionalProperties != null) {
+            overlayProperties(serviceProperties, additionalProperties);
+        }
+        Map<String, Object> key = makeKey(serviceProperties);
+
+        List<String> interfaceNames = getInterfaceNames(serviceProperties);
+
+        if (isImportedService(serviceReference)) {
+            return Collections.emptyList();
+        }
+
+        List<ExportRegistration> exportRegs = getExistingAndLock(key, interfaceNames);
+        if (exportRegs != null) {
+            return exportRegs;
+        }
+
+        try {
+            ExportRegistration exportReg = exportService(interfaceNames, serviceReference, serviceProperties);
+            exportRegs = new ArrayList<>();
+            exportRegs.add(exportReg);
+            store(key, exportRegs);
+            return exportRegs;
+        } finally {
+            unlock(key);
+        }
+    }
+
+    private void store(Map<String, Object> key, List<ExportRegistration> exportRegs) {
+        if (!exportRegs.isEmpty()) {
+            // enlist initial export registrations in global list of exportRegistrations
+            synchronized (exportedServices) {
+                exportedServices.put(key, new ArrayList<ExportRegistration>(exportRegs));
+            }
+            eventProducer.publishNotification(exportRegs);
+        }
+    }
+
+    private void unlock(Map<String, Object> key) {
+        synchronized (exportedServices) {
+            if (exportedServices.get(key) == exportInProgress) {
+                exportedServices.remove(key);
+            }
+            exportedServices.notifyAll(); // in any case, always notify waiting threads
+        }
+    }
+
+    private List<ExportRegistration> getExistingAndLock(Map<String, Object> key, List<String> interfaces) {
+        synchronized (exportedServices) {
+            // check if it is already exported...
+            Collection<ExportRegistration> existingRegs = exportedServices.get(key);
+
+            // if the export is already in progress, wait for it to be complete
+            while (existingRegs == exportInProgress) {
+                try {
+                    exportedServices.wait();
+                    existingRegs = exportedServices.get(key);
+                } catch (InterruptedException ie) {
+                    LOG.debug("interrupted while waiting for export in progress");
+                    return Collections.emptyList();
+                }
+            }
+
+            // if the export is complete, return a copy of existing export
+            if (existingRegs != null) {
+                LOG.debug("already exported this service. Returning existing exportRegs {} ", interfaces);
+                return copyExportRegistration(existingRegs);
+            }
+
+            // mark export as being in progress
+            exportedServices.put(key, exportInProgress);
+        }
+        return null;
+    }
+
+    private ExportRegistration exportService(List<String> interfaceNames,
+            ServiceReference<?> serviceReference, Map<String, Object> serviceProperties) {
+        LOG.info("interfaces selected for export: " + interfaceNames);
+        try {
+            Class<?>[] interfaces = getInterfaces(interfaceNames, serviceReference.getBundle());
+            Map<String, Object> eprops = createEndpointProps(serviceProperties, interfaces);
+            BundleContext serviceContext = serviceReference.getBundle().getBundleContext();
+            
+            // TODO unget service when export is destroyed
+            Object serviceO = serviceContext.getService(serviceReference);
+            Endpoint endpoint = provider.exportService(serviceO, serviceContext, eprops, interfaces);
+            return new ExportRegistrationImpl(serviceReference, endpoint, this);
+        } catch (Exception e) {
+            return new ExportRegistrationImpl(this, e);
+        }
+    }
+    
+    private Class<?>[] getInterfaces(List<String> interfaceNames, 
+                                         Bundle serviceBundle) throws ClassNotFoundException {
+        List<Class<?>> interfaces = new ArrayList<>();
+        for (String interfaceName : interfaceNames) {
+            interfaces.add(serviceBundle.loadClass(interfaceName));
+        }
+        return interfaces.toArray(new Class[]{});
+    }
+
+    /**
+     * Determines which interfaces should be exported.
+     *
+     * @param serviceProperties the exported service properties
+     * @return the interfaces to be exported
+     * @throws IllegalArgumentException if the service parameters are invalid
+     * @see RemoteServiceAdmin#exportService
+     * @see org.osgi.framework.Constants#OBJECTCLASS
+     * @see RemoteConstants#SERVICE_EXPORTED_INTERFACES
+     */
+    private List<String> getInterfaceNames(Map<String, Object> serviceProperties) {
+        String[] providedInterfaces = (String[])serviceProperties.get(org.osgi.framework.Constants.OBJECTCLASS);
+        if (providedInterfaces == null || providedInterfaces.length == 0) {
+            throw new IllegalArgumentException("service is missing the objectClass property");
+        }
+
+        String[] exportedInterfaces
+            = StringPlus.normalize(serviceProperties.get(RemoteConstants.SERVICE_EXPORTED_INTERFACES));
+        if (exportedInterfaces == null || exportedInterfaces.length == 0) {
+            throw new IllegalArgumentException("service is missing the service.exported.interfaces property");
+        }
+
+        List<String> interfaces = new ArrayList<String>(1);
+        if (exportedInterfaces.length == 1 && "*".equals(exportedInterfaces[0])) {
+            // FIXME: according to the spec, this should only return the interfaces, and not
+            // non-interface classes (which are valid OBJECTCLASS values, even if discouraged)
+            Collections.addAll(interfaces, providedInterfaces);
+        } else {
+            List<String> providedList = Arrays.asList(providedInterfaces);
+            List<String> allowedList = Arrays.asList(exportedInterfaces);
+            if (!providedList.containsAll(allowedList)) {
+                throw new IllegalArgumentException(String.format(
+                    "exported interfaces %s must be a subset of the service's registered types %s",
+                    allowedList, providedList));
+            }
+
+            Collections.addAll(interfaces, exportedInterfaces);
+        }
+        return interfaces;
+    }
+
+    /**
+     * Converts the given properties map into one that can be used as a map key itself.
+     * For example, if a value is an array, it is converted into a list so that the
+     * equals method will compare it properly.
+     *
+     * @param properties a properties map
+     * @return a map that represents the given map, but can be safely used as a map key itself
+     */
+    private Map<String, Object> makeKey(Map<String, Object> properties) {
+        // FIXME: we should also make logically equal values actually compare as equal
+        // (e.g. String+ values should be normalized)
+        Map<String, Object> converted = new HashMap<String, Object>(properties.size());
+        for (Map.Entry<String, Object> entry : properties.entrySet()) {
+            Object val = entry.getValue();
+            // convert arrays into lists so that they can be compared via equals()
+            if (val instanceof Object[]) {
+                val = Arrays.asList((Object[])val);
+            }
+            converted.put(entry.getKey(), val);
+        }
+        return converted;
+    }
+
+    private List<ExportRegistration> copyExportRegistration(Collection<ExportRegistration> regs) {
+        Set<EndpointDescription> copiedEndpoints = new HashSet<EndpointDescription>();
+
+        // create a new list with copies of the exportRegistrations
+        List<ExportRegistration> copy = new ArrayList<ExportRegistration>(regs.size());
+        for (ExportRegistration exportRegistration : regs) {
+            if (exportRegistration instanceof ExportRegistrationImpl) {
+                ExportRegistrationImpl exportRegistrationImpl = (ExportRegistrationImpl) exportRegistration;
+                EndpointDescription epd = exportRegistration.getExportReference().getExportedEndpoint();
+                // create one copy for each distinct endpoint description
+                if (!copiedEndpoints.contains(epd)) {
+                    copiedEndpoints.add(epd);
+                    copy.add(new ExportRegistrationImpl(exportRegistrationImpl));
+                }
+            }
+        }
+
+        regs.addAll(copy);
+
+        eventProducer.publishNotification(copy);
+        return copy;
+    }
+
+    private boolean isImportedService(ServiceReference<?> sref) {
+        return sref.getProperty(RemoteConstants.SERVICE_IMPORTED) != null;
+    }
+
+    @Override
+    public Collection<ExportReference> getExportedServices() {
+        synchronized (exportedServices) {
+            List<ExportReference> ers = new ArrayList<ExportReference>();
+            for (Collection<ExportRegistration> exportRegistrations : exportedServices.values()) {
+                for (ExportRegistration er : exportRegistrations) {
+                    ers.add(new ExportReferenceImpl(er.getExportReference()));
+                }
+            }
+            return Collections.unmodifiableCollection(ers);
+        }
+    }
+
+    @Override
+    public Collection<ImportReference> getImportedEndpoints() {
+        synchronized (importedServices) {
+            List<ImportReference> irs = new ArrayList<ImportReference>();
+            for (Collection<ImportRegistrationImpl> irl : importedServices.values()) {
+                for (ImportRegistrationImpl impl : irl) {
+                    irs.add(impl.getImportReference());
+                }
+            }
+            return Collections.unmodifiableCollection(irs);
+        }
+    }
+
+    /**
+     * Importing form here...
+     */
+    @Override
+    public ImportRegistration importService(EndpointDescription endpoint) {
+        LOG.debug("importService() Endpoint: {}", endpoint.getProperties());
+
+        synchronized (importedServices) {
+            Collection<ImportRegistrationImpl> imRegs = importedServices.get(endpoint);
+            if (imRegs != null && !imRegs.isEmpty()) {
+                LOG.debug("creating copy of existing import registrations");
+                ImportRegistrationImpl irParent = imRegs.iterator().next();
+                ImportRegistrationImpl ir = new ImportRegistrationImpl(irParent);
+                imRegs.add(ir);
+                eventProducer.publishNotification(ir);
+                return ir;
+            }
+
+            if (determineConfigTypesForImport(endpoint).size() == 0) {
+                LOG.info("No matching handler can be found for remote endpoint {}.", endpoint.getId());
+                return null;
+            }
+            
+            // TODO: somehow select the interfaces that should be imported ---> job of the TopologyManager?
+            List<String> matchingInterfaces = endpoint.getInterfaces();
+            
+            if (matchingInterfaces.size() == 0) {
+                LOG.info("No matching interfaces found for remote endpoint {}.", endpoint.getId());
+                return null;
+            }
+            
+            LOG.info("Importing service {} with interfaces {} using handler {}.", 
+                     endpoint.getId(), endpoint.getInterfaces(), provider.getClass());
+
+            ImportRegistrationImpl imReg = exposeServiceFactory(matchingInterfaces.get(0), endpoint, provider);
+            if (imRegs == null) {
+                imRegs = new ArrayList<ImportRegistrationImpl>();
+                importedServices.put(endpoint, imRegs);
+            }
+            imRegs.add(imReg);
+            eventProducer.publishNotification(imReg);
+            return imReg;
+        }
+    }
+    
+    private List<String> determineConfigTypesForImport(EndpointDescription endpoint) {
+        List<String> remoteConfigurationTypes = endpoint.getConfigurationTypes();
+
+        List<String> usableConfigurationTypes = new ArrayList<String>();
+        for (String ct : provider.getSupportedTypes()) {
+            if (remoteConfigurationTypes.contains(ct)) {
+                usableConfigurationTypes.add(ct);
+            }
+        }
+
+        LOG.info("Ignoring endpoint {} as it has no compatible configuration types: {}.", 
+                 endpoint.getId(), remoteConfigurationTypes);
+        return usableConfigurationTypes;
+    }
+
+    protected ImportRegistrationImpl exposeServiceFactory(String interfaceName,
+                                            EndpointDescription epd,
+                                            DistributionProvider handler) {
+        ImportRegistrationImpl imReg = new ImportRegistrationImpl(epd, this);
+        try {
+            EndpointDescription endpoint = imReg.getImportedEndpointDescription();
+            Dictionary<String, Object> serviceProps = new Hashtable<String, Object>(endpoint.getProperties());
+            serviceProps.put(RemoteConstants.SERVICE_IMPORTED, true);
+            serviceProps.remove(RemoteConstants.SERVICE_EXPORTED_INTERFACES);
+
+            ClientServiceFactory csf = new ClientServiceFactory(endpoint, handler, imReg);
+            imReg.setClientServiceFactory(csf);
+            
+            /**
+             *  Export the factory using the api context as it has very few imports.
+             *  If the bundle publishing the factory does not import the service interface
+             *  package then the factory is visible for all consumers which we want.
+             */
+            ServiceRegistration<?> csfReg = apictx.registerService(interfaceName, csf, serviceProps);
+            imReg.setImportedServiceRegistration(csfReg);
+        } catch (Exception ex) {
+            // Only logging at debug level as this might be written to the log at the TopologyManager
+            LOG.debug("Can not proxy service with interface " + interfaceName + ": " + ex.getMessage(), ex);
+            imReg.setException(ex);
+        }
+        return imReg;
+    }
+
+    /**
+     * Removes and closes all exports for the given service.
+     * This is called when the service is unregistered.
+     *
+     * @param sref the service whose exports should be removed and closed
+     */
+    protected void removeServiceExports(ServiceReference<?> sref) {
+        List<ExportRegistration> regs = new ArrayList<ExportRegistration>(1);
+        synchronized (exportedServices) {
+            for (Iterator<Collection<ExportRegistration>> it = exportedServices.values().iterator(); it.hasNext();) {
+                Collection<ExportRegistration> value = it.next();
+                for (Iterator<ExportRegistration> it2 = value.iterator(); it2.hasNext();) {
+                    ExportRegistration er = it2.next();
+                    if (er.getExportReference().getExportedService().equals(sref)) {
+                        regs.add(er);
+                    }
+                }
+            }
+            // do this outside of iteration to avoid concurrent modification
+            for (ExportRegistration er : regs) {
+                LOG.debug("closing export for service {}", sref);
+                er.close();
+            }
+        }
+
+    }
+
+    /**
+     * Removes the provided Export Registration from the internal management structures.
+     * This is called from the ExportRegistration itself when it is closed (so should
+     * not attempt to close it again here).
+     *
+     * @param eri the export registration to remove
+     */
+    protected void removeExportRegistration(ExportRegistrationImpl eri) {
+        synchronized (exportedServices) {
+            for (Iterator<Collection<ExportRegistration>> it = exportedServices.values().iterator(); it.hasNext();) {
+                Collection<ExportRegistration> value = it.next();
+                for (Iterator<ExportRegistration> it2 = value.iterator(); it2.hasNext();) {
+                    ExportRegistration er = it2.next();
+                    if (er.equals(eri)) {
+                        eventProducer.notifyRemoval(eri);
+                        it2.remove();
+                        if (value.isEmpty()) {
+                            it.remove();
+                        }
+                        return;
+                    }
+                }
+            }
+        }
+    }
+
+    // remove all export registrations associated with the given bundle
+    protected void removeExportRegistrations(Bundle exportingBundle) {
+        List<ExportRegistration> bundleExports = getExportsForBundle(exportingBundle);
+        for (ExportRegistration export : bundleExports) {
+            export.close();
+        }
+    }
+
+    // remove all import registrations
+    protected void removeImportRegistrations() {
+        Collection<ImportRegistrationImpl> copy = new ArrayList<ImportRegistrationImpl>();
+        synchronized (importedServices) {
+            for (Collection<ImportRegistrationImpl> irs : importedServices.values()) {
+                copy.addAll(irs);
+            }
+        }
+        for (ImportRegistrationImpl ir : copy) {
+            removeImportRegistration(ir);
+        }
+    }
+
+    private List<ExportRegistration> getExportsForBundle(Bundle exportingBundle) {
+        synchronized (exportedServices) {
+            List<ExportRegistration> bundleRegs = new ArrayList<ExportRegistration>();
+            for (Collection<ExportRegistration> regs : exportedServices.values()) {
+                if (!regs.isEmpty()) {
+                    ExportRegistration exportRegistration = regs.iterator().next();
+                    if (exportRegistration.getException() == null) {
+                        Bundle regBundle = exportRegistration.getExportReference().getExportedService().getBundle();
+                        if (exportingBundle.equals(regBundle)) {
+                            bundleRegs.addAll(regs);
+                        }
+                    }
+                }
+            }
+            return bundleRegs;
+        }
+    }
+
+    protected void removeImportRegistration(ImportRegistrationImpl iri) {
+        synchronized (importedServices) {
+            LOG.debug("Removing importRegistration {}", iri);
+
+            Collection<ImportRegistrationImpl> imRegs = importedServices.get(iri.getImportedEndpointAlways());
+            if (imRegs != null && imRegs.contains(iri)) {
+                imRegs.remove(iri);
+                eventProducer.notifyRemoval(iri);
+            }
+            if (imRegs == null || imRegs.isEmpty()) {
+                importedServices.remove(iri.getImportedEndpointAlways());
+            }
+        }
+    }
+
+    public void close() {
+        removeImportRegistrations();
+        bctx.removeServiceListener(exportedServiceListener);
+    }
+
+    static void overlayProperties(Map<String, Object> serviceProperties,
+                                         Map<String, Object> additionalProperties) {
+        Map<String, String> keysLowerCase = new HashMap<String, String>();
+        for (String key : serviceProperties.keySet()) {
+            keysLowerCase.put(key.toLowerCase(), key);
+        }
+    
+        for (Map.Entry<String, Object> e : additionalProperties.entrySet()) {
+            String key = e.getKey();
+            String lowerKey = key.toLowerCase();
+            if (org.osgi.framework.Constants.SERVICE_ID.toLowerCase().equals(lowerKey)
+                || org.osgi.framework.Constants.OBJECTCLASS.toLowerCase().equals(lowerKey)) {
+                // objectClass and service.id must not be overwritten
+                LOG.info("exportService called with additional properties map that contained illegal key: "
+                          + key + ", the key is ignored");
+            } else {
+                String origKey = keysLowerCase.get(lowerKey);
+                if (origKey != null) {
+                    LOG.debug("Overwriting property [{}] with value [{}]", origKey, e.getValue());
+                } else {
+                    origKey = key;
+                    keysLowerCase.put(lowerKey, origKey);
+                }
+                serviceProperties.put(origKey, e.getValue());
+            }
+        }
+    }
+
+    /**
+     * Returns a service's properties as a map.
+     *
+     * @param serviceReference a service reference
+     * @return the service's properties as a map
+     */
+    private Map<String, Object> getProperties(ServiceReference<?> serviceReference) {
+        String[] keys = serviceReference.getPropertyKeys();
+        Map<String, Object> props = new HashMap<String, Object>(keys.length);
+        for (String key : keys) {
+            Object val = serviceReference.getProperty(key);
+            props.put(key, val);
+        }
+        return props;
+    }
+    
+    protected Map<String, Object> createEndpointProps(Map<String, Object> effectiveProps, 
+                                                      Class<?>[] ifaces) {
+        Map<String, Object> props = new HashMap<String, Object>();
+        copyEndpointProperties(effectiveProps, props);
+        props.remove(org.osgi.framework.Constants.SERVICE_ID);
+        EndpointHelper.addObjectClass(props, ifaces);
+        props.put(RemoteConstants.ENDPOINT_SERVICE_ID, effectiveProps.get(org.osgi.framework.Constants.SERVICE_ID));
+        String frameworkUUID = bctx.getProperty(org.osgi.framework.Constants.FRAMEWORK_UUID);
+        props.put(RemoteConstants.ENDPOINT_FRAMEWORK_UUID, frameworkUUID);
+        for (Class<?> iface : ifaces) {
+            String pkg = iface.getPackage().getName();
+            props.put(RemoteConstants.ENDPOINT_PACKAGE_VERSION_ + pkg, PackageUtil.getVersion(iface, bctx));
+        }
+        return props;
+    }
+
+
+
+    private void copyEndpointProperties(Map<String, Object> sd, Map<String, Object> endpointProps) {
+        Set<Map.Entry<String, Object>> keys = sd.entrySet();
+        for (Map.Entry<String, Object> entry : keys) {
+            String skey = entry.getKey();
+            if (!skey.startsWith(".")) {
+                endpointProps.put(skey, entry.getValue());
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/aries-rsa/blob/d73a3a7f/rsa/src/main/java/org/apache/cxf/dosgi/dsw/service/RemoteServiceAdminInstance.java
----------------------------------------------------------------------
diff --git a/rsa/src/main/java/org/apache/cxf/dosgi/dsw/service/RemoteServiceAdminInstance.java b/rsa/src/main/java/org/apache/cxf/dosgi/dsw/service/RemoteServiceAdminInstance.java
new file mode 100644
index 0000000..fc3a67e
--- /dev/null
+++ b/rsa/src/main/java/org/apache/cxf/dosgi/dsw/service/RemoteServiceAdminInstance.java
@@ -0,0 +1,99 @@
+/**
+ * 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.cxf.dosgi.dsw.service;
+
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.remoteserviceadmin.EndpointDescription;
+import org.osgi.service.remoteserviceadmin.EndpointPermission;
+import org.osgi.service.remoteserviceadmin.ExportReference;
+import org.osgi.service.remoteserviceadmin.ExportRegistration;
+import org.osgi.service.remoteserviceadmin.ImportReference;
+import org.osgi.service.remoteserviceadmin.ImportRegistration;
+import org.osgi.service.remoteserviceadmin.RemoteServiceAdmin;
+
+public class RemoteServiceAdminInstance implements RemoteServiceAdmin {
+
+    private final BundleContext bctx;
+    private final RemoteServiceAdminCore rsaCore;
+
+    private boolean closed;
+
+    public RemoteServiceAdminInstance(BundleContext bc, RemoteServiceAdminCore core) {
+        bctx = bc;
+        rsaCore = core;
+    }
+
+    @Override
+    @SuppressWarnings("rawtypes")
+    public List<ExportRegistration> exportService(final ServiceReference ref, final Map properties) {
+        checkPermission(new EndpointPermission("*", EndpointPermission.EXPORT));
+        return AccessController.doPrivileged(new PrivilegedAction<List<ExportRegistration>>() {
+            public List<ExportRegistration> run() {
+                return closed ? Collections.<ExportRegistration>emptyList() : rsaCore.exportService(ref, properties);
+            }
+        });
+    }
+
+    @Override
+    public Collection<ExportReference> getExportedServices() {
+        checkPermission(new EndpointPermission("*", EndpointPermission.READ));
+        return closed ? null : rsaCore.getExportedServices();
+    }
+
+    @Override
+    public Collection<ImportReference> getImportedEndpoints() {
+        checkPermission(new EndpointPermission("*", EndpointPermission.READ));
+        return closed ? null : rsaCore.getImportedEndpoints();
+    }
+
+    @Override
+    public ImportRegistration importService(final EndpointDescription endpoint) {
+        String frameworkUUID = bctx.getProperty(Constants.FRAMEWORK_UUID);
+        checkPermission(new EndpointPermission(endpoint, frameworkUUID, EndpointPermission.IMPORT));
+        return AccessController.doPrivileged(new PrivilegedAction<ImportRegistration>() {
+            public ImportRegistration run() {
+                return closed ? null : rsaCore.importService(endpoint);
+            }
+        });
+    }
+
+    public void close(boolean closeAll) {
+        closed = true;
+        rsaCore.removeExportRegistrations(bctx.getBundle());
+        if (closeAll) {
+            rsaCore.close();
+        }
+    }
+
+    private void checkPermission(EndpointPermission permission) {
+        SecurityManager sm = System.getSecurityManager();
+        if (sm != null) {
+            sm.checkPermission(permission);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/aries-rsa/blob/d73a3a7f/rsa/src/main/java/org/apache/cxf/dosgi/dsw/service/RemoteServiceadminFactory.java
----------------------------------------------------------------------
diff --git a/rsa/src/main/java/org/apache/cxf/dosgi/dsw/service/RemoteServiceadminFactory.java b/rsa/src/main/java/org/apache/cxf/dosgi/dsw/service/RemoteServiceadminFactory.java
new file mode 100644
index 0000000..968ff16
--- /dev/null
+++ b/rsa/src/main/java/org/apache/cxf/dosgi/dsw/service/RemoteServiceadminFactory.java
@@ -0,0 +1,51 @@
+/**
+ * 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.cxf.dosgi.dsw.service;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.ServiceFactory;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.remoteserviceadmin.RemoteServiceAdmin;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class RemoteServiceadminFactory implements ServiceFactory<RemoteServiceAdmin> {
+
+    private static final Logger LOG = LoggerFactory.getLogger(RemoteServiceadminFactory.class);
+
+    private final RemoteServiceAdminCore rsaCore;
+    private int instances;
+
+    public RemoteServiceadminFactory(RemoteServiceAdminCore rsaCore) {
+        this.rsaCore = rsaCore;
+    }
+
+    public synchronized RemoteServiceAdmin getService(Bundle b, ServiceRegistration<RemoteServiceAdmin> sreg) {
+        LOG.debug("new RemoteServiceAdmin ServiceInstance created for Bundle {}", b.getSymbolicName());
+        instances++;
+        return new RemoteServiceAdminInstance(b.getBundleContext(), rsaCore);
+    }
+
+    public synchronized void ungetService(Bundle b, ServiceRegistration<RemoteServiceAdmin> sreg,
+                                          RemoteServiceAdmin serviceObject) {
+        LOG.debug("RemoteServiceAdmin ServiceInstance removed for Bundle {}", b.getSymbolicName());
+        instances--;
+        ((RemoteServiceAdminInstance)serviceObject).close(instances == 0);
+    }
+}

http://git-wip-us.apache.org/repos/asf/aries-rsa/blob/d73a3a7f/rsa/src/main/java/org/apache/cxf/dosgi/dsw/service/StringPlus.java
----------------------------------------------------------------------
diff --git a/rsa/src/main/java/org/apache/cxf/dosgi/dsw/service/StringPlus.java b/rsa/src/main/java/org/apache/cxf/dosgi/dsw/service/StringPlus.java
new file mode 100644
index 0000000..e449f1d
--- /dev/null
+++ b/rsa/src/main/java/org/apache/cxf/dosgi/dsw/service/StringPlus.java
@@ -0,0 +1,72 @@
+/**
+ * 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.cxf.dosgi.dsw.service;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public final class StringPlus {
+
+    private static final Logger LOG = LoggerFactory.getLogger(StringPlus.class);
+
+    private StringPlus() {
+        // never constructed
+    }
+
+    @SuppressWarnings("rawtypes")
+    public static String[] normalize(Object object) {
+        if (object instanceof String) {
+            String s = (String)object;
+            String[] values = s.split(",");
+            List<String> list = new ArrayList<String>();
+            for (String val : values) {
+                String actualValue = val.trim();
+                if (!actualValue.isEmpty()) {
+                    list.add(actualValue);
+                }
+            }
+            return list.toArray(new String[list.size()]);
+        }
+
+        if (object instanceof String[]) {
+            return (String[])object;
+        }
+        
+        if (object instanceof Collection) {
+            Collection col = (Collection)object;
+            List<String> ar = new ArrayList<String>(col.size());
+            for (Object o : col) {
+                if (o instanceof String) {
+                    String s = (String)o;
+                    ar.add(s);
+                } else {
+                    LOG.warn("stringPlus contained non string element in list! Element was skipped");
+                }
+            }
+            return ar.toArray(new String[ar.size()]);
+        }
+
+        return null;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/aries-rsa/blob/d73a3a7f/rsa/src/test/java/org/apache/cxf/dosgi/dsw/service/ClientServiceFactoryTest.java
----------------------------------------------------------------------
diff --git a/rsa/src/test/java/org/apache/cxf/dosgi/dsw/service/ClientServiceFactoryTest.java b/rsa/src/test/java/org/apache/cxf/dosgi/dsw/service/ClientServiceFactoryTest.java
new file mode 100644
index 0000000..fbe2c02
--- /dev/null
+++ b/rsa/src/test/java/org/apache/cxf/dosgi/dsw/service/ClientServiceFactoryTest.java
@@ -0,0 +1,92 @@
+/**
+ * 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.cxf.dosgi.dsw.service;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import junit.framework.TestCase;
+
+import org.apache.cxf.dosgi.dsw.api.DistributionProvider;
+import org.easymock.EasyMock;
+import org.easymock.IMocksControl;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.framework.wiring.BundleWiring;
+import org.osgi.service.remoteserviceadmin.EndpointDescription;
+import org.osgi.service.remoteserviceadmin.RemoteConstants;
+
+import static org.easymock.EasyMock.anyObject;
+import static org.easymock.EasyMock.isA;
+
+
+public class ClientServiceFactoryTest extends TestCase {
+
+    @SuppressWarnings({
+     "rawtypes"
+    })
+    public void testGetService() throws ClassNotFoundException {
+        final Object myTestProxyObject = new Object();
+
+        IMocksControl control = EasyMock.createControl();
+        EndpointDescription endpoint = createTestEndpointDesc();
+        ImportRegistrationImpl iri = new ImportRegistrationImpl(endpoint, null);
+
+        BundleContext consumerContext = control.createMock(BundleContext.class);
+        Bundle consumerBundle = control.createMock(Bundle.class);
+        BundleWiring bundleWiring = control.createMock(BundleWiring.class);
+        EasyMock.expect(bundleWiring.getClassLoader()).andReturn(this.getClass().getClassLoader());
+        EasyMock.expect(consumerBundle.adapt(BundleWiring.class)).andReturn(bundleWiring);
+        EasyMock.expect(consumerBundle.getBundleContext()).andReturn(consumerContext);
+        ServiceRegistration sreg = control.createMock(ServiceRegistration.class);
+
+
+        DistributionProvider handler = mockDistributionProvider(myTestProxyObject);
+        control.replay();
+
+        ClientServiceFactory csf = new ClientServiceFactory(endpoint, handler, iri);
+        assertSame(myTestProxyObject, csf.getService(consumerBundle, sreg));
+    }
+
+    /**
+     * Creating dummy class as I was not able to really mock it
+     * @param myTestProxyObject
+     * @return
+     */
+    private DistributionProvider mockDistributionProvider(final Object proxy) {
+        DistributionProvider handler = EasyMock.createMock(DistributionProvider.class);
+        EasyMock.expect(handler.importEndpoint(anyObject(ClassLoader.class), 
+                                               anyObject(BundleContext.class), 
+                                               isA(Class[].class), 
+                                               anyObject(EndpointDescription.class))).andReturn(proxy);
+        EasyMock.replay(handler);
+        return handler;
+    }
+
+    private EndpointDescription createTestEndpointDesc() {
+        Map<String, Object> map = new HashMap<String, Object>();
+        map.put(RemoteConstants.ENDPOINT_ID, "http://google.de");
+        map.put(RemoteConstants.SERVICE_IMPORTED_CONFIGS, "myGreatConfiguration");
+        map.put(Constants.OBJECTCLASS, new String[]{String.class.getName()});
+        EndpointDescription endpoint = new EndpointDescription(map);
+        return endpoint;
+    }
+}

http://git-wip-us.apache.org/repos/asf/aries-rsa/blob/d73a3a7f/rsa/src/test/java/org/apache/cxf/dosgi/dsw/service/DistributionProviderTrackerTest.java
----------------------------------------------------------------------
diff --git a/rsa/src/test/java/org/apache/cxf/dosgi/dsw/service/DistributionProviderTrackerTest.java b/rsa/src/test/java/org/apache/cxf/dosgi/dsw/service/DistributionProviderTrackerTest.java
new file mode 100644
index 0000000..6ae7004
--- /dev/null
+++ b/rsa/src/test/java/org/apache/cxf/dosgi/dsw/service/DistributionProviderTrackerTest.java
@@ -0,0 +1,82 @@
+/**
+ * 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.cxf.dosgi.dsw.service;
+
+import java.util.Dictionary;
+
+import org.apache.cxf.dosgi.dsw.api.DistributionProvider;
+import org.easymock.EasyMock;
+import org.easymock.IMocksControl;
+import org.junit.Test;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Filter;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceFactory;
+import org.osgi.framework.ServiceListener;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.remoteserviceadmin.RemoteConstants;
+
+public class DistributionProviderTrackerTest {
+
+    @SuppressWarnings({
+     "unchecked", "rawtypes"
+    })
+    @Test
+    public void testAddingRemoved() throws InvalidSyntaxException {
+        IMocksControl c = EasyMock.createControl();
+        DistributionProvider provider = c.createMock(DistributionProvider.class);
+        
+        ServiceReference<DistributionProvider> providerRef = c.createMock(ServiceReference.class);
+        EasyMock.expect(providerRef.getProperty(RemoteConstants.REMOTE_INTENTS_SUPPORTED)).andReturn("");
+        EasyMock.expect(providerRef.getProperty(RemoteConstants.REMOTE_CONFIGS_SUPPORTED)).andReturn("");
+
+        BundleContext context = c.createMock(BundleContext.class);
+        Filter filter = FrameworkUtil.createFilter("(objectClass=org.apache.cxf.dosgi.dsw.api.DistributionProvider)");
+        EasyMock.expect(context.createFilter("(objectClass=org.apache.cxf.dosgi.dsw.api.DistributionProvider)"))
+            .andReturn(filter);
+        EasyMock.expect(context.getService(providerRef)).andReturn(provider);
+        ServiceRegistration rsaReg = c.createMock(ServiceRegistration.class);
+        EasyMock.expect(context.registerService(EasyMock.isA(String.class), EasyMock.isA(ServiceFactory.class), 
+                                                EasyMock.isA(Dictionary.class)))
+            .andReturn(rsaReg).atLeastOnce();
+
+        context.addServiceListener(EasyMock.isA(ServiceListener.class), EasyMock.isA(String.class));
+        EasyMock.expectLastCall();
+        
+        final BundleContext apiContext = c.createMock(BundleContext.class);
+        c.replay();
+        DistributionProviderTracker tracker = new DistributionProviderTracker(context) {
+            protected BundleContext getAPIContext() {
+                return apiContext;
+            };
+        };
+        tracker.addingService(providerRef);
+        c.verify();
+        
+        c.reset();
+        rsaReg.unregister();
+        EasyMock.expectLastCall();
+        EasyMock.expect(context.ungetService(providerRef)).andReturn(true);
+        c.replay();
+        tracker.removedService(providerRef, rsaReg);
+        c.verify();
+    }
+}

http://git-wip-us.apache.org/repos/asf/aries-rsa/blob/d73a3a7f/rsa/src/test/java/org/apache/cxf/dosgi/dsw/service/EventProducerTest.java
----------------------------------------------------------------------
diff --git a/rsa/src/test/java/org/apache/cxf/dosgi/dsw/service/EventProducerTest.java b/rsa/src/test/java/org/apache/cxf/dosgi/dsw/service/EventProducerTest.java
new file mode 100644
index 0000000..71d3ed7
--- /dev/null
+++ b/rsa/src/test/java/org/apache/cxf/dosgi/dsw/service/EventProducerTest.java
@@ -0,0 +1,189 @@
+/**
+ * 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.cxf.dosgi.dsw.service;
+
+import java.util.Arrays;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.UUID;
+
+import org.apache.cxf.dosgi.dsw.api.Endpoint;
+import org.easymock.EasyMock;
+import org.easymock.IAnswer;
+import org.junit.Assert;
+import org.junit.Test;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.Version;
+import org.osgi.service.event.Event;
+import org.osgi.service.event.EventAdmin;
+import org.osgi.service.remoteserviceadmin.EndpointDescription;
+import org.osgi.service.remoteserviceadmin.ExportReference;
+import org.osgi.service.remoteserviceadmin.ExportRegistration;
+import org.osgi.service.remoteserviceadmin.RemoteServiceAdminEvent;
+
+@SuppressWarnings({"rawtypes", "unchecked"})
+public class EventProducerTest {
+    
+    
+    @Test
+    public void testPublishNotification() throws Exception {
+        RemoteServiceAdminCore remoteServiceAdminCore = EasyMock.createNiceMock(RemoteServiceAdminCore.class);
+        EasyMock.replay(remoteServiceAdminCore);
+
+        final EndpointDescription epd = EasyMock.createNiceMock(EndpointDescription.class);
+        EasyMock.expect(epd.getServiceId()).andReturn(Long.MAX_VALUE).anyTimes();
+        final String uuid = UUID.randomUUID().toString();
+        EasyMock.expect(epd.getFrameworkUUID()).andReturn(uuid).anyTimes();
+        EasyMock.expect(epd.getId()).andReturn("foo://bar").anyTimes();
+        final List<String> interfaces = Arrays.asList("org.foo.Bar", "org.boo.Far");
+        EasyMock.expect(epd.getInterfaces()).andReturn(interfaces).anyTimes();
+        EasyMock.expect(epd.getConfigurationTypes()).andReturn(Arrays.asList("org.apache.cxf.ws")).anyTimes();
+        EasyMock.replay(epd);
+        final ServiceReference sref = EasyMock.createNiceMock(ServiceReference.class);
+        EasyMock.replay(sref);
+
+        final Bundle bundle = EasyMock.createNiceMock(Bundle.class);
+        EasyMock.expect(bundle.getBundleId()).andReturn(42L).anyTimes();
+        EasyMock.expect(bundle.getSymbolicName()).andReturn("test.bundle").anyTimes();
+        Dictionary<String, String> headers = new Hashtable<String, String>();
+        headers.put("Bundle-Version", "1.2.3.test");
+        EasyMock.expect(bundle.getHeaders()).andReturn(headers).anyTimes();
+        EasyMock.replay(bundle);
+
+        EventAdmin ea = EasyMock.createNiceMock(EventAdmin.class);
+        ea.postEvent((Event) EasyMock.anyObject());
+        EasyMock.expectLastCall().andAnswer(new IAnswer<Object>() {
+            @Override
+            public Object answer() throws Throwable {
+                Event event = (Event) EasyMock.getCurrentArguments()[0];
+
+                Assert.assertEquals("org/osgi/service/remoteserviceadmin/EXPORT_REGISTRATION", event.getTopic());
+                Assert.assertSame(bundle, event.getProperty("bundle"));
+                Assert.assertEquals(42L, event.getProperty("bundle.id"));
+                Assert.assertEquals("test.bundle", event.getProperty("bundle.symbolicname"));
+                Assert.assertEquals(new Version(1, 2, 3, "test"), event.getProperty("bundle.version"));
+                Assert.assertNull(event.getProperty("cause"));
+                Assert.assertEquals(epd, event.getProperty("export.registration"));
+
+                Assert.assertEquals(Long.MAX_VALUE, event.getProperty("service.remote.id"));
+                Assert.assertEquals(uuid, event.getProperty("service.remote.uuid"));
+                Assert.assertEquals("foo://bar", event.getProperty("service.remote.uri"));
+                Assert.assertTrue(Arrays.equals(interfaces.toArray(new String[] {}),
+                                                (String[]) event.getProperty("objectClass")));
+
+                Assert.assertNotNull(event.getProperty("timestamp"));
+
+                RemoteServiceAdminEvent rsae = (RemoteServiceAdminEvent) event.getProperty("event");
+                Assert.assertNull(rsae.getException());
+                Assert.assertEquals(RemoteServiceAdminEvent.EXPORT_REGISTRATION, rsae.getType());
+                Assert.assertSame(bundle, rsae.getSource());
+                ExportReference er = rsae.getExportReference();
+                Assert.assertSame(epd, er.getExportedEndpoint());
+                Assert.assertSame(sref, er.getExportedService());
+
+                return null;
+            }
+        });
+        EasyMock.replay(ea);
+
+        ServiceReference eaSref = EasyMock.createNiceMock(ServiceReference.class);
+        EasyMock.replay(eaSref);
+
+        BundleContext bc = EasyMock.createNiceMock(BundleContext.class);
+        EasyMock.expect(bc.getBundle()).andReturn(bundle).anyTimes();
+        EasyMock.expect(bc.getAllServiceReferences(EventAdmin.class.getName(), null))
+            .andReturn(new ServiceReference[] {eaSref}).anyTimes();
+        EasyMock.expect(bc.getService(eaSref)).andReturn(ea).anyTimes();
+        Endpoint endpoint = EasyMock.mock(Endpoint.class);
+        EasyMock.expect(endpoint.description()).andReturn(epd);
+        EasyMock.replay(endpoint);
+        EasyMock.replay(bc);
+        EventProducer eventProducer = new EventProducer(bc);
+
+        ExportRegistrationImpl ereg = new ExportRegistrationImpl(sref, endpoint, remoteServiceAdminCore);
+        eventProducer.publishNotification(ereg);
+    }
+
+    @Test
+    public void testPublishErrorNotification() throws Exception {
+        RemoteServiceAdminCore rsaCore = EasyMock.createNiceMock(RemoteServiceAdminCore.class);
+        EasyMock.replay(rsaCore);
+
+        final EndpointDescription endpoint = EasyMock.createNiceMock(EndpointDescription.class);
+        EasyMock.expect(endpoint.getInterfaces()).andReturn(Arrays.asList("org.foo.Bar")).anyTimes();
+        EasyMock.replay(endpoint);
+        final ServiceReference sref = EasyMock.createNiceMock(ServiceReference.class);
+        EasyMock.replay(sref);
+
+        final Bundle bundle = EasyMock.createNiceMock(Bundle.class);
+        EasyMock.expect(bundle.getBundleId()).andReturn(42L).anyTimes();
+        EasyMock.expect(bundle.getSymbolicName()).andReturn("test.bundle").anyTimes();
+        EasyMock.expect(bundle.getHeaders()).andReturn(new Hashtable<String, String>()).anyTimes();
+        EasyMock.replay(bundle);
+
+        final Exception exportException = new Exception();
+
+        EventAdmin ea = EasyMock.createNiceMock(EventAdmin.class);
+        ea.postEvent((Event) EasyMock.anyObject());
+        EasyMock.expectLastCall().andAnswer(new IAnswer<Object>() {
+            @Override
+            public Object answer() throws Throwable {
+                Event event = (Event) EasyMock.getCurrentArguments()[0];
+
+                Assert.assertEquals("org/osgi/service/remoteserviceadmin/EXPORT_ERROR", event.getTopic());
+                Assert.assertSame(bundle, event.getProperty("bundle"));
+                Assert.assertEquals(42L, event.getProperty("bundle.id"));
+                Assert.assertEquals("test.bundle", event.getProperty("bundle.symbolicname"));
+                Assert.assertEquals(new Version("0"), event.getProperty("bundle.version"));
+                Assert.assertSame(exportException, event.getProperty("cause"));
+                Assert.assertEquals(endpoint, event.getProperty("export.registration"));
+                Assert.assertTrue(Arrays.equals(new String[] {"org.foo.Bar"},
+                                                (String[]) event.getProperty("objectClass")));
+
+                RemoteServiceAdminEvent rsae = (RemoteServiceAdminEvent) event.getProperty("event");
+                Assert.assertSame(exportException, rsae.getException());
+                Assert.assertEquals(RemoteServiceAdminEvent.EXPORT_ERROR, rsae.getType());
+                Assert.assertSame(bundle, rsae.getSource());
+                ExportReference er = rsae.getExportReference();
+                Assert.assertSame(endpoint, er.getExportedEndpoint());
+                Assert.assertSame(sref, er.getExportedService());
+
+                return null;
+            }
+        });
+        EasyMock.replay(ea);
+
+        ServiceReference eaSref = EasyMock.createNiceMock(ServiceReference.class);
+        EasyMock.replay(eaSref);
+
+        BundleContext bc = EasyMock.createNiceMock(BundleContext.class);
+        EasyMock.expect(bc.getBundle()).andReturn(bundle).anyTimes();
+        EasyMock.expect(bc.getAllServiceReferences(EventAdmin.class.getName(), null))
+            .andReturn(new ServiceReference[] {eaSref}).anyTimes();
+        EasyMock.expect(bc.getService(eaSref)).andReturn(ea).anyTimes();
+        EasyMock.replay(bc);
+        EventProducer eventProducer = new EventProducer(bc);
+
+        ExportRegistrationImpl ereg = new ExportRegistrationImpl(rsaCore, exportException);
+        eventProducer.publishNotification(Arrays.<ExportRegistration>asList(ereg));
+    }
+}

http://git-wip-us.apache.org/repos/asf/aries-rsa/blob/d73a3a7f/rsa/src/test/java/org/apache/cxf/dosgi/dsw/service/ImportRegistrationImplTest.java
----------------------------------------------------------------------
diff --git a/rsa/src/test/java/org/apache/cxf/dosgi/dsw/service/ImportRegistrationImplTest.java b/rsa/src/test/java/org/apache/cxf/dosgi/dsw/service/ImportRegistrationImplTest.java
new file mode 100644
index 0000000..23902a5
--- /dev/null
+++ b/rsa/src/test/java/org/apache/cxf/dosgi/dsw/service/ImportRegistrationImplTest.java
@@ -0,0 +1,178 @@
+/**
+ * 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.cxf.dosgi.dsw.service;
+
+import org.easymock.EasyMock;
+import org.easymock.IMocksControl;
+import org.junit.Test;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.remoteserviceadmin.EndpointDescription;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+public class ImportRegistrationImplTest {
+
+    @Test
+    public void testException() {
+        IMocksControl c = EasyMock.createNiceControl();
+        Exception e = c.createMock(Exception.class);
+        c.replay();
+
+        ImportRegistrationImpl i = new ImportRegistrationImpl(e);
+
+        assertEquals(e, i.getException());
+        assertNull(i.getImportedEndpointDescription());
+        assertNull(i.getImportedService());
+        assertEquals(i, i.getParent());
+    }
+
+    @Test
+    public void testDefaultCtor() {
+        IMocksControl c = EasyMock.createNiceControl();
+        EndpointDescription endpoint = c.createMock(EndpointDescription.class);
+        RemoteServiceAdminCore rsac = c.createMock(RemoteServiceAdminCore.class);
+
+        c.replay();
+
+        ImportRegistrationImpl i = new ImportRegistrationImpl(endpoint, rsac);
+
+        assertNull(i.getException());
+        assertEquals(i, i.getParent());
+        assertEquals(endpoint, i.getImportedEndpointDescription());
+    }
+
+    @SuppressWarnings("rawtypes")
+    @Test
+    public void testCloneAndClose() {
+        IMocksControl c = EasyMock.createControl();
+        EndpointDescription endpoint = c.createMock(EndpointDescription.class);
+        RemoteServiceAdminCore rsac = c.createMock(RemoteServiceAdminCore.class);
+
+        ServiceRegistration sr = c.createMock(ServiceRegistration.class);
+        ServiceReference sref = c.createMock(ServiceReference.class);
+        EasyMock.expect(sr.getReference()).andReturn(sref).anyTimes();
+
+        c.replay();
+
+        ImportRegistrationImpl i1 = new ImportRegistrationImpl(endpoint, rsac);
+
+        ImportRegistrationImpl i2 = new ImportRegistrationImpl(i1);
+
+        ImportRegistrationImpl i3 = new ImportRegistrationImpl(i2);
+
+        try {
+            i2.setImportedServiceRegistration(sr);
+            assertTrue("An exception should be thrown here !", false);
+        } catch (IllegalStateException e) {
+            // must be thrown here
+        }
+
+        i1.setImportedServiceRegistration(sr);
+
+        assertEquals(i1, i1.getParent());
+        assertEquals(i1, i2.getParent());
+        assertEquals(i1, i3.getParent());
+
+        assertEquals(endpoint, i1.getImportedEndpointDescription());
+        assertEquals(endpoint, i2.getImportedEndpointDescription());
+        assertEquals(endpoint, i3.getImportedEndpointDescription());
+
+        c.verify();
+        c.reset();
+
+        rsac.removeImportRegistration(EasyMock.eq(i3));
+        EasyMock.expectLastCall().once();
+
+        c.replay();
+
+        i3.close();
+        i3.close(); // shouldn't change anything
+
+        assertNull(i3.getImportedEndpointDescription());
+
+        c.verify();
+        c.reset();
+
+        rsac.removeImportRegistration(EasyMock.eq(i1));
+        EasyMock.expectLastCall().once();
+
+        c.replay();
+
+        i1.close();
+
+        c.verify();
+        c.reset();
+
+        rsac.removeImportRegistration(EasyMock.eq(i2));
+        EasyMock.expectLastCall().once();
+
+        sr.unregister();
+        EasyMock.expectLastCall().once();
+
+        c.replay();
+
+        i2.close();
+
+        c.verify();
+    }
+
+    @Test
+    public void testCloseAll() {
+        IMocksControl c = EasyMock.createControl();
+        EndpointDescription endpoint = c.createMock(EndpointDescription.class);
+        RemoteServiceAdminCore rsac = c.createMock(RemoteServiceAdminCore.class);
+
+        c.replay();
+
+        ImportRegistrationImpl i1 = new ImportRegistrationImpl(endpoint, rsac);
+
+        ImportRegistrationImpl i2 = new ImportRegistrationImpl(i1);
+
+        ImportRegistrationImpl i3 = new ImportRegistrationImpl(i2);
+
+        assertEquals(i1, i1.getParent());
+        assertEquals(i1, i2.getParent());
+        assertEquals(i1, i3.getParent());
+
+        c.verify();
+        c.reset();
+
+        rsac.removeImportRegistration(EasyMock.eq(i2));
+        EasyMock.expectLastCall().once();
+
+        c.replay();
+
+        i2.close();
+
+        c.verify();
+        c.reset();
+
+        rsac.removeImportRegistration(EasyMock.eq(i1));
+        EasyMock.expectLastCall().once();
+        rsac.removeImportRegistration(EasyMock.eq(i3));
+        EasyMock.expectLastCall().once();
+
+        c.replay();
+        i3.closeAll();
+        c.verify();
+    }
+}

http://git-wip-us.apache.org/repos/asf/aries-rsa/blob/d73a3a7f/rsa/src/test/java/org/apache/cxf/dosgi/dsw/service/RemoteServiceAdminCoreTest.java
----------------------------------------------------------------------
diff --git a/rsa/src/test/java/org/apache/cxf/dosgi/dsw/service/RemoteServiceAdminCoreTest.java b/rsa/src/test/java/org/apache/cxf/dosgi/dsw/service/RemoteServiceAdminCoreTest.java
new file mode 100644
index 0000000..b0f58eb
--- /dev/null
+++ b/rsa/src/test/java/org/apache/cxf/dosgi/dsw/service/RemoteServiceAdminCoreTest.java
@@ -0,0 +1,517 @@
+/**
+ * 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.cxf.dosgi.dsw.service;
+
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Dictionary;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.cxf.dosgi.dsw.api.DistributionProvider;
+import org.apache.cxf.dosgi.dsw.api.Endpoint;
+import org.easymock.EasyMock;
+import org.easymock.IAnswer;
+import org.easymock.IMocksControl;
+import org.junit.Assert;
+import org.junit.Test;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceListener;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.packageadmin.PackageAdmin;
+import org.osgi.service.remoteserviceadmin.EndpointDescription;
+import org.osgi.service.remoteserviceadmin.ExportRegistration;
+import org.osgi.service.remoteserviceadmin.ImportRegistration;
+import org.osgi.service.remoteserviceadmin.RemoteConstants;
+
+import static org.easymock.EasyMock.anyObject;
+import static org.easymock.EasyMock.isA;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+@SuppressWarnings({
+    "rawtypes", "unchecked", "deprecation"
+   })
+public class RemoteServiceAdminCoreTest {
+
+    private static final String MYCONFIG = "myconfig";
+
+    @Test
+    public void testDontExportOwnServiceProxies() throws InvalidSyntaxException {
+        IMocksControl c = EasyMock.createControl();
+        Bundle b = c.createMock(Bundle.class);
+        BundleContext bc = c.createMock(BundleContext.class);
+
+        EasyMock.expect(bc.getBundle()).andReturn(b).anyTimes();
+        bc.addServiceListener(EasyMock.<ServiceListener>anyObject(), EasyMock.<String>anyObject());
+        EasyMock.expectLastCall().anyTimes();
+        bc.removeServiceListener(EasyMock.<ServiceListener>anyObject());
+        EasyMock.expectLastCall().anyTimes();
+
+        Dictionary<String, String> d = new Hashtable<String, String>();
+        EasyMock.expect(b.getHeaders()).andReturn(d).anyTimes();
+
+        ServiceReference sref = c.createMock(ServiceReference.class);
+        EasyMock.expect(sref.getBundle()).andReturn(b).anyTimes();
+        EasyMock.expect(sref.getPropertyKeys())
+            .andReturn(new String[]{"objectClass", "service.exported.interfaces"}).anyTimes();
+        EasyMock.expect(sref.getProperty("objectClass")).andReturn(new String[] {"a.b.C"}).anyTimes();
+        EasyMock.expect(sref.getProperty(RemoteConstants.SERVICE_IMPORTED)).andReturn(true).anyTimes();
+        EasyMock.expect(sref.getProperty("service.exported.interfaces")).andReturn("*").anyTimes();
+
+        DistributionProvider provider = c.createMock(DistributionProvider.class);
+
+        c.replay();
+
+        RemoteServiceAdminCore rsaCore = new RemoteServiceAdminCore(bc, bc, provider);
+
+        // must return an empty List as sref if from the same bundle
+        List<ExportRegistration> exRefs = rsaCore.exportService(sref, null);
+
+        assertNotNull(exRefs);
+        assertEquals(0, exRefs.size());
+
+        // must be empty
+        assertEquals(rsaCore.getExportedServices().size(), 0);
+
+        c.verify();
+    }
+
+    @Test
+    public void testImport() {
+        IMocksControl c = EasyMock.createNiceControl();
+        Bundle b = c.createMock(Bundle.class);
+        BundleContext bc = c.createMock(BundleContext.class);
+
+        Dictionary<String, String> d = new Hashtable<String, String>();
+        EasyMock.expect(b.getHeaders()).andReturn(d).anyTimes();
+
+        EasyMock.expect(bc.getBundle()).andReturn(b).anyTimes();
+        EasyMock.expect(b.getSymbolicName()).andReturn("BundleName").anyTimes();
+
+        EndpointDescription endpoint = creatEndpointDesc("unsupportedConfiguration");
+
+        DistributionProvider provider = c.createMock(DistributionProvider.class);
+        EasyMock.expect(provider.getSupportedTypes())
+            .andReturn(new String[]{MYCONFIG}).atLeastOnce();
+        c.replay();
+
+        RemoteServiceAdminCore rsaCore = new RemoteServiceAdminCore(bc, bc, provider);
+
+        // must be null as the endpoint doesn't contain any usable configurations
+        assertNull(rsaCore.importService(endpoint));
+        // must be empty
+        assertEquals(0, rsaCore.getImportedEndpoints().size());
+
+        EndpointDescription endpoint2 = creatEndpointDesc(MYCONFIG);
+
+        ImportRegistration ireg = rsaCore.importService(endpoint2);
+        assertNotNull(ireg);
+
+        assertEquals(1, rsaCore.getImportedEndpoints().size());
+
+        // lets import the same endpoint once more -> should get a copy of the ImportRegistration
+        ImportRegistration ireg2 = rsaCore.importService(endpoint2);
+        assertNotNull(ireg2);
+        assertEquals(2, rsaCore.getImportedEndpoints().size());
+
+        assertEquals(ireg.getImportReference(), (rsaCore.getImportedEndpoints().toArray())[0]);
+
+        assertEquals(ireg.getImportReference().getImportedEndpoint(), ireg2.getImportReference()
+            .getImportedEndpoint());
+
+        // remove the registration
+
+        // first call shouldn't remove the import
+        ireg2.close();
+        assertEquals(1, rsaCore.getImportedEndpoints().size());
+
+        // second call should really close and remove the import
+        ireg.close();
+        assertEquals(0, rsaCore.getImportedEndpoints().size());
+
+        c.verify();
+    }
+
+    private EndpointDescription creatEndpointDesc(String configType) {
+        Map<String, Object> p = new HashMap<String, Object>();
+        p.put(RemoteConstants.ENDPOINT_ID, "http://google.de");
+        p.put(Constants.OBJECTCLASS, new String[] {
+            "es.schaaf.my.class"
+        });
+        p.put(RemoteConstants.SERVICE_IMPORTED_CONFIGS, configType);
+        EndpointDescription endpoint = new EndpointDescription(p);
+        return endpoint;
+    }
+
+    @Test
+    public void testExport() throws Exception {
+        BundleContext bc = EasyMock.createMock(BundleContext.class);
+        EasyMock.expect(bc.getProperty(Constants.FRAMEWORK_VERSION)).andReturn(null).anyTimes();
+        bc.addServiceListener(EasyMock.<ServiceListener>anyObject(), EasyMock.<String>anyObject());
+        EasyMock.expectLastCall().anyTimes();
+        bc.removeServiceListener(EasyMock.<ServiceListener>anyObject());
+        EasyMock.expectLastCall().anyTimes();
+        EasyMock.expect(bc.getServiceReferences(EasyMock.<String>anyObject(),
+                                                EasyMock.<String>anyObject())).andReturn(null).anyTimes();
+        EasyMock.expect(bc.getAllServiceReferences(EasyMock.<String>anyObject(),
+                                                   EasyMock.<String>anyObject())).andReturn(null).anyTimes();
+
+        Bundle b = createDummyRsaBundle(bc);
+
+        final Map<String, Object> sProps = new HashMap<String, Object>();
+        sProps.put("objectClass", new String[] {"java.lang.Runnable"});
+        sProps.put("service.id", 51L);
+        sProps.put("myProp", "myVal");
+        sProps.put("service.exported.interfaces", "*");
+        ServiceReference sref = mockServiceReference(sProps);
+
+        Runnable svcObject = EasyMock.createNiceMock(Runnable.class);
+        EasyMock.replay(svcObject);
+
+        EasyMock.expect(bc.getService(sref)).andReturn(svcObject).anyTimes();
+        EasyMock.expect(bc.getBundle()).andReturn(b).anyTimes();
+        EasyMock.expect(bc.createFilter("(service.id=51)"))
+            .andReturn(FrameworkUtil.createFilter("(service.id=51)")).anyTimes();
+        EasyMock.expect(bc.getProperty(org.osgi.framework.Constants.FRAMEWORK_UUID)).andReturn("1111");
+        EasyMock.expect(bc.getServiceReference(PackageAdmin.class)).andReturn(null);
+        EasyMock.replay(bc);
+
+        Map<String, Object> eProps = new HashMap<String, Object>(sProps);
+        eProps.put("endpoint.id", "http://something");
+        eProps.put("service.imported.configs", new String[] {"org.apache.cxf.ws"});
+        final EndpointDescription epd = new EndpointDescription(eProps);
+        Endpoint er = new Endpoint() {
+            
+            @Override
+            public void close() throws IOException {
+            }
+            
+            @Override
+            public EndpointDescription description() {
+                return epd;
+            }
+        };
+
+        DistributionProvider handler = EasyMock.createMock(DistributionProvider.class);
+        EasyMock.expect(handler.exportService(anyObject(),
+                                              anyObject(BundleContext.class), 
+                                              anyObject(Map.class), isA(Class[].class))).andReturn(er);
+        EasyMock.replay(handler);
+
+        RemoteServiceAdminCore rsaCore = new RemoteServiceAdminCore(bc, bc, handler);
+
+        // Export the service for the first time
+        List<ExportRegistration> ereg = rsaCore.exportService(sref, null);
+        assertEquals(1, ereg.size());
+        assertNull(ereg.get(0).getException());
+        assertSame(sref, ereg.get(0).getExportReference().getExportedService());
+        EndpointDescription endpoint = ereg.get(0).getExportReference().getExportedEndpoint();
+
+        Map<String, Object> edProps = endpoint.getProperties();
+        assertEquals("http://something", edProps.get("endpoint.id"));
+        assertNotNull(edProps.get("service.imported"));
+        assertTrue(Arrays.equals(new String[] {"java.lang.Runnable"},
+                                 (Object[]) edProps.get("objectClass")));
+        assertTrue(Arrays.equals(new String[] {"org.apache.cxf.ws"},
+                                 (Object[]) edProps.get("service.imported.configs")));
+
+        // Ask to export the same service again, this should not go through the whole process again but simply return
+        // a copy of the first instance.
+        final Map<String, Object> sProps2 = new HashMap<String, Object>();
+        sProps2.put("objectClass", new String[] {"java.lang.Runnable"});
+        sProps2.put("service.id", 51L);
+        sProps2.put("service.exported.interfaces", "*");
+        ServiceReference sref2 = mockServiceReference(sProps2);
+        Map<String, Object> props2 = new HashMap<String, Object>();
+        props2.put("myProp", "myVal");
+        List<ExportRegistration> ereg2 = rsaCore.exportService(sref2, props2);
+
+        assertEquals(1, ereg2.size());
+        assertNull(ereg2.get(0).getException());
+        assertEquals(ereg.get(0).getExportReference().getExportedEndpoint().getProperties(),
+                ereg2.get(0).getExportReference().getExportedEndpoint().getProperties());
+
+        // Look at the exportedServices data structure
+        Field field = RemoteServiceAdminCore.class.getDeclaredField("exportedServices");
+        field.setAccessible(true);
+        Map<Map<String, Object>, Collection<ExportRegistration>> exportedServices =
+                (Map<Map<String, Object>, Collection<ExportRegistration>>) field.get(rsaCore);
+
+        assertEquals("One service was exported", 1, exportedServices.size());
+        assertEquals("There are 2 export registrations (identical copies)",
+                2, exportedServices.values().iterator().next().size());
+
+        // Unregister one of the exports
+        rsaCore.removeExportRegistration((ExportRegistrationImpl) ereg.get(0));
+        assertEquals("One service was exported", 1, exportedServices.size());
+        assertEquals("There 1 export registrations left",
+                1, exportedServices.values().iterator().next().size());
+
+        // Unregister the other export
+        rsaCore.removeExportRegistration((ExportRegistrationImpl) ereg2.get(0));
+        assertEquals("No more exported services", 0, exportedServices.size());
+    }
+
+    private Bundle createDummyRsaBundle(BundleContext bc) {
+        Bundle b = EasyMock.createNiceMock(Bundle.class);
+        EasyMock.expect(b.getBundleContext()).andReturn(bc).anyTimes();
+        EasyMock.expect(b.getSymbolicName()).andReturn("rsabundle").anyTimes();
+        EasyMock.expect(b.getHeaders()).andReturn(new Hashtable<String, String>()).anyTimes();
+        EasyMock.replay(b);
+        return b;
+    }
+
+    @Test
+    public void testExportException() throws Exception {
+        BundleContext bc = EasyMock.createNiceMock(BundleContext.class);
+
+        Bundle b = createDummyRsaBundle(bc);
+
+        final Map<String, Object> sProps = new HashMap<String, Object>();
+        sProps.put("objectClass", new String[] {"java.lang.Runnable"});
+        sProps.put("service.id", 51L);
+        sProps.put("service.exported.interfaces", "*");
+        ServiceReference sref = mockServiceReference(sProps);
+
+        Runnable svcObject = EasyMock.createNiceMock(Runnable.class);
+        EasyMock.replay(svcObject);
+
+        EasyMock.expect(bc.getService(sref)).andReturn(svcObject).anyTimes();
+        EasyMock.expect(bc.getBundle()).andReturn(b).anyTimes();
+        EasyMock.replay(bc);
+
+        Map<String, Object> eProps = new HashMap<String, Object>(sProps);
+        eProps.put("endpoint.id", "http://something");
+        eProps.put("service.imported.configs", new String[] {"org.apache.cxf.ws"});
+
+        DistributionProvider handler = EasyMock.createMock(DistributionProvider.class);
+        EasyMock.expect(handler.exportService(anyObject(),
+                                              anyObject(BundleContext.class), 
+                                              anyObject(Map.class), isA(Class[].class))).andThrow(new TestException());
+        EasyMock.replay(handler);
+
+        RemoteServiceAdminCore rsaCore = new RemoteServiceAdminCore(bc, bc, handler);
+
+        List<ExportRegistration> ereg = rsaCore.exportService(sref, sProps);
+        assertEquals(1, ereg.size());
+        assertTrue(ereg.get(0).getException() instanceof TestException);
+
+        // Look at the exportedServices data structure
+        Field field = RemoteServiceAdminCore.class.getDeclaredField("exportedServices");
+        field.setAccessible(true);
+        Map<Map<String, Object>, Collection<ExportRegistration>> exportedServices =
+                (Map<Map<String, Object>, Collection<ExportRegistration>>) field.get(rsaCore);
+
+        assertEquals("One service was exported", 1, exportedServices.size());
+        assertEquals("There is 1 export registration",
+                1, exportedServices.values().iterator().next().size());
+
+    }
+
+    private ServiceReference mockServiceReference(final Map<String, Object> sProps) throws ClassNotFoundException {
+        BundleContext bc = EasyMock.createNiceMock(BundleContext.class);
+
+        Bundle b = EasyMock.createNiceMock(Bundle.class);
+        EasyMock.expect(b.getBundleContext()).andReturn(bc).anyTimes();
+        EasyMock.expect((Class)b.loadClass(Runnable.class.getName())).andReturn(Runnable.class);
+        EasyMock.replay(b);
+
+        EasyMock.expect(bc.getBundle()).andReturn(b).anyTimes();
+        EasyMock.replay(bc);
+
+        ServiceReference sref = EasyMock.createNiceMock(ServiceReference.class);
+        EasyMock.expect(sref.getBundle()).andReturn(b).anyTimes();
+        EasyMock.expect(sref.getPropertyKeys()).andReturn(sProps.keySet().toArray(new String[] {})).anyTimes();
+        EasyMock.expect(sref.getProperty((String) EasyMock.anyObject())).andAnswer(new IAnswer<Object>() {
+            @Override
+            public Object answer() throws Throwable {
+                return sProps.get(EasyMock.getCurrentArguments()[0]);
+            }
+        }).anyTimes();
+        EasyMock.replay(sref);
+        return sref;
+    }
+
+    @SuppressWarnings("serial")
+    private static class TestException extends RuntimeException {
+    }
+    
+    @Test
+    public void testOverlayProperties() {
+        Map<String, Object> sProps = new HashMap<String, Object>();
+        Map<String, Object> aProps = new HashMap<String, Object>();
+
+        RemoteServiceAdminCore.overlayProperties(sProps, aProps);
+        assertEquals(0, sProps.size());
+
+        sProps.put("aaa", "aval");
+        sProps.put("bbb", "bval");
+        sProps.put(Constants.OBJECTCLASS, new String[] {"X"});
+        sProps.put(Constants.SERVICE_ID, 17L);
+
+        aProps.put("AAA", "achanged");
+        aProps.put("CCC", "CVAL");
+        aProps.put(Constants.OBJECTCLASS, new String[] {"Y"});
+        aProps.put(Constants.SERVICE_ID.toUpperCase(), 51L);
+
+        Map<String, Object> aPropsOrg = new HashMap<String, Object>(aProps);
+        RemoteServiceAdminCore.overlayProperties(sProps, aProps);
+        assertEquals("The additional properties should not be modified", aPropsOrg, aProps);
+
+        assertEquals(5, sProps.size());
+        assertEquals("achanged", sProps.get("aaa"));
+        assertEquals("bval", sProps.get("bbb"));
+        assertEquals("CVAL", sProps.get("CCC"));
+        assertTrue("Should not be possible to override the objectClass property",
+                Arrays.equals(new String[] {"X"}, (Object[]) sProps.get(Constants.OBJECTCLASS)));
+        assertEquals("Should not be possible to override the service.id property",
+                17L, sProps.get(Constants.SERVICE_ID));
+    }
+    
+    @Test
+    public void testOverlayProperties2() {
+        Map<String, Object> original = new HashMap<String, Object>();
+
+        original.put("MyProp", "my value");
+        original.put(Constants.OBJECTCLASS, "myClass");
+
+        Map<String, Object> copy = new HashMap<String, Object>();
+        copy.putAll(original);
+
+        // nothing should change here
+        Map<String, Object> overload = new HashMap<String, Object>();
+        RemoteServiceAdminCore.overlayProperties(copy, overload);
+
+        assertEquals(original.size(), copy.size());
+        for (Object key : original.keySet()) {
+            assertEquals(original.get(key), copy.get(key));
+        }
+
+        copy.clear();
+        copy.putAll(original);
+
+        // a property should be added
+        overload = new HashMap<String, Object>();
+        overload.put("new", "prop");
+
+        RemoteServiceAdminCore.overlayProperties(copy, overload);
+
+        assertEquals(original.size() + 1, copy.size());
+        for (Object key : original.keySet()) {
+            assertEquals(original.get(key), copy.get(key));
+        }
+        assertNotNull(overload.get("new"));
+        assertEquals("prop", overload.get("new"));
+
+        copy.clear();
+        copy.putAll(original);
+
+        // only one property should be added
+        overload = new HashMap<String, Object>();
+        overload.put("new", "prop");
+        overload.put("NEW", "prop");
+
+        RemoteServiceAdminCore.overlayProperties(copy, overload);
+
+        assertEquals(original.size() + 1, copy.size());
+        for (Object key : original.keySet()) {
+            assertEquals(original.get(key), copy.get(key));
+        }
+        assertNotNull(overload.get("new"));
+        assertEquals("prop", overload.get("new"));
+
+        copy.clear();
+        copy.putAll(original);
+
+        // nothing should change here
+        overload = new HashMap<String, Object>();
+        overload.put(Constants.OBJECTCLASS, "assd");
+        overload.put(Constants.SERVICE_ID, "asasdasd");
+        RemoteServiceAdminCore.overlayProperties(copy, overload);
+
+        assertEquals(original.size(), copy.size());
+        for (Object key : original.keySet()) {
+            assertEquals(original.get(key), copy.get(key));
+        }
+
+        copy.clear();
+        copy.putAll(original);
+
+        // overwrite own prop
+        overload = new HashMap<String, Object>();
+        overload.put("MyProp", "newValue");
+        RemoteServiceAdminCore.overlayProperties(copy, overload);
+
+        assertEquals(original.size(), copy.size());
+        for (Object key : original.keySet()) {
+            if (!"MyProp".equals(key)) {
+                assertEquals(original.get(key), copy.get(key));
+            }
+        }
+        assertEquals("newValue", copy.get("MyProp"));
+
+        copy.clear();
+        copy.putAll(original);
+
+        // overwrite own prop in different case
+        overload = new HashMap<String, Object>();
+        overload.put("MYPROP", "newValue");
+        RemoteServiceAdminCore.overlayProperties(copy, overload);
+
+        assertEquals(original.size(), copy.size());
+        for (Object key : original.keySet()) {
+            if (!"MyProp".equals(key)) {
+                assertEquals(original.get(key), copy.get(key));
+            }
+        }
+        assertEquals("newValue", copy.get("MyProp"));
+    }
+    
+    @Test
+    public void testCreateEndpointProps() {
+        BundleContext bc = EasyMock.createNiceMock(BundleContext.class);
+        EasyMock.expect(bc.getProperty("org.osgi.framework.uuid")).andReturn("some_uuid1");
+        EasyMock.replay(bc);
+
+        Map<String, Object> sd = new HashMap<String, Object>();
+        sd.put(org.osgi.framework.Constants.SERVICE_ID, 42);
+        DistributionProvider provider = null;
+        RemoteServiceAdminCore rsa = new RemoteServiceAdminCore(bc, bc, provider);
+        Map<String, Object> props = rsa.createEndpointProps(sd, new Class[]{String.class});
+
+        Assert.assertFalse(props.containsKey(org.osgi.framework.Constants.SERVICE_ID));
+        assertEquals(42, props.get(RemoteConstants.ENDPOINT_SERVICE_ID));
+        assertEquals("some_uuid1", props.get(RemoteConstants.ENDPOINT_FRAMEWORK_UUID));
+        assertEquals(Arrays.asList("java.lang.String"),
+                     Arrays.asList((Object[]) props.get(org.osgi.framework.Constants.OBJECTCLASS)));
+        assertEquals("0.0.0", props.get("endpoint.package.version.java.lang"));
+    }
+}

http://git-wip-us.apache.org/repos/asf/aries-rsa/blob/d73a3a7f/rsa/src/test/java/org/apache/cxf/dosgi/dsw/service/StringPlusTest.java
----------------------------------------------------------------------
diff --git a/rsa/src/test/java/org/apache/cxf/dosgi/dsw/service/StringPlusTest.java b/rsa/src/test/java/org/apache/cxf/dosgi/dsw/service/StringPlusTest.java
new file mode 100644
index 0000000..5447966
--- /dev/null
+++ b/rsa/src/test/java/org/apache/cxf/dosgi/dsw/service/StringPlusTest.java
@@ -0,0 +1,63 @@
+/**
+ * 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.cxf.dosgi.dsw.service;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+
+public class StringPlusTest {
+
+    @Test
+    public void testSplitString() {
+        String[] values = StringPlus.normalize("1, 2");
+        assertEquals(2, values.length);
+        assertEquals(values[0], "1");
+        assertEquals(values[1], "2");
+    }
+
+    @Test
+    public void testNormalizeStringPlus() {
+        String s1 = "s1";
+        String s2 = "s2";
+        String s3 = "s3";
+
+        String[] sa = new String[] {
+            s1, s2, s3
+        };
+
+        Collection<Object> sl = new ArrayList<Object>(4);
+        sl.add(s1);
+        sl.add(s2);
+        sl.add(s3);
+        sl.add(new Object()); // must be skipped
+
+        assertArrayEquals(null, StringPlus.normalize(new Object()));
+        assertArrayEquals(new String[] {
+            s1
+        }, StringPlus.normalize(s1));
+        assertArrayEquals(sa, StringPlus.normalize(sa));
+        assertArrayEquals(sa, StringPlus.normalize(sl));
+    }
+
+}