blob: 6940dea0b518d35feee7a670a81e15bfab9cf6eb [file] [log] [blame]
/*
* 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.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Dictionary;
import java.util.List;
import java.util.Map;
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.installer.api.serializer.ConfigurationSerializer;
import org.apache.sling.installer.api.serializer.ConfigurationSerializerFactory;
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 {
private static final String CONFIG_FILE_EXTENSION = ".cfg.json";
/** 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;
private final boolean autoCreateDirectories;
public FileInstaller(final List<ScanConfiguration> configs, final boolean writeBack, boolean autoCreateDirectories) {
this.writeBack = writeBack;
this.autoCreateDirectories = autoCreateDirectories;
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);
if (autoCreateDirectories) {
try {
Files.createDirectory(Paths.get(config.directory));
} catch (IOException e) {
logger.warn("Could not create directory to monitor at {}", config.directory, e);
}
}
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 '.cfg.json'
if ( !oldPath.endsWith(CONFIG_FILE_EXTENSION) ) {
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_FILE_EXTENSION;
} else {
path = oldPath.substring(0, lastDot) + CONFIG_FILE_EXTENSION;
}
} 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_FILE_EXTENSION;
prefix = first.getListener().getScheme();
logger.debug("Add of {} at {}", resourceType, path);
}
final File file = new File(path);
file.getParentFile().mkdirs();
try (OutputStream fos = new BufferedOutputStream(new FileOutputStream(file))) {
// comments starting with "//" allowed according to https://osgi.org/specification/osgi.cmpn/7.0.0/service.configurator.html#d0e131566
fos.write("// Configuration created by Apache Sling File Installer\n".getBytes(StandardCharsets.UTF_8));
ConfigurationSerializer serializer = ConfigurationSerializerFactory.create(ConfigurationSerializerFactory.Format.JSON);
serializer.serialize(dict, fos);
}
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;
}
}
}