All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.paremus.dosgi.net.impl.RemoteServiceAdminImpl Maven / Gradle / Ivy

Go to download

An implementation of OSGi Remote Service Admin, enhanced to provide support for OSGi Promises and streaming using OSGi PushStreams.

The newest version!
/*-
 * #%L
 * com.paremus.dosgi.net
 * %%
 * Copyright (C) 2016 - 2019 Paremus Ltd
 * %%
 * Licensed under the Fair Source License, Version 0.9 (the "License");
 * 
 * See the NOTICE.txt file distributed with this work for additional 
 * information regarding copyright ownership. You may not use this file 
 * except in compliance with the License. For usage restrictions see the 
 * LICENSE.txt file distributed with this work
 * #L%
 */
package com.paremus.dosgi.net.impl;

import static com.paremus.dosgi.net.impl.RegistrationState.OPEN;
import static com.paremus.dosgi.net.wireformat.Protocol_V1.toSignature;
import static java.util.Collections.emptyList;
import static java.util.Collections.singleton;
import static java.util.Optional.ofNullable;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toMap;
import static java.util.stream.Collectors.toSet;
import static org.osgi.framework.Constants.FRAMEWORK_UUID;
import static org.osgi.framework.Constants.OBJECTCLASS;
import static org.osgi.framework.Constants.SERVICE_ID;
import static org.osgi.framework.ServiceException.UNREGISTERED;
import static org.osgi.service.remoteserviceadmin.RemoteConstants.ENDPOINT_FRAMEWORK_UUID;
import static org.osgi.service.remoteserviceadmin.RemoteConstants.ENDPOINT_ID;
import static org.osgi.service.remoteserviceadmin.RemoteConstants.ENDPOINT_SERVICE_ID;
import static org.osgi.service.remoteserviceadmin.RemoteConstants.SERVICE_IMPORTED_CONFIGS;
import static org.osgi.service.remoteserviceadmin.RemoteConstants.SERVICE_INTENTS;

import java.lang.reflect.Method;
import java.net.URI;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;

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.ServiceException;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.Version;
import org.osgi.framework.launch.Framework;
import org.osgi.framework.wiring.BundleWiring;
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.RemoteConstants;
import org.osgi.util.converter.Converters;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.paremus.dosgi.net.client.ClientConnectionManager;
import com.paremus.dosgi.net.config.ExportedServiceConfig;
import com.paremus.dosgi.net.config.TransportConfig;
import com.paremus.dosgi.net.serialize.SerializationType;
import com.paremus.dosgi.net.serialize.Serializer;
import com.paremus.dosgi.net.server.RemotingProvider;
import com.paremus.dosgi.net.server.ServiceInvoker;
import com.paremus.dosgi.scoping.rsa.MultiFrameworkRemoteServiceAdmin;

import io.netty.util.Timer;
import io.netty.util.concurrent.EventExecutorGroup;

public class RemoteServiceAdminImpl implements MultiFrameworkRemoteServiceAdmin {
	
	private static final String CONFIDENTIALITY_MESSAGE = "confidentiality.message";

	private static final Logger LOG = LoggerFactory.getLogger(RemoteServiceAdminImpl.class);
	
	private static final EndpointPermission exportPermission = new EndpointPermission("*",
	        EndpointPermission.EXPORT);
	
	private final Map, Set>> exports
		= new HashMap<>();
	private final Map>> imports
		= new HashMap<>();

	private final Framework defaultFramework;
	private final RemoteServiceAdminEventPublisher publisher;
	
	private final List remoteProviders;
	
	private final ClientConnectionManager clientConnectionManager;
	
	private final ProxyHostBundleFactory proxyHostBundleFactory;

	private final EventExecutorGroup serverWorkers;
	private final EventExecutorGroup clientWorkers;

	private final Timer timer;

	private TransportConfig config;

	private final List intents;

	private final RemoteServiceAdminFactoryImpl factory;
	
	public RemoteServiceAdminImpl(RemoteServiceAdminFactoryImpl factory, Framework defaultFramework, RemoteServiceAdminEventPublisher publisher,
			List remoteProviders, ClientConnectionManager ccm, List intents,
			ProxyHostBundleFactory phbf, EventExecutorGroup serverWorkers, EventExecutorGroup clientWorkers,
			Timer timer, TransportConfig config) {
		this.factory = factory;
		this.defaultFramework = defaultFramework;
		this.publisher = publisher;
		this.remoteProviders = remoteProviders;
		this.clientConnectionManager = ccm;
		this.intents = Collections.unmodifiableList(intents);
		this.proxyHostBundleFactory = phbf;
		this.serverWorkers = serverWorkers;
		this.clientWorkers = clientWorkers;
		this.timer = timer;
		this.config = config;
	}

	@Override
	public Collection exportService(ServiceReference reference, Map properties) {
		return exportService(defaultFramework, reference, properties);
	}
	
    @Override
    public Collection exportService(Framework framework, ServiceReference ref, 
    		Map additionalProperties) {
        LOG.debug("exportService: {}", ref);

        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            try {
                sm.checkPermission(exportPermission);
            }
            catch (SecurityException se) {
                return Collections.singletonList(new ExportRegistrationImpl(ref, this, se));
            }
        }

        PrivilegedAction> exportAction = 
        		() -> privilegedExportService(framework, ref, additionalProperties);

        return AccessController.doPrivileged(exportAction);
    }

	private Collection privilegedExportService(Framework framework, ServiceReference ref,
			Map props) {
		String target = config.endpoint_export_target();
		
		if(!target.isEmpty()) {
			try {
				if(!FrameworkUtil.createFilter(target).match(ref)) {
					LOG.debug("The service reference {} is excluded by the configured export filter {} and will not be exported", ref, target);
					return emptyList();
				}
			} catch (InvalidSyntaxException e) {
				LOG.error("Unable to filter the export operation due to a filter syntax error.", e);
				return singleton(new ExportRegistrationImpl(ref, this, e));
			}
		}
		
		ExportRegistrationImpl reg;
		synchronized (exports) {
			Map, Set> exportsForFramework = 
					exports.computeIfAbsent(framework, k -> new HashMap<>());
			ExportRegistrationImpl tmpReg;
			try {
				EndpointDescription endpoint = createEndpointDescription(framework, ref, props);
				if(endpoint == null) {
					return Collections.emptySet();
				}
				tmpReg = new ExportRegistrationImpl(ref, props, endpoint, framework, this);
			} catch (IllegalArgumentException ex) {
				publisher.notifyExportError(ref, ex);
				throw ex;
			} catch (UnsupportedOperationException ex) {
				publisher.notifyExportError(ref, ex);
				return Collections.emptySet();
			} catch (Exception e) {
				publisher.notifyExportError(ref, e);
				tmpReg = new ExportRegistrationImpl(ref, this, e);
			}
			reg = tmpReg;
			exportsForFramework.compute(ref, (k,v) -> {
				return Stream.concat(Stream.of(reg), ofNullable(v).map(Set::stream).orElse(Stream.empty()))
						.collect(toSet());
			});
		}

		switch(reg.getState()) {
			case PRE_INIT :
				notifyExport(reg, reg.start());
				break;
			case OPEN:
				LOG.info("The service {} already had an open export from this RSA", ref);
				break;
			case ERROR:
				LOG.info("The service {} failed to export from this RSA", ref);
				break;
			case CLOSED:
			default:
				break;
		}
		
		return singleton(reg);
	}

	private void notifyExport(ExportRegistrationImpl reg, RegistrationState registrationState) {
		switch(registrationState) {
			case ERROR:
				publisher.notifyExportError(reg.getServiceReference(), reg.getException());
				break;
			case OPEN:
				ExportReference er = reg.getExportReference();
				publisher.notifyExport(er.getExportedService(), er.getExportedEndpoint());
				break;
			case PRE_INIT:
				throw new IllegalStateException("The registration should have been initialized");
			case CLOSED:
			default:
				break;
		
		}
	}

	private EndpointDescription createEndpointDescription(Framework framework, 
			ServiceReference ref, Map additionalProperties) {
		return createEndpointDescription(framework, ref, additionalProperties, UUID.randomUUID());
	}
	
	private EndpointDescription createEndpointDescription(Framework framework, 
			ServiceReference ref, Map additionalProperties, UUID id) {
		
		// gather properties from service
        Map serviceProperties = new HashMap();
        for (String k : ref.getPropertyKeys()) {
        	if(k.charAt(0) != '.') {
        		serviceProperties.put(k, ref.getProperty(k));
        	}
        }

        // overlay properties with any additional properties
        if (additionalProperties != null) {
            overlayProperties(serviceProperties, additionalProperties);
        }

        ExportedServiceConfig config;
		try {
			config = Converters.standardConverter().convert(
					serviceProperties).to(ExportedServiceConfig.class);
		} catch (Exception ex) {
			LOG.error("A failure occurred trying to export service {}", ref, ex);
			return null;
		}
        
        String[] configs = config.service_exported_configs();
		if(configs.length > 0 && !Arrays.asList(configs)
				.contains("com.paremus.dosgi.net")) {
        	LOG.info("Unable to export the service {} as it only supports the configuration types {}",
        			ref, configs);
        	return null;
        }

        Set requiredIntents = Stream.concat(
        		Arrays.stream(config.service_exported_intents()), 
	            Arrays.stream(config.service_exported_intents_extra()))
        			.collect(toSet());
        	
        Set supportedIntents = new HashSet<>(intents);
        Set unsupported = requiredIntents.stream()
        	.filter(s -> !supportedIntents.contains(s))
        	.collect(Collectors.toSet());
        
        if (!unsupported.isEmpty()) {
        	LOG.info("Unable to export the service {} as the following intents are not supported {}"
		                            , ref, unsupported);
        	throw new UnsupportedOperationException(unsupported.toString());
		}
        
        Object service = ref.getBundle().getBundleContext().getService(ref);
        if(service == null) {
        	LOG.info("Unable to obtain the service object for {}", ref);
        	throw new ServiceException("The service object was null and so cannot be exported", UNREGISTERED);
        }
        
        List objectClass = Arrays.asList(config.objectClass());
        
        List> exportedClasses = Arrays.stream(config.service_exported_interfaces())
        		.flatMap(s -> "*".equals(s) ? objectClass.stream() : Stream.of(s))
        		.filter(s -> objectClass.contains(s))
        		.map(n -> {
	        			try {
	        				ClassLoader loader = service.getClass().getClassLoader();
	        				Class toReturn = (loader != null) ? loader.loadClass(n) : Class.forName(n);
							return toReturn;
		        		} catch (ClassNotFoundException cnfe) {
	        				LOG.error("The service {} exports the type {} but cannot load it.", ref, n);
	        				return null;
	        			}
        			})
        		.filter(c -> c != null)
        		.filter(c -> c.isInstance(service))
        		.collect(toList());

        // no interface can be resolved..why?
        if (exportedClasses.isEmpty()) {
        	LOG.error("Unable to obtain any exported types for service {} with exported interfaces {}", 
        			ref, config.service_exported_interfaces());
        	throw new IllegalArgumentException("Unable to load any exported types for the service " + ref + 
        			" with exported interfaces " + config.service_exported_interfaces());
        }

        try {
	        Predicate remotingSelector;
	        
	        if(requiredIntents.contains(CONFIDENTIALITY_MESSAGE) ||
	        		requiredIntents.contains("osgi.confidential")) {
	        	remotingSelector = rp -> rp.isSecure();
	        } else {
	        	remotingSelector = rp -> true;
	        }
	        
	        List validProviders = remoteProviders.stream()
	        		.filter(remotingSelector)
	        		.collect(Collectors.toList());
	        List invalidProviders = remoteProviders.stream()
	        		.filter(remotingSelector.negate())
	        		.collect(Collectors.toList());
	        
	        invalidProviders.stream()
	         	.forEach(rp -> rp.unregisterService(id));
	        
	        if(validProviders.isEmpty()) {
	        	LOG.debug("There are no remoting providers for this RSA able to export the service {}", ref);
	        	return null;
	        }
	         
	        SerializationType serializationType;
	        try {
	        	serializationType = config.com_paremus_dosgi_net_serialization();
	        } catch (Exception e) {
	        	throw new IllegalArgumentException("Invalid com.paremus.dosgi.net.serialization property", e);
	        }
	        
	        Bundle classSpace = FrameworkUtil.getBundle(service.getClass());
	        
			Serializer serializer = serializationType
	        		 .getFactory().create(classSpace == null ? ref.getBundle() : classSpace);
	         
	        SortedMap methodMappings = exportedClasses.stream()
	        		 .map(Class::getMethods)
	        		 .flatMap(Arrays::stream)
	        		 .collect(Collectors.toMap(m -> toSignature(m), Function.identity(),
	        				 (a,b) -> a, TreeMap::new));
	         
	        Function invoker = 
	        		rp -> new ServiceInvoker(rp, id, serializer, service, methodMappings
	        		.values().toArray(new Method[0]), serverWorkers, timer);
	       
	        List connectionStrings = validProviders.stream()
	        		 .map(rp -> rp.registerService(id, invoker.apply(rp)))
	        		 .flatMap(Collection::stream)
	        		 .map(URI::toString)
	        		 .collect(toList());
	         
	        if(connectionStrings.isEmpty()) {
	        	LOG.warn("No remoting providers successfully exposed the service {}", ref);
	        	throw new IllegalArgumentException("No remoting providers are able to expose the service " + ref);
	        }
	        addRSAProperties(serviceProperties, id, ref, config, exportedClasses, 
	        		supportedIntents, connectionStrings, methodMappings, framework);
	        
	        return new EndpointDescription(serviceProperties);
        } catch (Exception e) {
        	remoteProviders.stream()
        		.forEach(rp -> rp.unregisterService(id));
        	throw e;
        }
	}

	private void addRSAProperties(Map serviceProperties, UUID id, 
			ServiceReference ref, ExportedServiceConfig config, List> exportedClasses,
			Set intents, List connectionStrings, Map methodMappings, Framework framework) {
		
		Set packages = exportedClasses.stream()
					.map(Class::getPackage)
					.map(Package::getName)
					.collect(toSet());
		
		BundleWiring wiring = ref.getBundle().adapt(BundleWiring.class);
		Map importedVersions = wiring.getRequiredWires("osgi.wiring.package").stream()
			.map(bw -> bw.getCapability().getAttributes())
			.filter(m -> packages.contains(m.get("osgi.wiring.package")))
			.collect(toMap(m -> (String) m.get("osgi.wiring.package"), m -> (Version) m.get("version")));
		
		packages.removeAll(importedVersions.keySet());
		
		wiring.getCapabilities("osgi.wiring.package").stream()
			.map(c -> c.getAttributes())
			.filter(m -> packages.contains(m.get("osgi.wiring.package")))
			.forEach(m -> importedVersions.putIfAbsent((String)m.get("osgi.wiring.package"), (Version)m.get("version")));
		
		importedVersions.entrySet().stream()
			.forEach(e -> serviceProperties.put(RemoteConstants.ENDPOINT_PACKAGE_VERSION_ + e.getKey(), e.getValue().toString()));
        
        serviceProperties.put(ENDPOINT_ID, id.toString());
        serviceProperties.put(ENDPOINT_FRAMEWORK_UUID, framework.getBundleContext().getProperty(FRAMEWORK_UUID));
        serviceProperties.put(ENDPOINT_SERVICE_ID, ref.getProperty(SERVICE_ID));
        serviceProperties.put(SERVICE_INTENTS, 
        		Stream.concat(intents.stream(), 
        				ofNullable(config.service_intents())
        					.map(Arrays::stream)
        					.orElse(Stream.empty()))
        			.collect(toList()));
        serviceProperties.put("com.paremus.dosgi.net", connectionStrings);
        serviceProperties.put(SERVICE_IMPORTED_CONFIGS, "com.paremus.dosgi.net");
        serviceProperties.put(OBJECTCLASS, exportedClasses.stream()
        					.map(Class::getName)
        					.collect(toList()).toArray(new String[0]));
        
        AtomicInteger i = new AtomicInteger();
        List methodMappingData = methodMappings.keySet().stream()
        		.sequential()
        		.map(s -> new StringBuilder()
        				.append(i.getAndIncrement())
        				.append('=')
        				.append(s)
        				.toString())
        		.collect(toList());
        
        if(serviceProperties.containsKey("com.paremus.dosgi.net.methods")) {
        	throw new IllegalArgumentException("The com.paremus.dosgi.net.methods property is not user editable");
        }
        
        serviceProperties.put("com.paremus.dosgi.net.methods", methodMappingData);
        
        if(!this.config.endpoint_marker().isEmpty()) {
        	serviceProperties.put("com.paremus.dosgi.net.endpoint.marker", this.config.endpoint_marker());
        }
	}

	void removeExportRegistration(ExportRegistrationImpl exportRegistration,
			ServiceReference serviceReference) {
		synchronized (exports) {
			UUID id = exportRegistration.getId();
			// Exports that failed early may not have an id at all.
			if(id != null) {
				remoteProviders.stream()
					.forEach(rp -> {
						rp.unregisterService(id);
					});
			}
			
			exports.compute(exportRegistration.getSourceFramework(), (k,v) -> {
				Map, Set> m = 
						v == null ? new HashMap<>() : new HashMap<>(v);
				
						m.computeIfPresent(serviceReference, (ref, set) -> {
							Set s2 = set.stream()
									.filter(e -> e != exportRegistration)
									.collect(toSet());
							return s2.isEmpty() ? null : s2;
						});
				return m.isEmpty() ? null : m;
			});
		}
		ofNullable(serviceReference.getBundle())
			.map(Bundle::getBundleContext)
			.ifPresent(bc -> bc.ungetService(serviceReference));
		publisher.notifyExportRemoved(serviceReference, exportRegistration.getEndpointDescription(),
				exportRegistration.internalGetException());
	}

	@Override
	public ImportRegistration importService(EndpointDescription endpoint) {
		return importService(defaultFramework, endpoint);
	}

	@Override
	public ImportRegistration importService(Framework framework, EndpointDescription e) {
		LOG.debug("importService: {}", e);

        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            String property = doPrivGetFrameworkId(framework);
			EndpointPermission importPermission = new EndpointPermission(e,
                property, EndpointPermission.IMPORT);
            sm.checkPermission(importPermission);
        }

        PrivilegedAction importAction = () -> privilegedImportService(framework, e);

        return AccessController.doPrivileged(importAction);
	}

	private String doPrivGetFrameworkId(Framework framework) {
		PrivilegedAction getFwId = () -> framework.getBundleContext().getProperty(FRAMEWORK_UUID);
		String property = AccessController.doPrivileged(getFwId);
		return property;
	}
	
	private ImportRegistration privilegedImportService(Framework framework, EndpointDescription e) {
		
		if(!e.getConfigurationTypes().contains("com.paremus.dosgi.net")) {
			LOG.info("Unable to import the endpoint {} because it uses unsupported configuration types {}",
					e, e.getConfigurationTypes());
			return null;
		}

		Map endpointProperties = e.getProperties();
		
		String target = config.endpoint_import_target();
		if(!target.isEmpty()) {
			try {
				if(!FrameworkUtil.createFilter(target).match(new Hashtable<>(endpointProperties))) {
					LOG.debug("The endpointDescription {} is excluded by the configured import filter {} and will not be exported", e, target);
					return null;
				}
			} catch (InvalidSyntaxException ex) {
				LOG.error("Unable to filter the export operation due to a filter syntax error.", ex);
				return new ImportRegistrationImpl(e, framework, this, ex);
			}
		}
		
		ExportedServiceConfig edConfig;
		try {
			edConfig = Converters.standardConverter().convert(
			    		endpointProperties).to(ExportedServiceConfig.class);
		} catch (Exception ex) {
			LOG.error("A failure occurred trying to import endpoint {}", e, ex);
			return null;
		}
		
        Set unsupported = Stream.concat(
        		ofNullable(edConfig.service_exported_intents())
            	.map(Arrays::stream)
            	.orElse(Stream.empty()), 
            ofNullable(edConfig.service_exported_intents_extra())
            	.map(Arrays::stream)
            	.orElse(Stream.empty()))
		        	.filter(s -> !intents.contains(s))
		        	.collect(Collectors.toSet());
        
        if (!unsupported.isEmpty()) {
        	LOG.info("Unable to import the endpoint {} as the following intents are not supported {}"
		                            , e, unsupported);
        	return null;
		}
		
		UUID id = UUID.fromString(e.getId());
		
		BundleContext proxyHostContext = proxyHostBundleFactory.getProxyBundle(framework).getBundleContext();
		
		ImportRegistrationImpl reg;
		if(proxyHostContext == null) {
			// Don't bother with a stack trace
			IllegalStateException failure = new IllegalStateException(
					"The RSA host bundle context in target framework " + framework  + " is not active");
			failure.setStackTrace(new StackTraceElement[0]);
			reg = new ImportRegistrationImpl(e, framework, this, 
					failure);
		} else {
			reg = new ImportRegistrationImpl(e, framework, proxyHostContext, this, 
					clientConnectionManager, config.client_default_timeout(), 
					clientWorkers, timer);
			
			synchronized (imports) {
				imports.computeIfAbsent(framework, k -> new HashMap<>())
					.compute(id, (k,v) -> 
						Stream.concat(Stream.of(reg), 
								ofNullable(v).map(Set::stream).orElseGet(() -> Stream.empty()))
							.collect(toSet()));
			}
		}
		
		switch(reg.getState()) {
			case ERROR:
				publisher.notifyImportError(reg.getEndpointDescription(), reg.getException());
				break;
			case OPEN:
				publisher.notifyImport(reg.getServiceReference(), reg.getEndpointDescription());
				break;
			case CLOSED:
			case PRE_INIT:
				reg.asyncFail(new IllegalStateException("The registration was not fully initialized, and was found in state " + reg.getState()));
				break;
		}
		
		return reg;
	}

	/**
	 * Remove a registered ImportRegistration. Must not be called while holding
	 * a lock on the ImportRegistration
	 * @param importRegistration
	 * @param endpointId
	 */
	void removeImportRegistration(ImportRegistrationImpl importRegistration,
			String endpointId) {
		synchronized (imports) {
			imports.computeIfPresent(importRegistration.getTargetFramework(), (k,v) -> {
				Map> m2 = new HashMap<>(v);
						m2.computeIfPresent(UUID.fromString(endpointId), (id,set) -> {
							Set s2 = set.stream()
									.filter(ir -> ir != importRegistration)
									.collect(toSet());
							return s2.isEmpty() ? null : s2;
						});
				return m2.isEmpty() ? null : m2;
			});
		}
		
		publisher.notifyImportRemoved(importRegistration.getServiceReference(), 
				importRegistration.getEndpointDescription(), importRegistration.internalGetException());
	}

	/**
	 * Notify of a failed ImportRegistration. Must not be called while holding
	 * a lock on the ImportRegistration
	 * @param importRegistration
	 * @param endpointId
	 */
	void notifyImportError(ImportRegistrationImpl importRegistration, String endpointId) {
		synchronized (imports) {
			imports.computeIfPresent(importRegistration.getTargetFramework(), (k,v) -> {
				Map> m2 = new HashMap<>(v);
						m2.computeIfPresent(UUID.fromString(endpointId), (id,set) -> {
							Set s2 = set.stream()
									.filter(ir -> ir != importRegistration)
									.collect(toSet());
							return s2.isEmpty() ? null : s2;
						});
				return m2.isEmpty() ? null : m2;
			});
		}
		
		publisher.notifyImportError(importRegistration.getEndpointDescription(), 
				importRegistration.getException());
	}

	void notifyImportUpdate(ServiceReference reference, EndpointDescription endpointDescription,
			Throwable exception) {
		publisher.notifyImportUpdate(reference, endpointDescription, exception);
	}

	@Override
	public Collection getExportedServices() {
		return getExportedServices(defaultFramework);
	}

	@Override
	public Collection getExportedServices(Framework framework) {
		
		List> toShow = factory.getRemoteServiceAdmins().stream()
			.flatMap(impl -> impl.localGetExportedServices(framework).stream())
			.collect(toList());
		
		return doGetExportedServices(framework, toShow.stream());
	}

	private List> localGetExportedServices(Framework framework) {
		List> toShow;
		synchronized (exports) {
			toShow = ofNullable(exports.get(framework))
				.map(m -> m.values().stream()
						.collect(toList()))
				.orElse(emptyList());
		}
		return toShow;
	}

	@Override
	public Collection getAllExportedServices() {
		
		Map> toShow = factory.getRemoteServiceAdmins().stream()
			.map(RemoteServiceAdminImpl::localGetAllExportedServices)
			.reduce(new HashMap<>(), (a,b) -> {
					Map> toReturn = new HashMap<>(a);
					
					b.entrySet().stream()
						.forEach(e -> toReturn.compute(e.getKey(), 
								(k,v) -> Stream.concat(v.stream(), e.getValue().stream()).collect(toSet())));
					
					return toReturn;
				});
		
		return toShow.entrySet().stream()
				.map(e -> doGetExportedServices(e.getKey(), Stream.of(e.getValue())))
				.flatMap(Collection::stream)
				.collect(toSet());
	}

	private Map> localGetAllExportedServices() {
		Map> toShow;
		synchronized (exports) {
			toShow = exports.entrySet().stream()
					.collect(toMap(Entry::getKey, e -> e.getValue().values().stream()
							.flatMap(Set::stream)
							.collect(toSet())));
		}
		return toShow;
	}
	
	private Collection doGetExportedServices(Framework framework, Stream> stream) {
		
		Predicate securityCheck;
		
		SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
        	String fwId = doPrivGetFrameworkId(framework);
        	securityCheck = er -> {
        			try {
        				sm.checkPermission(new EndpointPermission(er.getExportedEndpoint(),
        					fwId, EndpointPermission.READ));
        				return true;
        			} catch (SecurityException se) {
        				return false;
        			}
        		};
        } else {
        	securityCheck = er -> true;
        }
		
		return stream.flatMap(Set::stream)
			.filter(i -> i.getState() == OPEN)
			.map(ExportRegistration::getExportReference)
			.filter(er -> er != null)
			.filter(securityCheck)
			.collect(toSet());
	}

	@Override
	public Collection getImportedEndpoints() {
		return getImportedEndpoints(defaultFramework);
	}

	@Override
	public Collection getImportedEndpoints(Framework framework) {
		
		List> toShow = factory.getRemoteServiceAdmins().stream()
				.flatMap(impl -> impl.localGetImportedServices(framework).stream())
				.collect(toList());
		
		return doGetImportedServices(framework, toShow.stream());
	}

	private List> localGetImportedServices(Framework framework) {
		List> toShow;
		synchronized (imports) {
			toShow = ofNullable(imports.get(framework))
				.map(m -> m.values().stream()
						.collect(toList()))
				.orElse(emptyList());
		}
		return toShow;
	}

	@Override
	public Collection getAllImportedEndpoints() {
		
		Map> toShow = factory.getRemoteServiceAdmins().stream()
				.map(RemoteServiceAdminImpl::localGetAllImportedEndpoints)
				.reduce(new HashMap<>(), (a,b) -> {
						Map> toReturn = new HashMap<>(a);
						
						b.entrySet().stream()
							.forEach(e -> toReturn.compute(e.getKey(), 
									(k,v) -> Stream.concat(v.stream(), e.getValue().stream()).collect(toSet())));
						
						return toReturn;
					});
		
		return toShow.entrySet().stream()
				.map(e -> doGetImportedServices(e.getKey(), Stream.of(e.getValue())))
				.flatMap(Collection::stream)
				.collect(toSet());
	}

	private Map> localGetAllImportedEndpoints() {
		Map> toShow;
		synchronized (imports) {
			toShow = imports.entrySet().stream()
					.collect(toMap(Entry::getKey, e -> e.getValue().values().stream()
							.flatMap(Set::stream)
							.collect(toSet())));
		}
		return toShow;
	}
	
	private Collection doGetImportedServices(Framework framework, Stream> stream) {
		
		Predicate securityCheck;
		
		SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
        	String fwId = doPrivGetFrameworkId(framework);
        	securityCheck = ir -> {
        			try {
        				sm.checkPermission(new EndpointPermission(ir.getImportedEndpoint(),
        					fwId, EndpointPermission.READ));
        				return true;
        			} catch (SecurityException se) {
        				return false;
        			}
        		};
        } else {
        	securityCheck = er -> true;
        }
		
		return stream.flatMap(Set::stream)
				.filter(i -> i.getState() == OPEN)
				.map(ImportRegistration::getImportReference)
				.filter(i -> i != null)
				.filter(securityCheck)
				.collect(toSet());
	}

	/**
     * Overlays (overwrites or adds) a set of key/value pairs onto a given Map. Keys are
     * handled case-insensitively: an original mapping of fooBar=x will be
     * overwritten with FooBar=y. Mappings with {@link Constants#OBJECTCLASS}
     * and {@link Constants#SERVICE_ID} keys are not overwritten, regardless of
     * case.
     * 
     * @param serviceProperties a mutable Map of key/value pairs
     * @param additionalProperties additional key/value mappings to overlay
     * @throws NullPointerException if either argument is null
     */
    static void overlayProperties(Map serviceProperties,
                                         Map additionalProperties) {
        Objects.requireNonNull(serviceProperties, "The service properties were null");

        if (additionalProperties == null || additionalProperties.isEmpty()) {
            // nothing to do
            return;
        }

        // Maps lower case key to original key
        Map lowerKeys = new HashMap(serviceProperties.size());
        for (Entry sp : serviceProperties.entrySet()) {
            lowerKeys.put(sp.getKey().toLowerCase(), sp.getKey());
        }

        // keys that must not be overwritten
        String lowerObjClass = OBJECTCLASS.toLowerCase();
        String lowerServiceId = SERVICE_ID.toLowerCase();

        for (Entry ap : additionalProperties.entrySet()) {
            String key = ap.getKey().toLowerCase();
            if (lowerObjClass.equals(key) || lowerServiceId.equals(key)) {
                // exportService called with additional properties map that contained
                // illegal key; the key is ignored
                continue;
            }
            else if (lowerKeys.containsKey(key)) {
                String origKey = lowerKeys.get(key);
                serviceProperties.put(origKey, ap.getValue());
            }
            else {
                serviceProperties.put(ap.getKey(), ap.getValue());
                lowerKeys.put(key, ap.getKey());
            }
        }
    }

	EndpointDescription updateExport(Framework source, ServiceReference ref, 
			Map additionalProperties, UUID id, EndpointDescription previous) {
		EndpointDescription ed = null;
		try {
			ed = createEndpointDescription(source, ref, additionalProperties, id);
			publisher.notifyExportUpdate(ref, ed, null);
		} catch (Exception e) {
			publisher.notifyExportUpdate(ref, previous, e);
		}
		return ed;
	}
	
	void close() {
		Set importsToClose;
		synchronized (imports) {
			importsToClose = imports.values().stream()
				.flatMap(m -> m.values().stream())
				.flatMap(Set::stream)
				.collect(toSet());
		}
		
		importsToClose.stream()
			.forEach(ImportRegistration::close);
		
		Set exportsToClose;
		synchronized (exports) {
			exportsToClose = exports.values().stream()
				.flatMap(m -> m.values().stream())
				.flatMap(Set::stream)
				.collect(toSet());
		}
		
		exportsToClose.stream()
			.forEach(ExportRegistration::close);
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy