/*
* 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<Composite> 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<Intent> getIntents(String[] intents) {
if (intents == null || intents.length == 0) {
return Collections.emptyList();
}
List<Intent> intentList = new ArrayList<Intent>();
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.<p>
* 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<String, Object> getProperties(ServiceReference reference, Map<String, Object> props) {
String[] names = reference.getPropertyKeys();
Map<String, Object> properties = new HashMap<String, Object>();
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<String, String> nameMap = new HashMap<String, String>();
if (names != null) {
for (String name : names) {
nameMap.put(name.toLowerCase(), name);
}
}
for (Map.Entry<String, Object> 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<String> names = new ArrayList<String>();
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<String, Object> props) throws Exception {
Bundle bundle = reference.getBundle();
Map<String, Object> properties = getProperties(reference, props);
Collection<OSGiProperty> osgiProps = implementationFactory.createOSGiProperties(reference);
Long sid = (Long)reference.getProperty(SERVICE_ID);
String[] requiredIntents = getStringArray(properties.get(RemoteConstants.SERVICE_EXPORTED_INTENTS));
List<Intent> intents = getIntents(requiredIntents);
String[] requiredIntentsExtra = getStringArray(properties.get(RemoteConstants.SERVICE_EXPORTED_INTENTS_EXTRA));
List<Intent> extraIntents = getIntents(requiredIntentsExtra);
Set<Intent> allIntents = new HashSet<Intent>(intents);
allIntents.addAll(extraIntents);
String[] bindingNames = getStringArray(properties.get(SCA_BINDINGS));
Collection<Binding> 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<String> objectClassSet = new HashSet<String>(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<String, Object> props, ComponentService service){
ServiceDescriptionsFactory serviceDescriptionFactory = registry.getExtensionPoint(ServiceDescriptionsFactory.class);
SCAConfig scaConfig = serviceDescriptionFactory.createSCAConfig();
// add the binding configurations
List<Binding> bindings = scaConfig.getBindings();
bindings.addAll(service.getBindings());
// add the intent configurations
List<Intent> intents = scaConfig.getIntents();
intents.addAll(service.getRequiredIntents());
// add the policy set configurations
List<PolicySet> 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<String, Object> 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<Binding> bindings,
Set<Intent> allIntents,
Collection<OSGiProperty> 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<Binding> bindings = Collections.emptyList();
Collection<String> interfaces = Collections.emptyList();
Collection<Intent> intents = Collections.emptyList();
Endpoint ep = (Endpoint)endpoint.getProperties().get(Endpoint.class.getName());
Collection<OSGiProperty> 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<String, Object> 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<String> remoteInterfaces,
Collection<Binding> bindings,
Collection<Intent> intents,
Collection<OSGiProperty> 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<Binding> 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<QName, Binding> bindingMap = new HashMap<QName, Binding>();
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();
}
}