| /* |
| * 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.felix.fileinstall.internal; |
| |
| import java.io.BufferedInputStream; |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.net.MalformedURLException; |
| import java.net.URI; |
| import java.net.URISyntaxException; |
| import java.net.URL; |
| import java.security.SecureRandom; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| 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.concurrent.atomic.AtomicBoolean; |
| import java.util.jar.JarInputStream; |
| import java.util.jar.Manifest; |
| |
| import org.apache.felix.fileinstall.ArtifactInstaller; |
| import org.apache.felix.fileinstall.ArtifactListener; |
| import org.apache.felix.fileinstall.ArtifactTransformer; |
| import org.apache.felix.fileinstall.ArtifactUrlTransformer; |
| import org.apache.felix.fileinstall.internal.Util.Logger; |
| import org.apache.felix.utils.manifest.Clause; |
| import org.apache.felix.utils.manifest.Parser; |
| import org.apache.felix.utils.version.VersionRange; |
| import org.apache.felix.utils.version.VersionTable; |
| import org.osgi.framework.Bundle; |
| import org.osgi.framework.BundleContext; |
| import org.osgi.framework.BundleEvent; |
| import org.osgi.framework.BundleException; |
| import org.osgi.framework.BundleListener; |
| import org.osgi.framework.Constants; |
| import org.osgi.framework.Version; |
| import org.osgi.framework.startlevel.BundleStartLevel; |
| import org.osgi.framework.startlevel.FrameworkStartLevel; |
| import org.osgi.framework.wiring.BundleRevision; |
| |
| /** |
| * -DirectoryWatcher- |
| * |
| * This class runs a background task that checks a directory for new files or |
| * removed files. These files can be configuration files or jars. |
| * For jar files, its behavior is defined below: |
| * - If there are new jar files, it installs them and optionally starts them. |
| * - If it fails to install a jar, it does not try to install it again until |
| * the jar has been modified. |
| * - If it fail to start a bundle, it attempts to start it in following |
| * iterations until it succeeds or the corresponding jar is uninstalled. |
| * - If some jar files have been deleted, it uninstalls them. |
| * - If some jar files have been updated, it updates them. |
| * - If it fails to update a bundle, it tries to update it in following |
| * iterations until it is successful. |
| * - If any bundle gets updated or uninstalled, it refreshes the framework |
| * for the changes to take effect. |
| * - If it detects any new installations, uninstallations or updations, |
| * it tries to start all the managed bundle unless it has been configured |
| * to only install bundles. |
| * |
| * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a> |
| */ |
| public class DirectoryWatcher extends Thread implements BundleListener |
| { |
| public final static String FILENAME = "felix.fileinstall.filename"; |
| public final static String POLL = "felix.fileinstall.poll"; |
| public final static String DIR = "felix.fileinstall.dir"; |
| public final static String LOG_LEVEL = "felix.fileinstall.log.level"; |
| public final static String TMPDIR = "felix.fileinstall.tmpdir"; |
| public final static String FILTER = "felix.fileinstall.filter"; |
| public final static String START_NEW_BUNDLES = "felix.fileinstall.bundles.new.start"; |
| public final static String USE_START_TRANSIENT = "felix.fileinstall.bundles.startTransient"; |
| public final static String USE_START_ACTIVATION_POLICY = "felix.fileinstall.bundles.startActivationPolicy"; |
| public final static String NO_INITIAL_DELAY = "felix.fileinstall.noInitialDelay"; |
| public final static String DISABLE_CONFIG_SAVE = "felix.fileinstall.disableConfigSave"; |
| public final static String ENABLE_CONFIG_SAVE = "felix.fileinstall.enableConfigSave"; |
| public final static String START_LEVEL = "felix.fileinstall.start.level"; |
| public final static String ACTIVE_LEVEL = "felix.fileinstall.active.level"; |
| public final static String UPDATE_WITH_LISTENERS = "felix.fileinstall.bundles.updateWithListeners"; |
| public final static String OPTIONAL_SCOPE = "felix.fileinstall.optionalImportRefreshScope"; |
| public final static String FRAGMENT_SCOPE = "felix.fileinstall.fragmentRefreshScope"; |
| public final static String DISABLE_NIO2 = "felix.fileinstall.disableNio2"; |
| |
| public final static String SCOPE_NONE = "none"; |
| public final static String SCOPE_MANAGED = "managed"; |
| public final static String SCOPE_ALL = "all"; |
| |
| static final SecureRandom random = new SecureRandom(); |
| |
| final File javaIoTmpdir = new File(System.getProperty("java.io.tmpdir")); |
| |
| final FileInstall fileInstall; |
| |
| Map<String, String> properties; |
| File watchedDirectory; |
| File tmpDir; |
| long poll; |
| int logLevel; |
| boolean startBundles; |
| boolean useStartTransient; |
| boolean useStartActivationPolicy; |
| String filter; |
| BundleContext context; |
| String originatingFileName; |
| boolean noInitialDelay; |
| int startLevel; |
| int activeLevel; |
| boolean updateWithListeners; |
| String fragmentScope; |
| String optionalScope; |
| boolean disableNio2; |
| |
| // Map of all installed artifacts |
| final Map<File, Artifact> currentManagedArtifacts = new HashMap<File, Artifact>(); |
| |
| // The scanner to report files changes |
| Scanner scanner; |
| |
| // Represents files that could not be processed because of a missing artifact listener |
| final Set<File> processingFailures = new HashSet<File>(); |
| |
| // Represents installed artifacts which need to be started later because they failed to start |
| Set<Bundle> delayedStart = new HashSet<Bundle>(); |
| |
| // Represents artifacts that could not be installed |
| final Map<File, Artifact> installationFailures = new HashMap<File, Artifact>(); |
| |
| // flag (acces to which must be synchronized) that indicates wheter there's a change in state of system, |
| // which may result in an attempt to start the watched bundles |
| private AtomicBoolean stateChanged = new AtomicBoolean(); |
| |
| public DirectoryWatcher(FileInstall fileInstall, Map<String, String> properties, BundleContext context) |
| { |
| super("fileinstall-" + getThreadName(properties)); |
| this.fileInstall = fileInstall; |
| this.properties = properties; |
| this.context = context; |
| poll = getLong(properties, POLL, 2000); |
| logLevel = getInt(properties, LOG_LEVEL, Util.getGlobalLogLevel(context)); |
| originatingFileName = properties.get(FILENAME); |
| watchedDirectory = getFile(properties, DIR, new File("./load")); |
| verifyWatchedDir(); |
| tmpDir = getFile(properties, TMPDIR, null); |
| prepareTempDir(); |
| startBundles = getBoolean(properties, START_NEW_BUNDLES, true); // by default, we start bundles. |
| useStartTransient = getBoolean(properties, USE_START_TRANSIENT, false); // by default, we start bundles persistently. |
| useStartActivationPolicy = getBoolean(properties, USE_START_ACTIVATION_POLICY, true); // by default, we start bundles using activation policy. |
| filter = properties.get(FILTER); |
| noInitialDelay = getBoolean(properties, NO_INITIAL_DELAY, false); |
| startLevel = getInt(properties, START_LEVEL, 0); // by default, do not touch start level |
| activeLevel = getInt(properties, ACTIVE_LEVEL, 0); // by default, always scan |
| updateWithListeners = getBoolean(properties, UPDATE_WITH_LISTENERS, false); // Do not update bundles when listeners are updated |
| fragmentScope = properties.get(FRAGMENT_SCOPE); |
| optionalScope = properties.get(OPTIONAL_SCOPE); |
| disableNio2 = getBoolean(properties, DISABLE_NIO2, false); |
| this.context.addBundleListener(this); |
| |
| if (disableNio2) { |
| scanner = new Scanner(watchedDirectory, filter); |
| } else { |
| try { |
| scanner = new WatcherScanner(context, watchedDirectory, filter); |
| } catch (Throwable t) { |
| scanner = new Scanner(watchedDirectory, filter); |
| } |
| } |
| } |
| |
| private void verifyWatchedDir() |
| { |
| if (!watchedDirectory.exists()) |
| { |
| // Issue #2069: Do not create the directory if it does not exist, |
| // instead, warn user and continue. We will automatically start |
| // monitoring the dir when it becomes available. |
| log(Logger.LOG_WARNING, |
| watchedDirectory + " does not exist, please create it.", |
| null); |
| } |
| else if (!watchedDirectory.isDirectory()) |
| { |
| log(Logger.LOG_ERROR, |
| "Cannot use " |
| + watchedDirectory |
| + " because it's not a directory", null); |
| throw new RuntimeException( |
| "File Install can't monitor " + watchedDirectory + " because it is not a directory"); |
| } |
| } |
| |
| public static String getThreadName(Map<String, String> properties) |
| { |
| return (properties.get(DIR) != null ? properties.get(DIR) : "./load"); |
| } |
| |
| public Map<String, String> getProperties() |
| { |
| return properties; |
| } |
| |
| public void start() |
| { |
| if (noInitialDelay) |
| { |
| log(Logger.LOG_DEBUG, "Starting initial scan", null); |
| initializeCurrentManagedBundles(); |
| Set<File> files = scanner.scan(true); |
| if (files != null) |
| { |
| try |
| { |
| process(files); |
| } |
| catch (InterruptedException e) |
| { |
| throw new RuntimeException(e); |
| } |
| } |
| } |
| super.start(); |
| } |
| |
| /** |
| * Main run loop, will traverse the directory, and then handle the delta |
| * between installed and newly found/lost bundles and configurations. |
| * |
| */ |
| public void run() |
| { |
| // We must wait for FileInstall to complete initialisation |
| // to avoid race conditions observed in FELIX-2791 |
| try |
| { |
| fileInstall.lock.readLock().lockInterruptibly(); |
| } |
| catch (InterruptedException e) |
| { |
| Thread.currentThread().interrupt(); |
| log(Logger.LOG_INFO, "Watcher for " + watchedDirectory + " exiting because of interruption.", e); |
| return; |
| } |
| try { |
| log(Logger.LOG_DEBUG, |
| "{" + POLL + " (ms) = " + poll + ", " |
| + DIR + " = " + watchedDirectory.getAbsolutePath() + ", " |
| + LOG_LEVEL + " = " + logLevel + ", " |
| + START_NEW_BUNDLES + " = " + startBundles + ", " |
| + TMPDIR + " = " + tmpDir + ", " |
| + FILTER + " = " + filter + ", " |
| + START_LEVEL + " = " + startLevel + "}", null |
| ); |
| |
| if (!noInitialDelay) { |
| try { |
| // enforce a delay before the first directory scan |
| Thread.sleep(poll); |
| } catch (InterruptedException e) { |
| log(Logger.LOG_DEBUG, "Watcher for " + watchedDirectory + " was interrupted while waiting " |
| + poll + " milliseconds for initial directory scan.", e); |
| return; |
| } |
| initializeCurrentManagedBundles(); |
| } |
| } |
| finally |
| { |
| fileInstall.lock.readLock().unlock(); |
| } |
| |
| while (!interrupted()) { |
| try { |
| FrameworkStartLevel startLevelSvc = context.getBundle(0).adapt(FrameworkStartLevel.class); |
| // Don't access the disk when the framework is still in a startup phase. |
| if (startLevelSvc.getStartLevel() >= activeLevel |
| && context.getBundle(0).getState() == Bundle.ACTIVE) { |
| Set<File> files = scanner.scan(false); |
| // Check that there is a result. If not, this means that the directory can not be listed, |
| // so it's presumably not a valid directory (it may have been deleted by someone). |
| // In such case, just sleep |
| if (files != null) { |
| process(files); |
| } |
| } |
| synchronized (this) { |
| wait(poll); |
| } |
| } catch (InterruptedException e) { |
| return; |
| } catch (Throwable e) { |
| try { |
| context.getBundle(); |
| } catch (IllegalStateException t) { |
| // FileInstall bundle has been uninstalled, exiting loop |
| return; |
| } |
| log(Logger.LOG_ERROR, "In main loop, we have serious trouble", e); |
| } |
| } |
| } |
| |
| public void bundleChanged(BundleEvent bundleEvent) |
| { |
| int type = bundleEvent.getType(); |
| if (type == BundleEvent.UNINSTALLED) |
| { |
| for (Iterator it = getArtifacts().iterator(); it.hasNext();) |
| { |
| Artifact artifact = (Artifact) it.next(); |
| if (artifact.getBundleId() == bundleEvent.getBundle().getBundleId()) |
| { |
| log(Logger.LOG_DEBUG, "Bundle " + bundleEvent.getBundle().getBundleId() |
| + " has been uninstalled", null); |
| it.remove(); |
| break; |
| } |
| } |
| } |
| if (type == BundleEvent.INSTALLED || type == BundleEvent.RESOLVED || type == BundleEvent.UNINSTALLED || |
| type == BundleEvent.UNRESOLVED || type == BundleEvent.UPDATED) { |
| setStateChanged(true); |
| } |
| } |
| |
| private void process(Set<File> files) throws InterruptedException |
| { |
| fileInstall.lock.readLock().lockInterruptibly(); |
| try |
| { |
| doProcess(files); |
| } |
| finally |
| { |
| fileInstall.lock.readLock().unlock(); |
| } |
| } |
| |
| private void doProcess(Set<File> files) throws InterruptedException |
| { |
| List<ArtifactListener> listeners = fileInstall.getListeners(); |
| List<Artifact> deleted = new ArrayList<Artifact>(); |
| List<Artifact> modified = new ArrayList<Artifact>(); |
| List<Artifact> created = new ArrayList<Artifact>(); |
| |
| // Try to process again files that could not be processed |
| synchronized (processingFailures) |
| { |
| files.addAll(processingFailures); |
| processingFailures.clear(); |
| } |
| |
| for (File file : files) { |
| boolean exists = file.exists(); |
| Artifact artifact = getArtifact(file); |
| // File has been deleted |
| if (!exists) { |
| if (artifact != null) { |
| deleteJaredDirectory(artifact); |
| deleteTransformedFile(artifact); |
| deleted.add(artifact); |
| } |
| } |
| // File exists |
| else { |
| File jar = file; |
| URL jaredUrl = null; |
| try { |
| jaredUrl = file.toURI().toURL(); |
| } catch (MalformedURLException e) { |
| // Ignore, can't happen |
| } |
| // Jar up the directory if needed |
| if (file.isDirectory()) { |
| prepareTempDir(); |
| try { |
| jar = new File(tmpDir, file.getName() + ".jar"); |
| Util.jarDir(file, jar); |
| jaredUrl = new URL(JarDirUrlHandler.PROTOCOL, null, file.getPath()); |
| |
| } catch (IOException e) { |
| // Notify user of problem, won't retry until the dir is updated. |
| log(Logger.LOG_ERROR, |
| "Unable to create jar for: " + file.getAbsolutePath(), e); |
| continue; |
| } |
| } |
| // File has been modified |
| if (artifact != null) { |
| artifact.setChecksum(scanner.getChecksum(file)); |
| // If there's no listener, this is because this artifact has been installed before |
| // fileinstall has been restarted. In this case, try to find a listener. |
| if (artifact.getListener() == null) { |
| ArtifactListener listener = findListener(jar, listeners); |
| // If no listener can handle this artifact, we need to defer the |
| // processing for this artifact until one is found |
| if (listener == null) { |
| synchronized (processingFailures) { |
| processingFailures.add(file); |
| } |
| continue; |
| } |
| artifact.setListener(listener); |
| } |
| // If the listener can not handle this file anymore, |
| // uninstall the artifact and try as if is was new |
| if (!listeners.contains(artifact.getListener()) || !artifact.getListener().canHandle(jar)) { |
| deleted.add(artifact); |
| } |
| // The listener is still ok |
| else { |
| deleteTransformedFile(artifact); |
| artifact.setJaredDirectory(jar); |
| artifact.setJaredUrl(jaredUrl); |
| if (transformArtifact(artifact)) { |
| modified.add(artifact); |
| } else { |
| deleteJaredDirectory(artifact); |
| deleted.add(artifact); |
| } |
| } |
| } |
| // File has been added |
| else { |
| // Find the listener |
| ArtifactListener listener = findListener(jar, listeners); |
| // If no listener can handle this artifact, we need to defer the |
| // processing for this artifact until one is found |
| if (listener == null) { |
| synchronized (processingFailures) { |
| processingFailures.add(file); |
| } |
| continue; |
| } |
| // Create the artifact |
| artifact = new Artifact(); |
| artifact.setPath(file); |
| artifact.setJaredDirectory(jar); |
| artifact.setJaredUrl(jaredUrl); |
| artifact.setListener(listener); |
| artifact.setChecksum(scanner.getChecksum(file)); |
| if (transformArtifact(artifact)) { |
| created.add(artifact); |
| } else { |
| deleteJaredDirectory(artifact); |
| } |
| } |
| } |
| } |
| // Handle deleted artifacts |
| // We do the operations in the following order: |
| // uninstall, update, install, refresh & start. |
| Collection<Bundle> uninstalledBundles = uninstall(deleted); |
| Collection<Bundle> updatedBundles = update(modified); |
| Collection<Bundle> installedBundles = install(created); |
| |
| if (!uninstalledBundles.isEmpty() || !updatedBundles.isEmpty() || !installedBundles.isEmpty()) |
| { |
| Set<Bundle> toRefresh = new HashSet<Bundle>(); |
| toRefresh.addAll(uninstalledBundles); |
| toRefresh.addAll(updatedBundles); |
| toRefresh.addAll(installedBundles); |
| findBundlesWithFragmentsToRefresh(toRefresh); |
| findBundlesWithOptionalPackagesToRefresh(toRefresh); |
| if (toRefresh.size() > 0) |
| { |
| // Refresh if any bundle got uninstalled or updated. |
| refresh(toRefresh); |
| // set the state to reattempt starting managed bundles which aren't already STARTING or ACTIVE |
| setStateChanged(true); |
| } |
| } |
| |
| if (startBundles && isStateChanged()) |
| { |
| // Try to start all the bundles that are not persistently stopped |
| startAllBundles(); |
| |
| delayedStart.addAll(installedBundles); |
| delayedStart.removeAll(uninstalledBundles); |
| // Try to start newly installed bundles, or bundles which we missed on a previous round |
| startBundles(delayedStart); |
| |
| // set the state as unchanged to not reattempt starting failed bundles |
| setStateChanged(false); |
| } |
| } |
| |
| ArtifactListener findListener(File artifact, List<ArtifactListener> listeners) |
| { |
| for (ArtifactListener listener : listeners) { |
| if (listener.canHandle(artifact)) { |
| return listener; |
| } |
| } |
| return null; |
| } |
| |
| boolean transformArtifact(Artifact artifact) |
| { |
| if (artifact.getListener() instanceof ArtifactTransformer) |
| { |
| prepareTempDir(); |
| try |
| { |
| File transformed = ((ArtifactTransformer) artifact.getListener()).transform(artifact.getJaredDirectory(), tmpDir); |
| if (transformed != null) |
| { |
| artifact.setTransformed(transformed); |
| return true; |
| } |
| } |
| catch (Exception e) |
| { |
| log(Logger.LOG_WARNING, |
| "Unable to transform artifact: " + artifact.getPath().getAbsolutePath(), e); |
| } |
| return false; |
| } |
| else if (artifact.getListener() instanceof ArtifactUrlTransformer) |
| { |
| try |
| { |
| URL url = artifact.getJaredUrl(); |
| URL transformed = ((ArtifactUrlTransformer) artifact.getListener()).transform(url); |
| if (transformed != null) |
| { |
| artifact.setTransformedUrl(transformed); |
| return true; |
| } |
| } |
| catch (Exception e) |
| { |
| log(Logger.LOG_WARNING, |
| "Unable to transform artifact: " + artifact.getPath().getAbsolutePath(), e); |
| } |
| return false; |
| } |
| return true; |
| } |
| |
| private void deleteTransformedFile(Artifact artifact) |
| { |
| if (artifact.getTransformed() != null |
| && !artifact.getTransformed().equals(artifact.getPath()) |
| && !artifact.getTransformed().delete()) |
| { |
| log(Logger.LOG_WARNING, |
| "Unable to delete transformed artifact: " + artifact.getTransformed().getAbsolutePath(), null); |
| } |
| } |
| |
| private void deleteJaredDirectory(Artifact artifact) |
| { |
| if (artifact.getJaredDirectory() != null |
| && !artifact.getJaredDirectory().equals(artifact.getPath()) |
| && !artifact.getJaredDirectory().delete()) |
| { |
| log(Logger.LOG_WARNING, |
| "Unable to delete jared artifact: " + artifact.getJaredDirectory().getAbsolutePath(), null); |
| } |
| } |
| |
| |
| private void prepareTempDir() |
| { |
| if (tmpDir == null) |
| { |
| if (!javaIoTmpdir.exists() && !javaIoTmpdir.mkdirs()) { |
| throw new IllegalStateException("Unable to create temporary directory " + javaIoTmpdir); |
| } |
| for (;;) |
| { |
| File f = new File(javaIoTmpdir, "fileinstall-" + Long.toString(random.nextLong())); |
| if (!f.exists() && f.mkdirs()) |
| { |
| tmpDir = f; |
| tmpDir.deleteOnExit(); |
| break; |
| } |
| } |
| } |
| else |
| { |
| prepareDir(tmpDir); |
| } |
| } |
| |
| /** |
| * Create the watched directory, if not existing. |
| * Throws a runtime exception if the directory cannot be created, |
| * or if the provided File parameter does not refer to a directory. |
| * |
| * @param dir |
| * The directory File Install will monitor |
| */ |
| private void prepareDir(File dir) |
| { |
| if (!dir.exists() && !dir.mkdirs()) |
| { |
| log(Logger.LOG_ERROR, |
| "Cannot create folder " |
| + dir |
| + ". Is the folder write-protected?", null); |
| throw new RuntimeException("Cannot create folder: " + dir); |
| } |
| |
| if (!dir.isDirectory()) |
| { |
| log(Logger.LOG_ERROR, |
| "Cannot use " |
| + dir |
| + " because it's not a directory", null); |
| throw new RuntimeException( |
| "Cannot start FileInstall using something that is not a directory"); |
| } |
| } |
| |
| /** |
| * Log a message and optional throwable. If there is a log service we use |
| * it, otherwise we log to the console |
| * |
| * @param message |
| * The message to log |
| * @param e |
| * The throwable to log |
| */ |
| void log(int msgLevel, String message, Throwable e) |
| { |
| Util.log(context, logLevel, msgLevel, message, e); |
| } |
| |
| /** |
| * Check if a bundle is a fragment. |
| */ |
| boolean isFragment(Bundle bundle) |
| { |
| BundleRevision rev = bundle.adapt(BundleRevision.class); |
| return (rev.getTypes() & BundleRevision.TYPE_FRAGMENT) != 0; |
| } |
| |
| /** |
| * Convenience to refresh the packages |
| */ |
| void refresh(Collection<Bundle> bundles) throws InterruptedException |
| { |
| FileInstall.refresh(context, bundles); |
| } |
| |
| /** |
| * Retrieve a property as a long. |
| * |
| * @param properties the properties to retrieve the value from |
| * @param property the name of the property to retrieve |
| * @param dflt the default value |
| * @return the property as a long or the default value |
| */ |
| int getInt(Map<String, String> properties, String property, int dflt) |
| { |
| String value = properties.get(property); |
| if (value != null) |
| { |
| try |
| { |
| return Integer.parseInt(value); |
| } |
| catch (Exception e) |
| { |
| log(Logger.LOG_WARNING, property + " set, but not a int: " + value, null); |
| } |
| } |
| return dflt; |
| } |
| |
| /** |
| * Retrieve a property as a long. |
| * |
| * @param properties the properties to retrieve the value from |
| * @param property the name of the property to retrieve |
| * @param dflt the default value |
| * @return the property as a long or the default value |
| */ |
| long getLong(Map<String, String> properties, String property, long dflt) |
| { |
| String value = properties.get(property); |
| if (value != null) |
| { |
| try |
| { |
| return Long.parseLong(value); |
| } |
| catch (Exception e) |
| { |
| log(Logger.LOG_WARNING, property + " set, but not a long: " + value, null); |
| } |
| } |
| return dflt; |
| } |
| |
| /** |
| * Retrieve a property as a File. |
| * |
| * @param properties the properties to retrieve the value from |
| * @param property the name of the property to retrieve |
| * @param dflt the default value |
| * @return the property as a File or the default value |
| */ |
| File getFile(Map<String, String> properties, String property, File dflt) |
| { |
| String value = properties.get(property); |
| if (value != null) |
| { |
| return new File(value); |
| } |
| return dflt; |
| } |
| |
| /** |
| * Retrieve a property as a boolan. |
| * |
| * @param properties the properties to retrieve the value from |
| * @param property the name of the property to retrieve |
| * @param dflt the default value |
| * @return the property as a boolean or the default value |
| */ |
| boolean getBoolean(Map<String, String> properties, String property, boolean dflt) |
| { |
| String value = properties.get(property); |
| if (value != null) |
| { |
| return Boolean.valueOf(value); |
| } |
| return dflt; |
| } |
| |
| public void close() |
| { |
| this.context.removeBundleListener(this); |
| interrupt(); |
| for (Artifact artifact : getArtifacts()) { |
| deleteTransformedFile(artifact); |
| deleteJaredDirectory(artifact); |
| } |
| try |
| { |
| scanner.close(); |
| } |
| catch (IOException e) |
| { |
| // Ignore |
| } |
| try |
| { |
| join(10000); |
| } |
| catch (InterruptedException ie) |
| { |
| // Ignore |
| } |
| } |
| |
| /** |
| * This method goes through all the currently installed bundles |
| * and returns information about those bundles whose location |
| * refers to a file in our {@link #watchedDirectory}. |
| */ |
| private void initializeCurrentManagedBundles() |
| { |
| Bundle[] bundles = this.context.getBundles(); |
| String watchedDirPath = watchedDirectory.toURI().normalize().getPath(); |
| Map<File, Long> checksums = new HashMap<File, Long>(); |
| for (Bundle bundle : bundles) { |
| // Convert to a URI because the location of a bundle |
| // is typically a URI. At least, that's the case for |
| // autostart bundles and bundles installed by fileinstall. |
| // Normalisation is needed to ensure that we don't treat (e.g.) |
| // /tmp/foo and /tmp//foo differently. |
| String location = bundle.getLocation(); |
| String path = null; |
| if (location != null && |
| !location.equals(Constants.SYSTEM_BUNDLE_LOCATION)) { |
| URI uri; |
| try { |
| uri = new URI(bundle.getLocation()).normalize(); |
| } catch (URISyntaxException e) { |
| // Let's try to interpret the location as a file path |
| uri = new File(location).toURI().normalize(); |
| } |
| if (uri.isOpaque() && uri.getSchemeSpecificPart() != null) { |
| // blueprint:file:/tmp/foo/baa.jar -> file:/tmp/foo/baa.jar |
| // blueprint:mvn:foo.baa/baa/0.0.1 -> mvn:foo.baa/baa/0.0.1 |
| // wrap:file:/tmp/foo/baa-1.0.jar$Symbolic-Name=baa&Version=1.0 -> file:/tmp/foo/baa-1.0.jar$Symbolic-Name=baa&Version1.0 |
| final String schemeSpecificPart = uri.getSchemeSpecificPart(); |
| // extract content behind the 'file:' protocol of scheme specific path |
| final int lastIndexOfFileProtocol = schemeSpecificPart.lastIndexOf("file:"); |
| final int offsetFileProtocol = lastIndexOfFileProtocol >= 0 ? lastIndexOfFileProtocol + "file:".length() : 0; |
| final int firstIndexOfDollar = schemeSpecificPart.indexOf("$"); |
| final int endOfPath = firstIndexOfDollar >= 0 ? firstIndexOfDollar : schemeSpecificPart.length(); |
| // file:/tmp/foo/baa.jar -> /tmp/foo/baa.jar |
| // mvn:foo.baa/baa/0.0.1 -> mvn:foo.baa/baa/0.0.1 |
| // file:/tmp/foo/baa-1.0.jar$Symbolic-Name=baa&Version=1.0 -> /tmp/foo/baa-1.0.jar |
| path = schemeSpecificPart.substring(offsetFileProtocol, endOfPath); |
| } else { |
| // file:/tmp/foo/baa.jar -> /tmp/foo/baa.jar |
| // mnv:foo.baa/baa/0.0.1 -> foo.baa/baa/0.0.1 |
| path = uri.getPath(); |
| } |
| } |
| if (path == null) { |
| // jar.getPath is null means we could not parse the location |
| // as a meaningful URI or file path. |
| // We can't do any meaningful processing for this bundle. |
| continue; |
| } |
| final int index = path.lastIndexOf('/'); |
| if (index != -1 && path.startsWith(watchedDirPath)) { |
| Artifact artifact = new Artifact(); |
| artifact.setBundleId(bundle.getBundleId()); |
| artifact.setChecksum(Util.loadChecksum(bundle, context)); |
| artifact.setListener(null); |
| artifact.setPath(new File(path)); |
| setArtifact(new File(path), artifact); |
| checksums.put(new File(path), artifact.getChecksum()); |
| } |
| } |
| scanner.initialize(checksums); |
| } |
| |
| /** |
| * This method installs a collection of artifacts. |
| * @param artifacts Collection of {@link Artifact}s to be installed |
| * @return List of Bundles just installed |
| */ |
| private Collection<Bundle> install(Collection<Artifact> artifacts) |
| { |
| List<Bundle> bundles = new ArrayList<Bundle>(); |
| for (Artifact artifact : artifacts) { |
| Bundle bundle = install(artifact); |
| if (bundle != null) { |
| bundles.add(bundle); |
| } |
| } |
| return bundles; |
| } |
| |
| /** |
| * This method uninstalls a collection of artifacts. |
| * @param artifacts Collection of {@link Artifact}s to be uninstalled |
| * @return Collection of Bundles that got uninstalled |
| */ |
| private Collection<Bundle> uninstall(Collection<Artifact> artifacts) |
| { |
| List<Bundle> bundles = new ArrayList<Bundle>(); |
| for (Artifact artifact : artifacts) { |
| Bundle bundle = uninstall(artifact); |
| if (bundle != null) { |
| bundles.add(bundle); |
| } |
| } |
| return bundles; |
| } |
| |
| /** |
| * This method updates a collection of artifacts. |
| * |
| * @param artifacts Collection of {@link Artifact}s to be updated. |
| * @return Collection of bundles that got updated |
| */ |
| private Collection<Bundle> update(Collection<Artifact> artifacts) |
| { |
| List<Bundle> bundles = new ArrayList<Bundle>(); |
| for (Artifact artifact : artifacts) { |
| Bundle bundle = update(artifact); |
| if (bundle != null) { |
| bundles.add(bundle); |
| } |
| } |
| return bundles; |
| } |
| |
| /** |
| * Install an artifact and return the bundle object. |
| * It uses {@link Artifact#getPath()} as location |
| * of the new bundle. Before installing a file, |
| * it sees if the file has been identified as a bad file in |
| * earlier run. If yes, then it compares to see if the file has changed |
| * since then. It installs the file if the file has changed. |
| * If the file has not been identified as a bad file in earlier run, |
| * then it always installs it. |
| * |
| * @param artifact the artifact to be installed |
| * @return Bundle object that was installed |
| */ |
| private Bundle install(Artifact artifact) |
| { |
| File path = artifact.getPath(); |
| Bundle bundle = null; |
| try |
| { |
| // If the listener is an installer, ask for an update |
| if (artifact.getListener() instanceof ArtifactInstaller) |
| { |
| ((ArtifactInstaller) artifact.getListener()).install(path); |
| } |
| // if the listener is an url transformer |
| else if (artifact.getListener() instanceof ArtifactUrlTransformer) |
| { |
| Artifact badArtifact = installationFailures.get(path); |
| if (badArtifact != null && badArtifact.getChecksum() == artifact.getChecksum()) |
| { |
| return null; // Don't attempt to install it; nothing has changed. |
| } |
| URL transformed = artifact.getTransformedUrl(); |
| String location = transformed.toString(); |
| BufferedInputStream in = new BufferedInputStream(transformed.openStream()); |
| try |
| { |
| bundle = installOrUpdateBundle(location, in, artifact.getChecksum()); |
| } |
| finally |
| { |
| in.close(); |
| } |
| artifact.setBundleId(bundle.getBundleId()); |
| } |
| // if the listener is an artifact transformer |
| else if (artifact.getListener() instanceof ArtifactTransformer) |
| { |
| Artifact badArtifact = installationFailures.get(path); |
| if (badArtifact != null && badArtifact.getChecksum() == artifact.getChecksum()) |
| { |
| return null; // Don't attempt to install it; nothing has changed. |
| } |
| File transformed = artifact.getTransformed(); |
| String location = path.toURI().normalize().toString(); |
| BufferedInputStream in = new BufferedInputStream(new FileInputStream(transformed != null ? transformed : path)); |
| try |
| { |
| bundle = installOrUpdateBundle(location, in, artifact.getChecksum()); |
| } |
| finally |
| { |
| in.close(); |
| } |
| artifact.setBundleId(bundle.getBundleId()); |
| } |
| installationFailures.remove(path); |
| setArtifact(path, artifact); |
| } |
| catch (Exception e) |
| { |
| log(Logger.LOG_ERROR, "Failed to install artifact: " + path, e); |
| |
| // Add it our bad jars list, so that we don't |
| // attempt to install it again and again until the underlying |
| // jar has been modified. |
| installationFailures.put(path, artifact); |
| } |
| return bundle; |
| } |
| |
| private Bundle installOrUpdateBundle( |
| String bundleLocation, BufferedInputStream is, long checksum) |
| throws IOException, BundleException |
| { |
| is.mark(256 * 1024); |
| JarInputStream jar = new JarInputStream(is); |
| Manifest m = jar.getManifest(); |
| if( m == null ) { |
| throw new BundleException( |
| "The bundle " + bundleLocation + " does not have a META-INF/MANIFEST.MF! "+ |
| "Make sure, META-INF and MANIFEST.MF are the first 2 entries in your JAR!"); |
| } |
| String sn = m.getMainAttributes().getValue(Constants.BUNDLE_SYMBOLICNAME); |
| String vStr = m.getMainAttributes().getValue(Constants.BUNDLE_VERSION); |
| Version v = vStr == null ? Version.emptyVersion : Version.parseVersion(vStr); |
| Bundle[] bundles = context.getBundles(); |
| for (Bundle b : bundles) { |
| if (b.getSymbolicName() != null && b.getSymbolicName().equals(sn)) { |
| vStr = b.getHeaders().get(Constants.BUNDLE_VERSION); |
| Version bv = vStr == null ? Version.emptyVersion : Version.parseVersion(vStr); |
| if (v.equals(bv)) { |
| is.reset(); |
| if (Util.loadChecksum(b, context) != checksum) { |
| log(Logger.LOG_WARNING, |
| "A bundle with the same symbolic name (" |
| + sn + ") and version (" + vStr |
| + ") is already installed. Updating this bundle instead.", null |
| ); |
| stopTransient(b); |
| Util.storeChecksum(b, checksum, context); |
| b.update(is); |
| } |
| return b; |
| } |
| } |
| } |
| is.reset(); |
| Util.log(context, Logger.LOG_INFO, "Installing bundle " + sn |
| + " / " + v, null); |
| Bundle b = context.installBundle(bundleLocation, is); |
| Util.storeChecksum(b, checksum, context); |
| |
| // Set default start level at install time, the user can override it if he wants |
| if (startLevel != 0) |
| { |
| b.adapt(BundleStartLevel.class).setStartLevel(startLevel); |
| } |
| |
| return b; |
| } |
| |
| /** |
| * Uninstall a jar file. |
| */ |
| private Bundle uninstall(Artifact artifact) |
| { |
| Bundle bundle = null; |
| try |
| { |
| File path = artifact.getPath(); |
| // Find a listener for this artifact if needed |
| if (artifact.getListener() == null) |
| { |
| artifact.setListener(findListener(path, fileInstall.getListeners())); |
| } |
| // Forget this artifact |
| removeArtifact(path); |
| // Delete transformed file |
| deleteTransformedFile(artifact); |
| // if the listener is an installer, uninstall the artifact |
| if (artifact.getListener() instanceof ArtifactInstaller) |
| { |
| ((ArtifactInstaller) artifact.getListener()).uninstall(path); |
| } |
| // else we need uninstall the bundle |
| else if (artifact.getBundleId() != 0) |
| { |
| // old can't be null because of the way we calculate deleted list. |
| bundle = context.getBundle(artifact.getBundleId()); |
| if (bundle == null) |
| { |
| log(Logger.LOG_WARNING, |
| "Failed to uninstall bundle: " |
| + path + " with id: " |
| + artifact.getBundleId() |
| + ". The bundle has already been uninstalled", null); |
| return null; |
| } |
| log(Logger.LOG_INFO, |
| "Uninstalling bundle " |
| + bundle.getBundleId() + " (" |
| + bundle.getSymbolicName() + ")", null); |
| bundle.uninstall(); |
| } |
| } |
| catch (Exception e) |
| { |
| log(Logger.LOG_WARNING, "Failed to uninstall artifact: " + artifact.getPath(), e); |
| } |
| return bundle; |
| } |
| |
| private Bundle update(Artifact artifact) |
| { |
| Bundle bundle = null; |
| try |
| { |
| File path = artifact.getPath(); |
| // If the listener is an installer, ask for an update |
| if (artifact.getListener() instanceof ArtifactInstaller) |
| { |
| ((ArtifactInstaller) artifact.getListener()).update(path); |
| } |
| // if the listener is an url transformer |
| else if (artifact.getListener() instanceof ArtifactUrlTransformer) |
| { |
| URL transformed = artifact.getTransformedUrl(); |
| bundle = context.getBundle(artifact.getBundleId()); |
| if (bundle == null) |
| { |
| log(Logger.LOG_WARNING, |
| "Failed to update bundle: " |
| + path + " with ID " |
| + artifact.getBundleId() |
| + ". The bundle has been uninstalled", null); |
| return null; |
| } |
| Util.log(context, Logger.LOG_INFO, "Updating bundle " + bundle.getSymbolicName() |
| + " / " + bundle.getVersion(), null); |
| stopTransient(bundle); |
| Util.storeChecksum(bundle, artifact.getChecksum(), context); |
| InputStream in = (transformed != null) |
| ? transformed.openStream() |
| : new FileInputStream(path); |
| try |
| { |
| bundle.update(in); |
| } |
| finally |
| { |
| in.close(); |
| } |
| } |
| // else we need to ask for an update on the bundle |
| else if (artifact.getListener() instanceof ArtifactTransformer) |
| { |
| File transformed = artifact.getTransformed(); |
| bundle = context.getBundle(artifact.getBundleId()); |
| if (bundle == null) |
| { |
| log(Logger.LOG_WARNING, |
| "Failed to update bundle: " |
| + path + " with ID " |
| + artifact.getBundleId() |
| + ". The bundle has been uninstalled", null); |
| return null; |
| } |
| Util.log(context, Logger.LOG_INFO, "Updating bundle " + bundle.getSymbolicName() |
| + " / " + bundle.getVersion(), null); |
| stopTransient(bundle); |
| Util.storeChecksum(bundle, artifact.getChecksum(), context); |
| InputStream in = new FileInputStream(transformed != null ? transformed : path); |
| try |
| { |
| bundle.update(in); |
| } |
| finally |
| { |
| in.close(); |
| } |
| } |
| } |
| catch (Throwable t) |
| { |
| log(Logger.LOG_WARNING, "Failed to update artifact " + artifact.getPath(), t); |
| } |
| return bundle; |
| } |
| |
| private void stopTransient(Bundle bundle) throws BundleException |
| { |
| // Stop the bundle transiently so that it will be restarted when startAllBundles() is called |
| // but this avoids the need to restart the bundle twice (once for the update and another one |
| // when refreshing packages). |
| if (startBundles) |
| { |
| if (!isFragment(bundle)) |
| { |
| bundle.stop(Bundle.STOP_TRANSIENT); |
| } |
| } |
| } |
| |
| /** |
| * Tries to start all the bundles which somehow got stopped transiently. |
| * The File Install component will only retry the start When {@link #USE_START_TRANSIENT} |
| * is set to true or when a bundle is persistently started. Persistently stopped bundles |
| * are ignored. |
| */ |
| private void startAllBundles() |
| { |
| FrameworkStartLevel startLevelSvc = context.getBundle(0).adapt(FrameworkStartLevel.class); |
| List<Bundle> bundles = new ArrayList<Bundle>(); |
| for (Artifact artifact : getArtifacts()) { |
| if (artifact.getBundleId() > 0) { |
| Bundle bundle = context.getBundle(artifact.getBundleId()); |
| if (bundle != null) { |
| if (bundle.getState() != Bundle.STARTING && bundle.getState() != Bundle.ACTIVE |
| && (useStartTransient || bundle.adapt(BundleStartLevel.class).isPersistentlyStarted()) |
| && startLevelSvc.getStartLevel() >= bundle.adapt(BundleStartLevel.class).getStartLevel()) { |
| bundles.add(bundle); |
| } |
| } |
| } |
| } |
| startBundles(bundles); |
| } |
| |
| /** |
| * Starts a bundle and removes it from the Collection when successfully started. |
| */ |
| private void startBundles(Collection<Bundle> bundles) |
| { |
| for (Iterator<Bundle> b = bundles.iterator(); b.hasNext(); ) |
| { |
| if (startBundle(b.next())) |
| { |
| b.remove(); |
| } |
| } |
| } |
| |
| /** |
| * Start a bundle, if the framework's startlevel allows it. |
| * @param bundle the bundle to start. |
| * @return whether the bundle was started. |
| */ |
| private boolean startBundle(Bundle bundle) |
| { |
| FrameworkStartLevel startLevelSvc = context.getBundle(0).adapt(FrameworkStartLevel.class); |
| // Fragments can never be started. |
| // Bundles can only be started transient when the start level of the framework is high |
| // enough. Persistent (i.e. non-transient) starts will simply make the framework start the |
| // bundle when the start level is high enough. |
| if (startBundles |
| && bundle.getState() != Bundle.UNINSTALLED |
| && !isFragment(bundle) |
| && startLevelSvc.getStartLevel() >= bundle.adapt(BundleStartLevel.class).getStartLevel()) |
| { |
| try |
| { |
| int options = useStartTransient ? Bundle.START_TRANSIENT : 0; |
| options |= useStartActivationPolicy ? Bundle.START_ACTIVATION_POLICY : 0; |
| bundle.start(options); |
| log(Logger.LOG_INFO, "Started bundle: " + bundle.getLocation(), null); |
| return true; |
| } |
| catch (BundleException e) |
| { |
| // Don't log this as an error, instead we start the bundle repeatedly. |
| log(Logger.LOG_WARNING, "Error while starting bundle: " + bundle.getLocation(), e); |
| } |
| } |
| return false; |
| } |
| |
| protected Set<Bundle> getScopedBundles(String scope) { |
| // No bundles to check |
| if (SCOPE_NONE.equals(scope)) { |
| return new HashSet<Bundle>(); |
| } |
| // Go through managed bundles |
| else if (SCOPE_MANAGED.equals(scope)) { |
| Set<Bundle> bundles = new HashSet<Bundle>(); |
| for (Artifact artifact : getArtifacts()) { |
| if (artifact.getBundleId() > 0) { |
| Bundle bundle = context.getBundle(artifact.getBundleId()); |
| if (bundle != null) { |
| bundles.add(bundle); |
| } |
| } |
| } |
| return bundles; |
| // Go through all bundles |
| } else { |
| return new HashSet<Bundle>(Arrays.asList(context.getBundles())); |
| } |
| } |
| |
| protected void findBundlesWithFragmentsToRefresh(Set<Bundle> toRefresh) { |
| Set<Bundle> fragments = new HashSet<Bundle>(); |
| Set<Bundle> bundles = getScopedBundles(fragmentScope); |
| for (Bundle b : toRefresh) { |
| if (b.getState() != Bundle.UNINSTALLED) { |
| String hostHeader = b.getHeaders().get(Constants.FRAGMENT_HOST); |
| if (hostHeader != null) { |
| Clause[] clauses = Parser.parseHeader(hostHeader); |
| if (clauses != null && clauses.length > 0) { |
| Clause path = clauses[0]; |
| for (Bundle hostBundle : bundles) { |
| if (hostBundle.getSymbolicName() != null && |
| hostBundle.getSymbolicName().equals(path.getName())) { |
| String ver = path.getAttribute(Constants.BUNDLE_VERSION_ATTRIBUTE); |
| if (ver != null) { |
| VersionRange v = VersionRange.parseVersionRange(ver); |
| if (v.contains(VersionTable.getVersion(hostBundle.getHeaders().get(Constants.BUNDLE_VERSION)))) { |
| fragments.add(hostBundle); |
| } |
| } else { |
| fragments.add(hostBundle); |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| toRefresh.addAll(fragments); |
| } |
| |
| protected void findBundlesWithOptionalPackagesToRefresh(Set<Bundle> toRefresh) { |
| Set<Bundle> bundles = getScopedBundles(optionalScope); |
| // First pass: include all bundles contained in these features |
| bundles.removeAll(toRefresh); |
| if (bundles.isEmpty()) { |
| return; |
| } |
| // Second pass: for each bundle, check if there is any unresolved optional package that could be resolved |
| Map<Bundle, List<Clause>> imports = new HashMap<Bundle, List<Clause>>(); |
| for (Iterator<Bundle> it = bundles.iterator(); it.hasNext(); ) { |
| Bundle b = it.next(); |
| String importsStr = b.getHeaders().get(Constants.IMPORT_PACKAGE); |
| List<Clause> importsList = getOptionalImports(importsStr); |
| if (importsList.isEmpty()) { |
| it.remove(); |
| } else { |
| imports.put(b, importsList); |
| } |
| } |
| if (bundles.isEmpty()) { |
| return; |
| } |
| // Third pass: compute a list of packages that are exported by our bundles and see if |
| // some exported packages can be wired to the optional imports |
| List<Clause> exports = new ArrayList<Clause>(); |
| for (Bundle b : toRefresh) { |
| if (b.getState() != Bundle.UNINSTALLED) { |
| String exportsStr = b.getHeaders().get(Constants.EXPORT_PACKAGE); |
| if (exportsStr != null) { |
| Clause[] exportsList = Parser.parseHeader(exportsStr); |
| exports.addAll(Arrays.asList(exportsList)); |
| } |
| } |
| } |
| for (Iterator<Bundle> it = bundles.iterator(); it.hasNext(); ) { |
| Bundle b = it.next(); |
| List<Clause> importsList = imports.get(b); |
| for (Iterator<Clause> itpi = importsList.iterator(); itpi.hasNext(); ) { |
| Clause pi = itpi.next(); |
| boolean matching = false; |
| for (Clause pe : exports) { |
| if (pi.getName().equals(pe.getName())) { |
| String evStr = pe.getAttribute(Constants.VERSION_ATTRIBUTE); |
| String ivStr = pi.getAttribute(Constants.VERSION_ATTRIBUTE); |
| Version exported = evStr != null ? Version.parseVersion(evStr) : Version.emptyVersion; |
| VersionRange imported = ivStr != null ? VersionRange.parseVersionRange(ivStr) : VersionRange.ANY_VERSION; |
| if (imported.contains(exported)) { |
| matching = true; |
| break; |
| } |
| } |
| } |
| if (!matching) { |
| itpi.remove(); |
| } |
| } |
| if (importsList.isEmpty()) { |
| it.remove(); |
| // } else { |
| // LOGGER.debug("Refreshing bundle {} ({}) to solve the following optional imports", b.getSymbolicName(), b.getBundleId()); |
| // for (Clause p : importsList) { |
| // LOGGER.debug(" {}", p); |
| // } |
| // |
| } |
| } |
| toRefresh.addAll(bundles); |
| } |
| |
| protected List<Clause> getOptionalImports(String importsStr) |
| { |
| Clause[] imports = Parser.parseHeader(importsStr); |
| List<Clause> result = new LinkedList<Clause>(); |
| for (Clause anImport : imports) |
| { |
| String resolution = anImport.getDirective(Constants.RESOLUTION_DIRECTIVE); |
| if (Constants.RESOLUTION_OPTIONAL.equals(resolution)) |
| { |
| result.add(anImport); |
| } |
| } |
| return result; |
| } |
| |
| public void addListener(ArtifactListener listener, long stamp) |
| { |
| if (updateWithListeners) |
| { |
| for (Artifact artifact : getArtifacts()) |
| { |
| if (artifact.getListener() == null && artifact.getBundleId() > 0) |
| { |
| Bundle bundle = context.getBundle(artifact.getBundleId()); |
| if (bundle != null && bundle.getLastModified() < stamp) |
| { |
| File path = artifact.getPath(); |
| if (listener.canHandle(path)) |
| { |
| synchronized (processingFailures) |
| { |
| processingFailures.add(path); |
| } |
| } |
| } |
| } |
| } |
| } |
| synchronized (this) |
| { |
| this.notifyAll(); |
| } |
| } |
| |
| public void removeListener(ArtifactListener listener) |
| { |
| for (Artifact artifact : getArtifacts()) |
| { |
| if (artifact.getListener() == listener) |
| { |
| artifact.setListener(null); |
| } |
| } |
| synchronized (this) |
| { |
| this.notifyAll(); |
| } |
| } |
| |
| private Artifact getArtifact(File file) |
| { |
| synchronized (currentManagedArtifacts) |
| { |
| return currentManagedArtifacts.get(file); |
| } |
| } |
| |
| private List<Artifact> getArtifacts() |
| { |
| synchronized (currentManagedArtifacts) |
| { |
| return new ArrayList<Artifact>(currentManagedArtifacts.values()); |
| } |
| } |
| |
| private void setArtifact(File file, Artifact artifact) |
| { |
| synchronized (currentManagedArtifacts) |
| { |
| currentManagedArtifacts.put(file, artifact); |
| } |
| } |
| |
| private void removeArtifact(File file) |
| { |
| synchronized (currentManagedArtifacts) |
| { |
| currentManagedArtifacts.remove(file); |
| } |
| } |
| |
| private void setStateChanged(boolean changed) { |
| this.stateChanged.set(changed); |
| } |
| |
| private boolean isStateChanged() { |
| return stateChanged.get(); |
| } |
| |
| } |