| /* |
| * 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; |
| } |
| } |