/* * 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.composite.service.provides; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Comparator; import java.util.Dictionary; import java.util.List; import java.util.Properties; import org.apache.felix.ipojo.ComponentInstance; import org.apache.felix.ipojo.ConfigurationException; import org.apache.felix.ipojo.Factory; import org.apache.felix.ipojo.HandlerFactory; import org.apache.felix.ipojo.HandlerManager; import org.apache.felix.ipojo.MissingHandlerException; import org.apache.felix.ipojo.PolicyServiceContext; import org.apache.felix.ipojo.UnacceptableConfiguration; import org.apache.felix.ipojo.architecture.ComponentTypeDescription; import org.apache.felix.ipojo.architecture.HandlerDescription; import org.apache.felix.ipojo.composite.CompositeHandler; import org.apache.felix.ipojo.composite.instance.InstanceHandler; import org.apache.felix.ipojo.composite.service.instantiator.ServiceDependencyHandler; import org.apache.felix.ipojo.composite.service.instantiator.ServiceImporter; import org.apache.felix.ipojo.composite.service.instantiator.SvcInstance; import org.apache.felix.ipojo.metadata.Element; import org.apache.felix.ipojo.parser.ManifestMetadataParser; import org.apache.felix.ipojo.parser.ParseException; import org.apache.felix.ipojo.util.DependencyMetadataHelper; import org.apache.felix.ipojo.util.DependencyModel; import org.apache.felix.ipojo.util.DependencyStateListener; import org.osgi.framework.BundleContext; import org.osgi.framework.Filter; import org.osgi.framework.InvalidSyntaxException; import org.osgi.framework.ServiceReference; /** * Composite Provided Service Handler. * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a> */ public class ProvidedServiceHandler extends CompositeHandler implements DependencyStateListener { /** * External context. */ private BundleContext m_context; /** * List of "available" services in the internal context. */ private List m_services = new ArrayList(); /** * List of exporters. */ private List m_exporters = new ArrayList(); /** * List of managed services. */ private List m_managedServices = new ArrayList(); /** * List of component type. */ private List m_types; /** * Handler description. */ private ProvidedServiceHandlerDescription m_description; /** * Initialize the component type. * @param desc : component type description to populate. * @param metadata : component type metadata. * @throws ConfigurationException : metadata are incorrect. * @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 { Element[] provides = metadata.getElements("provides"); for (int i = 0; i < provides.length; i++) { String action = provides[i].getAttribute("action"); if (action == null) { throw new ConfigurationException("Invalid composition service providing : no specified action"); } else if (action.equalsIgnoreCase("implement")) { String spec = provides[i].getAttribute("specification"); if (spec == null) { throw new ConfigurationException("Malformed provides : the specification attribute is mandatory"); } else { desc.addProvidedServiceSpecification(spec); } } else if (action.equalsIgnoreCase("export")) { String spec = provides[i].getAttribute("specification"); if (spec == null) { throw new ConfigurationException("Malformed exports - Missing the specification attribute"); } else { desc.addProvidedServiceSpecification(spec); } } else { throw new ConfigurationException("Invalid composition service providing : unknown action " + action); } } } /** * Configure the handler. * @param metadata : the metadata of the component * @param configuration : the instance configuration * @throws ConfigurationException : the exporter cannot be created * @see CompositeHandler#configure(org.apache.felix.ipojo.metadata.Element, java.util.Dictionary) */ public void configure(Element metadata, Dictionary configuration) throws ConfigurationException { m_context = getCompositeManager().getContext(); Element[] provides = metadata.getElements("provides", ""); for (int i = 0; i < provides.length; i++) { String action = provides[i].getAttribute("action"); if (action.equalsIgnoreCase("implement")) { ProvidedService svc = new ProvidedService(this, provides[i], Integer.toString(i)); m_managedServices.add(svc); } else if (action.equalsIgnoreCase("export")) { boolean optional = false; boolean aggregate = false; String specification = provides[i].getAttribute("specification"); String filter = "(objectClass=" + specification + ")"; String opt = provides[i].getAttribute("optional"); optional = opt != null && opt.equalsIgnoreCase("true"); String agg = provides[i].getAttribute("aggregate"); aggregate = agg != null && agg.equalsIgnoreCase("true"); String givenFilter = provides[i].getAttribute("filter"); if (givenFilter != null) { filter = "(&" + filter + givenFilter + ")"; //NOPMD } Filter fil = null; try { fil = m_context.createFilter(filter); } catch (InvalidSyntaxException e) { throw new ConfigurationException("An exporter filter is invalid " + filter, e); } Comparator cmp = DependencyMetadataHelper.getComparator(provides[i], m_context); int policy = DependencyMetadataHelper.getPolicy(provides[i]); Class spec = DependencyMetadataHelper.loadSpecification(specification, m_context); ServiceExporter imp = new ServiceExporter(spec, fil, aggregate, optional, cmp, policy, getCompositeManager().getServiceContext(), m_context, this, getCompositeManager()); m_exporters.add(imp); } // Others case cannot happen. The test was already made during the factory initialization. } m_description = new ProvidedServiceHandlerDescription(this, m_managedServices, m_exporters); } /** * Start method. * Start all managed provided service. * @see org.apache.felix.ipojo.composite.CompositeHandler#start() */ public void start() { // Compute imports and instances computeAvailableServices(); computeAvailableTypes(); for (int i = 0; i < m_managedServices.size(); i++) { ProvidedService svc = (ProvidedService) m_managedServices.get(i); try { checkServiceSpecification(svc); svc.start(); } catch (CompositionException e) { error("Cannot start the provided service handler", e); setValidity(false); return; } } for (int i = 0; i < m_exporters.size(); i++) { ServiceExporter exp = (ServiceExporter) m_exporters.get(i); exp.start(); } isHandlerValid(); } /** * Stop method. * Stop all managed provided service. * @see org.apache.felix.ipojo.composite.CompositeHandler#stop() */ public void stop() { for (int i = 0; i < m_managedServices.size(); i++) { ProvidedService svc = (ProvidedService) m_managedServices.get(i); svc.stop(); } for (int i = 0; i < m_exporters.size(); i++) { ServiceExporter exp = (ServiceExporter) m_exporters.get(i); exp.stop(); } } /** * Check the handler validity. * @see org.apache.felix.ipojo.composite.CompositeHandler#isValid() */ private void isHandlerValid() { for (int i = 0; i < m_exporters.size(); i++) { ServiceExporter exp = (ServiceExporter) m_exporters.get(i); if (exp.getState() != DependencyModel.RESOLVED) { setValidity(false); return; } } setValidity(true); } /** * Handler state changed. * @param state : the new instance state. * @see CompositeHandler#stateChanged(int) */ public void stateChanged(int state) { if (state == ComponentInstance.INVALID) { for (int i = 0; i < m_managedServices.size(); i++) { ProvidedService svc = (ProvidedService) m_managedServices.get(i); svc.unregister(); } return; } // If the new state is VALID => register all the services if (state == ComponentInstance.VALID) { for (int i = 0; i < m_managedServices.size(); i++) { ProvidedService svc = (ProvidedService) m_managedServices.get(i); svc.register(); } return; } } /** * Notify the handler that an exporter becomes invalid. * * @param exporter : the implicated exporter. */ public void invalidate(DependencyModel exporter) { // An export is no more valid if (getValidity()) { setValidity(false); } } /** * Notify the handler that an exporter becomes valid. * * @param exporter : the implicated exporter. */ public void validate(DependencyModel exporter) { // An import becomes valid if (!getValidity()) { isHandlerValid(); } } /** * Build the list of available specification. * @return the list of available specification. */ protected List getSpecifications() { return m_services; } /** * Build the list of available specifications. */ private void computeAvailableServices() { // Get instantiated services : ServiceDependencyHandler handler = (ServiceDependencyHandler) getHandler(HandlerFactory.IPOJO_NAMESPACE + ":subservice"); for (int i = 0; handler != null && i < handler.getInstances().size(); i++) { SvcInstance svc = (SvcInstance) handler.getInstances().get(i); String itf = svc.getServiceSpecification(); boolean agg = svc.isAggregate(); boolean opt = svc.isOptional(); SpecificationMetadata specMeta = new SpecificationMetadata(itf, m_context, agg, opt, this); m_services.add(specMeta); } for (int i = 0; handler != null && i < handler.getRequirements().size(); i++) { ServiceImporter imp = (ServiceImporter) handler.getRequirements().get(i); String itf = imp.getSpecification().getName(); boolean agg = imp.isAggregate(); boolean opt = imp.isOptional(); SpecificationMetadata specMeta = new SpecificationMetadata(itf, m_context, agg, opt, this); m_services.add(specMeta); } } /** * Check composite requirement against service specification requirement is available. * @param svc : the provided service to check * @throws CompositionException : occurs if the specification field of the service specification cannot be analyzed correctly. */ private void checkServiceSpecification(ProvidedService svc) throws CompositionException { try { Class spec = m_context.getBundle().loadClass(svc.getSpecification()); Field specField = spec.getField("specification"); Object object = specField.get(null); if (object instanceof String) { Element specification = ManifestMetadataParser.parse((String) object); Element[] reqs = specification.getElements("requires"); for (int j = 0; reqs != null && j < reqs.length; j++) { ServiceImporter imp = getAttachedRequirement(reqs[j]); if (imp != null) { // Fix service-level dependency flag imp.setServiceLevelDependency(); } checkRequirement(imp, reqs[j]); } } else { error("[" + getCompositeManager().getInstanceName() + "] The specification field of the service specification " + svc.getSpecification() + " needs to be a String"); throw new CompositionException("Service Specification checking failed : The specification field of the service specification " + svc.getSpecification() + " needs to be a String"); } } catch (NoSuchFieldException e) { return; // No specification field } catch (ClassNotFoundException e) { error("[" + getCompositeManager().getInstanceName() + "] The service specification " + svc.getSpecification() + " cannot be load"); throw new CompositionException("The service specification " + svc.getSpecification() + " cannot be loaded", e); } catch (IllegalArgumentException e) { error("[" + getCompositeManager().getInstanceName() + "] The field 'specification' of the service specification " + svc.getSpecification() + " is not accessible : " + e.getMessage()); throw new CompositionException("The field 'specification' of the service specification " + svc.getSpecification() + " is not accessible", e); } catch (IllegalAccessException e) { error("[" + getCompositeManager().getInstanceName() + "] The field 'specification' of the service specification " + svc.getSpecification() + " is not accessible : " + e.getMessage()); throw new CompositionException("The field 'specification' of the service specification " + svc.getSpecification() + " is not accessible", e); } catch (ParseException e) { error("[" + getCompositeManager().getInstanceName() + "] The field 'specification' of the service specification " + svc.getSpecification() + " does not contain a valid String : " + e.getMessage()); throw new CompositionException("The field 'specification' of the service specification " + svc.getSpecification() + " does not contain a valid String", e); } } /** * Look for the implementation (i.e. composite) requirement for the given service-level requirement metadata. * @param element : the service-level requirement metadata * @return the ServiceImporter object, null if not found or if the DependencyHandler is not plugged to the instance */ private ServiceImporter getAttachedRequirement(Element element) { ServiceDependencyHandler handler = (ServiceDependencyHandler) getHandler(HandlerFactory.IPOJO_NAMESPACE + ":subservice"); if (handler == null) { return null; } String identity = element.getAttribute("id"); if (identity != null) { // Look for dependency Id for (int i = 0; i < handler.getRequirements().size(); i++) { ServiceImporter imp = (ServiceImporter) handler.getRequirements().get(i); if (imp.getId().equals(identity)) { return imp; } } } // 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.getRequirements().size(); i++) { ServiceImporter imp = (ServiceImporter) handler.getRequirements().get(i); if (imp.getId().equals(requirement) || imp.getSpecification().getName().equals(requirement)) { return imp; } } return null; } /** * Check the correctness of the composite requirement against the service level dependency. * @param imp : requirement to check * @param elem : service-level dependency metadata * @throws CompositionException : occurs if the requirement does not match with service-level specification requirement */ private void checkRequirement(ServiceImporter imp, Element elem) throws CompositionException { 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 (imp == null) { // Add the missing requirement ServiceDependencyHandler handler = (ServiceDependencyHandler) getHandler(HandlerFactory.IPOJO_NAMESPACE + ":subservice"); if (handler == null) { // Look for the ServiceDependencyHandler factory HandlerManager handlerManager = null; try { ServiceReference[] refs = m_context.getServiceReferences(Factory.class.getName(), "(&(handler.name=subservice)(handler.namespace=" + HandlerFactory.IPOJO_NAMESPACE + ")(handler.type=composite))"); Factory factory = (Factory) m_context.getService(refs[0]); handlerManager = (HandlerManager) factory.createComponentInstance(null, getCompositeManager().getServiceContext()); } catch (InvalidSyntaxException e) { // Should not happen } catch (UnacceptableConfiguration e) { // Should not happen } catch (MissingHandlerException e) { // Should not happen } catch (ConfigurationException e) { // Should not happen } // Add the required handler try { handlerManager.init(getCompositeManager(), new Element("composite", ""), new Properties()); } catch (ConfigurationException e) { error("Internal error : cannot configure the Import Handler : " + e.getMessage()); throw new CompositionException("Internal error : cannot configure the Import Handler", e); } handler = (ServiceDependencyHandler) handlerManager.getHandler(); getCompositeManager().addCompositeHandler(handlerManager); } String spec = elem.getAttribute("specification"); String filter = "(&(objectClass=" + spec + ")(!(instance.name=" + getCompositeManager().getInstanceName() + ")))"; // Cannot import yourself String givenFilter = elem.getAttribute("filter"); if (givenFilter != null) { filter = "(&" + filter + givenFilter + ")"; //NOPMD } BundleContext context = new PolicyServiceContext(getCompositeManager().getGlobalContext(), getCompositeManager().getParentServiceContext(), PolicyServiceContext.GLOBAL); Filter fil = null; try { fil = getCompositeManager().getGlobalContext().createFilter(filter); } catch (InvalidSyntaxException e) { throw new CompositionException("A required filter " + filter + " is malformed", e); } Class specToImport = null; try { specToImport = getCompositeManager().getGlobalContext().getBundle().loadClass(spec); } catch (ClassNotFoundException e) { throw new CompositionException("A required specification cannot be loaded : " + spec, e); } ServiceImporter importer = new ServiceImporter(specToImport, fil, agg, opt, null, DependencyModel.DYNAMIC_BINDING_POLICY, context, null, handler); handler.getRequirements().add(importer); SpecificationMetadata specMeta = new SpecificationMetadata(spec, m_context, agg, opt, this); m_services.add(specMeta); // Update the available types return; } if (imp.isAggregate() && !agg) { error("[" + getCompositeManager().getInstanceName() + "] The requirement " + elem.getAttribute("specification") + " is aggregate in the implementation and is declared as a simple service-level requirement"); throw new CompositionException("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 (filter != null) { String filter2 = imp.getFilter(); if (filter2 == null || !filter2.equalsIgnoreCase(filter)) { error("[" + getCompositeManager().getInstanceName() + "] The specification requirement " + elem.getAttribute("specification") + " as not the same filter as declared in the service-level requirement"); throw new CompositionException("The specification requirement " + elem.getAttribute("specification") + " as not the same filter as declared in the service-level requirement"); } } } public HandlerDescription getDescription() { return m_description; } /** * Build available instance types. */ private void computeAvailableTypes() { InstanceHandler handler = (InstanceHandler) getHandler(HandlerFactory.IPOJO_NAMESPACE + ":instance"); if (handler == null) { m_types = new ArrayList(); } else { m_types = handler.getUsedType(); } } public List getInstanceType() { return m_types; } }