blob: 0a7035240775849d83b382044aecb10700fa1044 [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.felix.fileinstall.internal;
import java.io.*;
import java.net.URI;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.*;
import org.apache.felix.fileinstall.ArtifactInstaller;
import org.apache.felix.fileinstall.ArtifactListener;
import org.apache.felix.fileinstall.internal.Util.Logger;
import org.apache.felix.utils.collections.DictionaryAsMap;
import org.apache.felix.utils.properties.InterpolationHelper;
import org.apache.felix.utils.properties.TypedProperties;
import org.osgi.framework.*;
import org.osgi.service.cm.*;
/**
* ArtifactInstaller for configurations.
* TODO: This service lifecycle should be bound to the ConfigurationAdmin service lifecycle.
*/
public class ConfigInstaller implements ArtifactInstaller, ConfigurationListener
{
private final BundleContext context;
private final ConfigurationAdmin configAdmin;
private final FileInstall fileInstall;
private final Map<String, String> pidToFile = new HashMap<>();
private ServiceRegistration registration;
ConfigInstaller(BundleContext context, ConfigurationAdmin configAdmin, FileInstall fileInstall)
{
this.context = context;
this.configAdmin = configAdmin;
this.fileInstall = fileInstall;
}
public void init()
{
registration = this.context.registerService(
new String[] {
ConfigurationListener.class.getName(),
ArtifactListener.class.getName(),
ArtifactInstaller.class.getName()
},
this, null);
try
{
Configuration[] configs = configAdmin.listConfigurations(null);
if (configs != null)
{
for (Configuration config : configs)
{
Dictionary dict = config.getProperties();
String fileName = dict != null ? (String) dict.get( DirectoryWatcher.FILENAME ) : null;
if (fileName != null)
{
pidToFile.put(config.getPid(), fileName);
}
}
}
}
catch (Exception e)
{
Util.log( context, Logger.LOG_INFO, "Unable to initialize configurations list", e );
}
}
public void destroy()
{
registration.unregister();
}
public boolean canHandle(File artifact)
{
return artifact.getName().endsWith(".cfg")
|| artifact.getName().endsWith(".config");
}
public void install(File artifact) throws Exception
{
setConfig(artifact);
}
public void update(File artifact) throws Exception
{
setConfig(artifact);
}
public void uninstall(File artifact) throws Exception
{
deleteConfig(artifact);
}
public void configurationEvent(final ConfigurationEvent configurationEvent)
{
if (System.getSecurityManager() != null)
{
AccessController.doPrivileged(
new PrivilegedAction<Void>()
{
public Void run()
{
doConfigurationEvent(configurationEvent);
return null;
}
}
);
}
else
{
doConfigurationEvent(configurationEvent);
}
}
public void doConfigurationEvent(ConfigurationEvent configurationEvent)
{
// Check if writing back configurations has been disabled.
{
if (!shouldSaveConfig())
{
return;
}
}
if (configurationEvent.getType() == ConfigurationEvent.CM_UPDATED)
{
try
{
Configuration config = getConfigurationAdmin().getConfiguration(
configurationEvent.getPid(),
"?");
Dictionary dict = config.getProperties();
String fileName = dict != null ? (String) dict.get( DirectoryWatcher.FILENAME ) : null;
File file = fileName != null ? fromConfigKey(fileName) : null;
if( file != null && file.isFile() && canHandle( file ) ) {
pidToFile.put(config.getPid(), fileName);
TypedProperties props = new TypedProperties( bundleSubstitution() );
try (Reader r = new InputStreamReader(new FileInputStream(file), encoding()))
{
props.load(r);
}
// remove "removed" properties from the cfg file
List<String> propertiesToRemove = new ArrayList<>();
for( String key : props.keySet() )
{
if( dict.get(key) == null
&& !Constants.SERVICE_PID.equals(key)
&& !ConfigurationAdmin.SERVICE_FACTORYPID.equals(key)
&& !DirectoryWatcher.FILENAME.equals(key) ) {
propertiesToRemove.add(key);
}
}
for( Enumeration e = dict.keys(); e.hasMoreElements(); )
{
String key = e.nextElement().toString();
if( !Constants.SERVICE_PID.equals(key)
&& !ConfigurationAdmin.SERVICE_FACTORYPID.equals(key)
&& !DirectoryWatcher.FILENAME.equals(key) )
{
Object val = dict.get( key );
props.put( key, val );
}
}
for( String key : propertiesToRemove )
{
props.remove(key);
}
try (Writer fw = new OutputStreamWriter(new FileOutputStream(file), encoding()))
{
props.save( fw );
}
// we're just writing out what's already loaded into ConfigAdmin, so
// update file checksum since lastModified gets updated when writing
fileInstall.updateChecksum(file);
}
}
catch (Exception e)
{
Util.log( context, Logger.LOG_INFO, "Unable to save configuration", e );
}
}
if (configurationEvent.getType() == ConfigurationEvent.CM_DELETED)
{
try {
String fileName = pidToFile.remove(configurationEvent.getPid());
File file = fileName != null ? fromConfigKey(fileName) : null;
if (file != null && file.isFile()) {
if (!file.delete()) {
throw new IOException("Unable to delete file: " + file);
}
}
}
catch (Exception e)
{
Util.log( context, Logger.LOG_INFO, "Unable to delete configuration file", e );
}
}
}
boolean shouldSaveConfig()
{
String str = this.context.getProperty( DirectoryWatcher.ENABLE_CONFIG_SAVE );
if (str == null)
{
str = this.context.getProperty( DirectoryWatcher.DISABLE_CONFIG_SAVE );
}
if (str != null)
{
return Boolean.valueOf(str);
}
return true;
}
String encoding()
{
String str = this.context.getProperty( DirectoryWatcher.CONFIG_ENCODING );
return str != null ? str : "ISO-8859-1";
}
ConfigurationAdmin getConfigurationAdmin()
{
return configAdmin;
}
/**
* Set the configuration based on the config file.
*
* @param f
* Configuration file
* @return <code>true</code> if the configuration has been updated
* @throws Exception
*/
boolean setConfig(final File f) throws Exception
{
final Hashtable<String, Object> ht = new Hashtable<>();
final InputStream in = new BufferedInputStream(new FileInputStream(f));
try
{
in.mark(1);
boolean isXml = in.read() == '<';
in.reset();
if (isXml) {
final Properties p = new Properties();
p.loadFromXML(in);
Map<String, String> strMap = new HashMap<>();
for (Object k : p.keySet()) {
strMap.put(k.toString(), p.getProperty(k.toString()));
}
InterpolationHelper.performSubstitution(strMap, context);
ht.putAll(strMap);
} else {
TypedProperties p = new TypedProperties( bundleSubstitution() );
try (Reader r = new InputStreamReader(in, encoding()))
{
p.load(r);
}
for (String k : p.keySet()) {
ht.put(k, p.get(k));
}
}
}
finally
{
in.close();
}
String pid[] = parsePid(f.getName());
Configuration config = getConfiguration(toConfigKey(f), pid[0], pid[1]);
Dictionary<String, Object> props = config.getProperties();
Hashtable<String, Object> old = props != null ? new Hashtable<String, Object>(new DictionaryAsMap<>(props)) : null;
if (old != null) {
old.remove( DirectoryWatcher.FILENAME );
old.remove( Constants.SERVICE_PID );
old.remove( ConfigurationAdmin.SERVICE_FACTORYPID );
}
if( !ht.equals( old ) )
{
ht.put(DirectoryWatcher.FILENAME, toConfigKey(f));
if (old == null) {
Util.log(context, Logger.LOG_INFO, "Creating configuration from " + pid[0]
+ (pid[1] == null ? "" : "-" + pid[1]) + ".cfg", null);
} else {
Util.log(context, Logger.LOG_INFO, "Updating configuration from " + pid[0]
+ (pid[1] == null ? "" : "-" + pid[1]) + ".cfg", null);
}
config.update(ht);
return true;
}
else
{
return false;
}
}
/**
* Remove the configuration.
*
* @param f
* File where the configuration in was defined.
* @return <code>true</code>
* @throws Exception
*/
boolean deleteConfig(File f) throws Exception
{
String pid[] = parsePid(f.getName());
Util.log(context, Logger.LOG_INFO, "Deleting configuration from " + pid[0]
+ (pid[1] == null ? "" : "-" + pid[1]) + ".cfg", null);
Configuration config = getConfiguration(toConfigKey(f), pid[0], pid[1]);
config.delete();
return true;
}
String toConfigKey(File f) {
return f.getAbsoluteFile().toURI().toString();
}
File fromConfigKey(String key) {
return new File(URI.create(key));
}
String[] parsePid(String path)
{
String pid = path.substring(0, path.lastIndexOf('.'));
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
};
}
}
Configuration getConfiguration(String fileName, String pid, String factoryPid)
throws Exception
{
Configuration oldConfiguration = findExistingConfiguration(fileName);
Configuration cachedConfiguration = oldConfiguration != null ?
getConfigurationAdmin().getConfiguration(oldConfiguration.getPid()) : null;
if (cachedConfiguration != null)
{
return cachedConfiguration;
}
else
{
Configuration newConfiguration;
if (factoryPid != null)
{
newConfiguration = getConfigurationAdmin().createFactoryConfiguration(pid, "?");
}
else
{
newConfiguration = getConfigurationAdmin().getConfiguration(pid, "?");
}
return newConfiguration;
}
}
Configuration findExistingConfiguration(String fileName) throws Exception
{
String filter = "(" + DirectoryWatcher.FILENAME + "=" + escapeFilterValue(fileName) + ")";
Configuration[] configurations = getConfigurationAdmin().listConfigurations(filter);
if (configurations != null && configurations.length > 0)
{
return configurations[0];
}
else
{
return null;
}
}
TypedProperties.SubstitutionCallback bundleSubstitution() {
final InterpolationHelper.SubstitutionCallback cb = new InterpolationHelper.BundleContextSubstitutionCallback(context);
return new TypedProperties.SubstitutionCallback() {
@Override
public String getValue(String name, String key, String value) {
return cb.getValue(value);
}
};
}
private String escapeFilterValue(String s) {
return s.replaceAll("[(]", "\\\\(").
replaceAll("[)]", "\\\\)").
replaceAll("[=]", "\\\\=").
replaceAll("[\\*]", "\\\\*");
}
}