All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.fabric3.contribution.ContributionServiceImpl Maven / Gradle / Ivy

The newest version!
/*
 * Fabric3
 * Copyright (c) 2009-2015 Metaform Systems
 *
 * 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.
 *
 * Portions originally based on Apache Tuscany 2007
 * licensed under the Apache 2.0 license.
 */
package org.fabric3.contribution;

import javax.xml.namespace.QName;
import java.net.URI;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Set;
import java.util.stream.Collectors;

import org.fabric3.api.annotation.monitor.Monitor;
import org.fabric3.api.host.Fabric3Exception;
import org.fabric3.api.host.contribution.ArtifactValidationFailure;
import org.fabric3.api.host.contribution.ContributionOrder;
import org.fabric3.api.host.contribution.ContributionService;
import org.fabric3.api.host.contribution.ContributionSource;
import org.fabric3.api.host.contribution.Deployable;
import org.fabric3.api.host.contribution.ValidationException;
import org.fabric3.api.host.failure.ValidationFailure;
import org.fabric3.api.host.failure.ValidationUtils;
import org.fabric3.api.host.stream.Source;
import org.fabric3.api.model.type.component.Component;
import org.fabric3.api.model.type.component.Composite;
import org.fabric3.spi.contribution.Capability;
import org.fabric3.spi.contribution.ContentTypeResolver;
import org.fabric3.spi.contribution.Contribution;
import org.fabric3.spi.contribution.ContributionManifest;
import org.fabric3.spi.contribution.ContributionServiceListener;
import org.fabric3.spi.contribution.ContributionState;
import org.fabric3.spi.contribution.MetaDataStore;
import org.fabric3.spi.contribution.ProcessorRegistry;
import org.fabric3.spi.contribution.Resource;
import org.fabric3.spi.contribution.ResourceElement;
import org.fabric3.spi.contribution.manifest.QNameSymbol;
import org.fabric3.spi.introspection.DefaultIntrospectionContext;
import org.fabric3.spi.introspection.IntrospectionContext;
import org.oasisopen.sca.annotation.EagerInit;
import org.oasisopen.sca.annotation.Reference;

/**
 * Default ContributionService implementation.
 */
@EagerInit
public class ContributionServiceImpl implements ContributionService {
    private ProcessorRegistry processorRegistry;
    private MetaDataStore metaDataStore;
    private ContributionLoader contributionLoader;
    private ContentTypeResolver contentTypeResolver;
    private DependencyResolver dependencyResolver;
    private ContributionServiceMonitor monitor;
    private List listeners;

    public ContributionServiceImpl(@Reference ProcessorRegistry processorRegistry,
                                   @Reference MetaDataStore metaDataStore,
                                   @Reference ContributionLoader contributionLoader,
                                   @Reference ContentTypeResolver contentTypeResolver,
                                   @Reference DependencyResolver dependencyResolver,
                                   @Monitor ContributionServiceMonitor monitor) {
        this.processorRegistry = processorRegistry;
        this.metaDataStore = metaDataStore;
        this.contributionLoader = contributionLoader;
        this.contentTypeResolver = contentTypeResolver;
        this.dependencyResolver = dependencyResolver;
        this.monitor = monitor;
        listeners = new ArrayList<>();
    }

    @Reference(required = false)
    public void setListeners(List listeners) {
        this.listeners = listeners;
    }

    public Set getContributions() {
        Set contributions = metaDataStore.getContributions();
        Set uris = new HashSet<>(contributions.size());
        uris.addAll(contributions.stream().map(Contribution::getUri).collect(Collectors.toList()));
        return uris;
    }

    public boolean exists(URI uri) {
        return metaDataStore.find(uri) != null;
    }

    public List getContributionAndDependents(URI uri) {
        List contributions = dependencyResolver.orderContributionAndDependents(uri);
        return contributions.stream().map(Contribution::getUri).collect(Collectors.toList());
    }

    public List getDeployables(URI uri) {
        Contribution contribution = find(uri);
        List list = new ArrayList<>();
        if (contribution.getManifest() != null) {
            list.addAll(contribution.getManifest().getDeployables().stream().collect(Collectors.toList()));
        }
        return list;
    }

    public URI store(ContributionSource contributionSource) {
        Contribution contribution = create(contributionSource);
        metaDataStore.store(contribution);
        for (ContributionServiceListener listener : listeners) {
            listener.onStore(contribution);
        }
        return contribution.getUri();
    }

    public List store(List contributionSources) {
        List uris = new ArrayList<>();
        for (ContributionSource contributionSource : contributionSources) {
            URI uri = store(contributionSource);
            uris.add(uri);
        }
        return uris;
    }

    public void install(URI uri) {
        install(Collections.singletonList(uri));
    }

    public List install(List uris) {
        List contributions = new ArrayList<>(uris.size());
        for (URI uri : uris) {
            Contribution contribution = find(uri);
            contributions.add(contribution);
        }
        return installInOrder(contributions);
    }

    public void uninstall(URI uri) {
        Contribution contribution = find(uri);
        uninstall(contribution);
    }

    public void uninstall(List uris) {
        List contributions = new ArrayList<>(uris.size());
        for (URI uri : uris) {
            Contribution contribution = find(uri);
            contributions.add(contribution);
        }
        contributions = dependencyResolver.orderForUninstall(contributions);
        contributions.forEach(this::uninstall);
    }

    public void remove(URI uri) {
        Contribution contribution = find(uri);
        if (contribution.getState() != ContributionState.STORED) {
            throw new Fabric3Exception("Contribution must first be uninstalled: " + uri);
        }
        metaDataStore.remove(uri);
        for (ContributionServiceListener listener : listeners) {
            listener.onRemove(contribution);
        }
    }

    public void remove(List uris) {
        uris.forEach(this::remove);
    }

    public ContributionOrder processManifests(List contributionSources) {
        List contributions = new ArrayList<>();
        for (ContributionSource contributionSource : contributionSources) {
            // store the contributions
            Contribution contribution = create(contributionSource);
            metaDataStore.store(contribution);
            for (ContributionServiceListener listener : listeners) {
                listener.onStore(contribution);
            }
            contributions.add(contribution);
        }
        return introspectManifests(contributions);
    }

    public void processContents(URI uri) {
        Contribution contribution = find(uri);
        try {
            ClassLoader loader = contributionLoader.load(contribution);
            // continue processing the contributions. As they are ordered, dependencies will resolve correctly
            processContents(contribution, loader);
            contribution.install();
            for (ContributionServiceListener listener : listeners) {
                listener.onInstall(contribution);
            }
        } catch (Fabric3Exception e) {
            try {
                revertInstall(Collections.singletonList(contribution));
            } catch (RuntimeException ex) {
                monitor.error("Error reverting deployment", ex);
            }
            throw e;
        }
        String description = contribution.getManifest().getDescription();
        if (description != null) {
            monitor.installed(description);
        }
    }

    private ContributionOrder introspectManifests(List contributions) {
        ContributionOrder order = new ContributionOrder();
        for (Contribution contribution : contributions) {
            if (ContributionState.STORED != contribution.getState()) {
                throw new Fabric3Exception("Contribution is already installed: " + contribution.getUri());
            }
        }
        // process any SCA manifest information, including imports and exports
        contributions.forEach(this::processManifest);
        // order the contributions based on their dependencies
        contributions = dependencyResolver.resolve(contributions);

        List bootContributions = new ArrayList<>();

        for (Contribution contribution : contributions) {
            boolean requiresLoad = false;
            ContributionManifest manifest = contribution.getManifest();
            for (Capability capability : manifest.getRequiredCapabilities()) {
                if (capability.requiresLoad()) {
                    requiresLoad = true;
                    break;
                }
            }
            if (requiresLoad) {
                order.addIsolatedContribution(contribution.getUri());
            } else {
                if (manifest.getBootLevel() > -1) {
                    bootContributions.add(contribution);
                } else {
                    order.addBaseContribution(contribution.getUri());
                }
            }
        }

        // sort the boot contributions by boot level and add them to the order object
        Collections.sort(bootContributions, (c1, c2) -> c1.getManifest().getBootLevel() - c2.getManifest().getBootLevel());

        for (Contribution contribution : bootContributions) {
            order.addBootstrapContribution(contribution.getUri());
        }
        return order;
    }

    /**
     * Resolves a contribution by its URI.
     *
     * @param uri the contribution URI
     * @return the contribution
     * @ if the contribution does not exist
     */
    private Contribution find(URI uri) {
        Contribution contribution = metaDataStore.find(uri);
        if (contribution == null) {
            throw new Fabric3Exception("Contribution not found: " + uri);
        }
        return contribution;
    }

    /**
     * Installs a collection of contributions in order of their dependencies.
     *
     * @param contributions the contributions
     * @return the ordered list of contribution URIs
     * @ if there is an error installing the contributions
     */
    private List installInOrder(List contributions) {
        for (Contribution contribution : contributions) {
            if (ContributionState.STORED != contribution.getState()) {
                throw new Fabric3Exception("Contribution is already installed: " + contribution.getUri());
            }
        }
        // process any SCA manifest information, including imports and exports
        contributions.forEach(this::processManifest);
        // order the contributions based on their dependencies
        contributions = dependencyResolver.resolve(contributions);
        try {
            for (Contribution contribution : contributions) {
                ClassLoader loader = contributionLoader.load(contribution);
                // continue processing the contributions. As they are ordered, dependencies will resolve correctly
                processContents(contribution, loader);
                URI contributionUri = contribution.getUri();
                contribution.getResources().forEach(r -> {
                    r.getResourceElements().forEach(re -> {
                        if (re.getValue() instanceof Composite) {
                            Composite composite = (Composite) re.getValue();
                            setContributionUri(composite, contributionUri);
                        }
                    });
                });
                contribution.install();
                for (ContributionServiceListener listener : listeners) {
                    listener.onInstall(contribution);
                }
            }
        } catch (Fabric3Exception e) {
            try {
                revertInstall(contributions);
            } catch (RuntimeException ex) {
                monitor.error("Error reverting deployment", ex);
            }
            throw e;
        }
        List uris = new ArrayList<>(contributions.size());
        for (Contribution contribution : contributions) {
            URI uri = contribution.getUri();
            uris.add(uri);
            String description = contribution.getManifest().getDescription();
            if (description != null) {
                monitor.installed(description);
            } else if (!contribution.getManifest().isExtension()) {
                monitor.installed(uri.toString());
            }
        }
        return uris;
    }

    private void setContributionUri(Composite composite, URI contributionUri) {
        composite.setContributionUri(contributionUri);
        composite.getComponents().values().forEach(c -> setContributionUri(c, contributionUri));
        composite.getResources().forEach(r -> r.setContributionUri(contributionUri));
        composite.getChannels().values().forEach(c -> c.setContributionUri(contributionUri));
    }

    private void setContributionUri(Component component, URI contributionUri) {
        component.setContributionUri(contributionUri);
        if (component.getComponentType() instanceof Composite) {
            Composite composite = (Composite) component.getComponentType();
            setContributionUri(composite, contributionUri);
        }
    }

    private void revertInstall(List contributions) {
        ListIterator iterator = contributions.listIterator(contributions.size());
        while (iterator.hasPrevious()) {
            Contribution contribution = iterator.previous();
            try {
                if (ContributionState.INSTALLED == contribution.getState()) {
                    uninstall(contribution);
                }
                contributionLoader.unload(contribution);
                remove(contribution.getUri());
            } catch (Fabric3Exception ex) {
                monitor.error("Error reverting installation: " + contribution.getUri(), ex);
            }
        }
    }

    private void uninstall(Contribution contribution) {
        URI uri = contribution.getUri();
        if (contribution.getState() == ContributionState.STORED) {
            throw new Fabric3Exception("Contribution not installed: " + uri);
        } else if (contribution.getState() == ContributionState.DEPLOYED) {
            throw new Fabric3Exception("Contribution is currently deployed: " + uri);
        }
        // unload from memory
        contributionLoader.unload(contribution);
        contribution.uninstall();
        for (ContributionServiceListener listener : listeners) {
            listener.onUninstall(contribution);
        }
        String description = contribution.getManifest().getDescription();
        if (description != null) {
            monitor.uninstalled(description);
        } else if (!contribution.getManifest().isExtension()) {
            monitor.uninstalled(uri.toString());
        }
    }

    /**
     * Processes the contribution manifest.
     *
     * @param contribution the contribution
     * @ if there is an error during introspection
     */
    private void processManifest(Contribution contribution) {
        IntrospectionContext context = new DefaultIntrospectionContext();
        processorRegistry.processManifest(contribution, context);
        if (context.hasErrors()) {
            URI uri = contribution.getUri();
            ArtifactValidationFailure failure = new ArtifactValidationFailure(uri, "the contribution manifest (sca-contribution.xml)");
            failure.addFailures(context.getErrors());
            List failures = new ArrayList<>();
            failures.add(failure);
            ArtifactValidationFailure warning = new ArtifactValidationFailure(uri, "the contribution manifest (sca-contribution.xml)");
            warning.addFailures(context.getWarnings());
            List warnings = new ArrayList<>();
            warnings.add(warning);
            throw new ValidationException(failures, warnings);
        }
        for (ContributionServiceListener listener : listeners) {
            listener.onProcessManifest(contribution);
        }

    }

    /**
     * Processes contribution contents. This assumes all dependencies are installed and can be resolved.
     *
     * @param contribution the contribution to process
     * @param loader       the classloader to load resources in
     * @ if an error occurs during processing
     */
    private void processContents(Contribution contribution, ClassLoader loader) {
        URI contributionUri = contribution.getUri();
        IntrospectionContext context = new DefaultIntrospectionContext(contributionUri, loader);
        processorRegistry.indexContribution(contribution, context);
        if (context.hasErrors()) {
            throw new ValidationException(context.getErrors(), context.getWarnings());
        } else if (context.hasWarnings()) {
            // there were just warnings, report them
            monitor.contributionWarnings(ValidationUtils.outputWarnings(context.getWarnings()));
        }
        metaDataStore.store(contribution);
        context = new DefaultIntrospectionContext(contributionUri, loader);
        processorRegistry.processContribution(contribution, context);
        validateContribution(contribution, context);
        if (context.hasErrors()) {
            throw new ValidationException(context.getErrors(), context.getWarnings());
        } else if (context.hasWarnings()) {
            // there were just warnings, report them
            monitor.contributionWarnings(ValidationUtils.outputWarnings(context.getWarnings()));
        }
        addDeployableEntries(contribution);
    }

    /**
     * Performs final validation on a contribution.
     *
     * @param contribution the contribution to validate
     * @param context      the validation context
     */
    private void validateContribution(Contribution contribution, IntrospectionContext context) {
        for (Deployable deployable : contribution.getManifest().getDeployables()) {
            QName name = deployable.getName();
            QNameSymbol symbol = new QNameSymbol(name);
            boolean found = false;
            for (Resource resource : contribution.getResources()) {
                for (ResourceElement element : resource.getResourceElements()) {
                    if (element.getSymbol().equals(symbol)) {
                        found = true;
                    }
                }
            }
            if (!found) {
                URI uri = contribution.getUri();
                InvalidDeployable failure = new InvalidDeployable("Deployable composite " + name + " not found in " + uri, name);
                context.addError(failure);
            }

        }
    }

    /**
     * Adds deployable entries for composites that are defined as deployables but do not have an explicit entry in the manifest.
     *
     * @param contribution the contribution
     */
    private void addDeployableEntries(Contribution contribution) {
        ContributionManifest manifest = contribution.getManifest();
        for (Resource resource : contribution.getResources()) {
            resource.getResourceElements().stream().filter(element -> element.getValue() instanceof Composite).forEach(element -> {
                Composite composite = (Composite) element.getValue();
                if (composite.isDeployable()) {
                    Deployable deployable = new Deployable(composite.getName(), composite.getModes(), composite.getEnvironments());
                    if (!manifest.getDeployables().contains(deployable)) {
                        manifest.getDeployables().add(deployable);
                    }
                }
            });
        }
    }

    /**
     * Creates a contribution in the archive store
     *
     * @param contributionSource the contribution source
     * @return the contribution
     * @ if an error occurs during the store operation
     */
    private Contribution create(ContributionSource contributionSource) {
        URI contributionUri = contributionSource.getUri();
        if (metaDataStore.find(contributionUri) != null) {
            throw new Fabric3Exception("Contribution already exists: " + contributionUri);
        }
        Source source;
        URL locationUrl = contributionSource.getLocation();
        // reuse the source as the contribution is locally resolvable
        source = contributionSource.getSource();
        String type = contributionSource.getContentType();
        if (type == null && locationUrl == null) {
            throw new Fabric3Exception("Content type could not be determined for contribution: " + contributionUri);
        }
        if (type == null) {
            type = contentTypeResolver.getContentType(locationUrl);
        }
        long timestamp = contributionSource.getTimestamp();
        return new Contribution(contributionUri, source, locationUrl, timestamp, type);
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy