/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF 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 WARRANTIESOR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.apache.aries.application.modelling.impl; import static org.apache.aries.application.utils.AppConstants.LOG_ENTRY; import static org.apache.aries.application.utils.AppConstants.LOG_EXIT; import java.util.ArrayList; import java.util.Arrays; 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 org.apache.aries.application.management.ResolverException; import org.apache.aries.application.modelling.DeployedBundles; import org.apache.aries.application.modelling.DeploymentMFElement; import org.apache.aries.application.modelling.ExportedBundle; import org.apache.aries.application.modelling.ExportedPackage; import org.apache.aries.application.modelling.ExportedService; import org.apache.aries.application.modelling.ImportedBundle; import org.apache.aries.application.modelling.ImportedPackage; import org.apache.aries.application.modelling.ImportedService; import org.apache.aries.application.modelling.ModelledResource; import org.apache.aries.application.modelling.internal.MessageUtil; import org.apache.aries.application.modelling.internal.PackageRequirementMerger; import org.apache.aries.application.utils.AppConstants; import org.osgi.framework.Constants; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Class to generate DEPLOYMENT.MF manifest entries for resolved bundles based on * corresponding APPLICATION.MF entries. */ public final class DeployedBundlesImpl implements DeployedBundles { private final Logger logger = LoggerFactory.getLogger(DeployedBundlesImpl.class); private final String assetName; private String cachedImportPackage; private Collection<ModelledResource> cachedRequiredUseBundle; private Collection<ImportedPackage> cachedExternalRequirements; private String cachedDeployedImportService; /** Content from APPLICATION.MF */ private final Set<ImportedBundle> appContent = new HashSet<ImportedBundle>(); /** Use Bundle from APPLICATION.MF */ private final Set<ImportedBundle> appUseBundle = new HashSet<ImportedBundle>(); /** Content for deployment.mf deployed-content. */ private final Set<ModelledResource> deployedContent = new HashSet<ModelledResource>(); /** Content for deployment.mf use-bundle. */ private final Set<ModelledResource> deployedUseBundle = new HashSet<ModelledResource>(); /** Content for deployment.mf provision-bundle. */ private final Set<ModelledResource> deployedProvisionBundle = new HashSet<ModelledResource>(); /** Content for deployment.mf DeployedImport-Service. */ private final Collection<ImportedService> deployedImportService = new HashSet<ImportedService>(); private final Collection<ModelledResource> fakeDeployedBundles = new HashSet<ModelledResource>(); /** * Constructor for cases when we have one or more ' * @param assetName the name of the asset being deployed. * @param appContentNames the bundle names specified in Deployed-Content. * @param appUseBundleNames the bundle names specified in Deployed-Use-Bundle. * @param fakeServiceProvidingBundles bundles that we're pretending are part of the deployed content. Can be null. * These bundles are proxies for bundles provided (for example by SCA) that export * services matching Application-ImportService. */ public DeployedBundlesImpl(String assetName, Collection<ImportedBundle> appContentNames, Collection<ImportedBundle> appUseBundleNames, Collection<ModelledResource> fakeServiceProvidingBundles) { logger.debug(LOG_ENTRY, "DeployedBundles", new Object[]{appContentNames, appUseBundleNames, fakeServiceProvidingBundles}); this.assetName = assetName; appContent.addAll(appContentNames); appUseBundle.addAll(appUseBundleNames); if (fakeServiceProvidingBundles != null) { fakeDeployedBundles.addAll(fakeServiceProvidingBundles); } logger.debug(LOG_EXIT, "DeployedBundles"); } /** * Add provisioned version information for a specific bundle name. This will be added to the * appropriate manifest header for the specified bundle. * @param resolvedBundle the bundle that has been provisioned. * @param resolvedVersion the specific version provisioned. */ public void addBundle(ModelledResource modelledBundle) { logger.debug(LOG_ENTRY, "addBundle", new Object[]{modelledBundle}); // Identify the deployment.mf entries the bundle should be added to by matching // both the bundle name and resolved version against the name and version range // defined in application.mf. ExportedBundle resolvedBundle = modelledBundle.getExportedBundle(); if (isBundleMatch(appContent, resolvedBundle)) { logger.debug("Added to " + AppConstants.DEPLOYMENT_CONTENT + ": " + resolvedBundle); deployedContent.add(modelledBundle); // Add any service dependencies to the list deployedImportService.addAll(modelledBundle.getImportedServices()); } else if (isBundleMatch(appUseBundle, resolvedBundle)) { logger.debug("Added to " + AppConstants.DEPLOYMENT_USE_BUNDLE + ": " + resolvedBundle); deployedUseBundle.add(modelledBundle); } else { logger.debug("Added to " + AppConstants.DEPLOYMENT_PROVISION_BUNDLE + ": " + resolvedBundle); deployedProvisionBundle.add(modelledBundle); } // Invalidate caches cachedImportPackage = null; cachedRequiredUseBundle = null; cachedDeployedImportService = null; cachedExternalRequirements = null; logger.debug(LOG_EXIT, "addBundle"); } /** * Check if a match is found between the supplied map of application bundle name/version information, * and the supplied bundle name and version. * @param imports Imported bundles * @param potentialMatch the exported bundle or composite we're interested in * @return true if a match is found; otherwise false. */ private boolean isBundleMatch(Set<ImportedBundle> imports, ExportedBundle potentialMatch) { boolean result = false; for (ImportedBundle ib : imports) { if (ib.isSatisfied(potentialMatch)) { result = true; break; } } return result; } /** * Get the value corresponding to the Deployed-Content header in the deployment.mf. * @return a manifest entry, or an empty string if there is no content. */ public String getContent() { return createManifestString(deployedContent); } /** * Get the value corresponding to the Deployed-Use-Bundle header in the deployment.mf. * @return a manifest entry, or an empty string if there is no content. */ public String getUseBundle() { return createManifestString(deployedUseBundle); } /** * Get the value corresponding to the Provision-Bundle header in the deployment.mf. * @return a manifest entry, or an empty string if there is no content. */ public String getProvisionBundle() { return createManifestString(deployedProvisionBundle); } /** * Get the value corresponding to the Import-Package header in the deployment.mf. * @return a manifest entry, or an empty string if there is no content. * @throws ResolverException if the requirements could not be resolved. */ public String getImportPackage() throws ResolverException { logger.debug(LOG_ENTRY, "getImportPackage"); String result = cachedImportPackage; if (result == null) { Collection<ImportedPackage> externalReqs = new ArrayList<ImportedPackage>(getExternalPackageRequirements()); //Validate that we don't have attributes that will break until RFC138 is used validateOtherImports(externalReqs); // Find the matching capabilities from bundles in use bundle, and prune // matched requirements out of the external requirements collection. Map<ImportedPackage,ExportedPackage> useBundlePkgs = new HashMap<ImportedPackage,ExportedPackage>(); for (Iterator<ImportedPackage> iter = externalReqs.iterator(); iter.hasNext(); ) { ImportedPackage req = iter.next(); ExportedPackage match = getPackageMatch(req, deployedUseBundle); if (match != null) { useBundlePkgs.put(req, match); iter.remove(); } } StringBuilder useBundleImports = new StringBuilder(); for(Map.Entry<ImportedPackage, ExportedPackage> entry : useBundlePkgs.entrySet()) { useBundleImports.append(entry.getValue().toDeploymentString()); ImportedPackage key = entry.getKey(); if(key.isOptional()) useBundleImports.append(";" + Constants.RESOLUTION_DIRECTIVE +":=" + Constants.RESOLUTION_OPTIONAL); useBundleImports.append(","); } result = useBundleImports.toString() + createManifestString(externalReqs); if(result.endsWith(",")) result = result.substring(0, result.length() - 1); cachedImportPackage = result; } logger.debug(LOG_EXIT, "getImportPackage", result); return result; } /** * Get the Deployed-ImportService header. * this.deployedImportService contains all the service import filters for every * blueprint component within the application. We will only write an entry * to Deployed-ImportService if * a) the reference isMultiple(), or * b) the service was not available internally when the app was first deployed * */ public String getDeployedImportService() { logger.debug(LOG_ENTRY,"getDeployedImportService"); String result = cachedDeployedImportService; if (result == null) { Collection<ImportedService> deployedBundleServiceImports = new ArrayList<ImportedService>(); Collection<ExportedService> servicesExportedWithinIsolatedContent = new ArrayList<ExportedService>(); for (ModelledResource mRes : getDeployedContent()) { servicesExportedWithinIsolatedContent.addAll(mRes.getExportedServices()); } for (ModelledResource mRes : fakeDeployedBundles) { servicesExportedWithinIsolatedContent.addAll(mRes.getExportedServices()); } for (ImportedService impService : deployedImportService) { if (impService.isMultiple()) { deployedBundleServiceImports.add(impService); } else { boolean serviceProvidedWithinIsolatedContent = false; Iterator<ExportedService> it = servicesExportedWithinIsolatedContent.iterator(); while (!serviceProvidedWithinIsolatedContent && it.hasNext()) { ExportedService svc = it.next(); serviceProvidedWithinIsolatedContent |= impService.isSatisfied(svc); } if (!serviceProvidedWithinIsolatedContent) { deployedBundleServiceImports.add(impService); } } } result = createManifestString(deployedBundleServiceImports); cachedDeployedImportService = result; } logger.debug(LOG_EXIT,"getDeployedImportService", result); return result; } /** * Get all the requirements of bundles in deployed content that are not satisfied * by other bundles in deployed content. * @return a collection of package requirements. * @throws ResolverException if the requirements could not be resolved. */ private Collection<ImportedPackage> getExternalPackageRequirements() throws ResolverException { logger.debug(LOG_ENTRY,"getExternalPackageRequirements"); Collection<ImportedPackage> result = cachedExternalRequirements; if (result == null) { // Get all the internal requirements. Collection<ImportedPackage> requirements = new ArrayList<ImportedPackage>(); Collection<ExportedPackage> internalExports = new ArrayList<ExportedPackage>(); for (ModelledResource bundle : deployedContent) { requirements.addAll(bundle.getImportedPackages()); internalExports.addAll(bundle.getExportedPackages()); } // Filter out requirements satisfied by internal capabilities. result = new ArrayList<ImportedPackage>(); for (ImportedPackage req : requirements) { boolean satisfied = false; for (ExportedPackage export : internalExports) { if (req.isSatisfied(export)) { satisfied = true; break; } } //If we didn't find a match then it must come from outside if (!satisfied) result.add(req); } PackageRequirementMerger merger = new PackageRequirementMerger(result); if (!merger.isMergeSuccessful()) { List<String> pkgNames = new ArrayList<String>(merger.getInvalidRequirements()); StringBuilder buff = new StringBuilder(); for (String pkgName : merger.getInvalidRequirements()) { buff.append(pkgName).append(", "); } int buffLen = buff.length(); String pkgString = (buffLen > 0 ? buff.substring(0, buffLen - 2) : ""); ResolverException re = new ResolverException(MessageUtil.getMessage( "INCOMPATIBLE_PACKAGE_VERSION_REQUIREMENTS", new Object[] { assetName, pkgString })); re.setUnsatisfiedRequirements(pkgNames); logger.debug(LOG_EXIT,"getExternalPackageRequirements", re); throw re; } result = merger.getMergedRequirements(); cachedExternalRequirements = result; } logger.debug(LOG_EXIT,"getExternalPackageRequirements", result); return result; } /** * Create entries for the Import-Package header corresponding to the supplied * packages, referring to bundles not in Use-Bundle. * @param requirements packages for which entries should be created. * @return manifest header entries. * @throws ResolverException if the imports are invalid. */ private void validateOtherImports(Collection<ImportedPackage> requirements) throws ResolverException { logger.debug(LOG_ENTRY, "validateOtherImports", requirements); for (ImportedPackage req : requirements) { String pkgName = req.getPackageName(); for (String name : req.getAttributes().keySet()) { if (Constants.BUNDLE_SYMBOLICNAME_ATTRIBUTE.equals(name) || Constants.BUNDLE_VERSION_ATTRIBUTE.equals(name)) { ResolverException re = new ResolverException(MessageUtil.getMessage( "INVALID_PACKAGE_REQUIREMENT_ATTRIBUTES", new Object[] { assetName, name, pkgName })); re.setUnsatisfiedRequirements(Arrays.asList(pkgName)); logger.debug(LOG_EXIT, "validateOtherImports", re); throw re; } } } logger.debug(LOG_EXIT, "validateOtherImports"); } /** * Get a package match between the specified requirement and a capability of the supplied * bundles. The resulting match object might not refer to any matching capability. * @param requirement the {@link ImportedPackageImpl} to be matched. * @param bundles the bundles to be searched for matching capabilities. * @return an ExportedPackageImpl or null if no match is found. */ private ExportedPackage getPackageMatch(ImportedPackage requirement, Collection<ModelledResource> bundles) { logger.debug(LOG_ENTRY, "getPackageMatch", new Object[]{requirement, bundles}); ExportedPackage result = null; outer: for (ModelledResource bundle : bundles) { for (ExportedPackage pkg : bundle.getExportedPackages()) { if(requirement.isSatisfied(pkg)) { result = pkg; break outer; } } } logger.debug(LOG_EXIT, "getPackageMatch", new Object[]{result}); return result; } private String createManifestString(Collection<? extends DeploymentMFElement> values) { logger.debug(LOG_ENTRY, "createManifestString", new Object[]{values}); StringBuilder builder = new StringBuilder(); for (DeploymentMFElement value : values) { builder.append(value.toDeploymentString()).append(","); } int length = builder.length(); String result = (length > 0 ? builder.substring(0, length - 1) : ""); logger.debug(LOG_EXIT, "createManifestString", new Object[]{result}); return result; } @Override public String toString() { return AppConstants.DEPLOYMENT_CONTENT + '=' + deployedContent + ' ' + AppConstants.DEPLOYMENT_USE_BUNDLE + '=' + deployedUseBundle + ' ' + AppConstants.DEPLOYMENT_PROVISION_BUNDLE + '=' + deployedProvisionBundle; } /** * Get the set of bundles that are going to be deployed into an isolated framework * @return a set of bundle metadata */ public Collection<ModelledResource> getDeployedContent() { logger.debug(LOG_ENTRY, "getDeployedContent"); logger.debug(LOG_EXIT,"getDeployedContent", deployedContent); return Collections.unmodifiableCollection(deployedContent); } /** * Get the set of bundles that map to Provision-Bundle: these plus * getRequiredUseBundle combined give the bundles that will be provisioned * into the shared bundle space * 'getProvisionBundle' returns the manifest header string, so this method * needs to be called something else. * */ public Collection<ModelledResource> getDeployedProvisionBundle () { logger.debug(LOG_ENTRY,"getDeployedProvisionBundle"); logger.debug(LOG_EXIT, "getDeployedProvisionBundle", deployedContent); return Collections.unmodifiableCollection(deployedProvisionBundle); } /** * Get the subset of bundles specified in use-bundle that are actually required to * satisfy direct requirements of deployed content. * @return a set of bundle metadata. * @throws ResolverException if the requirements could not be resolved. */ public Collection<ModelledResource> getRequiredUseBundle() throws ResolverException { logger.debug(LOG_ENTRY, "getRequiredUseBundle"); Collection<ModelledResource> usedUseBundles = cachedRequiredUseBundle; if (usedUseBundles == null) { Collection<ImportedPackage> externalReqs = getExternalPackageRequirements(); usedUseBundles = new HashSet<ModelledResource>(); for (ImportedPackage req : externalReqs) { // Find a match from the supplied bundle capabilities. ExportedPackage match = getPackageMatch(req, deployedUseBundle); if (match != null) { usedUseBundles.add(match.getBundle()); } } cachedRequiredUseBundle = usedUseBundles; } logger.debug(LOG_EXIT, "getRequiredUseBundle", usedUseBundles); return usedUseBundles; } /** This method will be overridden by a PostResolveTransformer returning an extended version of * DeployedBundles */ public Map<String, String> getExtraHeaders() { logger.debug (LOG_ENTRY, "getExtraHeaders"); Map<String, String> result = Collections.emptyMap(); logger.debug (LOG_EXIT, "getExtraHeaders", result); return result; } }