blob: 97ca40a00c90de30c16c9e1e357d7e23e028a621 [file] [log] [blame]
/*
* 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) {
HandlerManager handlerManager = null;
try {
handlerManager = getHandler(req, null);
} catch (Exception e) {
m_logger.log(Logger.ERROR, "Cannot extract handler object from " + m_factoryName + " " + req
.getFullName());
return;
}
if (handlerManager == null) {
m_logger.log(Logger.ERROR, "Cannot extract handler object from " + m_factoryName + " " + req
.getFullName());
return;
}
Handler handler = handlerManager.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) throws MissingHandlerException,
UnacceptableConfiguration, ConfigurationException {
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();
throw e;
} catch (UnacceptableConfiguration e) {
m_logger.log(Logger.ERROR, "The creation of the handler "
+ req.getFullName()
+ " has failed (UnacceptableConfiguration): "
+ e.getMessage());
stop();
throw e;
} 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();
throw e;
}
}
/**
* 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;
}
}