Package org.apache.servicemix.kernel.filemonitor

Source Code of org.apache.servicemix.kernel.filemonitor.FileMonitor

/**
*
* 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.servicemix.kernel.filemonitor;

import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
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.InvalidSyntaxException;
import org.osgi.framework.ServiceEvent;
import org.osgi.framework.ServiceListener;
import org.osgi.framework.ServiceReference;
import org.osgi.service.cm.Configuration;
import org.osgi.service.cm.ConfigurationAdmin;
import org.osgi.service.packageadmin.PackageAdmin;
import org.osgi.service.prefs.Preferences;
import org.osgi.service.prefs.PreferencesService;
import org.osgi.util.tracker.ServiceTracker;

/**
* Watches a deploy directory for files that are added, updated or removed then
* processing them. Currently we support OSGi bundles, OSGi configuration files
* and expanded directories of OSGi bundles.
*
* @version $Revision: 1.1 $
*/
public class FileMonitor {

    public final static String CONFIG_DIR = "org.apache.servicemix.filemonitor.configDir";
    public final static String DEPLOY_DIR = "org.apache.servicemix.filemonitor.monitorDir";
    public final static String GENERATED_JAR_DIR = "org.apache.servicemix.filemonitor.generatedJarDir";
    public final static String SCAN_INTERVAL = "org.apache.servicemix.filemonitor.scanInterval";
    public final static String PREFERENCE_KEY = "FileMonitor";

    protected static final String ALIAS_KEY = "_alias_factory_pid";

    private static final Log LOGGER = LogFactory.getLog(FileMonitor.class);

    private FileMonitorActivator activator;
    private File configDir;
    private File deployDir;
    private File generateDir;
    private Scanner scanner = new Scanner();
    private long scanInterval = 500L;
    private boolean loggedConfigAdminWarning;
    private List<Bundle> bundlesToStart = new ArrayList<Bundle>();
    private List<Bundle> bundlesToUpdate = new ArrayList<Bundle>();
    private Map<String, String> artifactToBundle = new HashMap<String, String>();
    private final Set<String> pendingTransformationArtifacts = new HashSet<String>();
    private final Set<Bundle> pendingStartBundles = new HashSet<Bundle>();
    private ServiceListener deployerListener;
    private BundleListener bundleListener;
    private Executor executor;
    private String pid;

    public FileMonitor() {
        String base = System.getProperty("servicemix.base", ".");
        configDir = new File(base, "etc");
        deployDir = new File(base, "deploy");
        generateDir = new File(base, "data/generated-bundles");
    }

    @SuppressWarnings("unchecked")
    public FileMonitor(FileMonitorActivator activator, Dictionary properties, String pid) {
        this();

        this.activator = activator;
        this.pid = pid;

        File value = getFileValue(properties, CONFIG_DIR);
        if (value != null) {
            configDir = value;
        }
        value = getFileValue(properties, DEPLOY_DIR);
        if (value != null) {
            deployDir = value;
        }
        value = getFileValue(properties, GENERATED_JAR_DIR);
        if (value != null) {
            generateDir = value;
        }
        Long i = getLongValue(properties, SCAN_INTERVAL);
        if (i != null) {
            scanInterval = i;
        }
    }

    public void start() {
        executor = Executors.newSingleThreadExecutor();
        if (configDir != null) {
            configDir.mkdirs();
        }
        deployDir.mkdirs();
        generateDir.mkdirs();

        List<File> dirs = new ArrayList<File>();
        if (configDir != null) {
            dirs.add(configDir);
        }
        dirs.add(deployDir);
        scanner.setScanDirs(dirs);
        scanner.setScanInterval(scanInterval);
        // load the scan results for this monitor
        loadScannerState();
        scanner.addListener(new Scanner.BulkListener() {
            public void filesChanged(List<String> filenames) throws Exception {
                onFilesChanged(filenames);
            }
        });

        LOGGER.info("Starting to monitor the deploy directory: " + deployDir + " every " + scanInterval
                    + " millis");
        if (configDir != null) {
            LOGGER.info("Config directory is at: " + configDir);
        }
        LOGGER.info("Will generate bundles from expanded source directories to: " + generateDir);

        executor.execute(new Runnable() {
            public void run() {
                scanner.start();
            }
        });
    }

    public void stop() {
        // first stop the scanner
        scanner.stop();
        // load the scan results for this monitor
        saveScannerState();
    }

    // Properties
    // -------------------------------------------------------------------------

    public BundleContext getContext() {
        return activator.getContext();
    }

    public FileMonitorActivator getActivator() {
        return activator;
    }

    public void setActivator(FileMonitorActivator activator) {
        this.activator = activator;
    }

    public File getConfigDir() {
        return configDir;
    }

    public void setConfigDir(File configDir) {
        this.configDir = configDir;
    }

    public File getDeployDir() {
        return deployDir;
    }

    public void setDeployDir(File deployDir) {
        this.deployDir = deployDir;
    }

    public File getGenerateDir() {
        return generateDir;
    }

    public void setGenerateDir(File generateDir) {
        this.generateDir = generateDir;
    }

    public long getScanInterval() {
        return scanInterval;
    }

    public void setScanInterval(long scanInterval) {
        this.scanInterval = scanInterval;
    }

    // Implementation methods
    // -------------------------------------------------------------------------

    protected synchronized void onFilesChanged(Collection<String> filenames) {
        bundlesToStart.clear();
        bundlesToUpdate.clear();
        Set<File> bundleJarsCreated = new HashSet<File>();

        for (String filename : filenames) {
            File file = new File(filename);
            try {
                LOGGER.debug("File changed: " + filename);

                // Handle config files
                if (isValidConfigFile(file)) {
                    if (file.exists()) {
                        updateConfiguration(file);
                    } else {
                        deleteConfiguration(file);
                    }
                    continue;
                }

                // Handle exploded artifacts removal
                if (!file.exists() && file.getName().equals("MANIFEST.MF")) {
                    File parentFile = file.getParentFile();
                    if (parentFile.getName().equals("META-INF")) {
                        File bundleDir = parentFile.getParentFile();
                        if (isValidBundleSourceDirectory(bundleDir)) {
                            undeployBundle(bundleDir);
                            continue;
                        }
                    }
                }

                // Handle exploded artifacts add
                File jardir = getExpandedBundleRootDirectory(file);
                if (jardir != null) {
                    if (bundleJarsCreated.contains(jardir)) {
                        continue;
                    }
                    bundleJarsCreated.add(jardir);
                    file = createBundleJar(jardir);
                }

                // Transformation step
                if (file.exists()) {
                    File f = transformArtifact(file);
                    if (f == null) {
                        LOGGER.warn("Unsupported deployment: " + filename);
                        rescheduleTransformation(file);
                        continue;
                    }
                    file = f;
                } else {
                    String transformedFile = artifactToBundle.get(filename);
                    if (transformedFile != null) {
                        file = new File(transformedFile);
                        if (file.exists()) {
                            file.delete();
                        }
                    }
                }

                // Handle final bundles
                if (isValidArtifactFile(file)) {
                    if (file.exists()) {
                        deployBundle(file);
                    } else {
                        undeployBundle(file);
                    }
                }
            } catch (Exception e) {
                LOGGER.warn("Failed to process: " + file + ". Reason: " + e, e);
            }
        }
        refreshPackagesAndStartOrUpdateBundles();
    }

    private void rescheduleTransformation(File file) {
        synchronized (pendingTransformationArtifacts) {
            pendingTransformationArtifacts.add(file.getAbsolutePath());
        }
        if (deployerListener == null) {
            try {
                String filter = "(" + Constants.OBJECTCLASS + "=" + DeploymentListener.class.getName() + ")";
                deployerListener = new ServiceListener() {
                    public void serviceChanged(ServiceEvent event) {
                        executor.execute(new Runnable() {
                            public void run() {
                                Set<String> files;
                                synchronized (pendingTransformationArtifacts) {
                                    files = new HashSet<String>(pendingTransformationArtifacts);
                                    pendingTransformationArtifacts.clear();
                                }
                                onFilesChanged(files);
                            }
                        });
                    }
                };
                getContext().addServiceListener(deployerListener, filter);
            } catch (InvalidSyntaxException e) {
                // Ignore
            }
        }
    }

    private File transformArtifact(File file) throws Exception {
        // Check registered deployers
        ServiceReference[] srvRefs = getContext().getAllServiceReferences(DeploymentListener.class.getName(),
                                                                          null);
        if (srvRefs != null) {
            for (ServiceReference sr : srvRefs) {
                try {
                    DeploymentListener deploymentListener = (DeploymentListener)getContext().getService(sr);
                    if (deploymentListener.canHandle(file)) {
                        File transformedFile = deploymentListener.handle(file, getGenerateDir());
                        artifactToBundle.put(file.getAbsolutePath(), transformedFile.getAbsolutePath());
                        return transformedFile;
                    }
                } finally {
                    getContext().ungetService(sr);
                }
            }
        }
        JarFile jar = null;
        try {
            // Handle OSGi bundles with the default deployer
            if (file.getName().endsWith("txt") || file.getName().endsWith("xml")
                || file.getName().endsWith("properties")) {
                // that's file type which is not supported as bundle and avoid
                // exception in the log
                return null;
            }
            jar = new JarFile(file);
            Manifest m = jar.getManifest();
            if (m.getMainAttributes().getValue(new Attributes.Name("Bundle-SymbolicName")) != null
                && m.getMainAttributes().getValue(new Attributes.Name("Bundle-Version")) != null) {
                return file;
            }
        } catch (Exception e) {
            LOGGER.debug("Error transforming artifact " + file.getName(), e);
        } finally {
            if (jar != null) {
                jar.close();
            }
        }
        return null;
    }

    protected void deployBundle(File file) throws IOException, BundleException {
        LOGGER.info("Deploying: " + file.getCanonicalPath());

        InputStream in = new FileInputStream(file);

        try {
            Bundle bundle = getBundleForJarFile(file);
            if (bundle != null) {
                bundlesToUpdate.add(bundle);
            } else {
                bundle = getContext().installBundle(file.getCanonicalFile().toURI().toString(), in);
                if (!isBundleFragment(bundle)) {
                    bundlesToStart.add(bundle);
                }
            }
        } finally {
            closeQuietly(in);
        }
    }

    protected void undeployBundle(File file) throws BundleException, IOException {
        LOGGER.info("Undeploying: " + file.getCanonicalPath());
        Bundle bundle = getBundleForJarFile(file);

        if (bundle == null) {
            LOGGER.warn("Could not find Bundle for file: " + file.getCanonicalPath());
        } else {
            if (!isBundleFragment(bundle)) {
                bundle.stop();
            }
            bundle.uninstall();
        }
    }

    protected Bundle getBundleForJarFile(File file) throws IOException {
        String absoluteFilePath = file.getAbsoluteFile().toURI().toString();
        Bundle bundles[] = getContext().getBundles();
        for (Bundle bundle : bundles) {
            String location = bundle.getLocation();
            if (filePathsMatch(absoluteFilePath, location)) {
                return bundle;
            }
        }
        return null;
    }

    protected static boolean filePathsMatch(String p1, String p2) {
        p1 = normalizeFilePath(p1);
        p2 = normalizeFilePath(p2);
        return (p1 != null && p1.equalsIgnoreCase(p2));
    }

    protected static String normalizeFilePath(String path) {
        if (path != null) {
            path = path.replaceFirst("file:/*", "");
            path = path.replaceAll("[\\\\/]+", "/");
        }
        return path;
    }

    protected void updateConfiguration(File file) throws IOException, InvalidSyntaxException {
        ConfigurationAdmin configurationAdmin = activator.getConfigurationAdmin();
        if (configurationAdmin == null) {
            if (!loggedConfigAdminWarning) {
                LOGGER.warn("No ConfigurationAdmin so cannot deploy configurations");
                loggedConfigAdminWarning = true;
            }
        } else {
            Properties properties = new Properties();
            InputStream in = new FileInputStream(file);
            try {
                properties.load(in);
                interpolation(properties);
                closeQuietly(in);
                String[] pid = parsePid(file);
                Hashtable<Object, Object> hashtable = new Hashtable<Object, Object>();
                hashtable.putAll(properties);
                if (pid[1] != null) {
                    hashtable.put(ALIAS_KEY, pid[1]);
                }

                Configuration config = getConfiguration(pid[0], pid[1]);
                if (config.getBundleLocation() != null) {
                    config.setBundleLocation(null);
                }
                config.update(hashtable);
            } finally {
                closeQuietly(in);
            }
        }
    }

    protected void interpolation(Properties properties) {
        for (Enumeration e = properties.propertyNames(); e.hasMoreElements();) {
            String key = (String)e.nextElement();
            String val = properties.getProperty(key);
            Matcher matcher = Pattern.compile("\\$\\{([^}]+)\\}").matcher(val);
            while (matcher.find()) {
                String rep = System.getProperty(matcher.group(1));
                if (rep != null) {
                    val = val.replace(matcher.group(0), rep);
                    matcher.reset(val);
                }
            }
            properties.put(key, val);
        }
    }

    protected void deleteConfiguration(File file) throws IOException, InvalidSyntaxException {
        String[] pid = parsePid(file);
        Configuration config = getConfiguration(pid[0], pid[1]);
        config.delete();
    }

    protected Configuration getConfiguration(String pid, String factoryPid) throws IOException,
        InvalidSyntaxException {
        ConfigurationAdmin configurationAdmin = activator.getConfigurationAdmin();
        if (factoryPid != null) {
            Configuration[] configs = configurationAdmin.listConfigurations("(|(" + ALIAS_KEY + "=" + pid
                                                                            + ")(.alias_factory_pid="
                                                                            + factoryPid + "))");
            if (configs == null || configs.length == 0) {
                return configurationAdmin.createFactoryConfiguration(pid, null);
            } else {
                return configs[0];
            }
        } else {
            return configurationAdmin.getConfiguration(pid, null);
        }
    }

    protected String[] parsePid(File file) {
        String path = file.getName();
        String pid = path.substring(0, path.length() - 4);
        int n = pid.indexOf('-');
        if (n > 0) {
            String factoryPid = pid.substring(n + 1);
            pid = pid.substring(0, n);
            return new String[] {pid, factoryPid};
        } else {
            return new String[] {pid, null};
        }
    }

    protected PackageAdmin getPackageAdmin() {
        ServiceTracker packageAdminTracker = activator.getPackageAdminTracker();
        if (packageAdminTracker != null) {
            try {
                return (PackageAdmin)packageAdminTracker.waitForService(5000L);
            } catch (InterruptedException e) {
                // ignore
            }
        }
        return null;
    }

    protected boolean isBundleFragment(Bundle bundle) {
        PackageAdmin packageAdmin = getPackageAdmin();
        if (packageAdmin != null) {
            return packageAdmin.getBundleType(bundle) == PackageAdmin.BUNDLE_TYPE_FRAGMENT;
        }
        return false;
    }

    protected void refreshPackagesAndStartOrUpdateBundles() {
        for (Bundle bundle : bundlesToUpdate) {
            try {
                bundle.update();
                LOGGER.info("Updated: " + bundle);
            } catch (BundleException e) {
                LOGGER.warn("Failed to update bundle: " + bundle + ". Reason: " + e, e);
            }
        }

        for (Bundle bundle : bundlesToStart) {
            try {
                bundle.start();
                LOGGER.info("Started: " + bundle);
            } catch (BundleException e) {
                LOGGER.warn("Failed to start bundle: " + bundle + ". Reason: " + e, e);
                rescheduleStart(bundle);
            }
        }

        PackageAdmin packageAdmin = getPackageAdmin();
        if (packageAdmin != null) {
            packageAdmin.refreshPackages(null);
        }
    }

    private void rescheduleStart(Bundle bundle) {
        synchronized (pendingStartBundles) {
            pendingStartBundles.add(bundle);
            if (bundleListener == null) {
                bundleListener = new BundleListener() {
                    public void bundleChanged(BundleEvent event) {
                        if (event.getType() == BundleEvent.RESOLVED) {
                            executor.execute(new Runnable() {
                                public void run() {
                                    retryPendingStartBundles();
                                }
                            });
                        } else if (event.getType() == BundleEvent.UNINSTALLED) {
                          // bundle was uninstalled meanwhile, so remove
                          // it from the list of pending bundles
                          pendingStartBundles.remove(event.getBundle());
                        }
                    }
                };
                getContext().addBundleListener(bundleListener);
            }
        }
    }

    protected void retryPendingStartBundles() {
        synchronized (pendingStartBundles) {
            Set<Bundle> bundles = new HashSet<Bundle>();
            bundles.addAll(pendingStartBundles);
            pendingStartBundles.clear();
            boolean success = false;
            for (Bundle bundle : bundles) {
                try {
                    bundle.start();
                    LOGGER.info("Pending bundle started: " + bundle);
                    success = true;
                } catch (BundleException e) {
                    LOGGER.info("Pending bundle not started: " + bundle);
                    pendingStartBundles.add(bundle);
                }
            }
            if (success) {
                PackageAdmin packageAdmin = getPackageAdmin();
                if (packageAdmin != null) {
                    packageAdmin.refreshPackages(null);
                }
            }
        }
    }

    protected File createBundleJar(File dir) throws BundleException, IOException {
        File destFile = new File(generateDir, dir.getName() + ".jar");
        if (destFile.exists()) {
            undeployBundle(destFile);
            destFile.delete();
        }
        JarUtil.jarDir(dir.getPath(), destFile.getPath());
        return destFile;
    }

    /**
     * Returns the root directory of the expanded OSGi bundle if the file is
     * part of an expanded OSGi bundle or null if it is not
     */
    protected File getExpandedBundleRootDirectory(File file) throws IOException {
        File parent = file.getParentFile();
        if (file.isDirectory()) {
            String rootPath = deployDir.getCanonicalPath();
            if (file.getCanonicalPath().equals(rootPath)) {
                return null;
            }
            if (containsManifest(file)) {
                return file;
            }
        }
        if (isValidBundleSourceDirectory(parent)) {
            return getExpandedBundleRootDirectory(parent);
        }
        return null;
    }

    /**
     * Returns true if the given directory is a valid child directory within the
     * {@link #deployDir}
     */
    protected boolean isValidBundleSourceDirectory(File dir) throws IOException {
        if (dir != null) {
            String parentPath = dir.getCanonicalPath();
            String rootPath = deployDir.getCanonicalPath();
            return !parentPath.equals(rootPath) && parentPath.startsWith(rootPath);
        } else {
            return false;
        }
    }

    /**
     * Returns true if the given directory is a valid child file within the
     * {@link #deployDir} or a child of {@link #generateDir}
     */
    protected boolean isValidArtifactFile(File file) throws IOException {
        if (file != null) {
            String filePath = file.getParentFile().getCanonicalPath();
            String deployPath = deployDir.getCanonicalPath();
            String generatePath = generateDir.getCanonicalPath();
            return filePath.equals(deployPath) || filePath.startsWith(generatePath);
        } else {
            return false;
        }
    }

    /**
     * Returns true if the given directory is a valid child file within the
     * {@link #configDir}
     */
    protected boolean isValidConfigFile(File file) throws IOException {
        if (file != null && file.getName().endsWith(".cfg")) {
            String filePath = file.getParentFile().getCanonicalPath();
            String configPath = configDir.getCanonicalPath();
            return filePath.equals(configPath);
        } else {
            return false;
        }
    }

    /**
     * Returns true if the given directory contains a valid manifest file
     */
    protected boolean containsManifest(File dir) {
        File metaInfDir = new File(dir, "META-INF");
        if (metaInfDir.exists() && metaInfDir.isDirectory()) {
            File manifest = new File(metaInfDir, "MANIFEST.MF");
            return manifest.exists() && !manifest.isDirectory();
        }
        return false;
    }

    @SuppressWarnings("unchecked")
    protected File getFileValue(Dictionary properties, String key) {
        Object value = properties.get(key);
        if (value instanceof File) {
            return (File)value;
        } else if (value != null) {
            return new File(value.toString());
        }
        return null;
    }

    @SuppressWarnings("unchecked")
    protected Long getLongValue(Dictionary properties, String key) {
        Object value = properties.get(key);
        if (value instanceof Long) {
            return (Long)value;
        } else if (value != null) {
            return Long.parseLong(value.toString());
        }
        return null;
    }

    protected void closeQuietly(Closeable in) {
        try {
            in.close();
        } catch (IOException e) {
            LOGGER.warn("Failed to close stream. " + e, e);
        }
    }

    protected PreferencesService getPreferenceService() {
        if (activator.getPreferenceServiceTracker() != null) {
            try {
                return (PreferencesService)activator.getPreferenceServiceTracker().waitForService(5000L);
            } catch (InterruptedException e) {
                // ignore
            }
        }
        return null;
    }

    protected void saveScannerState() {
        try {
            Map<String, Long> results = scanner.getLastScanResults();
            Preferences prefs = getPreferenceService().getUserPreferences(PREFERENCE_KEY).node(pid);
            Iterator<String> it = results.keySet().iterator();
            while (it.hasNext()) {
                String key = it.next();
                Long value = results.get(key);
                prefs.putLong(key, value);
            }
            prefs.flush();
        } catch (Exception e) {
            LOGGER.error("Error persisting FileMonitor state", e);
        }
    }

    protected void loadScannerState() {
        try {
            Preferences prefs = getPreferenceService().getUserPreferences(PREFERENCE_KEY).node(pid);
            Map<String, Long> lastResult = new HashMap<String, Long>();
            for (String key : prefs.keys()) {
                lastResult.put(key, prefs.getLong(key, -1));
            }
            scanner.setLastScanResults(lastResult);
        } catch (Exception e) {
            LOGGER.error("Error loading FileMonitor state", e);
        }
    }
}
TOP

Related Classes of org.apache.servicemix.kernel.filemonitor.FileMonitor

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.