/* * 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 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 org.apache.felix.deploymentadmin; import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.jar.Manifest; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.InvalidSyntaxException; import org.osgi.framework.ServiceReference; import org.osgi.framework.Version; import org.osgi.service.deploymentadmin.BundleInfo; import org.osgi.service.deploymentadmin.DeploymentException; import org.osgi.service.deploymentadmin.DeploymentPackage; import org.osgi.service.deploymentadmin.spi.ResourceProcessor; import org.osgi.service.log.LogService; /** * Base class for various types of deployment packages. Indifferent in regard to * how the deployment package data is obtained, this should be handled by * extending classes. */ public abstract class AbstractDeploymentPackage implements DeploymentPackage, Constants { /** * Represents an empty deployment package. */ private static final class EmptyDeploymentPackage extends AbstractDeploymentPackage { private static final String[] STRINGS = new String[] {}; private static final ResourceInfoImpl[] RESOURCE_INFO_IMPLS = new ResourceInfoImpl[] {}; private static final BundleInfoImpl[] BUNDLE_INFO_IMPLS = new BundleInfoImpl[] {}; public Bundle getBundle(String symbolicName) { return null; } public BundleInfoImpl[] getBundleInfoImpls() { return BUNDLE_INFO_IMPLS; } public BundleInfo[] getBundleInfos() { return BUNDLE_INFO_IMPLS; } public InputStream getBundleStream(String symbolicName) throws IOException { return null; } public InputStream getCurrentEntryStream() { throw new UnsupportedOperationException(); } public String getDisplayName() { return ""; } public String getHeader(String header) { if (DEPLOYMENTPACKAGE_SYMBOLICMAME.equals(header)) { return ""; } else if (DEPLOYMENTPACKAGE_VERSION.equals(header)) { return Version.emptyVersion.toString(); } else { return null; } } public URL getIcon() { return null; } public String getName() { return ""; } public AbstractInfo getNextEntry() throws IOException { throw new UnsupportedOperationException(); } public BundleInfoImpl[] getOrderedBundleInfos() { return BUNDLE_INFO_IMPLS; } public ResourceInfoImpl[] getOrderedResourceInfos() { return RESOURCE_INFO_IMPLS; } public String getResourceHeader(String resource, String header) { return null; } public ResourceInfoImpl[] getResourceInfos() { return RESOURCE_INFO_IMPLS; } public ServiceReference getResourceProcessor(String resource) { return null; } public String[] getResources() { return STRINGS; } public Version getVersion() { return Version.emptyVersion; } public boolean isStale() { return true; } public void uninstall() throws DeploymentException { throw new IllegalStateException("Can not uninstall stale DeploymentPackage"); } public boolean uninstallForced() throws DeploymentException { throw new IllegalStateException("Can not uninstall stale DeploymentPackage"); } } protected static final AbstractDeploymentPackage EMPTY_PACKAGE = new EmptyDeploymentPackage(); private final BundleContext m_bundleContext; private final DeploymentAdminImpl m_deploymentAdmin; private final DeploymentPackageManifest m_manifest; private final Map m_nameToBundleInfo = new HashMap(); private final Map m_pathToEntry = new HashMap(); private final BundleInfoImpl[] m_bundleInfos; private final ResourceInfoImpl[] m_resourceInfos; private final String[] m_resourcePaths; private final boolean m_isFixPackage; private boolean m_isStale; /** * Creates an instance of this class. * * @param manifest The manifest of the deployment package. * @param bundleContext The bundle context. * @throws DeploymentException Thrown if the specified manifest does not * describe a valid deployment package. */ public AbstractDeploymentPackage(Manifest manifest, BundleContext bundleContext, DeploymentAdminImpl deploymentAdmin) throws DeploymentException { m_manifest = new DeploymentPackageManifest(manifest); m_isFixPackage = m_manifest.getFixPackage() != null; m_bundleContext = bundleContext; m_deploymentAdmin = deploymentAdmin; List bundleInfos = m_manifest.getBundleInfos(); m_bundleInfos = (BundleInfoImpl[]) bundleInfos.toArray(new BundleInfoImpl[bundleInfos.size()]); for (int i = 0; i < m_bundleInfos.length; i++) { String bsn = m_bundleInfos[i].getSymbolicName(); if (m_nameToBundleInfo.put(bsn, m_bundleInfos[i]) != null) { // FELIX-4463: make sure that the DP is consistent... throw new DeploymentException(CODE_OTHER_ERROR, "Duplicate bundle present in deployment package: " + bsn); } String path = m_bundleInfos[i].getPath(); if (m_pathToEntry.put(path, m_bundleInfos[i]) != null) { // FELIX-4463: make sure that the DP is consistent... throw new DeploymentException(CODE_OTHER_ERROR, "Non-unique path present in deployment package: " + path); } } List resourceInfos = m_manifest.getResourceInfos(); m_resourceInfos = (ResourceInfoImpl[]) resourceInfos.toArray(new ResourceInfoImpl[resourceInfos.size()]); for (int i = 0; i < m_resourceInfos.length; i++) { String path = m_resourceInfos[i].getPath(); if (m_pathToEntry.put(path, m_resourceInfos[i]) != null) { // FELIX-4463: make sure that the DP is consistent... throw new DeploymentException(CODE_OTHER_ERROR, "Non-unique path present in deployment package: " + path); } } m_resourcePaths = (String[]) m_pathToEntry.keySet().toArray(new String[m_pathToEntry.size()]); } /* Constructor only for use by the emptyPackage static variable */ private AbstractDeploymentPackage() { m_bundleContext = null; m_manifest = null; m_bundleInfos = null; m_resourceInfos = null; m_resourcePaths = null; m_isFixPackage = false; m_deploymentAdmin = null; } public Bundle getBundle(String symbolicName) { if (isStale()) { throw new IllegalStateException("Can not get bundle from stale deployment package."); } BundleInfo bundleInfo = (BundleInfo) m_nameToBundleInfo.get(symbolicName); if (bundleInfo != null) { Version version = bundleInfo.getVersion(); Bundle[] bundles = m_bundleContext.getBundles(); for (int i = 0; i < bundles.length; i++) { if (symbolicName.equals(bundles[i].getSymbolicName()) && version.equals(bundles[i].getVersion())) { return bundles[i]; } } } return null; } /** * Determines the info about a bundle resource based on the bundle symbolic * name. * * @param symbolicName String containing a bundle symbolic name * @return <code>BundleInfoImpl</code> for the bundle identified by the * specified symbolic name or null if the symbolic name is unknown */ public BundleInfoImpl getBundleInfoByName(String symbolicName) { return (BundleInfoImpl) m_nameToBundleInfo.get(symbolicName); } /** * Determines the info about a bundle based on it's path/resource-id. * * @param path String containing a bundle path * @return <code>BundleInfoImpl</code> for the bundle resource identified by * the specified path or null if the path is unknown or does not * describe a bundle resource */ public BundleInfoImpl getBundleInfoByPath(String path) { AbstractInfo info = (AbstractInfo) m_pathToEntry.get(path); if (info instanceof BundleInfoImpl) { return (BundleInfoImpl) info; } return null; } /** * Returns the bundles of this deployment package as an array of * <code>BundleInfoImpl</code> objects. * * @return Array containing <code>BundleInfoImpl</code> objects for each * bundle this deployment package. */ public BundleInfoImpl[] getBundleInfoImpls() { return (BundleInfoImpl[]) m_bundleInfos.clone(); } public BundleInfo[] getBundleInfos() { return (BundleInfo[]) m_bundleInfos.clone(); } /** * Determines the data stream of a bundle resource based on the bundle * symbolic name * * @param symbolicName Bundle symbolic name * @return Stream to the bundle identified by the specified symbolic name or * null if no such bundle exists in this deployment package. * @throws IOException If the bundle can not be properly offered as an * inputstream */ public abstract InputStream getBundleStream(String symbolicName) throws IOException; /** * Determines the data stream to the current entry of this deployment * package, use this together with the <code>getNextEntry</code> method. * * @return Stream to the current resource in the deployment package (as * determined by the order in which the deployment package was * received originally) or null if there is no entry */ public abstract InputStream getCurrentEntryStream(); public String getDisplayName() { return getHeader(DEPLOYMENTPACKAGE_NAME); } public String getHeader(String header) { return m_manifest.getHeader(header); } public URL getIcon() { String icon = getHeader(DEPLOYMENTPACKAGE_ICON); if (icon == null) { return null; } else { try { // TODO spec states this must be a local resource, but we don't make sure of that yet return new URL(icon); } catch (MalformedURLException e) { return null; } } } public String getName() { return m_manifest.getSymbolicName(); } /** * Determines the next resource entry in this deployment package based on * the order in which the resources appeared when the package was originally * received. * * @return <code>AbstractInfo</code> describing the next resource entry (as * determined by the order in which the deployment package was * received originally) or null if there is no next entry * @throws IOException if the next entry can not be properly determined */ public abstract AbstractInfo getNextEntry() throws IOException; /** * Determines the bundles of this deployment package in the order in which * they were originally received. * * @return Array containing <code>BundleInfoImpl</code> objects of the * bundles in this deployment package, ordered in the way they * appeared when the deployment package was first received. */ public abstract BundleInfoImpl[] getOrderedBundleInfos(); /** * Determines the resources of this deployment package in the order in which * they were originally received. * * @return Array containing <code>ResourceInfoImpl</code> objects of all * processed resources in this deployment package, ordered in the * way they appeared when the deployment package was first received */ public abstract ResourceInfoImpl[] getOrderedResourceInfos(); public String getResourceHeader(String resource, String header) { AbstractInfo info = (AbstractInfo) m_pathToEntry.get(resource); if (info != null) { return info.getHeader(header); } return null; } /** * Determines the info about a processed resource based on it's * path/resource-id. * * @param path String containing a (processed) resource path * @return <code>ResourceInfoImpl</code> for the resource identified by the * specified path or null if the path is unknown or does not * describe a processed resource */ public ResourceInfoImpl getResourceInfoByPath(String path) { AbstractInfo info = (AbstractInfo) m_pathToEntry.get(path); if (info instanceof ResourceInfoImpl) { return (ResourceInfoImpl) info; } return null; } /** * Returns the processed resources of this deployment package as an array of * <code>ResourceInfoImpl</code> objects. * * @return Array containing <code>ResourceInfoImpl</code> objects for each * processed resource of this deployment package. */ public ResourceInfoImpl[] getResourceInfos() { return (ResourceInfoImpl[]) m_resourceInfos.clone(); } public ServiceReference getResourceProcessor(String resource) { if (isStale()) { throw new IllegalStateException("Can not get bundle from stale deployment package."); } AbstractInfo info = (AbstractInfo) m_pathToEntry.get(resource); if (info instanceof ResourceInfoImpl) { String processor = ((ResourceInfoImpl) info).getResourceProcessor(); if (processor != null) { try { ServiceReference[] services = m_bundleContext.getServiceReferences(ResourceProcessor.class.getName(), "(" + SERVICE_PID + "=" + processor + ")"); if (services != null && services.length > 0) { return services[0]; } else { return null; } } catch (InvalidSyntaxException e) { m_deploymentAdmin.getLog().log(LogService.LOG_WARNING, "Invalid resource processor name: " + processor, e); return null; } } } return null; } public String[] getResources() { return (String[]) m_resourcePaths.clone(); } public Version getVersion() { return m_manifest.getVersion(); } /** * If this deployment package is a fix package this method determines the * version range this deployment package can be applied to. * * @return <code>VersionRange</code> the fix package can be applied to or * <code>null</code> if it is not a fix package. */ public VersionRange getVersionRange() { return m_manifest.getFixPackage(); } /** * Determines whether this deployment package is a fix package. * * @return True if this deployment package is a fix package, false * otherwise. */ public boolean isFixPackage() { return m_isFixPackage; } /** * @return <code>true</code> if this package is actually an empty package * used for installing new deployment packages, <code>false</code> * otherwise. */ public boolean isNew() { return this == EMPTY_PACKAGE; } public boolean isStale() { return m_isStale; } public void setStale(boolean isStale) { m_isStale = isStale; } public void uninstall() throws DeploymentException { if (isStale()) { throw new IllegalStateException("Deployment package is stale, cannot uninstall!"); } m_deploymentAdmin.uninstallDeploymentPackage(this, false /* force */); // FELIX-4484: only mark a DP as stale when it is *successfully* uninstalled... setStale(true); } public boolean uninstallForced() throws DeploymentException { if (isStale()) { throw new IllegalStateException("Deployment package is stale, cannot force uninstallation!"); } try { m_deploymentAdmin.uninstallDeploymentPackage(this, true /* force */); } finally { // FELIX-4484: this is a best-effort method, if it fails, we cannot do anything about it anymore... setStale(true); } return true; } /** * Determines the info about either a bundle or processed resource based on * it's path/resource-id. * * @param path String containing a resource path (either bundle or processed * resource) * @return <code>AbstractInfoImpl</code> for the resource identified by the * specified path or null if the path is unknown */ protected AbstractInfo getAbstractInfoByPath(String path) { return (AbstractInfo) m_pathToEntry.get(path); } /** * Returns whether the given name (which is expected to be the name of a * JarEntry) is a signature file or the JAR index file. * * @param name the name of the JAR entry to test, cannot be * <code>null</code>. * @return <code>true</code> if the given JAR entry name is a signature file * or JAR index file, <code>false</code> otherwise. */ protected boolean isMetaInfFile(String name) { name = name.toUpperCase(Locale.US); return name.startsWith("META-INF/") && (name.endsWith("/INDEX.LIST") || name.endsWith(".SF") || name.endsWith(".DSA") || name.endsWith(".RSA") || name.endsWith(".EC")); } }