org.fabric3.contribution.scanner.impl.ContributionDirectoryScanner Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of fabric3-contribution-scanner Show documentation
Show all versions of fabric3-contribution-scanner Show documentation
Fabric3 Contribution Scanner Implementation.
The newest version!
/*
* Fabric3
* Copyright (c) 2009-2013 Metaform Systems
*
* Fabric3 is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as
* published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version, with the
* following exception:
*
* Linking this software statically or dynamically with other
* modules is making a combined work based on this software.
* Thus, the terms and conditions of the GNU General Public
* License cover the whole combination.
*
* As a special exception, the copyright holders of this software
* give you permission to link this software with independent
* modules to produce an executable, regardless of the license
* terms of these independent modules, and to copy and distribute
* the resulting executable under terms of your choice, provided
* that you also meet, for each linked independent module, the
* terms and conditions of the license of that module. An
* independent module is a module which is not derived from or
* based on this software. If you modify this software, you may
* extend this exception to your version of the software, but
* you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version.
*
* Fabric3 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the
* GNU General Public License along with Fabric3.
* If not, see .
*/
package org.fabric3.contribution.scanner.impl;
import java.io.File;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.oasisopen.sca.annotation.Destroy;
import org.oasisopen.sca.annotation.EagerInit;
import org.oasisopen.sca.annotation.Init;
import org.oasisopen.sca.annotation.Property;
import org.oasisopen.sca.annotation.Reference;
import org.fabric3.api.annotation.monitor.Monitor;
import org.fabric3.contribution.scanner.spi.FileSystemResource;
import org.fabric3.contribution.scanner.spi.FileSystemResourceFactoryRegistry;
import org.fabric3.contribution.scanner.spi.FileSystemResourceState;
import org.fabric3.host.contribution.ContributionException;
import org.fabric3.host.contribution.ContributionNotFoundException;
import org.fabric3.host.contribution.ContributionService;
import org.fabric3.host.contribution.ContributionSource;
import org.fabric3.host.contribution.FileContributionSource;
import org.fabric3.host.contribution.RemoveException;
import org.fabric3.host.contribution.UninstallException;
import org.fabric3.host.contribution.ValidationException;
import org.fabric3.host.domain.AssemblyException;
import org.fabric3.host.domain.DeploymentException;
import org.fabric3.host.domain.Domain;
import org.fabric3.host.runtime.HostInfo;
import org.fabric3.spi.event.EventService;
import org.fabric3.spi.event.ExtensionsInitialized;
import org.fabric3.spi.event.Fabric3Event;
import org.fabric3.spi.event.Fabric3EventListener;
import org.fabric3.spi.event.RuntimeStart;
/**
* Scans deployment directories for contributions. In production mode, deployment directories will be scanned once at startup and any contained contributions
* will be deployed. In the default dynamic (non-production) mode, scanning will be periodic with support for adding, updating, and removing contributions.
*
* In dynamic mode, the scanner watches deployment directories at a fixed interval. Files are tracked as a {@link FileSystemResource}, which provides a
* consistent view across various types such as jars and exploded directories. Unknown file types are ignored. At the specified interval, removed files are
* determined by comparing the current directory contents with the contents from the previous pass. Changes or additions are also determined by comparing the
* current directory state with that of the previous pass. Detected changes and additions are cached for the following interval. Detected changes and additions
* from the previous interval are then compared using a timestamp to see if they have changed again. If so, they remain cached. If they have not changed, they
* are processed, contributed via the ContributionService, and deployed in the domain.
*/
@EagerInit
public class ContributionDirectoryScanner implements Runnable, Fabric3EventListener {
private ContributionService contributionService;
private FileSystemResourceFactoryRegistry registry;
private EventService eventService;
private ContributionTracker tracker;
private ScannerMonitor monitor;
private Domain domain;
private List paths;
private long delay = 2000;
private boolean production = false;
private ScheduledExecutorService executor;
private Set ignored = new HashSet();
private Map cache = new HashMap();
List notSeen = new ArrayList(); // contributions added when the runtime was offline and hence not previously seen by the scanner
public ContributionDirectoryScanner(@Reference ContributionService contributionService,
@Reference(name = "assembly") Domain domain,
@Reference FileSystemResourceFactoryRegistry registry,
@Reference EventService eventService,
@Reference ContributionTracker tracker,
@Reference HostInfo hostInfo,
@Monitor ScannerMonitor monitor) {
this.registry = registry;
this.contributionService = contributionService;
this.domain = domain;
this.eventService = eventService;
this.tracker = tracker;
paths = hostInfo.getDeployDirectories();
this.monitor = monitor;
}
@Property(required = false)
public void setProduction(boolean production) {
this.production = production;
}
@Property(required = false)
public void setDelay(long delay) {
this.delay = delay;
}
@SuppressWarnings({"unchecked"})
@Init
public void init() {
eventService.subscribe(ExtensionsInitialized.class, this);
// Register to be notified when the runtime starts to perform the following:
// 1. Contributions installed to deployment directories when the runtime is offline are deployed to the domain
// 2. The scanner thread is initialized
eventService.subscribe(RuntimeStart.class, this);
}
@Destroy
public void destroy() {
if (executor != null) {
executor.shutdownNow();
}
}
public void onEvent(Fabric3Event event) {
if (event instanceof ExtensionsInitialized) {
if (paths == null) {
return;
}
// process existing files in recovery mode
List files = new ArrayList();
for (File path : paths) {
File[] pathFiles = path.listFiles();
if (pathFiles != null) {
Collections.addAll(files, pathFiles);
}
}
processFiles(files, true);
} else if (event instanceof RuntimeStart) {
try {
domain.include(notSeen);
} catch (DeploymentException e) {
monitor.error(e);
}
notSeen.clear();
if (!production) {
executor = Executors.newSingleThreadScheduledExecutor();
executor.scheduleWithFixedDelay(this, 10, delay, TimeUnit.MILLISECONDS);
}
}
}
public synchronized void run() {
if (paths == null) {
return;
}
List files = new ArrayList();
for (File path : paths) {
if (!path.isDirectory()) {
// there is no extension directory, return without processing
continue;
}
File[] pathFiles = path.listFiles();
if (pathFiles != null) {
Collections.addAll(files, pathFiles);
}
}
processRemovals(files);
if (files.isEmpty()) {
// there are no files to process
return;
}
try {
processFiles(files, false);
processIgnored();
} catch (RuntimeException e) {
monitor.error(e);
} catch (Error e) {
monitor.error(e);
throw e;
}
}
/**
* Processes the contents of deployment directories and updates the state of cached resources based on those contents.
*
* @param files the files in the deployment directories
* @param recover true if processing is performed during recovery
*/
private synchronized void processFiles(List files, boolean recover) {
for (File file : files) {
String name = file.getName();
FileSystemResource cached = cache.get(name);
if (cached == null) {
cached = registry.createResource(file);
if (cached == null) {
// not a known type, ignore
if (!name.startsWith(".") && !name.endsWith(".txt") && !ignored.contains(file)) {
monitor.ignored(name);
}
ignored.add(file);
continue;
}
cache.put(name, cached);
if (recover) {
// recover, do not wait to install
cached.setState(FileSystemResourceState.ADDED);
} else {
// file may have been ignored previously as it was incomplete such as missing a manifest; remove it from the ignored list
ignored.remove(file);
continue;
}
} else {
if (cached.getState() == FileSystemResourceState.ERROR) {
if (cached.isChanged()) {
// file has changed since the error was reported, set to detected
cached.setState(FileSystemResourceState.DETECTED);
cached.checkpoint();
} else {
// corrupt file from a previous run, continue
continue;
}
} else if (cached.getState() == FileSystemResourceState.DETECTED) {
if (cached.isChanged()) {
// updates may still be pending, wait until the next pass
continue;
} else {
cached.setState(FileSystemResourceState.ADDED);
cached.checkpoint();
}
} else if (cached.getState() == FileSystemResourceState.PROCESSED) {
if (cached.isChanged()) {
cached.setState(FileSystemResourceState.UPDATED);
cached.checkpoint();
}
}
}
}
if (recover) {
processAdditions(true);
} else {
processUpdates();
processAdditions(false);
}
}
/**
* Processes updated resources in the deployment directories.
*/
private synchronized void processUpdates() {
List sources = new ArrayList();
List updatedResources = new ArrayList();
List uris = new ArrayList();
for (FileSystemResource resource : cache.values()) {
if (resource.getState() != FileSystemResourceState.UPDATED) {
continue;
}
try {
String name = resource.getName();
URI artifactUri = new URI(name);
URL location = resource.getLocation();
long timestamp = resource.getTimestamp();
// undeploy any deployed composites in the reverse order that they were deployed in
try {
domain.undeploy(artifactUri, false);
} catch (DeploymentException e) {
monitor.error(e);
return;
}
// if the resource has changed, wait until the next pass as updates may still be in progress
if (resource.isChanged()) {
resource.checkpoint();
continue;
}
ContributionSource source = new FileContributionSource(artifactUri, location, timestamp, false);
sources.add(source);
updatedResources.add(resource);
uris.add(artifactUri);
} catch (URISyntaxException e) {
resource.setState(FileSystemResourceState.ERROR);
monitor.error(e);
}
}
try {
if (!uris.isEmpty()) {
contributionService.uninstall(uris);
contributionService.remove(uris);
}
if (!sources.isEmpty()) {
List stored = contributionService.store(sources);
List contributions = contributionService.install(stored);
domain.include(contributions);
for (FileSystemResource resource : updatedResources) {
resource.setState(FileSystemResourceState.PROCESSED);
resource.checkpoint();
monitor.processed(resource.getName());
}
}
} catch (ContributionException e) {
for (FileSystemResource resource : updatedResources) {
resource.setState(FileSystemResourceState.ERROR);
}
monitor.error(e);
} catch (DeploymentException e) {
for (FileSystemResource resource : updatedResources) {
resource.setState(FileSystemResourceState.ERROR);
}
// back out installation
revertInstallation(uris);
monitor.error(e);
}
}
private void revertInstallation(List uris) {
try {
contributionService.uninstall(uris);
contributionService.remove(uris);
} catch (UninstallException e) {
monitor.error(e);
} catch (ContributionNotFoundException e) {
monitor.error(e);
} catch (RemoveException e) {
monitor.error(e);
}
}
/**
* Processes added resources in the deployment directories.
*
* @param recover true if files are being added in recovery mode
*/
private synchronized void processAdditions(boolean recover) {
List sources = new ArrayList();
List addedResources = new ArrayList();
for (FileSystemResource resource : cache.values()) {
if (resource.getState() != FileSystemResourceState.ADDED || resource.isChanged()) {
resource.checkpoint();
continue;
}
String name = resource.getName();
URL location = resource.getLocation();
long timestamp = resource.getTimestamp();
URI uri = URI.create(name);
ContributionSource source = new FileContributionSource(uri, location, timestamp, false);
sources.add(source);
addedResources.add(resource);
boolean seen = tracker.isTracked(name);
if (!seen && recover) {
// the contribution was not seen previously, schedule it to be deployed when the domain recovers
notSeen.add(uri);
}
// track the addition
tracker.addResource(name);
}
if (!sources.isEmpty()) {
try {
// Install contributions, which will be ordered transitively by import dependencies
List stored = contributionService.store(sources);
List addedUris = contributionService.install(stored);
// Include the contributions if this is not a recovery operation (recovery will handle inclusion separately)
if (!recover) {
domain.include(addedUris);
}
for (FileSystemResource resource : addedResources) {
resource.setState(FileSystemResourceState.PROCESSED);
resource.checkpoint();
monitor.processed(resource.getName());
}
} catch (ValidationException e) {
// remove from not seen: FABRICTHREE-583
for (ContributionSource source : sources) {
notSeen.remove(source.getUri());
}
// print out the validation errors
monitor.contributionErrors(e.getMessage());
for (FileSystemResource resource : addedResources) {
resource.setState(FileSystemResourceState.ERROR);
}
} catch (AssemblyException e) {
// print out the deployment errors
monitor.deploymentErrors(e.getMessage());
for (FileSystemResource resource : addedResources) {
resource.setState(FileSystemResourceState.ERROR);
}
} catch (ContributionException e) {
handleError(e, addedResources);
} catch (DeploymentException e) {
handleError(e, addedResources);
} catch (NoClassDefFoundError e) {
handleError(e, addedResources);
// don't re-throw the error since the contribution can be safely ignored
} catch (Error e) {
for (FileSystemResource resource : addedResources) {
resource.setState(FileSystemResourceState.ERROR);
}
// re-throw the exception as the runtime may be in an unstable state
throw e;
} catch (RuntimeException e) {
for (FileSystemResource resource : addedResources) {
resource.setState(FileSystemResourceState.ERROR);
}
// re-throw the exception as the runtime may be in an unstable state
throw e;
}
}
}
/**
* process removed files, which results in undeployment.
*
* @param files the current contents of the deployment directories
*/
private synchronized void processRemovals(List files) {
Map index = new HashMap(files.size());
for (File file : files) {
index.put(file.getName(), file);
}
for (Iterator iterator = cache.values().iterator(); iterator.hasNext(); ) {
FileSystemResource entry = iterator.next();
String name = entry.getName();
URI uri = URI.create(name);
if (index.get(name) == null) {
// artifact was removed
try {
// track the removal
tracker.removeResource(name);
iterator.remove();
// check that the resource was not deleted by another process
if (contributionService.exists(uri)) {
domain.undeploy(uri, false);
contributionService.uninstall(uri);
contributionService.remove(uri);
}
monitor.removed(name);
} catch (ContributionException e) {
monitor.removalError(name, e);
} catch (DeploymentException e) {
monitor.removalError(name, e);
}
}
}
}
private synchronized void processIgnored() {
for (Iterator iter = ignored.iterator(); iter.hasNext(); ) {
File file = iter.next();
if (!file.exists()) {
iter.remove();
}
}
}
private void handleError(Throwable e, List addedResources) {
monitor.error(e);
for (FileSystemResource resource : addedResources) {
resource.setState(FileSystemResourceState.ERROR);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy