blob: 2840ce34d24f1596777e9b19e1a8f71fb48c99fc [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.util.ArrayList;
import java.util.Collections;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Vector;
import java.util.regex.Pattern;
import javax.servlet.http.HttpServletRequest;
import org.apache.felix.webconsole.internal.misc.ServletSupport;
import org.apache.felix.webconsole.spi.ConfigurationHandler;
import org.apache.felix.webconsole.spi.ValidationException;
import org.osgi.framework.Constants;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.service.cm.Configuration;
import org.osgi.service.cm.ConfigurationAdmin;
import org.osgi.service.metatype.AttributeDefinition;
class ConfigAdminSupport {
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);
CONFIG_PROPERTIES_HIDE.add(ConfigurationAdmin.SERVICE_BUNDLELOCATION);
CONFIG_PROPERTIES_HIDE.add(ConfigurationAdmin.SERVICE_FACTORYPID);
CONFIG_PROPERTIES_HIDE.add(Constants.SERVICE_PID);
}
public static final Pattern NAMEHINT_PLACEHOLER_REGEXP = Pattern.compile("\\{([^\\{\\}]*)}");
private final ConfigurationAdmin service;
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 handlers The list of configuration handlers
*
* @throws ClassCastException if {@code service} is not a ConfigurationAdmin instances
*/
ConfigAdminSupport( final ServletSupport support,
final Object service,
final List<ConfigurationHandler> handlers ) {
this.servletSupport = support;
this.service = ( ConfigurationAdmin ) service;
this.configurationHandlers = handlers;
}
public ConfigJsonSupport getJsonSupport() {
return new ConfigJsonSupport(this.servletSupport, getMetaTypeSupport(), this.service, this.configurationHandlers);
}
MetaTypeServiceSupport getMetaTypeSupport() {
Object metaTypeService = servletSupport.getService( ConfigManager.META_TYPE_NAME );
if ( metaTypeService != null ) {
return new MetaTypeServiceSupport( servletSupport.getBundleContext(), metaTypeService );
}
return null;
}
private Map<String, Object> getAllowedValues(final Configuration config, final Dictionary<String, Object> props) throws IOException {
final List<String> allowedProperties = this.getJsonSupport().getPropertyNamesForForm(config.getFactoryPid(), config.getPid(), props);
final Map<String, Object> allowedValues = new HashMap<>();
final Dictionary<String, Object> origProps = config.getProperties();
if ( origProps != null ) {
for(final String name : Collections.list(origProps.keys())) {
if ( !allowedProperties.contains(name) ) {
allowedValues.put(name, origProps.get(name));
}
}
}
return allowedValues;
}
/**
* 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
* @throws IOException If a problem occurs
* @throws ValidationException If the configuration is not valid
*/
void applyConfiguration( final HttpServletRequest request, final String pid, final String[] propertyList, final boolean isUpdate )
throws ValidationException, IOException
{
final String factoryPid = request.getParameter( ConfigManager.FACTORY_PID );
final Configuration config = ConfigurationUtil.getOrCreateConfiguration( this.service, this.configurationHandlers, pid, factoryPid );
Dictionary<String, Object> props = config.getProperties();
if ( props == null ) {
props = new Hashtable<>();
}
// filter properties and keep filtered values
final Map<String, Object> allowedValues = getAllowedValues(config, props);
final MetaTypeServiceSupport mtss = getMetaTypeSupport();
final Map<String, MetatypePropertyDescriptor> adMap = ( mtss != null ) ? mtss.getAttributeDefinitionMap( config, null ) : new HashMap<>();
final List<String> propsToKeep = new ArrayList<>();
for(final String propName : propertyList)
{
final 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 );
}
final int attributeType = MetaTypeSupport.getAttributeType( ad );
if ( ad.getCardinality() == 0 && ( attributeType == AttributeDefinition.STRING || attributeType == AttributeDefinition.PASSWORD ) )
{
final String value = request.getParameter( paramName );
if ( value != null
&& ( attributeType != AttributeDefinition.PASSWORD || !MetaTypeSupport.PASSWORD_PLACEHOLDER_VALUE.equals( value ) ) )
{
props.put( propName, value );
}
}
else if ( ad.getCardinality() == 0 )
{
// scalar of non-string
final String value = request.getParameter( paramName );
if ( value != null )
{
try
{
props.put( propName, MetaTypeSupport.toType( attributeType, value ) );
}
catch ( final NumberFormatException nfe )
{
// the value is put as a string, for example this could be a placeholder etc
props.put( propName, value);
}
}
}
else
{
// array or vector of any type
Vector<Object> vec = new Vector<>();
boolean formatError = false;
final String[] values = request.getParameterValues( paramName );
if ( values != null )
{
if ( attributeType == AttributeDefinition.PASSWORD )
{
MetaTypeSupport.setPasswordProps( vec, values, props.get( propName ) );
}
else
{
for ( int i = 0; i < values.length; i++ )
{
try
{
vec.add( MetaTypeSupport.toType( attributeType, values[i] ) );
}
catch ( NumberFormatException nfe )
{
// the value is put as a string, for example this could be a placeholder etc
vec.add( values[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 );
}
// create array to compare
final String[] valueArray = new String[vec.size()];
for(int i=0; i<vec.size();i++)
{
valueArray[i] = vec.get(i).toString();
}
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 ) );
}
}
}
if ( !isUpdate )
{
// 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) && props.get(key) != null )
{
updateProps.put(key, props.get(key));
}
}
props = updateProps;
}
// call update handlers
for(final ConfigurationHandler h : this.configurationHandlers) {
h.updateConfiguration(factoryPid, pid, props);
}
// reapply allowed values
for(final Map.Entry<String, Object> allowed : allowedValues.entrySet()) {
props.put(allowed.getKey(), allowed.getValue());
}
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( props );
}
public void deleteConfiguration(final String pid) throws ValidationException, IOException {
// only delete if the PID is not our place holder
if ( !ConfigurationUtil.getPlaceholderPid().equals( pid ) ) {
final Configuration config = ConfigurationUtil.findConfiguration(this.service, pid);
if ( config != null ) {
for(final ConfigurationHandler h : this.configurationHandlers) {
h.deleteConfiguration(config.getFactoryPid(), config.getPid());
}
config.delete();
}
}
}
public Configuration findConfiguration(final String pid) {
return ConfigurationUtil.findConfiguration(this.service, pid);
}
public Configuration[] listConfigurations(final String filter) throws IOException, InvalidSyntaxException {
return this.service.listConfigurations(filter);
}
}