/* * 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.configuration; import org.apache.felix.ipojo.*; 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.providedservice.ProvidedServiceHandler; 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.MethodMetadata; import org.apache.felix.ipojo.parser.PojoMetadata; import org.apache.felix.ipojo.util.Callback; import org.apache.felix.ipojo.util.Log; import org.apache.felix.ipojo.util.Property; import org.apache.felix.ipojo.util.SecurityHelper; import org.osgi.framework.Constants; import org.osgi.framework.ServiceRegistration; import org.osgi.service.cm.ManagedService; import java.util.*; /** * Handler managing the Configuration Admin. * * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a> */ public class ConfigurationHandler extends PrimitiveHandler implements ManagedService { public static final String MANAGED_SERVICE_PID = "managed.service.pid"; /** * List of the configurable fields. */ private List<Property> m_configurableProperties = new ArrayList<Property>(1); /** * ProvidedServiceHandler of the component. It is useful to propagate * properties to service registrations. */ private ProvidedServiceHandler m_providedServiceHandler; /** * Properties propagated during the last instance "update". */ private Dictionary m_propagatedFromInstance = new Properties(); /** * Properties to propagate. */ private Dictionary<String, Object> m_toPropagate = new Hashtable<String, Object>(); /** * Properties propagated from the configuration admin. */ private Dictionary m_propagatedFromCA; /** * Check if the instance was already reconfigured by the configuration admin. */ private boolean m_configurationAlreadyPushed; /** * should the component propagate configuration ? */ private boolean m_mustPropagate; /** * Service Registration to publish the service registration. */ private ServiceRegistration m_sr; /** * Managed Service PID. * This PID must be different from the instance name if the instance was created * with the Configuration Admin. */ private String m_managedServicePID; /** * the handler description. */ private ConfigurationHandlerDescription m_description; /** * Updated method. * This method is called when a reconfiguration is completed. */ private Callback m_updated; /** * The configuration listeners. */ private final Set<ConfigurationListener> m_listeners = new LinkedHashSet<ConfigurationListener>(); /** * The last configuration sent to listeners. */ private Map<String, Object> m_lastConfiguration; /** * 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[] confs = metadata.getElements("Properties", ""); if (confs == null) { return; } Element[] configurables = confs[0].getElements("Property"); for (int i = 0; configurables != null && i < configurables.length; i++) { String fieldName = configurables[i].getAttribute("field"); String methodName = configurables[i].getAttribute("method"); String paramIndex = configurables[i].getAttribute("constructor-parameter"); if (fieldName == null && methodName == null && paramIndex == null) { throw new ConfigurationException("Malformed property : The property needs to contain" + " at least a field, a method or a constructor-parameter"); } String name = configurables[i].getAttribute("name"); if (name == null) { if (fieldName == null && methodName != null) { name = methodName; } else if (fieldName == null && paramIndex != null) { // Extract the name from the arguments. MethodMetadata[] constructors = getFactory().getPojoMetadata().getConstructors(); if (constructors.length != 1) { throw new ConfigurationException("Cannot infer the property name injected in the constructor " + "parameter #" + paramIndex + " - add the `name` attribute"); } else { int idx = Integer.valueOf(paramIndex); if (constructors[0].getMethodArgumentNames().length > idx) { name = constructors[0].getMethodArgumentNames()[idx]; } else { throw new ConfigurationException("Cannot infer the property name injected in the constructor " + "parameter #" + paramIndex + " - not enough argument in the constructor :" + constructors[0].getArguments()); } } } else { name = fieldName; } configurables[i].addAttribute(new Attribute("name", name)); // Add the type to avoid configure checking } String value = configurables[i].getAttribute("value"); // Detect the type of the property PojoMetadata manipulation = getFactory().getPojoMetadata(); String type = null; if (methodName != null) { MethodMetadata[] method = manipulation.getMethods(methodName); if (method.length == 0) { type = configurables[i].getAttribute("type"); if (type == null) { throw new ConfigurationException("Malformed property : The type of the property cannot be discovered, add a 'type' attribute"); } } else { if (method[0].getMethodArguments().length != 1) { throw new ConfigurationException("Malformed property : The method " + methodName + " does not have one argument"); } type = method[0].getMethodArguments()[0]; configurables[i].addAttribute(new Attribute("type", type)); // Add the type to avoid configure checking } } else if (fieldName != null) { FieldMetadata field = manipulation.getField(fieldName); if (field == null) { throw new ConfigurationException("Malformed property : The field " + fieldName + " does not exist in the implementation class"); } type = field.getFieldType(); configurables[i].addAttribute(new Attribute("type", type)); // Add the type to avoid configure checking } else if (paramIndex != null) { int index = Integer.parseInt(paramIndex); type = configurables[i].getAttribute("type"); MethodMetadata[] cts = manipulation.getConstructors(); // If we don't have a type, try to get the first constructor and get the type of the parameter // we the index 'index'. if (type == null && cts.length > 0 && cts[0].getMethodArguments().length > index) { type = cts[0].getMethodArguments()[index]; } else if (type == null) { // Applied only if type was not determined. throw new ConfigurationException("Cannot determine the type of the property " + index + ", please use the type attribute"); } configurables[i].addAttribute(new Attribute("type", type)); } // Is the property set to immutable boolean immutable = false; String imm = configurables[i].getAttribute("immutable"); immutable = imm != null && imm.equalsIgnoreCase("true"); boolean mandatory = false; String man = configurables[i].getAttribute("mandatory"); mandatory = man != null && man.equalsIgnoreCase("true"); PropertyDescription pd; if (value == null) { pd = new PropertyDescription(name, type, null, false); // Cannot be immutable if we have no value. } else { pd = new PropertyDescription(name, type, value, immutable); } if (mandatory) { pd.setMandatory(); } desc.addProperty(pd); } } /** * Configures the handler. * Access to field does not require synchronization as this method is executed * before any thread access to this object. * * @param metadata the metadata of the component * @param configuration the instance configuration * @throws ConfigurationException one property metadata is not correct * @see org.apache.felix.ipojo.Handler#configure(org.apache.felix.ipojo.metadata.Element, java.util.Dictionary) */ public void configure(Element metadata, Dictionary configuration) throws ConfigurationException { // Build the map Element[] confs = metadata.getElements("Properties", ""); Element[] configurables = confs[0].getElements("Property"); // Check if the component is dynamically configurable // Propagation enabled by default. m_mustPropagate = true; // We must create a copy as the Config Admin dictionary has some limitations m_toPropagate = new Hashtable<String, Object>(); if (configuration != null) { Enumeration keys = configuration.keys(); while (keys.hasMoreElements()) { String key = (String) keys.nextElement(); // To conform with 'Property Propagation 104.4.4 (Config Admin spec)', // we don't propagate properties starting with . if (!excluded(key)) { m_toPropagate.put(key, configuration.get(key)); } } } String propa = confs[0].getAttribute("propagation"); if (propa != null && propa.equalsIgnoreCase("false")) { m_mustPropagate = false; m_toPropagate = null; } // Check if the component support ConfigurationADmin reconfiguration m_managedServicePID = confs[0].getAttribute("pid"); // Look inside the component type description String instanceMSPID = (String) configuration.get(MANAGED_SERVICE_PID); // Look inside the instance configuration. if (instanceMSPID != null) { m_managedServicePID = instanceMSPID; } // updated method String upd = confs[0].getAttribute("updated"); if (upd != null) { MethodMetadata method = getPojoMetadata().getMethod(upd); if (method == null) { throw new ConfigurationException("The updated method is not found in the class " + getInstanceManager().getClassName()); } else if (method.getMethodArguments().length == 0) { m_updated = new Callback(upd, new Class[0], false, getInstanceManager()); } else if (method.getMethodArguments().length == 1 && method.getMethodArguments()[0].equals(Dictionary.class.getName())) { m_updated = new Callback(upd, new Class[]{Dictionary.class}, false, getInstanceManager()); } else { throw new ConfigurationException("The updated method is found in the class " + getInstanceManager().getClassName() + " must have either no argument or a Dictionary"); } } for (int i = 0; configurables != null && i < configurables.length; i++) { String fieldName = configurables[i].getAttribute("field"); String methodName = configurables[i].getAttribute("method"); String paramIndex = configurables[i].getAttribute("constructor-parameter"); int index = -1; String name = configurables[i].getAttribute("name"); // The initialize method has fixed the property name. String value = configurables[i].getAttribute("value"); String type = configurables[i].getAttribute("type"); // The initialize method has fixed the property name. Property prop; if (paramIndex == null) { prop = new Property(name, fieldName, methodName, value, type, getInstanceManager(), this); } else { index = Integer.parseInt(paramIndex); prop = new Property(name, fieldName, methodName, index, value, type, getInstanceManager(), this); } addProperty(prop); // Check if the instance configuration contains value for the current property : if (configuration.get(name) == null) { if (fieldName != null && configuration.get(fieldName) != null) { prop.setValue(configuration.get(fieldName)); } } else { prop.setValue(configuration.get(name)); } if (fieldName != null) { FieldMetadata field = new FieldMetadata(fieldName, type); getInstanceManager().register(field, prop); } if (index != -1) { getInstanceManager().register(index, prop); } } m_description = new ConfigurationHandlerDescription(this, m_configurableProperties, m_managedServicePID); } /** * Stop method. * This method is synchronized to avoid the configuration admin pushing a configuration during the un-registration. * Do nothing. * * @see org.apache.felix.ipojo.Handler#stop() */ public synchronized void stop() { if (m_sr != null) { m_sr.unregister(); m_sr = null; } m_lastConfiguration = Collections.emptyMap(); } /** * Start method. * This method is synchronized to avoid the config admin pushing a configuration before ending the method. * Propagate properties if the propagation is activated. * * @see org.apache.felix.ipojo.Handler#start() */ public synchronized void start() { // Get the provided service handler : m_providedServiceHandler = (ProvidedServiceHandler) getHandler(HandlerFactory.IPOJO_NAMESPACE + ":provides"); // Propagation if (m_mustPropagate) { for (Property prop : m_configurableProperties) { if (prop.getValue() != Property.NO_VALUE && prop.getValue() != null) { // No injected value, or null m_toPropagate.put(prop.getName(), prop.getValue()); } } // We cannot use the reconfigure method directly, as there are no real changes. Properties extra = reconfigureProperties(m_toPropagate); propagate(extra, m_propagatedFromInstance); m_propagatedFromInstance = extra; if (getInstanceManager().getPojoObjects() != null) { try { notifyUpdated(null); } catch (Throwable e) { error("Cannot call the updated method : " + e.getMessage(), e); } } } // Give initial values and reset the 'invoked' flag. for (Property prop : m_configurableProperties) { prop.reset(); // Clear the invoked flag. if (prop.hasField() && prop.getValue() != Property.NO_VALUE && prop.getValue() != null) { getInstanceManager().onSet(null, prop.getField(), prop.getValue()); } } if (m_managedServicePID != null && m_sr == null) { Hashtable<String, Object> props = new Hashtable<String, Object>(); props.put(Constants.SERVICE_PID, m_managedServicePID); props.put(Factory.INSTANCE_NAME_PROPERTY, getInstanceManager().getInstanceName()); props.put("factory.name", getInstanceManager().getFactory().getFactoryName()); // Security Check if (SecurityHelper.hasPermissionToRegisterService(ManagedService.class.getName(), getInstanceManager().getContext()) && SecurityHelper.canRegisterService (getInstanceManager().getContext())) { m_sr = getInstanceManager().getContext().registerService(ManagedService.class.getName(), this, props); } else { error("Cannot register the ManagedService - The bundle " + getInstanceManager().getContext().getBundle().getBundleId() + " does not have the permission to register the service"); } } } /** * Adds the given property metadata to the property metadata list. * * @param prop : property metadata to add */ protected void addProperty(Property prop) { m_configurableProperties.add(prop); } /** * Reconfigure the component instance. * Check if the new configuration modifies the current configuration. * Invokes the updated method if needed. * * @param configuration : the new configuration * @see org.apache.felix.ipojo.Handler#reconfigure(java.util.Dictionary) */ public void reconfigure(Dictionary configuration) { Map<String, Object> map = new LinkedHashMap<String, Object>(); boolean changed = false; synchronized (this) { info(getInstanceManager().getInstanceName() + " is reconfiguring the properties : " + configuration); // Is there any changes ? changed = detectConfigurationChanges(configuration); if (changed) { Properties extra = reconfigureProperties(configuration); propagate(extra, m_propagatedFromInstance); m_propagatedFromInstance = extra; if (getInstanceManager().getPojoObjects() != null) { try { notifyUpdated(null); } catch (Throwable e) { error("Cannot call the updated method : " + e.getMessage(), e); } } // Make a snapshot of the current configuration for (Property p : m_configurableProperties) { map.put(p.getName(), p.getValue()); } } } if (changed) { notifyListeners(map); } } private boolean detectConfigurationChanges(Dictionary configuration) { Enumeration keysEnumeration = configuration.keys(); while (keysEnumeration.hasMoreElements()) { String name = (String) keysEnumeration.nextElement(); Object value = configuration.get(name); // Some properties are skipped if (name.equals(Factory.INSTANCE_NAME_PROPERTY) || name.equals(Constants.SERVICE_PID) || name.equals(MANAGED_SERVICE_PID)) { continue; } // Do we have a property. Property p = getPropertyByName(name); if (p != null) { // Change detection based on the value. if (p.getValue() == null) { return true; } else if (! p.getValue().equals(value)) { return true; } } else { // Was it propagated ? if (m_propagatedFromCA != null) { Object v = m_propagatedFromCA.get(name); if (v == null || ! v.equals(value)) { return true; } } if (m_propagatedFromInstance != null) { Object v = m_propagatedFromInstance.get(name); if (v == null || ! v.equals(value)) { return true; } } } } // A propagated property may have been removed. if (m_propagatedFromCA != null) { Enumeration enumeration = m_propagatedFromCA.keys(); while (enumeration.hasMoreElements()) { String k = (String) enumeration.nextElement(); if (configuration.get(k) == null) { return true; } } } if (m_propagatedFromInstance != null) { Enumeration enumeration = m_propagatedFromInstance.keys(); while (enumeration.hasMoreElements()) { String k = (String) enumeration.nextElement(); if (configuration.get(k) == null) { return true; } } } return false; } private Property getPropertyByName(String name) { for (Property p : m_configurableProperties) { if (p.getName().equals(name)) { return p; } } return null; } /** * Reconfigured configuration properties and returns non matching properties. * When called, it must hold the monitor lock. * * @param configuration : new configuration * @return the properties that does not match with configuration properties */ private Properties reconfigureProperties(Dictionary configuration) { Properties toPropagate = new Properties(); Enumeration keysEnumeration = configuration.keys(); while (keysEnumeration.hasMoreElements()) { String name = (String) keysEnumeration.nextElement(); Object value = configuration.get(name); boolean found = false; // Check if the name is a configurable property for (Property prop : m_configurableProperties) { if (prop.getName().equals(name)) { Object v = reconfigureProperty(prop, value); found = true; if (m_mustPropagate && ! excluded(name)) { toPropagate.put(name, v); } break; // Exit the search loop } } if (!found && m_mustPropagate && ! excluded(name)) { toPropagate.put(name, value); } } // Every removed configurable property gets reset to its default value for (Property prop : m_configurableProperties) { if (configuration.get(prop.getName()) == null) { reconfigureProperty(prop, prop.getDefaultValue()); } } return toPropagate; } /** * Checks whether the property with this given name must not be propagated. * @param name the name of the property * @return {@code true} if the property must not be propagated */ private boolean excluded(String name) { return name.startsWith(".") || Factory.INSTANCE_NAME_PROPERTY.equals(name) || Factory.FACTORY_VERSION_PROPERTY.equals(name) || "factory.name".equals(name); } /** * Reconfigures the given property with the given value. * This methods handles {@link org.apache.felix.ipojo.InstanceManager#onSet(Object, String, Object)} * call and the callback invocation. * The reconfiguration occurs only if the value changes. * * @param prop the property object to reconfigure * @param value the new value. * @return the new property value */ public Object reconfigureProperty(Property prop, Object value) { if (prop.getValue() == null || !prop.getValue().equals(value)) { prop.setValue(value); if (prop.hasField()) { getInstanceManager().onSet(null, prop.getField(), prop.getValue()); // Notify other handler of the field value change. } if (prop.hasMethod()) { if (getInstanceManager().getPojoObjects() != null) { prop.invoke(null); // Call on all created pojo objects. } } } return prop.getValue(); } /** * Removes the old properties from the provided services and propagate new properties. * * @param newProps : new properties to propagate * @param oldProps : old properties to remove */ private void propagate(Dictionary newProps, Dictionary oldProps) { if (m_mustPropagate && m_providedServiceHandler != null) { if (oldProps != null) { m_providedServiceHandler.removeProperties(oldProps); } if (newProps != null) { // Remove the name, the pid and the managed service pid props newProps.remove(Factory.INSTANCE_NAME_PROPERTY); newProps.remove(MANAGED_SERVICE_PID); newProps.remove(Constants.SERVICE_PID); // Remove all properties starting with . (config admin specification) Enumeration<String> keys = newProps.keys(); List<String> propertiesStartingWithDot = new ArrayList<String>(); while (keys.hasMoreElements()) { String key = keys.nextElement(); if (key.startsWith(".")) { propertiesStartingWithDot.add(key); } } for (String k : propertiesStartingWithDot) { newProps.remove(k); } // Propagation of the properties to service registrations : m_providedServiceHandler.addProperties(newProps); } } } /** * Handler createInstance method. * This method is override to allow delayed callback invocation. * Invokes the updated method if needed. * * @param instance : the created object * @see org.apache.felix.ipojo.PrimitiveHandler#onCreation(Object) */ public void onCreation(Object instance) { Map<String, Object> map = new LinkedHashMap<String, Object>(); for (Property prop : m_configurableProperties) { if (prop.hasMethod()) { prop.invoke(instance); } // Fill the snapshot copy while calling callbacks map.put(prop.getName(), prop.getValue()); } try { notifyUpdated(instance); } catch (Throwable e) { error("Cannot call the updated method : " + e.getMessage(), e); } notifyListeners(map); } /** * Invokes the updated method. * This method build the dictionary containing all valued properties, * as well as properties propagated to the provided service handler ( * only if the propagation is enabled). * * @param instance the instance on which the callback must be called. * If <code>null</code> the callback is called on all the existing * object. */ private void notifyUpdated(Object instance) { if (m_updated == null) { return; } if (m_updated.getArguments().length == 0) { // We don't have to compute the properties, // we just call the callback. try { if (instance == null) { m_updated.call(new Object[0]); } else { m_updated.call(instance, new Object[0]); } } catch (Exception e) { error("Cannot call the updated method " + m_updated.getMethod() + " : " + e.getMessage()); } return; } // Else we must compute the properties. Properties props = new Properties(); for (Property property : m_configurableProperties) { String n = property.getName(); Object v = property.getValue(); if (v != Property.NO_VALUE) { props.put(n, v); } } // add propagated properties to the list if propagation is enabled if (m_mustPropagate) { // Start by properties from the configuration admin, if (m_propagatedFromCA != null) { Enumeration e = m_propagatedFromCA.keys(); while (e.hasMoreElements()) { String k = (String) e.nextElement(); if (!k.equals(Factory.INSTANCE_NAME_PROPERTY)) { props.put(k, m_propagatedFromCA.get(k)); } } } // Do also the one from the instance configuration if (m_propagatedFromInstance != null) { Enumeration e = m_propagatedFromInstance.keys(); while (e.hasMoreElements()) { String k = (String) e.nextElement(); if (!k.equals(Factory.INSTANCE_NAME_PROPERTY)) { // Skip instance.name props.put(k, m_propagatedFromInstance.get(k)); } } } } try { if (instance == null) { m_updated.call(new Object[]{props}); } else { m_updated.call(instance, new Object[]{props}); } } catch (Exception e) { error("Cannot call the updated method " + m_updated.getMethod() + " : " + e.getMessage()); } } /** * Managed Service method. * This method is called when the instance is reconfigured by the ConfigurationAdmin. * When called, it must hold the monitor lock. * * @param conf : pushed configuration. * @throws org.osgi.service.cm.ConfigurationException * the reconfiguration failed. * @see org.osgi.service.cm.ManagedService#updated(java.util.Dictionary) */ public void updated(Dictionary conf) throws org.osgi.service.cm.ConfigurationException { Map<String, Object> map = new LinkedHashMap<String, Object>(); synchronized (this) { if (conf == null && !m_configurationAlreadyPushed) { return; // First call } else if (conf != null) { // Configuration push Properties props = reconfigureProperties(conf); propagate(props, m_propagatedFromCA); m_propagatedFromCA = props; m_configurationAlreadyPushed = true; } else if (m_configurationAlreadyPushed) { // Configuration deletion propagate(null, m_propagatedFromCA); m_propagatedFromCA = null; m_configurationAlreadyPushed = false; } if (getInstanceManager().getPojoObjects() != null) { try { notifyUpdated(null); } catch (Throwable e) { error("Cannot call the updated method : " + e.getMessage(), e); } } // Make a snapshot of the current configuration for (Property p : m_configurableProperties) { map.put(p.getName(), p.getValue()); } } notifyListeners(map); } /** * Gets the configuration handler description. * * @return the configuration handler description. * @see org.apache.felix.ipojo.Handler#getDescription() */ public HandlerDescription getDescription() { return m_description; } /** * Add the given listener to the configuration handler's list of listeners. * * @param listener the {@code ConfigurationListener} object to be added * @throws NullPointerException if {@code listener} is {@code null} */ public void addListener(ConfigurationListener listener) { if (listener == null) { throw new NullPointerException("null listener"); } synchronized (m_listeners) { m_listeners.add(listener); } } /** * Remove the given listener from the configuration handler's list of listeners. * If the listeners is not registered, this method does nothing. * * @param listener the {@code ConfigurationListener} object to be removed * @throws NullPointerException if {@code listener} is {@code null} */ public void removeListener(ConfigurationListener listener) { if (listener == null) { throw new NullPointerException("The list of listener is null"); } synchronized (m_listeners) { // We definitely cannot rely on listener's equals method... // ...so we need to manually search for the listener, using ==. ConfigurationListener found = null; for (ConfigurationListener l : m_listeners) { if (l == listener) { found = l; break; } } if (found != null) { m_listeners.remove(found); } } } /** * Notify all listeners that a reconfiguration has occurred. * * @param map the new configuration of the component instance. */ private void notifyListeners(Map<String, Object> map) { // Get a snapshot of the listeners // and check if we had a change in the map. List<ConfigurationListener> tmp; synchronized (m_listeners) { tmp = new ArrayList<ConfigurationListener>(m_listeners); if (map == null) { if (m_lastConfiguration == null) { // No change. return; } // Else trigger the change. } else { if (m_lastConfiguration != null && m_lastConfiguration.size() == map.size()) { // Must compare key by key boolean diff = false; for (String k : map.keySet()) { if (! map.get(k).equals(m_lastConfiguration.get(k))) { // Difference found, break; diff = true; break; } } if (! diff) { // no difference found, skip notification return; } } // Else difference found, triggers the change } if (map == null) { m_lastConfiguration = Collections.emptyMap(); } else { m_lastConfiguration = Collections.unmodifiableMap(map); } } if (! tmp.isEmpty()) { getLogger().log(Log.DEBUG, String.format( "[%s] Notifying configuration listener: %s", getInstanceManager().getInstanceName(), tmp)); } // Protect the map. // Do notify, outside any lock for (ConfigurationListener l : tmp) { try { l.configurationChanged(getInstanceManager(), m_lastConfiguration); } catch (Throwable e) { // Failure inside a listener: put a warning on the logger, and continue warn(String.format( "[%s] A ConfigurationListener has failed: %s", getInstanceManager().getInstanceName(), e.getMessage()) , e); } } } @Override public void stateChanged(int state) { if (state == ComponentInstance.DISPOSED) { // Clean up the list of listeners synchronized (m_listeners) { m_listeners.clear(); } } super.stateChanged(state); } }