| /* |
| * 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; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.text.MessageFormat; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.Dictionary; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.SortedSet; |
| import java.util.TreeSet; |
| import java.util.concurrent.atomic.AtomicLong; |
| |
| import org.apache.sling.commons.osgi.PropertiesUtil; |
| import org.apache.sling.installer.api.InstallableResource; |
| import org.apache.sling.installer.api.OsgiInstaller; |
| import org.apache.sling.installer.api.ResourceChangeListener; |
| import org.apache.sling.installer.api.UpdateHandler; |
| import org.apache.sling.installer.api.UpdateResult; |
| import org.apache.sling.installer.api.info.InfoProvider; |
| import org.apache.sling.installer.api.info.InstallationState; |
| import org.apache.sling.installer.api.info.Resource; |
| import org.apache.sling.installer.api.info.ResourceGroup; |
| 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.InstallationContext; |
| 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.ResourceUpdater; |
| 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.api.tasks.TransformationResult; |
| import org.apache.sling.installer.api.tasks.UpdatableResourceGroup; |
| import org.apache.sling.installer.core.impl.tasks.BundleUpdateTask; |
| import org.jetbrains.annotations.Nullable; |
| import org.osgi.framework.Bundle; |
| import org.osgi.framework.BundleContext; |
| import org.osgi.framework.Constants; |
| import org.osgi.framework.ServiceReference; |
| import org.osgi.framework.Version; |
| import org.osgi.service.startlevel.StartLevel; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| /** |
| * Worker thread where all OSGi tasks are executed. |
| * Runs cycles where the list of RegisteredResources is examined, |
| * OsgiTasks are created accordingly and executed. |
| * |
| * A separate list of RegisteredResources is kept for resources |
| * that are updated or removed during a cycle, and merged with |
| * the main list at the end of the cycle. |
| */ |
| public class OsgiInstallerImpl |
| implements OsgiInstaller, ResourceChangeListener, RetryHandler, InfoProvider, Runnable { |
| |
| /** |
| * The name of the framework property defining handling of bundle updates |
| */ |
| private static final String PROP_START_LEVEL_HANDLING = "sling.installer.switchstartlevel"; |
| |
| /** |
| * The name of the framework property setting required services |
| */ |
| private static final String PROP_REQUIRED_SERVICES = "sling.installer.requiredservices"; |
| |
| /** The logger */ |
| private final Logger logger = LoggerFactory.getLogger(this.getClass()); |
| |
| /** The audit logger */ |
| private final Logger auditLogger = LoggerFactory.getLogger("org.apache.sling.audit.osgi.installer"); |
| |
| /** The bundle context. */ |
| private final BundleContext ctx; |
| |
| /** New clients are joining through this map. */ |
| private final Map<String, List<InternalResource>> newResourcesSchemes = new HashMap<>(); |
| |
| /** New resources added by clients. */ |
| private final List<InternalResource> newResources = new LinkedList<>(); |
| |
| /** Removed resources from clients. */ |
| private final Set<String> urlsToRemove = new HashSet<>(); |
| |
| /** Update infos to process. */ |
| private final List<UpdateInfo> updateInfos = new ArrayList<>(); |
| |
| /** Are the required services satisfied? */ |
| private volatile boolean satisfied = false; |
| |
| /** Are we still activate? */ |
| private volatile boolean active = true; |
| |
| /** Are we still running? */ |
| private volatile Thread backgroundThread; |
| |
| /** Flag indicating that a retry event occured during tasks executions. */ |
| private volatile boolean retryDuringTaskExecution = false; |
| |
| /** The persistent resource list. */ |
| private PersistentResourceList persistentList; |
| |
| /** A tracker for the factories. */ |
| private SortingServiceTracker<InstallTaskFactory> factoryTracker; |
| |
| /** A tracker for the transformers. */ |
| private SortingServiceTracker<ResourceTransformer> transformerTracker; |
| |
| /** A tracker for update handlers. */ |
| private SortingServiceTracker<UpdateHandler> updateHandlerTracker; |
| |
| /** A tracker for the factories. */ |
| private SortingServiceTracker<ResourceUpdater> updaterTracker; |
| |
| /** New resources lock. */ |
| private final Object resourcesLock = new Object(); |
| |
| private final InstallListener listener; |
| private final AtomicLong backgroundTaskCounter = new AtomicLong(); |
| |
| /** Switch start level on bundle update? */ |
| private final boolean switchStartLevel; |
| |
| /** |
| * Constructor |
| * |
| * Most of the initialization is deferred to the background thread |
| */ |
| public OsgiInstallerImpl(final BundleContext ctx) { |
| this.ctx = ctx; |
| // Initialize file util |
| new FileDataStore(ctx); |
| final File f = FileDataStore.SHARED.getDataFile("RegisteredResourceList.ser"); |
| this.listener = new InstallListener(ctx, logger); |
| this.persistentList = new PersistentResourceList(f, listener); |
| this.switchStartLevel = PropertiesUtil.toBoolean(ctx.getProperty(PROP_START_LEVEL_HANDLING), false); |
| } |
| |
| /** |
| * Deactivate |
| */ |
| public void deactivate() { |
| // wake up sleeping thread |
| synchronized (this.resourcesLock) { |
| logger.debug("Deactivating and notifying resourcesLock"); |
| this.active = false; |
| this.resourcesLock.notify(); |
| } |
| |
| // Stop service trackers. |
| if ( this.factoryTracker != null ) { |
| this.factoryTracker.close(); |
| } |
| if ( this.transformerTracker != null ) { |
| this.transformerTracker.close(); |
| } |
| if ( this.updateHandlerTracker != null ) { |
| this.updateHandlerTracker.close(); |
| } |
| if ( this.updaterTracker != null ) { |
| this.updaterTracker.close(); |
| } |
| |
| this.listener.dispose(); |
| |
| if ( this.backgroundThread != null ) { |
| if ( logger.isDebugEnabled() ) { |
| final Thread t = this.backgroundThread; |
| if ( t != null ) { |
| logger.debug("Waiting for main background thread {} to stop", t.getName()); |
| } |
| } |
| |
| while ( this.backgroundThread != null ) { |
| // use a local variable to avoid NPEs |
| final Thread t = backgroundThread; |
| if ( t != null ) { |
| try { |
| t.join(50L); |
| } catch (final InterruptedException e) { |
| // we simply ignore this |
| } |
| } |
| } |
| logger.debug("Done waiting for background thread"); |
| } |
| |
| // remove file util |
| FileDataStore.SHARED = null; |
| |
| this.logger.info("Apache Sling OSGi Installer Service stopped."); |
| } |
| |
| /** |
| * Start this component. |
| */ |
| public void start() { |
| this.startBackgroundThread(); |
| } |
| |
| /** |
| * Initialize the installer |
| */ |
| private void init() { |
| // start service trackers |
| this.factoryTracker = new SortingServiceTracker<>(ctx, InstallTaskFactory.class.getName(), this); |
| this.transformerTracker = new SortingServiceTracker<>(ctx, ResourceTransformer.class.getName(), this); |
| this.updateHandlerTracker = new SortingServiceTracker<>(ctx, UpdateHandler.class.getName(), null); |
| this.updaterTracker = new SortingServiceTracker<>(ctx, ResourceUpdater.class.getName(), this); |
| this.factoryTracker.open(); |
| this.transformerTracker.open(); |
| this.updateHandlerTracker.open(); |
| this.updaterTracker.open(); |
| |
| this.logger.info("Apache Sling OSGi Installer Service started."); |
| this.checkSatisfied(); |
| } |
| |
| /** |
| * Start the background thread. |
| */ |
| private void startBackgroundThread() { |
| this.backgroundThread = new Thread(this); |
| this.backgroundThread.setName(getClass().getSimpleName()); |
| this.backgroundThread.setDaemon(true); |
| this.backgroundThread.start(); |
| } |
| |
| /** |
| * @see java.lang.Runnable#run() |
| */ |
| @Override |
| public void run() { |
| logger.debug("Main background thread starts"); |
| try { |
| this.init(); |
| |
| while (this.active) { |
| this.listener.start(); |
| |
| this.handleResourceUpdaters(); |
| |
| this.processUpdateInfos(); |
| |
| // merge potential new resources |
| this.mergeNewlyRegisteredResources(); |
| |
| synchronized ( this.resourcesLock ) { |
| if ( !this.satisfied ) { |
| logger.debug("Required services are not available yet."); |
| try { |
| logger.debug("wait() on resourcesLock"); |
| this.resourcesLock.wait(); |
| } catch (final InterruptedException ignore) {} |
| continue; |
| } |
| this.retryDuringTaskExecution = false; |
| } |
| |
| // invoke transformers |
| this.transformResources(); |
| |
| // Compute tasks |
| final SortedSet<InstallTask> tasks = this.computeTasks(); |
| |
| // execute tasks and see if we have to stop processing |
| final ACTION action = this.executeTasks(tasks); |
| if ( action == ACTION.SLEEP ) { |
| synchronized ( this.resourcesLock ) { |
| // before we go to sleep, check if new resources arrived in the meantime |
| if ( !this.hasNewResources() && this.active && !this.retryDuringTaskExecution) { |
| // No tasks to execute - wait until new resources are |
| // registered |
| logger.debug("No more tasks to process, suspending listener and going idle"); |
| this.listener.suspend(); |
| |
| try { |
| logger.debug("wait() on resourcesLock"); |
| this.resourcesLock.wait(); |
| } catch (final InterruptedException ignore) {} |
| |
| if ( active ) { |
| logger.debug("Done wait()ing on resourcesLock, restarting listener"); |
| this.listener.start(); |
| } else { |
| logger.debug("Done wait()ing on resourcesLock, but active={}, listener won't be restarted now", active); |
| } |
| } |
| } |
| } else if ( action == ACTION.SHUTDOWN ) { |
| // stop processing |
| logger.debug("Action is SHUTDOWN, going inactive"); |
| active = false; |
| } |
| |
| } |
| this.listener.suspend(); |
| } catch ( final Exception fatal) { |
| logger.error("An unexpected error occured in the installer task. Installer is stopped now!", fatal); |
| } finally { |
| this.backgroundThread = null; |
| } |
| logger.debug("Main background thread ends"); |
| } |
| |
| /** |
| * Wake up the run cycle. |
| */ |
| private void wakeUp() { |
| logger.debug("wakeUp called"); |
| this.listener.start(); |
| synchronized (this.resourcesLock) { |
| this.resourcesLock.notify(); |
| } |
| } |
| |
| /** |
| * Checks if new resources are available. |
| * This method should only be invoked from within a synchronized (newResources) block! |
| */ |
| private boolean hasNewResources() { |
| return !this.newResources.isEmpty() |
| || !this.newResourcesSchemes.isEmpty() |
| || !this.urlsToRemove.isEmpty() |
| || !this.updateInfos.isEmpty(); |
| } |
| |
| /** |
| * Check the scheme |
| * @throws IllegalArgumentException |
| */ |
| private void checkScheme(final String scheme) { |
| if ( scheme == null || scheme.length() == 0 ) { |
| throw new IllegalArgumentException("Scheme required"); |
| } |
| if ( scheme.indexOf(':') != -1 ) { |
| throw new IllegalArgumentException("Scheme must not contain a colon"); |
| } |
| } |
| |
| /** |
| * Create new installable resources for all installable resources. |
| * The new versions has a set resource type. |
| */ |
| private List<InternalResource> createResources(final String scheme, |
| final InstallableResource[] resources) { |
| checkScheme(scheme); |
| List<InternalResource> createdResources = null; |
| if ( resources != null && resources.length > 0 ) { |
| createdResources = new ArrayList<>(); |
| for(final InstallableResource r : resources ) { |
| try { |
| final InternalResource rr = InternalResource.create(scheme, r); |
| createdResources.add(rr); |
| logger.debug("Registering new resource: {}", rr); |
| } catch (final Exception e) { |
| logger.warn("Cannot create InternalResource (resource will be ignored):" + r, e); |
| } |
| } |
| } |
| return createdResources; |
| } |
| |
| /** |
| * Try to close all input streams. |
| * This is just a sanity check for input streams which might not have been closed |
| * as either processing threw an exception or the resource type is not supported. |
| */ |
| private void closeInputStreams(final InstallableResource[] resources) { |
| if ( resources != null ) { |
| for(final InstallableResource r : resources ) { |
| final InputStream is = r.getInputStream(); |
| if ( is != null ) { |
| try { |
| is.close(); |
| } catch (final IOException ignore) { |
| // ignore |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * @see org.apache.sling.installer.api.OsgiInstaller#updateResources(java.lang.String, org.apache.sling.installer.api.InstallableResource[], java.lang.String[]) |
| */ |
| @Override |
| public void updateResources(final String scheme, |
| final InstallableResource[] resources, |
| final String[] idsToRemove) { |
| this.listener.start(); |
| try { |
| final List<InternalResource> updatedResources = createResources(scheme, resources); |
| |
| synchronized ( this.resourcesLock ) { |
| if ( updatedResources != null && updatedResources.size() > 0 ) { |
| this.newResources.addAll(updatedResources); |
| final Set<String> newUrls = new HashSet<>(); |
| for(final InternalResource rsrc : updatedResources) { |
| newUrls.add(rsrc.getURL()); |
| } |
| // now remove this from urlsToRemove |
| final Iterator<String> urlIter = this.urlsToRemove.iterator(); |
| while ( urlIter.hasNext() && !newUrls.isEmpty() ) { |
| final String url = urlIter.next(); |
| if ( newUrls.remove(url) ) { |
| urlIter.remove(); |
| } |
| } |
| } |
| if ( idsToRemove != null && idsToRemove.length > 0 ) { |
| final Set<String> removedUrls = new HashSet<>(); |
| for(final String id : idsToRemove) { |
| final String url = scheme + ':' + id; |
| // Will mark all resources which have r's URL as uninstallable |
| this.urlsToRemove.add(url); |
| removedUrls.add(url); |
| } |
| // now update newResources |
| final Iterator<InternalResource> rsrcIter = this.newResources.iterator(); |
| while ( rsrcIter.hasNext() && !removedUrls.isEmpty() ) { |
| final InternalResource rsrc = rsrcIter.next(); |
| if ( removedUrls.remove(rsrc.getURL()) ) { |
| if ( rsrc.getPrivateCopyOfFile() != null ) { |
| rsrc.getPrivateCopyOfFile().delete(); |
| } |
| rsrcIter.remove(); |
| } |
| } |
| } |
| } |
| this.wakeUp(); |
| } finally { |
| // we simply close all input streams now |
| this.closeInputStreams(resources); |
| } |
| } |
| |
| /** |
| * @see org.apache.sling.installer.api.OsgiInstaller#registerResources(java.lang.String, org.apache.sling.installer.api.InstallableResource[]) |
| */ |
| @Override |
| public void registerResources(final String scheme, final InstallableResource[] resources) { |
| this.listener.start(); |
| try { |
| List<InternalResource> incomingResources = this.createResources(scheme, resources); |
| if ( incomingResources == null ) { |
| // create empty list to make processing easier |
| incomingResources = new ArrayList<>(); |
| } |
| logger.debug("Registered new resource scheme: {}", scheme); |
| synchronized (this.resourcesLock) { |
| this.newResourcesSchemes.put(scheme, incomingResources); |
| |
| // Update new/removed resources |
| final String prefix = scheme + ':'; |
| |
| // newResources are the ones that arrived (via updateResources IIUC) |
| // since the last call to this method. Here we remove all newResources |
| // that match our prefix, as the incoming ones replace them |
| final Iterator<InternalResource> rsrcIter = this.newResources.iterator(); |
| while ( rsrcIter.hasNext() ) { |
| final InternalResource rsrc = rsrcIter.next(); |
| if ( rsrc.getURL().startsWith(prefix) ) { |
| prepareToRemove(rsrc, incomingResources); |
| rsrcIter.remove(); |
| } |
| } |
| |
| // removed urls |
| final Iterator<String> urlIter = this.urlsToRemove.iterator(); |
| while ( urlIter.hasNext() ) { |
| final String url = urlIter.next(); |
| if ( url.startsWith(prefix) ) { |
| urlIter.remove(); |
| } |
| } |
| } |
| this.wakeUp(); |
| } finally { |
| // we simply close all input streams now |
| this.closeInputStreams(resources); |
| } |
| } |
| |
| /** When a resource from "incoming" is about to replace "existing", we might need to transfer their private |
| * data file, or delete it if it's not needed anymore. |
| */ |
| private void prepareToRemove(InternalResource existing, Collection<InternalResource> incoming) { |
| if(existing.getPrivateCopyOfFile() != null) { |
| for(final InternalResource r : incoming) { |
| if(r.getURL().equals(existing.getURL())) { |
| // We have a resource r in "incoming" that's the same as "existing" |
| if(r.getPrivateCopyOfFile() == null) { |
| // New one has not data file, use the existing one |
| logger.debug("{} has no private data file, using the one from {}", r.getURL(), existing.getURL()); |
| r.setPrivateCopyOfFile(existing.getPrivateCopyOfFile()); |
| existing.setPrivateCopyOfFile(null); |
| } else if(r.getPrivateCopyOfFile().equals(existing.getPrivateCopyOfFile())) { |
| logger.debug("{} has same private data file as existing resource, keeping it", r.getURL()); |
| existing.setPrivateCopyOfFile(null); |
| } |
| break; |
| } |
| } |
| |
| if(existing.getPrivateCopyOfFile() != null) { |
| logger.debug("Private data file not needed anymore, deleting it: {}", existing.getURL()); |
| existing.getPrivateCopyOfFile().delete(); |
| } |
| } |
| } |
| |
| private void mergeNewlyRegisteredResources() { |
| synchronized ( this.resourcesLock ) { |
| for(final Map.Entry<String, List<InternalResource>> entry : this.newResourcesSchemes.entrySet()) { |
| final String scheme = entry.getKey(); |
| final List<InternalResource> registeredResources = entry.getValue(); |
| |
| logger.debug("Processing set of new resources with scheme {}", scheme); |
| |
| // set all previously found resources that are not available anymore to uninstall |
| // if they have been installed - remove resources with a different state |
| for(final String entityId : this.persistentList.getEntityIds()) { |
| final EntityResourceList group = this.persistentList.getEntityResourceList(entityId); |
| |
| final List<TaskResource> toRemove = new ArrayList<>(); |
| boolean first = true; |
| for(final TaskResource r : group.listResources()) { |
| if ( r.getScheme().equals(scheme) ) { |
| logger.debug("Checking {}", r); |
| // search if we have a new entry with the same url |
| boolean found = false; |
| if ( registeredResources != null ) { |
| final Iterator<InternalResource> m = registeredResources.iterator(); |
| while ( !found && m.hasNext() ) { |
| final InternalResource testResource = m.next(); |
| found = testResource.getURL().equals(r.getURL()); |
| } |
| } |
| if ( !found) { |
| logger.debug("Resource {} seems to be removed.", r); |
| if ( first && (r.getState() == ResourceState.INSTALLED |
| || r.getState() == ResourceState.INSTALL) ) { |
| ((RegisteredResourceImpl)r).setState(ResourceState.UNINSTALL, null); |
| } else { |
| toRemove.add(r); |
| } |
| } |
| } |
| first = false; |
| } |
| for(final TaskResource rr : toRemove) { |
| this.persistentList.remove(rr.getURL()); |
| } |
| } |
| if ( registeredResources != null ) { |
| this.newResources.addAll(registeredResources); |
| } |
| } |
| this.newResourcesSchemes.clear(); |
| this.mergeNewResources(); |
| |
| printResources("Merged"); |
| // persist list |
| this.persistentList.save(); |
| } |
| } |
| |
| /** |
| * Process new resources and deleted resources and |
| * merge them with existing resources. |
| */ |
| private void mergeNewResources() { |
| // if we have new resources we have to sync them |
| if ( newResources.size() > 0 ) { |
| logger.debug("Added set of {} new resources: {}", |
| new Object[] {newResources.size(), newResources}); |
| |
| for(final InternalResource r : newResources) { |
| this.persistentList.addOrUpdate(r); |
| } |
| newResources.clear(); |
| } |
| // Mark resources for removal according to urlsToRemove |
| if (!urlsToRemove.isEmpty()) { |
| logger.debug("Removing set of {} resources: {}", |
| new Object[] {urlsToRemove.size(), urlsToRemove}); |
| for(final String url : urlsToRemove ) { |
| this.persistentList.remove(url); |
| } |
| urlsToRemove.clear(); |
| } |
| } |
| |
| private void printResources(String hint) { |
| if ( !logger.isDebugEnabled() ) { |
| return; |
| } |
| int counter = 0; |
| final StringBuilder sb = new StringBuilder(); |
| sb.append(hint); |
| sb.append(" Resources={\n"); |
| for(final String id : this.persistentList.getEntityIds() ) { |
| sb.append("- ").append(hint).append(" RegisteredResource "); |
| sb.append(id); |
| sb.append("\n RegisteredResource.info=["); |
| String sep = ""; |
| for(final RegisteredResource rr : this.persistentList.getEntityResourceList(id).listResources()) { |
| sb.append(sep); |
| sep=", "; |
| sb.append(rr); |
| counter++; |
| } |
| sb.append("]\n"); |
| } |
| sb.append("} (").append(hint).append("): ").append(counter).append(" RegisteredResources\n"); |
| logger.debug(sb.toString()); |
| } |
| |
| /** |
| * Compute OSGi tasks based on our resources, and add to supplied list of tasks. |
| */ |
| private SortedSet<InstallTask> computeTasks() { |
| final SortedSet<InstallTask> tasks = new TreeSet<>(); |
| |
| // Walk the list of entities, and create appropriate OSGi tasks for each group |
| final List<InstallTaskFactory> services = this.factoryTracker.getSortedServices(); |
| if ( services.size() > 0 ) { |
| for(final String entityId : this.persistentList.getEntityIds()) { |
| final EntityResourceList group = this.persistentList.getEntityResourceList(entityId); |
| // Check the first resource in each group |
| final TaskResource toActivate = group.getActiveResource(); |
| if ( toActivate != null ) { |
| final InstallTask task = getTask(services, group); |
| if ( task != null ) { |
| tasks.add(task); |
| } |
| |
| } |
| } |
| } |
| return tasks; |
| } |
| |
| /** |
| * Get the task for the resource. |
| */ |
| private InstallTask getTask(List<InstallTaskFactory> services, |
| final TaskResourceGroup rrg) { |
| InstallTask result = null; |
| |
| for(final InstallTaskFactory factory : services) { |
| if ( factory != null ) { |
| try { |
| result = factory.createTask(rrg); |
| } catch ( final Exception fatal ) { |
| String message = MessageFormat.format("An exception occured while creating a task for {0}. Resource will be ignored: {1}", rrg.getActiveResource(), fatal.getMessage()); |
| logger.error(message, fatal); |
| result = new ChangeStateTask(rrg, ResourceState.IGNORED, message); |
| } |
| if ( result != null ) { |
| break; |
| } |
| } |
| } |
| return result; |
| } |
| |
| private enum ACTION { |
| SLEEP, |
| SHUTDOWN, |
| CYCLE |
| }; |
| |
| /** |
| * Execute all tasks |
| * @param tasks The tasks to executed. |
| * @return The action to perform after the execution. |
| */ |
| private ACTION executeTasks(final SortedSet<InstallTask> tasks) { |
| if (this.switchStartLevel && this.hasBundleUpdateTask(tasks)) { |
| // StartLevel service is always available |
| final ServiceReference ref = ctx.getServiceReference(StartLevel.class.getName()); |
| final StartLevel startLevel = (StartLevel) ctx.getService(ref); |
| try { |
| final int targetStartLevel = this.getLowestStartLevel(tasks, startLevel); |
| final int currentStartLevel = startLevel.getStartLevel(); |
| if (targetStartLevel < currentStartLevel) { |
| auditLogger.info("Switching to start level {}", targetStartLevel); |
| try { |
| startLevel.setStartLevel(targetStartLevel); |
| // now we have to wait until the start level is reached |
| while (startLevel.getStartLevel() > targetStartLevel) { |
| try { |
| Thread.sleep(300); |
| } catch (final InterruptedException ie) { |
| Thread.currentThread().interrupt(); |
| } |
| |
| } |
| |
| return doExecuteTasks(tasks); |
| |
| } finally { |
| // restore old start level in any case |
| startLevel.setStartLevel(currentStartLevel); |
| auditLogger.info("Switching back to start level {} after performing the required " + |
| "installation tasks", currentStartLevel); |
| |
| } |
| } |
| |
| } finally { |
| ctx.ungetService(ref); |
| } |
| } |
| return doExecuteTasks(tasks); |
| } |
| |
| /** |
| * Get the lowest start level for the update operation |
| */ |
| private int getLowestStartLevel(final SortedSet<InstallTask> tasks, final StartLevel startLevel) { |
| final int currentStartLevel = startLevel.getStartLevel(); |
| int startLevelToTarget = currentStartLevel; |
| for(final InstallTask task : tasks) { |
| if (task instanceof BundleUpdateTask){ |
| final Bundle b = ((BundleUpdateTask) task).getBundle(); |
| if (b != null) { |
| try { |
| final int bundleStartLevel = startLevel.getBundleStartLevel(b) - 1; |
| if (bundleStartLevel < startLevelToTarget){ |
| startLevelToTarget = bundleStartLevel; |
| } |
| } catch ( final IllegalArgumentException iae) { |
| // ignore - bundle is uninstalled |
| } |
| } |
| } |
| } |
| // check installer start level |
| final int ownStartLevel = startLevel.getBundleStartLevel(ctx.getBundle()); |
| if ( ownStartLevel > startLevelToTarget ) { |
| // we don't want to disable ourselves |
| startLevelToTarget = ownStartLevel; |
| } |
| return startLevelToTarget; |
| } |
| |
| /** |
| * Check if a bundle update task will be executed. |
| */ |
| private boolean hasBundleUpdateTask(final SortedSet<InstallTask> tasks) { |
| boolean result = false; |
| for(final InstallTask task : tasks){ |
| if ( task.isAsynchronousTask() ) { |
| result = false; // async task is executed immediately# |
| break; |
| } else if (task instanceof BundleUpdateTask){ |
| result = true; |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * Execute all tasks |
| * @param tasks The tasks to executed. |
| * @return The action to perform after the execution. |
| */ |
| private ACTION doExecuteTasks(final SortedSet<InstallTask> tasks) { |
| if ( !tasks.isEmpty() ) { |
| |
| final InstallationContext ctx = new InstallationContext() { |
| |
| @SuppressWarnings("deprecation") |
| @Override |
| public void addTaskToNextCycle(final InstallTask t) { |
| logger.warn("Deprecated method addTaskToNextCycle was called. Task will be executed in this cycle instead: {}", t); |
| synchronized ( tasks ) { |
| tasks.add(t); |
| } |
| } |
| |
| @Override |
| public void addTaskToCurrentCycle(final InstallTask t) { |
| logger.debug("Adding {}task to current cycle: {}", t.isAsynchronousTask() ? "async " : "", t); |
| synchronized ( tasks ) { |
| tasks.add(t); |
| } |
| } |
| |
| @SuppressWarnings("deprecation") |
| @Override |
| public void addAsyncTask(final InstallTask t) { |
| if ( t.isAsynchronousTask() ) { |
| logger.warn("Deprecated method addAsyncTask was called: {}", t); |
| this.addTaskToCurrentCycle(t); |
| } else { |
| logger.warn("Deprecated method addAsyncTask is called with non async task(!): {}", t); |
| this.addTaskToCurrentCycle(new AsyncWrapperInstallTask(t)); |
| } |
| } |
| |
| @Override |
| public void log(final String message, final Object... args) { |
| auditLogger.info(message, args); |
| } |
| |
| @Override |
| public void asyncTaskFailed(final InstallTask t) { |
| // persist all changes and retry restart |
| // remove attribute |
| logger.debug("asyncTaskFailed: {}", t); |
| if ( t.getResource() != null ) { |
| t.getResource().setAttribute(InstallTask.ASYNC_ATTR_NAME, null); |
| } |
| persistentList.save(); |
| synchronized ( resourcesLock ) { |
| if ( !active ) { |
| logger.debug("Restarting background thread from asyncTaskFailed"); |
| active = true; |
| startBackgroundThread(); |
| } else { |
| logger.debug("active={}, no need to restart background thread", active); |
| } |
| } |
| } |
| }; |
| while (this.active && !tasks.isEmpty()) { |
| InstallTask task = null; |
| synchronized (tasks) { |
| task = tasks.first(); |
| tasks.remove(task); |
| } |
| // async tasks are executed "immediately" |
| if ( task.isAsynchronousTask() ) { |
| logger.debug("Executing async task: {}", task); |
| // set attribute |
| final Integer oldValue; |
| if ( task.getResource() != null ) { |
| oldValue = (Integer)task.getResource().getAttribute(InstallTask.ASYNC_ATTR_NAME); |
| final Integer newValue; |
| if ( oldValue == null ) { |
| newValue = 1; |
| } else { |
| newValue = oldValue + 1; |
| } |
| task.getResource().setAttribute(InstallTask.ASYNC_ATTR_NAME, newValue); |
| } else { |
| oldValue = null; |
| } |
| // save new state |
| this.cleanupInstallableResources(); |
| final InstallTask aSyncTask = task; |
| final String threadName = "BackgroundTaskThread" + backgroundTaskCounter.incrementAndGet(); |
| final Thread t = new Thread(threadName) { |
| |
| @Override |
| public void run() { |
| logger.debug("Starting background thread {} to execute {}", |
| Thread.currentThread().getName(), |
| aSyncTask); |
| try { |
| Thread.sleep(2000L); |
| } catch (final InterruptedException ie) { |
| // ignore |
| } |
| // reset attribute |
| if ( aSyncTask.getResource() != null ) { |
| aSyncTask.getResource().setAttribute(InstallTask.ASYNC_ATTR_NAME, oldValue); |
| } |
| aSyncTask.execute(ctx); |
| logger.debug("Background thread {} ends", Thread.currentThread().getName()); |
| } |
| }; |
| t.start(); |
| return ACTION.SHUTDOWN; |
| } |
| try { |
| logger.debug("Executing task: {}", task); |
| task.execute(ctx); |
| } catch (final Throwable t) { |
| logger.error("Uncaught exception during task execution!", t); |
| } |
| } |
| // save new state |
| final boolean newCycle = this.cleanupInstallableResources(); |
| if ( newCycle ) { |
| return ACTION.CYCLE; |
| } |
| |
| } |
| return ACTION.SLEEP; |
| } |
| |
| /** |
| * Clean up and compact. |
| * @return <code>true</code> if another cycle should be started. |
| */ |
| private boolean cleanupInstallableResources() { |
| synchronized ( this.resourcesLock ) { |
| final boolean result = this.persistentList.compact(); |
| this.persistentList.save(); |
| printResources("Compacted"); |
| logger.debug("cleanupInstallableResources returns {}", result); |
| return result; |
| } |
| } |
| |
| /** |
| * Invoke the transformers on the resources. |
| */ |
| private void transformResources() { |
| boolean changed = false; |
| final List<ServiceReference> serviceRefs = this.transformerTracker.getSortedServiceReferences(); |
| |
| if ( serviceRefs.size() > 0 ) { |
| synchronized ( this.resourcesLock ) { |
| // Walk the list of unknown resources and invoke all transformers |
| int index = 0; |
| final List<RegisteredResource> unknownList = this.persistentList.getUntransformedResources(); |
| |
| while ( index < unknownList.size() ) { |
| final RegisteredResource resource = unknownList.get(index); |
| for(final ServiceReference reference : serviceRefs) { |
| final Long id = (Long)reference.getProperty(Constants.SERVICE_ID); |
| // check if this transformer has already been invoked for the resource |
| final String transformers = (String)((RegisteredResourceImpl)resource).getAttribute(ResourceTransformer.class.getName()); |
| if ( id == null || |
| (transformers != null && transformers.contains(":" + id + ':'))) { |
| continue; |
| } |
| final ResourceTransformer transformer = (ResourceTransformer) this.transformerTracker.getService(reference); |
| if ( transformer != null ) { |
| try { |
| final TransformationResult[] result = transformer.transform(resource); |
| final String newTransformers = (transformers == null ? ":" + id + ':' : transformers + id + ':'); |
| ((RegisteredResourceImpl)resource).setAttribute(ResourceTransformer.class.getName(), newTransformers); |
| if ( logger.isDebugEnabled() ) { |
| logger.debug("Invoked transformer {} on {} : {}", |
| new Object[] {transformer, resource, Arrays.toString(result)}); |
| } |
| if ( result != null && result.length > 0 ) { |
| this.persistentList.transform(resource, result); |
| changed = true; |
| index--; |
| break; |
| } |
| } catch (final Throwable t) { |
| logger.error("Uncaught exception during resource transformation!", t); |
| } |
| } |
| } |
| index++; |
| } |
| } |
| if ( changed ) { |
| this.persistentList.save(); |
| printResources("Transformed"); |
| } |
| } |
| } |
| |
| private void checkSatisfied() { |
| synchronized ( this.resourcesLock ) { |
| if ( !this.satisfied ) { |
| this.satisfied = true; |
| if ( this.ctx.getProperty(PROP_REQUIRED_SERVICES) != null ) { |
| final String[] reqs = this.ctx.getProperty(PROP_REQUIRED_SERVICES).split(","); |
| this.satisfied = true; |
| for(final String val : reqs) { |
| if ( val.startsWith("resourcetransformer:") ) { |
| final String name = val.substring(20); |
| |
| this.satisfied = this.transformerTracker.check(ResourceTransformer.NAME, name); |
| |
| } else if ( val.startsWith("installtaskfactory:") ) { |
| final String name = val.substring(19); |
| |
| this.satisfied = this.factoryTracker.check(InstallTaskFactory.NAME, name); |
| |
| } else { |
| logger.warn("Invalid requirements for installer: {}", val); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * @see org.apache.sling.installer.api.tasks.RetryHandler#scheduleRetry() |
| */ |
| @Override |
| public void scheduleRetry() { |
| logger.debug("scheduleRetry called"); |
| this.listener.start(); |
| synchronized ( this.resourcesLock ) { |
| this.retryDuringTaskExecution = true; |
| this.checkSatisfied(); |
| } |
| this.wakeUp(); |
| } |
| |
| private static final class UpdateInfo { |
| public ResourceData data; |
| public Dictionary<String, Object> dict; |
| public String resourceType; |
| public String entityId; |
| public Map<String, Object> attributes; |
| } |
| |
| /** |
| * Store the changes in an internal queue, the queue is processed in {@link #processUpdateInfos()}. |
| * @see org.apache.sling.installer.api.ResourceChangeListener#resourceAddedOrUpdated(java.lang.String, java.lang.String, java.io.InputStream, java.util.Dictionary, Map) |
| */ |
| @Override |
| public void resourceAddedOrUpdated(final String resourceType, |
| final String entityId, |
| final InputStream is, |
| final Dictionary<String, Object> dict, |
| final Map<String, Object> attributes) { |
| try { |
| final UpdateInfo ui = new UpdateInfo(); |
| ui.data = ResourceData.create(is, dict); |
| ui.resourceType = resourceType; |
| ui.dict = dict; |
| ui.entityId = entityId; |
| ui.attributes = attributes; |
| |
| synchronized ( this.resourcesLock ) { |
| updateInfos.add(ui); |
| this.wakeUp(); |
| } |
| } catch (final IOException ioe) { |
| logger.error("Unable to handle resource add or update of " + resourceType + ':' + entityId, ioe); |
| } finally { |
| // always close the input stream! |
| if ( is != null ) { |
| try { |
| is.close(); |
| } catch (final IOException ignore) { |
| // ignore |
| } |
| } |
| } |
| } |
| |
| /** |
| * Store the changes in an internal queue, the queue is processed in {@link #processUpdateInfos()}. |
| * @see org.apache.sling.installer.api.ResourceChangeListener#resourceRemoved(java.lang.String, java.lang.String) |
| */ |
| @Override |
| public void resourceRemoved(final String resourceType, String resourceId) { |
| final UpdateInfo ui = new UpdateInfo(); |
| ui.resourceType = resourceType; |
| ui.entityId = resourceId; |
| |
| synchronized ( this.resourcesLock ) { |
| updateInfos.add(ui); |
| this.wakeUp(); |
| } |
| } |
| |
| /** |
| * Process the internal queue of updates |
| * @see org.apache.sling.installer.api.ResourceChangeListener#resourceAddedOrUpdated(java.lang.String, java.lang.String, java.io.InputStream, java.util.Dictionary, Map) |
| * @see org.apache.sling.installer.api.ResourceChangeListener#resourceRemoved(java.lang.String, java.lang.String) |
| */ |
| private void processUpdateInfos() { |
| final List<UpdateInfo> infos = new ArrayList<>(); |
| synchronized ( resourcesLock ) { |
| infos.addAll(updateInfos); |
| updateInfos.clear(); |
| } |
| for(final UpdateInfo info : infos) { |
| if ( info.data != null ) { |
| this.internalResourceAddedOrUpdated(info.resourceType, info.entityId, info.data, info.dict, info.attributes); |
| } else { |
| this.internalResourceRemoved(info.resourceType, info.entityId); |
| } |
| } |
| } |
| |
| private boolean handleExternalUpdateWithoutWriteBack(final EntityResourceList erl) { |
| final TaskResource tr = erl.getFirstResource(); |
| |
| // if this is an update but the change should not be persisted, we have to change |
| // the state to IGNORED |
| // or to UNINSTALLED if state is UNINSTALL |
| if ( tr.getState() == ResourceState.UNINSTALLED || tr.getState() == ResourceState.IGNORED ) { |
| // ignore |
| return false; |
| } else if ( tr.getState() == ResourceState.UNINSTALL ) { |
| erl.setFinishState(ResourceState.UNINSTALLED, null); |
| return true; |
| } else { |
| erl.setForceFinishState(ResourceState.IGNORED, null); |
| return true; |
| } |
| } |
| /** |
| * Handle external addition or update of a resource |
| * @see org.apache.sling.installer.api.ResourceChangeListener#resourceAddedOrUpdated(java.lang.String, java.lang.String, java.io.InputStream, java.util.Dictionary, Map) |
| */ |
| private void internalResourceAddedOrUpdated(final String resourceType, |
| final String entityId, |
| final ResourceData data, |
| final Dictionary<String, Object> dict, |
| final Map<String, Object> attributes) { |
| final String key = resourceType + ':' + entityId; |
| final boolean persistChange = (attributes != null ? PropertiesUtil.toBoolean(attributes.get(ResourceChangeListener.RESOURCE_PERSIST), true) : true); |
| try { |
| boolean compactAndSave = false; |
| boolean done = false; |
| |
| synchronized ( this.resourcesLock ) { |
| final EntityResourceList erl = this.persistentList.getEntityResourceList(key); |
| logger.debug("Added or updated {} : {}", key, erl); |
| |
| // we first check for update |
| if ( erl != null && erl.getFirstResource() != null ) { |
| // check digest for dictionaries |
| final TaskResource tr = erl.getFirstResource(); |
| if ( dict != null ) { |
| final String digest = FileDataStore.computeDigest(dict); |
| if ( tr.getDigest().equals(digest) ) { |
| if ( tr.getState() == ResourceState.INSTALLED ) { |
| logger.debug("Resource did not change {}", key); |
| } else if ( tr.getState() == ResourceState.INSTALL |
| || tr.getState() == ResourceState.IGNORED ) { |
| erl.setForceFinishState(ResourceState.INSTALLED, null); |
| compactAndSave = true; |
| } |
| done = true; |
| } |
| } |
| |
| final UpdateHandler handler; |
| if ( !done && persistChange ) { |
| handler = this.findHandler(tr.getScheme()); |
| if ( handler == null ) { |
| logger.debug("No handler found to handle update of resource with scheme {}", tr.getScheme()); |
| } |
| } else { |
| handler = null; |
| } |
| |
| if ( !done && handler == null ) { |
| compactAndSave = this.handleExternalUpdateWithoutWriteBack(erl); |
| done = true; |
| } |
| |
| if ( !done ) { |
| final InputStream localIS = data.getInputStream(); |
| try { |
| final UpdateResult result = (localIS == null ? handler.handleUpdate(resourceType, entityId, tr.getURL(), data.getDictionary(), attributes) |
| : handler.handleUpdate(resourceType, entityId, tr.getURL(), localIS, attributes)); |
| if ( result != null ) { |
| if ( !result.getURL().equals(tr.getURL()) && !result.getResourceIsMoved() ) { |
| // resource has been added! |
| final InternalResource internalResource = new InternalResource(result.getScheme(), |
| result.getResourceId(), |
| null, |
| data.getDictionary(), |
| (data.getDictionary() != null ? InstallableResource.TYPE_PROPERTIES : InstallableResource.TYPE_FILE), |
| data.getDigest(result.getURL(), result.getDigest()), |
| result.getPriority(), |
| data.getDataFile(), |
| null); |
| final RegisteredResource rr = this.persistentList.addOrUpdate(internalResource); |
| final TransformationResult transRes = new TransformationResult(); |
| // use the old entity id |
| final int pos = erl.getResourceId().indexOf(':'); |
| transRes.setId(erl.getResourceId().substring(pos + 1)); |
| transRes.setResourceType(resourceType); |
| if ( attributes != null ) { |
| transRes.setAttributes(attributes); |
| } |
| this.persistentList.transform(rr, new TransformationResult[] { |
| transRes |
| }); |
| final EntityResourceList newGroup = this.persistentList.getEntityResourceList(key); |
| newGroup.setFinishState(ResourceState.INSTALLED, null); |
| newGroup.compact(); |
| } else { |
| // resource has been updated or moved |
| ((RegisteredResourceImpl)tr).update( |
| data.getDataFile(), data.getDictionary(), |
| data.getDigest(result.getURL(), result.getDigest()), |
| result.getPriority(), |
| result.getURL()); |
| erl.setForceFinishState(ResourceState.INSTALLED, null); |
| } |
| compactAndSave = true; |
| } else { |
| // handler does not persist |
| compactAndSave = this.handleExternalUpdateWithoutWriteBack(erl); |
| } |
| } finally { |
| if ( localIS != null ) { |
| // always close the input stream! |
| try { localIS.close(); } catch (final IOException ignore) { |
| // ignore |
| } |
| } |
| } |
| done = true; |
| } |
| } |
| |
| if ( !done ) { |
| // create |
| final List<UpdateHandler> handlerList = this.updateHandlerTracker.getSortedServices(); |
| for(final UpdateHandler handler : handlerList) { |
| final InputStream localIS = data.getInputStream(); |
| try { |
| final UpdateResult result = (localIS == null ? handler.handleUpdate(resourceType, entityId, null, data.getDictionary(), attributes) |
| : handler.handleUpdate(resourceType, entityId, null, localIS, attributes)); |
| if ( result != null ) { |
| final InternalResource internalResource = new InternalResource(result.getScheme(), |
| result.getResourceId(), |
| null, |
| data.getDictionary(), |
| (data.getDictionary() != null ? InstallableResource.TYPE_PROPERTIES : InstallableResource.TYPE_FILE), |
| data.getDigest(result.getURL(), result.getDigest()), |
| result.getPriority(), |
| data.getDataFile(), |
| null); |
| final RegisteredResource rr = this.persistentList.addOrUpdate(internalResource); |
| final TransformationResult transRes = new TransformationResult(); |
| transRes.setId(entityId); |
| transRes.setResourceType(resourceType); |
| if ( attributes != null ) { |
| transRes.setAttributes(attributes); |
| } |
| this.persistentList.transform(rr, new TransformationResult[] { |
| transRes |
| }); |
| final EntityResourceList newGroup = this.persistentList.getEntityResourceList(key); |
| newGroup.setFinishState(ResourceState.INSTALLED); |
| newGroup.compact(); |
| compactAndSave = true; |
| done = true; |
| break; |
| } |
| } finally { |
| if ( localIS != null ) { |
| // always close the input stream! |
| try { |
| localIS.close(); |
| } catch (final IOException ignore) { |
| // ignore |
| } |
| } |
| } |
| } |
| if ( !done ) { |
| logger.debug("No handler found to handle creation of resource {}", key); |
| } |
| } |
| if ( compactAndSave ) { |
| if ( erl != null ) { |
| erl.compact(); |
| } |
| this.persistentList.save(); |
| } |
| } |
| } catch (final IOException ioe) { |
| logger.error("Unable to handle resource add or update of " + key, ioe); |
| } |
| } |
| |
| /** |
| * Handle external removal a resource |
| * @see org.apache.sling.installer.api.ResourceChangeListener#resourceRemoved(java.lang.String, java.lang.String) |
| */ |
| private void internalResourceRemoved(final String resourceType, final String entityId) { |
| |
| String key = resourceType + ':' + entityId; |
| synchronized ( this.resourcesLock ) { |
| final EntityResourceList erl = this.persistentList.getEntityResourceList(key); |
| logger.debug("Removed {} : {}", key, erl); |
| // if this is not registered at all, we can simply ignore this |
| if ( erl != null ) { |
| final String resourceId = erl.getResourceId(); |
| key = resourceType + ':' + resourceId; |
| final TaskResource tr = erl.getFirstResource(); |
| if ( tr != null ) { |
| if ( tr.getState() == ResourceState.IGNORED ) { |
| // if it has been ignored before, we activate it now again! |
| // but only if it is not a template |
| if ( tr.getDictionary() == null |
| || tr.getDictionary().get(InstallableResource.RESOURCE_IS_TEMPLATE) == null ) { |
| ((RegisteredResourceImpl)tr).setState(ResourceState.INSTALL, null); |
| this.persistentList.save(); |
| } |
| } else if ( tr.getState() == ResourceState.UNINSTALLED ) { |
| // it has already been removed - nothing do to |
| } else { |
| final UpdateHandler handler = findHandler(tr.getScheme()); |
| if ( handler == null ) { |
| // set to ignored |
| String message = MessageFormat.format("No handler found to handle resource with scheme {0}", |
| tr.getScheme()); |
| logger.debug(message); |
| ((RegisteredResourceImpl) tr).setState(ResourceState.IGNORED, message); |
| } else { |
| // we don't need to check the result, we just check if a result is returned |
| if ( handler.handleRemoval(resourceType, resourceId, tr.getURL()) != null ) { |
| erl.setForceFinishState(ResourceState.UNINSTALLED, null); |
| erl.compact(); |
| } else { |
| // set to ignored |
| String message = MessageFormat |
| .format("No handler found to handle removal of resource with scheme {0}", tr.getScheme()); |
| logger.debug(message); |
| ((RegisteredResourceImpl) tr).setState(ResourceState.IGNORED, message); |
| } |
| } |
| this.persistentList.save(); |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * Search a handler for the scheme. |
| */ |
| private UpdateHandler findHandler(final String scheme) { |
| final List<ServiceReference> references = this.updateHandlerTracker.getSortedServiceReferences(); |
| for(final ServiceReference ref : references) { |
| final String[] supportedSchemes = PropertiesUtil.toStringArray(ref.getProperty(UpdateHandler.PROPERTY_SCHEMES)); |
| if ( supportedSchemes != null ) { |
| for(final String support : supportedSchemes ) { |
| if ( scheme.equals(support) ) { |
| return (UpdateHandler) this.updateHandlerTracker.getService(ref); |
| } |
| } |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * @see org.apache.sling.installer.api.info.InfoProvider#getInstallationState() |
| */ |
| @Override |
| public InstallationState getInstallationState() { |
| synchronized ( this.resourcesLock ) { |
| final InstallationState state = new InstallationState() { |
| |
| private final List<ResourceGroup> activeResources = new ArrayList<>(); |
| private final List<ResourceGroup> installedResources = new ArrayList<>(); |
| private final List<RegisteredResource> untransformedResources = new ArrayList<>(); |
| |
| @Override |
| public List<ResourceGroup> getActiveResources() { |
| return activeResources; |
| } |
| |
| @Override |
| public List<ResourceGroup> getInstalledResources() { |
| return installedResources; |
| } |
| |
| @Override |
| public List<RegisteredResource> getUntransformedResources() { |
| return untransformedResources; |
| } |
| |
| @Override |
| public String toString() { |
| return "InstallationState[active resources: " + this.activeResources + |
| ", installed resources: " + this.installedResources + |
| ", untransformed resources: " + this.untransformedResources + "]"; |
| } |
| }; |
| |
| for(final String entityId : this.persistentList.getEntityIds()) { |
| if ( !this.persistentList.isSpecialEntityId(entityId) ) { |
| final EntityResourceList group = this.persistentList.getEntityResourceList(entityId); |
| |
| final String alias = group.getAlias(); |
| final List<Resource> resources = new ArrayList<>(); |
| boolean first = true; |
| boolean isActive = false; |
| for(final TaskResource tr : group.getResources()) { |
| final ResourceState resourceState = tr.getState(); |
| if ( first ) { |
| if ( resourceState == ResourceState.INSTALL || resourceState == ResourceState.UNINSTALL ) { |
| isActive = true; |
| } |
| first = false; |
| } |
| resources.add(new Resource() { |
| |
| @Override |
| public String getScheme() { |
| return tr.getScheme(); |
| } |
| |
| @Override |
| public String getURL() { |
| return tr.getURL(); |
| } |
| |
| @Override |
| public String getType() { |
| return tr.getType(); |
| } |
| |
| @Override |
| public InputStream getInputStream() throws IOException { |
| return tr.getInputStream(); |
| } |
| |
| @Override |
| public Dictionary<String, Object> getDictionary() { |
| return tr.getDictionary(); |
| } |
| |
| @Override |
| public String getDigest() { |
| return tr.getDigest(); |
| } |
| |
| @Override |
| public int getPriority() { |
| return tr.getPriority(); |
| } |
| |
| @Override |
| public String getEntityId() { |
| return tr.getEntityId(); |
| } |
| |
| @Override |
| public ResourceState getState() { |
| return resourceState; |
| } |
| |
| @Override |
| public Version getVersion() { |
| return tr.getVersion(); |
| } |
| |
| @Override |
| public long getLastChange() { |
| return ((RegisteredResourceImpl)tr).getLastChange(); |
| } |
| |
| @Override |
| public Object getAttribute(final String key) { |
| return tr.getAttribute(key); |
| } |
| |
| @Override |
| @Nullable |
| public String getError() { |
| return tr.getError(); |
| } |
| |
| @Override |
| public String toString() { |
| return "resource[entityId=" + getEntityId() + |
| ", scheme=" + getScheme() + |
| ", url=" + getURL() + |
| ", type=" + getType() + |
| ", error=" + getError() + |
| ", state=" + getState() + |
| ", version=" + getVersion() + |
| ", lastChange=" + getLastChange() + |
| ", priority=" + getPriority() + |
| ", digest=" + getDigest() + |
| "]"; |
| } |
| }); |
| } |
| final ResourceGroup rg = new ResourceGroup() { |
| |
| @Override |
| public List<Resource> getResources() { |
| return resources; |
| } |
| |
| @Override |
| public String getAlias() { |
| return alias; |
| } |
| |
| @Override |
| public String toString() { |
| return "group[" + resources + "]"; |
| } |
| }; |
| if ( isActive ) { |
| state.getActiveResources().add(rg); |
| } else { |
| state.getInstalledResources().add(rg); |
| } |
| } |
| } |
| |
| Collections.sort(state.getActiveResources(), COMPARATOR); |
| Collections.sort(state.getInstalledResources(), COMPARATOR); |
| |
| state.getUntransformedResources().addAll(this.persistentList.getUntransformedResources()); |
| |
| return state; |
| } |
| } |
| |
| private static final Comparator<ResourceGroup> COMPARATOR = new Comparator<ResourceGroup>() { |
| |
| @Override |
| public int compare(ResourceGroup o1, ResourceGroup o2) { |
| RegisteredResource r1 = null; |
| RegisteredResource r2 = null; |
| if ( o1.getResources().size() > 0 ) { |
| r1 = o1.getResources().iterator().next(); |
| } |
| if ( o2.getResources().size() > 0 ) { |
| r2 = o2.getResources().iterator().next(); |
| } |
| int result; |
| if ( r1 == null && r2 == null ) { |
| result = 0; |
| } else if ( r1 == null ) { |
| result = -1; |
| } else if ( r2 == null ) { |
| result = 1; |
| } else { |
| result = r1.getType().compareTo(r2.getType()); |
| if ( result == 0 ) { |
| result = r1.getEntityId().compareTo(r2.getEntityId()); |
| } |
| } |
| return result; |
| } |
| |
| }; |
| |
| /** |
| * Handle resource updates |
| */ |
| private void handleResourceUpdaters() { |
| final List<ResourceUpdater> updaters = this.updaterTracker.getSortedServices(); |
| for(final ResourceUpdater up : updaters) { |
| this.logger.info("Invoking installer resource updater {}", up.getClass().getName()); |
| final List<UpdatableResourceGroup> groups = new ArrayList<>(); |
| for(final String groupId : this.persistentList.getEntityIds()) { |
| final EntityResourceList list = this.persistentList.getEntityResourceList(groupId); |
| final String resourceType = list.getFirstResource().getType(); |
| final String rsrcId = groupId.substring(resourceType.length() + 1); |
| |
| final UpdatableResourceGroup grp = new UpdatableResourceGroup() { |
| |
| private String id = rsrcId; |
| |
| private String alias = list.getAlias(); |
| |
| @Override |
| public void setId(final String id) { |
| if ( id == null ) { |
| throw new IllegalArgumentException(); |
| } |
| this.id = id; |
| } |
| |
| @Override |
| public void setAlias(final String value) { |
| this.alias = value; |
| } |
| |
| @Override |
| public String getResourceType() { |
| return resourceType; |
| } |
| |
| @Override |
| public String getId() { |
| return id; |
| } |
| |
| @Override |
| public String getAlias() { |
| return alias; |
| } |
| |
| @Override |
| public void update() { |
| if ( !rsrcId.equals(id) || |
| (list.getAlias() == null && alias != null) || |
| (list.getAlias() != null && !list.getAlias().equals(alias)) ) { |
| final String newGroupId = resourceType + ':' + id; |
| auditLogger.info("Updating resource group from {} to {}", groupId, newGroupId); |
| persistentList.update(groupId, alias, newGroupId); |
| // persist list |
| persistentList.save(); |
| } |
| } |
| }; |
| groups.add(grp); |
| } |
| up.update(groups); |
| } |
| } |
| } |