/* * 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; import org.apache.felix.ipojo.architecture.ComponentTypeDescription; import org.apache.felix.ipojo.metadata.Attribute; import org.apache.felix.ipojo.metadata.Element; import org.apache.felix.ipojo.parser.ParseUtils; import org.apache.felix.ipojo.parser.PojoMetadata; import org.apache.felix.ipojo.util.Log; import org.apache.felix.ipojo.util.Logger; import org.apache.felix.ipojo.util.Tracker; import org.apache.felix.ipojo.util.TrackerCustomizer; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.InvalidSyntaxException; import org.osgi.framework.ServiceReference; import java.security.ProtectionDomain; import java.util.*; /** * The component factory manages component instance objects. This management * consists to create and manage component instances build with the current * component factory. If the factory is public a {@link Factory} service is exposed. * * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a> * @see org.apache.felix.ipojo.Factory * @see org.apache.felix.ipojo.IPojoFactory * @see org.apache.felix.ipojo.util.TrackerCustomizer */ public class ComponentFactory extends IPojoFactory implements TrackerCustomizer { /** * System property set to automatically attach primitive handlers to primitive * component types. * The value is a String parsed as a list (comma separated). Each element is * the fully qualified name of the handler <code>namespace:name</code>. */ public static final String HANDLER_AUTO_PRIMITIVE = "org.apache.felix.ipojo.handler.auto.primitive"; /** * The tracker used to track required handler factories. * Immutable once set. */ protected Tracker m_tracker; /** * The class loader to delegate classloading. * Immutable once set. */ private FactoryClassloader m_classLoader; /** * The component implementation class. * (manipulated byte array) */ private byte[] m_clazz; /** * The component implementation qualified class name. * Immutable once set. * This attribute is set during the creation of the factory. */ private String m_classname; /** * The manipulation metadata of the implementation class. * Immutable once set. * This attribute is set during the creation of the factory. */ private PojoMetadata m_manipulation; /** * A flag enabling / disabling the use of the Factory classloader to define the class. * This flag must be enabled if the component class was manipulated on the fly. */ private boolean m_useFactoryClassloader = false; /** * Creates a instance manager factory. * The class is given in parameter. The component type is not a composite. * * @param context the bundle context * @param clazz the component class * @param element the metadata of the component * @throws ConfigurationException if the element describing the factory is malformed. */ public ComponentFactory(BundleContext context, byte[] clazz, Element element) throws ConfigurationException { this(context, element); m_clazz = clazz; } /** * Creates a instance manager factory. * * @param context the bundle context * @param element the metadata of the component to create * @throws ConfigurationException if element describing the factory is malformed. */ public ComponentFactory(BundleContext context, Element element) throws ConfigurationException { super(context, element); check(element); // NOPMD. This invocation is normal. } /** * Sets the flag enabling / disabling the factory classloader. * * @param use <code>true</code> enables the factory classloader. */ public void setUseFactoryClassloader(boolean use) { m_useFactoryClassloader = use; } /** * Gets the component type description of the current factory. * * @return the description of the component type attached to this factory. * @see org.apache.felix.ipojo.IPojoFactory#getComponentTypeDescription() */ public ComponentTypeDescription getComponentTypeDescription() { return new PrimitiveTypeDescription(this); } /** * Allows a factory to check if the given element is well-formed. * A component factory metadata is correct if they contain the 'classname' attribute. * As this method is called from the (single-threaded) constructor, no synchronization is needed. * * @param element the metadata describing the component * @throws ConfigurationException if the element describing the factory is malformed. */ public void check(Element element) throws ConfigurationException { m_classname = element.getAttribute("classname"); if (m_classname == null) { throw new ConfigurationException("A component needs a class name : " + element); } m_manipulation = new PojoMetadata(m_componentMetadata); } /** * Gets the class name. * No synchronization needed, the classname is immutable. * * @return the class name. * @see org.apache.felix.ipojo.IPojoFactory#getClassName() */ public String getClassName() { return m_classname; } /** * Creates a primitive instance. * This method is called when holding the lock. * * @param config the instance configuration * @param context the service context (null if the instance has to be created in the global space). * @param handlers the handlers to attach to the instance * @return the created instance * @throws org.apache.felix.ipojo.ConfigurationException * if the configuration process failed. * @see org.apache.felix.ipojo.IPojoFactory#createInstance(java.util.Dictionary, org.apache.felix.ipojo.IPojoContext, org.apache.felix.ipojo.HandlerManager[]) */ public ComponentInstance createInstance(Dictionary config, IPojoContext context, HandlerManager[] handlers) throws org.apache.felix.ipojo.ConfigurationException { InstanceManager instance = new InstanceManager(this, context, handlers); try { instance.configure(m_componentMetadata, config); instance.start(); return instance; } catch (ConfigurationException e) { // An exception occurs while executing the configure or start // methods, the instance is stopped so the architecture service is still published and so we can debug // the issue. instance.stop(); throw e; } catch (Throwable e) { // All others exception are handled here. // As for the previous case, the instance is stopped. instance.stop(); m_logger.log(Logger.INFO, "An error occurred when creating an instance of " + getFactoryName(), e); throw new ConfigurationException(e.getMessage(), e); } } /** * Defines a class. * This method needs to be synchronized to avoid that the classloader * is created twice. * This method delegates the <code>define</code> method invocation to the * factory classloader. * * @param name the qualified name of the class * @param clazz the byte array of the class * @param domain the protection domain of the class * @return the defined class object */ public synchronized Class<? extends Object> defineClass(String name, byte[] clazz, ProtectionDomain domain) { if (!m_useFactoryClassloader) { m_logger.log(Log.WARNING, "A class definition was required even without the factory classloader enabled"); } if (m_classLoader == null) { m_classLoader = new FactoryClassloader(this); } return m_classLoader.defineClass(name, clazz, domain); } /** * Loads a class. This method checks if the class * to load is the implementation class or not. * If it is, the factory classloader is used, else * the {@link Bundle#loadClass(String)} is called. * * The implementation class is loaded using the factory classloader only if the factory classloader was enabled * * @param className the name of the class to load * @return the resulting Class object * @throws ClassNotFoundException if the class is not found * @see #setUseFactoryClassloader(boolean) */ public Class loadClass(String className) throws ClassNotFoundException { if (m_useFactoryClassloader && m_clazz != null && m_classname.equals(className)) { // Immutable fields. return defineClass(className, m_clazz, null); } return m_context.getBundle().loadClass(className); } /** * Starts the factory. * This method is not called when holding the monitor lock. */ public void starting() { if (m_tracker == null) { if (m_requiredHandlers.size() != 0) { try { String filter = "(&(" + Handler.HANDLER_TYPE_PROPERTY + "=" + PrimitiveHandler.HANDLER_TYPE + ")" + "(factory.state=1)" + ")"; m_tracker = new Tracker(m_context, m_context.createFilter(filter), this); m_tracker.open(); } catch (InvalidSyntaxException e) { m_logger.log(Logger.ERROR, "A factory filter is not valid: " + e.getMessage()); //Holding the lock should not be an issue here. stop(); } } } // Else, the tracking has already started. } /** * Stops all the instance managers. * This method is called when holding the lock. */ public void stopping() { if (m_tracker != null) { m_tracker.close(); m_tracker = null; } } /** * Computes the factory name. The factory name is computed from * the 'name' and 'classname' attributes. * This method does not manipulate any non-immutable fields, * so does not need to be synchronized. * * @return the factory name. */ public String getFactoryName() { String name = m_componentMetadata.getAttribute("name"); if (name == null) { // No factory name, use the classname (mandatory attribute) name = m_componentMetadata.getAttribute("classname"); } return name; } /** * Computes required handlers. * This method does not manipulate any non-immutable fields, * so does not need to be synchronized. * This method checks the {@link ComponentFactory#HANDLER_AUTO_PRIMITIVE} * system property to add the listed handlers to the required handler set. * * @return the required handler list. */ public List<RequiredHandler> getRequiredHandlerList() { List<RequiredHandler> list = new ArrayList<RequiredHandler>(); Element[] elems = m_componentMetadata.getElements(); for (Element current : elems) { if (!"manipulation".equals(current.getName())) { // Remove the manipulation element RequiredHandler req = new RequiredHandler(current.getName(), current.getNameSpace()); if (!list.contains(req)) { list.add(req); } } } // Add architecture if architecture != 'false' String arch = m_componentMetadata.getAttribute("architecture"); if (arch == null || arch.equalsIgnoreCase("true")) { list.add(new RequiredHandler("architecture", null)); } // Determine if the component must be immediate. // A component becomes immediate if it doesn't provide a service, // and does not specified that the component is not immediate. if (m_componentMetadata.getElements("provides") == null) { String imm = m_componentMetadata.getAttribute("immediate"); if (imm == null) { // immediate not specified, set the immediate attribute to true getLogger().log( Logger.INFO, "The component type " + getFactoryName() + " becomes immediate"); m_componentMetadata.addAttribute(new Attribute("immediate", "true")); } } // Add lifecycle callback if immediate = true RequiredHandler reqCallback = new RequiredHandler("callback", null); String imm = m_componentMetadata.getAttribute("immediate"); if (!list.contains(reqCallback) && imm != null && imm.equalsIgnoreCase("true")) { list.add(reqCallback); } // Manage auto attached handler. String v = System.getProperty(HANDLER_AUTO_PRIMITIVE); if (v != null && v.length() != 0) { String[] hs = ParseUtils.split(v, ","); for (String h1 : hs) { String h = h1.trim(); String[] segments = ParseUtils.split(h, ":"); RequiredHandler rq = null; if (segments.length == 2) { // External handler rq = new RequiredHandler(segments[1], segments[0]); } else if (segments.length == 1) { // Core handler rq = new RequiredHandler(segments[1], null); } // Others case are ignored. if (rq != null) { // Check it's not already contained if (!list.contains(rq)) { list.add(rq); } } } } return list; } /** * This method is called when a new handler factory is detected. * Test if the factory can be used or not. * This method need to be synchronized as it accesses to the content * of required handlers. * * @param reference the new service reference. * @return <code>true</code> if the given factory reference matches with a required handler. * @see org.apache.felix.ipojo.util.TrackerCustomizer#addingService(org.osgi.framework.ServiceReference) */ public synchronized boolean addingService(ServiceReference reference) { for (int i = 0; i < m_requiredHandlers.size(); i++) { RequiredHandler req = (RequiredHandler) m_requiredHandlers.get(i); if (req.getReference() == null && match(req, reference)) { int oldP = req.getLevel(); req.setReference(reference); // If the priority has changed, sort the list. if (oldP != req.getLevel()) { // Manipulate the list. Collections.sort(m_requiredHandlers); } return true; } } return false; } /** * This method is called when a matching service has been added to the tracker, * we can no compute the factory state. This method is synchronized to avoid * concurrent calls to method modifying the factory state. * * @param reference the added service reference. * @see org.apache.felix.ipojo.util.TrackerCustomizer#addedService(org.osgi.framework.ServiceReference) */ public synchronized void addedService(ServiceReference reference) { if (m_state == INVALID) { computeFactoryState(); } } /** * This method is called when a used handler factory disappears. * This method is synchronized to avoid concurrent calls to method modifying * the factory state. * * @param reference the leaving service reference. * @param service the handler factory object. * @see org.apache.felix.ipojo.util.TrackerCustomizer#removedService(org.osgi.framework.ServiceReference, java.lang.Object) */ public synchronized void removedService(ServiceReference reference, Object service) { // Look for the implied reference and invalid the handler identifier for (Object m_requiredHandler : m_requiredHandlers) { RequiredHandler req = (RequiredHandler) m_requiredHandler; if (reference.equals(req.getReference())) { req.unRef(); // This method will unget the service. computeFactoryState(); return; // The factory can be used only once. } } } /** * This method is called when a used handler factory is modified. * However, handler factory modification is not possible, so this method * is never called. * * @param reference the service reference * @param service the Factory object (if already get) * @see org.apache.felix.ipojo.util.TrackerCustomizer#modifiedService(org.osgi.framework.ServiceReference, java.lang.Object) */ public void modifiedService(ServiceReference reference, Object service) { // Noting to do } /** * Returns manipulation metadata of this component type. * * @return manipulation metadata of this component type. */ public PojoMetadata getPojoMetadata() { return m_manipulation; } /** * Gets the version of the component type. * * @return the version of <code>null</code> if not set. * @see org.apache.felix.ipojo.Factory#getVersion() */ public String getVersion() { return m_version; } public ClassLoader getBundleClassLoader() { return m_classLoader; } }