blob: 6bb4f559a2c3e5baeb3bc2a06ab33ab96b74e71b [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.karaf.shell.config;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URL;
import java.util.ArrayList;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.List;
import org.apache.felix.fileinstall.ArtifactInstaller;
import org.apache.karaf.shell.console.OsgiCommandSupport;
import org.apache.karaf.util.Properties;
import org.osgi.framework.Constants;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;
import org.osgi.service.cm.Configuration;
import org.osgi.service.cm.ConfigurationAdmin;
/**
* Abstract class from which all commands related to the ConfigurationAdmin
* service should derive.
* This command retrieves a reference to the ConfigurationAdmin service before
* calling another method to actually process the command.
*/
public abstract class ConfigCommandSupport extends OsgiCommandSupport {
public static final String PROPERTY_CONFIG_PID = "ConfigCommand.PID";
public static final String PROPERTY_CONFIG_PROPS = "ConfigCommand.Props";
private static final String PID_FILTER = "(service.pid=%s*)";
private static final String FILE_PREFIX = "file:";
private static final String CONFIG_SUFFIX = ".cfg";
private static final String FACTORY_SEPARATOR = "-";
private static final String FILEINSTALL_FILE_NAME = "felix.fileinstall.filename";
protected File storage;
private List<ArtifactInstaller> artifactInstallers;
protected Object doExecute() throws Exception {
// Get config admin service.
ServiceReference ref = getBundleContext().getServiceReference(ConfigurationAdmin.class.getName());
if (ref == null) {
System.out.println("ConfigurationAdmin service is unavailable.");
return null;
}
ConfigurationAdmin admin = getConfigurationAdmin();
if (admin == null) {
System.out.println("ConfigAdmin service is unavailable.");
return null;
}
doExecute(admin);
return null;
}
protected Dictionary getEditedProps() throws Exception {
return (Dictionary) this.session.get(PROPERTY_CONFIG_PROPS);
}
protected ConfigurationAdmin getConfigurationAdmin() {
ServiceReference ref = getBundleContext().getServiceReference(ConfigurationAdmin.class.getName());
if (ref == null) {
return null;
}
try {
ConfigurationAdmin admin = (ConfigurationAdmin) getBundleContext().getService(ref);
if (admin == null) {
return null;
} else {
return admin;
}
} finally {
getBundleContext().ungetService(ref);
}
}
protected abstract void doExecute(ConfigurationAdmin admin) throws Exception;
/**
* <p>
* Returns the Configuration object of the given (felix fileinstall) file name.
* </p>
*
* @param fileName
* @return
*/
public Configuration findConfigurationByFileName(ConfigurationAdmin admin, String fileName) throws IOException, InvalidSyntaxException {
if (fileName != null && fileName.contains(FACTORY_SEPARATOR)) {
String factoryPid = fileName.substring(0, fileName.lastIndexOf(FACTORY_SEPARATOR));
String absoluteFileName = FILE_PREFIX + storage.getAbsolutePath() + File.separator + fileName + CONFIG_SUFFIX;
Configuration[] configurations = admin.listConfigurations(String.format(PID_FILTER, factoryPid));
if (configurations != null) {
for (Configuration configuration : configurations) {
Dictionary dictionary = configuration.getProperties();
if (dictionary != null) {
String fileInstallFileName = (String) dictionary.get(FILEINSTALL_FILE_NAME);
if (absoluteFileName.equals(fileInstallFileName)) {
return configuration;
}
}
}
}
}
return null;
}
/**
* Saves config to storage or ConfigurationAdmin.
*
* @param admin
* @param pid
* @param props
* @param bypassStorage
* @throws IOException
*/
protected void update(ConfigurationAdmin admin, String pid, Dictionary props, boolean bypassStorage) throws IOException {
if (!bypassStorage && storage != null) {
persistConfiguration(admin, pid, props);
} else {
updateConfiguration(admin, pid, props);
}
}
/**
* Persists configuration to storage.
*
* @param admin
* @param pid
* @param props
* @throws IOException
*/
protected void persistConfiguration(ConfigurationAdmin admin, String pid, Dictionary props) throws IOException {
File storageFile = new File(storage, pid + ".cfg");
Configuration cfg = admin.getConfiguration(pid, null);
if (cfg != null && cfg.getProperties() != null) {
Object val = cfg.getProperties().get(FILEINSTALL_FILE_NAME);
try {
if (val instanceof URL) {
storageFile = new File(((URL) val).toURI());
}
if (val instanceof URI) {
storageFile = new File((URI) val);
}
if (val instanceof String) {
storageFile = new File(new URL((String) val).toURI());
}
} catch (Exception e) {
throw (IOException) new IOException(e.getMessage()).initCause(e);
}
}
Properties p = new Properties(storageFile);
for (Enumeration keys = props.keys(); keys.hasMoreElements(); ) {
Object key = keys.nextElement();
if (!Constants.SERVICE_PID.equals(key)
&& !ConfigurationAdmin.SERVICE_FACTORYPID.equals(key)
&& !FILEINSTALL_FILE_NAME.equals(key)) {
p.put((String) key, (String) props.get(key));
}
}
// remove "removed" properties from the file
ArrayList<String> propertiesToRemove = new ArrayList<String>();
for (Object key : p.keySet()) {
if (props.get(key) == null
&& !Constants.SERVICE_PID.equals(key)
&& !ConfigurationAdmin.SERVICE_FACTORYPID.equals(key)
&& !FILEINSTALL_FILE_NAME.equals(key)) {
propertiesToRemove.add(key.toString());
}
}
for (String key : propertiesToRemove) {
p.remove(key);
}
// save the cfg file
storage.mkdirs();
p.save();
updateFileInstall(storageFile);
}
/**
* Trigger felix fileinstall to update the config so there is no delay till it polls the file
*
* @param storageFile
* @throws Exception
*/
private void updateFileInstall(File storageFile) {
if (artifactInstallers != null) {
for (ArtifactInstaller installer : artifactInstallers) {
if (installer.canHandle(storageFile)) {
try {
installer.update(storageFile);
} catch (Exception e) {
log.warn("Error updating config " + storageFile + " in felix fileinstall" + e.getMessage(), e);
}
}
}
}
}
/**
* Updates the configuration to the {@link ConfigurationAdmin} service.
*
* @param admin
* @param pid
* @param props
* @throws IOException
*/
public void updateConfiguration(ConfigurationAdmin admin, String pid, Dictionary props) throws IOException {
Configuration cfg = admin.getConfiguration(pid, null);
if (cfg.getProperties() == null) {
String[] pids = parsePid(pid);
if (pids[1] != null) {
cfg = admin.createFactoryConfiguration(pids[0], null);
}
}
if (cfg.getBundleLocation() != null) {
cfg.setBundleLocation(null);
}
cfg.update(props);
}
protected String[] parsePid(String pid) {
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 void deleteStorage(String pid) throws Exception {
File cfgFile = new File(storage, pid + ".cfg");
cfgFile.delete();
}
public File getStorage() {
return storage;
}
public void setStorage(File storage) {
this.storage = storage;
}
public void setArtifactInstallers(List<ArtifactInstaller> artifactInstallers) {
this.artifactInstallers = artifactInstallers;
}
}