/* | |
* 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 static java.lang.String.format; | |
import org.apache.felix.ipojo.architecture.ComponentTypeDescription; | |
import org.apache.felix.ipojo.architecture.PropertyDescription; | |
import org.apache.felix.ipojo.extender.internal.Extender; | |
import org.apache.felix.ipojo.metadata.Element; | |
import org.apache.felix.ipojo.util.Log; | |
import org.apache.felix.ipojo.util.Logger; | |
import org.apache.felix.ipojo.util.SecurityHelper; | |
import org.osgi.framework.BundleContext; | |
import org.osgi.framework.Constants; | |
import org.osgi.framework.ServiceReference; | |
import org.osgi.framework.ServiceRegistration; | |
import org.osgi.service.cm.ManagedServiceFactory; | |
import java.util.*; | |
import java.util.concurrent.ConcurrentHashMap; | |
import java.util.concurrent.atomic.AtomicLong; | |
/** | |
* This class defines common mechanisms of iPOJO component factories | |
* (i.e. component type). | |
* | |
* The factory is also tracking Factory configuration from the configuration admin to created / delete and update | |
* instances from this factory. | |
* | |
* @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a> | |
*/ | |
public abstract class IPojoFactory implements Factory { | |
/* | |
* TODO there is potentially an issue when calling FactoryStateListener callbacks with the lock | |
* It should be called by a separate thread dispatching events to listeners. | |
*/ | |
/** | |
* The list of the managed instance name. | |
* This list is shared by all factories and is used to assert name uniqueness. | |
*/ | |
protected static final List<String> INSTANCE_NAME = Collections.synchronizedList(new ArrayList<String>()); | |
/** | |
* The component type description exposed by the {@link Factory} service. | |
*/ | |
protected ComponentTypeDescription m_componentDesc; | |
/** | |
* The list of the managed instance managers. | |
* The key of this map is the name (i.e. instance names) of the created instance | |
*/ | |
protected final Map<String, ComponentInstance> m_componentInstances = new ConcurrentHashMap<String, ComponentInstance>(); | |
/** | |
* The component type metadata. | |
*/ | |
protected final Element m_componentMetadata; | |
/** | |
* The bundle context reference. | |
*/ | |
protected final BundleContext m_context; | |
/** | |
* The factory name. | |
* Could be the component class name if the factory name is not set. | |
* Immutable once set. | |
*/ | |
protected String m_factoryName; | |
/** | |
* The list of required handlers. | |
*/ | |
protected final List<RequiredHandler> m_requiredHandlers = new ArrayList<RequiredHandler>(); | |
/** | |
* The list of factory state listeners. | |
* @see FactoryStateListener | |
*/ | |
protected List<FactoryStateListener> m_listeners = new ArrayList<FactoryStateListener>(1); | |
/** | |
* The logger for the factory. | |
*/ | |
protected final Logger m_logger; | |
/** | |
* Is the factory public (exposed as services). | |
*/ | |
protected final boolean m_isPublic; | |
/** | |
* The version of the component type. | |
*/ | |
protected final String m_version; | |
/** | |
* The service registration of this factory (Factory & ManagedServiceFactory). | |
* @see ManagedServiceFactory | |
* @see Factory | |
*/ | |
protected ServiceRegistration m_sr; | |
/** | |
* The factory state. | |
* Can be: | |
* <li>{@link Factory#INVALID}</li> | |
* <li>{@link Factory#VALID}</li> | |
* The factory is invalid at the beginning. | |
* A factory becomes valid if every required handlers | |
* are available (i.e. can be created). | |
*/ | |
protected int m_state = Factory.INVALID; | |
/** | |
* The flag indicating if this factory has already a | |
* computed description or not. | |
*/ | |
private boolean m_described; | |
/** | |
* Generates a unique instance name if not provided by the configuration. | |
*/ | |
private final NameGenerator m_generator = new RetryNameGenerator(new DefaultNameGenerator()); | |
/** | |
* Creates an iPOJO Factory. | |
* At the end of this method, the required set of handler is computed. | |
* But the result is computed by a sub-class. | |
* @param context the bundle context of the bundle containing the factory. | |
* @param metadata the description of the component type. | |
* @throws ConfigurationException if the element describing the factory is malformed. | |
*/ | |
public IPojoFactory(BundleContext context, Element metadata) throws ConfigurationException { | |
m_context = context; | |
m_componentMetadata = metadata; | |
m_factoryName = getFactoryName(); | |
String fac = metadata.getAttribute("public"); | |
m_isPublic = fac == null || !fac.equalsIgnoreCase("false"); | |
m_logger = new Logger(m_context, m_factoryName); | |
// Compute the component type version. | |
String version = metadata.getAttribute("version"); | |
if ("bundle".equalsIgnoreCase(version)) { // Handle the "bundle" constant: use the bundle version. | |
// The cast is necessary in KF. | |
m_version = (String) m_context.getBundle().getHeaders().get(Constants.BUNDLE_VERSION); | |
} else { | |
m_version = version; | |
} | |
m_requiredHandlers.addAll(getRequiredHandlerList()); // Call sub-class to get the list of required handlers. | |
m_logger.log(Logger.INFO, "New factory created : " + m_factoryName); | |
} | |
/** | |
* Gets the component type description. | |
* @return the component type description | |
*/ | |
public ComponentTypeDescription getComponentTypeDescription() { | |
return new ComponentTypeDescription(this); | |
} | |
/** | |
* Adds a factory listener. | |
* @param listener the factory listener to add. | |
* @see org.apache.felix.ipojo.Factory#addFactoryStateListener(org.apache.felix.ipojo.FactoryStateListener) | |
*/ | |
public void addFactoryStateListener(FactoryStateListener listener) { | |
synchronized (this) { | |
m_listeners.add(listener); | |
} | |
} | |
/** | |
* Gets the logger used by instances created by the current factory. | |
* @return the factory logger. | |
*/ | |
public Logger getLogger() { | |
return m_logger; | |
} | |
/** | |
* Computes the factory name. | |
* Each sub-type must override this method. | |
* @return the factory name. | |
*/ | |
public abstract String getFactoryName(); | |
/** | |
* Computes the required handler list. | |
* Each sub-type must override this method. | |
* @return the required handler list | |
* @throws ConfigurationException when the list of handler cannot be computed. | |
*/ | |
public abstract List<RequiredHandler> getRequiredHandlerList() throws ConfigurationException; | |
/** | |
* Creates an instance. | |
* This method is called with the monitor lock. | |
* @param config the instance configuration | |
* @param context the iPOJO context to use | |
* @param handlers the handler array to use | |
* @return the new component instance. | |
* @throws ConfigurationException if the instance creation failed during the configuration process. | |
*/ | |
public abstract ComponentInstance createInstance(Dictionary config, IPojoContext context, HandlerManager[] handlers) | |
throws ConfigurationException; | |
/** | |
* Creates an instance. | |
* This method creates the instance in the global context. | |
* @param configuration the configuration of the created instance. | |
* @return the created component instance. | |
* @throws UnacceptableConfiguration if the given configuration is not consistent with the component type of this factory. | |
* @throws MissingHandlerException if an handler is unavailable when the instance is created. | |
* @throws org.apache.felix.ipojo.ConfigurationException if the instance or type configuration are not correct. | |
* @see org.apache.felix.ipojo.Factory#createComponentInstance(java.util.Dictionary) | |
*/ | |
public ComponentInstance createComponentInstance(Dictionary configuration) throws UnacceptableConfiguration, MissingHandlerException, | |
ConfigurationException { | |
return createComponentInstance(configuration, null); | |
} | |
/** | |
* Creates an instance in the specified service context. | |
* This method is synchronized to assert the validity of the factory during the creation. | |
* Callbacks to sub-class and created instances need to be aware that they are holding the monitor lock. | |
* This method call the override {@link IPojoFactory#createInstance(Dictionary, IPojoContext, HandlerManager[])} | |
* method. | |
* @param configuration the configuration of the created instance. | |
* @param serviceContext the service context to push for this instance. | |
* @return the created component instance. | |
* @throws UnacceptableConfiguration if the given configuration is not consistent with the component type of this factory. | |
* @throws MissingHandlerException if an handler is unavailable when creating the instance. | |
* @throws org.apache.felix.ipojo.ConfigurationException if the instance configuration failed. | |
* @see org.apache.felix.ipojo.Factory#createComponentInstance(java.util.Dictionary) | |
*/ | |
public synchronized ComponentInstance createComponentInstance(Dictionary configuration, ServiceContext serviceContext) throws UnacceptableConfiguration, // NOPMD | |
MissingHandlerException, ConfigurationException { | |
if (configuration == null) { | |
configuration = new Properties(); | |
} | |
IPojoContext context; | |
if (serviceContext == null) { | |
context = new IPojoContext(m_context); | |
} else { | |
context = new IPojoContext(m_context, serviceContext); | |
} | |
try { | |
checkAcceptability(configuration); | |
} catch (UnacceptableConfiguration e) { | |
m_logger.log(Logger.ERROR, "The configuration is not acceptable : " + e.getMessage()); | |
throw new UnacceptableConfiguration("The configuration " | |
+ configuration + " is not acceptable for " + m_factoryName | |
, e); | |
} | |
// Find name in the configuration | |
String name = findInstanceName(configuration); | |
// Execute name generation and verification in a synchronized block to ensure uniqueness | |
synchronized (INSTANCE_NAME) { | |
if (name != null) { | |
// Needs to ensure name uniqueness | |
if (INSTANCE_NAME.contains(name)) { | |
// name already in use, try to append factory's version | |
if (m_version != null) { | |
name = name + "-" + m_version; | |
if (INSTANCE_NAME.contains(name)) { | |
// It's still not unique | |
// We've done our best: throw an Exception | |
throw new UnacceptableConfiguration(format("%s : Instance name (suffixed with factory's version) \"%s\" is already used", getFactoryName(), name)); | |
} | |
} else { | |
// No version provided: we cannot do more | |
throw new UnacceptableConfiguration(format("%s : Instance name (provided by the instance) \"%s\" is already used", getFactoryName(), name)); | |
} | |
} | |
} else { | |
// Generated name is always unique, no verification required | |
name = m_generator.generate(this, INSTANCE_NAME); | |
} | |
// Reserve name | |
INSTANCE_NAME.add(name); | |
} | |
// Update name in configuration | |
configuration.put(Factory.INSTANCE_NAME_PROPERTY, name); | |
// Here we are sure to be valid until the end of the method. | |
HandlerManager[] handlers = new HandlerManager[m_requiredHandlers.size()]; | |
for (int i = 0; i < handlers.length; i++) { | |
handlers[i] = getHandler(m_requiredHandlers.get(i), serviceContext); | |
} | |
try { | |
ComponentInstance instance = createInstance(configuration, context, handlers); | |
m_componentInstances.put(name, instance); | |
m_logger.log(Logger.INFO, "Instance " + name + " from factory " + m_factoryName + " created"); | |
// Register the instance on the ConfigurationTracker to be updated if needed. | |
ConfigurationTracker.get().instanceCreated(instance); | |
return instance; | |
} catch (ConfigurationException e) { | |
INSTANCE_NAME.remove(name); | |
m_logger.log(Logger.ERROR, e.getMessage()); | |
throw new ConfigurationException(e.getMessage(), e, m_factoryName); | |
} | |
} | |
private String findInstanceName(final Dictionary configuration) { | |
String name; | |
if (configuration.get(Factory.INSTANCE_NAME_PROPERTY) == null && configuration.get("name") == null) { | |
// No name provided | |
name = null; | |
} else { | |
// Support both instance.name & name | |
name = (String) configuration.get(Factory.INSTANCE_NAME_PROPERTY); | |
if (name == null) { | |
name = (String) configuration.get("name"); | |
getLogger().log(Logger.WARNING, "The 'name' (" + name + ") attribute, used as the instance name, is deprecated, please use the 'instance.name' attribute"); | |
} | |
} | |
return name; | |
} | |
/** | |
* Gets the bundle context of the factory. | |
* @return the bundle context of the factory. | |
* @see org.apache.felix.ipojo.Factory#getBundleContext() | |
*/ | |
public BundleContext getBundleContext() { | |
return m_context; | |
} | |
/** | |
* Gets the factory class name. | |
* @return the factory class name. | |
* @see org.apache.felix.ipojo.Factory#getClassName() | |
*/ | |
public abstract String getClassName(); | |
/** | |
* Gets the component type description. | |
* @return the component type description object. <code>Null</code> if not already computed. | |
*/ | |
public ComponentTypeDescription getComponentDescription() { | |
return m_componentDesc; | |
} | |
/** | |
* Gets the component type description (Element-Attribute form). | |
* @return the component type description. | |
* @see org.apache.felix.ipojo.Factory#getDescription() | |
*/ | |
public Element getDescription() { | |
// Can be null, if not already computed. | |
if (m_componentDesc == null) { | |
return new Element("No description available for " + m_factoryName, ""); | |
} | |
return m_componentDesc.getDescription(); | |
} | |
/** | |
* Gets the component metadata. | |
* @return the component metadata | |
* @see org.apache.felix.ipojo.Factory#getComponentMetadata() | |
*/ | |
public Element getComponentMetadata() { | |
return m_componentMetadata; | |
} | |
/** | |
* Gets the list of instances created by the factory. The instances must be still alive. | |
* | |
* @return the list of created (and living) instances | |
* @since 1.11.0 | |
*/ | |
public List<ComponentInstance> getInstances() { | |
// m_componentInstances is a concurrent hashmap, we can create retrieve values directly. | |
return new ArrayList<ComponentInstance>(m_componentInstances.values()); | |
} | |
/** | |
* Gets the list of the names of the instances created by the factory. The instances must be still alive. | |
* | |
* @return the list of the names of created (and living) instances | |
* @since 1.11.0 | |
*/ | |
public List<String> getInstancesNames() { | |
// m_componentInstances is a concurrent hashmap, we can create retrieve values directly. | |
return new ArrayList<String>(m_componentInstances.keySet()); | |
} | |
/** | |
* Computes the list of missing handlers. | |
* @return the list of missing handlers. | |
* @see org.apache.felix.ipojo.Factory#getMissingHandlers() | |
*/ | |
public List<String> getMissingHandlers() { | |
List<String> list = new ArrayList<String>(); | |
for (RequiredHandler req : m_requiredHandlers) { | |
if (req.getReference() == null) { | |
list.add(req.getFullName()); | |
} | |
} | |
return list; | |
} | |
/** | |
* Gets the factory name. | |
* This name is immutable once set. | |
* @return the factory name. | |
* @see org.apache.felix.ipojo.Factory#getName() | |
*/ | |
public String getName() { | |
return m_factoryName; | |
} | |
/** | |
* Gets the list of required handlers. | |
* The required handler list cannot change. | |
* @return the list of required handlers. | |
* @see org.apache.felix.ipojo.Factory#getRequiredHandlers() | |
*/ | |
public List<String> getRequiredHandlers() { | |
List<String> list = new ArrayList<String>(); | |
for (RequiredHandler req : m_requiredHandlers) { | |
list.add(req.getFullName()); | |
} | |
return list; | |
} | |
/** | |
* Gets the actual factory state. | |
* Must be synchronized as this state is dependent of handler availability. | |
* @return the actual factory state. | |
* @see org.apache.felix.ipojo.Factory#getState() | |
*/ | |
public synchronized int getState() { | |
return m_state; | |
} | |
/** | |
* Gets a component instance created by the current factory. | |
* @param name the instance name | |
* @return the component instance, {@literal null} if not found | |
*/ | |
public ComponentInstance getInstanceByName(String name) { | |
return m_componentInstances.get(name); | |
} | |
/** | |
* Checks if the configuration is acceptable. | |
* @param conf the configuration to test. | |
* @return <code>true</code> if the configuration is acceptable. | |
* @see org.apache.felix.ipojo.Factory#isAcceptable(java.util.Dictionary) | |
*/ | |
public boolean isAcceptable(Dictionary conf) { | |
try { | |
checkAcceptability(conf); | |
} catch (MissingHandlerException e) { | |
return false; | |
} catch (UnacceptableConfiguration e) { | |
return false; | |
} | |
return true; | |
} | |
/** | |
* Checks if the configuration is acceptable. | |
* This method checks the following assertions: | |
* <li>All handlers can be creates</li> | |
* <li>The configuration does not override immutable properties</li> | |
* <li>The configuration contains a value for every unvalued property</li> | |
* @param conf the configuration to test. | |
* @throws UnacceptableConfiguration if the configuration is unacceptable. | |
* @throws MissingHandlerException if an handler is missing. | |
*/ | |
public void checkAcceptability(Dictionary<String, ?> conf) throws UnacceptableConfiguration, | |
MissingHandlerException { | |
PropertyDescription[] props; | |
synchronized (this) { | |
if (m_state == Factory.INVALID) { | |
throw new MissingHandlerException(getMissingHandlers()); | |
} | |
props = m_componentDesc.getProperties(); // Stack confinement. | |
// The property list is up to date, as the factory is valid. | |
} | |
// Check that the configuration does not override immutable properties. | |
for (PropertyDescription prop : props) { | |
// Is the property immutable | |
if (prop.isImmutable() && conf.get(prop.getName()) != null) { | |
throw new UnacceptableConfiguration("The property " + prop + " cannot be overridden : immutable " + | |
"property"); // The instance configuration tries to override an immutable property. | |
} | |
// Is the property required ? | |
if (prop.isMandatory() && prop.getValue() == null && conf.get(prop.getName()) == null) { | |
throw new UnacceptableConfiguration("The mandatory property " + prop.getName() + " is missing"); // The property must be set. | |
} | |
} | |
} | |
/** | |
* Reconfigures an existing instance. | |
* The acceptability of the configuration is checked before the reconfiguration. Moreover, | |
* the configuration must contain the 'instance.name' property specifying the instance | |
* to reconfigure. | |
* This method is synchronized to assert the validity of the factory during the reconfiguration. | |
* @param properties the new configuration to push. | |
* @throws UnacceptableConfiguration if the new configuration is not consistent with the component type. | |
* @throws MissingHandlerException if the current factory is not valid. | |
* @see org.apache.felix.ipojo.Factory#reconfigure(java.util.Dictionary) | |
*/ | |
public synchronized void reconfigure(Dictionary properties) throws UnacceptableConfiguration, MissingHandlerException { | |
if (properties == null || (properties.get(Factory.INSTANCE_NAME_PROPERTY) == null && properties.get("name") == null)) { // Support both instance.name and name | |
throw new UnacceptableConfiguration("The configuration does not contains the \"instance.name\" property"); | |
} | |
String name = (String) properties.get(Factory.INSTANCE_NAME_PROPERTY); | |
if (name == null) { | |
name = (String) properties.get("name"); | |
} | |
ComponentInstance instance = m_componentInstances.get(name); | |
if (instance == null) { // The instance does not exists. | |
return; | |
} | |
checkAcceptability(properties); // Test if the configuration is acceptable | |
instance.reconfigure(properties); // re-configure the instance | |
} | |
/** | |
* Removes a factory listener. | |
* @param listener the factory listener to remove. | |
* @see org.apache.felix.ipojo.Factory#removeFactoryStateListener(org.apache.felix.ipojo.FactoryStateListener) | |
*/ | |
public void removeFactoryStateListener(FactoryStateListener listener) { | |
synchronized (this) { | |
m_listeners.remove(listener); | |
} | |
} | |
/** | |
* Stopping method. | |
* This method is call when the factory is stopping. | |
* This method is called when holding the lock on the factory. | |
*/ | |
public abstract void stopping(); | |
/** | |
* Stops all the instance managers. | |
* This method calls the {@link IPojoFactory#stopping()} method, | |
* notifies listeners, and disposes created instances. Moreover, | |
* if the factory is public, services are also unregistered. | |
* | |
*/ | |
public synchronized void stop() { | |
ComponentInstance[] instances; | |
if (m_sr != null) { | |
m_sr.unregister(); | |
m_sr = null; | |
} | |
ConfigurationTracker.get().unregisterFactory(this); | |
stopping(); // Method called when holding the lock. | |
int oldState = m_state; // Create a variable to store the old state. Using a variable is important as | |
// after the next instruction, the getState() method must return INVALID. | |
m_state = INVALID; // Set here to avoid to create instances during the stops. | |
Set<String> col = m_componentInstances.keySet(); | |
Iterator<String> it = col.iterator(); | |
instances = new ComponentInstance[col.size()]; // Stack confinement | |
int index = 0; | |
while (it.hasNext()) { | |
instances[index] = m_componentInstances.get(it.next()); | |
index++; | |
} | |
if (oldState == VALID) { // Check if the old state was valid. | |
for (FactoryStateListener listener : m_listeners) { | |
listener.stateChanged(this, INVALID); | |
} | |
} | |
// Dispose created instances. | |
for (ComponentInstance instance : instances) { | |
if (instance.getState() != ComponentInstance.DISPOSED) { | |
instance.dispose(); | |
} | |
} | |
// Release each handler | |
for (RequiredHandler req : m_requiredHandlers) { | |
req.unRef(); | |
} | |
m_described = false; | |
m_componentDesc = null; | |
m_componentInstances.clear(); | |
m_logger.log(Logger.INFO, "Factory " + m_factoryName + " stopped"); | |
} | |
/** | |
* Destroys the factory. | |
* The factory cannot be restarted. Only the {@link Extender} can call this method. | |
*/ | |
public synchronized void dispose() { | |
// Fast exit if already disposed | |
// The m_listeners field is ONLY set to null after dispose(), so if it's null, that | |
// means the factory has already be disposed. We can return safely. | |
if (m_listeners == null) { | |
return; | |
} | |
stop(); // Does not hold the lock. | |
m_requiredHandlers.clear(); | |
m_listeners = null; | |
} | |
/** | |
* Starting method. | |
* This method is called when the factory is starting. | |
* This method is <strong>not</strong> called when holding the lock on the factory. | |
*/ | |
public abstract void starting(); | |
/** | |
* Starts the factory. | |
* Tries to compute the component type description, | |
* calls the {@link IPojoFactory#starting()} method, | |
* and published services if the factory is public. | |
*/ | |
public void start() { | |
synchronized (this) { | |
if (m_described) { // Already started. | |
return; | |
} | |
} | |
m_componentDesc = getComponentTypeDescription(); | |
starting(); | |
synchronized (this) { | |
computeFactoryState(); | |
} | |
if (m_isPublic) { | |
// Exposition of the factory service | |
if (m_componentDesc == null) { | |
m_logger.log(Logger.ERROR, "Unexpected state, the description of " + m_factoryName + " is null"); | |
return; | |
} | |
BundleContext bc = SecurityHelper.selectContextToRegisterServices(m_componentDesc.getFactoryInterfacesToPublish(), | |
m_context, getIPOJOBundleContext()); | |
if (SecurityHelper.canRegisterService(bc)) { | |
m_sr = | |
bc.registerService(m_componentDesc.getFactoryInterfacesToPublish(), this, m_componentDesc | |
.getPropertiesToPublish()); | |
m_logger.log(Logger.INFO, "Factory " + m_factoryName + " started"); | |
} else { | |
m_logger.log(Log.ERROR, "Cannot register the Factory service with the bundle context of the bundle " | |
+ bc.getBundle().getBundleId() + " - the bundle is in the state " + bc.getBundle().getState() | |
); | |
} | |
} | |
} | |
/** | |
* For testing purpose <b>ONLY</b>. | |
* This method recomputes the required handler list. | |
*/ | |
public void restart() { | |
// Call sub-class to get the list of required handlers. | |
m_requiredHandlers.clear(); | |
try { | |
m_requiredHandlers.addAll(getRequiredHandlerList()); | |
} catch (ConfigurationException e) { | |
// Swallow the exception. | |
} | |
} | |
/** | |
* Gets the iPOJO Bundle Context. | |
* @return the iPOJO Bundle Context | |
*/ | |
protected final BundleContext getIPOJOBundleContext() { | |
return Extender.getIPOJOBundleContext(); | |
} | |
/** | |
* Creates or updates an instance. | |
* This method is used from the configuration tracker. | |
* @param name the name of the instance | |
* @param properties the new configuration of the instance | |
*/ | |
public void updated(String name, Dictionary properties) throws org.osgi.service.cm | |
.ConfigurationException { | |
ComponentInstance instance; | |
synchronized (this) { | |
instance = m_componentInstances.get(name); | |
} | |
if (instance == null) { | |
try { | |
properties.put(Factory.INSTANCE_NAME_PROPERTY, name); // Add the name in the configuration | |
// If an instance with this name was created before, this creation will failed. | |
createComponentInstance(properties); | |
} catch (UnacceptableConfiguration e) { | |
m_logger.log(Logger.ERROR, "The configuration is not acceptable : " + e.getMessage()); | |
throw new org.osgi.service.cm.ConfigurationException(properties.toString(), e.getMessage(), e); | |
} catch (MissingHandlerException e) { | |
m_logger.log(Logger.ERROR, "Handler not available : " + e.getMessage()); | |
throw new org.osgi.service.cm.ConfigurationException(properties.toString(), e.getMessage(), e); | |
} catch (ConfigurationException e) { | |
m_logger.log(Logger.ERROR, "The Component Type metadata are not correct : " + e.getMessage()); | |
throw new org.osgi.service.cm.ConfigurationException(properties.toString(), e.getMessage(), e); | |
} | |
} else { | |
try { | |
properties.put(Factory.INSTANCE_NAME_PROPERTY, name); // Add the name in the configuration | |
reconfigure(properties); // re-configure the component | |
} catch (UnacceptableConfiguration e) { | |
m_logger.log(Logger.ERROR, "The configuration is not acceptable : " + e.getMessage()); | |
throw new org.osgi.service.cm.ConfigurationException(properties.toString(), e.getMessage(), e); | |
} catch (MissingHandlerException e) { | |
m_logger.log(Logger.ERROR, "The factory is not valid, at least one handler is missing : " + e.getMessage()); | |
throw new org.osgi.service.cm.ConfigurationException(properties.toString(), e.getMessage(), e); | |
} | |
} | |
} | |
/** | |
* Deletes an instance. | |
* @param name the name of the instance to delete | |
*/ | |
public synchronized void deleted(String name) { | |
INSTANCE_NAME.remove(name); | |
ComponentInstance instance = m_componentInstances.remove(name); | |
if (instance != null) { | |
instance.dispose(); | |
} | |
} | |
/** | |
* Callback called by instance when disposed. | |
* @param instance the destroyed instance | |
*/ | |
public void disposed(ComponentInstance instance) { | |
String name = instance.getInstanceName(); | |
m_componentInstances.remove(name); | |
INSTANCE_NAME.remove(name); | |
} | |
/** | |
* Computes the component type description. | |
* To do this, it creates a 'ghost' instance of the handler | |
* and calls the {@link Handler#initializeComponentFactory(ComponentTypeDescription, Element)} | |
* method. The handler instance is then deleted. | |
* The factory must be valid when calling this method. | |
* This method is called with the lock. | |
*/ | |
protected void computeDescription() { | |
for (RequiredHandler req : m_requiredHandlers) { | |
if (getHandler(req, null) == null) { | |
m_logger.log(Logger.ERROR, "Cannot extract handler object from " + m_factoryName + " " + req | |
.getFullName()); | |
} else { | |
Handler handler = getHandler(req, null).getHandler(); | |
try { | |
handler.setFactory(this); | |
handler.initializeComponentFactory(m_componentDesc, m_componentMetadata); | |
((Pojo) handler).getComponentInstance().dispose(); | |
} catch (ConfigurationException e) { | |
((Pojo) handler).getComponentInstance().dispose(); | |
m_logger.log(Logger.ERROR, e.getMessage()); | |
stop(); | |
throw new IllegalStateException(e); | |
} | |
} | |
} | |
} | |
/** | |
* Computes factory state. | |
* The factory is valid if every required handler are available. | |
* If the factory becomes valid for the first time, the component | |
* type description is computed. | |
* This method is called when holding the lock on the current factory. | |
*/ | |
protected void computeFactoryState() { | |
boolean isValid = true; | |
for (RequiredHandler req : m_requiredHandlers) { | |
if (req.getReference() == null) { | |
isValid = false; | |
break; | |
} | |
} | |
if (isValid) { | |
if (m_state == INVALID) { | |
if (!m_described) { | |
computeDescription(); | |
m_described = true; | |
} | |
m_state = VALID; | |
if (m_sr != null) { | |
if (SecurityHelper.canUpdateService(m_sr)) { | |
m_sr.setProperties(m_componentDesc.getPropertiesToPublish()); | |
} | |
} | |
// Register the factory on the ConfigurationTracker | |
ConfigurationTracker.get().registerFactory(this); | |
for (FactoryStateListener listener : m_listeners) { | |
listener.stateChanged(this, VALID); | |
} | |
} | |
} else { | |
if (m_state == VALID) { | |
m_state = INVALID; | |
// Un-register the factory on the ConfigurationTracker | |
ConfigurationTracker.get().unregisterFactory(this); | |
// Notify listeners. | |
for (FactoryStateListener listener : m_listeners) { | |
listener.stateChanged(this, INVALID); | |
} | |
// Dispose created instances. | |
// We must create a copy to avoid concurrent exceptions | |
Set<? extends String> keys = new HashSet<String>(m_componentInstances.keySet()); | |
for (String key : keys) { | |
ComponentInstance instance = m_componentInstances.get(key); | |
if (instance.getState() != ComponentInstance.DISPOSED) { | |
instance.dispose(); | |
} | |
INSTANCE_NAME.remove(instance.getInstanceName()); | |
} | |
m_componentInstances.clear(); | |
if (SecurityHelper.canUpdateService(m_sr)) { | |
// No null check required as the security helper is checking this too. | |
m_sr.setProperties(m_componentDesc.getPropertiesToPublish()); | |
} | |
} | |
} | |
} | |
/** | |
* Checks if the given handler identifier and the service reference match. | |
* Does not need to be synchronized as the method does not use any fields. | |
* @param req the handler identifier. | |
* @param ref the service reference. | |
* @return <code>true</code> if the service reference can fulfill the handler requirement | |
*/ | |
protected boolean match(RequiredHandler req, ServiceReference<?> ref) { | |
String name = (String) ref.getProperty(Handler.HANDLER_NAME_PROPERTY); | |
String namespace = (String) ref.getProperty(Handler.HANDLER_NAMESPACE_PROPERTY); | |
if (HandlerFactory.IPOJO_NAMESPACE.equals(namespace)) { | |
return name.equalsIgnoreCase(req.getName()) && req.getNamespace() == null; | |
} | |
return name.equalsIgnoreCase(req.getName()) && namespace.equalsIgnoreCase(req.getNamespace()); | |
} | |
/** | |
* Returns the handler object for the given required handler. | |
* The handler is instantiated in the given service context. | |
* This method is called with the lock. | |
* @param req the handler to create. | |
* @param context the service context in which the handler is created (same as the instance context). | |
* @return the handler object. | |
*/ | |
protected HandlerManager getHandler(RequiredHandler req, ServiceContext context) { | |
try { | |
return (HandlerManager) req.getFactory().createComponentInstance(null, context); | |
} catch (MissingHandlerException e) { | |
m_logger.log(Logger.ERROR, "The creation of the handler " + req.getFullName() + " has failed: " + e.getMessage()); | |
stop(); | |
return null; | |
} catch (UnacceptableConfiguration e) { | |
m_logger.log(Logger.ERROR, "The creation of the handler " | |
+ req.getFullName() | |
+ " has failed (UnacceptableConfiguration): " | |
+ e.getMessage()); | |
stop(); | |
return null; | |
} catch (org.apache.felix.ipojo.ConfigurationException e) { | |
m_logger.log(Logger.ERROR, "The configuration of the handler " | |
+ req.getFullName() | |
+ " has failed (ConfigurationException): " | |
+ e.getMessage()); | |
stop(); | |
return null; | |
} | |
} | |
/** | |
* Structure storing required handlers. | |
* Access to this class must mostly be with the lock on the factory. | |
* (except to access final fields) | |
*/ | |
protected class RequiredHandler implements Comparable { | |
/** | |
* The factory to create this handler. | |
*/ | |
private HandlerFactory m_factory; | |
/** | |
* The handler name. | |
*/ | |
private final String m_name; | |
/** | |
* The handler start level. | |
*/ | |
private int m_level = Integer.MAX_VALUE; | |
/** | |
* The handler namespace. | |
*/ | |
private final String m_namespace; | |
/** | |
* The Service Reference of the handler factory. | |
*/ | |
private ServiceReference<? extends HandlerFactory> m_reference; | |
/** | |
* Crates a Required Handler. | |
* @param name the handler name. | |
* @param namespace the handler namespace. | |
*/ | |
public RequiredHandler(String name, String namespace) { | |
m_name = name; | |
m_namespace = namespace; | |
} | |
/** | |
* Equals method. | |
* Two handlers are equals if they have same name and namespace or they share the same service reference. | |
* @param object the object to compare to the current object. | |
* @return <code>true</code> if the two compared object are equals | |
* @see java.lang.Object#equals(java.lang.Object) | |
*/ | |
public boolean equals(Object object) { | |
if (object instanceof RequiredHandler) { | |
RequiredHandler req = (RequiredHandler) object; | |
if (m_namespace == null) { | |
return req.m_name.equalsIgnoreCase(m_name) && req.m_namespace == null; | |
} else { | |
return req.m_name.equalsIgnoreCase(m_name) && m_namespace.equalsIgnoreCase(req.m_namespace); | |
} | |
} else { | |
return false; | |
} | |
} | |
/** | |
* Hashcode method. | |
* This method delegates to the {@link Object#hashCode()}. | |
* @return the object hashcode. | |
* @see java.lang.Object#hashCode() | |
*/ | |
public int hashCode() { | |
return super.hashCode(); | |
} | |
/** | |
* Gets the factory object used for this handler. | |
* The object is get when used for the first time. | |
* This method is called with the lock avoiding concurrent modification and on a valid factory. | |
* @return the factory object. | |
*/ | |
public HandlerFactory getFactory() { | |
if (m_reference == null) { | |
return null; | |
} | |
if (m_factory == null) { | |
m_factory = m_context.getService(getReference()); | |
} | |
return m_factory; | |
} | |
/** | |
* Gets the handler qualified name (<code>namespace:name</code>). | |
* @return the handler full name | |
*/ | |
public String getFullName() { | |
if (m_namespace == null) { | |
return HandlerFactory.IPOJO_NAMESPACE + ":" + m_name; | |
} else { | |
return m_namespace + ":" + m_name; | |
} | |
} | |
public String getName() { | |
return m_name; | |
} | |
public String getNamespace() { | |
return m_namespace; | |
} | |
public ServiceReference<? extends HandlerFactory> getReference() { | |
return m_reference; | |
} | |
public int getLevel() { | |
return m_level; | |
} | |
/** | |
* Releases the reference of the used factory. | |
* This method is called with the lock on the current factory. | |
*/ | |
public void unRef() { | |
if (m_reference != null) { | |
m_factory = null; | |
m_reference = null; | |
} | |
} | |
/** | |
* Sets the service reference. If the new service reference is <code>null</code>, it ungets the used factory (if already get). | |
* This method is called with the lock on the current factory. | |
* @param ref the new service reference. | |
*/ | |
public void setReference(ServiceReference<? extends HandlerFactory> ref) { | |
m_reference = ref; | |
Integer level = (Integer) m_reference.getProperty(Handler.HANDLER_LEVEL_PROPERTY); | |
if (level != null) { | |
m_level = level; | |
} | |
} | |
/** | |
* Start level Comparison. | |
* This method is used to sort the handler array. | |
* This method is called with the lock. | |
* @param object the object on which compare. | |
* @return <code>-1</code>, <code>0</code>, <code>+1</code> according to the comparison of their start levels. | |
* @see java.lang.Comparable#compareTo(java.lang.Object) | |
*/ | |
public int compareTo(Object object) { | |
if (object instanceof RequiredHandler) { | |
RequiredHandler req = (RequiredHandler) object; | |
if (this.m_level == req.m_level) { | |
return 0; | |
} else if (this.m_level < req.m_level) { | |
return -1; | |
} else { | |
return +1; | |
} | |
} | |
return 0; | |
} | |
} | |
/** | |
* This generator implements the default naming strategy. | |
* The name is composed of the factory name suffixed with a unique number identifier (starting from 0). | |
*/ | |
public static class DefaultNameGenerator implements NameGenerator { | |
private AtomicLong m_nextId = new AtomicLong(); | |
public String generate(Factory factory, List<String> reserved) throws UnacceptableConfiguration { | |
StringBuilder sb = new StringBuilder(); | |
sb.append(factory.getName()); | |
if (factory.getVersion() != null) { | |
sb.append("/"); | |
sb.append(factory.getVersion()); | |
} | |
sb.append("-"); | |
sb.append(m_nextId.getAndIncrement()); | |
return sb.toString(); | |
} | |
} | |
/** | |
* This generator implements a retry naming strategy. | |
* It uses a delegate {@link org.apache.felix.ipojo.IPojoFactory.NameGenerator}, and call it in sequence until an unused value is found. | |
*/ | |
public static class RetryNameGenerator implements NameGenerator { | |
private final NameGenerator m_delegate; | |
/** | |
* Bound the loop to avoid Stack overflows | |
*/ | |
private long maximum = 8096; | |
public RetryNameGenerator(final NameGenerator delegate) { | |
m_delegate = delegate; | |
} | |
public void setMaximum(final long maximum) { | |
this.maximum = maximum; | |
} | |
public String generate(final Factory factory, final List<String> reserved) throws UnacceptableConfiguration { | |
// Loop until we obtain a unique value (or counter overflow) | |
long counter = 0; | |
while (counter < maximum) { | |
String generated = m_delegate.generate(factory, reserved); | |
counter++; | |
// Verify uniqueness | |
if (!reserved.contains(generated)) { | |
return generated; | |
} | |
} | |
// Should never happen (except is NameGenerator composition is broken: like a delegate that | |
// never change its return value) | |
throw new UnacceptableConfiguration(format("Cannot generate unique instance name for factory %s/%s from bundle %d", | |
factory.getName(), | |
factory.getVersion(), | |
factory.getBundleContext().getBundle().getBundleId())); | |
} | |
} | |
/** | |
* Generate a unique name for a component instance. | |
*/ | |
public static interface NameGenerator { | |
/** | |
* @return a unique name. | |
* @param factory Factory called to create the instance | |
* @param reserved List of reserved (already used) instance names. | |
* This has to be used tp ensure generated name is unique among all other instances. | |
*/ | |
String generate(Factory factory, List<String> reserved) throws UnacceptableConfiguration; | |
} | |
} |