blob: c58d7a363eea051345c29be962e02fe809cce1ec [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.sling.installer.factories.subsystems.impl;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.jar.Manifest;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import org.apache.sling.installer.api.InstallableResource;
import org.apache.sling.installer.api.tasks.ChangeStateTask;
import org.apache.sling.installer.api.tasks.InstallTask;
import org.apache.sling.installer.api.tasks.InstallTaskFactory;
import org.apache.sling.installer.api.tasks.RegisteredResource;
import org.apache.sling.installer.api.tasks.ResourceState;
import org.apache.sling.installer.api.tasks.ResourceTransformer;
import org.apache.sling.installer.api.tasks.TaskResource;
import org.apache.sling.installer.api.tasks.TaskResourceGroup;
import org.apache.sling.installer.api.tasks.TransformationResult;
import org.osgi.framework.BundleContext;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.Version;
import org.osgi.service.subsystem.Subsystem;
import org.osgi.service.subsystem.Subsystem.State;
import org.osgi.service.subsystem.SubsystemConstants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This is an extension for the OSGi installer
* It listens for files ending with ".esa" and a proper subsystem manifest.
* Though subsystems does not require a complete manifest, the installer supports
* only subsystems with the basic info (name and version).
*
* As subsystems currently do not support an update, an uninstall/install is done
* instead - which will lose bundle private data, bound configurations etc.
*/
public class SubsystemInstaller
implements ResourceTransformer, InstallTaskFactory {
private static final String TYPE_SUBSYSTEM = "esa";
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private final Subsystem rootSubsystem;
private final BundleContext bundleContext;
public SubsystemInstaller(final Subsystem root, final BundleContext bundleContext) {
this.rootSubsystem = root;
this.bundleContext = bundleContext;
}
/**
* @see org.apache.sling.installer.api.tasks.ResourceTransformer#transform(org.apache.sling.installer.api.tasks.RegisteredResource)
*/
public TransformationResult[] transform(final RegisteredResource resource) {
if ( resource.getType().equals(InstallableResource.TYPE_FILE) ) {
if ( resource.getURL().endsWith("." + TYPE_SUBSYSTEM) ) {
logger.info("Found potential subsystem resource {}", resource);
final SubsystemInfo headers = readSubsystemHeaders(resource);
if ( headers != null ) {
// check the version for validity
boolean validVersion = true;
try {
new Version(headers.version);
} catch (final IllegalArgumentException iae) {
logger.info("Rejecting subsystem {} from {} due to invalid version information: {}.",
new Object[] {headers.symbolicName, resource, headers.version});
validVersion = false;
}
if ( validVersion ) {
final Map<String, Object> attr = new HashMap<String, Object>();
attr.put(SubsystemConstants.SUBSYSTEM_SYMBOLICNAME, headers.symbolicName);
attr.put(SubsystemConstants.SUBSYSTEM_VERSION, headers.version);
final TransformationResult tr = new TransformationResult();
tr.setId(headers.symbolicName);
tr.setResourceType(TYPE_SUBSYSTEM);
tr.setAttributes(attr);
tr.setVersion(new Version(headers.version));
return new TransformationResult[] {tr};
}
} else {
logger.info("Subsystem resource does not have required headers.");
}
}
}
return null;
}
/**
* Check that the required attributes are available.
* This is just a sanity check
*/
private SubsystemInfo checkResource(final TaskResourceGroup toActivate) {
final TaskResource rsrc = toActivate.getActiveResource();
SubsystemInfo result = null;
final String symbolicName = (String) rsrc.getAttribute(SubsystemConstants.SUBSYSTEM_SYMBOLICNAME);
if ( symbolicName == null ) {
logger.error("Subsystem resource is missing symbolic name {}", rsrc);
} else {
final String version = (String)rsrc.getAttribute(SubsystemConstants.SUBSYSTEM_VERSION);
if ( version == null ) {
logger.error("Subsystem resource is missing version {}", rsrc);
} else {
// check the version for validity
boolean validVersion = true;
try {
new Version(version);
} catch (final IllegalArgumentException iae) {
logger.info("Rejecting subsystem {} from {} due to invalid version information: {}.",
new Object[] {symbolicName, rsrc, version});
validVersion = false;
}
if ( validVersion ) {
result = new SubsystemInfo();
result.symbolicName = symbolicName;
result.version = version;
}
}
}
return result;
}
private ServiceReference<Subsystem> getSubsystemReference(final String symbolicName) {
// search a subsystem with the symbolic name
ServiceReference<Subsystem> ref = null;
try {
final Collection<ServiceReference<Subsystem>> refs = this.bundleContext.getServiceReferences(Subsystem.class, "(subsystem.symbolicName=" + symbolicName + ")");
if ( refs.size() > 0 ) {
ref = refs.iterator().next();
}
} catch (final InvalidSyntaxException e) {
logger.error("Problem searching for subsystem with symbolic name " + symbolicName, e);
}
return ref;
}
/**
* @see org.apache.sling.installer.api.tasks.InstallTaskFactory#createTask(org.apache.sling.installer.api.tasks.TaskResourceGroup)
*/
public InstallTask createTask(final TaskResourceGroup toActivate) {
final InstallTask result;
final TaskResource rsrc = toActivate.getActiveResource();
if ( rsrc.getType().equals(TYPE_SUBSYSTEM) ) {
// check if the required info is available
final SubsystemInfo info = checkResource(toActivate);
if ( info == null ) {
// ignore as info is missing
result = new ChangeStateTask(toActivate, ResourceState.IGNORED);
} else {
// search a subsystem with the symbolic name
final ServiceReference<Subsystem> ref = this.getSubsystemReference(info.symbolicName);
final Subsystem currentSubsystem = (ref != null ? this.bundleContext.getService(ref) : null);
try {
final Version newVersion = new Version(info.version);
final Version oldVersion = (ref == null ? null : (Version)ref.getProperty("subsystem.version"));
// Install
if ( rsrc.getState() == ResourceState.INSTALL ) {
if ( oldVersion != null ) {
final int compare = oldVersion.compareTo(newVersion);
if (compare < 0) {
// installed version is lower -> update
result = new UpdateSubsystemTask(toActivate, this.bundleContext, ref, this.rootSubsystem);
} else if ( compare == 0 && isSnapshot(newVersion) ) {
// same version but snapshot -> update
result = new UpdateSubsystemTask(toActivate, this.bundleContext, ref, this.rootSubsystem);
} else if ( compare == 0 && currentSubsystem != null && currentSubsystem.getState() != State.ACTIVE ) {
// try to start the version
result = new StartSubsystemTask(toActivate, currentSubsystem);
} else {
logger.info("{} is not installed, subsystem with same or higher version is already installed: {}", info, newVersion);
result = new ChangeStateTask(toActivate, ResourceState.IGNORED);
}
} else {
result = new InstallSubsystemTask(toActivate, this.rootSubsystem);
}
// Uninstall
} else if ( rsrc.getState() == ResourceState.UNINSTALL ) {
if ( oldVersion == null ) {
logger.error("Nothing to uninstall. {} is currently not installed.", info);
result = new ChangeStateTask(toActivate, ResourceState.IGNORED);
} else {
final int compare = oldVersion.compareTo(newVersion);
if ( compare == 0 ) {
result = new UninstallSubsystemTask(toActivate, this.bundleContext, ref);
} else {
logger.error("Nothing to uninstall. {} is currently not installed, different version is installed {}", info, oldVersion);
result = new ChangeStateTask(toActivate, ResourceState.IGNORED);
}
}
} else {
result = null;
}
} finally {
if ( currentSubsystem != null ) {
this.bundleContext.ungetService(ref);
}
}
}
} else {
result = null;
}
return result;
}
/**
* Read the manifest from supplied input stream, which is closed before return.
*/
private static Manifest getManifest(final RegisteredResource rsrc, final Logger logger)
throws IOException {
final InputStream ins = rsrc.getInputStream();
Manifest result = null;
if ( ins != null ) {
ZipInputStream jis = null;
try {
jis = new ZipInputStream(ins);
ZipEntry entry;
while ( (entry = jis.getNextEntry()) != null ) {
if (entry.getName().equals("OSGI-INF/SUBSYSTEM.MF") ) {
result = new Manifest(jis);
}
}
} finally {
// close the jar stream or the input stream, if the jar
// stream is set, we don't need to close the input stream
// since closing the jar stream closes the input stream
if (jis != null) {
try {
jis.close();
} catch (IOException ignore) {
}
} else {
try {
ins.close();
} catch (IOException ignore) {
}
}
}
}
return result;
}
final static public class SubsystemInfo {
public String symbolicName;
public String version;
@Override
public String toString() {
return "Subsystem[symbolicName=" + symbolicName + ", version="
+ version + "]";
}
}
/**
* Read the subsystem info from the manifest (if available)
*/
private SubsystemInfo readSubsystemHeaders(final RegisteredResource resource) {
try {
final Manifest m = SubsystemInstaller.getManifest(resource, logger);
if (m != null) {
final String sn = m.getMainAttributes().getValue(SubsystemConstants.SUBSYSTEM_SYMBOLICNAME);
if (sn != null) {
final String v = m.getMainAttributes().getValue(SubsystemConstants.SUBSYSTEM_VERSION);
final int paramPos = sn.indexOf(';');
final String symbolicName = (paramPos == -1 ? sn : sn.substring(0, paramPos));
final SubsystemInfo headers = new SubsystemInfo();
headers.symbolicName = symbolicName;
headers.version = v;
// if no version is specified, use default version
if ( headers.version == null ) {
headers.version = "0.0.0.0";
}
return headers;
}
}
} catch (final IOException ignore) {
// ignore
}
return null;
}
private static final String MAVEN_SNAPSHOT_MARKER = "SNAPSHOT";
/**
* Check if the version is a snapshot version
*/
public static boolean isSnapshot(final Version v) {
return v.toString().indexOf(MAVEN_SNAPSHOT_MARKER) >= 0;
}
}