FELIX-1988 Apply 13.components_plugin.patch by Valentin Valchev (thanks)

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@911450 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/internal/compendium/ComponentsServlet.java b/webconsole/src/main/java/org/apache/felix/webconsole/internal/compendium/ComponentsServlet.java
index f6ef5c4..ce38ab4 100644
--- a/webconsole/src/main/java/org/apache/felix/webconsole/internal/compendium/ComponentsServlet.java
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/internal/compendium/ComponentsServlet.java
@@ -19,6 +19,7 @@
 
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.io.StringWriter;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.Dictionary;
@@ -33,10 +34,15 @@
 import org.apache.felix.scr.Component;
 import org.apache.felix.scr.Reference;
 import org.apache.felix.scr.ScrService;
+import org.apache.felix.webconsole.ConfigurationPrinter;
+import org.apache.felix.webconsole.DefaultVariableResolver;
+import org.apache.felix.webconsole.SimpleWebConsolePlugin;
 import org.apache.felix.webconsole.WebConsoleConstants;
 import org.apache.felix.webconsole.WebConsoleUtil;
 import org.apache.felix.webconsole.internal.BaseWebConsolePlugin;
+import org.apache.felix.webconsole.internal.OsgiManagerPlugin;
 import org.apache.felix.webconsole.internal.Util;
+import org.apache.felix.webconsole.internal.core.BundlesServlet;
 import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONWriter;
@@ -51,43 +57,46 @@
 import org.osgi.service.metatype.MetaTypeService;
 
 
-public class ComponentsServlet extends BaseWebConsolePlugin
+/**
+ * ComponentsServlet provides a plugin for managing Service Components Runtime.
+ */
+public class ComponentsServlet extends SimpleWebConsolePlugin implements OsgiManagerPlugin
 {
 
     private static final long serialVersionUID = 1L;
 
-    public static final String NAME = "components";
+    private static final String LABEL = "components";
+    private static final String TITLE = "Components";
+    private static final String CSS[] = { "/res/ui/bundles.css" }; // yes, it's correct!
 
-    public static final String LABEL = "Components";
+    // actions
+    private static final String OPERATION = "action";
+    private static final String OPERATION_ENABLE = "enable";
+    private static final String OPERATION_DISABLE = "disable";
+    private static final String OPERATION_CONFIGURE = "configure";
 
-    public static final String COMPONENT_ID = "componentId";
-
-    public static final String OPERATION = "action";
-
-    public static final String OPERATION_ENABLE = "enable";
-
-    public static final String OPERATION_DISABLE = "disable";
-
-    public static final String OPERATION_CONFIGURE = "configure";
-
+    // needed services
     private static final String SCR_SERVICE = ScrService.class.getName();
-
     private static final String META_TYPE_NAME = MetaTypeService.class.getName();
-
     private static final String CONFIGURATION_ADMIN_NAME = ConfigurationAdmin.class.getName();
 
-    public String getTitle()
+    // templates
+    private final String TEMPLATE;
+
+    /** Default constructor */
+    public ComponentsServlet()
     {
-        return LABEL;
+        super(LABEL, TITLE, CSS);
+        
+        // load templates
+        TEMPLATE = readTemplateFile( "/templates/components.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
     {
         final RequestInfo reqInfo = new RequestInfo(request);
@@ -116,6 +125,9 @@
     }
 
 
+    /**
+     * @see org.apache.felix.webconsole.AbstractWebConsolePlugin#doGet(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
+     */
     protected void doGet( HttpServletRequest request, HttpServletResponse response ) throws ServletException,
     IOException {
         final RequestInfo reqInfo = new RequestInfo(request);
@@ -136,27 +148,25 @@
         super.doGet( request, response );
     }
 
+    /**
+     * @see org.apache.felix.webconsole.AbstractWebConsolePlugin#renderContent(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
+     */
     protected void renderContent( HttpServletRequest request, HttpServletResponse response ) throws IOException
     {
         // get request info from request attribute
         final RequestInfo reqInfo = getRequestInfo(request);
-        final PrintWriter pw = response.getWriter();
 
-        final String appRoot = ( String ) request.getAttribute( WebConsoleConstants.ATTR_APP_ROOT );
+        StringWriter w = new StringWriter();
+        PrintWriter w2 = new PrintWriter(w);
+        renderResult( w2, reqInfo.component );
+        
+        // prepare variables
+        DefaultVariableResolver vars = ( ( DefaultVariableResolver ) WebConsoleUtil.getVariableResolver( request ) );
+        vars.put( "__drawDetails__", reqInfo.componentRequested ? Boolean.TRUE : Boolean.FALSE );
+        vars.put( "__data__", w.toString() );
 
-        Util.startScript( pw );
-        pw.println( "var imgRoot = '" + appRoot + "/res/imgs';");
-        pw.println( "var drawDetails = " + reqInfo.componentRequested + ";");
-        Util.endScript( pw );
-
-        Util.script(pw, appRoot, "components.js");
-
-        pw.println( "<div id='plugin_content'/>");
-        Util.startScript( pw );
-        pw.print( "renderComponents(");
-        renderResult( pw, reqInfo.component );
-        pw.println(");" );
-        Util.endScript( pw );
+        response.getWriter().print( TEMPLATE );
+        
     }
 
 
@@ -172,7 +182,7 @@
             if ( scrService == null )
             {
                 jw.key( "status" );
-                jw.value( "Apache Felix Declarative Service required for this function" );
+                jw.value( -1 );
             }
             else
             {
@@ -181,7 +191,7 @@
                 if ( components == null || components.length == 0 )
                 {
                     jw.key( "status" );
-                    jw.value( "No components installed currently" );
+                    jw.value( 0 );
                 }
                 else
                 {
@@ -201,7 +211,7 @@
                     }
                     buffer.append(" installed.");
                     jw.key("status");
-                    jw.value(buffer.toString());
+                    jw.value(componentMap.size());
 
                     // render components
                     jw.key( "data" );
@@ -479,17 +489,17 @@
         return false;
     }
 
-    protected ConfigurationAdmin getConfigurationAdmin()
+    private final ConfigurationAdmin getConfigurationAdmin()
     {
         return ( ConfigurationAdmin ) getService( CONFIGURATION_ADMIN_NAME );
     }
 
-    private ScrService getScrService()
+    final ScrService getScrService()
     {
         return ( ScrService ) getService( SCR_SERVICE );
     }
 
-    protected MetaTypeService getMetaTypeService()
+    private final MetaTypeService getMetaTypeService()
     {
         return ( MetaTypeService ) getService( META_TYPE_NAME );
     }
@@ -556,7 +566,7 @@
 
     }
 
-    public static RequestInfo getRequestInfo(final HttpServletRequest request)
+    static RequestInfo getRequestInfo(final HttpServletRequest request)
     {
         return (RequestInfo)request.getAttribute(ComponentsServlet.class.getName());
     }
diff --git a/webconsole/src/main/resources/res/ui/components.js b/webconsole/src/main/resources/res/ui/components.js
index 6728e04..9b2ba33 100644
--- a/webconsole/src/main/resources/res/ui/components.js
+++ b/webconsole/src/main/resources/res/ui/components.js
@@ -6,7 +6,7 @@
  * (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
+ *	  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,
@@ -14,43 +14,33 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-function renderStatusLine() {
-	$("#plugin_content").append( "<div class='fullwidth'><div class='statusline'/></div>" );
-}
-
-function renderView( /* Array of String */ columns, /* Array of String */ buttons ) {
-    renderStatusLine();
-    renderButtons(buttons);
-    var txt = "<div class='table'><table id='plugin_table' class='tablelayout'><thead><tr>";
-    for ( var name in columns ) {
-    	txt = txt + "<th class='col_" + columns[name] + "'>" + columns[name] + "</th>";
-    }
-    txt = txt + "</tr></thead><tbody></tbody></table></div>";
-    $("#plugin_content").append( txt );
-    renderButtons(buttons);
-    renderStatusLine();	
-}
-
-function renderButtons( buttons ) {
-	$("#plugin_content").append( "<form method='post' enctype='multipart/form-data'><div class='fullwidth'><div class='buttons'>" +
-	                             buttons + "</div></div></form>" );
-}
-
 function renderData( eventData )  {
-	$(".statusline").empty().append(eventData.status);
-	$("#plugin_table > tbody > tr").remove();
-    for ( var idx in eventData.data ) {
-        entry( eventData.data[idx] );
-    }
-    $("#plugin_table").trigger("update");
-    if ( drawDetails ) {
-	    renderDetails(eventData);
-    }
+	switch(eventData.status) {
+		case -1: // no event admin
+			$(".statline").html(i18n.stat_no_service);
+			$("#scr").addClass('ui-helper-hidden');
+			break;
+		case  0: // no components
+			$(".statline").html(i18n.stat_no_components);
+			$('#scr').addClass('ui-helper-hidden');
+			break;
+		default:
+			$(".statline").html(i18n.stat_ok.msgFormat(eventData.status));
+			$('#scr').removeClass('ui-helper-hidden');
+			
+			$("#plugin_table > tbody > tr").remove();
+			for ( var idx in eventData.data ) {
+				entry( eventData.data[idx] );
+			}
+			$("#plugin_table").trigger("update");
+			if ( drawDetails ) renderDetails(eventData);
+			initStaticWidgets();
+	}
 }
 
 function entry( /* Object */ dataEntry ) {
-    var trElement = tr( null, { id: "entry" + dataEntry.id } );
-    entryInternal( trElement,  dataEntry );
+	var trElement = tr( null, { id: "entry" + dataEntry.id } );
+	entryInternal( trElement,  dataEntry );
 	$("#plugin_table > tbody").append(trElement);	
 }
 
@@ -59,64 +49,65 @@
 	var op = action.link;
 	var opLabel = action.name;
 	var img = action.image;
+	// fixup JQuery UI icons
+	if (img == 'configure')	  { img = 'wrench'
+	} else if (img == 'disable') { img = 'stop' //locked
+	} else if (img == 'enable')  { img = 'play' //unlocked
+	}
+	
+	// apply i18n
+	opLabel = i18n[opLabel] ? i18n[opLabel] : opLabel;
 	
 	var arg = id;
 	if ( op == "configure" ) {
 		arg = pid
 	}
-	var input = createElement( "input", null, {
-            type: 'image',
-            title: opLabel,
-            alt: opLabel,
-            src: imgRoot + '/component_' + img + '.png',
-            style: {"margin-left": "10px"}
-        });
-	$(input).click(function() {changeDataEntryState(arg, op)});
+	var input = createElement('li', 'dynhover', {
+		title: opLabel
+	});
+	$(input)
+		.html('<span class="ui-icon ui-icon-'+img+'"></span>')
+		.click(function() {changeDataEntryState(arg, op)});
+
+	if (!enabled) {
+		$(input).attr('disabled', true).addClass('ui-state-disabled');
 		
-    if (!enabled) {
-    	$(input).attr("disabled", true);
-    }
-    parent.appendChild( input );
+	}
+	parent.appendChild( input );
 }
 
 function entryInternal( /* Element */ parent, /* Object */ dataEntry ) {
-    var id = dataEntry.id;
-    var name = dataEntry.name;
-    var state = dataEntry.state;
-    
-    var inputElement = createElement("img", "rightButton", {
-    	src: appRoot + "/res/imgs/arrow_right.png",
-        style: {"border": "none"},
-    	id: 'img' + id,
-    	title: "Details",
-    	alt: "Details",
-    	width: 14,
-    	height: 14
-    });
+	var id = dataEntry.id;
+	var name = dataEntry.name;
+	var state = dataEntry.state;
+
+	var inputElement = createElement('span', 'ui-icon ui-icon-triangle-1-e', {
+		title: "Details",
+		id: 'img' + id,
+		style: {display: "inline-block"}
+	});
 	$(inputElement).click(function() {showDetails(id)});
-    var titleElement;
-    if ( drawDetails ) {
-    	titleElement = text(name);
-    } else {
-        titleElement = createElement ("a", null, {
-    	    href: window.location.pathname + "/" + id
-        });
-        titleElement.appendChild(text(name));
-    }
-    
-    parent.appendChild( td( null, null, [ text( id ) ] ) );
-    parent.appendChild( td( null, null, [ inputElement, text(" "), titleElement ] ) );
-    parent.appendChild( td( null, null, [ text( state ) ] ) );
-    var actionsTd = td( null, null );
-    var div = createElement("div", null, {
-    	style: { "text-align" : "left"}
-    });
-    actionsTd.appendChild(div);
-    
-    for ( var a in dataEntry.actions ) {
-    	actionButton( div, id, dataEntry.actions[a], dataEntry.pid );
-    }
-    parent.appendChild( actionsTd );
+	var titleElement;
+	if ( drawDetails ) {
+		titleElement = text(name);
+	} else {
+		titleElement = createElement ("a", null, {
+			href: window.location.pathname + "/" + id
+		});
+		titleElement.appendChild(text(name));
+	}
+
+	parent.appendChild( td( null, null, [ text( id ) ] ) );
+	parent.appendChild( td( null, null, [ inputElement, text(" "), titleElement ] ) );
+	parent.appendChild( td( null, null, [ text( state ) ] ) );
+	var actionsTd = td( null, null );
+	var div = createElement('ul', 'icons ui-widget');
+	actionsTd.appendChild(div);
+
+	for ( var a in dataEntry.actions ) {
+		actionButton( div, id, dataEntry.actions[a], dataEntry.pid );
+	}
+	parent.appendChild( actionsTd );
 }
 
 function changeDataEntryState(/* long */ id, /* String */ action) {
@@ -125,29 +116,31 @@
 		return;
 	}
 	$.post(pluginRoot + "/" + id, {"action":action}, function(data) {
-	    renderData(data);
+		renderData(data);
 	}, "json");	
 }
 
 function showDetails( id ) {
-    $.get(pluginRoot + "/" + id + ".json", null, function(data) {
-    	renderDetails(data);
-    }, "json");
+	$.get(pluginRoot + "/" + id + ".json", null, function(data) {
+		renderDetails(data);
+	}, "json");
 }
 
 function loadData() {
 	$.get(pluginRoot + "/.json", null, function(data) {
-	    renderData(data);
+		renderData(data);
 	}, "json");	
 }
 
 function hideDetails( id ) {
 	$("#img" + id).each(function() {
 		$("#pluginInlineDetails").remove();
-		$(this).attr("src", appRoot + "/res/imgs/arrow_right.png");
-        $(this).attr("title", "Details");
-        $(this).attr("alt", "Details");
-        $(this).unbind('click').click(function() {showDetails(id)});
+		$(this).
+			removeClass('ui-icon-triangle-1-w').//left
+			removeClass('ui-icon-triangle-1-s').//down
+			addClass('ui-icon-triangle-1-e').//right
+		    attr("title", "Details").
+			unbind('click').click(function() {showDetails(id)});
 	});
 }
 
@@ -157,73 +150,75 @@
 	$("#entry" + data.id + " > td").eq(1).append("<div id='pluginInlineDetails'/>");
 	$("#img" + data.id).each(function() {
 		if ( drawDetails ) {
-			$(this).attr("src", appRoot + "/res/imgs/arrow_left.png");
-	        $(this).attr("title", "Back");
-	        $(this).attr("alt", "Back");
-    	    var ref = window.location.pathname;
-    	    ref = ref.substring(0, ref.lastIndexOf('/'));
-            $(this).unbind('click').click(function() {window.location = ref;});
+			var ref = window.location.pathname;
+			ref = ref.substring(0, ref.lastIndexOf('/'));
+			$(this).
+				removeClass('ui-icon-triangle-1-e').//right
+				removeClass('ui-icon-triangle-1-s').//down
+				addClass('ui-icon-triangle-1-w').//left
+				attr("title", "Back").
+				unbind('click').click(function() {window.location = ref});
 		} else {
-			$(this).attr("src", appRoot + "/res/imgs/arrow_down.png");
-	        $(this).attr("title", "Hide Details");
-	        $(this).attr("alt", "Hide Details");
-            $(this).unbind('click').click(function() {hideDetails(data.id)});
+			$(this).
+				removeClass('ui-icon-triangle-1-w').//left
+				removeClass('ui-icon-triangle-1-e').//right
+				addClass('ui-icon-triangle-1-s').//down
+				attr("title", "Hide Details").
+				unbind('click').click(function() {hideDetails(data.id)});
 		}
 	});
 	$("#pluginInlineDetails").append("<table border='0'><tbody></tbody></table>");
-    var details = data.props;
-    for (var idx in details) {
-        var prop = details[idx];
-        
-        var txt = "<tr><td class='aligntop' noWrap='true' style='border:0px none'>" + prop.key + "</td><td class='aligntop' style='border:0px none'>";	        
-        if (prop.value) {
-    		if ( $.isArray(prop.value) ) {
-        		var i = 0;
-        		for(var pi in prop.value) {
-        			var value = prop.value[pi];
-	                if (i > 0) { txt = txt + "<br/>"; }
-	                var span;
-	                if (value.substring(0, 2) == "!!") {
-	                	txt = txt + "<span style='color: red;'>" + value + "</span>";
-	                } else {
-	                	txt = txt + value;
-	                }
-	                i++;
-        		}
-    		} else {
-    			txt = txt + prop.value;
-    		}
-        } else {
-        	txt = txt + "\u00a0";
-        }
-        txt = txt + "</td></tr>";
-        $("#pluginInlineDetails > table > tbody").append(txt);
+	var details = data.props;
+	for (var idx in details) {
+		var prop = details[idx];
+		var key = i18n[prop.key] ? i18n[prop.key] : prop.key; // i18n
+
+		var txt = "<tr><td class='aligntop' noWrap='true' style='border:0px none'>" + key + "</td><td class='aligntop' style='border:0px none'>";	
+		if (prop.value) {
+			if ( $.isArray(prop.value) ) {
+				var i = 0;
+				for(var pi in prop.value) {
+					var value = prop.value[pi];
+					if (i > 0) { txt = txt + "<br/>"; }
+					var span;
+					if (value.substring(0, 2) == "!!") {
+						txt = txt + "<span style='color: red;'>" + value + "</span>";
+					} else {
+						txt = txt + value;
+					}
+					i++;
+				}
+			} else {
+				txt = txt + prop.value;
+			}
+		} else {
+			txt = txt + "\u00a0";
+		}
+		txt = txt + "</td></tr>";
+		$("#pluginInlineDetails > table > tbody").append(txt);
 	}
 }
 
-function renderComponents(data) {
-	$(document).ready(function(){
-    	renderView( ["Id", "Name", "Status", "Actions"],
-        		"<div class='button'><button class='reloadButton' type='button' name='reload'>Reload</button></div>");
-        renderData(data);
-        
-        $(".reloadButton").click(loadData);
 
-        var extractMethod = function(node) {
-        	var link = node.getElementsByTagName("a");
-            if ( link && link.length == 1 ) {
-            	return link[0].innerHTML;
-            }
-            return node.innerHTML;
-        };
-        $("#plugin_table").tablesorter({
-            headers: {
-        	    0: { sorter:"digit"},
-                3: { sorter: false }
-            },
-            sortList: [[1,0]],
-            textExtraction:extractMethod 
-        });
-    });
-}
- 
+$(document).ready(function(){
+	renderData (scrData);
+
+	$(".reloadButton").click(loadData);
+
+	var extractMethod = function(node) {
+		var link = node.getElementsByTagName("a");
+		if ( link && link.length == 1 ) {
+			return link[0].innerHTML;
+		}
+		return node.innerHTML;
+	};
+	$("#plugin_table").tablesorter({
+		headers: {
+			0: { sorter:"digit"},
+			3: { sorter: false }
+		},
+		sortList: [[1,0]],
+		textExtraction:extractMethod
+	});
+});
+
diff --git a/webconsole/src/main/resources/templates/components.html b/webconsole/src/main/resources/templates/components.html
new file mode 100644
index 0000000..b995d31
--- /dev/null
+++ b/webconsole/src/main/resources/templates/components.html
@@ -0,0 +1,54 @@
+<script type="text/javascript" src="res/ui/components.js"></script>
+<script type="text/javascript">
+// <![CDATA[
+var drawDetails = ${__drawDetails__};
+var scrData = ${__data__};
+// i18n
+var i18n = {
+	'Enable'           : '${scr.action.enable}',
+	'Disable'          : '${scr.action.disable}',
+	'Configure'        : '${scr.action.configure}',
+	'Bundle'           : '${scr.prop.bundle}',
+	'Default State'    : '${scr.prop.defstate}',
+	'Activation'       : '${scr.prop.activation}',
+	'Service Type'     : '${scr.serv.type}',
+	'Services'         : '${scr.serv}',
+	'Properties'       : '${scr.prop.properties}',
+	stat_no_service    : "${scr.status.no_service}",
+	stat_no_components : "${scr.status.no_components}",
+	stat_ok            : "${scr.status.ok}"
+}
+// ]]>
+</script>
+<p class="statline">&nbsp;</p>
+
+
+<div id="scr"> <!-- data available -->
+	<!-- top header -->
+	<form method='post' enctype='multipart/form-data' action="">
+		<div class="ui-widget-header ui-corner-top buttonGroup">
+			<button class='reloadButton' type='button' name='reload'>${reload}</button>
+		</div>
+	</form>
+	
+	<table id="plugin_table" class="tablesorter nicetable noauto">
+	<thead>
+		<tr>
+			<th class="col_Id">${id}</th>
+			<th class="col_Name">${scr.title.name}</th>
+			<th class="col_Status">${scr.title.status}</th>
+			<th class="col_Actions">${scr.title.actions}</th>
+		</tr>
+	</thead>
+	<tbody>
+		<tr><td colspan="4">&nbsp;</td></tr>
+	</tbody>
+	</table>
+</div> <!-- end data available -->
+
+<!-- bottom header -->
+<form method='post' enctype='multipart/form-data' action="">
+	<div class="ui-widget-header ui-corner-bottom buttonGroup">
+		<button class='reloadButton' type='button' name='reload'>${reload}</button>
+	</div>
+</form>
\ No newline at end of file