blob: 0af94280a2f6da3a624e555c0539468292ff965f [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.webconsole.internal.configuration;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Dictionary;
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.StringTokenizer;
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.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
{
private static final String PROPERTY_FACTORYCONFIG_NAMEHINT = "webconsole.configurationFactory.nameHint";
private static final Set<String> CONFIG_PROPERTIES_HIDE = new HashSet<>();
static {
CONFIG_PROPERTIES_HIDE.add(PROPERTY_FACTORYCONFIG_NAMEHINT);
CONFIG_PROPERTIES_HIDE.add(ConfigurationAdmin.SERVICE_BUNDLELOCATION);
CONFIG_PROPERTIES_HIDE.add(ConfigurationAdmin.SERVICE_FACTORYPID);
CONFIG_PROPERTIES_HIDE.add(Constants.SERVICE_PID);
}
private static final Pattern NAMEHINT_PLACEHOLER_REGEXP = Pattern.compile("\\{([^\\{\\}]*)}");
private final BundleContext bundleContext;
private final ConfigurationAdmin service;
private final ConfigManager configManager;
/**
*
* @param bundleContext
* @param service
*
* @throws ClassCastException if {@code service} is not a MetaTypeService instances
*/
ConfigAdminSupport( final ConfigManager configManager, final BundleContext bundleContext, final Object service )
{
this.configManager = configManager;
this.bundleContext = bundleContext;
this.service = ( ConfigurationAdmin ) service;
}
public BundleContext getBundleContext()
{
return bundleContext;
}
private 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
}
}
// 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;
}
String applyConfiguration( HttpServletRequest request, String pid )
throws IOException
{
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 ) )
{
configManager.log( "applyConfiguration: Deleting configuration " + pid );
Configuration config = service.getConfiguration( pid, null );
config.delete();
}
return null; // return request.getHeader( "Referer" );
}
String factoryPid = request.getParameter( ConfigManager.FACTORY_PID );
Configuration config = null;
String propertyList = request.getParameter( ConfigManager.PROPERTY_LIST ); //$NON-NLS-1$
if ( propertyList == null )
{
// FIXME: this would be a bug !!
}
else
{
config = getConfiguration( pid, factoryPid );
Dictionary<String, Object> props = config.getProperties();
if ( props == null )
{
props = new Hashtable<>();
}
final MetaTypeServiceSupport mtss = getMetaTypeSupport();
final Map<String, MetatypePropertyDescriptor> adMap = ( mtss != null ) ? mtss.getAttributeDefinitionMap( config, null ) : new HashMap<>();
final StringTokenizer propTokens = new StringTokenizer( propertyList, "," ); //$NON-NLS-1$
final List<String> propsToKeep = new ArrayList<>();
while ( propTokens.hasMoreTokens() )
{
String propName = propTokens.nextToken();
String paramName = "action".equals(propName) //$NON-NLS-1$
|| ConfigManager.ACTION_DELETE.equals(propName)
|| ConfigManager.ACTION_APPLY.equals(propName)
|| ConfigManager.PROPERTY_LIST.equals(propName)
? '$' + propName : propName;
propsToKeep.add(propName);
PropertyDescriptor ad = adMap.get( propName );
// try to derive from current value
if (ad == null) {
Object currentValue = props.get( propName );
ad = MetaTypeSupport.createAttributeDefinition( propName, currentValue );
}
int attributeType = MetaTypeSupport.getAttributeType( ad );
if ( ad == null
|| ( ad.getCardinality() == 0 && ( attributeType == AttributeDefinition.STRING || attributeType == AttributeDefinition.PASSWORD ) ) )
{
String prop = request.getParameter( paramName );
if ( prop != null
&& ( attributeType != AttributeDefinition.PASSWORD || !MetaTypeSupport.PASSWORD_PLACEHOLDER_VALUE.equals( prop ) ) )
{
props.put( propName, prop );
}
}
else if ( ad.getCardinality() == 0 )
{
// scalar of non-string
String prop = request.getParameter( paramName );
if ( prop != null )
{
try
{
props.put( propName, MetaTypeSupport.toType( attributeType, prop ) );
}
catch ( NumberFormatException nfe )
{
// the value is put as a string, for example this could be a placeholder etc
props.put( propName, prop);
}
}
}
else
{
// array or vector of any type
Vector<Object> vec = new Vector<>();
boolean formatError = false;
String[] properties = request.getParameterValues( paramName );
if ( properties != null )
{
if ( attributeType == AttributeDefinition.PASSWORD )
{
MetaTypeSupport.setPasswordProps( vec, properties, props.get( propName ) );
}
else
{
for ( int i = 0; i < properties.length; i++ )
{
try
{
vec.add( MetaTypeSupport.toType( attributeType, properties[i] ) );
}
catch ( NumberFormatException nfe )
{
// the value is put as a string, for example this could be a placeholder etc
vec.add( properties[i] );
formatError = true;
}
}
}
}
// if a format error occurred revert to String!
if ( formatError )
{
Vector<Object> newVec = new Vector<Object>();
for(final Object v : vec)
{
newVec.add(v.toString());
}
vec = newVec;
}
// but ensure size (check for positive value since
// abs(Integer.MIN_VALUE) is still INTEGER.MIN_VALUE)
int maxSize = Math.abs( ad.getCardinality() );
if ( vec.size() > maxSize && maxSize > 0 )
{
vec.setSize( maxSize );
}
if ( ad.getCardinality() < 0 )
{
// keep the vector, but only add if not empty
if ( vec.isEmpty() )
{
props.remove( propName );
}
else
{
props.put( propName, vec );
}
}
else
{
// convert to an array
props.put( propName, MetaTypeSupport.toArray( formatError ? AttributeDefinition.STRING : attributeType, vec ) );
}
}
}
// remove the properties that are not specified in the request
final Dictionary<String, Object> updateProps = new Hashtable<>(props.size());
for ( Enumeration<String> e = props.keys(); e.hasMoreElements(); )
{
final String key = e.nextElement();
if ( propsToKeep.contains(key) )
{
updateProps.put(key, props.get(key));
}
}
final String location = request.getParameter(ConfigManager.LOCATION);
if ( location == null || location.trim().length() == 0 || ConfigManager.UNBOUND_LOCATION.equals(location) )
{
if ( config.getBundleLocation() != null )
{
config.setBundleLocation(null);
// workaround for Felix Config Admin 1.2.8 not clearing dynamic
// bundle location when clearing static bundle location. In
// this case we first set the static bundle location to the
// dynamic bundle location and then try to set both to null
if ( config.getBundleLocation() != null )
{
config.setBundleLocation( "??invalid:bundle/location" ); //$NON-NLS-1$
config.setBundleLocation( null );
}
}
} else
{
if ( config.getBundleLocation() == null || !config.getBundleLocation().equals(location) )
{
config.setBundleLocation(location);
}
}
config.update( updateProps );
}
// redirect to the new configuration (if existing)
return (config != null) ? config.getPid() : ""; //$NON-NLS-1$
}
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 void listConfigurations( JSONWriter jw, String pidFilter, String locale, Locale loc )
{
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(); )
{
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);
}
}
/**
* 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;
}
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() );
}
else
{
final Dictionary<String, Object> props = new Hashtable<>();
props.put( type, pid );
if ( filter.match( props ) )
{
pidMap.put( pid, ocd.getName() );
}
}
}
}
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[] listConfigurations(String filter) throws IOException, InvalidSyntaxException
{
return this.service.listConfigurations(filter);
}
}