/* * 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.*; 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 Set<ProvidedService> m_providedServices = new LinkedHashSet<ProvidedService>(); /** * The handler description. */ private ProvidedServiceHandlerDescription m_description; /** * Get the array of provided service. * @return the list of the provided service. */ public ProvidedService[] getProvidedServices() { return m_providedServices.toArray(new ProvidedService[m_providedServices.size()]); } /** * 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.metadata.Element, java.util.Dictionary) */ public void configure(Element componentMetadata, Dictionary configuration) throws ConfigurationException { m_providedServices.clear(); // Create the dependency according to the component metadata Element[] providedServices = componentMetadata.getElements("Provides"); for (Element providedService : providedServices) { String[] serviceSpecifications = ParseUtils.parseArrays(providedService.getAttribute("specifications")); // Set by the initialize component factory. // Get the factory policy int factory = ProvidedService.SINGLETON_STRATEGY; Class custom = null; String strategy = providedService.getAttribute("strategy"); if (strategy == null) { strategy = providedService.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); } } } // Then create the provided service ProvidedService svc = new ProvidedService(this, serviceSpecifications, factory, custom, configuration); // Post-Registration callback String post = providedService.getAttribute("post-registration"); if (post != null) { Callback cb = new Callback(post, new Class[]{ServiceReference.class}, false, getInstanceManager()); svc.setPostRegistrationCallback(cb); } post = providedService.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 = providedService.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 = providedService.getElements("Controller"); if (controllers != null) { for (Element controller : controllers) { String field = controller.getAttribute("field"); if (field == null) { throw new ConfigurationException("The field attribute of a controller is mandatory"); } String v = controller.getAttribute("value"); boolean value = !(v != null && v.equalsIgnoreCase("false")); String s = controller.getAttribute("specification"); if (s == null) { s = "ALL"; } svc.setController(field, value, s); getInstanceManager().register(new FieldMetadata(field, "boolean"), this); } } if (checkProvidedService(svc)) { m_providedServices.add(svc); } else { StringBuilder itfs = new StringBuilder(); for (String serviceSpecification : serviceSpecifications) { itfs.append(' '); itfs.append(serviceSpecification); } throw new ConfigurationException("The provided service" + itfs + " is not valid"); } // Initialize the description. m_description = new ProvidedServiceHandlerDescription(this, getProvidedServices()); } } /** * 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<String> interfaces, Set<String> classes) throws ClassNotFoundException { // First iterate on found specification in manipulation metadata for (String spec : specs) { interfaces.add(spec); // Iterate on interfaces implemented by the current interface Class clazz = bundle.loadClass(spec); 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<String> acc, Bundle bundle) throws ClassNotFoundException { Class[] clazzes = clazz.getInterfaces(); for (Class clazze : clazzes) { acc.add(clazze.getName()); collectInterfaces(clazze, 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<String> acc, Bundle bundle) throws ClassNotFoundException { Class[] clazzes = clazz.getInterfaces(); for (Class clazze : clazzes) { acc.add(clazze.getName()); collectInterfaces(clazze, 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<String> 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 { Set<ClassLoader> classloaders = new LinkedHashSet<ClassLoader>(); for (int i = 0; i < svc.getServiceSpecifications().length; i++) { String specName = svc.getServiceSpecifications()[i]; // Check service level dependencies try { Class spec = load(specName, classloaders); classloaders.add(spec.getClassLoader()); 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) { // Ignore it, keep and going. } catch (ClassNotFoundException e) { throw new ConfigurationException("Service Providing: The service specification " + svc.getServiceSpecifications()[i] + " cannot be loaded", e); } catch (IllegalArgumentException e) { throw new ConfigurationException("Service Providing: The field 'specification' of the service specification " + svc.getServiceSpecifications()[i] + " is not accessible", e); } catch (IllegalAccessException e) { throw new ConfigurationException("Service Providing: The field 'specification' of the service specification " + svc.getServiceSpecifications()[i] + " is not accessible", e); } 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); } } return true; } private Class load(String specName, Set<ClassLoader> classloaders) throws ClassNotFoundException { try { return getInstanceManager().getFactory().loadClass(specName); } catch (ClassNotFoundException e) { // Try collected classloaders. } for (ClassLoader cl : classloaders) { try { return cl.loadClass(specName); } catch (ClassNotFoundException e) { // Try next one. } } throw new ClassNotFoundException(specName); } /** * 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.FieldInterceptor#onSet(Object, String, Object) */ public void onSet(Object pojo, String fieldName, Object value) { // Verify that the field name correspond to a dependency for (ProvidedService svc : m_providedServices) { boolean update = false; // Retrieve a copy of the properties. final Property[] properties = svc.getProperties(); for (Property prop : properties) { 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 controller " + 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.FieldInterceptor#onGet(Object, String, Object) */ public Object onGet(Object pojo, String fieldName, Object value) { for (ProvidedService svc : m_providedServices) { 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 ctrl.getValue(); } } // Else it is not a property return value; } /** * Register the services if the new state is VALID. Un-register 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 => un-register all the services if (state == InstanceManager.INVALID) { for (ProvidedService m_providedService : m_providedServices) { m_providedService.unregisterService(); } return; } // If the new state is VALID => register all the services if (state == InstanceManager.VALID) { for (ProvidedService ps : m_providedServices) { ps.registerService(); } } // If the new state is DISPOSED => cleanup all the provided services listeners if (state == InstanceManager.DISPOSED) { for (ProvidedService ps : m_providedServices) { ps.cleanup(); } } } /** * Adds properties to all provided services. * @param dict : dictionary of properties to add */ public void addProperties(Dictionary dict) { for (ProvidedService ps : m_providedServices) { ps.addProperties(dict); ps.update(); } } /** * Remove properties form all provided services. * * @param dict : dictionary of properties to delete. */ public void removeProperties(Dictionary dict) { for (ProvidedService ps : m_providedServices) { ps.deleteProperties(dict); ps.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 (Property prop : props) { final Object receivedValue = dict.get(prop.getName()); if (receivedValue != null) { update = true; prop.setValue(receivedValue); } } 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 (Element provide : provides) { // First : create the serviceSpecification list String[] serviceSpecification = manipulation.getInterfaces(); String parent = manipulation.getSuperClass(); Set<String> interfaces = new HashSet<String>(); Set<String> parentClasses = new HashSet<String>(); 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); } String serviceSpecificationStr = provide.getAttribute("specifications"); if (serviceSpecificationStr == null) { serviceSpecificationStr = provide.getAttribute("interface"); if (serviceSpecificationStr != null) { warn("The 'interface' attribute is deprecated, use the 'specifications' attribute instead of 'interface'"); } } if (serviceSpecificationStr != null) { List<String> itfs = ParseUtils.parseArraysAsList(serviceSpecificationStr); for (String itf : itfs) if (!interfaces.contains(itf) && !parentClasses.contains(itf) && !desc.getFactory().getClassName().equals(itf)) { desc.getFactory().getLogger().log(Logger.ERROR, "The specification " + itf + " is not implemented by " + metadata.getAttribute("classname")); } interfaces.clear(); interfaces.addAll(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<String> set = new HashSet<String>(interfaces); set.remove(Pojo.class.getName()); // Remove POJO. for (String spec : set) { desc.addProvidedServiceSpecification(spec); if (specs == null) { specs = new StringBuffer("{"); specs.append(spec); } else { specs.append(','); specs.append(spec); } } specs.append('}'); provide.addAttribute(new Attribute("specifications", specs.toString())); // Add interface attribute to avoid checking in the configure method Element[] props = provide.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(); } } } } }