From e5b7380c874745c989d1816b8f552504f038e1bc Mon Sep 17 00:00:00 2001 From: lresende Date: Thu, 26 Sep 2013 20:33:20 +0000 Subject: 2.0 branch for possible maintenance release git-svn-id: http://svn.us.apache.org/repos/asf/tuscany@1526672 13f79535-47bb-0310-9956-ffa450edef68 --- .../impl/EndpointIntrospector.java | 550 +++++++++++++++++++++ 1 file changed, 550 insertions(+) create mode 100644 sca-java-2.x/branches/2.0/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/impl/EndpointIntrospector.java (limited to 'sca-java-2.x/branches/2.0/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/impl/EndpointIntrospector.java') diff --git a/sca-java-2.x/branches/2.0/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/impl/EndpointIntrospector.java b/sca-java-2.x/branches/2.0/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/impl/EndpointIntrospector.java new file mode 100644 index 0000000000..d00696a822 --- /dev/null +++ b/sca-java-2.x/branches/2.0/modules/node-impl-osgi/src/main/java/org/apache/tuscany/sca/osgi/remoteserviceadmin/impl/EndpointIntrospector.java @@ -0,0 +1,550 @@ +/* + * 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.tuscany.sca.osgi.remoteserviceadmin.impl; + +import static org.apache.tuscany.sca.assembly.Base.SCA11_TUSCANY_NS; +import static org.apache.tuscany.sca.implementation.osgi.OSGiProperty.SCA_BINDINGS; +import static org.apache.tuscany.sca.osgi.remoteserviceadmin.impl.OSGiHelper.createOSGiProperty; +import static org.apache.tuscany.sca.osgi.remoteserviceadmin.impl.OSGiHelper.getStringArray; +import static org.osgi.framework.Constants.OBJECTCLASS; +import static org.osgi.framework.Constants.SERVICE_ID; + +import java.io.IOException; +import java.io.StringWriter; +import java.io.Writer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +import javax.xml.namespace.QName; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamWriter; + +import org.apache.tuscany.sca.assembly.AssemblyFactory; +import org.apache.tuscany.sca.assembly.Base; +import org.apache.tuscany.sca.assembly.Binding; +import org.apache.tuscany.sca.assembly.Component; +import org.apache.tuscany.sca.assembly.ComponentReference; +import org.apache.tuscany.sca.assembly.ComponentService; +import org.apache.tuscany.sca.assembly.Composite; +import org.apache.tuscany.sca.assembly.Endpoint; +import org.apache.tuscany.sca.assembly.Reference; +import org.apache.tuscany.sca.assembly.Service; +import org.apache.tuscany.sca.contribution.Contribution; +import org.apache.tuscany.sca.contribution.ContributionFactory; +import org.apache.tuscany.sca.contribution.processor.ContributionReadException; +import org.apache.tuscany.sca.contribution.processor.ContributionResolveException; +import org.apache.tuscany.sca.contribution.resolver.ExtensibleModelResolver; +import org.apache.tuscany.sca.contribution.resolver.ModelResolver; +import org.apache.tuscany.sca.contribution.resolver.ModelResolverExtensionPoint; +import org.apache.tuscany.sca.core.ExtensionPointRegistry; +import org.apache.tuscany.sca.core.FactoryExtensionPoint; +import org.apache.tuscany.sca.core.UtilityExtensionPoint; +import org.apache.tuscany.sca.deployment.Deployer; +import org.apache.tuscany.sca.implementation.osgi.OSGiImplementation; +import org.apache.tuscany.sca.implementation.osgi.OSGiImplementationFactory; +import org.apache.tuscany.sca.implementation.osgi.OSGiProperty; +import org.apache.tuscany.sca.implementation.osgi.SCAConfig; +import org.apache.tuscany.sca.implementation.osgi.ServiceDescriptionsFactory; +import org.apache.tuscany.sca.interfacedef.InvalidInterfaceException; +import org.apache.tuscany.sca.interfacedef.java.JavaInterface; +import org.apache.tuscany.sca.interfacedef.java.JavaInterfaceContract; +import org.apache.tuscany.sca.interfacedef.java.JavaInterfaceFactory; +import org.apache.tuscany.sca.osgi.service.discovery.impl.LocalDiscoveryService; +import org.apache.tuscany.sca.osgi.service.discovery.impl.LocalDiscoveryService.ExtenderConfiguration; +import org.apache.tuscany.sca.policy.Intent; +import org.apache.tuscany.sca.policy.PolicyFactory; +import org.apache.tuscany.sca.policy.PolicySet; +import org.oasisopen.sca.ServiceRuntimeException; +import org.oasisopen.sca.annotation.PolicySets; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceReference; +import org.osgi.service.remoteserviceadmin.EndpointDescription; +import org.osgi.service.remoteserviceadmin.RemoteConstants; +import org.osgi.util.tracker.ServiceTracker; + +/** + * Introspect an OSGi Service to create an SCA composite that contains a single component with + * implementation.osgi + */ +public class EndpointIntrospector { + // private BundleContext context; + private AssemblyFactory assemblyFactory; + private ContributionFactory contributionFactory; + private OSGiImplementationFactory implementationFactory; + private PolicyFactory policyFactory; + private ExtensionPointRegistry registry; + private FactoryExtensionPoint factories; + private ModelResolverExtensionPoint modelResolvers; + // private StAXArtifactProcessor compositeProcessor; + private JavaInterfaceFactory javaInterfaceFactory; + private Deployer deployer; + private ServiceTracker discoveryTracker; + + /** + * @param name + * @return + */ + private static QName getQName(String name) { + QName qname; + if (name.startsWith("{")) { + int i = name.indexOf('}'); + if (i != -1) { + qname = new QName(name.substring(1, i), name.substring(i + 1)); + } else { + throw new IllegalArgumentException("Invalid qname: " + name); + } + } else { + // Default to SCA namespace + qname = new QName("", name); + } + return qname; + } + + /** + * @param context TODO + * @param registry + */ + public EndpointIntrospector(BundleContext context, ExtensionPointRegistry registry, ServiceTracker discoveryTracker) { + super(); + this.registry = registry; + // this.context = context; + this.discoveryTracker = discoveryTracker; + this.factories = registry.getExtensionPoint(FactoryExtensionPoint.class); + this.modelResolvers = registry.getExtensionPoint(ModelResolverExtensionPoint.class); +// this.compositeProcessor = +// registry.getExtensionPoint(StAXArtifactProcessorExtensionPoint.class).getProcessor(Composite.class); + this.assemblyFactory = factories.getFactory(AssemblyFactory.class); + this.contributionFactory = factories.getFactory(ContributionFactory.class); + this.policyFactory = factories.getFactory(PolicyFactory.class); + this.implementationFactory = factories.getFactory(OSGiImplementationFactory.class); + this.javaInterfaceFactory = factories.getFactory(JavaInterfaceFactory.class); + this.deployer = registry.getExtensionPoint(UtilityExtensionPoint.class).getUtility(Deployer.class); + } + + private Intent getIntent(String intent) { + QName name = getQName(intent); + Intent i = policyFactory.createIntent(); + i.setName(name); + return i; + } + + private List getIntents(String[] intents) { + if (intents == null || intents.length == 0) { + return Collections.emptyList(); + } + List intentList = new ArrayList(); + for (String i : intents) { + Intent intent = getIntent(i); + if (intent != null) { + intentList.add(intent); + } + } + return intentList; + } + + /** + * Any property in the map overrides the service reference properties, regardless of + * case. That is, if the map contains a key then it will override any case variant + * of this key in the Service Reference.

+ * If the map contains the objectClass or service. id property key in any case + * variant, then these properties must not override the Service References value. This + * implies that the map can provide the service.exported. interfaces, property allowing + * the Topology Manager to export any registered service, also services not specifically + * marked to be exported. + * @param reference + * @param props + * @return + */ + private Map getProperties(ServiceReference reference, Map props) { + String[] names = reference.getPropertyKeys(); + Map properties = new HashMap(); + if (names != null) { + for (String name : names) { + properties.put(name, reference.getProperty(name)); + } + } + if (props != null) { + // Create a map of names (key = lowcase name, value = name) + Map nameMap = new HashMap(); + if (names != null) { + for (String name : names) { + nameMap.put(name.toLowerCase(), name); + } + } + for (Map.Entry p : props.entrySet()) { + if (OBJECTCLASS.equalsIgnoreCase(p.getKey())) { + throw new IllegalArgumentException(OBJECTCLASS + " property cannot be overridden."); + } else if (SERVICE_ID.equalsIgnoreCase(p.getKey())) { + throw new IllegalArgumentException(SERVICE_ID + " property cannot be overridden."); + } + String key = nameMap.get(p.getKey().toLowerCase()); + if (key != null) { + properties.put(key, p.getValue()); + } else { + properties.put(p.getKey(), p.getValue()); + } + } + } + return properties; + } + + /** + * Parse the Stringp[] to support values that are separated by comma + * @param interfaces + * @return + */ + private String[] parse(String[] interfaces) { + if (interfaces == null) { + return null; + } + List names = new ArrayList(); + for (String i : interfaces) { + String[] parts = i.split(","); + for (String p : parts) { + names.add(p.trim()); + } + } + return names.toArray(new String[names.size()]); + } + + /** + * Introspect a local OSGi Service represented by the ServiceReference to create + * an SCA service with the required intents and bindings + * @param reference The service reference for a local OSGi service + * @param props Addiontal properties + * @return An SCA contribution with a deployable composite for the SCA service + * @throws Exception + */ + public Contribution introspect(ServiceReference reference, Map props) throws Exception { + Bundle bundle = reference.getBundle(); + Map properties = getProperties(reference, props); + Collection osgiProps = implementationFactory.createOSGiProperties(reference); + Long sid = (Long)reference.getProperty(SERVICE_ID); + + String[] requiredIntents = getStringArray(properties.get(RemoteConstants.SERVICE_EXPORTED_INTENTS)); + List intents = getIntents(requiredIntents); + String[] requiredIntentsExtra = getStringArray(properties.get(RemoteConstants.SERVICE_EXPORTED_INTENTS_EXTRA)); + List extraIntents = getIntents(requiredIntentsExtra); + Set allIntents = new HashSet(intents); + allIntents.addAll(extraIntents); + + String[] bindingNames = getStringArray(properties.get(SCA_BINDINGS)); + Collection bindings = loadBindings(bindingNames); + + String[] remoteInterfaces = getStringArray(reference.getProperty(RemoteConstants.SERVICE_EXPORTED_INTERFACES)); + if (remoteInterfaces == null || remoteInterfaces.length > 0 && "*".equals(remoteInterfaces[0])) { + remoteInterfaces = getStringArray(reference.getProperty(OBJECTCLASS)); + } else { + remoteInterfaces = parse(remoteInterfaces); + String[] objectClasses = getStringArray(reference.getProperty(OBJECTCLASS)); + Set objectClassSet = new HashSet(Arrays.asList(objectClasses)); + if (!objectClassSet.containsAll(Arrays.asList(remoteInterfaces))) { + throw new IllegalArgumentException( + "The exported interfaces are not a subset of the types" + " listed in the objectClass service property from the Service Reference"); + } + } + + Contribution contribution = generateContribution(bundle, sid, remoteInterfaces, bindings, allIntents, osgiProps); + return contribution; + } + + public String instrospectSCAConfig(ServiceReference reference, Map props, ComponentService service){ + + ServiceDescriptionsFactory serviceDescriptionFactory = registry.getExtensionPoint(ServiceDescriptionsFactory.class); + SCAConfig scaConfig = serviceDescriptionFactory.createSCAConfig(); + + // add the binding configurations + List bindings = scaConfig.getBindings(); + bindings.addAll(service.getBindings()); + + // add the intent configurations + List intents = scaConfig.getIntents(); + intents.addAll(service.getRequiredIntents()); + + // add the policy set configurations + List policySets = scaConfig.getPolicySets(); + policySets.addAll(service.getPolicySets()); + + // set up the target namespace + // TODO - there is a bug in the spec which only allow bindings from one + // namsepace to be included in sca-config element. Here we just + // the first bindings namespace + Map properties = getProperties(reference, props); + String[] bindingNames = getStringArray(properties.get(SCA_BINDINGS)); + if (bindingNames.length > 0){ + QName firstBindingQName = getQName(bindingNames[0]); + scaConfig.setTargetNamespace(firstBindingQName.getNamespaceURI()); + } + + // write the sca config out to XML + String scaConfigXMLString = ""; + + try { + Writer writer = new StringWriter(); + deployer.saveXMLDocument(scaConfig, writer, deployer.createMonitor()); + scaConfigXMLString = writer.toString(); + } catch (Exception ex){ + throw new ServiceRuntimeException(ex); + } + + return scaConfigXMLString; + } + + /* + public Contribution loadContribution(Bundle bundle, Composite composite) { + try { + URL root = bundle.getEntry("/"); + Contribution contribution = deployer.loadContribution(root.toURI(), root, deployer.createMonitor()); + deployer.attachDeploymentComposite(contribution, composite, false); + return contribution; + } catch (Exception e) { + throw new ServiceRuntimeException(e); + } + } + */ + + /** + * Generate a contribution that contains the composite for the exported service + * @param bundle The OSGi bundle + * @param sid The service id + * @param remoteInterfaces + * @param bindings + * @param allIntents + * @return + * @throws ClassNotFoundException + * @throws InvalidInterfaceException + */ + private Contribution generateContribution(Bundle bundle, + Long sid, + String[] remoteInterfaces, + Collection bindings, + Set allIntents, + Collection osgiProps) throws ClassNotFoundException, + InvalidInterfaceException { + String id = "osgi.service." + UUID.randomUUID(); + Composite composite = assemblyFactory.createComposite(); + composite.setName(new QName(SCA11_TUSCANY_NS, id)); + + Component component = assemblyFactory.createComponent(); + component.setName(id); + + composite.getComponents().add(component); + + OSGiImplementation implementation = implementationFactory.createOSGiImplementation(); + + implementation.setBundle(bundle); + component.setImplementation(implementation); + implementation.setUnresolved(false); + + OSGiProperty serviceID = implementationFactory.createOSGiProperty(); + serviceID.setName(SERVICE_ID); + // The service.id is Long + serviceID.setValue(String.valueOf(sid)); + + for (String intf : remoteInterfaces) { + Service service = assemblyFactory.createService(); + JavaInterfaceContract interfaceContract = createJavaInterfaceContract(bundle, intf); + String name = intf.substring(intf.lastIndexOf('.') + 1); + service.setName(name); + service.setInterfaceContract(interfaceContract); + + implementation.getServices().add(service); + + ComponentService componentService = assemblyFactory.createComponentService(); + componentService.setName(service.getName()); + componentService.getExtensions().add(serviceID); + componentService.getExtensions().addAll(osgiProps); + + component.getServices().add(componentService); + componentService.setService(service); + } + + for (ComponentService componentService : component.getServices()) { + componentService.getRequiredIntents().addAll(allIntents); + componentService.getBindings().addAll(bindings); + } + + // FIXME: Should we scan the owning bundle to create the SCA contribution? + Contribution contribution = loadContribution(bundle, id, composite); + return contribution; + } + + private Contribution loadContribution(Bundle bundle, String id, Composite composite) { + Contribution contribution = contributionFactory.createContribution(); + contribution.setClassLoader(OSGiHelper.createBundleClassLoader(bundle)); + contribution.setURI(id); + contribution.setLocation(bundle.getEntry("/").toString()); + deployer.attachDeploymentComposite(contribution, composite, false); + ModelResolver modelResolver = new ExtensibleModelResolver(contribution, modelResolvers, factories); + contribution.setModelResolver(modelResolver); + // compositeProcessor.resolve(composite, modelResolver, new ProcessorContext(registry)); + contribution.setUnresolved(true); + return contribution; + } + + /** + * @param bundle + * @param endpoint + * @return + * @throws Exception + */ + public Contribution introspect(Bundle bundle, EndpointDescription endpoint) throws Exception { + Collection bindings = Collections.emptyList(); + Collection interfaces = Collections.emptyList(); + Collection intents = Collections.emptyList(); + Endpoint ep = (Endpoint)endpoint.getProperties().get(Endpoint.class.getName()); + Collection osgiProps = implementationFactory.createOSGiProperties(endpoint.getProperties()); + if (ep != null) { + bindings = Collections.singletonList(ep.getBinding()); + interfaces = Collections.singletonList(((JavaInterface)ep.getComponentServiceInterfaceContract().getInterface()).getName()); + // FIXME: [rfeng] We need to build the in-memory composite so that intents are calculated at the ep level + intents = ep.getService().getRequiredIntents(); + } else { + Map properties = endpoint.getProperties(); + interfaces = endpoint.getInterfaces(); + String[] requiredIntents = getStringArray(properties.get(RemoteConstants.SERVICE_INTENTS)); + intents = getIntents(requiredIntents); + + String[] bindingNames = getStringArray(properties.get(SCA_BINDINGS)); + bindings = loadBindings(bindingNames); + } + + Contribution contribution = generateContribution(bundle, interfaces, bindings, intents, osgiProps); + return contribution; + } + + private Contribution generateContribution(Bundle bundle, + Collection remoteInterfaces, + Collection bindings, + Collection intents, + Collection osgiProps) throws ClassNotFoundException, + InvalidInterfaceException, ContributionResolveException { + String id = "osgi.reference." + UUID.randomUUID(); + Composite composite = assemblyFactory.createComposite(); + composite.setName(new QName(Base.SCA11_TUSCANY_NS, id)); + + Component component = assemblyFactory.createComponent(); + component.setName(id); + // component.setAutowire(Boolean.TRUE); + + composite.getComponents().add(component); + + OSGiImplementation implementation = implementationFactory.createOSGiImplementation(); + + implementation.setBundle(bundle); + component.setImplementation(implementation); + implementation.setUnresolved(false); + + int count = 0; + for (String intf : remoteInterfaces) { + Reference reference = assemblyFactory.createReference(); + JavaInterfaceContract interfaceContract = createJavaInterfaceContract(bundle, intf); + + reference.setName("ref" + (count++)); + reference.setInterfaceContract(interfaceContract); + + implementation.getReferences().add(reference); + + ComponentReference componentReference = assemblyFactory.createComponentReference(); + componentReference.setName(reference.getName()); + componentReference.getExtensions().addAll(osgiProps); + component.getReferences().add(componentReference); + componentReference.setReference(reference); + componentReference.setWiredByImpl(true); + } + + for (ComponentReference componentReference : component.getReferences()) { + componentReference.getRequiredIntents().addAll(intents); + componentReference.getBindings().addAll(bindings); + } + + Contribution contribution = loadContribution(bundle, id, composite); + return contribution; + } + + private JavaInterfaceContract createJavaInterfaceContract(Bundle bundle, String intf) + throws ClassNotFoundException, InvalidInterfaceException { + JavaInterfaceContract interfaceContract = javaInterfaceFactory.createJavaInterfaceContract(); + Class interfaceClass = bundle.loadClass(intf); + JavaInterface javaInterface = javaInterfaceFactory.createJavaInterface(); + // [rfeng] For OSGi, the interfaces should be marked as remotable + javaInterface.setRemotable(true); + // [rfeng] We need to mark the interface to be remotable before the createJavaInterface() is called + javaInterfaceFactory.createJavaInterface(javaInterface, interfaceClass); + interfaceContract.setInterface(javaInterface); + if (javaInterface.getCallbackClass() != null) { + JavaInterface callbackInterface = javaInterfaceFactory.createJavaInterface(javaInterface.getCallbackClass()); + callbackInterface.setRemotable(true); + interfaceContract.setCallbackInterface(callbackInterface); + } + return interfaceContract; + } + + private Collection loadBindings(String[] qnames) throws IOException, ContributionReadException, + XMLStreamException { + if (qnames == null || qnames.length == 0) { + return Collections.emptyList(); + } + QName[] bindingNames = new QName[qnames.length]; + int index = 0; + for (String name : qnames) { + bindingNames[index++] = getQName(name); + } + + LocalDiscoveryService discoveryService = (LocalDiscoveryService)discoveryTracker.getService(); + + Map bindingMap = new HashMap(); + if (discoveryService != null) { + for (ExtenderConfiguration config : discoveryService.getConfigurations()) { + for (SCAConfig sc : config.getSCAConfigs()) { + for (QName bindingName : bindingNames) { + if ("".equals(bindingName.getNamespaceURI()) || + sc.getTargetNamespace().equals(bindingName.getNamespaceURI())) { + for (Binding binding : sc.getBindings()) { + if (bindingName.getLocalPart().equals(binding.getName())) { + // We need to check duplications + if (bindingMap.put(bindingName, binding) != null) { + throw new ServiceRuntimeException("Duplicate binding found: " + bindingName); + } + } + } + } + } + } + } + } + for (QName bindingName : bindingNames) { + if (!bindingMap.containsKey(bindingName)) { + throw new ServiceRuntimeException("Binding cannot be resolved: " + bindingName); + } + } + return bindingMap.values(); + } + +} -- cgit v1.2.3