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.1.0 in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-installer-provider-file.git
commit c917afce158c6e9c299e8f53793cecf805a7e058 Author: Carsten Ziegeler <[email protected]> AuthorDate: Fri Apr 17 09:51:33 2015 +0000 [maven-release-plugin] copy for tag org.apache.sling.installer.provider.file-1.1.0 git-svn-id: https://svn.apache.org/repos/asf/sling/tags/org.apache.sling.installer.provider.file-1.1.0@1674248 13f79535-47bb-0310-9956-ffa450edef68 --- file/pom.xml | 109 ++++++++ .../installer/provider/file/impl/Activator.java | 87 +++++++ .../provider/file/impl/FileChangesListener.java | 31 +++ .../provider/file/impl/FileInstaller.java | 234 +++++++++++++++++ .../installer/provider/file/impl/FileMonitor.java | 287 +++++++++++++++++++++ .../installer/provider/file/impl/Installer.java | 196 ++++++++++++++ .../provider/file/impl/ScanConfiguration.java | 29 +++ .../provider/file/impl/ServicesListener.java | 200 ++++++++++++++ 8 files changed, 1173 insertions(+) diff --git a/file/pom.xml b/file/pom.xml new file mode 100644 index 0000000..4c3d6b9 --- /dev/null +++ b/file/pom.xml @@ -0,0 +1,109 @@ +<?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>22</version> + <relativePath>../../../parent/pom.xml</relativePath> + </parent> + + <artifactId>org.apache.sling.installer.provider.file</artifactId> + <version>1.1.0</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/tags/org.apache.sling.installer.provider.file-1.1.0</connection> + <developerConnection> scm:svn:https://svn.apache.org/repos/asf/sling/tags/org.apache.sling.installer.provider.file-1.1.0</developerConnection> + <url>http://svn.apache.org/viewvc/sling/tags/org.apache.sling.installer.provider.file-1.1.0</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.provider.file.impl.Activator + </Bundle-Activator> + <!-- + We need at least 3.1.2 as this allows reading comments from a config + --> + <Import-Package> + org.apache.sling.installer.api;version="[3.1.2,4)", + * + </Import-Package> + <Private-Package> + org.apache.sling.installer.provider.file.impl.* + </Private-Package> + <Embed-Dependency> + org.apache.felix.configadmin;inline="org/apache/felix/cm/file/ConfigurationHandler.*" + </Embed-Dependency> + </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.installer.core</artifactId> + <version>3.5.4</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.apache.sling</groupId> + <artifactId>org.apache.sling.settings</artifactId> + <version>1.1.0</version> + <scope>provided</scope> + </dependency> + <!-- We use a class from the config admin implementation to read config files --> + <dependency> + <groupId>org.apache.felix</groupId> + <artifactId>org.apache.felix.configadmin</artifactId> + <version>1.2.8</version> + <scope>provided</scope> + </dependency> + </dependencies> +</project> diff --git a/file/src/main/java/org/apache/sling/installer/provider/file/impl/Activator.java b/file/src/main/java/org/apache/sling/installer/provider/file/impl/Activator.java new file mode 100644 index 0000000..2b06592 --- /dev/null +++ b/file/src/main/java/org/apache/sling/installer/provider/file/impl/Activator.java @@ -0,0 +1,87 @@ +/* + * 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.provider.file.impl; + +import java.util.ArrayList; +import java.util.List; +import java.util.StringTokenizer; + +import org.osgi.framework.BundleActivator; +import org.osgi.framework.BundleContext; + +/** + * 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"; + public static final String KEY_WRITEBACK = "sling.fileinstall.writeback"; + + /** 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) { + // read initial scan configurations + final List<ScanConfiguration> configs = new ArrayList<ScanConfiguration>(); + final Object dir = getProp(context, KEY_DIR); + if ( dir != null ) { + Long delay = null; + final Object interval = 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; + } + + public static Object getProp(final BundleContext bundleContext, final String key) { + Object o = bundleContext.getProperty(key); + if (o == null) { + o = System.getProperty(key); + if ( o == null ) { + o = System.getProperty(key.toUpperCase().replace('.', '_')); + } + } + return o; + } +} diff --git a/file/src/main/java/org/apache/sling/installer/provider/file/impl/FileChangesListener.java b/file/src/main/java/org/apache/sling/installer/provider/file/impl/FileChangesListener.java new file mode 100644 index 0000000..2fd34d5 --- /dev/null +++ b/file/src/main/java/org/apache/sling/installer/provider/file/impl/FileChangesListener.java @@ -0,0 +1,31 @@ +/* + * 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.provider.file.impl; + +import java.io.File; +import java.util.List; + +public interface FileChangesListener { + + void initialSet(List<File> files); + + void updated(List<File> added, List<File> changed, List<File> removed); + + String getScheme(); +} diff --git a/file/src/main/java/org/apache/sling/installer/provider/file/impl/FileInstaller.java b/file/src/main/java/org/apache/sling/installer/provider/file/impl/FileInstaller.java new file mode 100644 index 0000000..a646f61 --- /dev/null +++ b/file/src/main/java/org/apache/sling/installer/provider/file/impl/FileInstaller.java @@ -0,0 +1,234 @@ +/* + * 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.provider.file.impl; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.math.BigInteger; +import java.security.MessageDigest; +import java.util.ArrayList; +import java.util.Dictionary; +import java.util.List; +import java.util.Map; + +import org.apache.felix.cm.file.ConfigurationHandler; +import org.apache.sling.installer.api.InstallableResource; +import org.apache.sling.installer.api.OsgiInstaller; +import org.apache.sling.installer.api.UpdateHandler; +import org.apache.sling.installer.api.UpdateResult; +import org.apache.sling.settings.SlingSettingsService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The <code>FileInstaller</code> manages the file installers and + * handles updates. + * + */ +public class FileInstaller + implements UpdateHandler { + + /** The scheme we use to register our resources. */ + public static final String SCHEME_PREFIX = "fileinstall"; + + /** Logger. */ + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + /** All active scan configurations. */ + private final List<ScanConfiguration> scanConfigurations = new ArrayList<ScanConfiguration>(); + + /** All monitors. */ + private final List<FileMonitor> monitors = new ArrayList<FileMonitor>(); + + private final boolean writeBack; + + public FileInstaller(final List<ScanConfiguration> configs, final boolean writeBack) { + this.writeBack = writeBack; + if ( configs != null ) { + scanConfigurations.addAll(configs); + } + } + + public boolean hasConfigurations() { + return !this.scanConfigurations.isEmpty(); + } + + public void start(final OsgiInstaller installer, final SlingSettingsService settings) { + for(final ScanConfiguration config : this.scanConfigurations) { + String key = config.directory; + if ( key.startsWith(settings.getSlingHomePath() + File.separator) ) { + key = "${sling.home}" + key.substring(settings.getSlingHomePath().length()); + } + logger.debug("Starting monitor for {}", config.directory); + this.monitors.add(new FileMonitor(new File(config.directory), + config.scanInterval, new Installer(installer, settings, config.directory, hash(key)))); + } + } + + public void stop() { + for(final FileMonitor monitor : this.monitors) { + monitor.stop(); + } + this.monitors.clear(); + + } + + public String[] getSchemes() { + final String[] schemes = new String[this.monitors.size()]; + int index = 0; + + for(final FileMonitor m : this.monitors) { + schemes[index] = m.getListener().getScheme(); + index++; + } + + return schemes; + } + + /** + * @see org.apache.sling.installer.api.UpdateHandler#handleRemoval(java.lang.String, java.lang.String, java.lang.String) + */ + public UpdateResult handleRemoval(final String resourceType, + final String id, + final String url) { + if ( !this.writeBack ) { + return null; + } + final int pos = url.indexOf(':'); + final String path = url.substring(pos + 1); + // remove + logger.debug("Removal of {}", path); + final File file = new File(path); + if ( file.exists() ) { + file.delete(); + } + return new UpdateResult(url); + } + + /** + * @see org.apache.sling.installer.api.UpdateHandler#handleUpdate(java.lang.String, java.lang.String, java.lang.String, java.util.Dictionary, Map) + */ + public UpdateResult handleUpdate(final String resourceType, + final String id, + final String url, + final Dictionary<String, Object> dict, + final Map<String, Object> attributes) { + return this.handleUpdate(resourceType, id, url, null, dict, attributes); + } + + /** + * @see org.apache.sling.installer.api.UpdateHandler#handleUpdate(java.lang.String, java.lang.String, java.lang.String, java.io.InputStream, Map) + */ + public UpdateResult handleUpdate(final String resourceType, + final String id, + final String url, + final InputStream is, + final Map<String, Object> attributes) { + return this.handleUpdate(resourceType, id, url, is, null, attributes); + } + + /** + * Internal implementation of update handling + */ + private UpdateResult handleUpdate(final String resourceType, + final String id, + final String url, + final InputStream is, + final Dictionary<String, Object> dict, + final Map<String, Object> attributes) { + if ( !this.writeBack ) { + return null; + } + + // we only handle add/update of configs for now + if ( !resourceType.equals(InstallableResource.TYPE_CONFIG) ) { + return null; + } + + try { + final String path; + final String prefix; + if ( url != null ) { + // update + final int pos = url.indexOf(':'); + final String oldPath = url.substring(pos + 1); + prefix = url.substring(0, pos); + // ensure extension 'config' + if ( !oldPath.endsWith(".config") ) { + final File file = new File(oldPath); + if ( file.exists() ) { + file.delete(); + } + final int lastDot = oldPath.lastIndexOf('.'); + final int lastSlash = oldPath.lastIndexOf('/'); + if ( lastDot <= lastSlash ) { + path = oldPath + ".config"; + } else { + path = oldPath.substring(0, lastDot) + ".config"; + } + } else { + path = oldPath; + } + logger.debug("Update of {} at {}", resourceType, path); + } else { + // add + final FileMonitor first = this.monitors.get(0); + path = first.getRoot().getAbsolutePath() + '/' + id + ".config"; + prefix = first.getListener().getScheme(); + logger.debug("Add of {} at {}", resourceType, path); + } + + final File file = new File(path); + file.getParentFile().mkdirs(); + final FileOutputStream fos = new FileOutputStream(file); + try { + fos.write("# Configuration created by Apache Sling File Installer\n".getBytes("UTF-8")); + ConfigurationHandler.write(fos, dict); + } finally { + try { + fos.close(); + } catch (final IOException ignore) {} + } + + final UpdateResult result = new UpdateResult(prefix + ':' + path); + result.setResourceIsMoved(true); + return result; + } catch (final IOException e) { + logger.error("Unable to add/update resource " + resourceType + ':' + id, e); + return null; + } + } + + /** + * Hash the string + */ + private static String hash(final 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 (final Exception ignore) { + // if anything goes wrong we just return the value + return value; + } + } +} diff --git a/file/src/main/java/org/apache/sling/installer/provider/file/impl/FileMonitor.java b/file/src/main/java/org/apache/sling/installer/provider/file/impl/FileMonitor.java new file mode 100644 index 0000000..085d07e --- /dev/null +++ b/file/src/main/java/org/apache/sling/installer/provider/file/impl/FileMonitor.java @@ -0,0 +1,287 @@ +/* + * 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.provider.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)); + } + + public File getRoot() { + return this.root.file; + } + + public FileChangesListener getListener() { + return this.listener; + } + + private void collect(final File file, final List<File> files) { + if ( file.exists() ) { + if ( file.isDirectory() ) { + final File[] children = file.listFiles(); + if ( children != null ) { + for(final File child : children ) { + 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); + } + } + + private final static class Collector { + public final List<File> added = new ArrayList<File>(); + public final List<File> removed = new ArrayList<File>(); + public final List<File> changed = new ArrayList<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 { + final Collector c = new Collector(); + this.check(this.root, c); + this.listener.updated(c.added, c.changed, c.removed); + } 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, final Collector collector) { + 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 ) { + collector.added.add(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 ) { + collector.removed.add(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() ) { + collector.changed.add(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], collector); + } + // 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], collector); + } + } + 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/file/src/main/java/org/apache/sling/installer/provider/file/impl/Installer.java b/file/src/main/java/org/apache/sling/installer/provider/file/impl/Installer.java new file mode 100644 index 0000000..1b05e78 --- /dev/null +++ b/file/src/main/java/org/apache/sling/installer/provider/file/impl/Installer.java @@ -0,0 +1,196 @@ +/* + * 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.provider.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 java.util.Set; + +import org.apache.sling.installer.api.InstallableResource; +import org.apache.sling.installer.api.OsgiInstaller; +import org.apache.sling.settings.SlingSettingsService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The <code>Installer</code> is the service calling the + * OSGi installer + * + */ +public class Installer + implements FileChangesListener { + + /** Logger. */ + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + /** The OSGi installer service. */ + private final OsgiInstaller installer; + + /** The settings service. */ + private final Set<String> activeRunModes; + + /** The scheme to use. */ + private final String scheme; + + /** Prefix. */ + private final String prefix; + + public Installer(final OsgiInstaller installer, + final SlingSettingsService settings, + final String root, + final String id) { + this.scheme = FileInstaller.SCHEME_PREFIX + id; + this.installer = installer; + this.activeRunModes = settings.getRunModes(); + this.prefix = new File(root).getAbsolutePath() + File.separator; + } + + /** + * @see org.apache.sling.installer.provider.file.impl.FileChangesListener#getScheme() + */ + public String getScheme() { + return this.scheme; + } + + /** + * @see org.apache.sling.installer.provider.file.impl.FileChangesListener#initialSet(java.util.List) + */ + public void initialSet(final List<File> files) { + logger.debug("Initial set for {}", this.scheme); + final List<InstallableResource> resources = new ArrayList<InstallableResource>(); + for(final File f : files) { + logger.debug("Initial file {}", f); + final InstallableResource resource = this.createResource(f); + if ( resource != null ) { + resources.add(resource); + } + } + this.installer.registerResources(this.scheme, resources.toArray(new InstallableResource[resources.size()])); + } + + /** + * @see org.apache.sling.installer.provider.file.impl.FileChangesListener#updated(java.util.List, java.util.List, java.util.List) + */ + public void updated(List<File> added, List<File> changed, List<File> removed) { + final List<InstallableResource> updated; + if ( (added != null && added.size() > 0) || (changed != null && changed.size() > 0) ) { + updated = new ArrayList<InstallableResource>(); + if ( added != null ) { + for(final File f : added) { + logger.debug("Added file {}", f); + final InstallableResource resource = this.createResource(f); + if ( resource != null ) { + updated.add(resource); + } + } + } + if ( changed != null ) { + for(final File f : changed) { + logger.debug("Changed file {}", f); + final InstallableResource resource = this.createResource(f); + if ( resource != null ) { + updated.add(resource); + } + } + } + } else { + updated = null; + } + final String[] removedUrls; + if ( removed != null && removed.size() > 0 ) { + removedUrls = new String[removed.size()]; + int index = 0; + for(final File f : removed) { + removedUrls[index] = f.getAbsolutePath(); + logger.debug("Removed file {}", removedUrls[index]); + index++; + } + } else { + removedUrls = null; + } + if ( updated != null || removedUrls != null ) { + this.installer.updateResources(this.scheme, + updated == null ? null : updated.toArray(new InstallableResource[updated.size()]), removedUrls); + } + } + + private InstallableResource createResource(final File file) { + try { + // check for run modes + final String name = file.getAbsolutePath().substring(this.prefix.length()).replace(File.separatorChar, '/'); + boolean isActive = true; + Integer prio = null; + final int pos = name.indexOf('/'); + if ( pos != -1 && name.startsWith("install.") ) { + final String runModes = name.substring(8, pos); + final int activeModes = this.isActive(runModes); + if ( activeModes > 0 ) { + prio = InstallableResource.DEFAULT_PRIORITY + activeModes; + } else { + isActive = false; + } + } + if ( isActive ) { + final InputStream is = new FileInputStream(file); + final String digest = String.valueOf(file.lastModified()); + // if this is a bundle check for start level directory! + final Dictionary<String, Object> dict = new Hashtable<String, Object>(); + 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.put(InstallableResource.BUNDLE_START_LEVEL, startLevel); + } + } catch (NumberFormatException nfe) { + // ignore this + } + } + dict.put(InstallableResource.RESOURCE_URI_HINT, file.toURI().toString()); + return new InstallableResource(file.getAbsolutePath(), is, dict, digest, + null, prio); + } else { + logger.info("Ignoring inactive resource at {}", file); + } + + } catch (IOException io) { + logger.error("Unable to read file " + file, io); + } + return null; + } + + private int isActive(final String runModesString) { + final String[] runModes = runModesString.split("\\."); + boolean active = true; + for(final String mode : runModes) { + if ( !activeRunModes.contains(mode) ) { + active = false; + break; + } + } + + return active ? runModes.length : 0; + } +} diff --git a/file/src/main/java/org/apache/sling/installer/provider/file/impl/ScanConfiguration.java b/file/src/main/java/org/apache/sling/installer/provider/file/impl/ScanConfiguration.java new file mode 100644 index 0000000..0e82304 --- /dev/null +++ b/file/src/main/java/org/apache/sling/installer/provider/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.provider.file.impl; + +/** + * A scan configuration for the file install. + */ +public class ScanConfiguration { + + public String directory; + + public Long scanInterval; +} diff --git a/file/src/main/java/org/apache/sling/installer/provider/file/impl/ServicesListener.java b/file/src/main/java/org/apache/sling/installer/provider/file/impl/ServicesListener.java new file mode 100644 index 0000000..90d016a --- /dev/null +++ b/file/src/main/java/org/apache/sling/installer/provider/file/impl/ServicesListener.java @@ -0,0 +1,200 @@ +/* + * 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.provider.file.impl; + +import java.util.Dictionary; +import java.util.Hashtable; +import java.util.List; + +import org.apache.sling.installer.api.OsgiInstaller; +import org.apache.sling.installer.api.UpdateHandler; +import org.apache.sling.settings.SlingSettingsService; +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; +import org.osgi.framework.ServiceRegistration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * 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 logger. */ + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + /** The name of the installer service. */ + private static final String INSTALLER_SERVICE_NAME = OsgiInstaller.class.getName(); + + /** The name of the settings service. */ + private static final String SETTINGS_SERVICE_NAME = SlingSettingsService.class.getName(); + + /** The bundle context. */ + private final BundleContext bundleContext; + + /** The listener for the installer. */ + private final Listener installerListener; + + /** The listener for the settings service. */ + private final Listener settingsListener; + + /** The file installer. */ + private final FileInstaller installer; + + /** Service registration. */ + private ServiceRegistration registration; + + private boolean running = false; + + public ServicesListener(final BundleContext bundleContext, + final List<ScanConfiguration> configs) { + this.bundleContext = bundleContext; + boolean writeBack = true; + final Object writeBackObj = Activator.getProp(this.bundleContext, Activator.KEY_WRITEBACK); + if ( writeBackObj != null && "false".equalsIgnoreCase(writeBackObj.toString())) { + writeBack = false; + } + this.installer = new FileInstaller(configs, writeBack); + this.installerListener = new Listener(INSTALLER_SERVICE_NAME); + this.settingsListener = new Listener(SETTINGS_SERVICE_NAME); + this.installerListener.start(); + this.settingsListener.start(); + } + + public synchronized void notifyChange() { + final boolean shouldRun = this.installer.hasConfigurations(); + if ( (shouldRun && !running) || (!shouldRun && running) ) { + final OsgiInstaller installer = (OsgiInstaller)this.installerListener.getService(); + final SlingSettingsService settings = (SlingSettingsService)this.settingsListener.getService(); + if ( installer != null && settings != null && !running ) { + logger.debug("Starting scanner"); + this.startScanner(installer, settings); + } else if ( running && (installer == null || settings == null) ) { + logger.debug("Stopping scanner"); + this.stopScanner(); + } + } + } + + /** + * Deactivate this listener. + */ + public void deactivate() { + this.installerListener.deactivate(); + this.stopScanner(); + } + + /** Vendor of all registered services. */ + public static final String VENDOR = "The Apache Software Foundation"; + + private void startScanner(final OsgiInstaller installer, final SlingSettingsService settings) { + if ( !running ) { + this.installer.start(installer, settings); + final Dictionary<String, Object> props = new Hashtable<String, Object>(); + props.put(Constants.SERVICE_DESCRIPTION, "Apache Sling File Installer Controller Service"); + props.put(Constants.SERVICE_VENDOR, VENDOR); + props.put(UpdateHandler.PROPERTY_SCHEMES, this.installer.getSchemes()); + + this.registration = this.bundleContext.registerService(UpdateHandler.class.getName(), + this.installer, props); + running = true; + } + } + + private void stopScanner() { + if ( running ) { + if ( this.registration != null ) { + this.registration.unregister(); + this.registration = null; + } + this.installer.stop(); + running = false; + } + } + + protected final class Listener implements ServiceListener { + + private final String serviceName; + + private ServiceReference reference; + private Object service; + + public Listener(final String serviceName) { + this.serviceName = serviceName; + } + + public void start() { + this.retainService(); + try { + bundleContext.addServiceListener(this, "(" + + Constants.OBJECTCLASS + "=" + serviceName + ")"); + } catch (final InvalidSyntaxException ise) { + // this should really never happen + throw new RuntimeException("Unexpected exception occured.", ise); + } + } + + 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(); + } + } + } +} -- To stop receiving notification emails like this one, please contact "[email protected]" <[email protected]>.
