/**
* Copyright 2011-2012 Universite Joseph Fourier, LIG, ADELE team
* 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 fr.imag.adele.apam.apform.impl.handlers;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Dictionary;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.apache.felix.ipojo.ConfigurationException;
import org.apache.felix.ipojo.FieldInterceptor;
import org.apache.felix.ipojo.architecture.ComponentTypeDescription;
import org.apache.felix.ipojo.architecture.HandlerDescription;
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 fr.imag.adele.apam.Component;
import fr.imag.adele.apam.Instance;
import fr.imag.adele.apam.apform.impl.ApamAtomicComponentFactory;
import fr.imag.adele.apam.apform.impl.ApamComponentFactory;
import fr.imag.adele.apam.apform.impl.ApamInstanceManager;
import fr.imag.adele.apam.apform.impl.PropertyCallback;
import fr.imag.adele.apam.declarations.AtomicImplementationDeclaration;
import fr.imag.adele.apam.declarations.ImplementationDeclaration;
import fr.imag.adele.apam.declarations.InjectedPropertyPolicy;
import fr.imag.adele.apam.declarations.PropertyDefinition;
import fr.imag.adele.apam.impl.InstanceImpl;
public class PropertyInjectionHandler extends ApformHandler implements FieldInterceptor {
/**
* The registered name of this iPojo handler
*/
public static final String NAME = "properties";
/**
* Whether this handler is required for the specified configuration
*/
public static boolean isRequired(AtomicImplementationDeclaration componentDeclaration) {
for (PropertyDefinition definition : componentDeclaration.getPropertyDefinitions()) {
if (definition.getField() != null)
return true;
if (definition.getCallback() != null)
return true;
}
return false;
}
@Override
public void initializeComponentFactory(ComponentTypeDescription typeDesc, Element metadata) throws ConfigurationException {
if (!(getFactory() instanceof ApamAtomicComponentFactory))
return;
ApamAtomicComponentFactory implementation = (ApamAtomicComponentFactory) getFactory();
ImplementationDeclaration declaration = implementation.getDeclaration();
if (!(declaration instanceof AtomicImplementationDeclaration))
return;
AtomicImplementationDeclaration primitive = (AtomicImplementationDeclaration) declaration;
for (PropertyDefinition definition : primitive.getPropertyDefinitions()) {
if (definition.getField() != null) {
FieldMetadata field = getPojoMetadata().getField(
definition.getField());
if (field == null)
throw new ConfigurationException("Invalid property definition " + definition.getName()+ ": the specified field does not exist");
}
if (definition.getCallback() != null) {
MethodMetadata method = getPojoMetadata().getMethod(definition.getCallback());
if (method == null)
throw new ConfigurationException("Invalid property definition " + definition.getName() + ": the specified method does not exist");
}
}
}
@Override
public void configure(Element metadata, @SuppressWarnings("rawtypes") Dictionary configuration) throws ConfigurationException {
/*
* Add interceptors to delegate property injection
*
* NOTE All validations were already performed when validating the
* factory @see initializeComponentFactory, including initializing
* unspecified properties with appropriate default values. Here we just
* assume metadata is correct.
*/
if (!(getFactory() instanceof ApamAtomicComponentFactory))
return;
ApamAtomicComponentFactory implementation = (ApamAtomicComponentFactory) getFactory();
ImplementationDeclaration declaration = implementation.getDeclaration();
if (!(declaration instanceof AtomicImplementationDeclaration))
return;
AtomicImplementationDeclaration primitive = (AtomicImplementationDeclaration) declaration;
for (PropertyDefinition definition : primitive.getPropertyDefinitions()) {
if (definition.getField() != null) {
FieldMetadata field = getPojoMetadata().getField(definition.getField());
getInstanceManager().register(field,this);
}
if (definition.getCallback() != null) {
getInstanceManager().addCallback(new PropertyCallback(getInstanceManager(), definition));
}
}
}
public void setApamComponent(Component component) {
if (component == null)
return;
ApamAtomicComponentFactory implementation = (ApamAtomicComponentFactory) getFactory();
ImplementationDeclaration declaration = implementation.getDeclaration();
if (!(declaration instanceof AtomicImplementationDeclaration))
return;
AtomicImplementationDeclaration primitive = (AtomicImplementationDeclaration) declaration;
for (PropertyDefinition definition : primitive.getPropertyDefinitions()) {
if (definition.getInjected() == InjectedPropertyPolicy.EXTERNAL)
continue;
if (definition.getField() != null) {
FieldMetadata field = getPojoMetadata().getField(definition.getField());
Object fieldValue = getInstanceManager().getFieldValue(field.getFieldName());
if (definition.getInjected() == InjectedPropertyPolicy.INTERNAL)
onSet(getInstanceManager().getPojoObject(), field.getFieldName(), fieldValue);
if (definition.getInjected() == InjectedPropertyPolicy.BOTH) {
Object apamValue = component.getPropertyObject(definition.getName());
if (apamValue == null)
onSet(getInstanceManager().getPojoObject(), field.getFieldName(), fieldValue);
}
}
}
}
@Override
public Object onGet(Object pojo, String fieldName, Object currentValue) {
if (getInstanceManager().getApamComponent() == null) {
return currentValue;
}
if (!(getFactory() instanceof ApamAtomicComponentFactory))
return currentValue;
ApamAtomicComponentFactory implementation = (ApamAtomicComponentFactory) getFactory();
ImplementationDeclaration declaration = implementation.getDeclaration();
if (! (declaration instanceof AtomicImplementationDeclaration))
return currentValue;
AtomicImplementationDeclaration primitive = (AtomicImplementationDeclaration) declaration;
for (PropertyDefinition definition : primitive.getPropertyDefinitions()) {
if (definition.getField() != null && definition.getField().equals(fieldName) ) {
String property = definition.getName();
Instance instance = getInstanceManager().getApamComponent();
/*
* For primitive property fields, always return the APAM value that is already of the
* correct type and is always synchronized
*/
if (! definition.isSet())
return instance.getPropertyObject(property);
/*
* For multi-valued property fields, convert the APAM value to a collection that can
* be injected into the field, and that track modifications
*/
if (definition.isSet() ) {
@SuppressWarnings("unchecked")
Set<String> newValue = (Set<String>) instance.getPropertyObject(property);
BoundSet<?> injectedValue = null;
if (definition.getBaseType().equals("int") && newValue != null) {
injectedValue = new BoundSet<Integer>(newValue,instance,definition) {
protected final Integer cast(String element) {
return Integer.valueOf(element);
}
protected final String uncast(Integer value) {
return value.toString();
}
};
}
else if (definition.getBaseType().equals("boolean") && newValue != null) {
injectedValue = new BoundSet<Boolean>(newValue,instance,definition) {
protected final Boolean cast(String element) {
return Boolean.valueOf(element);
}
protected final String uncast(Boolean value) {
return value.toString();
}
};
}
else if (newValue != null) {
injectedValue = new BoundSet<String>(newValue,instance,definition) {
protected final String cast(String element) {
return element;
}
protected final String uncast(String value) {
return value;
}
};
}
if (injectedValue == currentValue)
continue;
return injectedValue;
}
}
}
return currentValue;
}
@Override
public void onSet(Object pojo, String fieldName, Object newValue) {
if (getInstanceManager().getApamComponent() == null) {
return;
}
if (!(getFactory() instanceof ApamAtomicComponentFactory))
return;
ApamAtomicComponentFactory implementation = (ApamAtomicComponentFactory) getFactory();
ImplementationDeclaration declaration = implementation.getDeclaration();
if (! (declaration instanceof AtomicImplementationDeclaration))
return;
AtomicImplementationDeclaration primitive = (AtomicImplementationDeclaration) declaration;
for (PropertyDefinition definition : primitive.getPropertyDefinitions()) {
if (definition.getField() != null && definition.getField().equals(fieldName)) {
String property = definition.getName();
Instance instance = getInstanceManager().getApamComponent();
/*
* If the field has been modified, and the injection type is not EXTERNAL update the Apam value
*/
if (newValue != null && newValue instanceof BoundSet<?>)
newValue = ((BoundSet<?>) newValue).unwrap();
Object currentValue = instance.getPropertyObject(property);
if (newValue != null && currentValue != null && currentValue.equals(newValue))
continue;
if (newValue == null && currentValue == null)
continue;
if (definition.getInjected() != InjectedPropertyPolicy.EXTERNAL) {
if (newValue == null)
((InstanceImpl)instance).removeProperty(property,true);
if (newValue != null)
((InstanceImpl)instance).setProperty(property, newValue, true);
}
else {
System.out.println("ERROR modification of external property "+definition.getName()+" by field"+definition.getField());
}
}
}
return;
}
/**
* The description of this handler instance
*
*/
private static class Description extends HandlerDescription {
private final PropertyInjectionHandler propertyHandler;
public Description(PropertyInjectionHandler propertyHandler) {
super(propertyHandler);
this.propertyHandler = propertyHandler;
}
@Override
public Element getHandlerInfo() {
Element root = super.getHandlerInfo();
if (propertyHandler.getInstanceManager() instanceof ApamInstanceManager) {
ApamInstanceManager instance = (ApamInstanceManager) propertyHandler.getInstanceManager();
for (PropertyDefinition definition : instance.getFactory().getDeclaration().getPropertyDefinitions()) {
/*
* Ignore non injected properties
*/
if (definition.getField() == null && definition.getCallback() == null)
continue;
String name = definition.getName();
String field = definition.getField();
String method = definition.getCallback();
String value = instance.getApamComponent() != null ? instance.getApamComponent().getProperty(name) : null;
Element property = new Element("property", ApamComponentFactory.APAM_NAMESPACE);
property.addAttribute(new Attribute("name", ApamComponentFactory.APAM_NAMESPACE, name));
property.addAttribute(new Attribute("field", ApamComponentFactory.APAM_NAMESPACE, field != null ? field : ""));
property.addAttribute(new Attribute("method",ApamComponentFactory.APAM_NAMESPACE, method != null ? method : ""));
property.addAttribute(new Attribute("value", ApamComponentFactory.APAM_NAMESPACE, value != null ? value : ""));
root.addElement(property);
}
}
return root;
}
}
@Override
public HandlerDescription getDescription() {
return new Description(this);
}
@Override
public void start() {
}
@Override
public void stop() {
}
@Override
public String toString() {
return "APAM property manager for " + getInstanceManager().getInstanceName();
}
/**
* This class handles a typed set that is dynamically bound to a property of an APAM instance.
* Changes to the set are automatically propagated to the property
*/
private static abstract class BoundSet<E> implements Set<E> {
private final Instance instance;
private final PropertyDefinition property;
private final Set<String> backing;
public BoundSet(Set<String> backing, Instance instance, PropertyDefinition property) {
this.backing = backing;
this.instance = instance;
this.property = property;
}
private Set<String> unwrap() {
return backing;
}
/**
* Updates the associated property when the backing collection is changed
*/
private final void propagate() {
((InstanceImpl)instance).setProperty(property.getName(),this.unwrap(),true);
}
/**
* Verifies that non-internal properties can only be modified through the APAM API
*/
private final void checkModifiable() throws UnsupportedOperationException {
if (property.getInjected()!=InjectedPropertyPolicy.INTERNAL)
throw new UnsupportedOperationException("Field "+ property.getField()+
" is associated to non-internal property "+property.getName()+
" of component "+property.getComponent().getName()+
" and its value can not be modified");
}
/**
* Converts the string element of the backing collection to the type of the view
*/
protected abstract E cast(String element);
/**
* Converts a value of the type of the view to a string that can be stored in the
* backing collection
*/
protected abstract String uncast(E value);
/*
* The Object contract
*/
public boolean equals(Object o) {
return o == this || backing.equals(o);
}
public int hashCode() {
return backing.hashCode();
}
public String toString() {
return backing.toString();
}
/*
* The Collection contract
*
*/
public int size() {
return backing.size();
}
public boolean isEmpty() {
return backing.isEmpty();
}
@SuppressWarnings("unchecked")
public boolean contains(Object o) {
/*
* WARNING This implementation restricts the parameter to be a value of the type of
* the view. This is more restrictive that the contract of this method, but is needed
* in order to ensure the automatic conversion.
*/
return backing.contains(uncast((E)o));
}
@Override
@SuppressWarnings("unchecked")
public boolean containsAll(Collection<?> collection) {
/*
* WARNING This implementation restricts the parameter to be a value of the type of
* the view. This is more restrictive that the contract of this method, but is needed
* in order to ensure the automatic conversion.
*/
List<String> elements = new ArrayList<String>();
for(Object element : collection) {
elements.add(uncast((E)element));
}
return backing.containsAll(elements);
}
@Override
public Iterator<E> iterator() {
return new Iterator<E>() {
private final Iterator<String> backing = BoundSet.this.backing.iterator();
public boolean hasNext() {
return backing.hasNext();
}
public E next() {
return BoundSet.this.cast(backing.next());
}
public void remove() {
BoundSet.this.checkModifiable();
backing.remove();
BoundSet.this.propagate();
}
};
}
@Override
public Object[] toArray() {
List<E> elements = new ArrayList<E>();
for(String element : backing) {
elements.add(cast(element));
}
return elements.toArray();
}
public <T extends Object> T[] toArray(T[] a) {
List<E> elements = new ArrayList<E>();
for(String element : backing) {
elements.add(cast(element));
}
return elements.toArray(a);
}
@Override
public boolean add(E e) {
checkModifiable();
boolean added = backing.add(uncast(e));
if (added)
propagate();
return added;
}
@Override
public boolean addAll(Collection<? extends E> collection) {
List<String> elements = new ArrayList<String>();
for(E element : collection) {
elements.add(uncast(element));
}
checkModifiable();
boolean added = backing.addAll(elements);
if (added)
propagate();
return added;
}
@Override
@SuppressWarnings("unchecked")
public boolean remove(Object o) {
/*
* WARNING This implementation restricts the parameter to be a value of the type of
* the view. This is more restrictive that the contract of this method, but is needed
* in order to ensure the automatic conversion.
*/
checkModifiable();
boolean removed = backing.remove(uncast((E)o));
if (removed)
propagate();
return removed;
}
@Override
@SuppressWarnings("unchecked")
public boolean removeAll(Collection<?> collection) {
/*
* WARNING This implementation restricts the parameter to be a value of the type of
* the view. This is more restrictive that the contract of this method, but is needed
* in order to ensure the automatic conversion.
*/
List<String> elements = new ArrayList<String>();
for(Object element : collection) {
elements.add(uncast((E)element));
}
checkModifiable();
boolean removed = backing.removeAll(elements);
if (removed)
propagate();
return removed;
}
@Override
@SuppressWarnings("unchecked")
public boolean retainAll(Collection<?> collection) {
/*
* WARNING This implementation restricts the parameter to be a value of the type of
* the view. This is more restrictive that the contract of this method, but is needed
* in order to ensure the automatic conversion.
*/
List<String> elements = new ArrayList<String>();
for(Object element : collection) {
elements.add(uncast((E)element));
}
checkModifiable();
boolean removed = backing.retainAll(elements);
if (removed)
propagate();
return removed;
}
@Override
public void clear() {
checkModifiable();
backing.clear();
propagate();
}
}
}