FELIX-1988 Apply 14.configmgr_plugin.patch by Valentin Valchev (thanks)

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@911447 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/internal/compendium/ConfigManager.java b/webconsole/src/main/java/org/apache/felix/webconsole/internal/compendium/ConfigManager.java
index 6d0655a..5da4c13 100644
--- a/webconsole/src/main/java/org/apache/felix/webconsole/internal/compendium/ConfigManager.java
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/internal/compendium/ConfigManager.java
@@ -31,21 +31,19 @@
 import java.util.Map;
 import java.util.Properties;
 import java.util.SortedMap;
-import java.util.SortedSet;
 import java.util.StringTokenizer;
 import java.util.TreeMap;
-import java.util.TreeSet;
 import java.util.Vector;
-import java.util.Map.Entry;
 
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-import org.apache.felix.webconsole.WebConsoleConstants;
-import org.apache.felix.webconsole.internal.Util;
+import org.apache.felix.webconsole.DefaultVariableResolver;
+import org.apache.felix.webconsole.WebConsoleUtil;
 import org.json.JSONArray;
 import org.json.JSONException;
+import org.json.JSONObject;
 import org.json.JSONWriter;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.Constants;
@@ -57,7 +55,6 @@
 import org.osgi.service.cm.ConfigurationAdmin;
 import org.osgi.service.cm.ManagedService;
 import org.osgi.service.cm.ManagedServiceFactory;
-import org.osgi.service.log.LogService;
 import org.osgi.service.metatype.AttributeDefinition;
 import org.osgi.service.metatype.ObjectClassDefinition;
 
@@ -67,32 +64,31 @@
  */
 public class ConfigManager extends ConfigManagerBase
 {
+    private static final String LABEL = "configMgr"; // was name
+    private static final String TITLE = "Configuration";
+    private static final String CSS[] = { "/res/ui/config.css" };
 
     private static final String PID_FILTER = "pidFilter";
-
-    public static final String NAME = "configMgr";
-
-    public static final String LABEL = "Configuration";
-
-    public static final String PID = "pid";
-
-    public static final String factoryPID = "factoryPid";
+    private static final String PID = "pid";
+    private static final String factoryPID = "factoryPid";
 
     private static final String PLACEHOLDER_PID = "[Temporary PID replaced by real PID upon save]";
 
+    // templates
+    private final String TEMPLATE;
 
-    public String getTitle()
+    /** Default constructor */
+    public ConfigManager()
     {
-        return LABEL;
+        super(LABEL, TITLE, CSS);
+
+        // load templates
+        TEMPLATE = readTemplateFile( "/templates/config.html" );
     }
 
-
-    public String getLabel()
-    {
-        return NAME;
-    }
-
-
+    /**
+     * @see javax.servlet.http.HttpServlet#doPost(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
+     */
     protected void doPost( HttpServletRequest request, HttpServletResponse response ) throws IOException
     {
         // needed multiple times below
@@ -133,7 +129,7 @@
                     redirect += "?" + PID_FILTER + "=" + pidFilter;
                 }
 
-                this.sendRedirect(request, response, redirect);
+                WebConsoleUtil.sendRedirect(request, response, redirect);
             }
 
             return;
@@ -148,7 +144,7 @@
         if ( request.getParameter( "unbind" ) != null )
         {
             config.setBundleLocation( null );
-            sendRedirect( request, response, config.getPid() );
+            WebConsoleUtil.sendRedirect( request, response, config.getPid() );
             return;
         }
 
@@ -227,7 +223,7 @@
                 }
                 pw.write("]");
             } catch (InvalidSyntaxException e) {
-                // this should not happend as we checked the filter before
+                // this should not happened as we checked the filter before
             }
             // nothing more to do
             return;
@@ -239,7 +235,7 @@
     /**
      * @see org.apache.felix.webconsole.AbstractWebConsolePlugin#renderContent(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
      */
-    public void renderContent( HttpServletRequest request, HttpServletResponse response ) throws IOException
+    protected void renderContent( HttpServletRequest request, HttpServletResponse response ) throws IOException
     {
 
         // extract the configuration pid from the request path
@@ -281,42 +277,20 @@
 
         final Locale loc = getLocale( request );
         final String locale = ( loc != null ) ? loc.toString() : null;
+        
 
-        final PrintWriter pw = response.getWriter();
-
-        final String appRoot = (String) request.getAttribute( WebConsoleConstants.ATTR_APP_ROOT );
-        pw.println( "<script src='" + appRoot + "/res/ui/configmanager.js' language='JavaScript'></script>" );
-
-        pw.println( "<table class='content' cellpadding='0' cellspacing='0' width='100%'>" );
-
-        if ( ca == null )
+        JSONObject json = new JSONObject();
+        try
         {
-            pw.println( "<tr class='content' id='configField'>" );
-            pw.println( "<td class='content'>&nbsp;</th>" );
-            pw.println( "<td class='content'>" );
-            pw.print( "Configuration Admin Service not available" );
-            pw.println( "</td>" );
-            pw.println( "</tr>" );
+            json.put("status", ca != null ? Boolean.TRUE : Boolean.FALSE);
+            listConfigurations(json, ca, pidFilter, locale);
+            listFactoryConfigurations(json, pidFilter, locale);
         }
-        else
+        catch (JSONException e)
         {
-            pw.println( "<tr class='content' id='configField'>" );
-            pw.println( "<td class='content'>Configurations</th>" );
-            pw.println( "<td class='content'>" );
-            this.listConfigurations( pw, ca, pidFilter, locale );
-            pw.println( "</td>" );
-            pw.println( "</tr>" );
-
-            pw.println( "<tr class='content' id='factoryField'>" );
-            pw.println( "<td class='content'>Factory Configurations</th>" );
-            pw.println( "<td class='content'>" );
-            this.listFactoryConfigurations( pw, ca, pidFilter, locale );
-            pw.println( "</td>" );
-            pw.println( "</tr>" );
+            throw new IOException(e.toString());
         }
-
-        pw.println( "</table>" );
-
+        
         // if a configuration is addressed, display it immediately
         final Configuration config;
         if ( request.getParameter( "create" ) != null && pid != null )
@@ -329,18 +303,13 @@
             config = getConfiguration( ca, pid );
         }
 
-        if ( pid != null )
-        {
-            Util.startScript( pw );
 
-            pw.println( "var configuration=" );
-            printConfigurationJson( pw, pid, config, pidFilter, locale );
-            pw.println( ";" );
+        // prepare variables
+        DefaultVariableResolver vars = ( ( DefaultVariableResolver ) WebConsoleUtil.getVariableResolver( request ) );
+        vars.put( "__data__", json.toString() );
+        vars.put( "selectedPid", pid != null ? pid : "");
 
-            pw.println( "displayConfigForm(configuration);" );
-
-            Util.endScript( pw );
-        }
+        response.getWriter().print(TEMPLATE);
     }
 
 
@@ -375,33 +344,57 @@
     }
 
 
-    private void listConfigurations( PrintWriter pw, ConfigurationAdmin ca, String pidFilter, String locale )
+    private final void listFactoryConfigurations(JSONObject json, String pidFilter,
+        String locale)
+    {
+        try
+        {
+            Map optionsFactory = getServices(ManagedServiceFactory.class.getName(),
+                pidFilter, locale, true);
+            addMetaTypeNames(optionsFactory, getFactoryPidObjectClasses(locale),
+                pidFilter, ConfigurationAdmin.SERVICE_FACTORYPID);
+            for (Iterator i = optionsFactory.keySet().iterator(); i.hasNext();)
+            {
+                String id = (String) i.next();
+                Object name = optionsFactory.get(id);
+                json.append("fpids", new JSONObject().put("id", id).put("name", name));
+            }
+        }
+        catch (Exception e)
+        {
+            log("listFactoryConfigurations: Unexpected problem encountered", e);
+        }
+    }
+
+    private final void listConfigurations(JSONObject json, ConfigurationAdmin ca,
+        String pidFilter, String locale)
     {
         try
         {
             // start with ManagedService instances
-            SortedMap optionsPlain = getServices( ManagedService.class.getName(), pidFilter, locale, true );
+            Map optionsPlain = getServices(ManagedService.class.getName(), pidFilter,
+                locale, true);
 
             // next are the MetaType informations without ManagedService
-            addMetaTypeNames( optionsPlain, getPidObjectClasses( locale ), pidFilter, Constants.SERVICE_PID );
+            addMetaTypeNames(optionsPlain, getPidObjectClasses(locale), pidFilter,
+                Constants.SERVICE_PID);
 
             // add in existing configuration (not duplicating ManagedServices)
-            Configuration[] cfgs = ca.listConfigurations( pidFilter );
-            for ( int i = 0; cfgs != null && i < cfgs.length; i++ )
+            Configuration[] cfgs = ca.listConfigurations(pidFilter);
+            for (int i = 0; cfgs != null && i < cfgs.length; i++)
             {
 
-                // ignore configuration object if an entry already exists in the
-                // map
+                // ignore configuration object if an entry already exists in the map
                 String pid = cfgs[i].getPid();
-                if ( optionsPlain.containsKey( pid ) )
+                if (optionsPlain.containsKey(pid))
                 {
                     continue;
                 }
 
-                // insert and entry for the pid
-                ObjectClassDefinition ocd = this.getObjectClassDefinition( cfgs[i], locale );
+                // insert and entry for the PID
+                ObjectClassDefinition ocd = this.getObjectClassDefinition(cfgs[i], locale);
                 String name;
-                if ( ocd != null )
+                if (ocd != null)
                 {
                     name = ocd.getName() + " (";
                     name += pid + ")";
@@ -411,35 +404,22 @@
                     name = pid;
                 }
 
-                optionsPlain.put( pid, name );
+                optionsPlain.put(pid, name);
             }
 
-            printOptionsForm( pw, optionsPlain, "configSelection_pid", "configure", "Configure" );
+            for (Iterator i = optionsPlain.keySet().iterator(); i.hasNext();)
+            {
+                String id = (String) i.next();
+                Object name = optionsPlain.get(id);
+                json.append("pids", new JSONObject().put("id", id).put("name", name));
+            }
         }
-        catch ( Exception e )
+        catch (Exception e)
         {
-            getLog().log( LogService.LOG_ERROR, "listConfigurations: Unexpected problem encountered", e );
+            log("listConfigurations: Unexpected problem encountered", e);
         }
     }
 
-
-    private void listFactoryConfigurations( PrintWriter pw, ConfigurationAdmin ca, String pidFilter,
-        String locale )
-    {
-        try
-        {
-            SortedMap optionsFactory = getServices( ManagedServiceFactory.class.getName(), pidFilter, locale, true );
-            addMetaTypeNames( optionsFactory, getFactoryPidObjectClasses( locale ), pidFilter,
-                ConfigurationAdmin.SERVICE_FACTORYPID );
-            printOptionsForm( pw, optionsFactory, "configSelection_factory", "create", "Create" );
-        }
-        catch ( Exception e )
-        {
-            getLog().log( LogService.LOG_ERROR, "listFactoryConfigurations: Unexpected problem encountered", e );
-        }
-    }
-
-
     private SortedMap getServices( String serviceClass, String serviceFilter, String locale, boolean ocdRequired )
         throws InvalidSyntaxException
     {
@@ -487,6 +467,7 @@
             }
             catch ( InvalidSyntaxException not_expected )
             {
+                /* filter is correct */
             }
         }
 
@@ -506,35 +487,6 @@
     }
 
 
-    private void printOptionsForm( PrintWriter pw, SortedMap options, String formId, String submitMethod,
-        String submitLabel )
-    {
-        SortedSet set = new TreeSet();
-        for ( Iterator ei = options.entrySet().iterator(); ei.hasNext(); )
-        {
-            Entry entry = ( Entry ) ei.next();
-            set.add(entry.getValue().toString() + Character.MAX_VALUE + entry.getKey().toString());
-        }
-
-        pw.println( "<select class='select' name='pid' id='" + formId + "' onChange='" + submitMethod + "();'>" );
-        for ( Iterator ei = set.iterator(); ei.hasNext(); )
-        {
-            String entry = ( String ) ei.next();
-            int sep = entry.indexOf( Character.MAX_VALUE );
-            String value = entry.substring( 0, sep );
-            String key = entry.substring( sep + 1 );
-            pw.print( "<option value='" + key + "'>" );
-            pw.print( value );
-            pw.println( "</option>" );
-        }
-        pw.println( "</select>" );
-        pw.println( "&nbsp;&nbsp;" );
-        pw.println( "<input class='submit' type='button' value='" + submitLabel + "' onClick='" + submitMethod
-            + "();' />" );
-
-    }
-
-
     private void printConfigurationJson( PrintWriter pw, String pid, Configuration config, String pidFilter,
         String locale )
     {
@@ -801,7 +753,7 @@
             // only delete if the PID is not our place holder
             if ( !PLACEHOLDER_PID.equals( pid ) )
             {
-                getLog().log( LogService.LOG_INFO, "applyConfiguration: Deleting configuration " + pid );
+                log( "applyConfiguration: Deleting configuration " + pid );
                 Configuration config = ca.getConfiguration( pid, null );
                 config.delete();
             }
@@ -861,7 +813,7 @@
                         {
                             try
                             {
-                                props.put( propName, this.toType( ad.getType(), prop ) );
+                                props.put( propName, toType( ad.getType(), prop ) );
                             }
                             catch ( NumberFormatException nfe )
                             {
@@ -881,7 +833,7 @@
                             {
                                 try
                                 {
-                                    vec.add( this.toType( ad.getType(), properties[i] ) );
+                                    vec.add( toType( ad.getType(), properties[i] ) );
                                 }
                                 catch ( NumberFormatException nfe )
                                 {
@@ -906,7 +858,7 @@
                         else
                         {
                             // convert to an array
-                            props.put( propName, this.toArray( ad.getType(), vec ) );
+                            props.put( propName, toArray( ad.getType(), vec ) );
                         }
                     }
                 }
@@ -920,7 +872,7 @@
     }
 
 
-    private Configuration getConfiguration( ConfigurationAdmin ca, String pid, String factoryPid ) throws IOException
+    private static final Configuration getConfiguration( ConfigurationAdmin ca, String pid, String factoryPid ) throws IOException
     {
         if ( factoryPid != null && ( pid == null || pid.equals( PLACEHOLDER_PID ) ) )
         {
@@ -935,7 +887,7 @@
      * @throws NumberFormatException If the value cannot be converted to
      *      a number and type indicates a numeric type
      */
-    private Object toType( int type, String value )
+    private static final Object toType( int type, String value )
     {
         switch ( type )
         {
@@ -964,7 +916,7 @@
     }
 
 
-    private Object toArray( int type, Vector values )
+    private static final Object toArray( int type, Vector values )
     {
         int size = values.size();
 
@@ -979,20 +931,28 @@
         {
             case AttributeDefinition.BOOLEAN:
                 array = new boolean[size];
+                break;
             case AttributeDefinition.BYTE:
                 array = new byte[size];
+                break;
             case AttributeDefinition.CHARACTER:
                 array = new char[size];
+                break;
             case AttributeDefinition.DOUBLE:
                 array = new double[size];
+                break;
             case AttributeDefinition.FLOAT:
                 array = new float[size];
+                break;
             case AttributeDefinition.LONG:
                 array = new long[size];
+                break;
             case AttributeDefinition.INTEGER:
                 array = new int[size];
+                break;
             case AttributeDefinition.SHORT:
                 array = new short[size];
+                break;
             default:
                 // unexpected, but assume string
                 array = new String[size];
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/internal/compendium/ConfigManagerBase.java b/webconsole/src/main/java/org/apache/felix/webconsole/internal/compendium/ConfigManagerBase.java
index 8943a25..fea1f37 100644
--- a/webconsole/src/main/java/org/apache/felix/webconsole/internal/compendium/ConfigManagerBase.java
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/internal/compendium/ConfigManagerBase.java
@@ -25,7 +25,8 @@
 
 import javax.servlet.http.HttpServletRequest;
 
-import org.apache.felix.webconsole.internal.BaseWebConsolePlugin;
+import org.apache.felix.webconsole.SimpleWebConsolePlugin;
+import org.apache.felix.webconsole.internal.OsgiManagerPlugin;
 import org.osgi.framework.Bundle;
 import org.osgi.service.cm.Configuration;
 import org.osgi.service.cm.ConfigurationAdmin;
@@ -39,11 +40,16 @@
  * The <code>ConfigManagerBase</code> is the base class for the
  * ConfigurationAdmin support in the web console. It provides various helper
  * methods mostly with respect to using the MetaTypeService to access
- * configuration descriptiorns.
+ * configuration descriptions.
  */
-abstract class ConfigManagerBase extends BaseWebConsolePlugin
+abstract class ConfigManagerBase extends SimpleWebConsolePlugin implements OsgiManagerPlugin
 {
 
+    ConfigManagerBase(String label, String title, String[] css)
+    {
+        super(label, title, css);
+    }
+
     private static final long serialVersionUID = -6691093960031418130L;
 
     private static final String CONFIGURATION_ADMIN_NAME = ConfigurationAdmin.class.getName();
@@ -67,6 +73,9 @@
      * Returns a map of PIDs and providing bundles of MetaType information. The
      * map is indexed by PID and the value of each entry is the bundle providing
      * the MetaType information for that PID.
+     *
+     * @param locale The name of the locale to get the meta data for.
+     * @return see the method description 
      */
     protected Collection getPidObjectClasses( final String locale )
     {
@@ -79,6 +88,9 @@
      * information. The map is indexed by factory PID and the value of each
      * entry is the bundle providing the MetaType information for that factory
      * PID.
+     *
+     * @param locale The name of the locale to get the meta data for.
+     * @return see the method description 
      */
     protected Collection getFactoryPidObjectClasses( final String locale )
     {
@@ -87,13 +99,13 @@
 
 
     /**
-     * Returns the <code>ObjectClassDefinition</code> objects for the ids
+     * Returns the <code>ObjectClassDefinition</code> objects for the IDs
      * returned by the <code>idGetter</code>. Depending on the
      * <code>idGetter</code> implementation this will be for factory PIDs or
      * plain PIDs.
      *
      * @param idGetter The {@link IdGetter} used to get the list of factory PIDs
-     *          or PIDs from <code>MetaTypeInformation</code> objetcs.
+     *          or PIDs from <code>MetaTypeInformation</code> objects.
      * @param locale The name of the locale to get the object class definitions
      *          for.
      */
diff --git a/webconsole/src/main/resources/res/ui/config.css b/webconsole/src/main/resources/res/ui/config.css
new file mode 100644
index 0000000..8f96a81
--- /dev/null
+++ b/webconsole/src/main/resources/res/ui/config.css
@@ -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.
+ */
+
+/* IE hack */
+select { width: 50em }
+html>select { width: auto; min-width: 50em }
+#config_content table th {
+	width:20%
+}
+#config_content {
+	min-width: 80em;
+	width:100%;
+	margin:0;
+	padding:0
+}
+.multiselect {
+    display: block;
+}
diff --git a/webconsole/src/main/resources/res/ui/config.js b/webconsole/src/main/resources/res/ui/config.js
new file mode 100644
index 0000000..96f19f1
--- /dev/null
+++ b/webconsole/src/main/resources/res/ui/config.js
@@ -0,0 +1,507 @@
+/*
+ * 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.
+ */
+
+
+function configure() {
+    var select = document.getElementById('configSelection_pid');
+    var pid = select.options[select.selectedIndex].value;
+    var parm = pluginRoot + '/' + pid;
+	$.post(parm, null, displayConfigForm, "json");
+}
+
+
+function create() {
+    var select = document.getElementById('configSelection_factory');
+    var pid = select.options[select.selectedIndex].value;
+    var parm = pluginRoot + '/' + pid + '?create=true';
+	$.post(parm, null, displayConfigForm, "json");
+}
+
+function displayConfigForm(obj) {
+	var span1 = document.getElementById('configField');
+	var span2 = document.getElementById('factoryField');
+	if (!span1 && !span2) {
+		return;
+	} 
+    
+    var parent = span1 ? span1.parentNode : span2.parentNode;
+    
+    clearChildren( parent );
+    
+    if (span1) {
+        parent.appendChild( span1 );
+    }
+    if (span2) {
+        parent.appendChild( span2 );
+    }
+    
+    var trEl = tr( null );
+    var tdEl = createElement( "th", null, { colSpan: "2" } );
+    addText( tdEl, obj.title );
+    trEl.appendChild( tdEl );
+    parent.appendChild( trEl );
+
+    trEl = tr( );
+    parent.appendChild( trEl );
+    
+    tdEl = td( );
+    addText( tdEl, "\u00a0" );
+    trEl.appendChild( tdEl );
+    
+    tdEl = td( );
+    trEl.appendChild( tdEl );
+    
+    var formEl = createElement( "form", null, {
+            method: "POST",
+            action: pluginRoot + "/" + obj.pid
+        });
+    tdEl.appendChild( formEl );
+    
+    var inputEl = createElement( "input", null, {
+            type: "hidden",
+            name: "apply",
+            value: "true"
+        });
+    formEl.appendChild( inputEl );
+    
+    // add the factory PID as a hidden form field if present
+    if (obj.factoryPid)
+    {
+        inputEl = createElement( "input", null, {
+                type: "hidden",
+                name: "factoryPid",
+                value: obj.factoryPid
+            });
+        formEl.appendChild( inputEl );
+    }
+    
+    // add the PID filter as a hidden form field if present
+    if (obj.pidFilter)
+    {
+        inputEl = createElement( "input", null, {
+                type: "hidden",
+                name: "pidFilter",
+                value: obj.pidFilter
+            });
+        formEl.appendChild( inputEl );
+    }
+    
+    inputEl = createElement( "input", null, {
+            type: "hidden",
+            name: "action",
+            value: "ajaxConfigManager"
+        });
+    formEl.appendChild( inputEl );
+    
+    var tableEl = createElement( "table", null, {
+            border: 0,
+            width: "100%"
+        });
+    formEl.appendChild( tableEl );
+    
+    var bodyEl = createElement( "tbody" );
+    tableEl.appendChild( bodyEl );
+    
+    if (obj.description)
+    {
+        trEl = tr( );
+        tdEl = td( null, { colSpan: "2" } );
+        addText( tdEl, obj.description );
+        trEl.appendChild( tdEl );
+        bodyEl.appendChild( trEl );
+    }
+    
+    if (obj.propertylist == 'properties')
+    {
+        printTextArea(bodyEl, obj.properties);
+    }
+    else
+    {
+        printForm(bodyEl, obj);
+    }
+    
+    trEl = tr( );
+    bodyEl.appendChild( trEl );
+    
+    tdEl = td( );
+    addText( tdEl, "\u00a0" );
+    trEl.appendChild( tdEl );
+    
+    tdEl = td( );
+    trEl.appendChild( tdEl );
+
+    // define this TD as innerHTML otherwise the onClick event handler
+    // of the Delete button is not accepted by IE6 (!)...    
+    var innerHTML = '<input type="submit" name="submit" value="'+i18n.save+'" />';
+    innerHTML += '&nbsp;&nbsp;&nbsp;';
+    innerHTML += '<input type="reset" name="reset" value="'+i18n.reset+'" />';
+    innerHTML += '&nbsp;&nbsp;&nbsp;';
+    innerHTML += '<input type="submit" name="delete" value="'+i18n.del+'" onClick="return confirmDelete(\'' + obj.pid + '\', \'' + obj.bundleLocation + '\');"/>';
+    tdEl.innerHTML = innerHTML;
+
+    printConfigurationInfo(parent, obj);
+	initStaticWidgets($(parent));
+}
+
+function printTextArea(/* Element */ parent, props )
+{
+    
+    var propsValue = "";
+    for (var key in props)
+    {
+        propsValue += key + ' =  ' + props[key] + '\r\n';
+    }
+
+    parent.appendChild(
+        tr( null, null, [
+            td( null, null, [
+                text( i18n.props_title )
+            ]),
+            td( null, { style: { width: "99%" } }, [
+                createElement( "textarea", null, {
+                        name: "properties",
+                        style: { height: "50%", width: "99%" }
+                    }, [ text( propsValue ) ] ),
+                text( i18n.props_enter )
+            ])
+        ])
+    );        
+}
+
+function printForm( /* Element */ parent, obj ) {
+    var propList;
+    for (var idx in obj.propertylist)
+    {
+        var prop = obj.propertylist[idx];
+        var attr = obj[prop];
+  
+        var trEl = tr( null, null, [
+                td( null, null, [ text( attr.name ) ] )
+            ]);
+        parent.appendChild( trEl );
+
+        var tdEl = td( null, { style: { width: "99%" } } );
+        trEl.appendChild( tdEl );
+  
+        if (attr.value != undefined)
+        {
+            // check is required to also handle empty strings, 0 and false
+            tdEl.appendChild( createInput( prop, attr.value, attr.type, '99%' ) );
+            tdEl.appendChild( createElement( "br" ) );
+        }
+        else if (typeof(attr.type) == 'object')
+        {
+        	// assume attr.values and multiselect
+        	createMultiSelect( tdEl, prop, attr.values, attr.type, '99%' );
+            tdEl.appendChild( createElement( "br" ) );
+        }
+        else if (attr.values.length == 0)
+        {
+            tdEl.appendChild( createSpan( prop, "", attr.type ) );
+        }
+        else
+        {
+            for (var i=0;i<attr.values.length;i++)
+            {
+                tdEl.appendChild( createSpan( prop, attr.values[i], attr.type ) );
+            }
+        }
+        
+        if (attr.description)
+        {
+            addText( tdEl, attr.description );
+        }
+        
+        if (propList) {
+            propList += ',' + prop;
+        } else {
+            propList = prop;
+        }
+    }
+    
+    parent.appendChild( createElement( "input", null, {
+            type: "hidden",
+            name: "propertylist",
+            value: propList
+        })
+    );
+    // FIX for IE6 and above: checkbox can only be checked after it is in the DOM
+    $(".checked_box").attr("checked", true).removeClass("checked_box");
+}
+
+function printConfigurationInfo( /* Element */ parent, obj )
+{
+    parent.appendChild( tr( null, null, [
+            createElement( "th", null, { colSpan: "2" }, [
+                text( i18n.cfg_title )
+            ])
+        ])
+    );
+    
+    parent.appendChild( tr( null, null, [
+            td( null, null, [
+                text( i18n.pid )
+            ]),
+            td( null, null, [
+                text( obj.pid )
+            ])
+        ])
+    );
+
+    if (obj.factoryPid)
+    {
+        parent.appendChild( tr( null, null, [
+                td( null, null, [
+                    text( i18n.fpid )
+                ]),
+                td( null, null, [
+                    text( obj.factoryPid )
+                ])
+            ])
+        );
+    }
+    
+    var binding = obj.bundleLocation;
+    if (!binding)
+    {
+        binding = i18n.unbound;
+    }
+    
+    parent.appendChild( tr( null, null, [
+            td( null, null, [
+                text( i18n.binding )
+            ]),
+            td( null, null, [
+                text( binding )
+            ])
+        ])
+    );
+    
+    if (obj.bundleLocation)
+    {         
+        var form = createElement( "form", null, {
+                        method: "POST",
+                        action: pluginRoot + "/" + obj.pid
+                    });
+
+        // define this form contents as innerHTML otherwise the onClick
+        // event handler of the Unbind button is not accepted by IE6 (!)...
+        var formInner = '<input type="hidden" name="unbind" value="true"/>';
+        formInner += '<input type="submit" name="submit" value="'+i18n.unbind_btn+'" class="ui-state-default ui-corner-all" title="'+i18n.unbind_tip+'"  onClick="return confirmUnbind(\'' + obj.pid + '\', \'' + obj.bundleLocation + '\');"/>';
+        form.innerHTML = formInner;
+    
+        parent.appendChild( tr( null, null, [
+                td( null, null, [
+                    text( " " )
+                ]),
+                td( null, null, [ form ] )
+            ])
+        );
+    }
+	//$(form).ready(initStaticWidgets);
+}
+
+
+var spanCounter = 0;
+/* Element */ function createSpan(prop, value, type) {
+    spanCounter++;
+    var newId = prop + spanCounter;
+    
+    var addButton = createElement("input", null,
+    		{   type: "button",
+    	        style: {width : "5%"},
+    	        value: "+"
+    	    }
+      );
+    $(addButton).click(function() {addValue(prop, newId)});
+    var remButton = createElement("input", null,
+    		{   type: "button",
+    	        style: {width : "5%"},
+    	        value: "-"
+    	    }
+      );
+    $(remButton).click(function() {removeValue(newId)});
+    var spanEl = createElement( "span", null, { id: newId }, [
+        createInput( prop, value, type, '89%' ), addButton, remButton,
+        createElement("br")
+    ]);
+    
+    return spanEl;
+}
+
+/* Element */ function createInput(prop, value, type, width) {
+    if (type == 11) { // AttributeDefinition.BOOLEAN
+
+        var inputEl = createElement( "input", null, {
+                type: "checkbox",
+                name: prop,
+                value: "true"
+            });
+            
+        if (value && typeof(value) != "boolean")
+        {
+            value = value.toString().toLowerCase() == "true";
+        }
+        if (value)
+        {
+        	$(inputEl).addClass("checked_box");
+        }
+        var hiddenEl = createElement( "input", null, {
+            type: "hidden",
+            name: prop,
+            value: "false"
+        });
+        var divEl = createElement("div");
+        divEl.appendChild(inputEl);
+        divEl.appendChild(hiddenEl);
+        return divEl;
+        
+    } else if (typeof(type) == "object") { // predefined values
+    
+        var selectEl = createElement( "select", null, {
+                name: prop,
+                style: { width: width }
+            });
+
+    	var labels = type.labels;
+    	var values = type.values;
+        for (var idx in labels) {
+            var optionEl = createElement( "option", null, {
+                    value: values[idx]
+                }, [ text( labels[idx] ) ]);
+                
+            if (value == values[idx])
+            {
+                optionEl.setAttribute( "selected", true );
+            }
+            selectEl.appendChild( optionEl );
+    	}
+        
+    	return selectEl;
+        
+    } else { // Simple 
+        return createElement( "input", null, {
+                type: "text",
+                name: prop,
+                value: value,
+                style: { width: width }
+            });
+    }
+}
+
+function createMultiSelect(/* Element */ parent, prop, values, options, width) {
+    // convert value list into 'set'
+    var valueSet = new Object();
+    for (var idx in values) {
+    	valueSet[ values[idx] ] = true;
+    }
+    
+   	var labels = options.labels;
+   	var values = options.values;
+   	for (var idx in labels) {
+    
+        var inputEl = createElement( "input", null, {
+                type: "checkbox",
+                name: prop,
+                value: values[idx] 
+            });
+    
+        if (valueSet[ values[idx] ])
+        {
+            inputEl.setAttribute( "checked", true );
+        }
+        
+        var labelEl = createElement( "label", "multiselect" );
+        labelEl.appendChild( inputEl );
+        addText( labelEl, labels[idx] );
+        
+        parent.appendChild( labelEl );
+   	}
+}
+
+
+function addValue(prop, vidx)
+{
+    var span = document.getElementById(vidx);
+    if (!span)
+    {
+        return;
+    }
+    var newSpan = createSpan(prop, '');
+    span.parentNode.insertBefore(newSpan, span.nextSibling);
+    // FIX for IE6 and above: checkbox can only be checked after it is in the DOM
+    $(".checked_box").attr("checked", true).removeClass("checked_box");
+	//$(span).ready(initStaticWidgets);
+}
+
+function removeValue(vidx)
+{
+    var span = document.getElementById(vidx);
+    if (!span)
+    {
+        return;
+    }
+    span.parentNode.removeChild(span);
+}
+
+function configConfirm(/* String */ message, /* String */ title, /* String */ location)
+{
+    var message = i18n.del_ask;
+    
+    if (title) {
+        message += "\r\n" + i18n.del_config + title;
+    }
+    if (location) {
+        message += "\r\n" + i18n.del_bundle + location;
+    }
+    
+    return confirm(message);
+}
+
+function confirmDelete(/* String */ title, /* String */ location)
+{
+    return configConfirm(i18n.unbind_ask, title, location);
+}
+
+function confirmUnbind(/* String */ title, /* String */ location)
+{
+    return configConfirm(i18n.unbind_ask, title, location);
+}
+
+function addOption(list, target) 
+{
+	var html = "";
+	for (i in list) {
+		var sel = list[i].id == selectedPid ? '" selected="selected' : '';
+		html += '<option value="' + list[i].id + sel + '">' + list[i].name + '</option>';
+	}
+	if (html) target.html(html);
+}
+
+var configSelection_pid = false;
+var configSelection_factory = false;
+$(document).ready(function() {
+	configSelection_pid = $('#configSelection_pid');
+	configSelection_factory = $('#configSelection_factory');
+	$(".statline").html(configData.status ? i18n.stat_ok : i18n.stat_missing);
+	$("#config_content").css("display", configData.status ? "block" : "none");
+	if (configData.status) {
+		addOption(configData.pids, $("#configSelection_pid"));
+		addOption(configData.fpids, $("#configSelection_factory"));
+	}
+	if (selectedPid) configure();
+});
\ No newline at end of file
diff --git a/webconsole/src/main/resources/templates/config.html b/webconsole/src/main/resources/templates/config.html
new file mode 100644
index 0000000..6c4b3d2
--- /dev/null
+++ b/webconsole/src/main/resources/templates/config.html
@@ -0,0 +1,59 @@
+<script type="text/javascript" src="res/ui/config.js"></script>
+<script type="text/javascript">
+// <![CDATA[
+// data
+var configData = ${__data__};
+var selectedPid = '${selectedPid}';
+var i18n = { // i18n
+	stat_ok      : '${config.status.ok}', // "Configuration Admin Service is running.";
+	stat_missing : '${config.status.missing}', //"Configuration Admin Service is not installed/running."
+	save         : '${save}',
+	reset        : '${reset}',
+	del          : '${delete}',
+	props_title  : '${config.properties}',
+	props_enter  : '${config.properties.enter}', // "Enter Name-Value pairs of configuration properties"
+	cfg_title    : '${config.info.title}', // "Configuration Information"
+	pid          : '${config.info.pid}', // "Persistent Identity (PID)";
+	fpid         : '${config.info.fpid}', // "Factory Peristent Identifier (Factory PID)";
+	binding      : '${config.info.binding}', // "Configuration Binding";
+	unbound      : '${config.info.unbound}', // "Unbound or new configuration"; 
+	unbind_btn   : '${config.unbind.btn}', // "Unbind;
+	unbind_tip   : '${config.unbind.tip}', // "Unbind Configuration from Bundle"
+	del_ask      : '${config.del.ask}', // "Are you sure to delete this configuration ?";
+	del_config   : '${config.del.config}', // "Configuration: ";
+	del_bundle   : '${config.del.bundle}', // "Bundle: ";
+	unbind_ask   : '${config.unbind.ask}', // "Are you sure to unbind this configuration ?"
+	'xx' : '${config.form.properties}',
+	'xx' : '${config.form.properties}',
+
+}
+// ]]>
+</script>
+
+<!-- status line -->
+<p class="statline">&nbsp;</p>
+
+<div id="config_content" >
+	<table class="nicetable">
+	<tr id="configField">
+		<td>Configurations</td>
+		<td>
+			<select name="pid" id="configSelection_pid" onchange="configure();">
+				<option>&nbsp;</option>
+			</select>
+			&nbsp;
+			<button onclick="configure();">Configure</button>
+		</td>
+	</tr>
+	<tr id="factoryField">
+		<td>Factory Configurations</td>
+		<td>
+			<select name="pid" id="configSelection_factory" onchange="create();">
+				<option>&nbsp;</option>
+			</select>
+			&nbsp;
+			<button onclick="create();">Create</button>
+		</td>
+	</tr>
+	</table>
+</div>