blob: e4986b37b7316e3d283200e7c569d7fcb8f8232d [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.configuration;
import java.util.ArrayList;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.List;
import java.util.Properties;
import org.apache.felix.ipojo.ConfigurationException;
import org.apache.felix.ipojo.HandlerFactory;
import org.apache.felix.ipojo.PrimitiveHandler;
import org.apache.felix.ipojo.architecture.ComponentTypeDescription;
import org.apache.felix.ipojo.architecture.PropertyDescription;
import org.apache.felix.ipojo.handlers.providedservice.ProvidedServiceHandler;
import org.apache.felix.ipojo.metadata.Attribute;
import org.apache.felix.ipojo.metadata.Element;
import org.apache.felix.ipojo.parser.FieldMetadata;
import org.apache.felix.ipojo.parser.MethodMetadata;
import org.apache.felix.ipojo.parser.PojoMetadata;
import org.apache.felix.ipojo.util.Property;
import org.osgi.framework.Constants;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.cm.ManagedService;
/**
* Handler managing the Configuration Admin.
* @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
*/
public class ConfigurationHandler extends PrimitiveHandler implements ManagedService {
/**
* List of the configurable fields.
*/
private List/*<Property>*/ m_configurableProperties = new ArrayList(1);
/**
* ProvidedServiceHandler of the component. It is useful to propagate
* properties to service registrations.
*/
private ProvidedServiceHandler m_providedServiceHandler;
/**
* Properties propagated during the last instance "update".
*/
private Dictionary m_propagatedFromInstance = new Properties();
/**
* Properties to propagate.
*/
private Dictionary m_toPropagate = new Properties();
/**
* Properties propagated from the configuration admin.
*/
private Dictionary m_propagatedFromCA;
/**
* Check if the instance was already reconfigured by the configuration admin.
*/
private boolean m_configurationAlreadyPushed;
/**
* should the component propagate configuration ?
*/
private boolean m_mustPropagate;
/**
* Service Registration to publish the service registration.
*/
private ServiceRegistration m_sr;
/**
* Managed Service PID.
* This PID must be different from the instance name if the instance was created
* with the Configuration Admin.
*/
private String m_managedServicePID;
/**
* Initialize the component type.
* @param desc : component type description to populate.
* @param metadata : component type metadata.
* @throws ConfigurationException : metadata are incorrect.
* @see org.apache.felix.ipojo.Handler#initializeComponentFactory(org.apache.felix.ipojo.architecture.ComponentTypeDescription, org.apache.felix.ipojo.metadata.Element)
*/
public void initializeComponentFactory(ComponentTypeDescription desc, Element metadata) throws ConfigurationException {
Element[] confs = metadata.getElements("Properties", "");
if (confs == null) { return; }
Element[] configurables = confs[0].getElements("Property");
for (int i = 0; configurables != null && i < configurables.length; i++) {
String fieldName = configurables[i].getAttribute("field");
String methodName = configurables[i].getAttribute("method");
if (fieldName == null && methodName == null) {
throw new ConfigurationException("Malformed property : The property needs to contain at least a field or a method");
}
String name = configurables[i].getAttribute("name");
if (name == null) {
if (fieldName == null) {
name = methodName;
} else {
name = fieldName;
}
configurables[i].addAttribute(new Attribute("name", name)); // Add the type to avoid configure checking
}
String value = configurables[i].getAttribute("value");
// Detect the type of the property
PojoMetadata manipulation = getFactory().getPojoMetadata();
String type = null;
if (fieldName == null) {
MethodMetadata[] method = manipulation.getMethods(methodName);
if (method.length == 0) {
type = configurables[i].getAttribute("type");
if (type == null) {
throw new ConfigurationException("Malformed property : The type of the property cannot be discovered, add a 'type' attribute");
}
} else {
if (method[0].getMethodArguments().length != 1) {
throw new ConfigurationException("Malformed property : The method " + methodName + " does not have one argument");
}
type = method[0].getMethodArguments()[0];
configurables[i].addAttribute(new Attribute("type", type)); // Add the type to avoid configure checking
}
} else {
FieldMetadata field = manipulation.getField(fieldName);
if (field == null) { throw new ConfigurationException("Malformed property : The field " + fieldName + " does not exist in the implementation class"); }
type = field.getFieldType();
configurables[i].addAttribute(new Attribute("type", type)); // Add the type to avoid configure checking
}
// Is the property set to immutable
boolean immutable = false;
String imm = configurables[i].getAttribute("immutable");
immutable = imm != null && imm.equalsIgnoreCase("true");
boolean mandatory = false;
String man = configurables[i].getAttribute("mandatory");
mandatory = man != null && man.equalsIgnoreCase("true");
PropertyDescription pd = null;
if (value == null) {
pd = new PropertyDescription(name, type, null, false); // Cannot be immutable if we have no value.
} else {
pd = new PropertyDescription(name, type, value, immutable);
}
if (mandatory) {
pd.setMandatory();
}
desc.addProperty(pd);
}
}
/**
* Configures the handler.
* Access to field does not require synchronization as this method is executed
* before any thread access to this object.
* @param metadata the metadata of the component
* @param configuration the instance configuration
* @throws ConfigurationException one property metadata is not correct
* @see org.apache.felix.ipojo.Handler#configure(org.apache.felix.ipojo.InstanceManager,
* org.apache.felix.ipojo.metadata.Element)
*/
public void configure(Element metadata, Dictionary configuration) throws ConfigurationException {
// Build the map
Element[] confs = metadata.getElements("Properties", "");
Element[] configurables = confs[0].getElements("Property");
// Check if the component is dynamically configurable
m_mustPropagate = false;
String propa = confs[0].getAttribute("propagation");
if (propa != null && propa.equalsIgnoreCase("true")) {
m_mustPropagate = true;
m_toPropagate = configuration; // Instance configuration to propagate.
}
// Check if the component support ConfigurationADmin reconfiguration
m_managedServicePID = confs[0].getAttribute("pid"); // Look inside the component type description
String instanceMSPID = (String) configuration.get("managed.service.pid"); // Look inside the instance configuration.
if (instanceMSPID != null) {
m_managedServicePID = instanceMSPID;
}
for (int i = 0; configurables != null && i < configurables.length; i++) {
String fieldName = configurables[i].getAttribute("field");
String methodName = configurables[i].getAttribute("method");
String name = configurables[i].getAttribute("name"); // The initialize method has fixed the property name.
String value = configurables[i].getAttribute("value");
String type = configurables[i].getAttribute("type"); // The initialize method has fixed the property name.
Property prop = new Property(name, fieldName, methodName, value, type, getInstanceManager(), this);
addProperty(prop);
// Check if the instance configuration contains value for the current property :
if (configuration.get(name) == null) {
if (fieldName != null && configuration.get(fieldName) != null) {
prop.setValue(configuration.get(fieldName));
}
} else {
prop.setValue(configuration.get(name));
}
if (fieldName != null) {
FieldMetadata field = new FieldMetadata(fieldName, type);
getInstanceManager().register(field, prop);
}
}
}
/**
* Stop method.
* This method is synchronized to avoid the configuration admin pushing a configuration during the un-registration.
* Do nothing.
* @see org.apache.felix.ipojo.Handler#stop()
*/
public synchronized void stop() {
if (m_sr != null) {
m_sr.unregister();
m_sr = null;
}
}
/**
* Start method.
* This method is synchronized to avoid the config admin pushing a configuration before ending the method.
* Propagate properties if the propagation is activated.
* @see org.apache.felix.ipojo.Handler#start()
*/
public synchronized void start() {
// Get the provided service handler :
m_providedServiceHandler = (ProvidedServiceHandler) getHandler(HandlerFactory.IPOJO_NAMESPACE + ":provides");
// Propagation
if (m_mustPropagate) {
for (int i = 0; i < m_configurableProperties.size(); i++) {
Property prop = (Property) m_configurableProperties.get(i);
if (prop.getValue() != Property.NO_VALUE && prop.getValue() != null) { // No injected value, or null
m_toPropagate.put(prop.getName(), prop.getValue());
}
}
reconfigure(m_toPropagate);
}
if (m_managedServicePID != null && m_sr == null) {
Properties props = new Properties();
props.put(Constants.SERVICE_PID, m_managedServicePID);
props.put("instance.name", getInstanceManager().getInstanceName());
props.put("factory.name", getInstanceManager().getFactory().getFactoryName());
m_sr = getInstanceManager().getContext().registerService(ManagedService.class.getName(), this, props);
}
}
// /**
// * Handler state changed.
// * @param state : the new instance state.
// * @see org.apache.felix.ipojo.CompositeHandler#stateChanged(int)
// */
// public void stateChanged(int state) {
// if (state == InstanceManager.VALID) {
// start();
// return;
// }
// if (state == InstanceManager.INVALID) {
// stop();
// return;
// }
// }
/**
* Adds the given property metadata to the property metadata list.
*
* @param prop : property metadata to add
*/
protected void addProperty(Property prop) {
m_configurableProperties.add(prop);
}
/**
* Checks if the list contains the property.
*
* @param name : name of the property
* @return true if the property exist in the list
*/
protected boolean containsProperty(String name) {
for (int i = 0; i < m_configurableProperties.size(); i++) {
if (((Property) m_configurableProperties.get(i)).getName().equals(name)) { return true; }
}
return false;
}
/**
* Reconfigure the component instance.
* Check if the new configuration modify the current configuration.
* @param configuration : the new configuration
* @see org.apache.felix.ipojo.Handler#reconfigure(java.util.Dictionary)
*/
public synchronized void reconfigure(Dictionary configuration) {
info(getInstanceManager().getInstanceName() + " is reconfiguring the properties : " + configuration);
Properties props = reconfigureProperties(configuration);
propagate(props, m_propagatedFromInstance);
m_propagatedFromInstance = props;
}
/**
* Reconfigured configuration properties and returns non matching properties.
* When called, it must hold the monitor lock.
* @param configuration : new configuration
* @return the properties that does not match with configuration properties
*/
private Properties reconfigureProperties(Dictionary configuration) {
Properties toPropagate = new Properties();
Enumeration keysEnumeration = configuration.keys();
while (keysEnumeration.hasMoreElements()) {
String name = (String) keysEnumeration.nextElement();
Object value = configuration.get(name);
boolean found = false;
// Check if the name is a configurable property
for (int i = 0; i < m_configurableProperties.size(); i++) {
Property prop = (Property) m_configurableProperties.get(i);
if (prop.getName().equals(name)) {
reconfigureProperty(prop, value);
found = true;
break; // Exit the search loop
}
}
if (!found) {
// The property is not a configurable property, aadd it to the toPropagate list.
toPropagate.put(name, value);
}
}
return toPropagate;
}
/**
* Reconfigures the given property with the given value.
* This methods handles {@link InstanceManager#onSet(Object, String, Object)}
* call and the callback invocation.
* The reconfiguration occurs only if the value changes.
* @param prop the property object to reconfigure
* @param value the new value.
*/
private void reconfigureProperty(Property prop, Object value) {
if (prop.getValue() == null || ! prop.getValue().equals(value)) {
prop.setValue(value);
if (prop.hasField()) {
getInstanceManager().onSet(null, prop.getField(), prop.getValue()); // Notify other handler of the field value change.
}
if (prop.hasMethod()) {
if (getInstanceManager().getPojoObjects() != null) {
prop.invoke(null); // Call on all created pojo objects.
}
}
}
}
/**
* Removes the old properties from the provided services and propagate new properties.
* @param newProps : new properties to propagate
* @param oldProps : old properties to remove
*/
private void propagate(Dictionary newProps, Dictionary oldProps) {
if (m_mustPropagate && m_providedServiceHandler != null) {
if (oldProps != null) {
m_providedServiceHandler.removeProperties(oldProps);
}
if (newProps != null) {
// Remove the name, the pid and the managed service pid props
newProps.remove("name");
newProps.remove("managed.service.pid");
newProps.remove(Constants.SERVICE_PID);
// Propagation of the properties to service registrations :
m_providedServiceHandler.addProperties(newProps);
}
}
}
/**
* Handler createInstance method.
* This method is override to allow delayed callback invocation.
* @param instance : the created object
* @see org.apache.felix.ipojo.Handler#onCreation(java.lang.Object)
*/
public void onCreation(Object instance) {
for (int i = 0; i < m_configurableProperties.size(); i++) {
Property prop = (Property) m_configurableProperties.get(i);
if (prop.hasMethod()) {
prop.invoke(instance);
}
}
}
/**
* Managed Service method.
* This method is called when the instance is reconfigured by the ConfigurationAdmin.
* When called, it must hold the monitor lock.
* @param conf : pushed configuration.
* @throws org.osgi.service.cm.ConfigurationException the reconfiguration failed.
* @see org.osgi.service.cm.ManagedService#updated(java.util.Dictionary)
*/
public synchronized void updated(Dictionary conf) throws org.osgi.service.cm.ConfigurationException {
if (conf == null && ! m_configurationAlreadyPushed) {
return; // First call
} else if (conf != null) { // Configuration push
Properties props = reconfigureProperties(conf);
propagate(props, m_propagatedFromCA);
m_propagatedFromCA = props;
m_configurationAlreadyPushed = true;
} else if (m_configurationAlreadyPushed) { // Configuration deletion
propagate(null, m_propagatedFromCA);
m_propagatedFromCA = null;
m_configurationAlreadyPushed = false;
}
}
}