You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by ro...@apache.org on 2017/11/07 09:43:15 UTC

[sling-org-apache-sling-installer-provider-file] 01/19: Add new file installer module

This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.installer.provider.file-1.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-installer-provider-file.git

commit e1c446cb3d9e514de63d72945d010864c261a2ff
Author: Carsten Ziegeler <cz...@apache.org>
AuthorDate: Fri Aug 13 11:36:08 2010 +0000

    Add new file installer module
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/installer/fileinstall@985160 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml                                            |  85 +++++++
 .../sling/installer/file/impl/Activator.java       |  84 +++++++
 .../installer/file/impl/FileChangesListener.java   |  33 +++
 .../sling/installer/file/impl/FileMonitor.java     | 268 +++++++++++++++++++++
 .../sling/installer/file/impl/Installer.java       | 120 +++++++++
 .../installer/file/impl/ScanConfiguration.java     |  29 +++
 .../installer/file/impl/ServicesListener.java      | 180 ++++++++++++++
 7 files changed, 799 insertions(+)

diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..31c6ab8
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.apache.sling</groupId>
+        <artifactId>sling</artifactId>
+        <version>9</version>
+        <relativePath>../../parent/pom.xml</relativePath>
+    </parent>
+
+    <artifactId>org.apache.sling.install.fileinstall</artifactId>
+    <version>0.1.0-SNAPSHOT</version>
+    <packaging>bundle</packaging>
+
+    <name>Apache Sling File Installer</name>
+    <description> 
+        Installs OSGi bundles and configurations from the file system.
+    </description>
+
+    <scm>
+        <connection>scm:svn:http://svn.apache.org/repos/asf/sling/trunk/installer/fileinstall</connection>
+        <developerConnection> scm:svn:https://svn.apache.org/repos/asf/sling/trunk/installer/fileinstall</developerConnection>
+        <url>http://svn.apache.org/viewvc/sling/trunk/installer/fileinstall/</url>
+    </scm>
+
+    <build>
+        <plugins>
+
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <extensions>true</extensions>
+                <configuration>
+                    <instructions>
+                        <Bundle-Activator>
+                            org.apache.sling.installer.file.impl.Activator
+                        </Bundle-Activator>
+                        <Private-Package>
+                            org.apache.sling.installer.file.impl.*
+                        </Private-Package>
+                    </instructions>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.compendium</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.osgi.installer</artifactId>
+            <version>3.0.0-SNAPSHOT</version>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/src/main/java/org/apache/sling/installer/file/impl/Activator.java b/src/main/java/org/apache/sling/installer/file/impl/Activator.java
new file mode 100644
index 0000000..9702cbe
--- /dev/null
+++ b/src/main/java/org/apache/sling/installer/file/impl/Activator.java
@@ -0,0 +1,84 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.installer.file.impl;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.StringTokenizer;
+
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.InvalidSyntaxException;
+
+/**
+ * The <code>Activator</code>
+ */
+public class Activator implements BundleActivator {
+
+    public static final String KEY_DIR = "sling.fileinstall.dir";
+    public static final String KEY_DELAY = "sling.fileinstall.interval";
+
+    /** The services listener will activate the installer. */
+    private ServicesListener servicesListener;
+
+    /**
+     * @see org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext)
+     */
+    public void start(final BundleContext context) throws InvalidSyntaxException {
+        // read initial scan configurations
+        final List<ScanConfiguration> configs = new ArrayList<ScanConfiguration>();
+        final Object dir = this.getProp(context, KEY_DIR);
+        if ( dir != null ) {
+            Long delay = null;
+            final Object interval = this.getProp(context, KEY_DELAY);
+            if ( interval != null ) {
+                if ( interval instanceof Number ) {
+                    delay = ((Number)interval).longValue();
+                } else {
+                    delay = Long.valueOf(interval.toString());
+                }
+            }
+            final StringTokenizer st = new StringTokenizer(dir.toString(), ",");
+            while ( st.hasMoreTokens() ) {
+                final ScanConfiguration sc = new ScanConfiguration();
+                sc.directory = st.nextToken();
+                sc.scanInterval = delay;
+
+                configs.add(sc);
+            }
+        }
+        this.servicesListener = new ServicesListener(context, configs);
+    }
+
+    /**
+     * @see org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext)
+     */
+    public void stop(final BundleContext context) {
+        this.servicesListener.deactivate();
+        this.servicesListener = null;
+    }
+
+    private Object getProp(final BundleContext bundleContext, final String key) {
+        Object o = bundleContext.getProperty(key);
+        if (o == null) {
+            o = System.getProperty(key.toUpperCase().replace('.', '_'));
+        }
+        return o;
+    }
+}
diff --git a/src/main/java/org/apache/sling/installer/file/impl/FileChangesListener.java b/src/main/java/org/apache/sling/installer/file/impl/FileChangesListener.java
new file mode 100644
index 0000000..b9d7485
--- /dev/null
+++ b/src/main/java/org/apache/sling/installer/file/impl/FileChangesListener.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.installer.file.impl;
+
+import java.io.File;
+import java.util.List;
+
+public interface FileChangesListener {
+
+    void initialSet(List<File> files);
+
+    void removed(File file);
+
+    void added(File file);
+
+    void changed(File file);
+}
diff --git a/src/main/java/org/apache/sling/installer/file/impl/FileMonitor.java b/src/main/java/org/apache/sling/installer/file/impl/FileMonitor.java
new file mode 100644
index 0000000..a80a0ba
--- /dev/null
+++ b/src/main/java/org/apache/sling/installer/file/impl/FileMonitor.java
@@ -0,0 +1,268 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.installer.file.impl;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Timer;
+import java.util.TimerTask;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This class is a monitor for the file system
+ * that periodically checks for changes.
+ */
+public class FileMonitor extends TimerTask {
+
+    /** The logger. */
+    private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+    private final Timer timer = new Timer();
+    private boolean stop = false;
+    private boolean stopped = true;
+
+    private final Monitorable root;
+
+    private final FileChangesListener listener;
+
+    /**
+     * Creates a new instance of this class.
+     * @param interval The interval between executions of the task, in milliseconds.
+     */
+    public FileMonitor(final File rootDir, final Long interval, final FileChangesListener listener) {
+        this.listener = listener;
+        this.root = new Monitorable(rootDir);
+        createStatus(this.root);
+        final List<File> files = new ArrayList<File>();
+        collect(this.root.file, files);
+        this.listener.initialSet(files);
+        logger.debug("Starting file monitor for {} with an interval of {}ms", this.root.file, interval);
+        timer.schedule(this, 0, (interval != null ? interval : 5000));
+    }
+
+    private void collect(final File file, final List<File> files) {
+        if ( file.exists() ) {
+            if ( file.isDirectory() ) {
+                for(final File child : file.listFiles() ) {
+                    collect(child, files);
+                }
+            } else {
+                files.add(file);
+            }
+        }
+    }
+
+    private void collectDeleted(final Monitorable m, final List<File> files) {
+        if ( m.status instanceof DirStatus ) {
+            for(final Monitorable child : ((DirStatus)m.status).children ) {
+                collectDeleted(child, files);
+            }
+        } else {
+            files.add(m.file);
+        }
+    }
+
+    /**
+     * Stop periodically executing this task. If the task is currently executing it
+     * will never be run again after the current execution, otherwise it will simply
+     * never run (again).
+     */
+    void stop() {
+        synchronized (timer) {
+            if (!stop) {
+                stop = true;
+                cancel();
+                timer.cancel();
+            }
+
+            boolean interrupted = false;
+            while (!stopped) {
+                try {
+                    timer.wait();
+                }
+                catch (InterruptedException e) {
+                    interrupted = true;
+                }
+            }
+            if (interrupted) {
+                Thread.currentThread().interrupt();
+            }
+        }
+        logger.debug("Stopped file monitor for {}", this.root.file);
+    }
+
+    /**
+     * @see java.util.TimerTask#run()
+     */
+    public void run() {
+        synchronized (timer) {
+            stopped = false;
+            if (stop) {
+                stopped = true;
+                timer.notifyAll();
+                return;
+            }
+        }
+        synchronized ( this ) {
+            try {
+                this.check(this.root);
+            } catch (Exception e) {
+                // ignore this
+            }
+        }
+        synchronized (timer) {
+            stopped = true;
+            timer.notifyAll();
+        }
+    }
+
+    /**
+     * Check the monitorable
+     * @param monitorable The monitorable to check
+     * @param localEA The event admin
+     */
+    private void check(final Monitorable monitorable) {
+        logger.debug("Checking {}", monitorable.file);
+        // if the file is non existing, check if it has been readded
+        if ( monitorable.status instanceof NonExistingStatus ) {
+            if ( monitorable.file.exists() ) {
+                // new file and reset status
+                createStatus(monitorable);
+                final List<File> files = new ArrayList<File>();
+                collect(monitorable.file, files);
+                for(final File file : files ) {
+                    this.listener.added(file);
+                }
+            }
+        } else {
+            // check if the file has been removed
+            if ( !monitorable.file.exists() ) {
+                // removed file and update status
+                final List<File> files = new ArrayList<File>();
+                collectDeleted(monitorable, files);
+                for(final File file : files ) {
+                    this.listener.removed(file);
+                }
+                monitorable.status = NonExistingStatus.SINGLETON;
+            } else {
+                // check for changes
+                final FileStatus fs = (FileStatus)monitorable.status;
+                boolean changed = false;
+                if ( fs.lastModified < monitorable.file.lastModified() ) {
+                    fs.lastModified = monitorable.file.lastModified();
+                    // changed
+                    if ( monitorable.file.isFile() ) {
+                        this.listener.changed(monitorable.file);
+                    }
+                    changed = true;
+                }
+                if ( fs instanceof DirStatus ) {
+                    // directory
+                    final DirStatus ds = (DirStatus)fs;
+                    for(int i=0; i<ds.children.length; i++) {
+                        check(ds.children[i]);
+                    }
+                    // if the dir changed we have to update
+                    if ( changed ) {
+                        // and now update
+                        final File[] files = monitorable.file.listFiles();
+                        if (files != null) {
+                            final Monitorable[] children = new Monitorable[files.length];
+                            for (int i = 0; i < files.length; i++) {
+                                // search in old list
+                                for (int m = 0; m < ds.children.length; m++) {
+                                    if (ds.children[m].file.equals(files[i])) {
+                                        children[i] = ds.children[m];
+                                        break;
+                                    }
+                                }
+                                if (children[i] == null) {
+                                    children[i] = new Monitorable(files[i]);
+                                    children[i].status = NonExistingStatus.SINGLETON;
+                                    check(children[i]);
+                                }
+                            }
+                            ds.children = children;
+                        } else {
+                            ds.children = new Monitorable[0];
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Create a status object for the monitorable
+     */
+    private static void createStatus(final Monitorable monitorable) {
+        if ( !monitorable.file.exists() ) {
+            monitorable.status = NonExistingStatus.SINGLETON;
+        } else if ( monitorable.file.isFile() ) {
+            monitorable.status = new FileStatus(monitorable.file);
+        } else {
+            monitorable.status = new DirStatus(monitorable.file);
+        }
+    }
+
+    /** The monitorable to hold the resource path, the file and the status. */
+    private static final class Monitorable {
+        public final File   file;
+        public Object status;
+
+        public Monitorable(final File file) {
+            this.file = file;
+        }
+    }
+
+    /** Status for files. */
+    private static class FileStatus {
+        public long lastModified;
+        public FileStatus(final File file) {
+            this.lastModified = file.lastModified();
+        }
+    }
+
+    /** Status for directories. */
+    private static final class DirStatus extends FileStatus {
+        public Monitorable[] children;
+
+        public DirStatus(final File dir) {
+            super(dir);
+            final File[] files = dir.listFiles();
+            if (files != null) {
+                this.children = new Monitorable[files.length];
+                for (int i = 0; i < files.length; i++) {
+                    this.children[i] = new Monitorable(files[i]);
+                    FileMonitor.createStatus(this.children[i]);
+                }
+            } else {
+                this.children = new Monitorable[0];
+            }
+        }
+    }
+
+    /** Status for non existing files. */
+    private static final class NonExistingStatus {
+        public static NonExistingStatus SINGLETON = new NonExistingStatus();
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/installer/file/impl/Installer.java b/src/main/java/org/apache/sling/installer/file/impl/Installer.java
new file mode 100644
index 0000000..7029957
--- /dev/null
+++ b/src/main/java/org/apache/sling/installer/file/impl/Installer.java
@@ -0,0 +1,120 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.installer.file.impl;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.List;
+
+import org.apache.sling.osgi.installer.InstallableResource;
+import org.apache.sling.osgi.installer.OsgiInstaller;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The <code>Installer</code> is the service calling the
+ * OSGi installer
+ */
+public class Installer implements FileChangesListener {
+
+    private static final String SCHEME_PREFIX = "fileinstall";
+
+    /** The OSGi installer service. */
+    private final OsgiInstaller installer;
+
+    /** The scheme to use. */
+    private final String scheme;
+
+    public Installer(final OsgiInstaller installer,
+            final String id) {
+        this.scheme = SCHEME_PREFIX + id;
+        this.installer = installer;
+    }
+
+    /**
+     * @see org.apache.sling.installer.file.impl.FileChangesListener#added(java.io.File)
+     */
+    public void added(final File file) {
+        LoggerFactory.getLogger(this.getClass()).info("Added file {}", file);
+        final InstallableResource resource = this.createResource(file);
+        this.installer.addResource(this.scheme, resource);
+    }
+
+    /**
+     * @see org.apache.sling.installer.file.impl.FileChangesListener#changed(java.io.File)
+     */
+    public void changed(final File file) {
+        LoggerFactory.getLogger(this.getClass()).info("Changed file {}", file);
+        final InstallableResource resource = this.createResource(file);
+        this.installer.addResource(this.scheme, resource);
+    }
+
+    /**
+     * @see org.apache.sling.installer.file.impl.FileChangesListener#initialSet(java.util.List)
+     */
+    public void initialSet(final List<File> files) {
+        LoggerFactory.getLogger(this.getClass()).info("Initial set for {}", this.scheme);
+        final List<InstallableResource> resources = new ArrayList<InstallableResource>();
+        for(final File f : files) {
+            LoggerFactory.getLogger(this.getClass()).info("File {}", f);
+            final InstallableResource resource = this.createResource(f);
+            if ( resource != null ) {
+                resources.add(resource);
+            }
+        }
+        this.installer.registerResources(this.scheme, resources);
+    }
+
+    private InstallableResource createResource(final File file) {
+        try {
+            final InputStream is = new FileInputStream(file);
+            final String digest = String.valueOf(file.lastModified());
+            // if this is a bundle check for start level directory!
+            Dictionary<String, Object> dict = null;
+            if ( file.getName().endsWith(".jar") || file.getName().endsWith(".war") ) {
+                final String parentName = file.getParentFile().getName();
+                try {
+                    final int startLevel = Integer.valueOf(parentName);
+                    if ( startLevel > 0 ) {
+                        dict = new Hashtable<String, Object>();
+                        dict.put(InstallableResource.BUNDLE_START_LEVEL, startLevel);
+                    }
+                } catch (NumberFormatException nfe) {
+                    // ignore this
+                }
+            }
+            return new InstallableResource(file.getAbsolutePath(), is, dict, digest,
+                null, null);
+        } catch (IOException io) {
+            // ignore this for now (TODO)
+        }
+        return null;
+    }
+    /**
+     * @see org.apache.sling.installer.file.impl.FileChangesListener#removed(java.io.File)
+     */
+    public void removed(final File file) {
+        LoggerFactory.getLogger(this.getClass()).info("Removed file {}", file);
+        this.installer.removeResource(this.scheme, file.getAbsolutePath());
+    }
+}
diff --git a/src/main/java/org/apache/sling/installer/file/impl/ScanConfiguration.java b/src/main/java/org/apache/sling/installer/file/impl/ScanConfiguration.java
new file mode 100644
index 0000000..54d8a1a
--- /dev/null
+++ b/src/main/java/org/apache/sling/installer/file/impl/ScanConfiguration.java
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.installer.file.impl;
+
+/**
+ * A scan configuration for the file install.
+ */
+public class ScanConfiguration {
+
+    public String directory;
+
+    public Long   scanInterval;
+}
diff --git a/src/main/java/org/apache/sling/installer/file/impl/ServicesListener.java b/src/main/java/org/apache/sling/installer/file/impl/ServicesListener.java
new file mode 100644
index 0000000..72dd79c
--- /dev/null
+++ b/src/main/java/org/apache/sling/installer/file/impl/ServicesListener.java
@@ -0,0 +1,180 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.installer.file.impl;
+
+import java.io.File;
+import java.math.BigInteger;
+import java.security.MessageDigest;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.sling.osgi.installer.OsgiInstaller;
+import org.osgi.framework.BundleContext;
+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;
+
+/**
+ * The <code>ServicesListener</code> listens for the required services
+ * and starts/stops the scanners based on the availability of the
+ * services.
+ */
+public class ServicesListener {
+
+    /** The name of the installer service. */
+    private static final String INSTALLER_SERVICE_NAME = OsgiInstaller.class.getName();
+
+    /** The bundle context. */
+    private final BundleContext bundleContext;
+
+    /** The listener for the installer. */
+    private final Listener installerListener;
+
+    /** All active scan configurations. */
+    private final List<ScanConfiguration> scanConfigurations = new ArrayList<ScanConfiguration>();
+
+    /** All monitors. */
+    private final List<FileMonitor> monitors = new ArrayList<FileMonitor>();
+
+    private boolean running = false;
+
+    public ServicesListener(final BundleContext bundleContext,
+            final List<ScanConfiguration> configs)
+    throws InvalidSyntaxException{
+        if ( configs != null ) {
+            scanConfigurations.addAll(configs);
+        }
+        this.bundleContext = bundleContext;
+        this.installerListener = new Listener(INSTALLER_SERVICE_NAME);
+    }
+
+    public synchronized void notifyChange() {
+        final boolean shouldRun = !this.scanConfigurations.isEmpty();
+        if ( (shouldRun && !running) || (!shouldRun && running) ) {
+            final OsgiInstaller installer = (OsgiInstaller)this.installerListener.getService();
+
+            if ( installer != null&& !running ) {
+                this.startScanner(installer);
+            } else if ( running && installer == null ) {
+                this.stopScanner();
+            }
+        }
+    }
+
+    /**
+     * Deactivate this listener.
+     */
+    public void deactivate() {
+        this.installerListener.deactivate();
+        this.stopScanner();
+    }
+
+    private void startScanner(final OsgiInstaller installer) {
+        if ( !running ) {
+            for(final ScanConfiguration config : this.scanConfigurations) {
+                this.monitors.add(new FileMonitor(new File(config.directory),
+                        config.scanInterval, new Installer(installer, hash(config.directory))));
+            }
+            running = true;
+        }
+    }
+
+    private void stopScanner() {
+        if ( running ) {
+            for(final FileMonitor monitor : this.monitors) {
+                monitor.stop();
+            }
+            this.monitors.clear();
+            running = false;
+        }
+    }
+
+    protected final class Listener implements ServiceListener {
+
+        private final String serviceName;
+
+        private ServiceReference reference;
+        private Object service;
+
+        public Listener(final String serviceName) throws InvalidSyntaxException {
+            this.serviceName = serviceName;
+            bundleContext.addServiceListener(this, "("
+                    + Constants.OBJECTCLASS + "=" + serviceName + ")");
+            this.getService();
+        }
+
+        public void deactivate() {
+            bundleContext.removeServiceListener(this);
+        }
+
+        public synchronized Object getService() {
+            return this.service;
+        }
+        private synchronized void retainService() {
+            if ( this.reference == null ) {
+                this.reference = bundleContext.getServiceReference(this.serviceName);
+                if ( this.reference != null ) {
+                    this.service = bundleContext.getService(this.reference);
+                    if ( this.service == null ) {
+                        this.reference = null;
+                    } else {
+                        notifyChange();
+                    }
+                }
+            }
+        }
+
+        private synchronized void releaseService() {
+            if ( this.reference != null ) {
+                this.service = null;
+                bundleContext.ungetService(this.reference);
+                this.reference = null;
+                notifyChange();
+            }
+        }
+
+        /**
+         * @see org.osgi.framework.ServiceListener#serviceChanged(org.osgi.framework.ServiceEvent)
+         */
+        public void serviceChanged(ServiceEvent event) {
+            if (event.getType() == ServiceEvent.REGISTERED && this.service == null ) {
+                this.retainService();
+            } else if ( event.getType() == ServiceEvent.UNREGISTERING && this.service != null ) {
+                this.releaseService();
+            }
+        }
+    }
+
+    /**
+     * Hash the string
+     */
+    private static String hash(String value) {
+        try {
+            final MessageDigest d = MessageDigest.getInstance("MD5");
+            d.update(value.getBytes("UTF-8"));
+            final BigInteger bigInt = new BigInteger(1, d.digest());
+            return new String(bigInt.toString(16));
+        } catch (Exception ignore) {
+            // if anything goes wrong we just return the value
+            return value;
+        }
+    }
+}

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.