/** * 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.Arrays; import java.util.Dictionary; import java.util.HashMap; import java.util.HashSet; import java.util.Hashtable; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ArrayBlockingQueue; import org.apache.felix.ipojo.ConfigurationException; import org.apache.felix.ipojo.Handler; import org.apache.felix.ipojo.metadata.Attribute; import org.apache.felix.ipojo.metadata.Element; import org.apache.felix.ipojo.parser.MethodMetadata; import org.apache.felix.ipojo.util.Callback; import org.osgi.framework.ServiceRegistration; import org.osgi.service.wireadmin.Consumer; import org.osgi.service.wireadmin.Wire; import org.osgi.service.wireadmin.WireAdmin; import org.osgi.service.wireadmin.WireConstants; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import fr.imag.adele.apam.Component; import fr.imag.adele.apam.Instance; import fr.imag.adele.apam.apform.ApformInstance; import fr.imag.adele.apam.apform.impl.ApamComponentFactory; import fr.imag.adele.apam.apform.impl.ApamInstanceManager; import fr.imag.adele.apam.declarations.RelationDeclaration; import fr.imag.adele.apam.declarations.RequirerInstrumentation; import fr.imag.adele.apam.declarations.references.resources.MessageReference; import fr.imag.adele.apam.message.Message; import fr.imag.adele.apam.util.ApAMQueue; /** * This handler handles message consumers. It is at the same time a OSGi's * WireAdmin consumer and an APAM message consumer, so that it translates * message exchanges over APAM wires into concrete message exchanges over * WireAdmin wires. * * This handler is also a field interceptor that injects a message queue * into fields to transparently consume messages. Fields must be declared of type * Queue<D>. * * This handler is also in charge of triggering lazy resolution, if data is * consumed and there is no producer bound to this relation. It also translates * APAM notifications for wire creation and deletion into appropriate actions at * the WireAdmin level. * * @author vega * */ public class MessageInjectionManager implements RelationInjectionManager, Consumer { Logger logger = LoggerFactory.getLogger(getClass()); /** * The registered name of this iPojo handler */ public static final String NAME = "consumer"; /** * The source component of the relation */ private final ApamComponentFactory component; /** * The relation injection handler */ private final RelationInjectionHandler handler; /** * The associated resolver */ private final ApamInstanceManager instance; /** * The relation that is going to be injected */ private final RelationDeclaration relation; /** * The relation injection managed by this relation */ private final RequirerInstrumentation injection; /** * The list of target services. */ private final Set<Instance> targetServices; /** * The WireAdmin consumer registration. A consumer is registered when the * relation is resolved and automatically unregistered when the relation * gets unresolved. * */ private ServiceRegistration<?> consumer; /** * The list of connected producers, indexed by producer identification */ private final Map<String,Wire> wires; /** * The maximum size of the buffer */ private final static int MAX_BUFFER_SIZE = 100; /** * The buffer of received messages that are waiting for being consumed */ private final ArrayBlockingQueue<Object> buffer; /** * In case of method callback, an object to allow direct invocation of the instance */ private final Callback callback; private final boolean isMessageCallback; /** * Represent the consumed flavors (Registration Property) */ private final Class<?>[] messageFlavors; /** * Represent the consumer persistent id */ private final String consumerId; /** * The field injected in the instance */ private ApAMQueue<?> fieldBuffer; public MessageInjectionManager(ApamComponentFactory component, ApamInstanceManager instance, RelationInjectionHandler handler, RelationDeclaration relation, RequirerInstrumentation injection) throws ConfigurationException { assert injection.getRequiredResource() instanceof MessageReference; this.component = component; this.instance = instance; this.handler = handler; this.relation = relation; this.injection = injection; if (injection instanceof RequirerInstrumentation.MessageConsumerCallback) { MethodMetadata callbackMetadata = null; String callbackParameterType = null; for (MethodMetadata method : this.component.getPojoMetadata().getMethods(injection.getName())) { if (method.getMethodArguments().length == 1) { callbackMetadata = method; callbackParameterType = method.getMethodArguments()[0]; } } this.callback = new Callback(callbackMetadata, instance); this.isMessageCallback = Message.class.getCanonicalName().equals(callbackParameterType); } else { this.callback = null; this.isMessageCallback = false; } this.targetServices = new HashSet<Instance>(); this.consumer = null; try { this.messageFlavors = new Class<?>[] {this.component.loadClass(injection.getRequiredResource().getJavaType())}; this.consumerId = NAME+"["+instance.getInstanceName()+","+relation.getIdentifier()+","+injection.getName()+"]"; this.wires = new HashMap<String,Wire>(); this.buffer = new ArrayBlockingQueue<Object>(MAX_BUFFER_SIZE); this.fieldBuffer = new ApAMQueue<Object>(buffer); } catch (ClassNotFoundException e) { throw new ConfigurationException(e.getLocalizedMessage()); } instance.addInjection(this); } /** * The apform instance associated with this manager */ ApformInstance getInstance() { return instance.getApform(); } /** * The relation injection associated to this manager */ @Override public RelationDeclaration getRelation() { return relation; } /** * The relation injection associated to this manager */ @Override public RequirerInstrumentation getRelationInjection() { return injection; } /** * Get an XML representation of the state of this relation */ @Override public Element getDescription() { Element consumerDescription = new Element("injection", ApamComponentFactory.APAM_NAMESPACE); consumerDescription.addAttribute(new Attribute("relation", relation.getIdentifier())); consumerDescription.addAttribute(new Attribute("target", relation.getTarget().toString())); consumerDescription.addAttribute(new Attribute("method", injection.getName())); consumerDescription.addAttribute(new Attribute("type", injection.getRequiredResource().toString())); consumerDescription.addAttribute(new Attribute("isAggregate", Boolean.toString(injection.acceptMultipleProviders()))); /* * show the current state of resolution. To avoid unnecessary synchronization overhead make a copy of the * current target services and do not use directly the field that can be concurrently modified */ List<Wire> resolutions = new ArrayList<Wire>(); synchronized (this) { resolutions.addAll(wires.values()); } consumerDescription.addAttribute(new Attribute("resolved",Boolean.toString(!resolutions.isEmpty()))); consumerDescription.addAttribute(new Attribute("consumer.id",consumerId)); consumerDescription.addAttribute(new Attribute("flavors",Arrays.toString(messageFlavors))); consumerDescription.addAttribute(new Attribute("buffered",Integer.toString(buffer.size()))); for (Wire wire : resolutions) { Element wireInfo = new Element("wire",ApamComponentFactory.APAM_NAMESPACE); wireInfo.addAttribute(new Attribute("producer.id",(String)wire.getProperties().get(WireConstants.WIREADMIN_PRODUCER_PID))); wireInfo.addAttribute(new Attribute("flavors",Arrays.toString(wire.getFlavors()))); consumerDescription.addElement(wireInfo); } return consumerDescription; } /** * Handle modification of the injected field */ @Override public void onSet(Object pojo, String fieldName, Object value) { // Nothing to do, this should never happen as we exclusively handle the field's value } /** * Handle access to the injected field */ @Override public Object onGet(Object pojo, String fieldName, Object value) { synchronized (this) { if (consumer != null) return fieldBuffer; } /* * Ask APAM to resolve the relation. Depending on the application * policies this may throw an error, or block the thread until the * relation is fulfilled, or keep the relation unresolved in the case of * optional dependencies. * * Resolution has as side-effect a modification of the target services. */ instance.resolve(this); return consumer !=null ? fieldBuffer : null; } /** * Get access to the wireadmin, via the relationInjectionHandler * */ private WireAdmin getWireAdmin() { return handler.getWireAdmin(); } /** * This implementation of message injection requires that the WireAdmin service be available on the platform */ @Override public boolean isValid() { WireAdmin wireAdmin = getWireAdmin(); if (wireAdmin == null) { System.err.println("The WireAdmin service must be available in the platform to handle message consumer injection \""+injection.toString()+"\""); } return wireAdmin != null; } /** * The consumer Id associated to this manager */ public String getConsumerId() { return consumerId; } /** * The message provider handler associated to the given target instance * * NOTE This performs an unchecked down cast, as it assumes the target instance is an Apform-iPojo provided * instance */ private static MessageProviderHandler getMessageProvider(Instance target) { return getHandler(target,ApamComponentFactory.APAM_NAMESPACE,MessageProviderHandler.NAME); } /** * Get the reference to an Apform handler associated to an instance * * NOTE This performs an unchecked down casts, as it assumes the calling client knows the exact class of the * requested handler */ @SuppressWarnings("unchecked") private static <T extends Handler> T getHandler(Instance component, String namespace, String handlerId) { String qualifiedHandlerId = namespace+":"+handlerId; return (T) ((ApamInstanceManager.Apform)component.getApformInst()).getManager().getHandler(qualifiedHandlerId); } /* * (non-Javadoc) * * @see * fr.imag.adele.apam.apform.impl.handlers.relationInjectionManager#addTarget * (fr.imag.adele.apam.Instance) */ @Override public void addTarget(Component target) { /* * Messages can only be exchanged between instances */ if (! (target instanceof Instance)) return; /* * Add this target and invalidate cache */ synchronized (this) { /* * Register the consumer on the first resolution */ if (targetServices.isEmpty()) { Dictionary<String,Object> properties = new Hashtable<String,Object>(); properties.put(WireConstants.WIREADMIN_CONSUMER_FLAVORS,messageFlavors); properties.put("service.pid",consumerId); consumer = handler.getHandlerManager().getContext().registerService(Consumer.class.getCanonicalName(), this, properties); } targetServices.add((Instance)target); /* * Create a wire at the WireAdmin level */ MessageProviderHandler producer = getMessageProvider((Instance)target); if (producer != null) { wires.put(target.getName(),producer.createWire(this)); } } } /* * (non-Javadoc) * * @see * fr.imag.adele.apam.apform.impl.handlers.relationInjectionManager#removeTarget * (fr.imag.adele.apam.Instance) */ @Override public void removeTarget(Component target) { synchronized (this) { /* * Delete the wire at the WireAdmin level */ MessageProviderHandler producer = getMessageProvider((Instance)target); if (producer != null) { producer.deleteWire(this); } /* * Remove this target and invalidate cache */ wires.remove(target.getName()); targetServices.remove(target); /* * Unregister the consumer if the relation becomes unresolved */ if (targetServices.isEmpty()) { consumer.unregister(); consumer = null; } } } /** * The APAM relation handler only manages wires created indirectly by mapping APAM resolution * into WireAdmin events. * * Those wires are already tracked by the provider handler, wer do not use notifications. */ @Override public void producersConnected(Wire[] newWires) { } /** * Consumes a message and put it in the buffer for later retrieval. * * NOTE This methods unsafely down cast the received value to a message, as we assume that * we only exchange messages with APAM producers. */ @Override @SuppressWarnings("unchecked") public void updated(Wire wire, Object value) { if (!(value instanceof Message)) return; Message<?> message = (Message<?>) value; message.markAsReceived(wire.getProperties()); if (isMessageCallback){ buffer.offer(message); }else { buffer.offer(message.getData()); } /* * If a callback is registered consume message directly and push it to the component */ if (callback != null) { try { Object consumed = buffer.poll(); if (consumed != null) { if (isMessageCallback) callback.call(new Object[] {(Message<?>)consumed}); else callback.call(new Object[] {consumed}); } } catch (Exception e) { System.err.println("error invoking callback "+e); e.printStackTrace(System.err); } } } }