blob: 6d042f372156067ace31481c8d7c6430bc9694c8 [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.dm.runtime;
import java.lang.reflect.Method;
import java.util.AbstractSet;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.felix.dm.Component;
import org.apache.felix.dm.Dependency;
import org.apache.felix.dm.DependencyManager;
import org.osgi.framework.Bundle;
/**
* This class implements a <code>java.util.Set</code> which acts as a service factory.
* When a <code>Service</annotation> contains a <code>factory</code> attribute, this class is provided
* into the OSGi registry with a <code>dm.factory.name</code> service property. So, another factory component
* may be injected with this Set. And each time a Dictionary configuration is registered in the Set,
* then a new Service instance will be instantiated, and will be provided with the Dictionary passed to the
* Service instance.
*
* @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
*/
@SuppressWarnings( { "unchecked", "rawtypes"})
public class FactorySet extends AbstractSet<Dictionary>
{
/**
* When a Dictionary is registered in a factory Set, we use this special
* property key, whose value may provide the instance to use when
* creating a service.
*/
private final static String DM_FACTORY_INSTANCE = "dm.factory.instance";
/**
* The actual Service instance that is allocated for each dictionaries added in this Set.
*/
private volatile Object m_impl;
/**
* The Service provided in the OSGi registry.
*/
private final String[] m_provide;
/**
* The properties to be provided by the Service.
*/
private final Dictionary m_serviceProperties;
/**
* The configure Service callback used to pass configuration added in this Set.
*/
private final String m_configure;
/**
* The map between our Dictionaries and corresponding Service instances.
* This map is modified from our serial executor, or from the add method.
*/
private final ConcurrentHashMap<ServiceKey, Object> m_services = new ConcurrentHashMap<ServiceKey, Object>();
/**
* The list of Dependencies which are applied in the Service.
*/
private final MetaData m_srvMeta;
/**
* The list of Dependencies which are applied in the Service.
*/
private final List<MetaData> m_depsMeta;
/**
* The DependencyManager which is used to create Service instances.
*/
private volatile DependencyManager m_dm;
/**
* This class is used to serialize concurrent method calls, and allow to leave our methods unsynchronized.
* This is required because some of our methods may invoke some Service callbacks, which must be called outside
* synchronized block (in order to avoid potential dead locks).
*/
private final SerialExecutor m_serialExecutor = new SerialExecutor();
/**
* Flag used to check if our service is Active.
*/
private volatile boolean m_active;
/**
* The bundle containing the Service annotated with the factory attribute.
*/
private final Bundle m_bundle;
/**
* Flag used to check if a service is being created
*/
private final static Object SERVICE_CREATING = new Object();
/**
* This class wraps <tt>Dictionary</tt>, allowing to store the dictionary into a Map, using
* reference-equality in place of object-equality when getting the Dictionary from the Map.
*/
private static class ServiceKey
{
private Dictionary m_dictionary;
public ServiceKey(Dictionary dictionary)
{
m_dictionary = dictionary;
}
Dictionary getDictionary()
{
return m_dictionary;
}
@Override
public boolean equals(Object that)
{
return that instanceof ServiceKey ? (((ServiceKey) that).getDictionary() == m_dictionary)
: false;
}
@Override
public int hashCode()
{
return System.identityHashCode(m_dictionary);
}
@Override
public String toString()
{
return Dictionary.class.getName() + "@" + System.identityHashCode(m_dictionary);
}
}
/**
* Sole constructor.
* @param b the bundle containing the Service annotated with the factory attribute
* @param impl The Service implementation class
* @param serviceProperties The Service properties
* @param provides The Services provided by this Service
* @param factoryConfigure The configure callback invoked in order to pass configurations added in this Set.
*/
public FactorySet(Bundle b, MetaData srvMeta, List<MetaData> depsMeta)
{
m_serviceProperties = srvMeta.getDictionary(Params.properties, null);
m_provide = srvMeta.getStrings(Params.provides, null);
m_configure = srvMeta.getString(Params.factoryConfigure, null);
m_bundle = b;
m_srvMeta = srvMeta;
m_depsMeta = depsMeta;
}
/**
* Our Service is starting.
*/
public void start(Component c)
{
m_active = true;
m_dm = c.getDependencyManager();
}
/**
* Our Service is stopping: we have to remove all Service instances that we have created.
*/
public void stop()
{
try
{
clear();
}
finally
{
m_active = false;
}
}
/**
* Create or Update a Service.
*/
@Override
@SuppressWarnings({ "synthetic-access" })
public boolean add(final Dictionary configuration)
{
// Check parameter validity
if (configuration == null)
{
throw new NullPointerException("configuration parameter can't be null");
}
// Check if our service is running.
checkServiceAvailable();
// Check if service is being created
ServiceKey serviceKey = new ServiceKey(configuration);
boolean creating = m_services.putIfAbsent(serviceKey, SERVICE_CREATING) == null;
// Create or Update the Service.
m_serialExecutor.enqueue(new Runnable()
{
public void run()
{
doAdd(configuration);
}
});
m_serialExecutor.execute();
return creating;
}
/**
* Another Service wants to remove an existing Service.
* This method is not synchronized but uses a SerialExecutor for serializing concurrent method call.
* (This avoid potential dead locks, especially when Service callback methods are invoked).
*/
@Override
@SuppressWarnings("synthetic-access")
public boolean remove(final Object configuration)
{
// Check parameter validity.
if (configuration == null)
{
throw new NullPointerException("configuration parameter can't be null");
}
if (!(configuration instanceof Dictionary))
{
throw new IllegalArgumentException("configuration must be an instance of a Dictionary");
}
// Check if our service is active.
checkServiceAvailable();
// Check if service is created (or creating)
boolean found = m_services.containsKey(new ServiceKey((Dictionary) configuration));
if (found)
{
// Create or Update the Service.
m_serialExecutor.enqueue(new Runnable()
{
public void run()
{
doRemove((Dictionary) configuration);
}
});
m_serialExecutor.execute();
}
return found;
}
/**
* Another Service wants to remove all existing Services.
* This method is not synchronized but uses a SerialExecutor for serializing concurrent method call.
* (This avoid potential dead locks, especially when Service callback methods are invoked).
*/
@Override
@SuppressWarnings("synthetic-access")
public void clear()
{
if (!m_active)
{
return;
}
// Make sure add/update/clear events are handled in FIFO order (serially).
m_serialExecutor.enqueue(new Runnable()
{
public void run()
{
doClear();
}
});
m_serialExecutor.execute();
}
/**
* returns the list of active Service instances configurations.
*/
@Override
public Iterator<Dictionary> iterator()
{
throw new UnsupportedOperationException(
"iterator method is not supported by DependencyManager Set's service factories");
}
@Override
public String toString()
{
return FactorySet.class.getName() + "(" + m_services.size() + " active instances)";
}
/**
* Returns the number of active Service instances.
*/
@Override
public int size()
{
if (!m_active)
{
return 0;
}
return m_services.size();
}
/**
* Checks if our Service is available (we are not stopped").
*/
private void checkServiceAvailable()
{
if (!m_active)
{
throw new IllegalStateException("Service not available");
}
}
/**
* Add or create a new Service instance, given its configuration. This method is invoked by the
* SerialExecutor, hence it's thread safe and we'll invoke Service's callbacks without being
* synchronized (hence this will avoid potential dead locks).
*/
private void doAdd(Dictionary configuration)
{
// Check if the service exists.
ServiceKey serviceKey = new ServiceKey(configuration);
Object service = m_services.get(serviceKey);
if (service == null || service == SERVICE_CREATING)
{
try
{
// Create the Service / impl, unless it is explicitly provided from the
// configuration (using the specific key "dm.factory.instance").
Component s = m_dm.createComponent();
Class<?> implClass = m_bundle.loadClass(m_srvMeta.getString(Params.impl));
Object impl = configuration.get(DM_FACTORY_INSTANCE);
if (impl == null)
{
String factoryMethod = m_srvMeta.getString(Params.factoryMethod, null);
if (factoryMethod == null)
{
m_impl = implClass.newInstance();
}
else
{
Method m = implClass.getDeclaredMethod(factoryMethod);
m.setAccessible(true);
m_impl = m.invoke(null);
}
}
else
{
m_impl = impl;
}
// Invoke "configure" callback
if (m_configure != null)
{
invokeConfigure(m_impl, m_configure, configuration);
}
// Create Service
s.setImplementation(m_impl);
if (m_provide != null)
{
// Merge service properties with the configuration provided by the factory.
Dictionary serviceProperties = mergeSettings(m_serviceProperties, configuration);
s.setInterface(m_provide, serviceProperties);
}
s.setComposition(m_srvMeta.getString(Params.composition, null));
ServiceLifecycleHandler lfcleHandler = new ServiceLifecycleHandler(s, m_bundle, m_dm,
m_srvMeta, m_depsMeta);
// The dependencies will be plugged by our lifecycle handler.
s.setCallbacks(lfcleHandler, "init", "start", "stop", "destroy");
// Adds dependencies (except named dependencies, which are managed by the lifecycle handler).
for (MetaData dependency: m_depsMeta)
{
String name = dependency.getString(Params.name, null);
if (name == null)
{
DependencyBuilder depBuilder = new DependencyBuilder(dependency);
Log.instance().info("ServiceLifecycleHandler.init: adding dependency %s into service %s",
dependency, m_srvMeta);
Dependency d = depBuilder.build(m_bundle, m_dm);
s.add(d);
}
}
// Register the Service instance, and keep track of it.
Log.instance().info("ServiceFactory: created service %s", m_srvMeta);
m_dm.add(s);
m_services.put(serviceKey, s);
}
catch (Throwable t)
{
// Make sure the SERVICE_CREATING flag is also removed
m_services.remove(serviceKey);
Log.instance().error("ServiceFactory: could not instantiate service %s",
t, m_srvMeta);
}
}
else
{
// Reconfigure an already existing Service.
if (m_configure != null)
{
Log.instance().info("ServiceFactory: updating service %s", m_impl);
invokeConfigure(m_impl, m_configure, configuration);
}
// Update service properties
if (m_provide != null)
{
Dictionary settings = mergeSettings(m_serviceProperties, configuration);
((Component) service).setServiceProperties(settings);
}
}
}
private void doRemove(Dictionary configuraton)
{
Log.instance().info("ServiceFactory: removing service %s", m_srvMeta);
ServiceKey serviceKey = new ServiceKey(configuraton);
Object service = m_services.remove(serviceKey);
if (service != null && service != SERVICE_CREATING)
{
m_dm.remove((Component) service);
}
}
private void doClear()
{
for (Object service : m_services.values())
{
if (service instanceof Component)
{
m_dm.remove((Component) service);
}
}
m_services.clear();
}
/**
* Merge factory configuration settings with the service properties. The private factory configuration
* settings are ignored. A factory configuration property is private if its name starts with a dot (".").
*
* @param serviceProperties
* @param factoryConfiguration
* @return
*/
private Dictionary mergeSettings(Dictionary serviceProperties, Dictionary factoryConfiguration)
{
Dictionary props = new Hashtable();
if (serviceProperties != null)
{
Enumeration keys = serviceProperties.keys();
while (keys.hasMoreElements())
{
Object key = keys.nextElement();
Object val = serviceProperties.get(key);
props.put(key, val);
}
}
Enumeration keys = factoryConfiguration.keys();
while (keys.hasMoreElements())
{
Object key = keys.nextElement();
if (!key.toString().startsWith("."))
{
// public properties are propagated
Object val = factoryConfiguration.get(key);
props.put(key, val);
}
}
return props;
}
/**
* Invokes the configure callback method on the service instance implemenatation.
* @param impl
* @param configure
* @param config
*/
private void invokeConfigure(Object impl, String configure, Dictionary config)
{
try
{
InvocationUtil.invokeCallbackMethod(impl, configure,
new Class[][] { { Dictionary.class } },
new Object[][] { { config } });
}
catch (Throwable t)
{
if (t instanceof RuntimeException)
{
throw (RuntimeException) t;
}
else
{
throw new RuntimeException("Could not invoke method " + configure
+ " on object " + impl, t);
}
}
}
}