blob: 87aa589794ee3b0842ee8df8c91b29968d727a6e [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.core.impl.tasks;
import java.text.MessageFormat;
import java.util.Iterator;
import org.apache.sling.installer.api.InstallableResource;
import org.apache.sling.installer.api.ResourceChangeListener;
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.ResourceState;
import org.apache.sling.installer.api.tasks.RetryHandler;
import org.apache.sling.installer.api.tasks.TaskResource;
import org.apache.sling.installer.api.tasks.TaskResourceGroup;
import org.apache.sling.installer.core.impl.BundleBlackList;
import org.apache.sling.installer.core.impl.EntityResourceList;
import org.apache.sling.installer.core.impl.InternalService;
import org.apache.sling.installer.core.impl.PersistentResourceList;
import org.apache.sling.installer.core.impl.RegisteredResourceImpl;
import org.apache.sling.installer.core.impl.Util;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleEvent;
import org.osgi.framework.BundleListener;
import org.osgi.framework.Constants;
import org.osgi.framework.FrameworkEvent;
import org.osgi.framework.FrameworkListener;
import org.osgi.framework.Version;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Task creator for bundles
*/
public class BundleTaskCreator
implements InternalService, InstallTaskFactory, FrameworkListener, BundleListener {
/** If this property is set, the bundle is installed if the currently installed version
* is the version specified by the property.
*/
private final static String FORCE_INSTALL_VERSION = "force.install.version";
/** The logger */
private final Logger logger = LoggerFactory.getLogger(this.getClass());
/** Support service for the tasks */
private TaskSupport taskSupport;
/** The bundle context. */
private BundleContext bundleContext;
/** The retry handler. */
private RetryHandler retryHandler;
private BundleBlackList bundleBlacklist;
private boolean isMultiVersion;
/**
* @see org.apache.sling.installer.core.impl.InternalService#init(org.osgi.framework.BundleContext, org.apache.sling.installer.api.ResourceChangeListener, RetryHandler)
*/
@Override
public void init(final BundleContext bc, final ResourceChangeListener listener, final RetryHandler retryHandler) {
this.bundleContext = bc;
this.retryHandler = retryHandler;
this.isMultiVersion = Boolean.TRUE.equals(Boolean.valueOf(bc.getProperty("sling.installer.multiversion")));
this.bundleContext.addBundleListener(this);
this.bundleContext.addFrameworkListener(this);
this.taskSupport = new TaskSupport(bc);
this.bundleBlacklist = new BundleBlackList(bc);
}
/**
* @see org.apache.sling.installer.core.impl.InternalService#deactivate()
*/
@Override
public void deactivate() {
if ( this.bundleContext != null ) {
this.bundleContext.removeBundleListener(this);
this.bundleContext.removeFrameworkListener(this);
}
if ( this.taskSupport != null ) {
this.taskSupport = null;
}
}
/**
* @see org.osgi.framework.FrameworkListener#frameworkEvent(org.osgi.framework.FrameworkEvent)
*/
@Override
public void frameworkEvent(final FrameworkEvent event) {
if ( event.getType() == FrameworkEvent.PACKAGES_REFRESHED ) {
logger.debug("Received FrameworkEvent triggering a retry of the installer: {}", event);
this.retryHandler.scheduleRetry();
}
}
/**
* @see org.osgi.framework.BundleListener#bundleChanged(org.osgi.framework.BundleEvent)
*/
@Override
public void bundleChanged(final BundleEvent event) {
final int t = event.getType();
if (t == BundleEvent.INSTALLED || t == BundleEvent.RESOLVED || t == BundleEvent.STARTED || t == BundleEvent.UPDATED) {
logger.debug("Received BundleEvent triggering a retry of the installer: {}", event);
this.retryHandler.scheduleRetry();
}
}
/**
* @see org.apache.sling.installer.core.impl.InternalService#getDescription()
*/
@Override
public String getDescription() {
return "Apache Sling Bundle Install Task Factory";
}
/**
* Create a bundle task - install, update or remove
*
* @see org.apache.sling.installer.api.tasks.InstallTaskFactory#createTask(org.apache.sling.installer.api.tasks.TaskResourceGroup)
*/
@Override
public InstallTask createTask(final TaskResourceGroup resourceList) {
// quick check of the resource type.
final TaskResource toActivate = resourceList.getActiveResource();
if ( toActivate.getType().equals(PersistentResourceList.RESTART_ACTIVE_BUNDLES_TYPE) ) {
return new RestartActiveBundlesTask(resourceList, this.taskSupport);
}
if ( !toActivate.getType().equals(InstallableResource.TYPE_BUNDLE) ) {
return null;
}
// check if symbolic name and version is provided in the attributes
if ( toActivate.getAttribute(Constants.BUNDLE_SYMBOLICNAME) == null ) {
final Util.BundleHeaders headers = Util.readBundleHeaders(toActivate, logger);
if ( headers == null ) {
String message = MessageFormat.format("Resource of type bundle {0} is not really a bundle - manifest entries are missing.", toActivate);
logger.info(message);
return new ChangeStateTask(resourceList, ResourceState.IGNORED, message);
}
toActivate.setAttribute(Constants.BUNDLE_SYMBOLICNAME, headers.symbolicName);
toActivate.setAttribute(Constants.BUNDLE_VERSION, headers.version);
if ( headers.activationPolicy != null ) {
toActivate.setAttribute(Constants.BUNDLE_ACTIVATIONPOLICY, headers.activationPolicy);
}
}
final String symbolicName = (String)toActivate.getAttribute(Constants.BUNDLE_SYMBOLICNAME);
final boolean isInstallerCoreBundle = this.bundleContext.getBundle().getSymbolicName().equals(symbolicName);
// Uninstall
final InstallTask result;
if (toActivate.getState() == ResourceState.UNINSTALL) {
// find the info with the exact version
final BundleInfo info = this.getBundleInfo(
symbolicName,
(String)toActivate.getAttribute(Constants.BUNDLE_VERSION));
// Remove corresponding bundle if present and if we installed it
if ( info != null ) {
// if this is an uninstall, check if we have to install an older version
// in this case we should do an update instead of uninstall/install (!)
Iterator<TaskResource> candidatesIt = ((EntityResourceList)resourceList).getActiveResourceIterator();
TaskResource second = null;
while (candidatesIt != null && second == null && candidatesIt.hasNext()) {
TaskResource candidate = candidatesIt.next();
boolean sameVersion = toActivate.getVersion().equals(candidate.getVersion());
if (!sameVersion) {
if (bundleBlacklist.isBlacklisted(symbolicName, candidate.getVersion())) {
String message = MessageFormat.format("Uninstalling blacklisted bundle {0} found at {1}", symbolicName, candidate.getURL());
logger.info(message);
// blacklisted candidates should be uninstalled to no longer be taken into account anymore
((RegisteredResourceImpl)candidate).setState(ResourceState.UNINSTALL, message);
} else {
second = candidate;
}
}
}
if ( second != null &&
( second.getState() == ResourceState.IGNORED || second.getState() == ResourceState.INSTALLED || second.getState() == ResourceState.INSTALL ) ) {
second.setAttribute(FORCE_INSTALL_VERSION, info.version.toString());
BundleUtil.clearBundleStart(second);
logger.debug("Detected downgrade of bundle {}", symbolicName);
result = new ChangeStateTask(resourceList, ResourceState.UNINSTALLED, null);
} else {
// prevent uninstalling the installer itself!
if ( isInstallerCoreBundle ) {
logger.debug("Prevent completely uninstalling installer bundle {}", symbolicName);
result = new ChangeStateTask(resourceList, ResourceState.UNINSTALLED, null);
} else {
result = new BundleRemoveTask(resourceList, this.taskSupport);
}
}
} else {
logger.debug("Bundle {}:{} is not installed anymore - nothing to remove.", symbolicName,
toActivate.getAttribute(Constants.BUNDLE_VERSION));
result = new ChangeStateTask(resourceList, ResourceState.UNINSTALLED, null);
}
// Install
} else {
// check for installer and system update
final Integer asyncTaskCounter = (Integer)toActivate.getAttribute(InstallTask.ASYNC_ATTR_NAME);
if ( asyncTaskCounter != null ) {
if ( isInstallerCoreBundle ) {
result = new InstallerBundleUpdateTask(resourceList, this.taskSupport);
} else {
// system bundle
result = new ChangeStateTask(resourceList, ResourceState.INSTALLED, null,
new String[] {InstallTask.ASYNC_ATTR_NAME}, null);
}
} else {
final Version newVersion = new Version((String) toActivate.getAttribute(Constants.BUNDLE_VERSION));
if (bundleBlacklist.isBlacklisted(symbolicName, newVersion)) {
String message = MessageFormat.format("Ignoring blacklisted bundle {0} found at {1}", symbolicName, toActivate.getURL());
logger.info(message);
result = new ChangeStateTask(resourceList, ResourceState.IGNORED, message);
} else {
// if not isMultiVersion for install and update, we want the bundle with the
// highest version - otherwise only check for very same version (potential updates of snapshots)
String bundleVersion = null;
if (this.isMultiVersion) {
bundleVersion = newVersion.toString();
}
final BundleInfo info = this.getBundleInfo(symbolicName, bundleVersion);
// check if we should start the bundle as we installed it in the previous run
if (info == null) {
// bundle is not installed yet: install
result = new BundleInstallTask(resourceList, this.taskSupport);
} else if ( BundleUtil.isBundleStart(toActivate) ) {
result = new BundleStartTask(resourceList, info.id, this.taskSupport);
} else {
boolean doUpdate = false;
final int compare = info.version.compareTo(newVersion);
if (compare < 0) {
// installed version is lower -> update
doUpdate = true;
} else if (compare > 0) {
final String forceVersion = (String) toActivate.getAttribute(FORCE_INSTALL_VERSION);
if ( forceVersion != null && info.version.compareTo(new Version(forceVersion)) == 0 ) {
doUpdate = true;
} else {
logger.debug("Bundle " + info.symbolicName + " " + newVersion
+ " is not installed, bundle with higher version is already installed.");
}
} else if (compare == 0 && BundleInfo.isSnapshot(newVersion)) {
// installed, same version but SNAPSHOT
doUpdate = true;
}
if (doUpdate) {
logger.debug("Scheduling update of {}", toActivate);
// check if this is the system bundle
if ( Constants.SYSTEM_BUNDLE_SYMBOLICNAME.equals(symbolicName) ) {
result = new SystemBundleUpdateTask(resourceList, this.taskSupport);
// check if this is a installer update
} else if ( isInstallerCoreBundle ) {
result = new InstallerBundleUpdateTask(resourceList, this.taskSupport);
} else {
result = new BundleUpdateTask(resourceList, this.taskSupport);
}
} else if ( compare == 0 && (isInstallerCoreBundle || Constants.SYSTEM_BUNDLE_SYMBOLICNAME.equals(symbolicName)) ) {
// the installer core bundle / system bundle has been updated, just set state
result = new ChangeStateTask(resourceList, ResourceState.INSTALLED, "This is the system bundle, therefore nothing was actually done!");
} else {
String message = MessageFormat.format("Nothing to install for {0}, same or newer version {1} already installed.", toActivate, newVersion);
logger.debug(message);
result = new ChangeStateTask(resourceList, ResourceState.IGNORED, message);
}
}
}
}
toActivate.setAttribute(FORCE_INSTALL_VERSION, null);
}
return result;
}
protected BundleInfo getBundleInfo(final String symbolicName, final String version) {
return BundleInfo.getBundleInfo(this.bundleContext, symbolicName, version);
}
}