/** * 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.obrMan.internal; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import fr.imag.adele.apam.CST; import fr.imag.adele.apam.Component; import fr.imag.adele.apam.CompositeType; import fr.imag.adele.apam.DeploymentManager; import fr.imag.adele.apam.Implementation; import fr.imag.adele.apam.RelToResolve; import fr.imag.adele.apam.Resolved; import fr.imag.adele.apam.declarations.ComponentKind; import fr.imag.adele.apam.declarations.references.components.ComponentReference; import fr.imag.adele.apam.declarations.references.resources.InterfaceReference; import fr.imag.adele.apam.declarations.references.resources.MessageReference; import fr.imag.adele.apam.declarations.references.resources.ResourceReference; import fr.imag.adele.apam.util.ApamFilter; /** * This manager handles OBR request for a specific composite. * * Resolution in this context is based on repositories specified in the OBR manager * model associated with the context in APAM * * * @author vega * */ public class OBRManager { /** * The context this manager handles */ private final CompositeType context; /** * The global obr manager */ private final OBRMan obrMan; /** * The model associated with this composite */ private final Model model; public OBRManager(OBRMan obrman, CompositeType context, Model model) { this.obrMan = obrman; this.context = context; this.model = model; } public Model getModel() { return model; } public String getName() { return context.getName(); } public Resolved<?> resolve(RelToResolve relation) { /* * Try to optimize the case of finding a component by name, without constraints. * */ ComponentReference<?> reference = relation.getTarget().as(ComponentReference.class); if (reference != null && reference.getKind() == relation.getTargetKind()) { DeployableComponent component = getComponent(reference); return component != null ? new Resolved<Component>(component.install(this)) : null; } /* * This is the case of relation resolution, based on searching for components providing * the specified resources */ Set<DeployableComponent> candidates = getProviders(relation); if (candidates.isEmpty()) return null; /* * When the target kind is INSTANCE we must consider a special case, as the calculated candidate * providers are implementations. * * We must check if there are deployable instances of these implementations declared in the * repository, as these are the real candidates. * */ boolean instantiableCandidates = (relation.getTargetKind() == ComponentKind.INSTANCE); if (instantiableCandidates) { /* * Select a single revision of each implementation * * TODO Because the revision is based uniquely based on the version number, it is not possible to use preferences * to select the preferred version. A better alternative would be to use preferences as a ranking, and use version * number to break ties. */ selectRevisions(candidates); StringBuffer providerInstancesQuery = new StringBuffer(); providerInstancesQuery.append("(|"); for (DeployableComponent candidate : candidates) { if (candidate.getReference().getKind() == ComponentKind.IMPLEMENTATION) providerInstancesQuery.append("(").append(CST.IMPLNAME).append("=").append(candidate.getReference().getName()).append(")"); } providerInstancesQuery.append(")"); ApamFilter query = ApamFilter.newInstance(providerInstancesQuery.toString()); /* * TODO We can not be sure that the candidate instance corresponds to the selected revision of the implementation. We * are just assuming that instances can be created without considering versions, this may not be true if the implementation * declaration has changed in a not backward compatible way */ Set<DeployableComponent> providerInstances = new HashSet<DeployableComponent>(); for (DeployableComponent component : obrMan.getComponents(this)) { if (component.getReference().getKind() == ComponentKind.INSTANCE && component.satisfies(query) && component.satisfies(relation)) providerInstances.add(component); } if (! providerInstances.isEmpty()) { candidates = providerInstances; instantiableCandidates = false; } } /* * Select a single revision of each candidate * * TODO Because the revision is based uniquely based on the version number, it is not possible to use preferences * to select the preferred version. A better alternative would be to use preferences as a ranking, and use version * number to break ties. */ selectRevisions(candidates); /* * Select best candidate according to preferences * * TODO currently if the target kind is INSTANCE and there are both implementation and instance preferences, only one * set is evaluated. A better alternative would be to use both criteria for ranking. */ if (relation.hasPreferences() && !relation.isMultiple()) { int bestRanking = 0; DeployableComponent best = null; for (DeployableComponent candidate : candidates) { int ranking = candidate.ranking(relation); if (ranking >= bestRanking) { best = candidate; bestRanking = ranking; } } candidates = Collections.singleton(best); } /* * Deploy and install candidates * * TODO currently we only install a single candidate, irrespective of the multiplicity of the relationship. * This is a trade-off between comprehensiveness and laziness (in the worst case scenario, when there is no * constraints, we would install all possible providers) we should have an explicit strategy. */ Component selected = candidates.iterator().next().install(this); return !instantiableCandidates ? new Resolved<Component>(selected) : new Resolved<Implementation>((Implementation)selected,true); } /** * Look for a specific component in the component repository. * * Notice that a component is searched by the specified name and kind of the reference. However, * several versions may exist , in this case we choose the latest version. * * TODO Should we consider preferences when selecting the best revision ? A possible implementation * would be to order according to preferences, and use version number to break ties. * */ private DeployableComponent getComponent(ComponentReference<?> searched) { boolean ignoreKind = searched.getKind() == ComponentKind.COMPONENT; DeployableComponent result = null; for (DeployableComponent component : obrMan.getComponents(this)) { if ( (component.getReference().equals(searched)) && (ignoreKind || component.getReference().getKind() == searched.getKind()) ) { result = (result == null || component.isPreferedVersionThan(result)) ? component : result; } } return result; } /** * Look for components that provide the resource specified in the relation * */ private Set<DeployableComponent> getProviders(RelToResolve relation) { /* * Depending on the kind requested resource, we calculate the filter that must be satisfied * by the providing components. * * For java resources and specifications the metadata contains all the information to find the * provider components. For specific component references we search by name. */ String requirementQuery = null; ResourceReference requestedResource = relation.getTarget().as(ResourceReference.class); if (requestedResource != null) { if (requestedResource instanceof InterfaceReference) { requirementQuery = "("+CST.PROVIDE_INTERFACES+"*>" + requestedResource.getJavaType() + ")"; } else if (requestedResource instanceof MessageReference) { requirementQuery = "("+CST.PROVIDE_MESSAGES+"*>" + requestedResource.getJavaType() + ")"; } } ComponentReference<?> requestedComponent = relation.getTarget().as(ComponentReference.class); if (requestedComponent != null) { if (requestedComponent.getKind() == ComponentKind.SPECIFICATION) { requirementQuery = "("+CST.PROVIDE_SPECIFICATION+"*>" + requestedComponent.getName() + ")"; } else if (requestedComponent.getKind() == ComponentKind.IMPLEMENTATION) { requirementQuery = "("+CST.IMPLNAME+ "=" + requestedComponent.getName() + ")"; } else if (requestedComponent.getKind() == ComponentKind.COMPONENT) { requirementQuery ="("+CST.NAME+ "=" + requestedComponent.getName() + ")"; } } if (requirementQuery == null) return Collections.emptySet(); /* * We need to do an special case when the target kind is INSTANCE, as the metadata of instances doesn't contain * the provided resources, so we search for corresponding implementations. * * The caller should decide if it look for instances in the repository or instantiate the found implementations. */ ComponentKind searchedKind = relation.getTargetKind(); if (searchedKind == ComponentKind.INSTANCE) searchedKind = ComponentKind.IMPLEMENTATION; ApamFilter requirement = ApamFilter.newInstance(requirementQuery); Set<DeployableComponent> candidates = new HashSet<DeployableComponent>(); for (DeployableComponent component : obrMan.getComponents(this)) { /* * Ignore all components that do not provide the required resource */ if (component.getReference().getKind() != searchedKind || ! component.satisfies(requirement)) continue; /* * Evaluate constraints specified in the relationship */ if (component.satisfies(relation)) candidates.add(component); } /* * keep a single version of each candidate */ return candidates; } /** * Verify if this manager deployed the specified component, and return a newer version that can be used * to update it. * */ public DeploymentManager.Unit getDeploymentUnit(Component deployed) { DeployableComponent result = null; for (DeployableComponent component : obrMan.getComponents(this)) { if (component.isRepositoryVersionOf(deployed)) { result = (result == null || component.isPreferedVersionThan(result)) ? component : result; } } return result != null ? new DeploymentUnit(this,result,deployed) : null; } /** * This represents a component that has been deployed in the system by obrman, and that can be * updated from the repository */ private static class DeploymentUnit implements DeploymentManager.Unit { private final Component component; private final DeployableComponent repositoryVersion; private final OBRManager context; public DeploymentUnit(OBRManager context, DeployableComponent repositoryVersion, Component component) { this.context = context; this.component = component; this.repositoryVersion = repositoryVersion; } @Override public Set<String> getComponents() { return repositoryVersion.getDeploymentUnitComponents(); } @Override public void update() throws Exception { repositoryVersion.update(context,component); } } /** * Utility method to keep a single revision of each component in the given set. * */ private static void selectRevisions(Set<DeployableComponent> components) { Map<ComponentReference<?>,DeployableComponent> retained = new HashMap<ComponentReference<?>,DeployableComponent>(); for (DeployableComponent component : components) { DeployableComponent best = retained.get(component.getReference()); if (best == null || component.isPreferedVersionThan(best)) retained.put(component.getReference(),component); } components.retainAll(retained.values()); } }