blob: 512381b93adaa004102c5341d5b849d6fb5cb842 [file] [log] [blame]
/*
* 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"));
}
}