blob: 03910f916fe9bc59e9cb09b891646682fcb4bae4 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) Intel Corporation
* Copyright (c) 2017
*
* 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.
*******************************************************************************/
package org.apache.felix.fileinstall.plugins.installer.impl;
import static org.apache.felix.fileinstall.plugins.installer.impl.Locking.withLock;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Queue;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.Supplier;
import java.util.jar.Attributes;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.stream.Collectors;
import org.apache.felix.fileinstall.ArtifactInstaller;
import org.apache.felix.fileinstall.plugins.installer.Artifact;
import org.apache.felix.fileinstall.plugins.installer.FrameworkInstaller;
import org.apache.felix.fileinstall.plugins.installer.Hash;
import org.apache.felix.fileinstall.plugins.installer.InstallableListener;
import org.apache.felix.fileinstall.plugins.installer.InstallableManager;
import org.apache.felix.fileinstall.plugins.installer.InstallableUnit;
import org.apache.felix.fileinstall.plugins.installer.InstallableUnitEvent;
import org.apache.felix.fileinstall.plugins.installer.State;
import org.apache.felix.fileinstall.plugins.resolver.PluginResolver;
import org.apache.felix.fileinstall.plugins.resolver.ResolveRequest;
import org.apache.felix.fileinstall.plugins.resolver.ResolveResult;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleEvent;
import org.osgi.framework.BundleException;
import org.osgi.framework.BundleListener;
import org.osgi.framework.FrameworkEvent;
import org.osgi.framework.FrameworkListener;
import org.osgi.framework.Version;
import org.osgi.framework.namespace.IdentityNamespace;
import org.osgi.framework.wiring.BundleRevision;
import org.osgi.resource.Capability;
import org.osgi.resource.Requirement;
import org.osgi.resource.Resource;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Deactivate;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ReferenceCardinality;
import org.osgi.service.component.annotations.ReferencePolicy;
import org.osgi.service.component.annotations.ReferencePolicyOption;
import org.osgi.service.log.LogService;
import org.osgi.service.repository.ContentNamespace;
import org.osgi.util.promise.Promise;
import org.osgi.util.promise.Promises;
import aQute.lib.hex.Hex;
@Component(immediate = true)
public class DeploymentInstaller implements ArtifactInstaller, InstallableManager {
/**
* Set environment variable "org.apache.felix.fileinstall.plugins.installer.debug" to enable develeroper-level debug output to the console.
*/
public static final boolean DEBUG = Boolean.getBoolean("org.apache.felix.fileinstall.plugins.installer.debug");
// If Deployment version is not specified, the version to return
private static final String UNKNOWN_DEPLOYMENT_VERSION = "0.0.0";
/**
* OSGi Repository indexes are documented to use SHA-256 for their content hashes.
* @see ContentNamespace#CONTENT_NAMESPACE
*/
private static final String SHA256 = "SHA-256";
// Magic number (not officially but good enough for our purposes) of a ZIP file is PK\0x03\0x04
private static final byte[] ZIP_FILE_MAGIC = new byte[] { 0x50, 0x4b, 0x03, 0x04 };
// Queues for pending resolve and install operations.
private final Queue<Job<List<Bundle>>> pendingInstalls = new LinkedList<>();
private final Map<File, Job<InstallableUnitImpl>> pendingResolves = new LinkedHashMap<>();
// Currently available Installable Units, and a lock for modifying that map safely.
private final Map<File, InstallableUnitImpl> units = new HashMap<>();
private final ReadWriteLock unitsLock = new ReentrantReadWriteLock();
// Listeners for install/uninstall events
private final List<InstallableListener> installListeners = new CopyOnWriteArrayList<>();
// The framework listener invalidates any RESOLVED install units whenever the OSGi Framework packages are refreshed.
private final FrameworkListener frameworkListener = new FrameworkListener() {
@Override
public void frameworkEvent(FrameworkEvent event) {
if (event.getType() == FrameworkEvent.PACKAGES_REFRESHED) {
invalidateAllUnits("Packaged Refreshed");
}
}
};
// The bundle listener invalidated any RESOLVED install units whenever OSGi Bundles are installed, resolved, unresolved, updated or uninstalled.
private final BundleListener bundleListener = new BundleListener() {
@Override
public void bundleChanged(BundleEvent event) {
int mask = BundleEvent.INSTALLED | BundleEvent.RESOLVED | BundleEvent.UNRESOLVED | BundleEvent.UPDATED | BundleEvent.UNINSTALLED;
if ((mask & event.getType()) > 0) {
invalidateAllUnits(String.format("Bundle %s entered state %s", event.getBundle().getSymbolicName(), bundleEventToString(event.getType())));
}
}
};
// Component instance state.
private BundleContext context;
private Thread processorThread;
// The PluginResolver service that calculates resolution results for us, but doesn't produce any effects.
@Reference(target = "(!(" + org.osgi.framework.Constants.SERVICE_BUNDLEID + "=0))")
PluginResolver resolver;
// The FrameworkInstaller service is responsible for ensuring required bundles are present in the OSGi Framework -- installing them when necessary.
@Reference
FrameworkInstaller frameworkInstaller;
@Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC, policyOption = ReferencePolicyOption.GREEDY)
void addInstallListener(InstallableListener listener) {
this.installListeners.add(listener);
}
void removeInstallListener(InstallableListener listener) {
this.installListeners.remove(listener);
}
@Reference(cardinality = ReferenceCardinality.OPTIONAL, policyOption = ReferencePolicyOption.GREEDY)
LogService log;
@Activate
void activate(BundleContext context) {
this.context = context;
context.addFrameworkListener(this.frameworkListener);
context.addBundleListener(this.bundleListener);
this.processorThread = new Thread(() -> {
this.log.log(LogService.LOG_INFO, "Deployment Installer thread starting");
debug("Deployment Install thread starting");
while (!Thread.interrupted()) {
try {
Runnable job = waitForJob();
job.run();
} catch (InterruptedException e) {
debug("Deployment Install thread interrupted");
this.log.log(LogService.LOG_INFO, "Deployment Installer thread interrupted");
break;
} catch (Exception e) {
this.log.log(LogService.LOG_ERROR, "Error processing job on Deployment Installer thread", e);
}
}
debug("Deployment Install thread terminated.");
this.log.log(LogService.LOG_INFO, "Deployment Installer thread terminated");
});
this.processorThread.start();
}
@Deactivate
void deactivate() {
this.processorThread.interrupt();
this.context.removeBundleListener(this.bundleListener);
this.context.removeFrameworkListener(this.frameworkListener);
}
/**
* Wait for the next job to do from the two pending queues. Note that we
* always favour the install queue, because performing an install/uninstall
* will invalidate any completed resolves. Therefore the resolves are only
* processed when the install queue is empty.
*/
private Job<?> waitForJob() throws InterruptedException {
synchronized (this.processorThread) {
while (true) {
if (Thread.interrupted()) {
throw new InterruptedException();
}
// Try to get an InstallableUnit to install
Job<List<Bundle>> installJob = this.pendingInstalls.poll();
if (installJob != null) {
return installJob;
}
// Try to get a File to resolve. An iterator is needed to remove the first entry from the LinkedHashMap.
Iterator<Job<InstallableUnitImpl>> resolvesIter = this.pendingResolves.values().iterator();
if (resolvesIter.hasNext()) {
Job<InstallableUnitImpl> resolveJob = resolvesIter.next();
resolvesIter.remove();
return resolveJob;
}
// Wait for a notification
this.processorThread.wait();
}
}
}
/**
* Put an install job onto the queue
*/
Promise<List<Bundle>> putInstallJob(InstallableUnitImpl unit) {
Job<List<Bundle>> job = new Job<>(() -> installArtifacts(unit));
synchronized (this.processorThread) {
this.pendingInstalls.add(job);
this.processorThread.notifyAll();
}
return job.getPromise();
}
/**
* Put an uninstall job onto the queue
*/
Promise<List<Bundle>> putUninstallJob(InstallableUnitImpl unit) {
Job<List<Bundle>> job = new Job<>(() -> uninstallArtifacts(unit));
synchronized (this.processorThread) {
this.pendingInstalls.add(job);
this.processorThread.notifyAll();
}
return job.getPromise();
}
private List<Bundle> installArtifacts(InstallableUnitImpl unit) {
Supplier<List<Bundle>> func = () -> {
State oldState = unit.getState();
unit.setState(State.INSTALLING);
notifyListeners(Collections.singleton(new InstallableUnitEvent(oldState, State.INSTALLING, unit)));
try {
// Install Bundles
Collection<Artifact> artifacts = unit.getArtifacts();
List<String> locations = artifacts.stream().map(Artifact::getLocation).collect(Collectors.toList());
List<Bundle> installed = this.frameworkInstaller.addLocations(unit, locations);
// Start bundles
for (Bundle bundle : installed) {
if (!isFragment(bundle)) {
bundle.start();
}
}
// Mark unit installed
oldState = unit.getState();
unit.setState(State.INSTALLED);
notifyListeners(Collections.singleton(new InstallableUnitEvent(oldState, State.INSTALLED, unit)));
return installed;
} catch (BundleException | IOException e) {
oldState = unit.getState();
unit.setState(State.ERROR);
unit.setErrorMessage(e.getMessage());
if (log != null) {
log.log(LogService.LOG_ERROR, "Error installing artifact(s)", e);
}
notifyListeners(Collections.singleton(new InstallableUnitEvent(oldState, State.ERROR, unit)));
return Collections.emptyList();
}
};
return withLock(this.unitsLock.writeLock(), func);
}
private List<Bundle> uninstallArtifacts(InstallableUnitImpl unit) {
List<Bundle> bundles = this.frameworkInstaller.removeSponsor(unit);
// Mark unit uninstalled
withLock(this.unitsLock.writeLock(), () -> {
State oldState = unit.getState();
if (unit.setState(State.REMOVED)) {
notifyListeners(Collections.singleton(new InstallableUnitEvent(oldState, State.REMOVED, unit)));
}
});
return bundles;
}
/**
* Put a resolve job onto the queue. The resolve queue is a little
* different: because there is no need to resolve the same file twice, we
* check whether there's already a pending resolve job for the given file.
* If so, just return the same promise.
*/
private Promise<InstallableUnitImpl> putResolveJob(File file) {
Promise<InstallableUnitImpl> promise;
Job<InstallableUnitImpl> newJob = new Job<>(() -> {
InstallableUnitImpl unit = performResolve(file);
replaceUnit(file, null, unit);
return unit;
});
synchronized (this.processorThread) {
Job<InstallableUnitImpl> existing = this.pendingResolves.putIfAbsent(file, newJob);
if (existing != null) {
// no need to unblock the processor thread because we haven't added anything
debug("Not adding resolve job for %s as it is already on the queue", file);
promise = existing.getPromise();
} else {
debug("Added resolve job for %s", file);
promise = newJob.getPromise();
this.processorThread.notifyAll();
}
}
return promise;
}
private InstallableUnitImpl performResolve(File file) {
debug("Starting resolve for file: %s", file);
ResolveRequest request;
try {
request = analyseFile(file);
} catch (Exception e) {
debug("Failed to analyse file %s: %s", file, e.getMessage());
return newFailedUnit(file, file.getName(), file.getName(), "UNKNOWN_VERSION", String.format("Error reading archive file %s: %s", file.getAbsolutePath(), e.getMessage()));
}
ResolveResult result;
try {
result = this.resolver.resolve(request);
} catch (Exception e) {
debug("Failed to resolve file %s: %s", file, e.getMessage());
return newFailedUnit(file, request.getName(), request.getSymbolicName(), request.getVersion(), "Resolution failed: " + e.getMessage());
}
List<Artifact> artifacts = new ArrayList<>(result.getResources().size());
for (Entry<Resource, String> resourceEntry : result.getResources().entrySet()) {
Capability idCap = getIdentityCapability(resourceEntry.getKey());
ArtifactImpl artifact = new ArtifactImpl(getIdentity(idCap), getVersion(idCap), resourceEntry.getValue(), getContentHash(resourceEntry.getKey()));
artifacts.add(artifact);
}
debug("Sucessful resolve for file %s: Deployment-Name=%s, Deployment-SymbolicName=%s, Deployment-Version= %s", file, request.getName(), request.getSymbolicName(), request.getVersion());
return newResolvedUnit(file, request.getName(), request.getSymbolicName(), request.getVersion(), artifacts);
}
private InstallableUnitImpl newResolvedUnit(File file, String name, String symbolicName, String version, List<Artifact> artifacts) {
InstallableUnitImpl newUnit = new InstallableUnitImpl(this, file, name, symbolicName, version, artifacts);
newUnit.setState(State.RESOLVED);
return newUnit;
}
private InstallableUnitImpl newFailedUnit(File file, String name, String symbolicName, String version, String message) {
InstallableUnitImpl newUnit = new InstallableUnitImpl(this, file, name, symbolicName, version, Collections.emptyList());
newUnit.setState(State.ERROR);
newUnit.setErrorMessage(message);
return newUnit;
}
@Override
public boolean canHandle(File file) {
String fileName = file.getName();
if (!fileName.toLowerCase().endsWith(Constants.ARTIFACT_EXTENSION.toLowerCase())) {
log(LogService.LOG_DEBUG, null, "Ignoring %s, does not end with '%s' extension", file.getAbsolutePath(), Constants.ARTIFACT_EXTENSION);
return false;
}
// Check if it's a valid ZIP
try {
if (!isZipFile(file)) {
log(LogService.LOG_WARNING, null, "Not a valid ZIP file, ignoring file: %s", file.getAbsolutePath());
return false;
}
} catch (IOException e) {
log(LogService.LOG_ERROR, e, "Failed to check ZIP header on %s", file.getAbsolutePath());
return false;
}
// Read the requires header and index
try (JarFile jar = new JarFile(file, true)) {
Manifest manifest = jar.getManifest();
if (manifest == null) {
log(LogService.LOG_WARNING, null, "Not a valid bundle archive: no META-INF/MANIFEST.MF in %s", file.getAbsolutePath());
return false;
}
String requireBundleStr = manifest.getMainAttributes().getValue(org.osgi.framework.Constants.REQUIRE_BUNDLE);
String requireCapsStr = manifest.getMainAttributes().getValue(org.osgi.framework.Constants.REQUIRE_CAPABILITY);
if (requireBundleStr == null && requireCapsStr == null) {
log(LogService.LOG_WARNING, null, "Not a valid bundle archive: missing %s or %s header in manifest in %s", org.osgi.framework.Constants.REQUIRE_BUNDLE, org.osgi.framework.Constants.REQUIRE_CAPABILITY, file.getAbsolutePath());
return false;
}
JarEntry indexEntry = findEntry(jar, Constants.INDEX_FILE, Constants.DEFAULT_INDEX_FILE);
if (indexEntry == null) {
log(LogService.LOG_WARNING, null, "Not a valid bundle archive: no index entry found in %s", file.getAbsolutePath());
return false;
}
// Everything seems present and correct
log(LogService.LOG_INFO, null, "Detected valid bundle archive in file %s", file.getAbsolutePath());
return true;
} catch (IOException e) {
log(LogService.LOG_ERROR, e, "Failed to check PKZIP header for candidate archive file %s", file.getAbsolutePath());
return false;
}
}
private ResolveRequest analyseFile(File file) throws IOException {
log(LogService.LOG_INFO, null, "Resolving bundle archive: %s", file.getAbsolutePath());
String fileUriStr = file.toURI().toString();
String indexUriStr;
String name;
String symbolicName;
String version = "";
List<Requirement> requirements = new LinkedList<>();
try (JarFile jar = new JarFile(file)) {
Attributes manifestAttribs = jar.getManifest().getMainAttributes();
symbolicName = manifestAttribs.getValue(Constants.DEPLOYMENT_SYMBOLIC_NAME);
if (symbolicName == null) {
symbolicName = file.getName();
}
name = manifestAttribs.getValue(Constants.DEPLOYMENT_NAME);
if (name == null) {
name = symbolicName;
}
version = manifestAttribs.getValue(Constants.DEPLOYMENT_VERSION);
if (version == null) {
version = UNKNOWN_DEPLOYMENT_VERSION;
}
requirements.addAll(RequirementParser.parseRequireBundle(manifestAttribs.getValue(org.osgi.framework.Constants.REQUIRE_BUNDLE)));
requirements.addAll(RequirementParser.parseRequireCapability(manifestAttribs.getValue(org.osgi.framework.Constants.REQUIRE_CAPABILITY)));
if (requirements.isEmpty()) {
throw new IllegalArgumentException(String.format("Missing %s or %s header in manifest in %s", org.osgi.framework.Constants.REQUIRE_BUNDLE, org.osgi.framework.Constants.REQUIRE_CAPABILITY, file.getAbsolutePath()));
}
JarEntry indexEntry = findEntry(jar, Constants.INDEX_FILE, Constants.DEFAULT_INDEX_FILE);
if (indexEntry == null) {
throw new IllegalArgumentException("Missing index entry in " + file.getAbsolutePath());
}
indexUriStr = "jar:" + fileUriStr + "!/" + indexEntry.getName();
}
try {
ResolveRequest request = new ResolveRequest(name, symbolicName, version,
Collections.singletonList(new URI(indexUriStr)), requirements);
return request;
} catch (URISyntaxException e) {
throw new IOException("Unable to convert index URI " + indexUriStr, e);
}
}
@Override
public void install(File file) {
log(LogService.LOG_INFO, null, "Installing bundle archive: %s", file.getAbsolutePath());
putResolveJob(file);
}
private static String getIdentity(Capability identityCap) {
Object idObj = identityCap.getAttributes().get(IdentityNamespace.IDENTITY_NAMESPACE);
if (!(idObj instanceof String)) {
throw new IllegalArgumentException("Missing identity capability on resource, or incorrect type");
}
return (String) idObj;
}
private static String getVersion(Capability identityCap) {
Object versionObj = identityCap.getAttributes().get(IdentityNamespace.CAPABILITY_VERSION_ATTRIBUTE);
if (versionObj == null) {
return Version.emptyVersion.toString();
}
if (versionObj instanceof Version) {
return ((Version) versionObj).toString();
}
if (versionObj instanceof String) {
return Version.parseVersion((String) versionObj).toString();
}
throw new IllegalArgumentException("Incorrect type on identity version");
}
private static Capability getIdentityCapability(Resource resource) {
List<Capability> caps = resource.getCapabilities(IdentityNamespace.IDENTITY_NAMESPACE);
if (caps == null || caps.isEmpty()) {
throw new IllegalArgumentException("Missing identity capability on resource");
}
return caps.get(0);
}
@Override
public void uninstall(File file) throws Exception {
log(LogService.LOG_INFO, null, "Uninstalling bundle archive: %s", file.getAbsolutePath());
unsponsorUnit(file);
}
@Override
public void update(File file) throws Exception {
log(LogService.LOG_INFO, null, "Uninstalling bundle archive: %s", file.getAbsolutePath());
unsponsorUnit(file).flatMap(lb -> {
return putResolveJob(file);
});
}
@Override
public Collection<InstallableUnit> getInstallableUnits() {
return withLock(this.unitsLock.readLock(), ArrayList<InstallableUnit>::new, this.units.values());
}
/**
* Unsponsor the installable unit. Returns a promise which is resolved when the uninstall is completed.
*/
private Promise<List<Bundle>> unsponsorUnit(File file) {
List<InstallableUnitEvent> events = new LinkedList<>();
Promise<List<Bundle>> bundles = withLock(this.unitsLock.writeLock(), () -> {
Promise<List<Bundle>> promise = Promises.resolved(Collections.emptyList());
InstallableUnitImpl existing = this.units.remove(file);
if (existing != null) {
State origState = existing.getState();
if (origState.equals(State.INSTALLED)) {
promise = putUninstallJob(existing);
}
if (existing.setState(State.REMOVED)) {
events.add(new InstallableUnitEvent(origState, State.REMOVED, existing));
}
}
return promise;
});
notifyListeners(events);
return bundles;
}
private void invalidateAllUnits(String reason) {
List<File> affectedFiles = new LinkedList<>();
List<InstallableUnitEvent> events = new LinkedList<>();
withLock(this.unitsLock.writeLock(), () -> {
for (Entry<File, InstallableUnitImpl> entry : this.units.entrySet()) {
InstallableUnitImpl unit = entry.getValue();
State currentState = unit.getState();
if (EnumSet.of(State.RESOLVED, State.ERROR).contains(currentState)) {
unit.setState(State.REMOVED);
affectedFiles.add(entry.getKey());
events.add(new InstallableUnitEvent(currentState, State.REMOVED, unit));
debug("invalidated %s because: %s%n", entry.getKey(), reason);
}
}
});
notifyListeners(events);
// Schedule re-resolution of the affected files.
for (File file : affectedFiles) {
putResolveJob(file);
}
}
private void replaceUnit(File file, State origState, InstallableUnitImpl newUnit) {
Collection<InstallableUnitEvent> events = new ArrayList<>(2);
events.add(new InstallableUnitEvent(origState, newUnit.getState(), newUnit));
events.addAll(withLock(this.unitsLock.writeLock(), () -> {
InstallableUnitImpl existing = this.units.put(file, newUnit);
if (existing != null) {
State oldState = existing.getState();
if (existing.setState(State.REMOVED)) {
return Collections.singleton(new InstallableUnitEvent(oldState, State.REMOVED, existing));
}
}
return Collections.emptyList();
}));
notifyListeners(events);
}
private void notifyListeners(Collection<InstallableUnitEvent> events) {
if (events == null || events.isEmpty()) {
return;
}
for (InstallableListener listener : this.installListeners) {
try {
listener.installableUnitsChanged(events);
} catch (Exception e) {
log(LogService.LOG_ERROR, e, "Error dispatching installable unit event to registered listener");
}
}
}
private void log(int level, Throwable t, String format, Object... args) {
if (this.log != null) {
this.log.log(level, String.format(format, args), t);
}
}
private static boolean isZipFile(File file) throws IOException {
byte[] tmp = new byte[4];
try (InputStream in = new FileInputStream(file)) {
int bytesRead = in.read(tmp, 0, 4);
if (bytesRead < 4) {
throw new IOException("Insufficient bytes read for ZIP header test: possibly truncated file?");
}
}
return Arrays.equals(tmp, ZIP_FILE_MAGIC);
}
private static JarEntry findEntry(JarFile jar, String headerName, String defaultPath) throws IOException {
String path = defaultPath;
String headerPath = jar.getManifest().getMainAttributes().getValue(headerName);
if (headerPath != null) {
path = headerPath.trim();
}
if (path == null) {
// Could happen if no default and no header
return null;
}
return jar.getJarEntry(path);
}
private static boolean isFragment(Bundle bundle) {
return (bundle.adapt(BundleRevision.class).getTypes() & BundleRevision.TYPE_FRAGMENT) > 0;
}
private static String bundleEventToString(int type) {
String s;
switch (type) {
case BundleEvent.INSTALLED:
s = "INSTALLED";
break;
case BundleEvent.RESOLVED:
s = "RESOLVED";
break;
case BundleEvent.STARTING:
s = "STARTING";
break;
case BundleEvent.STARTED:
s = "STARTED";
break;
case BundleEvent.STOPPING:
s = "STOPPING";
break;
case BundleEvent.STOPPED:
s = "STOPPED";
break;
case BundleEvent.UNRESOLVED:
s = "UNRESOLVED";
break;
case BundleEvent.UPDATED:
s = "UPDATED";
break;
case BundleEvent.UNINSTALLED:
s = "UNINSTALLED";
break;
case BundleEvent.LAZY_ACTIVATION:
s = "LAZY_ACTIVATION";
break;
default:
s = "UNKNOWN";
}
return s;
}
private static Hash getContentHash(Resource resource) throws IllegalArgumentException {
Hash result = null;
List<Capability> contentCaps = resource.getCapabilities(ContentNamespace.CONTENT_NAMESPACE);
if (contentCaps != null) {
for (Capability contentCap : contentCaps) {
String hashHexStr = (String) contentCap.getAttributes().get(ContentNamespace.CONTENT_NAMESPACE);
byte[] hashHex = Hex.toByteArray(hashHexStr);
Hash hash = new Hash(SHA256, hashHex);
if (result != null && !hash.equals(result)) {
throw new IllegalArgumentException("Resource '" + resource + "' has multiple inconsistent content hashes.");
}
result = hash;
}
}
return result;
}
private void debug(String format, Object... args) {
if (DEBUG) {
System.out.printf("DEBUG: " + format + "%n", args);
}
}
}