blob: 42907a05b0dfa6553e271dcd8cb77f2e07feb6c0 [file] [log] [blame]
/*
* Copyright 2006 The Apache Software Foundation
*
* Licensed 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.dependencymanager;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Dictionary;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceRegistration;
/**
* Service implementation.
*
* @author <a href="mailto:felix-dev@incubator.apache.org">Felix Project Team</a>
*/
public class ServiceImpl implements Service {
private static final ServiceRegistration NULL_REGISTRATION;
private static final int STARTING = 1;
private static final int WAITING_FOR_REQUIRED = 2;
private static final int TRACKING_OPTIONAL = 3;
private static final int STOPPING = 4;
private static final String[] STATE_NAMES = {
"(unknown)",
"starting",
"waiting for required dependencies",
"tracking optional dependencies",
"stopping"};
private BundleContext m_context;
private ServiceRegistration m_registration;
private String m_callbackInit;
private String m_callbackStart;
private String m_callbackStop;
private String m_callbackDestroy;
private List m_listeners = new ArrayList();
private ArrayList m_dependencies = new ArrayList();
private int m_state;
private Object m_serviceInstance;
private Object m_implementation;
private Object m_serviceName;
private Dictionary m_serviceProperties;
public ServiceImpl(BundleContext context) {
m_state = STARTING;
m_context = context;
m_callbackInit = "init";
m_callbackStart = "start";
m_callbackStop = "stop";
m_callbackDestroy = "destroy";
}
public Service add(Dependency dependency) {
synchronized (m_dependencies) {
m_dependencies.add(dependency);
}
if (m_state == WAITING_FOR_REQUIRED) {
// if we're waiting for required dependencies, and
// this is a required dependency, start tracking it
// ...otherwise, we don't need to do anything yet
if (dependency.isRequired()) {
dependency.start(this);
}
}
else if (m_state == TRACKING_OPTIONAL) {
// start tracking the dependency
dependency.start(this);
if (dependency.isRequired() && !dependency.isAvailable()) {
// if this is a required dependency and it can not
// be resolved right away, then we need to go back to
// the waiting for required state, until this
// dependency is available
deactivateService();
}
}
return this;
}
public Service remove(Dependency dependency) {
synchronized (m_dependencies) {
m_dependencies.remove(dependency);
}
if (m_state == TRACKING_OPTIONAL) {
// if we're tracking optional dependencies, then any
// dependency that is removed can be stopped without
// causing state changes
dependency.stop(this);
}
else if (m_state == WAITING_FOR_REQUIRED) {
// if we're waiting for required dependencies, then
// we only need to stop tracking the dependency if it
// too is required; this might trigger a state change
if (dependency.isRequired()) {
dependency.stop(this);
if (allRequiredDependenciesAvailable()) {
activateService();
}
}
}
return this;
}
public List getDependencies() {
List list;
synchronized (m_dependencies) {
list = (List) m_dependencies.clone();
}
return list;
}
public ServiceRegistration getServiceRegistration() {
return m_registration;
}
public Object getService() {
return m_serviceInstance;
}
public void dependencyAvailable(Dependency dependency) {
if ((dependency.isRequired())
&& (m_state == WAITING_FOR_REQUIRED)
&& (allRequiredDependenciesAvailable())) {
activateService();
}
if ((!dependency.isRequired()) && (m_state == TRACKING_OPTIONAL)) {
updateInstance(dependency);
}
}
public void dependencyChanged(Dependency dependency) {
if (m_state == TRACKING_OPTIONAL) {
updateInstance(dependency);
}
}
public void dependencyUnavailable(Dependency dependency) {
if (dependency.isRequired()) {
if (m_state == TRACKING_OPTIONAL) {
if (!allRequiredDependenciesAvailable()) {
deactivateService();
}
}
}
else {
// optional dependency
}
if (m_state == TRACKING_OPTIONAL) {
updateInstance(dependency);
}
}
public synchronized void start() {
if ((m_state != STARTING) && (m_state != STOPPING)) {
throw new IllegalStateException("Cannot start from state " + STATE_NAMES[m_state]);
}
startTrackingRequired();
if (allRequiredDependenciesAvailable() && (m_state == WAITING_FOR_REQUIRED)) {
activateService();
}
}
public synchronized void stop() {
if ((m_state != WAITING_FOR_REQUIRED) && (m_state != TRACKING_OPTIONAL)) {
if ((m_state > 0) && (m_state < STATE_NAMES.length)) {
throw new IllegalStateException("Cannot stop from state " + STATE_NAMES[m_state]);
}
throw new IllegalStateException("Cannot stop from unknown state.");
}
if (m_state == TRACKING_OPTIONAL) {
deactivateService();
}
stopTrackingRequired();
}
private void activateService() {
// service activation logic, first we initialize the service instance itself
// meaning it is created if necessary and the bundle context is set
initService();
// then we invoke the init callback so the service can further initialize
// itself
invoke(m_callbackInit);
// now is the time to configure the service, meaning all required
// dependencies will be set and any callbacks called
configureService();
// inform the state listeners we're starting
stateListenersStarting();
// start tracking optional services
startTrackingOptional();
// invoke the start callback, since we're now ready to be used
invoke(m_callbackStart);
// register the service in the framework's service registry
registerService();
// inform the state listeners we've started
stateListenersStarted();
}
private void deactivateService() {
// service deactivation logic, first inform the state listeners
// we're stopping
stateListenersStopping();
// then, unregister the service from the framework
unregisterService();
// invoke the stop callback
invoke(m_callbackStop);
// stop tracking optional services
stopTrackingOptional();
// inform the state listeners we've stopped
stateListenersStopped();
// invoke the destroy callback
invoke(m_callbackDestroy);
// destroy the service instance
destroyService();
}
private void invoke(String name) {
if (name != null) {
// invoke method if it exists
AccessibleObject.setAccessible(m_serviceInstance.getClass().getDeclaredMethods(), true);
try {
m_serviceInstance.getClass().getDeclaredMethod(name, null).invoke(m_serviceInstance, null);
}
catch (NoSuchMethodException e) {
// ignore this, we don't care if the method does not exist
}
catch (Exception e) {
// TODO handle this exception
e.printStackTrace();
}
}
}
private synchronized void stateListenersStarting() {
Iterator i = m_listeners.iterator();
while (i.hasNext()) {
ServiceStateListener ssl = (ServiceStateListener) i.next();
ssl.starting(this);
}
}
private synchronized void stateListenersStarted() {
Iterator i = m_listeners.iterator();
while (i.hasNext()) {
ServiceStateListener ssl = (ServiceStateListener) i.next();
ssl.started(this);
}
}
private synchronized void stateListenersStopping() {
Iterator i = m_listeners.iterator();
while (i.hasNext()) {
ServiceStateListener ssl = (ServiceStateListener) i.next();
ssl.stopping(this);
}
}
private synchronized void stateListenersStopped() {
Iterator i = m_listeners.iterator();
while (i.hasNext()) {
ServiceStateListener ssl = (ServiceStateListener) i.next();
ssl.stopped(this);
}
}
private boolean allRequiredDependenciesAvailable() {
Iterator i = getDependencies().iterator();
while (i.hasNext()) {
Dependency dependency = (Dependency) i.next();
if (dependency.isRequired() && !dependency.isAvailable()) {
return false;
}
}
return true;
}
private void startTrackingOptional() {
m_state = TRACKING_OPTIONAL;
Iterator i = getDependencies().iterator();
while (i.hasNext()) {
Dependency dependency = (Dependency) i.next();
if (!dependency.isRequired()) {
dependency.start(this);
}
}
}
private void stopTrackingOptional() {
m_state = WAITING_FOR_REQUIRED;
Iterator i = getDependencies().iterator();
while (i.hasNext()) {
Dependency dependency = (Dependency) i.next();
if (!dependency.isRequired()) {
dependency.stop(this);
}
}
}
private void startTrackingRequired() {
m_state = WAITING_FOR_REQUIRED;
Iterator i = getDependencies().iterator();
while (i.hasNext()) {
Dependency dependency = (Dependency) i.next();
if (dependency.isRequired()) {
dependency.start(this);
}
}
}
private void stopTrackingRequired() {
m_state = STOPPING;
Iterator i = getDependencies().iterator();
while (i.hasNext()) {
Dependency dependency = (Dependency) i.next();
if (dependency.isRequired()) {
dependency.stop(this);
}
}
}
private void initService() {
if (m_implementation instanceof Class) {
// instantiate
try {
m_serviceInstance = ((Class) m_implementation).newInstance();
} catch (InstantiationException e) {
// TODO handle this exception
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO handle this exception
e.printStackTrace();
}
}
else {
m_serviceInstance = m_implementation;
}
// configure the bundle context
configureImplementation(BundleContext.class, m_context);
configureImplementation(ServiceRegistration.class, NULL_REGISTRATION);
}
private void configureService() {
// configure all services (the optional dependencies might be configured
// as null objects but that's what we want at this point)
configureServices();
}
private void destroyService() {
unconfigureServices();
m_serviceInstance = null;
}
private void registerService() {
if (m_serviceName != null) {
ServiceRegistrationImpl wrapper = new ServiceRegistrationImpl();
m_registration = wrapper;
configureImplementation(ServiceRegistration.class, wrapper);
// service name can either be a string or an array of strings
ServiceRegistration registration;
if (m_serviceName instanceof String) {
registration = m_context.registerService((String) m_serviceName, m_serviceInstance, m_serviceProperties);
}
else {
registration = m_context.registerService((String[]) m_serviceName, m_serviceInstance, m_serviceProperties);
}
wrapper.setServiceRegistration(registration);
}
}
private void unregisterService() {
if (m_serviceName != null) {
m_registration.unregister();
configureImplementation(ServiceRegistration.class, NULL_REGISTRATION);
}
}
private void updateInstance(Dependency dependency) {
if (dependency instanceof ServiceDependency) {
ServiceDependency sd = (ServiceDependency) dependency;
// update the dependency in the service instance (it will use
// a null object if necessary)
if (sd.isAutoConfig()) {
configureImplementation(sd.getInterface(), sd.getService());
}
}
}
/**
* Configure a field in the service implementation. The service implementation
* is searched for fields that have the same type as the class that was specified
* and for each of these fields, the specified instance is filled in.
*
* @param clazz the class to search for
* @param instance the instance to fill in
*/
private void configureImplementation(Class clazz, Object instance) {
Class serviceClazz = m_serviceInstance.getClass();
while (serviceClazz != null) {
Field[] fields = serviceClazz.getDeclaredFields();
AccessibleObject.setAccessible(fields, true);
for (int j = 0; j < fields.length; j++) {
if (fields[j].getType().equals(clazz)) {
try {
// synchronized makes sure the field is actually written to immediately
synchronized (new Object()) {
fields[j].set(m_serviceInstance, instance);
}
}
catch (Exception e) {
System.err.println("Exception while trying to set " + fields[j].getName() +
" of type " + fields[j].getType().getName() +
" by classloader " + fields[j].getType().getClassLoader() +
" which should equal type " + clazz.getName() +
" by classloader " + clazz.getClassLoader() +
" of type " + serviceClazz.getName() +
" by classloader " + serviceClazz.getClassLoader() +
" on " + m_serviceInstance +
" by classloader " + m_serviceInstance.getClass().getClassLoader() +
"\nDumping stack:"
);
e.printStackTrace();
System.out.println("C: " + clazz);
System.out.println("I: " + instance);
System.out.println("I:C: " + instance.getClass().getClassLoader());
Class[] classes = instance.getClass().getInterfaces();
for (int i = 0; i < classes.length; i++) {
Class c = classes[i];
System.out.println("I:C:I: " + c);
System.out.println("I:C:I:C: " + c.getClassLoader());
}
System.out.println("F: " + fields[j]);
throw new IllegalStateException("Could not set field " + fields[j].getName() + " on " + m_serviceInstance);
}
}
}
serviceClazz = serviceClazz.getSuperclass();
}
}
private void configureServices() {
Iterator i = getDependencies().iterator();
while (i.hasNext()) {
Dependency dependency = (Dependency) i.next();
if (dependency instanceof ServiceDependency) {
ServiceDependency sd = (ServiceDependency) dependency;
if (sd.isAutoConfig()) {
configureImplementation(sd.getInterface(), sd.getService());
}
// for required dependencies, we invoke any callbacks here
if (sd.isRequired()) {
sd.invokeAdded();
}
}
}
}
private void unconfigureServices() {
Iterator i = getDependencies().iterator();
while (i.hasNext()) {
Dependency dependency = (Dependency) i.next();
if (dependency instanceof ServiceDependency) {
ServiceDependency sd = (ServiceDependency) dependency;
// for required dependencies, we invoke any callbacks here
if (sd.isRequired()) {
sd.invokeRemoved();
}
}
}
}
public synchronized void addStateListener(ServiceStateListener listener) {
m_listeners.add(listener);
if (m_state == TRACKING_OPTIONAL) {
listener.starting(this);
listener.started(this);
}
}
public synchronized void removeStateListener(ServiceStateListener listener) {
m_listeners.remove(listener);
}
synchronized void removeStateListeners() {
m_listeners.clear();
}
public synchronized Service setInterface(String serviceName, Dictionary properties) {
ensureNotActive();
m_serviceName = serviceName;
m_serviceProperties = properties;
return this;
}
public synchronized Service setInterface(String[] serviceName, Dictionary properties) {
ensureNotActive();
m_serviceName = serviceName;
m_serviceProperties = properties;
return this;
}
public synchronized Service setCallbacks(String init, String start, String stop, String destroy) {
ensureNotActive();
m_callbackInit = init;
m_callbackStart = start;
m_callbackStop = stop;
m_callbackDestroy = destroy;
return this;
}
public synchronized Service setImplementation(Object implementation) {
ensureNotActive();
m_implementation = implementation;
return this;
}
private void ensureNotActive() {
if ((m_state == TRACKING_OPTIONAL) || (m_state == WAITING_FOR_REQUIRED)) {
throw new IllegalStateException("Cannot modify state while active.");
}
}
boolean isRegistered() {
return (m_state == TRACKING_OPTIONAL);
}
public String toString() {
return "ServiceImpl[" + m_serviceName + " " + m_implementation + "]";
}
public synchronized Dictionary getServiceProperties() {
if (m_serviceProperties != null) {
return (Dictionary) ((Hashtable) m_serviceProperties).clone();
}
return null;
}
public synchronized void setServiceProperties(Dictionary serviceProperties) {
m_serviceProperties = serviceProperties;
if (isRegistered() && (m_serviceName != null) && (m_serviceProperties != null)) {
m_registration.setProperties(m_serviceProperties);
}
}
static {
NULL_REGISTRATION = (ServiceRegistration) Proxy.newProxyInstance(ServiceImpl.class.getClassLoader(), new Class[] {ServiceRegistration.class}, new DefaultNullObject());
}
}