/** * 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.impl; import java.util.ArrayList; import java.util.List; import fr.imag.adele.apam.Apam; import fr.imag.adele.apam.ApamManagers; import fr.imag.adele.apam.CST; import fr.imag.adele.apam.Component; import fr.imag.adele.apam.DynamicManager; import fr.imag.adele.apam.Instance; import fr.imag.adele.apam.Link; import fr.imag.adele.apam.PropertyManager; import fr.imag.adele.apam.RelToResolve; import fr.imag.adele.apam.RelationManager; import fr.imag.adele.apam.ResolutionException; import fr.imag.adele.apam.Resolved; import fr.imag.adele.apam.apform.Apform2Apam.PendingThread; import fr.imag.adele.apam.declarations.MissingPolicy; import fr.imag.adele.apam.declarations.RelationDeclaration; /** * This class handles failure when a dependency is missing and the resolution process fails * to find a solution. * * @author vega * */ public class FailedResolutionManager implements RelationManager, DynamicManager, PropertyManager { /** * The list of waiting resolutions */ private final List<PendingRequest> waitingResolutions; /** * A reference to the APAM machine */ private Apam apam; public FailedResolutionManager() { waitingResolutions = new ArrayList<PendingRequest>(); } @Override public String getName() { return "FailedResolutionManager"; } public Apam getApam() { return apam; } /** * Get the list of threads blocked waiting for a resolution */ public final List<? extends PendingThread> getWaitingThreads() { return getWaitingRequests(); } /** * A filter that can be used to request a resolution for a subset of the waiting requests * satisfying a condition. * * This may be useful to avoid retrying all the request when a condition is met, but only * the requests that are impacted by the event. */ public static interface Scope { public boolean concerns(PendingRequest request); } /** * Try to resolve all the waiting requests that are potentially satisfied by a given component. * * This may happen when a new component is created, or some property has changed that makes true * the constraints of a waiting request. * */ public void resolveWaitingRequests(Scope scope, Component candidate) { for (PendingRequest request : getWaitingRequests()) { if (scope.concerns(request) && request.isSatisfiedBy(candidate)) { request.resolve(); } } } private static final Scope GLOBAL = new Scope() { public boolean concerns(PendingRequest request) { return true; } }; public final void resolveWaitingRequests(Component candidate) { resolveWaitingRequests(GLOBAL, candidate); } /** * This is an INTERNAL manager that will be invoked by the core. * * So in this method we signal that we are not part of the external handlers to * invoke for this resolution request. * */ @Override public boolean beginResolving(RelToResolve relToResolve) { return false; } /** * If this manager is invoked it means that the specified relationship could not be resolved, so we have * to apply the specified failure policy. */ @Override public Resolved<?> resolve(RelToResolve relToResolve) { /* * Apply failure policies */ MissingPolicy policy = relToResolve.getMissingPolicy() != null ? relToResolve.getMissingPolicy() : MissingPolicy.OPTIONAL; switch (policy) { case OPTIONAL: { return null; } case EXCEPTION: { throwMissingException(relToResolve.getLinkSource(), relToResolve); } case WAIT: { PendingRequest request = new PendingRequest(CST.apamResolver, relToResolve.getLinkSource(), relToResolve.getRelationDefinition()); addWaitingRequest(request); request.block(); removeWaitingRequest(request); return request.getResolution(); } } return null; } @Override public void addedComponent(Component component) { resolveWaitingRequests(component); } @Override public void removedComponent(Component component) { for (PendingRequest request : getWaitingRequests()) { if (request.getSource().equals(component)) { request.dispose(); } } } private void propertyChanged(Component component, String property) { resolveWaitingRequests(component); } @Override public void attributeRemoved(Component component, String attr, String oldValue) { propertyChanged(component, attr); } @Override public void attributeAdded(Component component, String attr, String newValue) { propertyChanged(component, attr); } @Override public void attributeChanged(Component component, String attr, String newValue, String oldValue) { propertyChanged(component, attr); } @Override public void addedLink(Link link) { } @Override public void removedLink(Link link) { /* * If the target of the wire is a non sharable instance, the released * instance can potentially be used by a pending requests. */ if (link.getDestination() instanceof Instance) { Instance candidate = (Instance) link.getDestination(); if ((!candidate.isShared()) && candidate.isSharable()) { resolveWaitingRequests(candidate); } } } /** * This method is automatically invoked when the manager is validated, so we * can safely assume that APAM is available */ public synchronized void start(Apam apam) { this.apam = apam; ApamManagers.addDynamicManager(this); ApamManagers.addPropertyManager(this); } /** * This method is automatically invoked when the manager is invalidated, so * APAM is no longer available */ public synchronized void stop() { this.apam = null; ApamManagers.removeDynamicManager(this); ApamManagers.removePropertyManager(this); /* * Try to dispose all waiting requests */ for (PendingRequest request : getWaitingRequests()) { request.dispose(); removeWaitingRequest(request); } } /** * Add a new pending request in the waiting list */ private void addWaitingRequest(PendingRequest request) { synchronized (waitingResolutions) { waitingResolutions.add(request); } } /** * Remove a pending request from the waiting list */ private void removeWaitingRequest(PendingRequest request) { synchronized (waitingResolutions) { waitingResolutions.remove(request); } } /** * Get a thread-safe (stack confined) copy of the waiting requests */ private List<PendingRequest> getWaitingRequests() { synchronized (waitingResolutions) { return new ArrayList<PendingRequest>(waitingResolutions); } } /** * Hack to throw a checked exception from inside the framework without wrapping it. * * When the formal type parameter E is replaced by the actual type argument RuntimeException the erased * signature of this method seems to throw an unchecked exception, however we throw the original exception. * * Currently at runtime the specified cast is a NOOP, this may not work depending on the used JVM */ @SuppressWarnings("unchecked") private static <E extends Exception> void doThrow(Exception e) throws E { throw (E) e; } /** * Throws the exception associated with a missing relation */ private static void throwMissingException(Component source, RelToResolve relToResolve) { try { /* * If no exception is specified throw ResolutionException */ String exceptionName = relToResolve.getMissingException(); if (exceptionName == null) { throw new ResolutionException(); } /* * Try to find the component declaring the relation that specified the Exception * to throw * * TODO BUG : the class should be loaded using the bundle context of the component where the relation * is declared. This can be either the specification, or the implementation of the source component, * or a composite in the case of contextual dependencies. * * The current implementation may not handle every case. The best solution is to modify relationDeclaration * to load the exception class, but this is not possible at compile time, so we can not change the signature * of relationDeclaration.getMissingException. * * A possible solution is to move this method to relationDeclaration and make it work only at runtime, but we * need to consider merge of contextual dependencies and use the correct bundle context. * * Evaluate changes to relationDeclaration, relation, CoreMetadataParser and computeEffectiverelation */ Component declaringComponent = source; while (declaringComponent != null) { RelationDeclaration declaration = declaringComponent.getDeclaration().getRelation(relToResolve.getName()); if (declaration != null && declaration.getMissingException() != null && declaration.getMissingException().equals(exceptionName)) { break; } declaringComponent = declaringComponent.getGroup(); } if (declaringComponent == null && source instanceof Instance) { declaringComponent = ((Instance) source).getComposite().getCompType(); } if (declaringComponent == null) { throw new ResolutionException(); } /* * throw the specified exception */ Class<?> exceptionClass = declaringComponent.getApformComponent().getBundle().loadClass(exceptionName); Exception exception = Exception.class.cast(exceptionClass.newInstance()); FailedResolutionManager.<RuntimeException> doThrow(exception); } catch (ClassNotFoundException e) { throw new ResolutionException(e); } catch (InstantiationException e) { throw new ResolutionException(e); } catch (IllegalAccessException e) { throw new ResolutionException(e); } } }