Fixed FELIX-4677 : Web Console Configuration plugin is confusing about default values & optionality of elements
https://issues.apache.org/jira/browse/FELIX-4677


git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1640155 13f79535-47bb-0310-9956-ffa450edef68
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 a52d684..bd1aa3c 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
@@ -21,11 +21,13 @@
 
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.util.ArrayList;
 import java.util.Dictionary;
 import java.util.Enumeration;
 import java.util.HashMap;
 import java.util.Hashtable;
 import java.util.Iterator;
+import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.SortedMap;
@@ -183,6 +185,7 @@
             final MetaTypeServiceSupport mtss = getMetaTypeSupport();
             final Map adMap = ( mtss != null ) ? mtss.getAttributeDefinitionMap( config, null ) : new HashMap();
             final StringTokenizer propTokens = new StringTokenizer( propertyList, "," ); //$NON-NLS-1$
+            final List propsToKeep = new ArrayList();
             while ( propTokens.hasMoreTokens() )
             {
                 String propName = propTokens.nextToken();
@@ -191,6 +194,7 @@
                     || ConfigManager.ACTION_APPLY.equals(propName)
                     || ConfigManager.PROPERTY_LIST.equals(propName) 
                     ? '$' + propName : propName;
+                propsToKeep.add(propName);
                 
                 PropertyDescriptor ad = (PropertyDescriptor) adMap.get( propName );
 
@@ -283,6 +287,16 @@
                     }
                 }
             }
+            
+            // remove the properties that are not specified in the request
+            for ( Enumeration e = props.keys(); e.hasMoreElements(); )
+            {
+                final Object key = e.nextElement();
+                if ( !propsToKeep.contains(key) )
+                {
+                    props.remove(key);
+                }
+            }
 
             config.update( props );
         }
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/internal/configuration/MetaTypeServiceSupport.java b/webconsole/src/main/java/org/apache/felix/webconsole/internal/configuration/MetaTypeServiceSupport.java
index 5abbe9e..149c256 100644
--- a/webconsole/src/main/java/org/apache/felix/webconsole/internal/configuration/MetaTypeServiceSupport.java
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/internal/configuration/MetaTypeServiceSupport.java
@@ -17,9 +17,13 @@
 package org.apache.felix.webconsole.internal.configuration;
 
 
+import java.util.Arrays;
+import java.util.Collections;
 import java.util.Dictionary;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
+
 import org.json.JSONException;
 import org.json.JSONWriter;
 import org.osgi.framework.Bundle;
@@ -249,7 +253,7 @@
             {
                 for ( int i = 0; i < ad.length; i++ )
                 {
-                    adMap.put( ad[i].getID(), new MetatypePropertyDescriptor( ad[i] ) );
+                    adMap.put( ad[i].getID(), new MetatypePropertyDescriptor( ad[i], false ) );
                 }
             }
         }
@@ -267,6 +271,8 @@
         }
 
         AttributeDefinition[] ad = ocd.getAttributeDefinitions( ObjectClassDefinition.ALL );
+        AttributeDefinition[] optionalArray = ocd.getAttributeDefinitions( ObjectClassDefinition.OPTIONAL );
+        List/*<AttributeDefinition>*/ optional = optionalArray == null ? Collections.EMPTY_LIST : Arrays.asList( optionalArray ); 
         if ( ad != null )
         {
             json.key( "properties" ).object(); //$NON-NLS-1$
@@ -275,7 +281,8 @@
                 final AttributeDefinition adi = ad[i];
                 final String attrId = adi.getID();
                 json.key( attrId );
-                attributeToJson( json, new MetatypePropertyDescriptor( adi ), props.get( attrId ) );
+                boolean isOptional = optional.contains( adi );
+                attributeToJson( json, new MetatypePropertyDescriptor( adi, isOptional ), props.get( attrId ) );
             }
             json.endObject();
         }
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/internal/configuration/MetaTypeSupport.java b/webconsole/src/main/java/org/apache/felix/webconsole/internal/configuration/MetaTypeSupport.java
index f58d4bb..bcfc6ff 100644
--- a/webconsole/src/main/java/org/apache/felix/webconsole/internal/configuration/MetaTypeSupport.java
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/internal/configuration/MetaTypeSupport.java
@@ -102,6 +102,10 @@
 
         json.key( "name" ); //$NON-NLS-1$
         json.value( ad.getName() );
+        json.key( "optional" ); //$NON-NLS-1$
+        json.value( ad.isOptional() );
+        json.key( "is_set" ); //$NON-NLS-1$
+        json.value( propValue != null );
 
         // attribute type - overwrite metatype provided type
         // if the property name contains "password" and the
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/internal/configuration/MetatypePropertyDescriptor.java b/webconsole/src/main/java/org/apache/felix/webconsole/internal/configuration/MetatypePropertyDescriptor.java
index 533c1e5..96adeb0 100644
--- a/webconsole/src/main/java/org/apache/felix/webconsole/internal/configuration/MetatypePropertyDescriptor.java
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/internal/configuration/MetatypePropertyDescriptor.java
@@ -30,12 +30,14 @@
 public class MetatypePropertyDescriptor extends PropertyDescriptor
 {
     private final AttributeDefinition ad;
+    private final boolean optional; 
 
 
-    public MetatypePropertyDescriptor( AttributeDefinition ad )
+    public MetatypePropertyDescriptor( AttributeDefinition ad, boolean optional )
     {
         super( ad.getID(), ad.getType(), ad.getCardinality() );
         this.ad = ad;
+        this.optional = optional;
     }
 
 
@@ -79,4 +81,9 @@
     {
         return ad.getDefaultValue();
     }
+
+    public boolean isOptional()
+    {
+        return optional;
+    }
 }
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/internal/configuration/PropertyDescriptor.java b/webconsole/src/main/java/org/apache/felix/webconsole/internal/configuration/PropertyDescriptor.java
index dc412c3..9ea82e4 100644
--- a/webconsole/src/main/java/org/apache/felix/webconsole/internal/configuration/PropertyDescriptor.java
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/internal/configuration/PropertyDescriptor.java
@@ -94,4 +94,10 @@
     {
         return null;
     }
+
+    public boolean isOptional()
+    {
+        return false;
+    }
+    
 }
\ No newline at end of file
diff --git a/webconsole/src/main/resources/OSGI-INF/l10n/bundle.properties b/webconsole/src/main/resources/OSGI-INF/l10n/bundle.properties
index f99802a..a3014a3 100644
--- a/webconsole/src/main/resources/OSGI-INF/l10n/bundle.properties
+++ b/webconsole/src/main/resources/OSGI-INF/l10n/bundle.properties
@@ -187,6 +187,8 @@
 config.title.bundle=Bundle
 config.title.name=Name
 config.bind.error=Error: the PID "{0}" is bound to "{1}" but the actual managed service is registered from "{2}" bundle
+config.property.default.value=Note, that this property is not set. The above field contains is the *default value* specified in the Meta Type service.
+config.property.optional.value=This attribute is optional. To save it in the current configuration you must check it. To remove it from the configuration uncheck the checkbox.
 
 # License plugin
 licenses.pluginTitle=Licenses
diff --git a/webconsole/src/main/resources/res/ui/config.css b/webconsole/src/main/resources/res/ui/config.css
index 5f69d84..21e543d 100644
--- a/webconsole/src/main/resources/res/ui/config.css
+++ b/webconsole/src/main/resources/res/ui/config.css
@@ -33,3 +33,6 @@
 .subpid   { margin-left: 1em; float: left }
 tr.fpid   td   { font-style:italic }
 #factoryTableCaption { margin-top: 1.5em }
+span.default_value {
+	float: left; margin-right: .3em;
+}
\ No newline at end of file
diff --git a/webconsole/src/main/resources/res/ui/config.js b/webconsole/src/main/resources/res/ui/config.js
index 02ca0ff..54d0868 100644
--- a/webconsole/src/main/resources/res/ui/config.js
+++ b/webconsole/src/main/resources/res/ui/config.js
@@ -107,7 +107,7 @@
     if (obj.description)
     {
         trEl = tr( );
-        tdEl = td( null, { colSpan: "2" } );
+        tdEl = td( null, { colSpan: "3" } );
         addText( tdEl, obj.description );
         trEl.appendChild( tdEl );
         bodyEl.appendChild( trEl );
@@ -134,13 +134,41 @@
 		.dialog('open'));
 }
 
+/* Element */ function addDefaultValue( /* Element */ element ) {
+	if (element) {
+		element.appendChild( 
+			createElement('span', 'default_value ui-state-highlight1 ui-icon ui-icon-alert', {
+				title : i18n.dflt_value
+			})
+		);
+	}
+	return element;
+}
+
 function printForm( /* Element */ parent, /* Object */ properties ) {
     var propList;
     for (var prop in properties)
     {
         var attr = properties[prop];
-  
+
+		// create optionality element
+		var optElement = false;
+		if (attr.optional) {
+			var elAttributes = {
+                type: "checkbox",
+                name: "opt" + prop,
+				title: i18n.opt_value
+            };
+			if (attr.is_set) {
+				elAttributes['checked'] = 'checked';
+			}
+			optElement = createElement( "input", "optionality", elAttributes);
+		} else {
+			optElement = text( "" );
+		}
+		// create the raw
         var trEl = tr( null, null, [
+				td( null, null, [ optElement ] ),
                 td( null, null, [ text( attr.name ) ] )
             ]);
         parent.appendChild( trEl );
@@ -152,7 +180,8 @@
         {
             // check is required to also handle empty strings, 0 and false
             var inputName = (prop == "action" || prop == "propertylist" || prop == "apply" || prop == "delete") ? '$' + prop : prop;
-            tdEl.appendChild( createInput( inputName, attr.value, attr.type, '99%' ) );
+			var inputEl = createInput( inputName, attr.value, attr.type, '99%' );
+            tdEl.appendChild( inputEl );
             tdEl.appendChild( createElement( "br" ) );
         }
         else if (typeof(attr.type) == 'object')
@@ -173,6 +202,10 @@
             }
         }
         
+		if (!attr.is_set) {
+			addDefaultValue( tdEl );
+		}
+
         if (attr.description)
         {
             addText( tdEl, attr.description );
@@ -514,6 +547,20 @@
 	    	unbindConfig($(this).attr('__pid'), $(this).attr('__location'));
 	}
 	_buttons[i18n.save] = function() {
+		// get all the configuration properties names
+		var propListElement = $(this).find('form').find('[name=propertylist]');
+		var propListArray = propListElement.val().split(',');
+
+		// removes the properties, that are unchecked
+		$(this).find('form').find('input.optionality:not(:checked)').each( function(idx, el) {
+			var name = $(el).attr('name').substring(3); // name - 'opt'
+			var index = propListArray.indexOf(name);
+			if (index >= 0) {
+				propListArray.splice(index, 1);
+			}
+		});
+		propListElement.val(propListArray.join(','));
+
 		$.post(pluginRoot + '/' + $(this).attr('__pid'), $(this).find('form').serialize(), function() {
 			// reload on success - prevents AJAX errors - see FELIX-3116
 			document.location.href = pluginRoot; 
diff --git a/webconsole/src/main/resources/templates/config.html b/webconsole/src/main/resources/templates/config.html
index 1934d43..97d21c7 100644
--- a/webconsole/src/main/resources/templates/config.html
+++ b/webconsole/src/main/resources/templates/config.html
@@ -24,6 +24,8 @@
 	del_config   : '${config.del.config}', // "Configuration: ";
 	del_bundle   : '${config.del.bundle}', // "Bundle: ";
 	unbind_ask   : '${config.unbind.ask}', // "Are you sure to unbind this configuration ?"
+	dflt_value   : '${config.property.default.value}', // "Note, that this property is not set. The above field contains is the *default value* specified in the Meta Type service"
+	opt_value    : '${config.property.optional.value}', // "This attribute is optional. To save it in the current configuration you must check it. To remove it from the configuration uncheck the checkbox."
 	err_bind     : '${config.bind.error}' // Error: the PID'{0}' is bound to '{1}' but the actual managed service is registered from '{2}' bundle
 };
 var param = { // param