Package io.fabric8.agent

Source Code of io.fabric8.agent.DeploymentBuilder$DeploymentDownloadListener

/**
*  Copyright 2005-2014 Red Hat, Inc.
*
*  Red Hat licenses this file to you 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 io.fabric8.agent;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
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.Set;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeoutException;
import java.util.jar.Attributes;
import java.util.jar.Manifest;

import aQute.bnd.osgi.Macro;
import aQute.bnd.osgi.Processor;
import io.fabric8.agent.download.DownloadManager;
import io.fabric8.agent.repository.AggregateRepository;
import io.fabric8.agent.repository.StaticRepository;
import io.fabric8.agent.resolver.FeatureNamespace;
import io.fabric8.agent.resolver.FeatureResource;
import io.fabric8.agent.resolver.RequirementImpl;
import io.fabric8.agent.resolver.ResolveContextImpl;
import io.fabric8.agent.resolver.ResourceBuilder;
import io.fabric8.agent.resolver.ResourceImpl;
import io.fabric8.agent.resolver.Slf4jResolverLog;
import io.fabric8.agent.utils.AgentUtils;
import io.fabric8.utils.Manifests;
import io.fabric8.utils.MultiException;
import io.fabric8.utils.Strings;
import io.fabric8.fab.DependencyTree;
import io.fabric8.fab.osgi.FabBundleInfo;
import io.fabric8.fab.osgi.FabResolver;
import io.fabric8.fab.osgi.FabResolverFactory;
import org.apache.felix.resolver.ResolverImpl;
import org.apache.felix.utils.version.VersionRange;
import org.apache.felix.utils.version.VersionTable;
import org.apache.karaf.features.BundleInfo;
import org.apache.karaf.features.Conditional;
import org.apache.karaf.features.Feature;
import org.apache.karaf.features.Repository;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleException;
import org.osgi.framework.Constants;
import org.osgi.framework.Filter;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.Version;
import org.osgi.framework.namespace.IdentityNamespace;
import org.osgi.resource.Capability;
import org.osgi.resource.Requirement;
import org.osgi.resource.Resource;
import org.osgi.resource.Wire;
import org.osgi.service.resolver.ResolutionException;
import org.osgi.service.resolver.ResolveContext;
import org.osgi.util.tracker.ServiceTracker;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static io.fabric8.agent.resolver.UriNamespace.getUri;
import static io.fabric8.agent.utils.AgentUtils.FAB_PROTOCOL;
import static io.fabric8.agent.utils.AgentUtils.REQ_PROTOCOL;
import static io.fabric8.utils.PatchUtils.extractUrl;
import static io.fabric8.utils.PatchUtils.extractVersionRange;
import static org.apache.felix.resolver.Util.getSymbolicName;
import static org.apache.felix.resolver.Util.getVersion;
import static org.osgi.framework.namespace.IdentityNamespace.CAPABILITY_TYPE_ATTRIBUTE;
import static org.osgi.framework.namespace.IdentityNamespace.CAPABILITY_VERSION_ATTRIBUTE;
import static org.osgi.framework.namespace.IdentityNamespace.IDENTITY_NAMESPACE;
import static org.osgi.resource.Namespace.REQUIREMENT_RESOLUTION_DIRECTIVE;
import static org.osgi.resource.Namespace.RESOLUTION_MANDATORY;
import static org.osgi.resource.Namespace.RESOLUTION_OPTIONAL;

/**
*/
public class DeploymentBuilder {

    private static final Logger LOGGER = LoggerFactory.getLogger(DeploymentBuilder.class);

    private static final String[] PROTOCOLS = { "blueprint", "spring", "profile", "wrap", "war" };

    private final DownloadManager manager;
    private final FabResolverFactory fabResolverFactory;
    private final Collection<Repository> repositories;

    private final List<org.osgi.service.repository.Repository> resourceRepos;

    String featureRange = "${version;==}";

    AgentUtils.FileDownloader downloader;
    ResourceImpl requirements;
    Map<String, Resource> resources;
    Map<String, StreamProvider> providers;
    long urlHandlersTimeout;
    Map<Resource, List<Wire>> wiring;

    Set<Feature> featuresToRegister = new HashSet<Feature>();

    Map<String, Map<VersionRange, Map<String, String>>> metadata;

    public interface DeploymentDownloadListener {
        void onDownload(File file, int pending);
    }

    public DeploymentBuilder(DownloadManager manager,
                             FabResolverFactory fabResolverFactory,
                             Collection<Repository> repositories,
                             long urlHandlersTimeout) {
        this.manager = manager;
        this.fabResolverFactory = fabResolverFactory;
        this.repositories = repositories;
        this.resourceRepos = new ArrayList<org.osgi.service.repository.Repository>();
        this.urlHandlersTimeout = urlHandlersTimeout;
    }

    public void addResourceRepository(org.osgi.service.repository.Repository repository) {
        resourceRepos.add(repository);
    }

    public Map<String, StreamProvider> getProviders() {
        return providers;
    }

    public Map<String, Resource> download(Set<String> features,
                         Set<String> bundles,
                         Set<String> fabs,
                         Set<String> reqs,
                         Set<String> overrides,
                         Set<String> optionals,
                         Map<String, Map<VersionRange, Map<String, String>>> metadata,
                         DeploymentDownloadListener listener) throws IOException, MultiException, InterruptedException, ResolutionException {
        this.downloader = new AgentUtils.FileDownloader(manager);
        this.resources = new ConcurrentHashMap<String, Resource>();
        this.providers = new ConcurrentHashMap<String, StreamProvider>();
        this.requirements = new ResourceImpl("dummy", "dummy", Version.emptyVersion);
        this.metadata = metadata;
        // First, gather all bundle resources
        for (String feature : features) {
            registerMatchingFeatures(feature);
        }
        for (String bundle : bundles) {
            downloadAndBuildResource(bundle, listener);
        }
        for (String fab : fabs) {
            downloadAndBuildResource(FAB_PROTOCOL + fab, listener);
        }
        for (String req : reqs) {
            downloadAndBuildResource(REQ_PROTOCOL + req, listener);
        }
        for (String override : overrides) {
            // TODO: ignore download failures for overrides
            downloadAndBuildResource(extractUrl(override), listener);
        }
        for (String optional : optionals) {
            downloadAndBuildResource(optional, listener);
        }
        // Wait for all resources to be created
        downloader.await();
        // Do override replacement
        for (String override : overrides) {
            Resource over = resources.remove(extractUrl(override));
            if (over == null) {
                // Ignore invalid overrides
                continue;
            }
            for (String uri : new ArrayList<String>(resources.keySet())) {
                Resource res = resources.get(uri);
                if (getSymbolicName(res).equals(getSymbolicName(over))) {
                    VersionRange range;
                    String vr = extractVersionRange(override);
                    if (vr == null) {
                        // default to micro version compatibility
                        Version v1 = getVersion(res);
                        Version v2 = new Version(v1.getMajor(), v1.getMinor() + 1, 0);
                        range = new VersionRange(false, v1, v2, true);
                    } else {
                        range = VersionRange.parseVersionRange(vr);
                    }
                    // The resource matches, so replace it with the overridden resource
                    // if the override is actually a newer version than what we currently have
                    if (range.contains(getVersion(res)) && getVersion(res).compareTo(getVersion(over)) < 0) {
                        resources.put(uri, over);
                    }
                }
            }
        }
        // Build features resources
        for (Feature feature : featuresToRegister) {
            ResourceImpl resource = FeatureResource.build(feature, featureRange, resources);
            resources.put("feature:" + feature.getName() + "/" + feature.getVersion(), resource);
            for (Conditional cond : feature.getConditional()) {
                Feature featCond = cond.asFeature(feature.getName(), feature.getVersion());
                FeatureResource resCond = FeatureResource.build(feature, cond, featureRange, resources);
                requireFeature(featCond.getName() + "/" + featCond.getVersion(), resource, true);
                resources.put("feature:" + featCond.getName() + "/" + featCond.getVersion(), resCond);
            }
        }
        // Build requirements
        for (String feature : features) {
            requireFeature(feature, requirements);
        }
        for (String bundle : bundles) {
            requireResource(bundle);
        }
        for (String req : reqs) {
            requireResource(REQ_PROTOCOL + req);
        }
        for (String fab : fabs) {
            requireResource(FAB_PROTOCOL + fab);
        }
        return resources;
    }

    public Collection<Resource> resolve(Resource systemBundle,
                                        boolean resolveOptionalImports) throws ResolutionException {
        // Resolve
        resources.put("system-bundle", systemBundle);

        List<org.osgi.service.repository.Repository> repos = new ArrayList<org.osgi.service.repository.Repository>();
        repos.add(new StaticRepository(resources.values()));
        repos.addAll(resourceRepos);

        ResolverImpl resolver = new ResolverImpl(new Slf4jResolverLog(LOGGER));
        ResolveContext context = new ResolveContextImpl(
                Collections.<Resource>singleton(requirements),
                Collections.<Resource>emptySet(),
                new AggregateRepository(repos),
                resolveOptionalImports);

        try {
            wiring = resolver.resolve(context);
        } catch (ResolutionException e) {
            // if its missing feature(s) then build a better error response the end user better understands
            // as today its a bit confusing with dummy/0.0.0 etc
            List<String> missing = new ArrayList<>();

            Collection<Requirement> reqs = e.getUnresolvedRequirements();
            Iterator<Requirement> it = reqs.iterator();
            while (it.hasNext()) {
                Requirement req = it.next();
                String type = (String) req.getAttributes().get(IdentityNamespace.CAPABILITY_TYPE_ATTRIBUTE);
                boolean isFeature = type != null && FeatureNamespace.TYPE_FEATURE.equals(type);
                String name = (String) req.getAttributes().get(IdentityNamespace.IDENTITY_NAMESPACE);
                if (isFeature && name != null) {
                    missing.add(name);
                }
            }

            if (!missing.isEmpty()) {
                throw new ResolutionException("The following feature(s) may not exist or cannot be resolved: [" + Strings.join(missing, ", ") + "]", e, reqs);
            }
        }

        Map<String, Resource> deploy = new TreeMap<String, Resource>();
        for (Resource res : wiring.keySet()) {
            String uri = getUri(res);
            if (uri != null) {
                deploy.put(uri, res);
            }
        }
        return deploy.values();
    }

    public Map<Resource, List<Wire>> getWiring() {
        return wiring;
    }

    public void requireFeature(String feature, ResourceImpl resource) throws IOException {
        requireFeature(feature, resource, false);
    }

    public void requireFeature(String feature, ResourceImpl resource, boolean optional) throws IOException {
        // Find name and version range
        String[] split = feature.split("/");
        String name = split[0].trim();
        String version = (split.length > 1) ? split[1].trim() : "";
        VersionRange range = version.length() == 0
                        ? VersionRange.ANY_VERSION : new VersionRange(version);
        // Add requirement
        Map<String, String> dirs = new HashMap<>();
        dirs.put(REQUIREMENT_RESOLUTION_DIRECTIVE, optional ? RESOLUTION_OPTIONAL : RESOLUTION_MANDATORY);
        Map<String, Object> attrs = new HashMap<String, Object>();
        attrs.put(IdentityNamespace.IDENTITY_NAMESPACE, name);
        attrs.put(IdentityNamespace.CAPABILITY_TYPE_ATTRIBUTE, FeatureNamespace.TYPE_FEATURE);
        attrs.put(IdentityNamespace.CAPABILITY_VERSION_ATTRIBUTE, range);
        resource.addRequirement(
                new RequirementImpl(resource, IdentityNamespace.IDENTITY_NAMESPACE,
                        dirs, attrs));
    }

    public void requireResource(String location) {
        Resource res = resources.get(location);
        if (res == null) {
            throw new IllegalStateException("Could not find resource for " + location);
        }
        List<Capability> caps = res.getCapabilities(IdentityNamespace.IDENTITY_NAMESPACE);
        if (caps.size() != 1) {
            throw new IllegalStateException("Resource does not have a single " + IdentityNamespace.IDENTITY_NAMESPACE + " capability");
        }
        Capability cap = caps.get(0);
        // Add requirement
        Map<String, Object> attrs = new HashMap<String, Object>();
        attrs.put(IdentityNamespace.IDENTITY_NAMESPACE, cap.getAttributes().get(IdentityNamespace.IDENTITY_NAMESPACE));
        attrs.put(IdentityNamespace.CAPABILITY_TYPE_ATTRIBUTE, cap.getAttributes().get(IdentityNamespace.CAPABILITY_TYPE_ATTRIBUTE));
        attrs.put(IdentityNamespace.CAPABILITY_VERSION_ATTRIBUTE, new VersionRange((Version) cap.getAttributes().get(IdentityNamespace.CAPABILITY_VERSION_ATTRIBUTE), true));
        requirements.addRequirement(
                new RequirementImpl(requirements, IdentityNamespace.IDENTITY_NAMESPACE,
                        Collections.<String, String>emptyMap(), attrs));

    }

    public void registerMatchingFeatures(String feature) throws IOException {
        // Find name and version range
        String[] split = feature.split("/");
        String name = split[0].trim();
        String version = (split.length > 1)
                ? split[1].trim() : Version.emptyVersion.toString();
        // Register matching features
        registerMatchingFeatures(name, new VersionRange(version));
    }

    public void registerMatchingFeatures(Feature feature) throws IOException {
        registerMatchingFeatures(feature.getName(), feature.getVersion());
    }

    public void registerMatchingFeatures(String name, String version) throws IOException {
        if (!version.startsWith("[") && !version.startsWith("(")) {
            Processor processor = new Processor();
            processor.setProperty("@", VersionTable.getVersion(version).toString());
            Macro macro = new Macro(processor);
            version = macro.process(featureRange);
        }
        registerMatchingFeatures(name, new VersionRange(version));
    }

    public void registerMatchingFeatures(String name, VersionRange range) throws IOException {
        for (Repository repo : repositories) {
            Feature[] features;
            try {
                features = repo.getFeatures();
            } catch (Exception e) {
                // This should not happen as the repository has been loaded already
                throw new IllegalStateException(e);
            }
            for (Feature f : features) {
                if (name.equals(f.getName())) {
                    Version v = VersionTable.getVersion(f.getVersion());
                    if (range.contains(v)) {
                        featuresToRegister.add(f);
                        for (Feature dep : f.getDependencies()) {
                            registerMatchingFeatures(dep);
                        }
                        for (BundleInfo bundle : f.getBundles()) {
                            downloadAndBuildResource(bundle.getLocation(), null);
                        }
                        for (Conditional cond : f.getConditional()) {
                            for (Feature dep : cond.getDependencies()) {
                                registerMatchingFeatures(dep);
                            }
                            for (BundleInfo bundle : cond.getBundles()) {
                                downloadAndBuildResource(bundle.getLocation(), null);
                            }
                        }
                    }
                }
            }
        }
    }

    public void downloadAndBuildResource(final String location, final DeploymentDownloadListener listener) throws IOException {
        if (location.startsWith(FAB_PROTOCOL)) {
            String url = location.substring(FAB_PROTOCOL.length());
            downloader.download(url, new AgentUtils.DownloadCallback() {
                @Override
                public void downloaded(File file, int pendings) throws Exception {
                    if (listener != null) {
                        listener.onDownload(file, pendings);
                    }
                    FabResolver resolver = fabResolverFactory.getResolver(file.toURI().toURL());
                    FabBundleInfo fabInfo = resolver.getInfo();

                    ResourceImpl resource = (ResourceImpl) manageResource(location, fabInfo.getManifest(), new StreamProvider.Fab(fabInfo));
                    for (String name : fabInfo.getFeatures()) {
                        registerMatchingFeatures(name);
                        requireFeature(name, resource);
                    }
                    for (DependencyTree dep : fabInfo.getBundles()) {
                        File depFile = dep.getJarFile();
                        Attributes attrs = getAttributes(dep.getJarURL().toString(), depFile);
                        if (attrs.getValue(Constants.BUNDLE_SYMBOLICNAME) != null) {
                            manageResource(getMvnUrl(dep), attrs, new StreamProvider.File(depFile));
                        }
                    }
                }
            });
        } else if (location.startsWith(REQ_PROTOCOL)) {
            try {
                ResourceImpl resource = new ResourceImpl(location, "dummy", Version.emptyVersion);
                for (Requirement req : ResourceBuilder.parseRequirement(resource, location.substring(REQ_PROTOCOL.length()))) {
                    resource.addRequirement(req);
                }
                resources.put(location, resource);
            } catch (BundleException e) {
                throw new IOException("Error parsing requirement", e);
            }
        } else {
            if (urlHandlersTimeout >= 0) {
                try {
                    // Find needed service ldap filters
                    List<String> filters = new ArrayList<String>();
                    int oldSize = -1;
                    String tmpUrl = location;
                    while (filters.size() > oldSize) {
                        oldSize = filters.size();
                        for (String protocol : PROTOCOLS) {
                            if (tmpUrl.startsWith(protocol + ":")) {
                                tmpUrl = tmpUrl.substring(protocol.length() + 1);
                                String filter = "(&(objectClass=org.osgi.service.url.URLStreamHandlerService)(url.handler.protocol=" + protocol + "))";
                                filters.add(filter);
                                break;
                            }
                        }
                    }
                    // Wait for services if needed
                    if (!filters.isEmpty()) {
                        BundleContext context = FrameworkUtil.getBundle(getClass()).getBundleContext();
                        List<ServiceTracker> trackers = new ArrayList<ServiceTracker>();
                        for (String filter : filters) {
                            Filter flt = FrameworkUtil.createFilter(filter);
                            ServiceTracker tracker = new ServiceTracker(context, flt, null);
                            tracker.open();
                            trackers.add(tracker);
                        }
                        long t0 = System.currentTimeMillis();
                        boolean hasAll = false;
                        while (!hasAll && (System.currentTimeMillis() - t0) < urlHandlersTimeout) {
                            hasAll = true;
                            for (ServiceTracker tracker : trackers) {
                                hasAll &= tracker.waitForService(100) != null;
                            }
                        }
                        for (ServiceTracker tracker : trackers) {
                            tracker.close();
                        }
                        if (!hasAll) {
                            throw new TimeoutException("Timed out waiting for URL handlers: ");
                        }
                    }
                } catch (Exception e) {
                    throw new IOException("Unable to download " + location, e);
                }
            }
            downloader.download(location, new AgentUtils.DownloadCallback() {
                @Override
                public void downloaded(File file, int pendings) throws Exception {
                    if (listener != null) {
                        listener.onDownload(file, pendings);
                    }
                    manageResource(location, file);
                }
            });
        }
    }

    private String getMvnUrl(DependencyTree dep) {
        String groupId = dep.getGroupId();
        String artifactId = dep.getArtifactId();
        String version = dep.getVersion();
        String classifier = dep.getClassifier();
        String extension = dep.getExtension();
        StringBuilder sb = new StringBuilder();
        sb.append("mvn:");
        sb.append(groupId);
        sb.append("/");
        sb.append(artifactId);
        sb.append("/");
        sb.append(version);
        if (!"".equals(classifier) || !"jar".equals(extension)) {
            sb.append("/");
            sb.append(extension);
        }
        if (!"".equals(classifier)) {
            sb.append("/");
            sb.append(classifier);
        }
        return sb.toString();
    }

    private Resource manageResource(String location, File file) throws Exception {
        Resource resource = resources.get(location);
        if (resource == null) {
            Attributes attributes = getAttributes(location, file);
            resource = manageResource(location, attributes, new StreamProvider.File(file));
        }
        return resource;
    }

    private Resource manageResource(String location, Attributes attributes, StreamProvider provider) throws Exception {
        Resource resource = resources.get(location);
        if (resource == null) {
            resource = createResource(location, attributes);
            resources.put(location, resource);
            providers.put(location, provider);
        }
        return resource;
    }

    private Resource createResource(String uri, Attributes attributes) throws Exception {
        Map<String, String> headers = new HashMap<String, String>();
        for (Map.Entry attr : attributes.entrySet()) {
            headers.put(attr.getKey().toString(), attr.getValue().toString());
        }
        try {
            return ResourceBuilder.build(uri, headers);
        } catch (BundleException e) {
            throw new Exception("Unable to create resource for bundle " + uri, e);
        }
    }

    protected Attributes getAttributes(String uri, File file) throws Exception {
        Manifest man = null;
        try {
            man = Manifests.getManifest(file);
        } catch (Exception e) {
            if (file == null) {
                throw new IOException("Error - file must not be null. Source: \"" + uri + "\"", e);
            } else {
                throw new IOException("Error opening file \"" + file.getCanonicalPath() + "\". Source: \"" + uri + "\", size: " + file.length(), e);
            }
        }
        if (man == null) {
            throw new IllegalArgumentException("Resource " + uri + " does not contain a manifest");
        }
        Attributes attributes = man.getMainAttributes();
        return overrideAttributes(attributes, metadata);
    }

    public static Attributes overrideAttributes(Attributes attributes, Map<String, Map<VersionRange, Map<String, String>>> metadata) {
        String bsn = attributes.getValue(Constants.BUNDLE_SYMBOLICNAME);
        String vstr = attributes.getValue(Constants.BUNDLE_VERSION);
        if (bsn != null && vstr != null) {
            if (bsn.indexOf(';') > 0) {
                bsn = bsn.substring(0, bsn.indexOf(';'));
            }
            Version ver = VersionTable.getVersion(vstr);
            Map<VersionRange, Map<String, String>> ranges = metadata != null && bsn != null ? metadata.get(bsn) : null;
            if (ranges != null) {
                for (Map.Entry<VersionRange, Map<String, String>> entry2 : ranges.entrySet()) {
                    if (entry2.getKey().contains(ver)) {
                        for (Map.Entry<String, String> entry3 : entry2.getValue().entrySet()) {
                            String val;
                            if (entry3.getValue().startsWith("=")) {
                                val = entry3.getValue().substring(1);
                            } else {
                                val = attributes.getValue(entry3.getKey());
                                if (val != null) {
                                    val += "," + entry3.getValue();
                                } else {
                                    val = entry3.getValue();
                                }
                            }
                            attributes.putValue(entry3.getKey(), val);
                        }
                    }
                }
            }
        }
        return attributes;
    }

    public static void addIdentityRequirement(ResourceImpl resource, Resource required, boolean mandatory) {
        for (Capability cap : required.getCapabilities(null)) {
            if (cap.getNamespace().equals(IDENTITY_NAMESPACE)) {
                Map<String, Object> attributes = cap.getAttributes();
                Map<String, String> dirs = new HashMap<>();
                dirs.put(REQUIREMENT_RESOLUTION_DIRECTIVE, mandatory ? RESOLUTION_MANDATORY : RESOLUTION_OPTIONAL);
                Map<String, Object> attrs = new HashMap<>();
                attrs.put(IDENTITY_NAMESPACE, attributes.get(IDENTITY_NAMESPACE));
                attrs.put(CAPABILITY_TYPE_ATTRIBUTE, attributes.get(CAPABILITY_TYPE_ATTRIBUTE));
                Version version = (Version) attributes.get(CAPABILITY_VERSION_ATTRIBUTE);
                if (version != null) {
                    attrs.put(CAPABILITY_VERSION_ATTRIBUTE, new VersionRange(version, true));
                }
                resource.addRequirement(new RequirementImpl(resource, IDENTITY_NAMESPACE, dirs, attrs));
            }
        }
    }

}
TOP

Related Classes of io.fabric8.agent.DeploymentBuilder$DeploymentDownloadListener

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.