/**
* 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.aries.application.modelling.impl;
import static org.apache.aries.application.utils.AppConstants.LOG_ENTRY;
import static org.apache.aries.application.utils.AppConstants.LOG_EXIT;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import org.apache.aries.application.InvalidAttributeException;
import org.apache.aries.application.modelling.ExportedService;
import org.apache.aries.application.modelling.ImportedService;
import org.apache.aries.application.modelling.ModellingManager;
import org.apache.aries.application.modelling.ParsedServiceElements;
import org.apache.aries.application.modelling.ParserProxy;
import org.apache.aries.application.modelling.WrappedServiceMetadata;
import org.apache.aries.blueprint.ComponentDefinitionRegistry;
import org.apache.aries.util.manifest.ManifestHeaderProcessor;
import org.osgi.service.blueprint.reflect.BeanArgument;
import org.osgi.service.blueprint.reflect.BeanMetadata;
import org.osgi.service.blueprint.reflect.BeanProperty;
import org.osgi.service.blueprint.reflect.CollectionMetadata;
import org.osgi.service.blueprint.reflect.ComponentMetadata;
import org.osgi.service.blueprint.reflect.MapEntry;
import org.osgi.service.blueprint.reflect.MapMetadata;
import org.osgi.service.blueprint.reflect.Metadata;
import org.osgi.service.blueprint.reflect.RefMetadata;
import org.osgi.service.blueprint.reflect.ReferenceListMetadata;
import org.osgi.service.blueprint.reflect.ReferenceListener;
import org.osgi.service.blueprint.reflect.RegistrationListener;
import org.osgi.service.blueprint.reflect.ServiceMetadata;
import org.osgi.service.blueprint.reflect.ServiceReferenceMetadata;
import org.osgi.service.blueprint.reflect.Target;
import org.osgi.service.blueprint.reflect.ValueMetadata;
import org.osgi.service.jndi.JNDIConstants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
abstract public class AbstractParserProxy implements ParserProxy {
private final Logger _logger = LoggerFactory.getLogger(AbstractParserProxy.class);
private ModellingManager _modellingManager;
protected abstract ComponentDefinitionRegistry parseCDR(List<URL> blueprintsToParse) throws Exception;
protected abstract ComponentDefinitionRegistry parseCDR(InputStream blueprintToParse) throws Exception;
public void setModellingManager (ModellingManager m) {
_modellingManager = m;
}
public List<? extends WrappedServiceMetadata> parse(List<URL> blueprintsToParse) throws Exception {
_logger.debug(LOG_ENTRY, "parse", new Object[]{blueprintsToParse});
ComponentDefinitionRegistry cdr = parseCDR (blueprintsToParse);
List<? extends WrappedServiceMetadata> result = parseCDRForServices (cdr, true);
_logger.debug(LOG_EXIT, "parse", new Object[]{result});
return result;
}
public List<? extends WrappedServiceMetadata> parse(URL blueprintToParse) throws Exception {
_logger.debug(LOG_ENTRY, "parse", new Object[]{blueprintToParse});
List<URL> list = new ArrayList<URL>();
list.add(blueprintToParse);
List<? extends WrappedServiceMetadata> result = parse (list);
_logger.debug(LOG_EXIT, "parse", new Object[]{result});
return result;
}
public List<? extends WrappedServiceMetadata> parse(InputStream blueprintToParse) throws Exception {
_logger.debug(LOG_ENTRY, "parse", new Object[]{blueprintToParse});
ComponentDefinitionRegistry cdr = parseCDR (blueprintToParse);
List<? extends WrappedServiceMetadata> result = parseCDRForServices (cdr, true);
_logger.debug(LOG_EXIT, "parse", new Object[]{result});
return result;
}
public ParsedServiceElements parseAllServiceElements(InputStream blueprintToParse) throws Exception {
_logger.debug(LOG_ENTRY, "parseAllServiceElements", new Object[]{blueprintToParse});
ComponentDefinitionRegistry cdr = parseCDR (blueprintToParse);
Collection<ExportedService> services = parseCDRForServices(cdr, false);
Collection<ImportedService> references = parseCDRForReferences (cdr);
ParsedServiceElements result = _modellingManager.getParsedServiceElements(services, references);
_logger.debug(LOG_EXIT, "parseAllServiceElements", new Object[]{result});
return result;
}
/**
* Extract Service metadata from a ComponentDefinitionRegistry. When doing SCA modelling, we
* need to suppress anonymous services. We don't want to do that when we're modelling for
* provisioning dependencies.
* @param cdr ComponentDefinitionRegistry
* @param suppressAnonymousServices Unnamed services will not be returned if this is true
* @return List<WrappedServiceMetadata>
*/
private List<ExportedService> parseCDRForServices (ComponentDefinitionRegistry cdr,
boolean suppressAnonymousServices) {
_logger.debug(LOG_ENTRY, "parseCDRForServices", new Object[]{cdr, suppressAnonymousServices});
List<ExportedService> result = new ArrayList<ExportedService>();
for (ComponentMetadata compMetadata : findAllComponents(cdr)) {
if (compMetadata instanceof ServiceMetadata) {
ServiceMetadata serviceMetadata = (ServiceMetadata)compMetadata;
String serviceName;
int ranking;
Collection<String> interfaces = new ArrayList<String>();
Map<String, Object> serviceProps = new HashMap<String, Object>();
ranking = serviceMetadata.getRanking();
for (Object i : serviceMetadata.getInterfaces()) {
interfaces.add((String)i);
}
// get the service properties
List<MapEntry> props = serviceMetadata.getServiceProperties();
for (MapEntry entry : props) {
String key = ((ValueMetadata)entry.getKey()).getStringValue();
Metadata value = entry.getValue();
if (value instanceof CollectionMetadata) {
processMultiValueProperty(serviceProps, key, value);
} else {
serviceProps.put(key, ((ValueMetadata)entry.getValue()).getStringValue());
}
}
// serviceName: use the service id unless that's not set,
// in which case we use the bean id.
serviceName = serviceMetadata.getId();
// If the Service references a Bean, export the bean id as a service property
// as per 121.6.5 p669 of the blueprint 1.0 specification
Target t = serviceMetadata.getServiceComponent();
String targetId = null;
if (t instanceof RefMetadata) {
targetId = ((RefMetadata)t).getComponentId();
} else if (t instanceof BeanMetadata) {
targetId = ((BeanMetadata)t).getId();
}
// Our OBR code MUST have access to targetId if it's available (i.e. not null
// or auto-generated for an anonymous service. This must ALWAYS be set.
if (targetId != null && !targetId.startsWith(".")) { // Don't set this for anonymous inner components
serviceProps.put("osgi.service.blueprint.compname", targetId);
if (serviceName == null || serviceName.equals("") || serviceName.startsWith(".")) {
serviceName = targetId;
}
}
if(serviceName != null && serviceName.startsWith("."))
serviceName = null;
// If suppressAnonymous services, do not expose services that have no name
if (!suppressAnonymousServices || (serviceName != null)) {
ExportedService wsm = _modellingManager.getExportedService(serviceName, ranking, interfaces, serviceProps);
result.add(wsm);
}
}
}
_logger.debug(LOG_EXIT, "parseAllServiceElements", new Object[]{result});
return result;
}
private void processMultiValueProperty(Map<String, Object> serviceProps,
String key, Metadata value) {
List<Metadata> values = ((CollectionMetadata)value).getValues();
Class<?> collectionClass = ((CollectionMetadata)value).getCollectionClass();
Object collectionValue;
if(Collection.class.isAssignableFrom(collectionClass)) {
Collection<String> theseValues = getCollectionFromClass(collectionClass);
for(Metadata m : values) {
theseValues.add(((ValueMetadata)m).getStringValue());
}
collectionValue = theseValues;
} else {
String[] theseValues = new String[values.size()];
for (int i=0; i < values.size(); i++) {
Metadata m = values.get(i);
theseValues[i] = ((ValueMetadata)m).getStringValue();
}
collectionValue = theseValues;
}
serviceProps.put(key, collectionValue);
}
private Collection<String> getCollectionFromClass(Class<?> collectionClass) {
if(List.class.isAssignableFrom(collectionClass)) {
return new ArrayList<String>();
} else if (Set.class.isAssignableFrom(collectionClass)) {
return new LinkedHashSet<String>();
} else if (Queue.class.isAssignableFrom(collectionClass)) {
//This covers Queue and Deque, which is caught by the isAssignableFrom check
//as a sub-interface of Queue
return new LinkedList<String>();
} else {
throw new IllegalArgumentException(collectionClass.getName());
}
}
/**
* Extract References metadata from a ComponentDefinitionRegistry.
* @param cdr ComponentDefinitionRegistry
* @return List<WrappedReferenceMetadata>
* @throws InvalidAttributeException
*/
private List<ImportedService> parseCDRForReferences (ComponentDefinitionRegistry cdr) throws InvalidAttributeException {
_logger.debug(LOG_ENTRY, "parseCDRForReferences", new Object[]{cdr});
List<ImportedService> result = new ArrayList<ImportedService>();
for (ComponentMetadata compMetadata : findAllComponents(cdr)) {
if (compMetadata instanceof ServiceReferenceMetadata) {
ServiceReferenceMetadata referenceMetadata = (ServiceReferenceMetadata)compMetadata;
boolean optional = referenceMetadata.getAvailability() == ServiceReferenceMetadata.AVAILABILITY_OPTIONAL;
String iface = referenceMetadata.getInterface();
String compName = referenceMetadata.getComponentName();
String blueprintFilter = referenceMetadata.getFilter();
String id = referenceMetadata.getId();
boolean isMultiple = (referenceMetadata instanceof ReferenceListMetadata);
//The blueprint parser teams up with JPA and blueprint resource ref
// namespace handlers to give us service imports of the form,
// objectClass=javax.persistence.EntityManagerFactory, org.apache.aries.jpa.proxy.factory=*, osgi.unit.name=blabber
//
// There will be no matching service for this reference.
// For now we blacklist certain objectClasses and filters - this is a pretty dreadful thing to do.
if (!isBlacklisted (iface, blueprintFilter)) {
ImportedService ref = _modellingManager.getImportedService (optional, iface, compName, blueprintFilter,
id, isMultiple);
result.add (ref);
}
}
}
_logger.debug(LOG_EXIT, "parseCDRForReferences", new Object[]{result});
return result;
}
/**
* Find all the components in a given {@link ComponentDefinitionRegistry} this finds top-level
* components as well as their nested counter-parts. It may however not find components in custom namespacehandler
* {@link ComponentMetadata} instances.
*
* @param cdr The {@link ComponentDefinitionRegistry} to scan
* @return a {@link Set} of {@link ComponentMetadata}
*/
private Set<ComponentMetadata> findAllComponents(ComponentDefinitionRegistry cdr) {
Set<ComponentMetadata> components = new HashSet<ComponentMetadata>();
for (String name : cdr.getComponentDefinitionNames()) {
ComponentMetadata component = cdr.getComponentDefinition(name);
traverseComponent(component, components);
}
return components;
}
/**
* Traverse to find all nested {@link ComponentMetadata} instances
* @param metadata
* @param output
*/
private void traverse(Metadata metadata, Set<ComponentMetadata> output) {
if (metadata instanceof ComponentMetadata) {
traverseComponent((ComponentMetadata) metadata, output);
} else if (metadata instanceof CollectionMetadata) {
CollectionMetadata collection = (CollectionMetadata) metadata;
for (Metadata v : collection.getValues()) traverse(v, output);
} else if (metadata instanceof MapMetadata) {
MapMetadata map = (MapMetadata) metadata;
for (MapEntry e : map.getEntries()) {
traverse(e.getKey(), output);
traverse(e.getValue(), output);
}
}
}
/**
* Traverse {@link ComponentMetadata} instances to find all nested {@link ComponentMetadata} instances
* @param component
* @param output
*/
private void traverseComponent(ComponentMetadata component, Set<ComponentMetadata> output) {
if (!!!output.add(component)) return;
if (component instanceof BeanMetadata) {
BeanMetadata bean = (BeanMetadata) component;
traverse(bean.getFactoryComponent(), output);
for (BeanArgument argument : bean.getArguments()) {
traverse(argument.getValue(), output);
}
for (BeanProperty property : bean.getProperties()) {
traverse(property.getValue(), output);
}
} else if (component instanceof ServiceMetadata) {
ServiceMetadata service = (ServiceMetadata) component;
traverse(service.getServiceComponent(), output);
for (RegistrationListener listener : service.getRegistrationListeners()) {
traverse(listener.getListenerComponent(), output);
}
for (MapEntry e : service.getServiceProperties()) {
traverse(e.getKey(), output);
traverse(e.getValue(), output);
}
} else if (component instanceof ServiceReferenceMetadata) {
ServiceReferenceMetadata reference = (ServiceReferenceMetadata) component;
for (ReferenceListener listener : reference.getReferenceListeners()) {
traverse(listener.getListenerComponent(), output);
}
}
}
/**
* Some services are injected directly into isolated frameworks by default. We do
* not need to model these services. They are not represented as ExportedServices
* (Capabilities) in the various OBR registries, and so cannot be resolved against.
* Since they are injected directly into each isolated framework, we do not need
* an entry in DEPLOYMENT.MF's Deployed-ImportService header for any of these
* services.
*
* @param iface The interface declared on a blueprint reference
* @param blueprintFilter The filter on the blueprint reference
* @return True if the service is not 'blacklisted' and so may be exposed
* in the model being generated.
*/
protected boolean isBlacklisted (String iface, String blueprintFilter) {
_logger.debug(LOG_ENTRY, "isBlacklisted", new Object[]{iface, blueprintFilter});
boolean blacklisted = false;
if (iface != null) {
// JPA - detect interface;
blacklisted |= iface.equals("javax.persistence.EntityManagerFactory");
blacklisted |= iface.equals("javax.persistence.EntityManager");
// JTA - detect interface
blacklisted |= iface.equals("javax.transaction.UserTransaction");
blacklisted |= iface.equals("javax.transaction.TransactionSynchronizationRegistry");
// ConfigurationAdmin - detect interface
blacklisted |= iface.equals("org.osgi.service.cm.ConfigurationAdmin");
// Don't provision against JNDI references
if (blueprintFilter != null && blueprintFilter.trim().length() != 0) {
Map<String, String> filter = ManifestHeaderProcessor.parseFilter(blueprintFilter);
blacklisted |= filter.containsKey(JNDIConstants.JNDI_SERVICENAME);
}
}
_logger.debug(LOG_EXIT, "isBlacklisted", new Object[]{!blacklisted});
return blacklisted;
}
}