Package fr.imag.adele.obrMan.internal

Source Code of fr.imag.adele.obrMan.internal.DeployableComponent

package fr.imag.adele.obrMan.internal;

import java.net.URI;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.felix.bundlerepository.Capability;
import org.apache.felix.bundlerepository.Reason;
import org.apache.felix.bundlerepository.Repository;
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.BundleException;
import org.osgi.framework.Version;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import fr.imag.adele.apam.CST;
import fr.imag.adele.apam.Component;
import fr.imag.adele.apam.RelToResolve;
import fr.imag.adele.apam.declarations.ComponentDeclaration;
import fr.imag.adele.apam.declarations.ImplementationDeclaration;
import fr.imag.adele.apam.declarations.references.components.ComponentReference;
import fr.imag.adele.apam.declarations.references.components.ImplementationReference;
import fr.imag.adele.apam.declarations.references.components.InstanceReference;
import fr.imag.adele.apam.declarations.references.components.SpecificationReference;
import fr.imag.adele.apam.util.ApamFilter;

/**
* This class represents an APAM component that can be deployed from a bundle repository,
* and searched by its associated metadata.
*/
public class DeployableComponent {

  private final static Logger     logger = LoggerFactory.getLogger(DeployableComponent.class);

  private final OBRMan         manager;

  private final String        repository;
 
  private final Resource         resource;
  private final Capability      metadata;
  private final ComponentReference<?> component;
 
  private final int           hashCode;



  public DeployableComponent(Repository repository, OBRMan manager, Resource resource, Capability capability) {
   
    assert capability.getName().equals(CST.CAPABILITY_COMPONENT);

    this.manager  = manager;
    this.resource   = resource;
   
    this.repository  = repository.getURI();
    this.metadata  = capability;

    this.component  = getComponent(metadata);
   
    this.hashCode  = this.resource.getURI().hashCode()^
              this.component.getName().hashCode();

  }
 
  /**
   * The component in this deployment unit
   */
  public ComponentReference<?> getReference() {
    return component;
  }

  /**
   * The list of components within the same deployment unit as this component.
   *
   * A deployable component can be packaged in the same deployment unit along with other components.
   * Installing or updating it then can have some side-effects on other components.
   */
  public Set<String> getDeploymentUnitComponents() {
    Set<String> resourceComponents = new HashSet<String>();
   
    for (Capability capability : resource.getCapabilities()) {
      if (capability.getName().equals(CST.CAPABILITY_COMPONENT)) {
        Object name = capability.getPropertiesAsMap().get(CST.NAME);
        if (name != null)
          resourceComponents.add(name.toString());
      }
    }
   
    return resourceComponents;
  }

  /**
   * Whether this component satisfies the specified requirement, the requirement must concern the
   * component metadata in the repository
   */
  public boolean satisfies(Requirement requirement) {
    assert requirement.getName().equals(CST.CAPABILITY_COMPONENT);
    return requirement.isSatisfied(metadata);
     
  }

  /**
   * Whether this component satisfies the specified filter, the filter must concern the component
   * metadata in the repository
   *
   * TODO Currently there seems to be a problem with repository requirements as superset and subset
   * operators used by APAM doesn't appear to work. Needs further investigation, just using APAM
   * filter directly by now
   *
   */
  public boolean satisfies(ApamFilter filter) {
    return filter.matchCase(metadata.getPropertiesAsMap());
  }


  /**
   * Whether this component satisfies requirement specified in the given relation.
   *
   * TODO NOTE Currently, because of filter substitutions,  we can not translate the APAM constraints
   * into repository requirements, and use a single search/resolution mechanism.
   *
   * An alternative implementation of substitution is to evaluate variables in the context of the
   * source when the RelToResolve object is created, and to translate the constraints and preferences
   * into normal ldap filters that can be used with standard OSGi services.
   */
  @SuppressWarnings("unchecked")
  public boolean satisfies(RelToResolve relation) {
    return relation.matchRelationConstraints(this.getReference().getKind(), (Map<String,Object>) metadata.getPropertiesAsMap());
  }
 
  /**
   * The ranking of this component as a candidate to satisfy the given relation
   */
  @SuppressWarnings("unchecked")
  public int ranking(RelToResolve relation) {
    return relation.ranking(this.getReference().getKind(), (Map<String,Object>) metadata.getPropertiesAsMap());
  }


  /**
   * The version of this component
   */
  public Version getVersion() {
    Object version = this.metadata.getPropertiesAsMap().get(Resource.VERSION);
    return (version != null) && (version instanceof Version) ? (Version) version : Version.emptyVersion;
  }

  /**
   * Whether the receiver represents a version of the specified component
   */
  public boolean isVersionOf(DeployableComponent that) {
    return this.component.equals(that.component);
  }

  /**
   * Whether the receiver represents a preferred version of the specified component
   *
   * The preferred version has the higher version number, and in case both deployable
   * units have the same version we prefer the resource providing more capabilities
   */
  public boolean isPreferedVersionThan(DeployableComponent that) {
   
    assert this.isVersionOf(that);
   
    int deltaVersion =  this.getVersion().compareTo(that.getVersion());
   
    return   deltaVersion > 0 ||
        (deltaVersion == 0 && this.resource.getCapabilities().length > that.resource.getCapabilities().length);
   
  }

  /**
   * Installs the component in the platform using the specified context to resolve requirements, and waits
   * until it is reified in APAM.
   *
   * Several cases must be considered as installations can proceed in parallel in several threads, we try
   * to avoid starting several deployment unnecessarily.
   */
  public Component install(OBRManager context) {

    /*
     * If the component exists, maybe arrived in the mean time, or from another bundle or in another version, do nothing
     */
    Component result = CST.componentBroker.getComponent(component.getName());
    if (result != null)
      return result;
   
    /*
     * Check if the bundle is already existing in the platform
     */
    Bundle installed = getBundle(resource);

    return  installed != null ? CST.componentBroker.getWaitComponent(component.getName(), 10*1000 /* milliseconds*/) : deploy(context);

  }

  /**
   * Get the installed bundle corresponding to the specified resource. Activates the bundle if necessary.
   */
  private final Bundle getBundle(Resource resource) {

    /*
     * Get the platform bundle with the same symbolic name, if any
     */
    Bundle bundle = manager.getBundle(resource);
   
    if (bundle == null) {
      return null;
    }

    /*
     * the bundle is installed but is not started : try to start it before checking version numbers to be sure that any updates are finished
     */
    if (bundle.getState() == Bundle.INSTALLED || bundle.getState() == Bundle.RESOLVED) {
      try {
        bundle.start();
        logger.info("The bundle " + bundle.getSymbolicName() + " is installed and has been started!");
      } catch (BundleException e) {
        logger.info("The bundle " + bundle.getSymbolicName() + " is installed but cannot be started!");
      }

    }

    /*
     * Verify the version, notice that there can be only a single version of a bundle in the platform, so if we find another installed version,
     * there may be conflicting updates in progress.
     *
     */
    boolean matchVersion   = bundle.getVersion().equals(resource.getVersion());
    boolean active      = (bundle.getState() == Bundle.ACTIVE || bundle.getState() == Bundle.STARTING);

    if (!matchVersion) {
      logger.error("Bundle " + resource.getSymbolicName() + " is already installed under version " + bundle.getVersion() + " while trying to deploy version " + resource.getVersion());
    }


   
    return active && matchVersion ? bundle : null;

  }

  /**
   * Deploy this resource on the platform and waits for the component to be reified in APAM.
   *
   * Return null if deployment is not possible.
   *
   */
  private Component deploy(OBRManager context) {
   
    Resolver resolver = manager.getResolver(context);

    /*
     * Try to perform requirement resolution and deployment. This may need to be retried several times
     * as other installations can be ongoing in parallel, and the resource requirement resolution must
     * be recalculated in the new resource context.
     */
    boolean deployed = false;
    boolean retrying = true;
   
    do {

      /*
       * calculate the transitive dependencies to satisfy requirements of the resource being deployed
       */
      resolver.add(this.resource);
      resolver.resolve();

      /*
       * If we can not resolve the resource requirements, just give up
       */
      if (resolver.getUnsatisfiedRequirements().length > 0) {
       
        retrying = false;
        deployed = false;
       
        continue;
      }

      /*
       * Perform the actual installation,
       */
      try {

        resolver.deploy(Resolver.START+Resolver.NO_OPTIONAL_RESOURCES);

        retrying = false;
        deployed = true;
       
        /*
         * Verify that all required resources were actually installed. A resource may not be installed if a
         * concurrent deployment has installed a conflicting version.
         *
         * In this case there is not much we can do, we just give up the deployment
         */
        List<Resource> deployedResources = new ArrayList<Resource>();
       
        deployedResources.addAll(Arrays.asList(resolver.getAddedResources()));
        deployedResources.addAll(Arrays.asList(resolver.getRequiredResources()));
       

        for (Resource deployedResource : deployedResources) {
          if (getBundle(deployedResource) == null)
            deployed = false;
        }

       
      }
     
      /*
       * Concurrent bundle deployment may interfere with installation and change the state of the local repository,
       * which produces an IllegalStateException of the resolver.
       *
       * We can recover by trying again the resolution and installation process, using the new local bundles.
       *
       */
      catch (IllegalStateException e) {
       
        logger.debug("OBR changed state. Resolving again " + this.resource.getSymbolicName());
       
        retrying = true;
        deployed = false;
      }

      /*
       * If we can not recover from the error, just give up
       *
       */
      catch (Exception e) {
       
        logger.error ("Deployment of " + component + " from "+resource+" failed.",e) ;
       
        retrying = false;
        deployed = false;
      }

    } while (retrying);

   
    /*
     * Log deployment result
     */

    logger.debug("Component: " + component+ (deployed ? " successfully " : " not ") + "deployed");
    logger.debug("  repository: " + repository);
   
    List<Resource> deployedResources = new ArrayList<Resource>();
   
    deployedResources.addAll(Arrays.asList(resolver.getAddedResources()));
    deployedResources.addAll(Arrays.asList(resolver.getRequiredResources()));
   
    for (Resource deployedResource : deployedResources) {
      logger.debug("  required resource: " + deployedResource+(getBundle(deployedResource) != null ? " (installed) ":" (not installed) "));
      logger.debug("    location: " + deployedResource.getURI());
     
      Reason[] reasons = resolver.getReason(deployedResource);
      for(Reason reason : reasons != null ? reasons : new Reason[0]) {
        logger.debug("    satisfies requirement: " + reason.getRequirement()+" of "+reason.getResource());
      }
    }

    for (Reason missingRequirement : resolver.getUnsatisfiedRequirements()) {
      logger.error("  unsatisfied requirement: " + missingRequirement.getRequirement());
    }

    return deployed ? CST.componentBroker.getWaitComponent(component.getName(), 10*1000 /* milliseconds*/) : null;
  }


  /**
   * Whether this unit represents the repository version of an installed component in the platform.
   *
   * This is useful to determine if the installed component can be updated from this unit, notice that
   * to be considered versions of each other the component name AND the bundle symbolic name must match.
   * Otherwise, component packaging has changed, and we can not perform an update.
   *
   */
  public boolean isRepositoryVersionOf(Component component) {
    return  component.getDeclaration().getReference().equals(this.component) &&
        component.getApformComponent().getBundle().getSymbolicName().equals(this.resource.getSymbolicName());
  }

  /**
   * Updates the currently deployed component from this repository version
   */
  public void update(OBRManager context, Component component) throws Exception  {
   
    assert this.isRepositoryVersionOf(component);

    URL updateLocation = (new URI(resource.getURI())).toURL();
    component.getApformComponent().getBundle().update(updateLocation.openStream());
  }

 
  @Override
  public boolean equals(Object object) {
    if (this == object)
      return true;
   
    if (object == null)
      return false;
   
    if (! (object instanceof DeployableComponent))
      return false;
   
    DeployableComponent that = (DeployableComponent) object;

    return  this.resource.getURI().equals(that.resource.getURI()) &&
        this.component.equals(that.component);
  }

  @Override
  public int hashCode() {
    return hashCode;
  }

  @Override
  public String toString() {
    return this.component.getName()+"["+getVersion()+"] @ "+resource.getURI();
  }
 
 
  /*
   * Utility methods to manipulate APAM metadata in capabilities
   */
 
  private final static ComponentReference<?> getComponent(Capability metadata) {
   
    String componentName   = getAttributeInCapability(metadata, CST.NAME);
    String componentkind  = getAttributeInCapability(metadata, CST.COMPONENT_TYPE);
   
    if (CST.SPECIFICATION.equals(componentkind)) {
      return new SpecificationReference(componentName);
    }
   
    if (CST.IMPLEMENTATION.equals(componentkind)) {
      return new ImplementationReference<ImplementationDeclaration>(componentName);
    }
   
    if (CST.INSTANCE.equals(componentkind)) {
      return new InstanceReference(componentName);
    }
   
    return new ComponentReference<ComponentDeclaration>(componentName);
  }
 
 
  private final static String getAttributeInCapability(Capability aCap, String attr) {
    return (String) (aCap.getPropertiesAsMap().get(attr));
  }


}
TOP

Related Classes of fr.imag.adele.obrMan.internal.DeployableComponent

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.