This is an automated email from the ASF dual-hosted git repository.
cziegeler pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/felix-dev.git
The following commit(s) were added to refs/heads/master by this push:
new d600205 FELIX-6367 : Provide SPI for configuration management (WiP)
d600205 is described below
commit d6002050b461cf58458b1d52509cac8be5238fe9
Author: Carsten Ziegeler <[email protected]>
AuthorDate: Sun Aug 29 10:32:45 2021 +0200
FELIX-6367 : Provide SPI for configuration management (WiP)
---
webconsole/pom.xml | 5 +-
.../internal/configuration/ConfigAdminSupport.java | 750 ++-------------------
.../internal/configuration/ConfigJsonSupport.java | 494 ++++++++++++++
.../internal/configuration/ConfigManager.java | 64 +-
.../internal/configuration/ConfigurationUtil.java | 150 +++++
.../webconsole/internal/misc/ServletSupport.java | 51 ++
.../felix/webconsole/spi/ConfigurationHandler.java | 64 ++
.../felix/webconsole/spi/ValidationException.java | 32 +
.../apache/felix/webconsole/spi/package-info.java | 21 +
9 files changed, 903 insertions(+), 728 deletions(-)
diff --git a/webconsole/pom.xml b/webconsole/pom.xml
index 864195b..7485a13 100644
--- a/webconsole/pom.xml
+++ b/webconsole/pom.xml
@@ -41,8 +41,9 @@
<webconsole.exports>
org.apache.felix.webconsole;provide:=true,
org.apache.felix.webconsole.bundleinfo;provide:=true,
- org.apache.felix.webconsole.i18n;provide:=true
- </webconsole.exports>
+ org.apache.felix.webconsole.i18n;provide:=true,
+ org.apache.felix.webconsole.spi
+ </webconsole.exports>
</properties>
<scm>
diff --git
a/webconsole/src/main/java/org/apache/felix/webconsole/internal/configuration/ConfigAdminSupport.java
b/webconsole/src/main/java/org/apache/felix/webconsole/internal/configuration/ConfigAdminSupport.java
index b876f26..e7b7aad 100644
---
a/webconsole/src/main/java/org/apache/felix/webconsole/internal/configuration/ConfigAdminSupport.java
+++
b/webconsole/src/main/java/org/apache/felix/webconsole/internal/configuration/ConfigAdminSupport.java
@@ -20,8 +20,6 @@ package org.apache.felix.webconsole.internal.configuration;
import java.io.IOException;
-import java.io.PrintWriter;
-import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Dictionary;
@@ -29,41 +27,26 @@ import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
-import java.util.Iterator;
import java.util.List;
-import java.util.Locale;
import java.util.Map;
-import java.util.Map.Entry;
import java.util.Set;
-import java.util.SortedMap;
-import java.util.TreeMap;
import java.util.Vector;
-import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.servlet.http.HttpServletRequest;
-import org.apache.felix.utils.json.JSONWriter;
-import org.apache.felix.webconsole.internal.Util;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.BundleContext;
+import org.apache.felix.webconsole.internal.misc.ServletSupport;
+import org.apache.felix.webconsole.spi.ConfigurationHandler;
import org.osgi.framework.Constants;
-import org.osgi.framework.Filter;
import org.osgi.framework.InvalidSyntaxException;
-import org.osgi.framework.ServiceReference;
-import org.osgi.framework.Version;
import org.osgi.service.cm.Configuration;
import org.osgi.service.cm.ConfigurationAdmin;
-import org.osgi.service.cm.ManagedService;
-import org.osgi.service.cm.ManagedServiceFactory;
import org.osgi.service.metatype.AttributeDefinition;
-import org.osgi.service.metatype.ObjectClassDefinition;
-class ConfigAdminSupport
-{
+class ConfigAdminSupport {
- private static final String PROPERTY_FACTORYCONFIG_NAMEHINT =
"webconsole.configurationFactory.nameHint";
+ public static final String PROPERTY_FACTORYCONFIG_NAMEHINT =
"webconsole.configurationFactory.nameHint";
public static final Set<String> CONFIG_PROPERTIES_HIDE = new HashSet<>();
static {
CONFIG_PROPERTIES_HIDE.add(PROPERTY_FACTORYCONFIG_NAMEHINT);
@@ -71,97 +54,42 @@ class ConfigAdminSupport
CONFIG_PROPERTIES_HIDE.add(ConfigurationAdmin.SERVICE_FACTORYPID);
CONFIG_PROPERTIES_HIDE.add(Constants.SERVICE_PID);
}
- private static final Pattern NAMEHINT_PLACEHOLER_REGEXP =
Pattern.compile("\\{([^\\{\\}]*)}");
+ public static final Pattern NAMEHINT_PLACEHOLER_REGEXP =
Pattern.compile("\\{([^\\{\\}]*)}");
- private final BundleContext bundleContext;
private final ConfigurationAdmin service;
- private final ConfigManager configManager;
+ private final ServletSupport servletSupport;
+ private final List<ConfigurationHandler> configurationHandlers;
/**
+ * Create a new support instance
+ * @param support The servlet support for logging and bundle context
+ * @param service The configuration admin service
*
- * @param bundleContext
- * @param service
- *
- * @throws ClassCastException if {@code service} is not a MetaTypeService
instances
+ * @throws ClassCastException if {@code service} is not a
ConfigurationAdmin instances
*/
- ConfigAdminSupport( final ConfigManager configManager, final BundleContext
bundleContext, final Object service )
- {
- this.configManager = configManager;
- this.bundleContext = bundleContext;
+ ConfigAdminSupport( final ServletSupport support,
+ final Object service,
+ final List<ConfigurationHandler> handlers ) {
+ this.servletSupport = support;
this.service = ( ConfigurationAdmin ) service;
+ this.configurationHandlers = handlers;
}
-
- public BundleContext getBundleContext()
- {
- return bundleContext;
+ public ConfigJsonSupport getJsonSupport() {
+ return new ConfigJsonSupport(this.servletSupport,
getMetaTypeSupport(), this.service);
}
- MetaTypeServiceSupport getMetaTypeSupport()
- {
- Object metaTypeService = configManager.getService(
ConfigManager.META_TYPE_NAME );
- if ( metaTypeService != null )
- {
- return new MetaTypeServiceSupport( this.getBundleContext(),
metaTypeService );
- }
-
- return null;
- }
-
-
- final Configuration getConfiguration( String pid )
- {
- if ( pid != null )
- {
- try
- {
- // we use listConfigurations to not create configuration
- // objects persistently without the user providing actual
- // configuration
- String filter = '(' + Constants.SERVICE_PID + '=' + pid + ')';
- Configuration[] configs = this.service.listConfigurations(
filter );
- if ( configs != null && configs.length > 0 )
- {
- return configs[0];
- }
- }
- catch ( InvalidSyntaxException ise )
- {
- // should print message
- }
- catch ( IOException ioe )
- {
- // should print message
- }
+ MetaTypeServiceSupport getMetaTypeSupport() {
+ Object metaTypeService = servletSupport.getService(
ConfigManager.META_TYPE_NAME );
+ if ( metaTypeService != null ) {
+ return new MetaTypeServiceSupport(
servletSupport.getBundleContext(), metaTypeService );
}
- // fallback to no configuration at all
return null;
}
-
- final Configuration getConfiguration( String pid, String factoryPid )
throws IOException
- {
- if ( factoryPid != null && ( pid == null || pid.equals(
ConfigManager.PLACEHOLDER_PID ) ) )
- {
- return this.service.createFactoryConfiguration( factoryPid, null );
- }
-
- return this.service.getConfiguration( pid, null );
- }
-
-
- Configuration getPlaceholderConfiguration( final String factoryPid )
- {
- return new PlaceholderConfiguration( factoryPid );
- }
-
- String getPlaceholderPid() {
- return ConfigManager.PLACEHOLDER_PID;
- }
-
boolean shouldSet(final PropertyDescriptor ad, final String value, final
boolean isUpdate)
{
if ( ad.hasMetatype() && !isUpdate )
@@ -197,15 +125,23 @@ class ConfigAdminSupport
return true;
}
+ /**
+ * Apply the update to the configuration
+ * @param request The request
+ * @param pid The pid
+ * @param propertyList The list of properties
+ * @param isUpdate {@code true} if this is a rest call, false if it is
done via the webconsole UI
+ * @return {@code true} when the configuration got updated, {@code false}
when the codnfiugration got deleted
+ * @throws IOException
+ */
boolean applyConfiguration( final HttpServletRequest request, final String
pid, final String propertyList, final boolean isUpdate )
throws IOException
{
final String factoryPid = request.getParameter(
ConfigManager.FACTORY_PID );
- final Configuration config = getConfiguration( pid, factoryPid );
+ final Configuration config =
ConfigurationUtil.getOrCreateConfiguration( this.service,
this.configurationHandlers, pid, factoryPid );
Dictionary<String, Object> props = config.getProperties();
- if ( props == null )
- {
+ if ( props == null ) {
props = new Hashtable<>();
}
@@ -237,7 +173,7 @@ class ConfigAdminSupport
if ( value != null
&& ( attributeType != AttributeDefinition.PASSWORD ||
!MetaTypeSupport.PASSWORD_PLACEHOLDER_VALUE.equals( value ) ) )
{
- if ( shouldSet(ad, value, isUpdate) )
+ if ( this.configurationHandlers.isEmpty() && shouldSet(ad,
value, isUpdate) )
{
props.put( propName, value );
}
@@ -253,7 +189,7 @@ class ConfigAdminSupport
final String value = request.getParameter( paramName );
if ( value != null )
{
- if ( shouldSet(ad, value, isUpdate) )
+ if ( this.configurationHandlers.isEmpty() && shouldSet(ad,
value, isUpdate) )
{
try
{
@@ -328,7 +264,7 @@ class ConfigAdminSupport
valueArray[i] = vec.get(i).toString();
}
- final boolean shouldSet = shouldSet(ad, valueArray, isUpdate);
+ final boolean shouldSet = this.configurationHandlers.isEmpty()
&& shouldSet(ad, valueArray, isUpdate);
if ( ad.getCardinality() < 0 )
{
@@ -375,14 +311,16 @@ class ConfigAdminSupport
props = updateProps;
}
- if ( props.isEmpty() )
- {
- config.delete();
+ // call update handlers
+ for(final ConfigurationHandler h : this.configurationHandlers) {
+ h.updateConfiguration(factoryPid, pid, props);
+ }
+
+ if ( props.isEmpty() ) {
+ this.deleteConfiguration(config);
return false;
- }
- else
- {
+ } else {
final String location =
request.getParameter(ConfigManager.LOCATION);
if ( location == null || location.trim().length() == 0 ||
ConfigManager.UNBOUND_LOCATION.equals(location) )
{
@@ -413,597 +351,29 @@ class ConfigAdminSupport
}
}
-
- void printConfigurationJson( PrintWriter pw, String pid, Configuration
config, String pidFilter,
- String locale )
- {
-
- JSONWriter result = new JSONWriter( pw );
-
- if ( pid != null )
- {
- try
- {
- result.object();
- this.configForm( result, pid, config, pidFilter, locale );
- result.endObject();
- }
- catch ( Exception e )
- {
- configManager.log( "Error reading configuration PID " + pid, e
);
- }
- }
-
- }
-
-
- void configForm( JSONWriter json, String pid, Configuration config, String
pidFilter, String locale )
- throws IOException
- {
-
- json.key( ConfigManager.PID );
- json.value( pid );
-
- if ( pidFilter != null )
- {
- json.key( ConfigManager.PID_FILTER );
- json.value( pidFilter );
- }
-
- Dictionary<String, Object> props = null;
- if ( config != null )
- {
- props = config.getProperties();
- }
- if ( props == null )
- {
- props = new Hashtable<>();
- }
-
- boolean doSimpleMerge = true;
- final MetaTypeServiceSupport mtss = getMetaTypeSupport();
- if ( mtss != null )
- {
- ObjectClassDefinition ocd = null;
- if ( config != null )
- {
- ocd = mtss.getObjectClassDefinition( config, locale );
- }
- if ( ocd == null )
- {
- ocd = mtss.getObjectClassDefinition( pid, locale );
- }
- if ( ocd != null )
- {
- mtss.mergeWithMetaType( props, ocd, json,
CONFIG_PROPERTIES_HIDE );
- doSimpleMerge = false;
- }
- }
-
- if (doSimpleMerge)
- {
- json.key( "title" ).value( pid ); //$NON-NLS-1$
- json.key( "description" ).value( //$NON-NLS-1$
- "This form is automatically generated from existing
properties because no property "
- + "descriptors are available for this configuration. This
may be cause by the absence "
- + "of the OSGi Metatype Service or the absence of a
MetaType descriptor for this configuration." );
-
- json.key( "properties" ).object(); //$NON-NLS-1$
- for ( Enumeration<String> pe = props.keys(); pe.hasMoreElements();
)
- {
- final String id = pe.nextElement();
-
- // ignore well known special properties
- if ( !id.equals( Constants.SERVICE_PID ) && !id.equals(
Constants.SERVICE_DESCRIPTION )
- && !id.equals( Constants.SERVICE_ID ) && !id.equals(
Constants.SERVICE_VENDOR )
- && !id.equals(
ConfigurationAdmin.SERVICE_BUNDLELOCATION )
- && !id.equals( ConfigurationAdmin.SERVICE_FACTORYPID )
)
- {
- final Object value = props.get( id );
- final PropertyDescriptor ad =
MetaTypeServiceSupport.createAttributeDefinition( id, value );
- json.key( id );
- MetaTypeServiceSupport.attributeToJson( json, ad, value );
- }
- }
- json.endObject();
- }
-
- if ( config != null )
- {
- this.addConfigurationInfo( config, json, locale );
- }
- }
-
-
- void addConfigurationInfo( Configuration config, JSONWriter json, String
locale )
- throws IOException
- {
-
- if ( config.getFactoryPid() != null )
- {
- json.key( ConfigManager.FACTORY_PID );
- json.value( config.getFactoryPid() );
- }
-
- String bundleLocation = config.getBundleLocation();
- if ( ConfigManager.UNBOUND_LOCATION.equals(bundleLocation) )
- {
- bundleLocation = null;
- }
- String location;
- if ( bundleLocation == null )
- {
- location = ""; //$NON-NLS-1$
- }
- else
- {
- // if the configuration is bound to a bundle location which
- // is not related to an installed bundle, we just print the
- // raw bundle location binding
- Bundle bundle = MetaTypeServiceSupport.getBundle(
this.getBundleContext(), bundleLocation );
- if ( bundle == null )
- {
- location = bundleLocation;
- }
- else
- {
- Dictionary<String, String> headers = bundle.getHeaders( locale
);
- String name = headers.get( Constants.BUNDLE_NAME );
- if ( name == null )
- {
- location = bundle.getSymbolicName();
- }
- else
- {
- location = name + " (" + bundle.getSymbolicName() + ')';
//$NON-NLS-1$
- }
-
- Version v = Version.parseVersion( headers.get(
Constants.BUNDLE_VERSION ) );
- location += ", Version " + v.toString();
- }
- }
- json.key( "bundleLocation" ); //$NON-NLS-1$
- json.value( location );
- // raw bundle location and service locations
- final String pid = config.getPid();
- String serviceLocation = ""; //$NON-NLS-1$
- try
- {
- final ServiceReference<?>[] refs =
getBundleContext().getServiceReferences(
- (String)null,
- "(&(" + Constants.OBJECTCLASS + '=' +
ManagedService.class.getName() //$NON-NLS-1$
- + ")(" + Constants.SERVICE_PID + '=' + pid + "))");
//$NON-NLS-1$ //$NON-NLS-2$
- if ( refs != null && refs.length > 0 )
- {
- serviceLocation = refs[0].getBundle().getLocation();
- }
- }
- catch (Throwable t)
- {
- configManager.log( "Error getting service associated with
configuration " + pid, t );
- }
- json.key( "bundle_location" ); //$NON-NLS-1$
- json.value ( bundleLocation );
- json.key( "service_location" ); //$NON-NLS-1$
- json.value ( serviceLocation );
- }
-
-
- private final Bundle getBoundBundle(Configuration config)
- {
- if (null == config)
- return null;
- final String location = config.getBundleLocation();
- if (null == location)
- return null;
-
- final Bundle bundles[] = getBundleContext().getBundles();
- for (int i = 0; bundles != null && i < bundles.length; i++)
- {
- if (bundles[i].getLocation().equals(location))
- return bundles[i];
-
- }
- return null;
- }
-
-
- final boolean listConfigurations( JSONWriter jw, String pidFilter, String
locale, Locale loc )
- {
- boolean hasConfigurations = false;
- try
- {
- // start with ManagedService instances
- Map<String, String> optionsPlain =
getServices(ManagedService.class.getName(), pidFilter,
- locale, true);
-
- // next are the MetaType informations without ManagedService
- final MetaTypeServiceSupport mtss = getMetaTypeSupport();
- if ( mtss != null )
- {
- addMetaTypeNames( optionsPlain, mtss.getPidObjectClasses(
locale ), pidFilter, Constants.SERVICE_PID );
- }
-
- // add in existing configuration (not duplicating ManagedServices)
- Configuration[] cfgs = this.service.listConfigurations(pidFilter);
- for (int i = 0; cfgs != null && i < cfgs.length; i++)
- {
-
- // ignore configuration object if an entry already exists in
the map
- // or if it is invalid
- final String pid = cfgs[i].getPid();
- if (optionsPlain.containsKey(pid) ||
!ConfigManager.isAllowedPid(pid) )
- {
- continue;
- }
-
- // insert and entry for the PID
- if ( mtss != null )
- {
- try
- {
- ObjectClassDefinition ocd =
mtss.getObjectClassDefinition( cfgs[i], locale );
- if ( ocd != null )
- {
- optionsPlain.put( pid, ocd.getName() );
- continue;
- }
- }
- catch ( IllegalArgumentException t )
- {
- // MetaTypeProvider.getObjectClassDefinition might
throw illegal
- // argument exception. So we must catch it here,
otherwise the
- // other configurations will not be shown
- // See https://issues.apache.org/jira/browse/FELIX-2390
- }
- }
-
- // no object class definition, use plain PID
- optionsPlain.put( pid, pid );
- }
-
- jw.key("pids");//$NON-NLS-1$
- jw.array();
- for ( Iterator<String> ii = optionsPlain.keySet().iterator();
ii.hasNext(); )
- {
- hasConfigurations = true;
- String id = ii.next();
- Object name = optionsPlain.get( id );
-
- final Configuration config = this.getConfiguration( id );
- jw.object();
- jw.key("id").value( id ); //$NON-NLS-1$
- jw.key( "name").value( name ); //$NON-NLS-1$
- if ( null != config )
- {
- // FELIX-3848
- jw.key("has_config").value( true ); //$NON-NLS-1$
-
- final String fpid = config.getFactoryPid();
- if ( null != fpid )
- {
- jw.key("fpid").value( fpid ); //$NON-NLS-1$
- final String val =
getConfigurationFactoryNameHint(config, mtss);
- if ( val != null )
- {
- jw.key( "nameHint").value(val ); //$NON-NLS-1$
- }
- }
-
- final Bundle bundle = getBoundBundle( config );
- if ( null != bundle )
- {
- jw.key( "bundle").value( bundle.getBundleId() );
//$NON-NLS-1$
- jw.key( "bundle_name").value( Util.getName( bundle,
loc ) ); //$NON-NLS-1$
- }
- }
- jw.endObject();
-
- }
- jw.endArray();
- }
- catch (Exception e)
- {
- configManager.log("listConfigurations: Unexpected problem
encountered", e);
- }
- return hasConfigurations;
- }
-
- /**
- * Builds a "name hint" for factory configuration based on other property
- * values of the config and a "name hint template" defined as hidden
- * property in the service.
- * @param props Service properties.
- * @return Name hint or null if none is defined.
- */
- private static final String getConfigurationFactoryNameHint(Configuration
config, MetaTypeServiceSupport mtss)
- {
- Dictionary<String, Object> props = config.getProperties();
- Map<String, MetatypePropertyDescriptor> adMap = (mtss != null) ?
mtss.getAttributeDefinitionMap(config, null) : null;
- if (null == adMap)
- {
- return null;
- }
-
- // check for configured name hint template
- String nameHint =
getConfigurationPropertyValueOrDefault(PROPERTY_FACTORYCONFIG_NAMEHINT, props,
adMap);
- if (nameHint == null)
- {
- return null;
- }
-
- // search for all variable patterns in name hint and replace them with
configured/default values
- Matcher matcher = NAMEHINT_PLACEHOLER_REGEXP.matcher(nameHint);
- StringBuffer sb = new StringBuffer();
- while (matcher.find())
- {
- String propertyName = matcher.group(1);
- String value =
getConfigurationPropertyValueOrDefault(propertyName, props, adMap);
- if (value == null) {
- value = "";
- }
- matcher.appendReplacement(sb, matcherQuoteReplacement(value));
- }
- matcher.appendTail(sb);
-
- // make sure name hint does not only contain whitespaces
- nameHint = sb.toString().trim();
- if (nameHint.length() == 0) {
- return null;
- }
- else {
- return nameHint;
- }
- }
-
- /**
- * Gets configured service property value, or default value if no value is
configured.
- * @param propertyName Property name
- * @param props Service configuration properties map
- * @param adMap Attribute definitions map
- * @return Value or null if none found
- */
- private static String getConfigurationPropertyValueOrDefault(String
propertyName, Dictionary<String, Object> props, Map<String,
MetatypePropertyDescriptor> adMap) {
- // get configured property value
- Object value = props.get(propertyName);
-
- if (value != null)
- {
- // convert array to string
- if (value.getClass().isArray())
- {
- StringBuffer valueString = new StringBuffer();
- for (int i = 0; i < Array.getLength(value); i++)
- {
- if (i > 0)
- {
- valueString.append(", ");
- }
- valueString.append(Array.get(value, i));
- }
- return valueString.toString();
- }
- else
- {
- return value.toString();
- }
- }
- else
- {
- // if not set try to get default value
- PropertyDescriptor ad = adMap.get(propertyName);
- if (ad != null && ad.getDefaultValue() != null &&
ad.getDefaultValue().length == 1)
- {
- return ad.getDefaultValue()[0];
- }
- }
-
- return null;
- }
-
- /**
- * Replacement for Matcher.quoteReplacement which is only available in JDK
1.5 and up.
- * @param str Unquoted string
- * @return Quoted string
- */
- private static String matcherQuoteReplacement(String str)
- {
- StringBuffer sb = new StringBuffer();
- for (int i = 0; i < str.length(); i++) {
- char c = str.charAt(i);
- if (c == '$' || c == '\\') {
- sb.append('\\');
- }
- sb.append(c);
- }
- return sb.toString();
- }
-
- final void listFactoryConfigurations(JSONWriter jw, String pidFilter,
- String locale)
- {
- try
- {
- final Map<String, String> optionsFactory =
getServices(ManagedServiceFactory.class.getName(),
- pidFilter, locale, true);
- final MetaTypeServiceSupport mtss = getMetaTypeSupport();
- if ( mtss != null )
- {
- addMetaTypeNames( optionsFactory,
mtss.getFactoryPidObjectClasses( locale ), pidFilter,
- ConfigurationAdmin.SERVICE_FACTORYPID );
- }
- jw.key("fpids");
- jw.array();
- for ( Iterator<String> ii = optionsFactory.keySet().iterator();
ii.hasNext(); )
- {
- String id = ii.next();
- Object name = optionsFactory.get( id );
- jw.object();
- jw.key("id").value(id ); //$NON-NLS-1$
- jw.key("name").value( name ); //$NON-NLS-1$
- jw.endObject();
- }
- jw.endArray();
- }
- catch (Exception e)
- {
- configManager.log("listFactoryConfigurations: Unexpected problem
encountered", e);
- }
- }
-
- SortedMap<String, String> getServices( String serviceClass, String
serviceFilter, String locale,
- boolean ocdRequired ) throws InvalidSyntaxException
- {
- // sorted map of options
- SortedMap<String, String> optionsFactory = new TreeMap<>(
String.CASE_INSENSITIVE_ORDER );
-
- // find all ManagedServiceFactories to get the factoryPIDs
- ServiceReference<?>[] refs =
this.getBundleContext().getServiceReferences( serviceClass, serviceFilter );
- for ( int i = 0; refs != null && i < refs.length; i++ )
- {
- Object pidObject = refs[i].getProperty( Constants.SERVICE_PID );
- // only include valid PIDs
- if ( pidObject instanceof String &&
ConfigManager.isAllowedPid((String)pidObject) )
- {
- String pid = ( String ) pidObject;
- String name = pid;
- boolean haveOcd = !ocdRequired;
- final MetaTypeServiceSupport mtss = getMetaTypeSupport();
- if ( mtss != null )
- {
- final ObjectClassDefinition ocd =
mtss.getObjectClassDefinition( refs[i].getBundle(), pid, locale );
- if ( ocd != null )
- {
- name = ocd.getName();
- haveOcd = true;
- }
- }
-
- if ( haveOcd )
- {
- optionsFactory.put( pid, name );
- }
- }
- }
-
- return optionsFactory;
+ public void deleteConfiguration(final String pid) throws IOException {
+ // only delete if the PID is not our place holder
+ if ( !ConfigurationUtil.getPlaceholderPid().equals( pid ) ) {
+ final Configuration config =
ConfigurationUtil.findConfiguration(this.service, pid);
+ this.deleteConfiguration(config);
+ }
}
- private void addMetaTypeNames( final Map<String, String> pidMap, final
Map<String, ObjectClassDefinition> ocdCollection, final String filterSpec,
final String type )
- {
- Filter filter = null;
- if ( filterSpec != null )
- {
- try
- {
- filter = getBundleContext().createFilter( filterSpec );
- }
- catch ( InvalidSyntaxException not_expected )
- {
- /* filter is correct */
- }
- }
-
- for ( Iterator<Map.Entry<String, ObjectClassDefinition>> ei =
ocdCollection.entrySet().iterator(); ei.hasNext(); )
- {
- Entry<String, ObjectClassDefinition> ociEntry = ei.next();
- final String pid = ociEntry.getKey();
- final ObjectClassDefinition ocd = ociEntry.getValue();
- if ( filter == null )
- {
- pidMap.put( pid, ocd.getName() );
+ void deleteConfiguration(final Configuration config) throws IOException {
+ // only delete if the PID is not our place holder
+ if ( config != null && !ConfigurationUtil.getPlaceholderPid().equals(
config.getPid() ) ) {
+ for(final ConfigurationHandler h : this.configurationHandlers) {
+ h.deleteConfiguration(config.getFactoryPid(), config.getPid());
}
- else
- {
- final Dictionary<String, Object> props = new Hashtable<>();
- props.put( type, pid );
- if ( filter.match( props ) )
- {
- pidMap.put( pid, ocd.getName() );
- }
- }
- }
+ config.delete();
+ }
}
- private static class PlaceholderConfiguration implements Configuration
- {
-
- private final String factoryPid;
- private String bundleLocation;
-
-
- PlaceholderConfiguration( String factoryPid )
- {
- this.factoryPid = factoryPid;
- }
-
-
- @Override
- public String getPid()
- {
- return ConfigManager.PLACEHOLDER_PID;
- }
-
-
- @Override
- public String getFactoryPid()
- {
- return factoryPid;
- }
-
-
- @Override
- public void setBundleLocation( String bundleLocation )
- {
- this.bundleLocation = bundleLocation;
- }
-
-
- @Override
- public String getBundleLocation()
- {
- return bundleLocation;
- }
-
-
- @Override
- public Dictionary<String, Object> getProperties()
- {
- // dummy configuration has no properties
- return null;
- }
-
-
- @Override
- public void update()
- {
- // dummy configuration cannot be updated
- }
-
-
- @Override
- public void update( Dictionary<String, ?> properties )
- {
- // dummy configuration cannot be updated
- }
-
-
- @Override
- public void delete()
- {
- // dummy configuration cannot be deleted
- }
-
-
- @Override
- public long getChangeCount() {
- // dummy configuration always returns 0
- return 0;
- }
+ public Configuration findConfiguration(final String pid) {
+ return ConfigurationUtil.findConfiguration(this.service, pid);
}
- public Configuration[] listConfigurations(String filter) throws
IOException, InvalidSyntaxException
- {
+ public Configuration[] listConfigurations(final String filter) throws
IOException, InvalidSyntaxException {
return this.service.listConfigurations(filter);
}
}
diff --git
a/webconsole/src/main/java/org/apache/felix/webconsole/internal/configuration/ConfigJsonSupport.java
b/webconsole/src/main/java/org/apache/felix/webconsole/internal/configuration/ConfigJsonSupport.java
new file mode 100644
index 0000000..a3a50d5
--- /dev/null
+++
b/webconsole/src/main/java/org/apache/felix/webconsole/internal/configuration/ConfigJsonSupport.java
@@ -0,0 +1,494 @@
+/*
+ * 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.webconsole.internal.configuration;
+
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.lang.reflect.Array;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.SortedMap;
+import java.util.TreeMap;
+import java.util.regex.Matcher;
+
+import org.apache.felix.utils.json.JSONWriter;
+import org.apache.felix.webconsole.internal.Util;
+import org.apache.felix.webconsole.internal.misc.ServletSupport;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.Constants;
+import org.osgi.framework.Filter;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.Version;
+import org.osgi.service.cm.Configuration;
+import org.osgi.service.cm.ConfigurationAdmin;
+import org.osgi.service.cm.ManagedService;
+import org.osgi.service.cm.ManagedServiceFactory;
+import org.osgi.service.metatype.ObjectClassDefinition;
+
+
+class ConfigJsonSupport {
+
+ private final ServletSupport servletSupport;
+
+ private final MetaTypeServiceSupport mtss;
+
+ private final ConfigurationAdmin configurationAdmin;
+
+ public ConfigJsonSupport(final ServletSupport support,
+ final MetaTypeServiceSupport mtss,
+ final ConfigurationAdmin cfgAdmin) {
+ this.servletSupport = support;
+ this.mtss = mtss;
+ this.configurationAdmin = cfgAdmin;
+ }
+
+ public void printConfigurationJson( final PrintWriter pw, final String
pid, final Configuration config, final String pidFilter,
+ final String locale ) {
+
+ final JSONWriter result = new JSONWriter( pw );
+
+ if ( pid != null ) {
+ try{
+ result.object();
+ this.configForm( result, pid, config, pidFilter, locale );
+ result.endObject();
+ } catch ( final Exception e ) {
+ this.servletSupport.log( "Error reading configuration PID " +
pid, e );
+ }
+ }
+
+ }
+
+ void configForm( final JSONWriter json, final String pid, final
Configuration config, final String pidFilter, final String locale )
+ throws IOException {
+ json.key( ConfigManager.PID );
+ json.value( pid );
+
+ if ( pidFilter != null ) {
+ json.key( ConfigManager.PID_FILTER );
+ json.value( pidFilter );
+ }
+
+ Dictionary<String, Object> props = null;
+ if ( config != null ) {
+ props = config.getProperties();
+ }
+ if ( props == null ) {
+ props = new Hashtable<>();
+ }
+
+ boolean doSimpleMerge = true;
+ if ( this.mtss != null ) {
+ ObjectClassDefinition ocd = null;
+ if ( config != null ) {
+ ocd = mtss.getObjectClassDefinition( config, locale );
+ }
+ if ( ocd == null ) {
+ ocd = mtss.getObjectClassDefinition( pid, locale );
+ }
+ if ( ocd != null ) {
+ mtss.mergeWithMetaType( props, ocd, json,
ConfigAdminSupport.CONFIG_PROPERTIES_HIDE );
+ doSimpleMerge = false;
+ }
+ }
+
+ if (doSimpleMerge) {
+ json.key( "title" ).value( pid ); //$NON-NLS-1$
+ json.key( "description" ).value( //$NON-NLS-1$
+ "This form is automatically generated from existing
properties because no property "
+ + "descriptors are available for this configuration. This
may be cause by the absence "
+ + "of the OSGi Metatype Service or the absence of a
MetaType descriptor for this configuration." );
+
+ json.key( "properties" ).object(); //$NON-NLS-1$
+ for ( Enumeration<String> pe = props.keys(); pe.hasMoreElements();
) {
+ final String id = pe.nextElement();
+
+ // ignore well known special properties
+ if ( !id.equals( Constants.SERVICE_PID ) && !id.equals(
Constants.SERVICE_DESCRIPTION )
+ && !id.equals( Constants.SERVICE_ID ) && !id.equals(
Constants.SERVICE_VENDOR )
+ && !id.equals(
ConfigurationAdmin.SERVICE_BUNDLELOCATION )
+ && !id.equals( ConfigurationAdmin.SERVICE_FACTORYPID )
) {
+ final Object value = props.get( id );
+ final PropertyDescriptor ad =
MetaTypeServiceSupport.createAttributeDefinition( id, value );
+ json.key( id );
+ MetaTypeServiceSupport.attributeToJson( json, ad, value );
+ }
+ }
+ json.endObject();
+ }
+
+ if ( config != null ) {
+ this.addConfigurationInfo( config, json, locale );
+ }
+ }
+
+
+ void addConfigurationInfo( final Configuration config, final JSONWriter
json, final String locale )
+ throws IOException {
+
+ if ( config.getFactoryPid() != null ) {
+ json.key( ConfigManager.FACTORY_PID );
+ json.value( config.getFactoryPid() );
+ }
+
+ String bundleLocation = config.getBundleLocation();
+ if ( ConfigManager.UNBOUND_LOCATION.equals(bundleLocation) ) {
+ bundleLocation = null;
+ }
+ String location;
+ if ( bundleLocation == null ) {
+ location = ""; //$NON-NLS-1$
+ } else {
+ // if the configuration is bound to a bundle location which
+ // is not related to an installed bundle, we just print the
+ // raw bundle location binding
+ Bundle bundle = MetaTypeServiceSupport.getBundle(
this.servletSupport.getBundleContext(), bundleLocation );
+ if ( bundle == null ) {
+ location = bundleLocation;
+ } else {
+ Dictionary<String, String> headers = bundle.getHeaders( locale
);
+ String name = headers.get( Constants.BUNDLE_NAME );
+ if ( name == null ) {
+ location = bundle.getSymbolicName();
+ } else {
+ location = name + " (" + bundle.getSymbolicName() + ')';
//$NON-NLS-1$
+ }
+
+ Version v = Version.parseVersion( headers.get(
Constants.BUNDLE_VERSION ) );
+ location += ", Version " + v.toString();
+ }
+ }
+ json.key( "bundleLocation" ); //$NON-NLS-1$
+ json.value( location );
+ // raw bundle location and service locations
+ final String pid = config.getPid();
+ String serviceLocation = ""; //$NON-NLS-1$
+ try {
+ final ServiceReference<?>[] refs =
this.servletSupport.getBundleContext().getServiceReferences(
+ (String)null,
+ "(&(" + Constants.OBJECTCLASS + '=' +
ManagedService.class.getName() //$NON-NLS-1$
+ + ")(" + Constants.SERVICE_PID + '=' + pid + "))");
//$NON-NLS-1$ //$NON-NLS-2$
+ if ( refs != null && refs.length > 0 ) {
+ serviceLocation = refs[0].getBundle().getLocation();
+ }
+ } catch (final Throwable t) {
+ this.servletSupport.log( "Error getting service associated with
configuration " + pid, t );
+ }
+ json.key( "bundle_location" ); //$NON-NLS-1$
+ json.value ( bundleLocation );
+ json.key( "service_location" ); //$NON-NLS-1$
+ json.value ( serviceLocation );
+ }
+
+ private final Bundle getBoundBundle(final Configuration config) {
+ if (null == config) {
+ return null;
+ }
+ final String location = config.getBundleLocation();
+ if (null == location) {
+ return null;
+ }
+
+ final Bundle bundles[] =
this.servletSupport.getBundleContext().getBundles();
+ for (int i = 0; bundles != null && i < bundles.length; i++) {
+ if (bundles[i].getLocation().equals(location)) {
+ return bundles[i];
+ }
+ }
+ return null;
+ }
+
+ final boolean listConfigurations( final JSONWriter jw, final String
pidFilter, final String locale, final Locale loc ) {
+ boolean hasConfigurations = false;
+ try {
+ // start with ManagedService instances
+ Map<String, String> optionsPlain =
getServices(ManagedService.class.getName(), pidFilter,
+ locale, true);
+
+ // next are the MetaType informations without ManagedService
+ if ( mtss != null ) {
+ addMetaTypeNames( optionsPlain, mtss.getPidObjectClasses(
locale ), pidFilter, Constants.SERVICE_PID );
+ }
+
+ // add in existing configuration (not duplicating ManagedServices)
+ Configuration[] cfgs =
this.configurationAdmin.listConfigurations(pidFilter);
+ for (int i = 0; cfgs != null && i < cfgs.length; i++)
+ {
+
+ // ignore configuration object if an entry already exists in
the map
+ // or if it is invalid
+ final String pid = cfgs[i].getPid();
+ if (optionsPlain.containsKey(pid) ||
!ConfigManager.isAllowedPid(pid) )
+ {
+ continue;
+ }
+
+ // insert and entry for the PID
+ if ( mtss != null )
+ {
+ try
+ {
+ ObjectClassDefinition ocd =
mtss.getObjectClassDefinition( cfgs[i], locale );
+ if ( ocd != null )
+ {
+ optionsPlain.put( pid, ocd.getName() );
+ continue;
+ }
+ }
+ catch ( IllegalArgumentException t )
+ {
+ // MetaTypeProvider.getObjectClassDefinition might
throw illegal
+ // argument exception. So we must catch it here,
otherwise the
+ // other configurations will not be shown
+ // See https://issues.apache.org/jira/browse/FELIX-2390
+ }
+ }
+
+ // no object class definition, use plain PID
+ optionsPlain.put( pid, pid );
+ }
+
+ jw.key("pids");//$NON-NLS-1$
+ jw.array();
+ for ( Iterator<String> ii = optionsPlain.keySet().iterator();
ii.hasNext(); )
+ {
+ hasConfigurations = true;
+ String id = ii.next();
+ Object name = optionsPlain.get( id );
+
+ final Configuration config =
ConfigurationUtil.findConfiguration( this.configurationAdmin, id );
+ jw.object();
+ jw.key("id").value( id ); //$NON-NLS-1$
+ jw.key( "name").value( name ); //$NON-NLS-1$
+ if ( null != config )
+ {
+ // FELIX-3848
+ jw.key("has_config").value( true ); //$NON-NLS-1$
+
+ final String fpid = config.getFactoryPid();
+ if ( null != fpid )
+ {
+ jw.key("fpid").value( fpid ); //$NON-NLS-1$
+ final String val =
getConfigurationFactoryNameHint(config, mtss);
+ if ( val != null )
+ {
+ jw.key( "nameHint").value(val ); //$NON-NLS-1$
+ }
+ }
+
+ final Bundle bundle = getBoundBundle( config );
+ if ( null != bundle ) {
+ jw.key( "bundle").value( bundle.getBundleId() );
//$NON-NLS-1$
+ jw.key( "bundle_name").value( Util.getName( bundle,
loc ) ); //$NON-NLS-1$
+ }
+ }
+ jw.endObject();
+
+ }
+ jw.endArray();
+ } catch (final Exception e) {
+ this.servletSupport.log("listConfigurations: Unexpected problem
encountered", e);
+ }
+ return hasConfigurations;
+ }
+
+ /**
+ * Builds a "name hint" for factory configuration based on other property
+ * values of the config and a "name hint template" defined as hidden
+ * property in the service.
+ * @param props Service properties.
+ * @return Name hint or null if none is defined.
+ */
+ private static final String getConfigurationFactoryNameHint(Configuration
config, MetaTypeServiceSupport mtss) {
+ Dictionary<String, Object> props = config.getProperties();
+ Map<String, MetatypePropertyDescriptor> adMap = (mtss != null) ?
mtss.getAttributeDefinitionMap(config, null) : null;
+ if (null == adMap) {
+ return null;
+ }
+
+ // check for configured name hint template
+ String nameHint =
getConfigurationPropertyValueOrDefault(ConfigAdminSupport.PROPERTY_FACTORYCONFIG_NAMEHINT,
props, adMap);
+ if (nameHint == null) {
+ return null;
+ }
+
+ // search for all variable patterns in name hint and replace them with
configured/default values
+ Matcher matcher =
ConfigAdminSupport.NAMEHINT_PLACEHOLER_REGEXP.matcher(nameHint);
+ StringBuffer sb = new StringBuffer();
+ while (matcher.find()) {
+ String propertyName = matcher.group(1);
+ String value =
getConfigurationPropertyValueOrDefault(propertyName, props, adMap);
+ if (value == null) {
+ value = "";
+ }
+ matcher.appendReplacement(sb, matcherQuoteReplacement(value));
+ }
+ matcher.appendTail(sb);
+
+ // make sure name hint does not only contain whitespaces
+ nameHint = sb.toString().trim();
+ if (nameHint.length() == 0) {
+ return null;
+ } else {
+ return nameHint;
+ }
+ }
+
+ /**
+ * Gets configured service property value, or default value if no value is
configured.
+ * @param propertyName Property name
+ * @param props Service configuration properties map
+ * @param adMap Attribute definitions map
+ * @return Value or null if none found
+ */
+ private static String getConfigurationPropertyValueOrDefault(String
propertyName, Dictionary<String, Object> props, Map<String,
MetatypePropertyDescriptor> adMap) {
+ // get configured property value
+ Object value = props.get(propertyName);
+
+ if (value != null) {
+ // convert array to string
+ if (value.getClass().isArray()) {
+ StringBuffer valueString = new StringBuffer();
+ for (int i = 0; i < Array.getLength(value); i++) {
+ if (i > 0) {
+ valueString.append(", ");
+ }
+ valueString.append(Array.get(value, i));
+ }
+ return valueString.toString();
+ } else {
+ return value.toString();
+ }
+ } else {
+ // if not set try to get default value
+ PropertyDescriptor ad = adMap.get(propertyName);
+ if (ad != null && ad.getDefaultValue() != null &&
ad.getDefaultValue().length == 1) {
+ return ad.getDefaultValue()[0];
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Replacement for Matcher.quoteReplacement which is only available in JDK
1.5 and up.
+ * @param str Unquoted string
+ * @return Quoted string
+ */
+ private static String matcherQuoteReplacement(String str) {
+ StringBuffer sb = new StringBuffer();
+ for (int i = 0; i < str.length(); i++) {
+ char c = str.charAt(i);
+ if (c == '$' || c == '\\') {
+ sb.append('\\');
+ }
+ sb.append(c);
+ }
+ return sb.toString();
+ }
+
+ final void listFactoryConfigurations(JSONWriter jw, String pidFilter,
+ String locale) {
+ try {
+ final Map<String, String> optionsFactory =
getServices(ManagedServiceFactory.class.getName(),
+ pidFilter, locale, true);
+ if ( mtss != null ) {
+ addMetaTypeNames( optionsFactory,
mtss.getFactoryPidObjectClasses( locale ), pidFilter,
+ ConfigurationAdmin.SERVICE_FACTORYPID );
+ }
+ jw.key("fpids");
+ jw.array();
+ for ( Iterator<String> ii = optionsFactory.keySet().iterator();
ii.hasNext(); ) {
+ String id = ii.next();
+ Object name = optionsFactory.get( id );
+ jw.object();
+ jw.key("id").value(id ); //$NON-NLS-1$
+ jw.key("name").value( name ); //$NON-NLS-1$
+ jw.endObject();
+ }
+ jw.endArray();
+ } catch (final Exception e) {
+ this.servletSupport.log("listFactoryConfigurations: Unexpected
problem encountered", e);
+ }
+ }
+
+ SortedMap<String, String> getServices( String serviceClass, String
serviceFilter, String locale,
+ boolean ocdRequired ) throws InvalidSyntaxException {
+ // sorted map of options
+ SortedMap<String, String> optionsFactory = new TreeMap<>(
String.CASE_INSENSITIVE_ORDER );
+
+ // find all ManagedServiceFactories to get the factoryPIDs
+ ServiceReference<?>[] refs =
this.servletSupport.getBundleContext().getServiceReferences( serviceClass,
serviceFilter );
+ for ( int i = 0; refs != null && i < refs.length; i++ ) {
+ Object pidObject = refs[i].getProperty( Constants.SERVICE_PID );
+ // only include valid PIDs
+ if ( pidObject instanceof String &&
ConfigManager.isAllowedPid((String)pidObject) ) {
+ String pid = ( String ) pidObject;
+ String name = pid;
+ boolean haveOcd = !ocdRequired;
+ if ( mtss != null ) {
+ final ObjectClassDefinition ocd =
mtss.getObjectClassDefinition( refs[i].getBundle(), pid, locale );
+ if ( ocd != null ) {
+ name = ocd.getName();
+ haveOcd = true;
+ }
+ }
+
+ if ( haveOcd ) {
+ optionsFactory.put( pid, name );
+ }
+ }
+ }
+
+ return optionsFactory;
+ }
+
+ private void addMetaTypeNames( final Map<String, String> pidMap, final
Map<String, ObjectClassDefinition> ocdCollection, final String filterSpec,
final String type ) {
+ Filter filter = null;
+ if ( filterSpec != null ) {
+ try {
+ filter = this.servletSupport.getBundleContext().createFilter(
filterSpec );
+ } catch ( InvalidSyntaxException not_expected ){
+ // filter is correct
+ }
+ }
+
+ for ( Iterator<Map.Entry<String, ObjectClassDefinition>> ei =
ocdCollection.entrySet().iterator(); ei.hasNext(); ) {
+ Entry<String, ObjectClassDefinition> ociEntry = ei.next();
+ final String pid = ociEntry.getKey();
+ final ObjectClassDefinition ocd = ociEntry.getValue();
+ if ( filter == null ) {
+ pidMap.put( pid, ocd.getName() );
+ } else {
+ final Dictionary<String, Object> props = new Hashtable<>();
+ props.put( type, pid );
+ if ( filter.match( props ) ) {
+ pidMap.put( pid, ocd.getName() );
+ }
+ }
+ }
+ }
+}
diff --git
a/webconsole/src/main/java/org/apache/felix/webconsole/internal/configuration/ConfigManager.java
b/webconsole/src/main/java/org/apache/felix/webconsole/internal/configuration/ConfigManager.java
index 37145c0..48a0f6e 100644
---
a/webconsole/src/main/java/org/apache/felix/webconsole/internal/configuration/ConfigManager.java
+++
b/webconsole/src/main/java/org/apache/felix/webconsole/internal/configuration/ConfigManager.java
@@ -16,10 +16,10 @@
*/
package org.apache.felix.webconsole.internal.configuration;
-
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
+import java.util.Collections;
import java.util.Locale;
import java.util.Map;
@@ -33,6 +33,8 @@ import org.apache.felix.webconsole.WebConsoleConstants;
import org.apache.felix.webconsole.WebConsoleUtil;
import org.apache.felix.webconsole.internal.OsgiManagerPlugin;
import org.apache.felix.webconsole.internal.Util;
+import org.apache.felix.webconsole.internal.misc.ServletSupport;
+import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.service.cm.Configuration;
@@ -42,7 +44,7 @@ import org.osgi.service.cm.Configuration;
* The <code>ConfigManager</code> class is the Web Console plugin to
* manage configurations.
*/
-public class ConfigManager extends SimpleWebConsolePlugin implements
OsgiManagerPlugin
+public class ConfigManager extends SimpleWebConsolePlugin implements
OsgiManagerPlugin, ServletSupport
{
private static final long serialVersionUID = 5021174538498622428L;
@@ -54,7 +56,6 @@ public class ConfigManager extends SimpleWebConsolePlugin
implements OsgiManager
static final String PID_FILTER = "pidFilter"; //$NON-NLS-1$
static final String PID = "pid"; //$NON-NLS-1$
static final String FACTORY_PID = "factoryPid"; //$NON-NLS-1$
- static final String PLACEHOLDER_PID = "[Temporary PID replaced by real PID
upon save]"; //$NON-NLS-1$
static final String REFERER = "referer"; //$NON-NLS-1$
static final String FACTORY_CREATE = "factoryCreate"; //$NON-NLS-1$
@@ -152,13 +153,7 @@ public class ConfigManager extends SimpleWebConsolePlugin
implements OsgiManager
{
if ( request.getParameter( ConfigManager.ACTION_DELETE ) != null )
//$NON-NLS-1$
{
- // only delete if the PID is not our place holder
- if ( !ConfigManager.PLACEHOLDER_PID.equals( pid ) )
- {
- this.log( "applyConfiguration: Deleting configuration " +
pid );
- cas.getConfiguration( pid, null ).delete();
- }
-
+ cas.deleteConfiguration( pid );
Util.sendJsonOk(response);
}
else
@@ -192,21 +187,17 @@ public class ConfigManager extends SimpleWebConsolePlugin
implements OsgiManager
final Configuration config;
// should actually apply the configuration before redirecting
- if ( request.getParameter( ACTION_CREATE ) != null )
- {
- config = cas.getPlaceholderConfiguration( pid );
+ if ( request.getParameter( ACTION_CREATE ) != null ) {
+ config = ConfigurationUtil.getPlaceholderConfiguration( pid );
pid = config.getPid();
- }
- else
- {
- config = cas.getConfiguration( pid );
+ } else {
+ config = cas.findConfiguration( pid );
}
// check for configuration unbinding
if ( request.getParameter( ACTION_UNBIND ) != null )
{
- if ( config != null && config.getBundleLocation() != null )
- {
+ if ( config != null && config.getBundleLocation() != null ) {
config.setBundleLocation( UNBOUND_LOCATION );
}
@@ -219,7 +210,7 @@ public class ConfigManager extends SimpleWebConsolePlugin
implements OsgiManager
response.setCharacterEncoding( "UTF-8" ); //$NON-NLS-1$
final Locale loc = getLocale( request );
final String locale = ( loc != null ) ? loc.toString() : null;
- cas.printConfigurationJson( response.getWriter(), pid, config,
pidFilter, locale );
+ cas.getJsonSupport().printConfigurationJson( response.getWriter(),
pid, config, pidFilter, locale );
}
@@ -327,7 +318,7 @@ public class ConfigManager extends SimpleWebConsolePlugin
implements OsgiManager
{
pw.print( ',' );
}
- ca.printConfigurationJson( pw, config.getPid(),
config, null, locale );
+ ca.getJsonSupport().printConfigurationJson( pw,
config.getPid(), config, null, locale );
printComma = true;
}
}
@@ -410,21 +401,21 @@ public class ConfigManager extends SimpleWebConsolePlugin
implements OsgiManager
StringWriter json = new StringWriter();
JSONWriter jw = new JSONWriter(json);
jw.object();
- final ConfigAdminSupport ca = getConfigurationAdminSupport();
+ final ConfigAdminSupport cas = getConfigurationAdminSupport();
// check for osgi installer plugin
@SuppressWarnings("unchecked")
final Map<String, Object> labelMap = (Map<String, Object>)
request.getAttribute(WebConsoleConstants.ATTR_LABEL_MAP);
jw.key("jsonsupport").value(
labelMap.containsKey("osgi-installer-config-printer") ); //$NON-NLS-1$
- final boolean hasMetatype = ca.getMetaTypeSupport() != null;
- jw.key("status").value( ca != null ? Boolean.TRUE : Boolean.FALSE);
//$NON-NLS-1$
+ final boolean hasMetatype = cas.getMetaTypeSupport() != null;
+ jw.key("status").value( cas != null ? Boolean.TRUE : Boolean.FALSE);
//$NON-NLS-1$
jw.key("metatype").value( hasMetatype ? Boolean.TRUE : Boolean.FALSE);
//$NON-NLS-1$
boolean hasConfigs = true;
- if ( ca != null )
+ if ( cas != null )
{
- hasConfigs = ca.listConfigurations( jw, pidFilter, locale, loc );
- ca.listFactoryConfigurations( jw, pidFilter, locale );
+ hasConfigs = cas.getJsonSupport().listConfigurations( jw,
pidFilter, locale, loc );
+ cas.getJsonSupport().listFactoryConfigurations( jw, pidFilter,
locale );
}
- if ( !hasConfigs && !hasMetatype && ca != null ) {
+ if ( !hasConfigs && !hasMetatype && cas != null ) {
jw.key("noconfigs").value(true); //$NON-NLS-1$
} else {
jw.key("noconfigs").value(false); //$NON-NLS-1$
@@ -433,9 +424,8 @@ public class ConfigManager extends SimpleWebConsolePlugin
implements OsgiManager
jw.endObject();
// if a configuration is addressed, display it immediately
- if ( request.getParameter( ACTION_CREATE ) != null && pid != null )
- {
- pid = PLACEHOLDER_PID; // new PlaceholderConfiguration( pid
).getPid();
+ if ( request.getParameter( ACTION_CREATE ) != null && pid != null ) {
+ pid = ConfigurationUtil.getPlaceholderPid();
}
@@ -457,14 +447,16 @@ public class ConfigManager extends SimpleWebConsolePlugin
implements OsgiManager
response.getWriter().print(TEMPLATE);
}
- private ConfigAdminSupport getConfigurationAdminSupport()
- {
+ private ConfigAdminSupport getConfigurationAdminSupport() {
Object configurationAdmin = getService( CONFIGURATION_ADMIN_NAME );
- if ( configurationAdmin != null )
- {
- return new ConfigAdminSupport( this, this.getBundleContext(),
configurationAdmin );
+ if ( configurationAdmin != null ) {
+ return new ConfigAdminSupport( this, configurationAdmin,
Collections.emptyList() );
}
return null;
}
+
+ public BundleContext getBundleContext() {
+ return super.getBundleContext();
+ }
}
diff --git
a/webconsole/src/main/java/org/apache/felix/webconsole/internal/configuration/ConfigurationUtil.java
b/webconsole/src/main/java/org/apache/felix/webconsole/internal/configuration/ConfigurationUtil.java
new file mode 100644
index 0000000..5ebff02
--- /dev/null
+++
b/webconsole/src/main/java/org/apache/felix/webconsole/internal/configuration/ConfigurationUtil.java
@@ -0,0 +1,150 @@
+/*
+ * 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.webconsole.internal.configuration;
+
+
+import java.io.IOException;
+import java.util.Dictionary;
+import java.util.List;
+
+import org.apache.felix.webconsole.spi.ConfigurationHandler;
+import org.osgi.framework.Constants;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.service.cm.Configuration;
+import org.osgi.service.cm.ConfigurationAdmin;
+
+
+public class ConfigurationUtil {
+
+ private static final String PLACEHOLDER_PID = "[Temporary PID replaced by
real PID upon save]"; //$NON-NLS-1$
+
+ public static Configuration findConfiguration( final ConfigurationAdmin
service, final String pid ) {
+ if ( pid != null ) {
+ try {
+ // we use listConfigurations to not create configuration
+ // objects persistently without the user providing actual
+ // configuration
+ String filter = '(' + Constants.SERVICE_PID + '=' + pid + ')';
+ Configuration[] configs = service.listConfigurations( filter );
+ if ( configs != null && configs.length > 0 ) {
+ return configs[0];
+ }
+ } catch ( final InvalidSyntaxException | IOException e ) {
+ // should print message
+ }
+ }
+
+ // fallback to no configuration at all
+ return null;
+ }
+
+
+ public static Configuration getOrCreateConfiguration( final
ConfigurationAdmin service,
+ final List<ConfigurationHandler> handlers,
+ final String pid,
+ final String factoryPid ) throws IOException {
+ Configuration cfg = null;
+ if ( !PLACEHOLDER_PID.equals(pid) ) {
+ cfg = findConfiguration(service, pid);
+ }
+ if ( cfg == null ) {
+ if ( factoryPid != null ) {
+ for(final ConfigurationHandler handler : handlers) {
+ handler.createFactoryConfiguration(factoryPid, null);
+ }
+ cfg = service.createFactoryConfiguration( factoryPid, null );
+ } else {
+ for(final ConfigurationHandler handler : handlers) {
+ handler.createConfiguration(pid);
+ }
+ cfg = service.getConfiguration( pid, null );
+ }
+ }
+ return cfg;
+ }
+
+
+ public static Configuration getPlaceholderConfiguration( final String
factoryPid ) {
+ return new PlaceholderConfiguration( factoryPid );
+ }
+
+ public static String getPlaceholderPid() {
+ return PLACEHOLDER_PID;
+ }
+
+ private static class PlaceholderConfiguration implements Configuration {
+
+ private final String factoryPid;
+ private String bundleLocation;
+
+
+ PlaceholderConfiguration( final String factoryPid ) {
+ this.factoryPid = factoryPid;
+ }
+
+
+ @Override
+ public String getPid() {
+ return PLACEHOLDER_PID;
+ }
+
+
+ @Override
+ public String getFactoryPid() {
+ return factoryPid;
+ }
+
+ @Override
+ public void setBundleLocation( final String bundleLocation ) {
+ this.bundleLocation = bundleLocation;
+ }
+
+ @Override
+ public String getBundleLocation() {
+ return bundleLocation;
+ }
+
+ @Override
+ public Dictionary<String, Object> getProperties() {
+ // dummy configuration has no properties
+ return null;
+ }
+
+ @Override
+ public void update() {
+ // dummy configuration cannot be updated
+ }
+
+ @Override
+ public void update( Dictionary<String, ?> properties ) {
+ // dummy configuration cannot be updated
+ }
+
+ @Override
+ public void delete() {
+ // dummy configuration cannot be deleted
+ }
+
+ @Override
+ public long getChangeCount() {
+ // dummy configuration always returns 0
+ return 0;
+ }
+ }
+}
diff --git
a/webconsole/src/main/java/org/apache/felix/webconsole/internal/misc/ServletSupport.java
b/webconsole/src/main/java/org/apache/felix/webconsole/internal/misc/ServletSupport.java
new file mode 100644
index 0000000..6f34c0c
--- /dev/null
+++
b/webconsole/src/main/java/org/apache/felix/webconsole/internal/misc/ServletSupport.java
@@ -0,0 +1,51 @@
+/*
+ * 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.webconsole.internal.misc;
+
+import org.osgi.framework.BundleContext;
+
+public interface ServletSupport {
+
+ /**
+ * Log the message
+ * @param msg a log message
+ */
+ void log(String msg);
+
+ /**
+ * Log the message
+ * @param msg a log message
+ * @param t a throwable
+ */
+ void log(String message, Throwable t);
+
+ /**
+ * Get the bundle context
+ * @return The bundle contect
+ */
+ BundleContext getBundleContext();
+
+ /**
+ * Gets the service with the specified class name.
+ *
+ * @param serviceName the service name to obtain
+ * @return the service or <code>null</code> if missing.
+ */
+ Object getService( String serviceName );
+}
diff --git
a/webconsole/src/main/java/org/apache/felix/webconsole/spi/ConfigurationHandler.java
b/webconsole/src/main/java/org/apache/felix/webconsole/spi/ConfigurationHandler.java
new file mode 100644
index 0000000..f35f5e6
--- /dev/null
+++
b/webconsole/src/main/java/org/apache/felix/webconsole/spi/ConfigurationHandler.java
@@ -0,0 +1,64 @@
+/*
+ * 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.webconsole.spi;
+
+import java.io.IOException;
+import java.util.Dictionary;
+
+/**
+ * A configuration handler allows to hook into the processing of
configurations for
+ * the webconsole plugin.
+ * A handler can decide to hide configurations and properties but also
implement
+ * additional validation.
+ * All configuration handlers are called in order of their service ranking,
highest first.
+ */
+public interface ConfigurationHandler {
+
+ /**
+ * A new configuration with that pid should be created
+ * @param pid The pid
+ * @throws IOException For an error or {@link ValidationException} if
creation is not allowed
+ */
+ void createConfiguration(String pid) throws IOException;
+
+ /**
+ * A new factory configuration with that pid should be created
+ * @param factoryPid The factory pid
+ * @param name Optional name, might be {@code null} if unknown
+ * @throws IOException For an error or {@link ValidationException} if
creation is not allowed
+ */
+ void createFactoryConfiguration(String factoryPid, String name) throws
IOException;
+
+ /**
+ * A configuration should be deleted
+ * @param factoryPid Optional factory pid
+ * @param pid The pid
+ * @throws IOException For an error or {@link ValidationException} if
deletion is not allowed
+ */
+ void deleteConfiguration(String factoryPid, String pid) throws IOException;
+
+ /**
+ * A configuration should be updated
+ * @param factoryPid Optional factory pid
+ * @param pid The pid
+ * @param props Mutable dictionary
+ * @throws IOException For an error or {@link ValidationException} if
deletion is not allowed
+ */
+ void updateConfiguration(String factoryPid, String pid, Dictionary<String,
Object> props) throws IOException;
+}
\ No newline at end of file
diff --git
a/webconsole/src/main/java/org/apache/felix/webconsole/spi/ValidationException.java
b/webconsole/src/main/java/org/apache/felix/webconsole/spi/ValidationException.java
new file mode 100644
index 0000000..a661b82
--- /dev/null
+++
b/webconsole/src/main/java/org/apache/felix/webconsole/spi/ValidationException.java
@@ -0,0 +1,32 @@
+/*
+ * 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.webconsole.spi;
+
+import java.io.IOException;
+
+public class ValidationException extends IOException {
+
+ /**
+ * Create validation exception
+ * @param message The validation message
+ */
+ public ValidationException(final String message) {
+ super(message);
+ }
+}
diff --git
a/webconsole/src/main/java/org/apache/felix/webconsole/spi/package-info.java
b/webconsole/src/main/java/org/apache/felix/webconsole/spi/package-info.java
new file mode 100644
index 0000000..c359a98
--- /dev/null
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/spi/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * 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.
+ */
[email protected]("1.0.0")
+package org.apache.felix.webconsole.spi;
+