/* * 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.scr.impl; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Dictionary; import java.util.Iterator; import java.util.List; import java.util.Map; import org.osgi.framework.BundleContext; import org.osgi.service.cm.Configuration; import org.osgi.service.component.ComponentConstants; import org.osgi.service.component.ComponentContext; import org.osgi.service.log.LogService; /** * The default ComponentManager. Objects of this class are responsible for managing * implementation object's lifecycle. */ class ImmediateComponentManager extends AbstractComponentManager { private static final Class COMPONENT_CONTEXT_CLASS = ComponentContext.class; private static final Class BUNDLE_CONTEXT_CLASS = BundleContext.class; private static final Class MAP_CLASS = Map.class; // this is an internal field made available only to the unit tests static final Class[][] ACTIVATE_PARAMETER_LIST = { { COMPONENT_CONTEXT_CLASS }, { BUNDLE_CONTEXT_CLASS }, { MAP_CLASS }, { COMPONENT_CONTEXT_CLASS, BUNDLE_CONTEXT_CLASS }, { COMPONENT_CONTEXT_CLASS, MAP_CLASS }, { BUNDLE_CONTEXT_CLASS, COMPONENT_CONTEXT_CLASS }, { BUNDLE_CONTEXT_CLASS, MAP_CLASS }, { MAP_CLASS, COMPONENT_CONTEXT_CLASS }, { MAP_CLASS, BUNDLE_CONTEXT_CLASS }, { COMPONENT_CONTEXT_CLASS, BUNDLE_CONTEXT_CLASS, MAP_CLASS }, { COMPONENT_CONTEXT_CLASS, MAP_CLASS, BUNDLE_CONTEXT_CLASS }, { BUNDLE_CONTEXT_CLASS, COMPONENT_CONTEXT_CLASS, MAP_CLASS }, { BUNDLE_CONTEXT_CLASS, MAP_CLASS, COMPONENT_CONTEXT_CLASS }, { MAP_CLASS, COMPONENT_CONTEXT_CLASS, BUNDLE_CONTEXT_CLASS }, { MAP_CLASS, BUNDLE_CONTEXT_CLASS, COMPONENT_CONTEXT_CLASS }, {} }; // The object that implements the service and that is bound to other services private Object m_implementationObject; // The context that will be passed to the implementationObject private ComponentContext m_componentContext; // the activate method private Method activateMethod = ReflectionHelper.SENTINEL; // the deactivate method private Method deactivateMethod = ReflectionHelper.SENTINEL; // optional properties provided in the ComponentFactory.newInstance method private Dictionary m_factoryProperties; // the component properties, also used as service properties private Dictionary m_properties; // the component properties from the Configuration Admin Service // this is null, if none exist or none are provided private Dictionary m_configurationProperties; /** * The constructor receives both the activator and the metadata * * @param activator * @param metadata */ ImmediateComponentManager( BundleComponentActivator activator, ComponentMetadata metadata, ComponentRegistry componentRegistry ) { super( activator, metadata, componentRegistry ); // only ask for configuration if not created by a Component Factory, in // which case the configuration is provided by the Component Factory if ( !getComponentMetadata().isFactory() ) { Configuration cfg = componentRegistry.getConfiguration( activator.getBundleContext(), getComponentMetadata().getName() ); if ( cfg != null ) { m_configurationProperties = cfg.getProperties(); } } } /** * Before doing real disposal, we also have to unregister the managed * service which was registered when the instance was created. */ public synchronized void dispose() { // really dispose off this manager instance disposeInternal(); } // 1. Load the component implementation class // 2. Create the component instance and component context // 3. Bind the target services // 4. Call the activate method, if present // if this method is overwritten, the deleteComponent method should // also be overwritten protected boolean createComponent() { ComponentContext tmpContext = new ComponentContextImpl( this ); Object tmpComponent = createImplementationObject( tmpContext ); // if something failed creating the component instance, return false if ( tmpComponent == null ) { return false; } // otherwise set the context and component instance and return true m_componentContext = tmpContext; m_implementationObject = tmpComponent; return true; } protected void deleteComponent() { disposeImplementationObject( m_implementationObject, m_componentContext ); m_implementationObject = null; m_componentContext = null; m_properties = null; } //********************************************************************************************************** /** * Get the object that is implementing this descriptor * * @return the object that implements the services */ public Object getInstance() { return m_implementationObject; } protected Object createImplementationObject( ComponentContext componentContext ) { Object implementationObject; // 1. Load the component implementation class // 2. Create the component instance and component context // If the component is not immediate, this is not done at this moment try { // 112.4.4 The class is retrieved with the loadClass method of the component's bundle Class c = getActivator().getBundleContext().getBundle().loadClass( getComponentMetadata().getImplementationClassName() ); // 112.4.4 The class must be public and have a public constructor without arguments so component instances // may be created by the SCR with the newInstance method on Class implementationObject = c.newInstance(); } catch ( Exception ex ) { // failed to instantiate, return null log( LogService.LOG_ERROR, "Error during instantiation of the implementation object", getComponentMetadata(), ex ); return null; } // 3. Bind the target services Iterator it = getDependencyManagers(); while ( it.hasNext() ) { // if a dependency turned unresolved since the validation check, // creating the instance fails here, so we deactivate and return // null. DependencyManager dm = ( DependencyManager ) it.next(); if ( !dm.open( implementationObject ) ) { log( LogService.LOG_ERROR, "Cannot create component instance due to failure to bind reference " + dm.getName(), getComponentMetadata(), null ); // make sure, we keep no bindings it = getDependencyManagers(); while ( it.hasNext() ) { dm = ( DependencyManager ) it.next(); dm.close(); } return null; } } // get the method if ( activateMethod == ReflectionHelper.SENTINEL ) { activateMethod = getMethod( implementationObject, getComponentMetadata().getActivate() ); } // 4. Call the activate method, if present if ( activateMethod != null && !invokeMethod( activateMethod, implementationObject, componentContext ) ) { // 112.5.8 If the activate method throws an exception, SCR must log an error message // containing the exception with the Log Service and activation fails it = getDependencyManagers(); while ( it.hasNext() ) { DependencyManager dm = ( DependencyManager ) it.next(); dm.close(); } implementationObject = null; } return implementationObject; } protected void disposeImplementationObject( Object implementationObject, ComponentContext componentContext ) { // get the method if ( deactivateMethod == ReflectionHelper.SENTINEL ) { deactivateMethod = getMethod( implementationObject, getComponentMetadata().getDeactivate() ); } // 1. Call the deactivate method, if present // don't care for the result, the error (acccording to 112.5.12 If the deactivate // method throws an exception, SCR must log an error message containing the // exception with the Log Service and continue) has already been logged invokeMethod( deactivateMethod, implementationObject, componentContext ); // 2. Unbind any bound services Iterator it = getDependencyManagers(); while ( it.hasNext() ) { DependencyManager dm = ( DependencyManager ) it.next(); dm.close(); } // 3. Release all references // nothing to do, we keep no references on per-Bundle services } /** * Returns the service object to be registered if the service element is * specified. *

* Extensions of this class may overwrite this method to return a * ServiceFactory to register in the case of a delayed or a service * factory component. */ protected Object getService() { return m_implementationObject; } protected void setFactoryProperties( Dictionary dictionary ) { m_factoryProperties = copyTo( null, dictionary ); } /** * Returns the (private copy) of the Component properties to be used * for the ComponentContext as well as eventual service registration. *

* Method implements the Component Properties provisioning as described * in 112.6, Component Properties. * * @return a private Hashtable of component properties */ public Dictionary getProperties() { if ( m_properties == null ) { // 1. the properties from the component descriptor Dictionary props = copyTo( null, getComponentMetadata().getProperties() ); // 2. add target properties of references // 112.6 Component Properties, target properties (p. 302) List depMetaData = getComponentMetadata().getDependencies(); for ( Iterator di = depMetaData.iterator(); di.hasNext(); ) { ReferenceMetadata rm = ( ReferenceMetadata ) di.next(); if ( rm.getTarget() != null ) { props.put( rm.getTargetPropertyName(), rm.getTarget() ); } } // 3. overlay with Configuration Admin properties copyTo( props, m_configurationProperties ); // 4. copy any component factory properties, not supported yet copyTo( props, m_factoryProperties ); // 5. set component.name and component.id props.put( ComponentConstants.COMPONENT_NAME, getComponentMetadata().getName() ); props.put( ComponentConstants.COMPONENT_ID, new Long( getId() ) ); m_properties = props; } return m_properties; } /** * Called by the Configuration Admin Service to update the component with * Configuration properties. *

* This causes the component to be reactivated with the new configuration * unless no configuration has ever been set on this component and the * configuration parameter is null. In this case * nothing is to be done. If a configuration has previously been set and * now the configuration is deleted, the configuration * parameter is null and the component has to be reactivated * with the default configuration. * * @param configuration The configuration properties for the component from * the Configuration Admin Service or null if there is * no configuration or if the configuration has just been deleted. */ public void reconfigure( Dictionary configuration ) { // nothing to do if there is no configuration (see FELIX-714) if ( configuration == null && m_configurationProperties == null ) { log( LogService.LOG_DEBUG, "No configuration provided (or deleted), nothing to do", getComponentMetadata(), null ); return; } // store the properties m_configurationProperties = configuration; // clear the current properties to force using the configuration data m_properties = null; // reactivate the component to ensure it is provided with the // configuration data if ( ( getState() & ( STATE_ACTIVE | STATE_FACTORY | STATE_REGISTERED ) ) != 0 ) { log( LogService.LOG_DEBUG, "Deactivating and Activating to reconfigure from configuration", getComponentMetadata(), null ); reactivate(); } } /** * Find the method with the given name in the class hierarchy of the * implementation object's class. This method looks for methods which have * one of the parameter lists of the {@link #ACTIVATE_PARAMETER_LIST} array. * * @param implementationObject The object whose class (and its super classes) * may provide the method * @param methodName Name of the method to look for * @return The named method or null if no such method is available. */ private Method getMethod( final Object implementationObject, final String methodName ) { try { return ReflectionHelper.getMethod( implementationObject.getClass(), methodName, ACTIVATE_PARAMETER_LIST ); } catch ( InvocationTargetException ite ) { // We can safely ignore this one log( LogService.LOG_WARNING, methodName + " cannot be found", getComponentMetadata(), ite .getTargetException() ); } catch ( NoSuchMethodException ex ) { // We can safely ignore this one log( LogService.LOG_DEBUG, methodName + " method is not implemented", getComponentMetadata(), null ); } return null; } /** * Invokes the given method on the implementationObject using * the componentContext as the base to create method argument * list. * * @param method The method to call. This method must already have been * made accessible by calling * Method.setAccessible(boolean). * @param implementationObject The object on which to call the method. * @param componentContext The ComponentContext used to * build the argument list * * @return true if the method should be considered invoked * successfully. false is returned if the method threw * an exception. * * @throws NullPointerException if any of the parameters is null. */ private boolean invokeMethod( final Method method, final Object implementationObject, final ComponentContext componentContext ) { final String methodName = method.getName(); try { // build argument list Class[] paramTypes = method.getParameterTypes(); Object[] param = new Object[paramTypes.length]; for ( int i = 0; i < param.length; i++ ) { if ( paramTypes[i] == COMPONENT_CONTEXT_CLASS ) { param[i] = componentContext; } else if ( paramTypes[i] == BUNDLE_CONTEXT_CLASS ) { param[i] = componentContext.getBundleContext(); } else if ( paramTypes[i] == MAP_CLASS ) { // note: getProperties() returns a Hashtable which is a Map param[i] = componentContext.getProperties(); } } method.invoke( implementationObject, new Object[] { componentContext } ); } catch ( IllegalAccessException ex ) { // Ignored, but should it be logged? log( LogService.LOG_DEBUG, methodName + " method cannot be called", getComponentMetadata(), null ); } catch ( InvocationTargetException ex ) { // 112.5.8 If the activate method throws an exception, SCR must log an error message // containing the exception with the Log Service and activation fails log( LogService.LOG_ERROR, "The " + methodName + " method has thrown an exception", getComponentMetadata(), ex.getCause() ); // method threw, so it was a failure return false; } // assume success (also if the method is not available or accessible) return true; } }