/** * 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.io.IOException; import java.net.URI; import java.net.URL; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; import java.util.concurrent.ConcurrentSkipListMap; import org.apache.felix.bundlerepository.Capability; import org.apache.felix.bundlerepository.Repository; import org.apache.felix.bundlerepository.RepositoryAdmin; import org.apache.felix.bundlerepository.Requirement; import org.apache.felix.bundlerepository.Resolver; import org.apache.felix.bundlerepository.Resource; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; 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.CompositeType; import fr.imag.adele.apam.ContextualManager; import fr.imag.adele.apam.DeploymentManager; import fr.imag.adele.apam.Implementation; import fr.imag.adele.apam.Instance; import fr.imag.adele.apam.RelToResolve; import fr.imag.adele.apam.RelationManager; import fr.imag.adele.apam.Resolved; import fr.imag.adele.apam.impl.CompositeTypeImpl; import fr.imag.adele.obrMan.OBRManCommand; /** * This manager handles automatic component installation from a bundle repository. This allows * incremental installation of components as they are requested by service clients. * * This is a contextual manager, a model can be associated with each composite type context to * configure the repositories used to look for components. * * This is the main entry point that extends the APAM core, it dispatches to appropriate managers * depending on the context of the source component of the request. * * This class handles a pool of bundle repositories that is shared by all contextual managers, * this reduces the memory footprint in the usual case that the same repository is configured * for several composite types. * * @author vega * */ public class OBRMan implements ContextualManager, DeploymentManager, RelationManager, OBRManCommand { private final Logger logger = LoggerFactory.getLogger(OBRMan.class); /** * An injected reference to the APAM core, used to ensure proper order */ private Apam apam; /** * An injected reference to the OSGi Bundle Repository administrator */ private RepositoryAdmin repoAdmin; /** * The context of this bundle */ private final BundleContext m_context; /** * The list of OBR managers associated with each composite context */ private final Map<CompositeType, OBRManager> obrManagers; /** * This class represents an APAM component repository associated with a bundle repository. * * The repository handles a read-only view on the APAM components declared in the bundle * repository */ private static class ComponentRepository { private final Repository repository; private final Set<DeployableComponent> components; public ComponentRepository(OBRMan manager, Repository repository) { this.repository = repository; this.components = new HashSet<DeployableComponent>(); for (Resource resource : repository.getResources()) { for (Capability capability : resource.getCapabilities()) { if (capability.getName().equals(CST.CAPABILITY_COMPONENT)) components.add(new DeployableComponent(repository,manager,resource,capability)); } } } public Repository getBundleRepository() { return repository; } public Set<DeployableComponent> getComponents() { return components; } } /** * The pool of all APAM components repositories, shared by all contexts */ private final Map<URI,ComponentRepository> repositories; /** * Updates the in-memory repositories associated with the given contextual managers, and * the metadata associated with APAM components in that context */ private void update(Set<OBRManager> contexts) { Set<URI> loadedBundleRepositories = new HashSet<URI>(); for (OBRManager context : contexts) { for (URI repositoryLocation : context.getModel().getRepositoryLocations()) { try { /* * just avoid loading the same repository several times */ if (loadedBundleRepositories.contains(repositoryLocation)) { continue; } /* * load repository in memory and extract APAM component metadata */ Repository bundleRepository = repoAdmin.getHelper().repository(repositoryLocation.toURL()); ComponentRepository componentRepository = new ComponentRepository(this,bundleRepository); loadedBundleRepositories.add(repositoryLocation); repositories.put(repositoryLocation,componentRepository); } catch (Exception e) { logger.error("Composite "+context.getName(),"Error when loading repository :" + repositoryLocation, e); } } } } /** * This class provides a filtered read-only view of the APAM component repositories visible * in a given context. * * The view show only the components that are specified as part of the model associated with * the composite context. * */ private static class ContextualIterator implements Iterator<DeployableComponent> { /** * Iterator over the visible repositories in the context */ private final Iterator<ComponentRepository> repositories; /** * The currently iterated component repository */ private Iterator<DeployableComponent> repository; private ContextualIterator(OBRMan manager, OBRManager context) { Set<ComponentRepository> selectedRepositories = new HashSet<ComponentRepository>(); for (URI location : context.getModel().getRepositoryLocations()) { ComponentRepository repository = manager.repositories.get(location); if (repository != null) selectedRepositories.add(repository); } this.repositories = selectedRepositories.iterator(); this.repository = null; } /** * Updates the reference to the next repository in the iteration */ private void changeRepositoryIfNeeded() { while ((repository == null || !repository.hasNext()) && repositories.hasNext()) { repository = repositories.next().getComponents().iterator(); } } @Override public boolean hasNext() { changeRepositoryIfNeeded(); return repository != null && repository.hasNext(); } @Override public DeployableComponent next() { changeRepositoryIfNeeded(); if (repository == null) throw new NoSuchElementException(); return repository.next(); } @Override public void remove() { throw new UnsupportedOperationException("component repository is read-only"); } } /** * Initialize OBR Man */ public OBRMan(BundleContext context) { m_context = context; repositories = new ConcurrentSkipListMap<URI,ComponentRepository>(); obrManagers = new HashMap<CompositeType, OBRManager>(); } @Override public String getName() { return "OBRMAN"; } /** * Register with APAM on start up */ public void start() { ApamManagers.addRelationManager(this,Priority.HIGH); } /** * Unregister from APAM on stop */ public void stop() { ApamManagers.removeRelationManager(this); obrManagers.clear(); } /** * Give access to the apam instance */ public Apam getApam() { return apam; } /** * Initializes a new context from its specified model */ @Override public synchronized void initializeContext(CompositeType compositeType) { Model model = Model.loadModel(this,compositeType,m_context); /* * If no model is specified for this context, use the model of the root composite * context. This ensures that there is always a manager created for every context. * * If no model is specified for the root composite, then initialize a default model. * * NOTE: a manager can be assured that the root composite context is initialized by * the APAM core before any other context. */ if (model == null) { if (compositeType.equals(CompositeTypeImpl.getRootCompositeType())) { model = Model.loadDefaultRootModel(this,m_context); } else { OBRManager rootManager = obrManagers.get(CompositeTypeImpl.getRootCompositeType()); model = rootManager.getModel(); } } OBRManager contextualManager = new OBRManager(this, compositeType, model); obrManagers.put(compositeType,contextualManager); update(Collections.singleton(contextualManager)); } /** * The model associated with a given context */ public Model getModel(CompositeType context) { return obrManagers.get(context).getModel(); } /** * Get the components available in the specified context */ public Iterable<DeployableComponent> getComponents(final OBRManager context) { return new Iterable<DeployableComponent>() { public Iterator<DeployableComponent> iterator() { return new ContextualIterator(OBRMan.this,context); } }; } /** * Creates a query that can be used to filter components in the repository * associated with the given context */ public Requirement parseRequirement(OBRManager context, String requirement) { return repoAdmin.getHelper().requirement(CST.CAPABILITY_COMPONENT,requirement); } /** * Creates a resolver that can be used to install components from the repository * associated with the given context */ public Resolver getResolver(OBRManager context) { List<Repository> scope = new ArrayList<Repository>(); for (URI repositoryLocation : context.getModel().getRepositoryLocations()) { ComponentRepository repository = repositories.get(repositoryLocation); if (repository != null) scope.add(repository.getBundleRepository()); } scope.add(0,repoAdmin.getLocalRepository()); scope.add(0,repoAdmin.getSystemRepository()); return repoAdmin.resolver(scope.toArray(new Repository[scope.size()])); } /** * Search a deployed version of the specified resource in the running platform */ public Bundle getBundle(Resource resource) { String bundleName = resource.getSymbolicName(); for (Bundle bundle : m_context.getBundles()) { if (bundle.getSymbolicName() != null && bundle.getSymbolicName().equals(bundleName)) { return bundle; } } return null; } /** * This external manager is actively involved in resolution */ @Override public boolean beginResolving(RelToResolve dep) { return true; } /** * Perform resolution */ @Override public Resolved<?> resolve(RelToResolve relation) { /* * Find the context in which the resolution must be performed. * * This has some subtle corner cases as this method is used for two very different * purposes : * * 1) resolving a relationship : in this case the context is determined by the source * of the relation, but there are several cases depending on its kind. * * 2) finding a component by name : in this case either the specified source is an * instance (and the component must be visible in its enclosing composite type), or * directly the composite type that must be used as context. * */ Component source = relation.getLinkSource(); CompositeType context = null; if (relation.isRelation()) { switch (relation.getRelationDefinition().getSourceKind()) { case INSTANCE: context = ((Instance) source).getComposite().getCompType(); break; case IMPLEMENTATION: context = ((Implementation) source).getFirstDeployed(); break; case SPECIFICATION: context = CompositeTypeImpl.getRootCompositeType(); break; default: break; } } else { if (source instanceof Instance) { context = ((Instance) source).getComposite().getCompType(); } if (source instanceof CompositeType) { context = (CompositeType) source; } } if (context == null) { logger.error("OBR: No context found for resolution " + relation); return null; } /* * Resolve in the appropriate context */ return obrManagers.get(context).resolve(relation); } /** * Get the deployment unit associated with a bundle deployed by this manager. */ @Override public DeploymentManager.Unit getDeploymentUnit(CompositeType context, Implementation component) { Bundle bundle = component.getApformComponent().getBundle(); String componentName = component.getName(); if (bundle.getSymbolicName() == null || componentName == null) { return null; } // Find the composite OBRManager return obrManagers.get(context).getDeploymentUnit(component); } @Override public synchronized void setInitialConfig(URL modelLocation) throws IOException { CompositeType compositeType = CompositeTypeImpl.getRootCompositeType(); Model model = Model.loadRootModel(this,m_context,modelLocation); obrManagers.put(compositeType, new OBRManager(this, compositeType, model)); } @Override public Set<String> getCompositeRepositories(String compositeName) { Set<String> result = new HashSet<String>(); CompositeType context = !compositeName.equals("root") ? apam.getCompositeType(compositeName) : CompositeTypeImpl.getRootCompositeType(); if (context == null) return result; return obrManagers.get(context).getModel().getRepositories(); } /** * Update resources from repositories * * @param compositeName * the name of the composite to update or * */ @Override public boolean updateRepos(String compositeName) { if (compositeName == null) { return false; } /* * refresh all */ if (compositeName.equals("*")) { update(new HashSet<OBRManager>(obrManagers.values())); return true; } /* * refresh the specified manager */ CompositeType context = !compositeName.equals("root") ? apam.getCompositeType(compositeName) : CompositeTypeImpl.getRootCompositeType(); if (context == null) return false; update(Collections.singleton(obrManagers.get(context))); return true; } }