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

org.osgi.service.remoteserviceadmin.EndpointDescription Maven / Gradle / Ivy

There is a newer version: 5.0.0
Show newest version
/*
 * Copyright (c) OSGi Alliance (2008, 2010). All Rights Reserved.
 *
 * Licensed 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.osgi.service.remoteserviceadmin;

import static org.osgi.service.remoteserviceadmin.RemoteConstants.*;

import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import org.osgi.framework.Constants;
import org.osgi.framework.Filter;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.Version;

/**
 * A description of an endpoint that provides sufficient information for a
 * compatible distribution provider to create a connection to this endpoint
 * 
 * An Endpoint Description is easy to transfer between different systems because
 * it is property based where the property keys are strings and the values are
 * simple types. This allows it to be used as a communications device to convey
 * available endpoint information to nodes in a network.
 * 
 * An Endpoint Description reflects the perspective of an importer. That
 * is, the property keys have been chosen to match filters that are created by
 * client bundles that need a service. Therefore the map must not contain any
 * service.exported.* property and must contain the corresponding
 * service.imported.* ones.
 * 
 * The service.intents property must contain the intents provided
 * by the service itself combined with the intents added by the exporting
 * distribution provider. Qualified intents appear fully expanded on this
 * property.
 * 
 * @Immutable
 * @version $Revision: 8645 $
 */

public class EndpointDescription {
	private final Map	properties;
	private final List			interfaces;
	private final long					serviceId;
	private final String				frameworkUUID;
	private final String				id;

	/**
	 * Create an Endpoint Description from a Map.
	 * 
	 * 

* The {@link RemoteConstants#ENDPOINT_ID endpoint.id}, * {@link RemoteConstants#SERVICE_IMPORTED_CONFIGS service.imported.configs} * and objectClass properties must be set. * * @param properties The map from which to create the Endpoint Description. * The keys in the map must be type String and, since * the keys are case insensitive, there must be no duplicates with * case variation. * @throws IllegalArgumentException When the properties are not proper for * an Endpoint Description. */ public EndpointDescription(Map properties) { Map props = new TreeMap( String.CASE_INSENSITIVE_ORDER); try { props.putAll(properties); } catch (ClassCastException e) { IllegalArgumentException iae = new IllegalArgumentException( "non-String key in properties"); iae.initCause(e); throw iae; } if (props.size() < properties.size()) { throw new IllegalArgumentException( "duplicate keys with different cases in properties: " + new ArrayList(props.keySet()) .removeAll(properties.keySet())); } conditionProperties(props); this.properties = Collections.unmodifiableMap(props); /* properties must be initialized before calling the following methods */ interfaces = verifyObjectClassProperty(); serviceId = verifyLongProperty(ENDPOINT_SERVICE_ID); frameworkUUID = verifyStringProperty(ENDPOINT_FRAMEWORK_UUID); id = verifyStringProperty(ENDPOINT_ID).trim(); if (id == null) { throw new IllegalArgumentException(ENDPOINT_ID + " property must be set"); } if (getConfigurationTypes().isEmpty()) { throw new IllegalArgumentException(SERVICE_IMPORTED_CONFIGS + " property must be set and non-empty"); } } /** * Create an Endpoint Description based on a Service Reference and a Map of * properties. The properties in the map take precedence over the properties * in the Service Reference. * *

* This method will automatically set the * {@link RemoteConstants#ENDPOINT_FRAMEWORK_UUID endpoint.framework.uuid} * and {@link RemoteConstants#ENDPOINT_SERVICE_ID endpoint.service.id} * properties based on the specified Service Reference as well as the * {@link RemoteConstants#SERVICE_IMPORTED service.imported} property if * they are not specified as properties. *

* The {@link RemoteConstants#ENDPOINT_ID endpoint.id}, * {@link RemoteConstants#SERVICE_IMPORTED_CONFIGS service.imported.configs} * and objectClass properties must be set. * * @param reference A service reference that can be exported. * @param properties Map of properties. This argument can be * null. The keys in the map must be type * String and, since the keys are case insensitive, * there must be no duplicates with case variation. * @throws IllegalArgumentException When the properties are not proper for * an Endpoint Description */ public EndpointDescription(final ServiceReference reference, final Map properties) { Map props = new TreeMap( String.CASE_INSENSITIVE_ORDER); if (properties != null) { try { props.putAll(properties); } catch (ClassCastException e) { IllegalArgumentException iae = new IllegalArgumentException( "non-String key in properties"); iae.initCause(e); throw iae; } if (props.size() < properties.size()) { throw new IllegalArgumentException( "duplicate keys with different cases in properties: " + new ArrayList(props.keySet()) .removeAll(properties.keySet())); } } for (String key : reference.getPropertyKeys()) { if (!props.containsKey(key)) { props.put(key, reference.getProperty(key)); } } if (!props.containsKey(ENDPOINT_SERVICE_ID)) { props.put(ENDPOINT_SERVICE_ID, reference.getProperty(Constants.SERVICE_ID)); } if (!props.containsKey(ENDPOINT_FRAMEWORK_UUID)) { String uuid = null; try { uuid = AccessController .doPrivileged(new PrivilegedAction() { public String run() { return reference.getBundle().getBundleContext() .getProperty("org.osgi.framework.uuid"); } }); } catch (SecurityException e) { // if we don't have permission, we can't get the property } if (uuid != null) { props.put(ENDPOINT_FRAMEWORK_UUID, uuid); } } conditionProperties(props); this.properties = Collections.unmodifiableMap(props); /* properties must be initialized before calling the following methods */ interfaces = verifyObjectClassProperty(); serviceId = verifyLongProperty(ENDPOINT_SERVICE_ID); frameworkUUID = verifyStringProperty(ENDPOINT_FRAMEWORK_UUID); id = verifyStringProperty(ENDPOINT_ID).trim(); if (id == null) { throw new IllegalArgumentException(ENDPOINT_ID + " property must be set"); } if (getConfigurationTypes().isEmpty()) { throw new IllegalArgumentException(SERVICE_IMPORTED_CONFIGS + " property must be set and non-empty"); } } private static final String SERVICE_EXPORTED_ = "service.exported."; private static final int SERVICE_EXPORTED_length = SERVICE_EXPORTED_ .length(); /** * Condition the properties. * * @param props Property map to condition. */ private void conditionProperties(Map props) { // ensure service.imported is set if (!props.containsKey(SERVICE_IMPORTED)) { props.put(SERVICE_IMPORTED, Boolean.toString(true)); } // remove service.exported.* properties for (Iterator iter = props.keySet().iterator(); iter.hasNext();) { String key = iter.next(); if (SERVICE_EXPORTED_.regionMatches(true, 0, key, 0, SERVICE_EXPORTED_length)) { iter.remove(); } } } /** * Verify and obtain the interface list from the properties. * * @return A list with the interface names. * @throws IllegalArgumentException If the objectClass property is not set * or is empty or if the package version property values are * malformed. */ private List verifyObjectClassProperty() { Object o = properties.get(Constants.OBJECTCLASS); if (!(o instanceof String[])) { throw new IllegalArgumentException( "objectClass value must be of type String[]"); } String[] objectClass = (String[]) o; if (objectClass.length < 1) { throw new IllegalArgumentException("objectClass is empty"); } for (String interf : objectClass) { int index = interf.lastIndexOf('.'); if (index == -1) { continue; } String packageName = interf.substring(0, index); try { /* Make sure any package version properties are well formed */ getPackageVersion(packageName); } catch (IllegalArgumentException e) { IllegalArgumentException iae = new IllegalArgumentException( "Improper version for package " + packageName); iae.initCause(e); throw iae; } } return Collections.unmodifiableList(Arrays.asList(objectClass)); } /** * Verify and obtain a required String property. * * @param propName The name of the property * @return The value of the property or null if the property is * not set. * @throws IllegalArgumentException when the property doesn't have the * correct data type. */ private String verifyStringProperty(String propName) { Object r = properties.get(propName); try { return (String) r; } catch (ClassCastException e) { IllegalArgumentException iae = new IllegalArgumentException( "property value is not a String: " + propName); iae.initCause(e); throw iae; } } /** * Verify and obtain a required long property. * * @param propName The name of the property * @return The value of the property or 0 if the property is not set. * @throws IllegalArgumentException when the property doesn't have the * correct data type. */ private long verifyLongProperty(String propName) { Object r = properties.get(propName); if (r == null) { return 0l; } try { return ((Long) r).longValue(); } catch (ClassCastException e) { IllegalArgumentException iae = new IllegalArgumentException( "property value is not a Long: " + propName); iae.initCause(e); throw iae; } } /** * Returns the endpoint's id. * * The id is an opaque id for an endpoint. No two different endpoints must * have the same id. Two Endpoint Descriptions with the same id must * represent the same endpoint. * * The value of the id is stored in the {@link RemoteConstants#ENDPOINT_ID} * property. * * @return The id of the endpoint, never null. The returned * value has leading and trailing whitespace removed. */ public String getId() { return id; } /** * Provide the list of interfaces implemented by the exported service. * * The value of the interfaces is derived from the objectClass * property. * * @return An unmodifiable list of Java interface names implemented by this * endpoint. */ public List getInterfaces() { return interfaces; } /** * Provide the version of the given package name. * * The version is encoded by prefixing the given package name with * {@link RemoteConstants#ENDPOINT_PACKAGE_VERSION_ * endpoint.package.version.}, and then using this as an endpoint property * key. For example: * *

	 * endpoint.package.version.com.acme
	 * 
* * The value of this property is in String format and will be converted to a * Version object by this method. * * @param packageName The name of the package for which a version is * requested. * @return The version of the specified package or * Version.emptyVersion if the package has no version * in this Endpoint Description. * @throws IllegalArgumentException If the version property value is not * String. */ public Version getPackageVersion(String packageName) { String key = ENDPOINT_PACKAGE_VERSION_ + packageName; Object value = properties.get(key); String version; try { version = (String) value; } catch (ClassCastException e) { IllegalArgumentException iae = new IllegalArgumentException(key + " property value is not a String"); iae.initCause(e); throw iae; } return Version.parseVersion(version); } /** * Returns the service id for the service exported through this endpoint. * * This is the service id under which the framework has registered the * service. This field together with the Framework UUID is a globally unique * id for a service. * * The value of the remote service id is stored in the * {@link RemoteConstants#ENDPOINT_SERVICE_ID} endpoint property. * * @return Service id of a service or 0 if this Endpoint Description does * not relate to an OSGi service. * */ public long getServiceId() { return serviceId; } /** * Returns the configuration types. * * A distribution provider exports a service with an endpoint. This endpoint * uses some kind of communications protocol with a set of configuration * parameters. There are many different types but each endpoint is * configured by only one configuration type. However, a distribution * provider can be aware of different configuration types and provide * synonyms to increase the change a receiving distribution provider can * create a connection to this endpoint. * * This value of the configuration types is stored in the * {@link RemoteConstants#SERVICE_IMPORTED_CONFIGS} service property. * * @return An unmodifiable list of the configuration types used for the * associated endpoint and optionally synonyms. */ public List getConfigurationTypes() { return getStringPlusProperty(SERVICE_IMPORTED_CONFIGS); } /** * Return the list of intents implemented by this endpoint. * * The intents are based on the service.intents on an imported service, * except for any intents that are additionally provided by the importing * distribution provider. All qualified intents must have been expanded. * * This value of the intents is stored in the * {@link RemoteConstants#SERVICE_INTENTS} service property. * * @return An unmodifiable list of expanded intents that are provided by * this endpoint. */ public List getIntents() { return getStringPlusProperty(SERVICE_INTENTS); } /** * Reads a 'String+' property from the properties map, which may be of type * String, String[] or Collection<String> and returns it as an * unmodifiable List. * * @param key The property * @return An unmodifiable list */ private List getStringPlusProperty(String key) { Object value = properties.get(key); if (value == null) { return Collections.EMPTY_LIST; } if (value instanceof String) { return Collections.singletonList((String) value); } if (value instanceof String[]) { String[] values = (String[]) value; List result = new ArrayList(values.length); for (String v : values) { if (v != null) { result.add(v); } } return Collections.unmodifiableList(result); } if (value instanceof Collection< ? >) { Collection< ? > values = (Collection< ? >) value; List result = new ArrayList(values.size()); for (Iterator< ? > iter = values.iterator(); iter.hasNext();) { Object v = iter.next(); if (v instanceof String) { result.add((String) v); } } return Collections.unmodifiableList(result); } return Collections.EMPTY_LIST; } /** * Return the framework UUID for the remote service, if present. * * The value of the remote framework uuid is stored in the * {@link RemoteConstants#ENDPOINT_FRAMEWORK_UUID} endpoint property. * * @return Remote Framework UUID, or null if this endpoint is * not associated with an OSGi framework having a framework uuid. */ public String getFrameworkUUID() { return frameworkUUID; } /** * Returns all endpoint properties. * * @return An unmodifiable map referring to the properties of this Endpoint * Description. */ public Map getProperties() { return properties; } /** * Answers if this Endpoint Description refers to the same service instance * as the given Endpoint Description. * * Two Endpoint Descriptions point to the same service if they have the same * id or their framework UUIDs and remote service ids are equal. * * @param other The Endpoint Description to look at * @return True if this endpoint description points to the same service as * the other */ public boolean isSameService(EndpointDescription other) { if (this.equals(other)) { return true; } if (this.getFrameworkUUID() == null) { return false; } return (this.getServiceId() == other.getServiceId()) && this.getFrameworkUUID().equals( other.getFrameworkUUID()); } /** * Returns a hash code value for the object. * * @return An integer which is a hash code value for this object. */ public int hashCode() { return getId().hashCode(); } /** * Compares this EndpointDescription object to another object. * *

* An Endpoint Description is considered to be equal to another * Endpoint Description if their ids are equal. * * @param other The EndpointDescription object to be compared. * @return true if object is a * EndpointDescription and is equal to this object; * false otherwise. */ public boolean equals(Object other) { if (this == other) { return true; } if (!(other instanceof EndpointDescription)) { return false; } return getId().equals( ((EndpointDescription) other).getId()); } /** * Tests the properties of this EndpointDescription against * the given filter using a case insensitive match. * * @param filter The filter to test. * @return true If the properties of this * EndpointDescription match the filter, * false otherwise. * @throws IllegalArgumentException If filter contains an * invalid filter string that cannot be parsed. */ public boolean matches(String filter) { Filter f; try { f = FrameworkUtil.createFilter(filter); } catch (InvalidSyntaxException e) { IllegalArgumentException iae = new IllegalArgumentException(e .getMessage()); iae.initCause(e); throw iae; } Dictionary d = new UnmodifiableDictionary( properties); /* * we can use matchCase here since properties already supports case * insensitive key lookup. */ return f.matchCase(d); } /** * Returns the string representation of this EndpointDescription. * * @return String form of this EndpointDescription. */ public String toString() { StringBuffer sb = new StringBuffer(); sb.append('{'); Iterator> iter = properties.entrySet() .iterator(); boolean comma = false; while (iter.hasNext()) { Map.Entry entry = iter.next(); if (comma) { sb.append(", "); } else { comma = true; } sb.append(entry.getKey()); sb.append('='); Object value = entry.getValue(); if (value != null) { Class< ? > valueType = value.getClass(); if (Object[].class.isAssignableFrom(valueType)) { append(sb, (Object[]) value); continue; } } sb.append(value); } sb.append('}'); return sb.toString(); } /** * Append the specified Object array to the specified StringBuffer. * * @param sb Receiving StringBuffer. * @param value Object array to append to the specified StringBuffer. */ private static void append(StringBuffer sb, Object[] value) { sb.append('['); boolean comma = false; final int length = value.length; for (int i = 0; i < length; i++) { if (comma) { sb.append(", "); } else { comma = true; } sb.append(String.valueOf(value[i])); } sb.append(']'); } /** * Unmodifiable Dictionary wrapper for a Map. This class is also used by * EndpointPermission. */ static class UnmodifiableDictionary extends Dictionary { private final Map wrapped; UnmodifiableDictionary(Map wrapped) { this.wrapped = wrapped; } public Enumeration elements() { return Collections.enumeration(wrapped.values()); } public V get(Object key) { return wrapped.get(key); } public boolean isEmpty() { return wrapped.isEmpty(); } public Enumeration keys() { return Collections.enumeration(wrapped.keySet()); } public V put(K key, V value) { throw new UnsupportedOperationException(); } public V remove(Object key) { throw new UnsupportedOperationException(); } public int size() { return wrapped.size(); } public String toString() { return wrapped.toString(); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy