blob: 084255db30aa1f57d63cb55d66dc02ac2c7684cb [file] [log] [blame]
/*
* 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.felix.ipojo.handlers.providedservice;
import java.lang.reflect.Field;
import java.util.Dictionary;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.apache.felix.ipojo.ConfigurationException;
import org.apache.felix.ipojo.HandlerFactory;
import org.apache.felix.ipojo.InstanceManager;
import org.apache.felix.ipojo.Pojo;
import org.apache.felix.ipojo.PrimitiveHandler;
import org.apache.felix.ipojo.architecture.ComponentTypeDescription;
import org.apache.felix.ipojo.architecture.HandlerDescription;
import org.apache.felix.ipojo.architecture.PropertyDescription;
import org.apache.felix.ipojo.handlers.dependency.Dependency;
import org.apache.felix.ipojo.handlers.dependency.DependencyHandler;
import org.apache.felix.ipojo.handlers.providedservice.ProvidedService.ServiceController;
import org.apache.felix.ipojo.metadata.Attribute;
import org.apache.felix.ipojo.metadata.Element;
import org.apache.felix.ipojo.parser.FieldMetadata;
import org.apache.felix.ipojo.parser.ManifestMetadataParser;
import org.apache.felix.ipojo.parser.ParseException;
import org.apache.felix.ipojo.parser.ParseUtils;
import org.apache.felix.ipojo.parser.PojoMetadata;
import org.apache.felix.ipojo.util.Callback;
import org.apache.felix.ipojo.util.Logger;
import org.apache.felix.ipojo.util.Property;
import org.osgi.framework.Bundle;
import org.osgi.framework.ServiceReference;
/**
* Composite Provided Service Handler.
* This handler manage the service providing for a composition.
* @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
*/
public class ProvidedServiceHandler extends PrimitiveHandler {
/**
* The list of the provided service.
*/
private ProvidedService[] m_providedServices = new ProvidedService[0];
/**
* The handler description.
*/
private ProvidedServiceHandlerDescription m_description;
/**
* Add a provided service to the list .
*
* @param svc : the provided service to add
*/
private void addProvidedService(ProvidedService svc) {
// Verify that the provided service is not already in the array.
for (int i = 0; i < m_providedServices.length; i++) {
if (m_providedServices[i] == svc) { return; }
}
if (m_providedServices.length > 0) {
ProvidedService[] newPS = new ProvidedService[m_providedServices.length + 1];
System.arraycopy(m_providedServices, 0, newPS, 0, m_providedServices.length);
newPS[m_providedServices.length] = svc;
m_providedServices = newPS;
} else {
m_providedServices = new ProvidedService[] { svc };
}
}
/**
* Get the array of provided service.
* @return the list of the provided service.
*/
public ProvidedService[] getProvidedServices() {
return m_providedServices;
}
/**
* Configure the handler.
* @param componentMetadata : the component type metadata
* @param configuration : the instance configuration
* @throws ConfigurationException : the metadata are not correct.
* @see org.apache.felix.ipojo.Handler#configure(org.apache.felix.ipojo.InstanceManager, org.apache.felix.ipojo.metadata.Element, java.util.Dictionary)
*/
public void configure(Element componentMetadata, Dictionary configuration) throws ConfigurationException {
m_providedServices = new ProvidedService[0];
// Create the dependency according to the component metadata
Element[] providedServices = componentMetadata.getElements("Provides");
for (int i = 0; i < providedServices.length; i++) {
String[] serviceSpecifications = ParseUtils.parseArrays(providedServices[i].getAttribute("specifications")); // Set by the initialize component factory.
// Get the factory policy
int factory = ProvidedService.SINGLETON_STRATEGY;
Class custom = null;
String strategy = providedServices[i].getAttribute("strategy");
if (strategy == null) {
strategy = providedServices[i].getAttribute("factory");
}
if (strategy != null) {
if ("singleton".equalsIgnoreCase(strategy)) {
factory = ProvidedService.SINGLETON_STRATEGY;
} else if ("service".equalsIgnoreCase(strategy)) {
factory = ProvidedService.SERVICE_STRATEGY;
} else if ("method".equalsIgnoreCase(strategy)) {
factory = ProvidedService.STATIC_STRATEGY;
} else if ("instance".equalsIgnoreCase(strategy)) {
factory = ProvidedService.INSTANCE_STRATEGY;
} else {
// Customized policy
try {
custom = getInstanceManager().getContext().getBundle().loadClass(strategy);
if (! CreationStrategy.class.isAssignableFrom(custom)) {
throw new ConfigurationException("The custom creation policy class " + custom.getName() + " does not implement " + CreationStrategy.class.getName());
}
} catch (ClassNotFoundException e) {
throw new ConfigurationException("The custom creation policy class " + strategy + " cannot be loaded " + e.getMessage());
}
}
}
// Then create the provided service
ProvidedService svc = new ProvidedService(this, serviceSpecifications, factory, custom, configuration);
// Post-Registration callback
String post = providedServices[i].getAttribute("post-registration");
if (post != null) {
Callback cb = new Callback(post, new Class[] {ServiceReference.class}, false, getInstanceManager());
svc.setPostRegistrationCallback(cb);
}
post = providedServices[i].getAttribute("post-unregistration");
if (post != null) {
// TODO Can we really send the service reference here ?
Callback cb = new Callback(post, new Class[] {ServiceReference.class}, false, getInstanceManager());
svc.setPostUnregistrationCallback(cb);
}
Element[] props = providedServices[i].getElements("Property");
if (props != null) {
//Property[] properties = new Property[props.length];
Property[] properties = new Property[props.length];
for (int j = 0; j < props.length; j++) {
String name = props[j].getAttribute("name");
String value = props[j].getAttribute("value");
String type = props[j].getAttribute("type");
String field = props[j].getAttribute("field");
Property prop = new Property(name, field, null, value, type, getInstanceManager(), this);
properties[j] = prop;
// Check if the instance configuration has a value for this property
Object object = configuration.get(prop.getName());
if (object != null) {
prop.setValue(object);
}
if (field != null) {
getInstanceManager().register(new FieldMetadata(field, type), this);
// Cannot register the property as the interception is necessary
// to deal with registration update.
}
}
// Attach to properties to the provided service
svc.setProperties(properties);
}
Element[] controllers = providedServices[i].getElements("Controller");
if (controllers != null) {
for (int k = 0; k < controllers.length; k++) {
String field = controllers[k].getAttribute("field");
if (field == null) {
throw new ConfigurationException("The field attribute of a controller is mandatory");
}
String v = controllers[k].getAttribute("value");
boolean value = ! (v != null && v.equalsIgnoreCase("false"));
String s = controllers[k].getAttribute("specification");
if (s == null) {
s ="ALL";
}
svc.setController(field, value, s);
getInstanceManager().register(new FieldMetadata(field, "boolean"), this);
}
}
if (checkProvidedService(svc)) {
addProvidedService(svc);
} else {
StringBuffer itfs = new StringBuffer();
for (int j = 0; j < serviceSpecifications.length; j++) {
itfs.append(' ');
itfs.append(serviceSpecifications[j]);
}
throw new ConfigurationException("The provided service" + itfs + " is not valid");
}
// Initialize the description.
m_description = new ProvidedServiceHandlerDescription(this, m_providedServices);
}
}
/**
* Collect interfaces implemented by the POJO.
* @param specs : implemented interfaces.
* @param parent : parent class.
* @param bundle : Bundle object.
* @param interfaces : the set of implemented interfaces
* @param classes : the set of extended classes
* @throws ClassNotFoundException : occurs when an interface cannot be loaded.
*/
private void computeInterfacesAndSuperClasses(String[] specs, String parent, Bundle bundle, Set interfaces, Set classes) throws ClassNotFoundException {
// First iterate on found specification in manipulation metadata
for (int i = 0; i < specs.length; i++) {
interfaces.add(specs[i]);
// Iterate on interfaces implemented by the current interface
Class clazz = bundle.loadClass(specs[i]);
collectInterfaces(clazz, interfaces, bundle);
}
// Look for parent class.
if (parent != null) {
Class clazz = bundle.loadClass(parent);
collectInterfacesFromClass(clazz, interfaces, bundle);
classes.add(parent);
collectParentClassesFromClass(clazz, classes, bundle);
}
}
/**
* Look for inherited interfaces.
* @param clazz : interface name to explore (class object)
* @param acc : set (accumulator)
* @param bundle : bundle
* @throws ClassNotFoundException : occurs when an interface cannot be loaded.
*/
private void collectInterfaces(Class clazz, Set acc, Bundle bundle) throws ClassNotFoundException {
Class[] clazzes = clazz.getInterfaces();
for (int i = 0; i < clazzes.length; i++) {
acc.add(clazzes[i].getName());
collectInterfaces(clazzes[i], acc, bundle);
}
}
/**
* Collect interfaces for the given class.
* This method explores super class to.
* @param clazz : class object.
* @param acc : set of implemented interface (accumulator)
* @param bundle : bundle.
* @throws ClassNotFoundException : occurs if an interface cannot be load.
*/
private void collectInterfacesFromClass(Class clazz, Set acc, Bundle bundle) throws ClassNotFoundException {
Class[] clazzes = clazz.getInterfaces();
for (int i = 0; i < clazzes.length; i++) {
acc.add(clazzes[i].getName());
collectInterfaces(clazzes[i], acc, bundle);
}
// Iterate on parent classes
Class sup = clazz.getSuperclass();
if (sup != null) {
collectInterfacesFromClass(sup, acc, bundle);
}
}
/**
* Collect parent classes for the given class.
* @param clazz : class object.
* @param acc : set of extended classes (accumulator)
* @param bundle : bundle.
* @throws ClassNotFoundException : occurs if an interface cannot be load.
*/
private void collectParentClassesFromClass(Class clazz, Set acc, Bundle bundle) throws ClassNotFoundException {
Class parent = clazz.getSuperclass();
if (parent != null) {
acc.add(parent.getName());
collectParentClassesFromClass(parent, acc, bundle);
}
}
/**
* Check the provided service given in argument in the sense that the metadata are consistent.
* @param svc : the provided service to check.
* @return true if the provided service is correct
* @throws ConfigurationException : the checked provided service is not correct.
*/
private boolean checkProvidedService(ProvidedService svc) throws ConfigurationException {
for (int i = 0; i < svc.getServiceSpecifications().length; i++) {
String specName = svc.getServiceSpecifications()[i];
// Check service level dependencies
try {
Class spec = getInstanceManager().getFactory().loadClass(specName);
Field specField = spec.getField("specification");
Object specif = specField.get(null);
if (specif instanceof String) {
Element specification = ManifestMetadataParser.parse((String) specif);
Element[] deps = specification.getElements("requires");
for (int j = 0; deps != null && j < deps.length; j++) {
Dependency dep = getAttachedDependency(deps[j]);
if (dep != null) {
// Fix service-level dependency flag
dep.setServiceLevelDependency();
}
isDependencyCorrect(dep, deps[j]);
}
} else {
throw new ConfigurationException("Service Providing: The specification field of the service specification " + svc.getServiceSpecifications()[i] + " needs to be a String");
}
} catch (NoSuchFieldException e) {
return true; // No specification field
} catch (ClassNotFoundException e) {
throw new ConfigurationException("Service Providing: The service specification " + svc.getServiceSpecifications()[i] + " cannot be load");
} catch (IllegalArgumentException e) {
throw new ConfigurationException("Service Providing: The field 'specification' of the service specification " + svc.getServiceSpecifications()[i] + " is not accessible : " + e.getMessage());
} catch (IllegalAccessException e) {
throw new ConfigurationException("Service Providing: The field 'specification' of the service specification " + svc.getServiceSpecifications()[i] + " is not accessible : " + e.getMessage());
} catch (ParseException e) {
throw new ConfigurationException("Service Providing: The field 'specification' of the service specification " + svc.getServiceSpecifications()[i] + " does not contain a valid String : " + e.getMessage());
}
}
return true;
}
/**
* Look for the implementation (i.e. component) dependency for the given service-level requirement metadata.
* @param element : the service-level requirement metadata
* @return the Dependency object, null if not found or if the DependencyHandler is not plugged to the instance
*/
private Dependency getAttachedDependency(Element element) {
DependencyHandler handler = (DependencyHandler) getHandler(HandlerFactory.IPOJO_NAMESPACE + ":requires");
if (handler == null) { return null; }
String identity = element.getAttribute("id");
if (identity != null) {
// Look for dependency Id
for (int i = 0; i < handler.getDependencies().length; i++) {
if (handler.getDependencies()[i].getId().equals(identity)) { return handler.getDependencies()[i]; }
}
}
// If not found or no id, look for a dependency with the same specification
String requirement = element.getAttribute("specification");
for (int i = 0; i < handler.getDependencies().length; i++) {
if ((handler.getDependencies()[i].getSpecification().getName()).equals(requirement)) { return handler.getDependencies()[i]; }
}
return null;
}
/**
* Check the correctness of the implementation dependency against the service level dependency.
* @param dep : dependency to check
* @param elem : service-level dependency metadata
* @throws ConfigurationException : the service level dependency and the implementation dependency does not match.
*/
private void isDependencyCorrect(Dependency dep, Element elem) throws ConfigurationException {
String optional = elem.getAttribute("optional");
boolean opt = optional != null && optional.equalsIgnoreCase("true");
String aggregate = elem.getAttribute("aggregate");
boolean agg = aggregate != null && aggregate.equalsIgnoreCase("true");
if (dep == null && !opt) {
throw new ConfigurationException("Service Providing: The requirement " + elem.getAttribute("specification") + " is not present in the implementation and is declared as a mandatory service-level requirement");
}
if (dep != null && dep.isAggregate() && !agg) {
throw new ConfigurationException("Service Providing: The requirement " + elem.getAttribute("specification") + " is aggregate in the implementation and is declared as a simple service-level requirement");
}
String filter = elem.getAttribute("filter");
if (dep != null && filter != null) {
String filter2 = dep.getFilter();
if (filter2 == null || !filter2.equalsIgnoreCase(filter)) {
throw new ConfigurationException("Service Providing: The specification requirement " + elem.getAttribute("specification") + " has not the same filter as declared in the service-level requirement");
}
}
}
/**
* Stop the provided service handler.
*
* @see org.apache.felix.ipojo.Handler#stop()
*/
public void stop() {
//Nothing to do.
}
/**
* Start the provided service handler.
*
* @see org.apache.felix.ipojo.Handler#start()
*/
public void start() {
// Nothing to do.
}
/**
* Setter Callback Method.
* Check if the modified field is a property to update the value.
* @param pojo : the pojo object on which the field is accessed
* @param fieldName : field name
* @param value : new value
* @see org.apache.felix.ipojo.Handler#onSet(Object,
* java.lang.String, java.lang.Object)
*/
public void onSet(Object pojo, String fieldName, Object value) {
// Verify that the field name correspond to a dependency
for (int i = 0; i < m_providedServices.length; i++) {
ProvidedService svc = m_providedServices[i];
boolean update = false;
for (int j = 0; j < svc.getProperties().length; j++) {
Property prop = svc.getProperties()[j];
if (fieldName.equals(prop.getField()) && ! prop.getValue().equals(value)) {
// it is the associated property
prop.setValue(value);
update = true;
}
}
if (update) {
svc.update();
}
ServiceController ctrl = svc.getController(fieldName);
if (ctrl != null) {
if (value instanceof Boolean) {
ctrl.setValue((Boolean) value);
} else {
warn("Boolean value expected for the service controler " + fieldName);
}
}
}
// Else do nothing
}
/**
* Getter Callback Method.
* Check if the field is a property to push the stored value.
* @param pojo : the pojo object on which the field is accessed
* @param fieldName : field name
* @param value : value pushed by the previous handler
* @return the stored value or the previous value.
* @see org.apache.felix.ipojo.Handler#onGet(Object,
* java.lang.String, java.lang.Object)
*/
public Object onGet(Object pojo, String fieldName, Object value) {
for (int i = 0; i < m_providedServices.length; i++) {
ProvidedService svc = m_providedServices[i];
for (int j = 0; j < svc.getProperties().length; j++) {
Property prop = svc.getProperties()[j];
if (fieldName.equals(prop.getField())) {
// Manage the No Value case.
return prop.onGet(pojo, fieldName, value);
}
}
ServiceController ctrl = svc.getController(fieldName);
if (ctrl != null) {
return new Boolean(ctrl.getValue());
}
}
// Else it is not a property
return value;
}
/**
* Register the services if the new state is VALID. Unregister the services
* if the new state is UNRESOLVED.
*
* @param state : the new instance state.
* @see org.apache.felix.ipojo.Handler#stateChanged(int)
*/
public void stateChanged(int state) {
// If the new state is INVALID => unregister all the services
if (state == InstanceManager.INVALID) {
for (int i = 0; i < m_providedServices.length; i++) {
m_providedServices[i].unregisterService();
}
return;
}
// If the new state is VALID => register all the services
if (state == InstanceManager.VALID) {
for (int i = 0; i < m_providedServices.length; i++) {
m_providedServices[i].registerService();
}
return;
}
}
/**
* Adds properties to all provided services.
* @param dict : dictionary of properties to add
*/
public void addProperties(Dictionary dict) {
for (int i = 0; i < m_providedServices.length; i++) {
m_providedServices[i].addProperties(dict);
m_providedServices[i].update();
}
}
/**
* Remove properties form all provided services.
*
* @param dict : dictionary of properties to delete.
*/
public void removeProperties(Dictionary dict) {
for (int i = 0; i < m_providedServices.length; i++) {
m_providedServices[i].deleteProperties(dict);
m_providedServices[i].update();
}
}
/**
* Build the provided service description.
* @return the handler description.
* @see org.apache.felix.ipojo.Handler#getDescription()
*/
public HandlerDescription getDescription() {
return m_description;
}
/**
* Reconfigure provided service.
* @param dict : the new instance configuration.
* @see org.apache.felix.ipojo.Handler#reconfigure(java.util.Dictionary)
*/
public void reconfigure(Dictionary dict) {
for (int j = 0; j < getProvidedServices().length; j++) {
ProvidedService svc = getProvidedServices()[j];
Property[] props = svc.getProperties();
boolean update = false;
for (int k = 0; k < props.length; k++) {
if (dict.get(props[k].getName()) != null) {
update = true;
props[k].setValue(dict.get(props[k].getName()));
}
}
if (update) {
svc.update();
}
}
}
/**
* Initialize the component type.
* @param desc : component type description to populate.
* @param metadata : component type metadata.
* @throws ConfigurationException : occurs when the POJO does not implement any interfaces.
* @see org.apache.felix.ipojo.Handler#initializeComponentFactory(org.apache.felix.ipojo.architecture.ComponentTypeDescription, org.apache.felix.ipojo.metadata.Element)
*/
public void initializeComponentFactory(ComponentTypeDescription desc, Element metadata) throws ConfigurationException {
// Change ComponentInfo
Element[] provides = metadata.getElements("provides");
PojoMetadata manipulation = getFactory().getPojoMetadata();
for (int i = 0; i < provides.length; i++) {
// First : create the serviceSpecification list
String[] serviceSpecification = manipulation.getInterfaces();
String parent = manipulation.getSuperClass();
Set interfaces = new HashSet();
Set parentClasses = new HashSet();
try {
computeInterfacesAndSuperClasses(serviceSpecification, parent, desc.getBundleContext().getBundle(), interfaces, parentClasses);
getLogger().log(Logger.INFO, "Collected interfaces from " + metadata.getAttribute("classname") + " : " + interfaces);
getLogger().log(Logger.INFO, "Collected super classes from " + metadata.getAttribute("classname") + " : " + parentClasses);
} catch (ClassNotFoundException e) {
throw new ConfigurationException("An interface or parent class cannot be loaded : " + e.getMessage());
}
String serviceSpecificationStr = provides[i].getAttribute("specifications");
if (serviceSpecificationStr == null) {
serviceSpecificationStr = provides[i].getAttribute("interface");
if (serviceSpecificationStr != null) {
warn("The 'interface' attribute is deprecated, use the 'specifications' attribute instead of 'interface'");
}
}
if (serviceSpecificationStr != null) {
List itfs = ParseUtils.parseArraysAsList(serviceSpecificationStr);
for (int j = 0; j < itfs.size(); j++) {
if (! interfaces.contains(itfs.get(j))
&& ! parentClasses.contains(itfs.get(j))
&& ! desc.getFactory().getClassName().equals(itfs.get(j))) {
desc.getFactory().getLogger().log(Logger.ERROR, "The specification " + itfs.get(j) + " is not implemented by " + metadata.getAttribute("classname"));
}
}
interfaces = new HashSet(itfs);
}
if (interfaces.isEmpty()) {
warn("No service interface found in the class hierarchy, use the implementation class");
interfaces.add(desc.getFactory().getClassName());
}
StringBuffer specs = null;
Set set = new HashSet(interfaces);
set.remove(Pojo.class.getName()); // Remove POJO.
Iterator iterator = set.iterator();
while (iterator.hasNext()) {
String spec = (String) iterator.next();
desc.addProvidedServiceSpecification(spec);
if (specs == null) {
specs = new StringBuffer("{");
specs.append(spec);
} else {
specs.append(',');
specs.append(spec);
}
}
specs.append('}');
provides[i].addAttribute(new Attribute("specifications", specs.toString())); // Add interface attribute to avoid checking in the configure method
Element[] props = provides[i].getElements("property");
for (int j = 0; props != null && j < props.length; j++) {
String name = props[j].getAttribute("name");
String value = props[j].getAttribute("value");
String type = props[j].getAttribute("type");
String field = props[j].getAttribute("field");
// Get property name :
if (field != null && name == null) {
name = field;
}
// Check type if not already set
if (type == null) {
if (field == null) {
throw new ConfigurationException("The property " + name + " has neither type nor field.");
}
FieldMetadata fieldMeta = manipulation.getField(field);
if (fieldMeta == null) {
throw new ConfigurationException("A declared property was not found in the implementation class : " + field);
}
type = fieldMeta.getFieldType();
props[j].addAttribute(new Attribute("type", type));
}
// Is the property set to immutable
boolean immutable = false;
String imm = props[j].getAttribute("immutable");
if (imm != null && imm.equalsIgnoreCase("true")) {
immutable = true;
}
PropertyDescription pd = new PropertyDescription(name, type, value, immutable);
desc.addProperty(pd);
String man = props[j].getAttribute("mandatory");
if (man != null && man.equalsIgnoreCase("true")) {
pd.setMandatory();
}
}
}
}
}