FELIX-1988 Apply 10.deployment_plugin.patch by Valentin Valchev (thanks)

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@911379 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/internal/deppack/DepPackServlet.java b/webconsole/src/main/java/org/apache/felix/webconsole/internal/deppack/DepPackServlet.java
index 19c1041..2c096ce 100644
--- a/webconsole/src/main/java/org/apache/felix/webconsole/internal/deppack/DepPackServlet.java
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/internal/deppack/DepPackServlet.java
@@ -19,6 +19,7 @@
 
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.io.StringWriter;
 import java.util.Map;
 
 import javax.servlet.ServletException;
@@ -27,56 +28,49 @@
 
 import org.apache.commons.fileupload.FileItem;
 import org.apache.felix.webconsole.AbstractWebConsolePlugin;
-import org.apache.felix.webconsole.WebConsoleConstants;
+import org.apache.felix.webconsole.DefaultVariableResolver;
+import org.apache.felix.webconsole.SimpleWebConsolePlugin;
 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.json.JSONException;
 import org.json.JSONWriter;
-import org.osgi.service.component.ComponentContext;
 import org.osgi.service.deploymentadmin.DeploymentAdmin;
 import org.osgi.service.deploymentadmin.DeploymentException;
 import org.osgi.service.deploymentadmin.DeploymentPackage;
 
 
-public class DepPackServlet extends BaseWebConsolePlugin
+/**
+ * DepPackServlet provides a plugin for managing deployment admin packages.
+ */
+public class DepPackServlet extends SimpleWebConsolePlugin implements OsgiManagerPlugin
 {
 
-    public static final String LABEL = "deppack";
+    private static final String LABEL = "deppack";
+    private static final String TITLE = "Deployment Packages";
+    private static final String CSS[] = { "/res/ui/deployment.css" };
 
-    public static final String TITLE = "Deployment Packages";
-
+    //
     private static final String ACTION_DEPLOY = "deploydp";
-
     private static final String ACTION_UNINSTALL = "uninstalldp";
-
     private static final String PARAMETER_PCK_FILE = "pckfile";
 
+    // templates
+    private final String TEMPLATE;
 
-    public String getLabel()
+    /** Default constructor */
+    public DepPackServlet()
     {
-        return LABEL;
+        super(LABEL, TITLE, CSS);
+
+        // load templates
+        TEMPLATE = readTemplateFile( "/templates/deployment.html" );
     }
 
 
-    public String getTitle()
-    {
-        return TITLE;
-    }
-
-
-    protected void activate( ComponentContext context )
-    {
-        this.activate( context.getBundleContext() );
-    }
-
-
-    protected void deactivate( ComponentContext context )
-    {
-        this.deactivate();
-    }
-
-
+    /**
+     * @see javax.servlet.http.HttpServlet#doPost(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
+     */
     protected void doPost( HttpServletRequest req, HttpServletResponse resp ) throws ServletException, IOException
     {
         // get the uploaded data
@@ -138,7 +132,7 @@
     }
 
 
-    private FileItem getFileItem( Map params, String name, boolean isFormField )
+    private static final FileItem getFileItem( Map params, String name, boolean isFormField )
     {
         FileItem[] items = ( FileItem[] ) params.get( name );
         if ( items != null )
@@ -157,42 +151,39 @@
     }
 
 
+    /**
+     * @see org.apache.felix.webconsole.AbstractWebConsolePlugin#renderContent(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
+     */
     protected void renderContent( HttpServletRequest request, HttpServletResponse response ) throws ServletException,
         IOException
     {
 
-        PrintWriter pw = response.getWriter();
-
-        String appRoot = ( String ) request.getAttribute( WebConsoleConstants.ATTR_APP_ROOT );
-        pw.println( "<script src='" + appRoot + "/res/ui/packages.js' language='JavaScript'></script>" );
-
-        pw.println( "<h1>Deployment Admin</h1>" );
         final DeploymentAdmin admin = ( DeploymentAdmin ) this.getService( DeploymentAdmin.class.getName() );
-        if ( admin == null )
-        {
-            pw.println( "<p><em>Deployment Admin is not installed.</em></p>" );
-            return;
-        }
-        final DeploymentPackage[] packages = admin.listDeploymentPackages();
 
-        Util.startScript( pw );
-        pw.println( "var packageListData = " );
-        JSONWriter jw = new JSONWriter( pw );
+        StringWriter w = new StringWriter();
+        PrintWriter w2 = new PrintWriter(w);
+        JSONWriter jw = new JSONWriter( w2 );
         try
         {
             jw.object();
-
-            jw.key( "data" );
-
-            jw.array();
-
-            for ( int i = 0; i < packages.length; i++ )
+            if ( null == admin )
             {
-                packageInfoJson( jw, packages[i] );
+                jw.key( "error" );
+                jw.value( true );
             }
+            else
+            {
+                final DeploymentPackage[] packages = admin.listDeploymentPackages();
+                jw.key( "data" );
 
-            jw.endArray();
+                jw.array();
+                for ( int i = 0; i < packages.length; i++ )
+                {
+                    packageInfoJson( jw, packages[i] );
+                }
+                jw.endArray();
 
+            }
             jw.endObject();
 
         }
@@ -201,13 +192,15 @@
             throw new IOException( je.toString() );
         }
 
-        pw.println( ";" );
-        pw.println( "renderPackage( packageListData );" );
-        Util.endScript( pw );
+        // prepare variables
+        DefaultVariableResolver vars = ( ( DefaultVariableResolver ) WebConsoleUtil.getVariableResolver( request ) );
+        vars.put( "__data__", w.toString() );
+
+        response.getWriter().print(TEMPLATE);
     }
 
 
-    private void packageInfoJson( JSONWriter jw, DeploymentPackage pack ) throws JSONException
+    private static final void packageInfoJson( JSONWriter jw, DeploymentPackage pack ) throws JSONException
     {
         jw.object();
         jw.key( "id" );
diff --git a/webconsole/src/main/resources/res/ui/deployment.css b/webconsole/src/main/resources/res/ui/deployment.css
new file mode 100644
index 0000000..5c569c7
--- /dev/null
+++ b/webconsole/src/main/resources/res/ui/deployment.css
@@ -0,0 +1,19 @@
+/*
+ * 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.
+ */
+
+.pkgname { cursor:pointer }
+.pkgname span { float:left }
diff --git a/webconsole/src/main/resources/res/ui/deployment.js b/webconsole/src/main/resources/res/ui/deployment.js
new file mode 100644
index 0000000..7dbe6ec
--- /dev/null
+++ b/webconsole/src/main/resources/res/ui/deployment.js
@@ -0,0 +1,171 @@
+/*
+ * 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 fixId(id) { return id.replace('.', '_'); }
+
+function data( /* Array of Object */ dataArray ) { // render components
+	if (dataArray.length == 1) {
+		entry( dataArray[0], true );
+	} else {
+		for ( var idx in dataArray ) {
+			entry( dataArray[idx] );
+		}
+	}
+}
+
+function entry( /* Object */ dataEntry, /* boolean */ singleEntry ) {
+	var id = fixId(dataEntry.id);
+	var trElement = tr( 'ui-state-active', { 'id': 'entry' + id });
+
+	// entry brief
+	entryInternal( trElement,  dataEntry, singleEntry );
+	plugin_table.append(trElement);
+
+	// dataEntry detailed properties
+	trElement = tr( 'ui-helper-hidden', { 
+		'id'   : 'entry' + id + '_details'
+	});
+	if (dataEntry.props) {
+		getDataEntryDetails( trElement, dataEntry.props );
+	}
+	plugin_table.append(trElement);
+}
+
+function entryInternal( /* Element */ parent, /* Object */ dataEntry, /* boolean */ singleEntry ) {
+	var id = dataEntry.id;
+	var _id = fixId(id);
+
+	parent.appendChild( td( null, null, [ text( id ) ] ) );
+	parent.appendChild( td( null, 
+			{
+				'onclick': 'toggleDetails("#entry' + _id + '")',
+				'class': 'pkgname'
+			}, 
+			[
+				createElement( 'span', 'ui-icon ui-icon-triangle-1-e', { id: 'entry' + _id + '_icon'} ),
+				text(dataEntry.name)
+			]
+		)
+	);
+	parent.appendChild( td( null, null, [ text( dataEntry.state ) ] ) );
+
+	for ( var aidx in dataEntry.actions ) {
+		var action = dataEntry.actions[aidx];
+		parent.appendChild( actionButton( action.enabled, id, action.link, action.name ) );
+	}
+}
+
+
+/* Element */ function actionButton( /* boolean */ enabled, /* long */ id, /* String */ op, /* String */ opLabel ) {
+	var buttonTd = td( "content", { align: "right" } );
+	if ( op ) {
+		switch (opLabel) {
+			case "Uninstall" : opLabel = i18n.uninstall; break;
+		}
+		var input = createElement( "input", null, {
+				type: 'button',
+				value: opLabel,
+				onclick: 'changeDataEntryState("' + id + '", "' + op + '");'
+			});
+		if (!enabled) {
+			input.setAttribute( "disabled", true );
+			$(input).addClass('ui-state-disabled');
+		}
+		buttonTd.appendChild( input );
+	} else {
+		addText( buttonTd, "\u00a0" );
+	}
+	return buttonTd;
+}
+
+
+function getDataEntryDetails( /* Element */ parent, /* Array of Object */ details )
+{
+	parent.appendChild( addText( td( "content"), "\u00a0" ) );
+	
+	var tdEl = td( null, { colspan: 4 } );
+	parent.appendChild( tdEl );
+	
+	var tableEl = createElement( "table", null, { border: 0 } );
+	tdEl.appendChild( tableEl );
+	
+	var tbody = createElement( "tbody" );
+	tableEl.appendChild( tbody );
+	for (var idx in details) {
+		var prop = details[idx];
+		var trEl = tr();
+		var key = prop.key;
+		switch (key) {
+			case 'Package Name': key = i18n.package_name; break;
+			case 'Version'     : key = i18n.version; break;
+			case 'Bundles'     : key = i18n.bundles; break;
+		}
+		trEl.appendChild( addText( td( null, { noWrap: true } ), key ) );
+
+		var proptd = td();
+		trEl.appendChild( proptd );
+		$(proptd).html( prop.value ? prop.value : "\u00a0");
+
+		tbody.appendChild( trEl );
+	}
+ }
+
+
+function changeDataEntryState(/* long */ id, /* String */ action) {
+	var parm = pluginRoot + "/" + id + "?action=" + action;
+	$.post(parm, null, function() { // always reload
+		document.location.reload();
+	}, "text");
+}
+
+
+function toggleDetails(name) {
+	var icon = $(name + '_icon');
+	var details = $(name + '_details');
+	var show = icon.attr('show');
+	if (typeof show == 'undefined') show = '';
+	icon.attr('show', show ? '' : 'true');
+
+	if (show == 'true') {
+		icon.removeClass('ui-icon-triangle-1-s').addClass('ui-icon-triangle-1-e');
+		details.addClass('ui-helper-hidden');
+	} else {
+		icon.removeClass('ui-icon-triangle-1-e').addClass('ui-icon-triangle-1-s');
+		details.removeClass('ui-helper-hidden');
+	}
+}
+
+var plugin_table = false;
+$(document).ready(function() {
+	plugin_table = $('#plugin_table');
+	var /* Array of Data Objects */ bundleData = packageListData;
+	if (bundleData.error) {
+		$('.statline').text(i18n.status_no_serv);
+		$('#dps1').addClass('ui-helper-hidden');
+	} else if (!bundleData.data || bundleData.data.length == 0) {
+		$('.statline').text(i18n.status_no_data);
+		$('#dps1').removeClass('ui-helper-hidden');
+		$('#dps2').addClass('ui-helper-hidden');
+	} else {
+		data( bundleData.data );
+		$('.statline').text(i18n.status_ok);
+		$('#dps1').removeClass('ui-helper-hidden');
+		$('#dps2').removeClass('ui-helper-hidden');
+		initStaticWidgets(plugin_table);
+	}
+});
+
diff --git a/webconsole/src/main/resources/templates/deployment.html b/webconsole/src/main/resources/templates/deployment.html
new file mode 100644
index 0000000..525738d
--- /dev/null
+++ b/webconsole/src/main/resources/templates/deployment.html
@@ -0,0 +1,52 @@
+<script type="text/javascript" src="res/ui/deployment.js"></script>
+<script type="text/javascript">
+// <![CDATA[
+var i18n = {
+	status_no_data : "${deployment.status.no_data}",
+	status_no_serv : "${deployment.status.no_service}",
+	status_ok      : "${deployment.status.ok}",
+	package_name   : "${deployment.package.name}",
+	version        : "${version}",
+	bundles        : "${deployment.bundles}",
+	uninstall      : "${deployment.uninstall}"
+}
+var packageListData = ${__data__};
+// ]]>
+</script>
+
+<p class="statline">&nbsp;</p>
+
+<div id="dps1"> <!-- will be hidden if no DP service available -->
+<!-- top header -->
+<form method="post" enctype="multipart/form-data" >
+	<div class="ui-widget-header ui-corner-top buttonGroup" style="text-align: right">
+		<input name="action"  type="hidden" value="deploydp" />
+		<input name="pckfile" type="file" size="50" />
+		<input type="submit" value="${deployment.install_update}" />
+	</div>
+</form>
+
+<div id="dps2"> <!-- will be hidden if no data available -->
+	<table id="plugin_table" class="nicetable">
+	<thead>
+		<tr>
+			<th>${id}</th>
+			<th width="100%">${deployment.details}</th>
+			<th>${version}</th>
+			<th colspan="1">${deployment.actions}</th>
+		</tr>
+	</thead>
+	<tbody>
+	</tbody>
+	</table>
+
+	<!-- bottom header -->
+	<form method="post" enctype="multipart/form-data" >
+		<div class="ui-widget-header ui-corner-bottom buttonGroup" style="text-align: right">
+			<input name="action"  type="hidden" value="deploydp" />
+			<input name="pckfile" type="file" size="50" />
+			<input type="submit" value="${deployment.install_update}" />
+		</div>
+	</form>
+</div><!--dps2-->
+</div><!--dps1-->