blob: e5c1d28eb602090b1726805ed86c2aba973e6259 [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.handlers.dependency;
import java.net.URL;
import java.util.ArrayList;
import java.util.Dictionary;
import java.util.List;
import org.apache.felix.ipojo.Handler;
import org.apache.felix.ipojo.InstanceManager;
import org.apache.felix.ipojo.PolicyServiceContext;
import org.apache.felix.ipojo.architecture.HandlerDescription;
import org.apache.felix.ipojo.handlers.dependency.nullable.NullableObjectWriter;
import org.apache.felix.ipojo.metadata.Element;
import org.apache.felix.ipojo.parser.FieldMetadata;
import org.apache.felix.ipojo.parser.ManipulationMetadata;
import org.apache.felix.ipojo.parser.MethodMetadata;
import org.apache.felix.ipojo.util.Logger;
import org.osgi.framework.ServiceReference;
/**
* The dependency handler manages a list of dependencies.
*
* @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
*/
public class DependencyHandler extends Handler {
/**
* The instance manager using this handler.
*/
private InstanceManager m_manager;
/**
* List of depednencies of the component.
*/
private Dependency[] m_dependencies = new Dependency[0];
/**
* List of nullable class for optional dependencies.
*/
private Class[] m_nullableClasses = new Class[0];
/**
* State of the handler.
*/
private int m_state;
// ===================== Fields getters & setters =====================
/**
* Add a dependency.
*
* @param dep : the dependency to add
*/
private void addDependency(Dependency dep) {
for (int i = 0; (m_dependencies != null) && (i < m_dependencies.length); i++) {
if (m_dependencies[i] == dep) {
return;
}
}
if (m_dependencies.length > 0) {
Dependency[] newDep = new Dependency[m_dependencies.length + 1];
System.arraycopy(m_dependencies, 0, newDep, 0, m_dependencies.length);
newDep[m_dependencies.length] = dep;
m_dependencies = newDep;
} else {
m_dependencies = new Dependency[] { dep };
}
}
/**
* Add a nullable class.
*
* @param clazz : the class to add
*/
private void addNullableClass(Class clazz) {
for (int i = 0; (m_nullableClasses != null) && (i < m_nullableClasses.length); i++) {
if (m_nullableClasses[i] == clazz) {
return;
}
}
if (m_nullableClasses.length > 0) {
Class[] newClass = new Class[m_nullableClasses.length + 1];
System.arraycopy(m_nullableClasses, 0, newClass, 0, m_nullableClasses.length);
newClass[m_nullableClasses.length] = clazz;
m_nullableClasses = newClass;
} else {
m_nullableClasses = new Class[] { clazz };
}
}
/**
* Get the list of managed dependency.
* @return the dependency list
*/
public Dependency[] getDependencies() {
return m_dependencies;
}
/**
* Get the instance manager.
* @return the instance manager
*/
protected InstanceManager getInstanceManager() {
return m_manager;
}
// ===================== Handler implementation =====================
/**
* Check the validity of the dependencies.
*/
protected void checkContext() {
synchronized (this) {
// Store the initial state
int initialState = m_state;
// Check the component dependencies
if (validateComponentDependencies()) {
// The dependencies are valid
if (initialState == InstanceManager.INVALID) {
// There is a state change
m_state = InstanceManager.VALID;
m_manager.checkInstanceState();
}
// Else do nothing, the component state stay VALID
} else {
// The dependencies are not valid
if (initialState == InstanceManager.VALID) {
// There is a state change
m_state = InstanceManager.INVALID;
m_manager.checkInstanceState();
}
// Else do nothing, the component state stay UNRESOLVED
}
}
}
/**
* Check if the dependency given is valid in the sense that metadata are
* consistent.
*
* @param dep : the dependency to check
* @param manipulation : the component-type manipulation metadata
* @return true if the dependency is valid
*/
private boolean checkDependency(Dependency dep, ManipulationMetadata manipulation) {
// Check the internal type of dependency
String field = dep.getField();
DependencyCallback[] callbacks = dep.getCallbacks();
for (int i = 0; i < callbacks.length; i++) {
MethodMetadata[] mets = manipulation.getMethods(callbacks[i].getMethodName());
if (mets.length == 0) {
getInstanceManager().getFactory().getLogger().log(Logger.ERROR, "A dependency callback " + callbacks[i].getMethodName() + " does not exist in the implementation");
return false;
}
if (mets[0].getMethodArguments().length > 1) {
getInstanceManager().getFactory().getLogger().log(Logger.ERROR, "A dependency callback " + callbacks[i].getMethodName() + " must have 0 or 1 argument");
return false;
}
if (mets[0].getMethodArguments().length == 0) {
callbacks[i].setArgument("EMPTY");
} else {
callbacks[i].setArgument(mets[0].getMethodArguments()[0]);
if (!mets[0].getMethodArguments()[0].equals(ServiceReference.class.getName())) {
if (dep.getSpecification() == null) {
dep.setSpecification(mets[0].getMethodArguments()[0]);
}
if (!dep.getSpecification().equals(mets[0].getMethodArguments()[0])) {
m_manager.getFactory().getLogger().log(Logger.WARNING, "[DependencyHandler on " + m_manager.getClassName() + "] The field type [" + mets[0].getMethodArguments()[0] + "] and the needed service interface ["
+ dep.getSpecification() + "] are not the same");
dep.setSpecification(mets[0].getMethodArguments()[0]);
}
}
}
}
if (field != null) {
FieldMetadata fm = manipulation.getField(field);
if (fm == null) {
getInstanceManager().getFactory().getLogger().log(Logger.ERROR, "A dependency field " + field + " does not exist in the implementation class");
return false;
}
String type = fm.getFieldType();
if (type.endsWith("[]")) {
// Set the dependency to multiple
dep.setAggregate();
type = type.substring(0, type.length() - 2);
}
if (dep.getSpecification() == null) { dep.setSpecification(type); }
if (!dep.getSpecification().equals(type)) {
m_manager.getFactory().getLogger().log(Logger.WARNING, "[DependencyHandler on " + m_manager.getClassName() + "] The field type [" + type + "] and the needed service interface ["
+ dep.getSpecification() + "] are not the same");
dep.setSpecification(type);
}
}
//Check that all required info are set
return dep.getSpecification() != null;
}
/**
* Configure the handler.
* @param im : the instance manager
* @param componentMetadata : the component type metadata
* @param configuration : the instance configuration
* @see org.apache.felix.ipojo.Handler#configure(org.apache.felix.ipojo.InstanceManager, org.apache.felix.ipojo.metadata.Element, java.util.Dictionary)
*/
public void configure(InstanceManager im, Element componentMetadata, Dictionary configuration) {
m_manager = im;
m_dependencies = new Dependency[0];
m_nullableClasses = new Class[0];
ManipulationMetadata manipulation = new ManipulationMetadata(componentMetadata);
List fl = new ArrayList();
// Create the dependency according to the component metadata
Element[] deps = componentMetadata.getElements("Requires");
// DEPRECATED BLOCK :
if (deps.length == 0) {
deps = componentMetadata.getElements("Dependency");
if (deps.length != 0) {
im.getFactory().getLogger().log(Logger.WARNING, "Dependency is deprecated, please use 'requires' instead of 'dependency'");
}
}
// END OF DEPRECATED BLOCK
for (int i = 0; i < deps.length; i++) {
// Create the dependency metadata
String field = null;
if (deps[i].containsAttribute("field")) {
field = deps[i].getAttribute("field");
}
String serviceSpecification = null;
if (deps[i].containsAttribute("interface")) {
serviceSpecification = deps[i].getAttribute("interface");
}
String filter = "";
if (deps[i].containsAttribute("filter")) {
filter = deps[i].getAttribute("filter");
}
boolean optional = false;
if (deps[i].containsAttribute("optional") && deps[i].getAttribute("optional").equals("true")) {
optional = true;
}
boolean aggregate = false;
if (deps[i].containsAttribute("aggregate") && deps[i].getAttribute("aggregate").equals("true")) {
aggregate = true;
}
String id = null;
if (deps[i].containsAttribute("id")) {
id = deps[i].getAttribute("id");
}
int scopePolicy = -1;
if (deps[i].containsAttribute("scope")) {
if (deps[i].getAttribute("scope").equalsIgnoreCase("global")) {
scopePolicy = PolicyServiceContext.GLOBAL;
} else if (deps[i].getAttribute("scope").equalsIgnoreCase("composite")) {
scopePolicy = PolicyServiceContext.LOCAL;
} else if (deps[i].getAttribute("scope").equalsIgnoreCase("composite+global")) {
scopePolicy = PolicyServiceContext.LOCAL_AND_GLOBAL;
}
}
Dependency dep = new Dependency(this, field, serviceSpecification, filter, optional, aggregate, id, scopePolicy);
// Look for dependency callback :
for (int j = 0; j < (deps[i].getElements("Callback", "")).length; j++) {
String method = deps[i].getElements("Callback", "")[j].getAttribute("method");
String type = deps[i].getElements("Callback", "")[j].getAttribute("type");
int methodType = 0;
if (type.equals("bind")) {
methodType = DependencyCallback.BIND;
} else {
methodType = DependencyCallback.UNBIND;
}
DependencyCallback dc = new DependencyCallback(dep, method, methodType);
dep.addDependencyCallback(dc);
}
// Check the dependency :
if (checkDependency(dep, manipulation)) {
addDependency(dep);
if (dep.getField() != null) {
fl.add(manipulation.getField(dep.getField()));
}
} else {
m_manager.getFactory().getLogger().log(Logger.ERROR,
"[DependencyHandler on " + m_manager.getClassName() + "] The dependency on " + dep.getField() + " is not valid");
}
}
if (deps.length > 0) {
m_manager.register(this, (FieldMetadata[]) fl.toArray(new FieldMetadata[0]), null);
}
}
/**
* Create a nullable class for the given dependency.
*
* @param dep : the dependency
*/
private void createNullableClass(Dependency dep) {
m_manager.getFactory().getLogger().log(Logger.INFO,
"[DependencyHandler on " + m_manager.getClassName() + "] Try to load the nullable class for " + dep.getSpecification());
// String[] segment =
// dep.getMetadata().getServiceSpecification().split("[.]");
// String className = "org/apache/felix/ipojo/" + segment[segment.length
// - 1] + "Nullable";
String className = dep.getSpecification() + "Nullable";
String resource = dep.getSpecification().replace('.', '/') + ".class";
URL url = m_manager.getContext().getBundle().getResource(resource);
try {
byte[] b = NullableObjectWriter.dump(url, dep.getSpecification());
Class c = null;
try {
c = m_manager.getFactory().defineClass(className, b, null);
} catch (Exception e) {
m_manager.getFactory().getLogger().log(Logger.ERROR, "Cannot define the nullable class : " + e.getMessage());
return;
}
addNullableClass(c);
m_manager.getFactory().getLogger().log(Logger.INFO,
"[DependencyHandler on " + m_manager.getClassName() + "] Nullable class created for " + dep.getSpecification());
} catch (Exception e2) {
m_manager.getFactory().getLogger().log(Logger.ERROR,
"[DependencyHandler on " + m_manager.getClassName() + "] Cannot load the nullable class for " + dep.getSpecification(), e2);
}
}
/**
* Return the nullable class corresponding to the given name.
*
* @param name the needed type
* @return the class correspondig to the name, or null if the class does not
* exist.
*/
protected Class getNullableClass(String name) {
for (int i = 0; i < m_nullableClasses.length; i++) {
Class c = m_nullableClasses[i];
if (c.getName().equals(name)) {
return c;
}
}
return null;
}
/**
* GetterCallback Method.
* @param fieldName : the field name.
* @param value : the value passed to the field (by the previous handler).
* @return the object that the dependency handler want to push.
* @see org.apache.felix.ipojo.Handler#getterCallback(java.lang.String, java.lang.Object)
*/
public Object getterCallback(String fieldName, Object value) {
for (int i = 0; i < m_dependencies.length; i++) {
Dependency dep = m_dependencies[i];
if (dep.getField().equals(fieldName)) {
// The field name is a dependency, return the get
return dep.get();
}
}
// Else return the value
return value;
}
/**
* Check the handler validity.
* @return true if all mandatory dependencies are resolved.
* @see org.apache.felix.ipojo.Handler#isValid()
*/
public boolean isValid() {
return m_state == InstanceManager.VALID;
}
/**
* Handler start method.
* @see org.apache.felix.ipojo.Handler#start()
*/
public void start() {
m_manager.getFactory().getLogger().log(Logger.INFO, "[DependencyHandler on " + m_manager.getClassName() + "] Start the dependency handler");
// Start the dependencies, for optional dependencies create Nullable
// class
for (int i = 0; i < m_dependencies.length; i++) {
Dependency dep = m_dependencies[i];
if (dep.isOptional() && !dep.isAggregate()) {
createNullableClass(dep);
}
dep.start();
}
// Check the state
m_state = m_manager.getState();
checkContext();
}
/**
* Handler stateChanged method.
* @param state : new instance state
* @see org.apache.felix.ipojo.Handler#stateChanged(int)
*/
public void stateChanged(int state) {
m_state = state;
}
/**
* Handler stop method.
* @see org.apache.felix.ipojo.Handler#stop()
*/
public void stop() {
for (int i = 0; i < m_dependencies.length; i++) {
m_dependencies[i].stop();
}
m_nullableClasses = new Class[0];
}
/**
* Handler createInstance method.
* This method is overided to allow delayed callback invocation.
* @param instance : the created object
* @see org.apache.felix.ipojo.Handler#createInstance(java.lang.Object)
*/
public void createInstance(Object instance) {
for (int i = 0; i < m_dependencies.length; i++) {
m_dependencies[i].callBindMethod(instance);
}
}
/**
* Check if dependencies are resolved.
* @return true if all dependencies are resolved.
*/
private boolean validateComponentDependencies() {
boolean valide = true;
for (int i = 0; i < m_dependencies.length; i++) {
Dependency dep = m_dependencies[i];
valide = valide & dep.isSatisfied();
if (!valide) {
return false;
}
}
return valide;
}
/**
* Get the dependency handler description.
* @return the dependency handler description.
* @see org.apache.felix.ipojo.Handler#getDescription()
*/
public HandlerDescription getDescription() {
DependencyHandlerDescription dhd = new DependencyHandlerDescription(isValid());
for (int j = 0; j < getDependencies().length; j++) {
Dependency dep = getDependencies()[j];
// Create & add the dependency description
DependencyDescription dd = new DependencyDescription(dep.getSpecification(), dep.isAggregate(), dep.isOptional(), dep.getFilter(), dep.getState());
dd.setServiceReferences(dep.getServiceReferences());
dd.setUsedServices(dep.getUsedServices());
dhd.addDependency(dd);
}
return dhd;
}
}