FELIX-691 : Initial version of a console for the deployment admin.
git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@687299 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/webconsole/pom.xml b/webconsole/pom.xml
index f20dff6..8869960 100644
--- a/webconsole/pom.xml
+++ b/webconsole/pom.xml
@@ -50,7 +50,7 @@
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-scr-plugin</artifactId>
- <version>1.0.4</version>
+ <version>1.0.6</version>
<executions>
<execution>
<id>generate-scr-scrdescriptor</id>
@@ -63,7 +63,7 @@
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
- <version>1.4.1</version>
+ <version>1.4.3</version>
<extensions>true</extensions>
<configuration>
<instructions>
@@ -136,13 +136,13 @@
<dependency>
<groupId>org.apache.felix</groupId>
<artifactId>org.osgi.core</artifactId>
- <version>1.0.1</version>
+ <version>1.1.0-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.felix</groupId>
<artifactId>org.osgi.compendium</artifactId>
- <version>1.0.1</version>
+ <version>1.1.0-SNAPSHOT</version>
<scope>provided</scope>
<exclusions>
<exclusion>
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
new file mode 100644
index 0000000..71d53f9
--- /dev/null
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/internal/deppack/DepPackServlet.java
@@ -0,0 +1,261 @@
+/*
+ * 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.deppack;
+
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Map;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.fileupload.FileItem;
+import org.apache.felix.webconsole.AbstractWebConsolePlugin;
+import org.apache.felix.webconsole.internal.BaseWebConsolePlugin;
+import org.apache.felix.webconsole.internal.Util;
+import org.apache.felix.webconsole.internal.servlet.OsgiManager;
+import org.json.JSONException;
+import org.json.JSONWriter;
+import org.osgi.service.component.ComponentContext;
+import org.osgi.service.deploymentadmin.*;
+
+
+/**
+ * The <code>DepPackServlet</code> TODO
+ * @scr.component metainfo="false"
+ * @scr.service interface="javax.servlet.Servlet"
+ * @scr.property name="felix.webconsole.label" valueRef="LABEL"
+ */
+public class DepPackServlet extends BaseWebConsolePlugin
+{
+
+ public static final String LABEL = "deppack";
+
+ 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";
+
+ public String getLabel()
+ {
+ return LABEL;
+ }
+
+
+ public String getTitle()
+ {
+ return TITLE;
+ }
+
+
+ protected void activate(ComponentContext context) {
+ this.activate(context.getBundleContext());
+ }
+
+ protected void deactivate(ComponentContext context) {
+ this.deactivate();
+ }
+
+ protected void doPost(HttpServletRequest req, HttpServletResponse resp)
+ throws ServletException, IOException
+ {
+ // get the uploaded data
+ final String action = getParameter(req, Util.PARAM_ACTION);
+ if ( ACTION_DEPLOY.equals(action))
+ {
+ Map params = ( Map ) req.getAttribute( AbstractWebConsolePlugin.ATTR_FILEUPLOAD );
+ if ( params != null )
+ {
+ final FileItem pck = getFileItem( params, PARAMETER_PCK_FILE, false );
+ final DeploymentAdmin admin = (DeploymentAdmin) this.getService(DeploymentAdmin.class.getName());
+ if ( admin != null )
+ {
+ try
+ {
+ admin.installDeploymentPackage(pck.getInputStream());
+
+ final String uri = req.getRequestURI();
+ resp.sendRedirect( uri );
+ return;
+ }
+ catch (DeploymentException e)
+ {
+ throw new ServletException("Unable to deploy package.", e);
+ }
+ }
+ }
+ throw new ServletException("Upload file or deployment admin missing.");
+ }
+ else if (ACTION_UNINSTALL.equals(action))
+ {
+ final String pckId = req.getPathInfo().substring( req.getPathInfo().lastIndexOf( '/' ) + 1 );
+ if ( pckId != null && pckId.length() > 0 )
+ {
+ final DeploymentAdmin admin = (DeploymentAdmin) this.getService(DeploymentAdmin.class.getName());
+ if ( admin != null )
+ {
+ try
+ {
+ final DeploymentPackage pck = admin.getDeploymentPackage(pckId);
+ if ( pck != null )
+ {
+ pck.uninstall();
+ }
+ }
+ catch (DeploymentException e)
+ {
+ throw new ServletException("Unable to undeploy package.", e);
+ }
+ }
+
+ }
+
+ final PrintWriter pw = resp.getWriter();
+ pw.println("{ \"reload\":true }");
+ return;
+ }
+ throw new ServletException("Unknown action: " + action);
+ }
+
+ private FileItem getFileItem( Map params, String name, boolean isFormField )
+ {
+ FileItem[] items = ( FileItem[] ) params.get( name );
+ if ( items != null )
+ {
+ for ( int i = 0; i < items.length; i++ )
+ {
+ if ( items[i].isFormField() == isFormField )
+ {
+ return items[i];
+ }
+ }
+ }
+
+ // nothing found, fail
+ return null;
+ }
+
+ protected void renderContent( HttpServletRequest request, HttpServletResponse response ) throws ServletException,
+ IOException
+ {
+
+ PrintWriter pw = response.getWriter();
+
+ String appRoot = ( String ) request.getAttribute( OsgiManager.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 );
+ try
+ {
+ jw.object();
+
+ jw.key( "data" );
+
+ jw.array();
+
+ for(int i=0; i<packages.length; i++)
+ {
+ packageInfoJson( jw, packages[i] );
+ }
+
+ jw.endArray();
+
+ jw.endObject();
+
+ }
+ catch ( JSONException je )
+ {
+ throw new IOException( je.toString() );
+ }
+
+ pw.println( ";" );
+ pw.println( "renderPackage( packageListData );" );
+ Util.endScript( pw );
+ }
+
+ private void packageInfoJson( JSONWriter jw, DeploymentPackage pack)
+ throws JSONException
+ {
+ jw.object();
+ jw.key( "id" );
+ jw.value( pack.getName() );
+ jw.key( "name" );
+ jw.value( pack.getName());
+ jw.key( "state" );
+ jw.value( pack.getVersion() );
+
+ jw.key( "actions" );
+ jw.array();
+
+ jw.object();
+ jw.key( "enabled" );
+ jw.value( true );
+ jw.key( "name" );
+ jw.value( "Uninstall" );
+ jw.key( "link" );
+ jw.value( ACTION_UNINSTALL );
+ jw.endObject();
+
+ jw.endArray();
+
+ jw.key( "props" );
+ jw.array();
+ keyVal( jw, "Package Name", pack.getName() );
+ keyVal( jw, "Version", pack.getVersion() );
+
+ final StringBuffer buffer = new StringBuffer();
+ for(int i=0; i<pack.getBundleInfos().length; i++) {
+ buffer.append(pack.getBundleInfos()[i].getSymbolicName() );
+ buffer.append(" - " );
+ buffer.append(pack.getBundleInfos()[i].getVersion() );
+ buffer.append("<br/>");
+ }
+ keyVal(jw, "Bundles", buffer.toString());
+
+ jw.endArray();
+
+ jw.endObject();
+ }
+
+ private void keyVal( JSONWriter jw, String key, Object value ) throws JSONException
+ {
+ if ( key != null && value != null )
+ {
+ jw.object();
+ jw.key( "key" );
+ jw.value( key );
+ jw.key( "value" );
+ jw.value( value );
+ jw.endObject();
+ }
+ }
+}
diff --git a/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/OsgiManager.java b/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/OsgiManager.java
index f438c03..99983b6 100644
--- a/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/OsgiManager.java
+++ b/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/OsgiManager.java
@@ -18,46 +18,21 @@
import java.io.IOException;
-import java.util.Dictionary;
-import java.util.Enumeration;
-import java.util.HashMap;
-import java.util.Hashtable;
-import java.util.Iterator;
-import java.util.Map;
+import java.util.*;
-import javax.servlet.GenericServlet;
-import javax.servlet.Servlet;
-import javax.servlet.ServletException;
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
+import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
-import org.apache.felix.webconsole.AbstractWebConsolePlugin;
-import org.apache.felix.webconsole.Action;
-import org.apache.felix.webconsole.Render;
-import org.apache.felix.webconsole.WebConsoleConstants;
-import org.apache.felix.webconsole.internal.Logger;
-import org.apache.felix.webconsole.internal.OsgiManagerPlugin;
-import org.apache.felix.webconsole.internal.Util;
-import org.apache.felix.webconsole.internal.compendium.ComponentConfigurationPrinter;
-import org.apache.felix.webconsole.internal.compendium.ComponentsServlet;
-import org.apache.felix.webconsole.internal.compendium.ConfigManager;
-import org.apache.felix.webconsole.internal.core.BundlesServlet;
-import org.apache.felix.webconsole.internal.core.InstallAction;
-import org.apache.felix.webconsole.internal.core.SetStartLevelAction;
-import org.apache.felix.webconsole.internal.misc.ConfigurationRender;
-import org.apache.felix.webconsole.internal.misc.LicenseServlet;
-import org.apache.felix.webconsole.internal.misc.ShellServlet;
+import org.apache.felix.webconsole.*;
+import org.apache.felix.webconsole.internal.*;
+import org.apache.felix.webconsole.internal.compendium.*;
+import org.apache.felix.webconsole.internal.core.*;
+import org.apache.felix.webconsole.internal.misc.*;
import org.apache.felix.webconsole.internal.obr.BundleRepositoryRender;
import org.apache.felix.webconsole.internal.obr.RefreshRepoAction;
-import org.apache.felix.webconsole.internal.system.GCAction;
-import org.apache.felix.webconsole.internal.system.ShutdownAction;
-import org.apache.felix.webconsole.internal.system.ShutdownRender;
-import org.apache.felix.webconsole.internal.system.VMStatRender;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.ServiceReference;
-import org.osgi.framework.ServiceRegistration;
+import org.apache.felix.webconsole.internal.system.*;
+import org.osgi.framework.*;
import org.osgi.service.http.HttpContext;
import org.osgi.service.http.HttpService;
import org.osgi.service.log.LogService;
diff --git a/webconsole/src/main/resources/res/ui/bundles.js b/webconsole/src/main/resources/res/ui/bundles.js
index 3a2d57a..bc18bd0 100644
--- a/webconsole/src/main/resources/res/ui/bundles.js
+++ b/webconsole/src/main/resources/res/ui/bundles.js
@@ -40,7 +40,6 @@
footer( columns );
}
-
function installForm( /* int */ startLevel )
{
document.write( "<form method='post' enctype='multipart/form-data'>" );
diff --git a/webconsole/src/main/resources/res/ui/packages.js b/webconsole/src/main/resources/res/ui/packages.js
new file mode 100644
index 0000000..2a895b7
--- /dev/null
+++ b/webconsole/src/main/resources/res/ui/packages.js
@@ -0,0 +1,352 @@
+/*
+ * 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 renderDataTable( /* Array of Data Objects */ components )
+{
+ // number of actions plus 3 -- id, name and state
+ var columns = components.numActions + 3;
+
+ header( columns );
+
+ if (components.error)
+ {
+ error( columns, components.error );
+ }
+ else
+ {
+ data ( components.data );
+ }
+
+ footer( columns );
+}
+
+
+function header( /* int */ columns )
+{
+ document.write( "<table class='content' cellpadding='0' cellspacing='0' width='100%'>" );
+
+ document.write( "<tr class='content'>" );
+ document.write( "<td colspan='" + columns + "' class='content'> </th>" );
+ document.write( "</tr>" );
+
+ document.write( "<tr class='content'>" );
+ document.write( "<th class='content'>Name</th>" );
+ document.write( "<th class='content' width='100%'>Details</th>" );
+ document.write( "<th class='content'>Version</th>" );
+ document.write( "<th class='content' colspan='" + (columns - 3) + "'>Actions</th>" );
+ document.write( "</tr>" );
+
+}
+
+
+function error( /* int */ columns, /* String */ message )
+{
+ document.write( "<tr class='content'>" );
+ document.write( "<td class='content'> </td>" );
+ document.write( "<td class='content' colspan='" + (columns - 1) + "'>" + message + "</td>" );
+ document.write( "</tr>" );
+}
+
+
+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 footer( /* int */ columns )
+{
+ document.write( "<tr class='content'>" );
+ document.write( "<td colspan='" + columns + "' class='content'> </th>" );
+ document.write( "</tr>" );
+
+ document.write( "</table>" );
+}
+
+
+function entry( /* Object */ dataEntry, /* boolean */ singleEntry )
+{
+ var trElement = tr( null, { id: "entry" + dataEntry.id } );
+ entryInternal( trElement, dataEntry, singleEntry );
+ document.write( serialize( trElement ) );
+
+ // dataEntry detailed properties
+ trElement = tr( null, { id: "entry" + dataEntry.id + "_details" } );
+ if (dataEntry.props)
+ {
+ getDataEntryDetails( trElement, dataEntry.props );
+ }
+ document.write( serialize( trElement ) );
+}
+
+
+function entryInternal( /* Element */ parent, /* Object */ dataEntry, /* boolean */ singleEntry )
+{
+
+ var id = dataEntry.id;
+ var name = dataEntry.name;
+ var state = dataEntry.state;
+ var icon = singleEntry ? "left" : (dataEntry.props ? "down" : "right");
+ var event = singleEntry ? "history.back()" : "showDataEntryDetails(" + id + ")";
+
+ parent.appendChild( td( "content right", null, [ text( id ) ] ) );
+
+ parent.appendChild( td( "content", null, [
+ createElement( "img", null, {
+ src: appRoot + "/res/imgs/" + icon + ".gif",
+ onClick: event,
+ id: "entry" + id + "_inline"
+ } ),
+ text( "\u00a0" ),
+ createElement( "a", null, {
+ href: pluginRoot + "/" + id
+ }, [ text( name ) ]
+ )]
+ )
+ );
+
+ parent.appendChild( td( "content center", null, [ text( 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 )
+ {
+ var input = createElement( "input", "submit", {
+ type: 'button',
+ value: opLabel,
+ onClick: 'changeDataEntryState("' + id + '", "' + op + '");'
+ });
+ if (!enabled)
+ {
+ input.setAttribute( "disabled", true );
+ }
+ 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( "content", { 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();
+ trEl.appendChild( addText( td( "aligntop", { noWrap: true } ), prop.key ) );
+
+ var proptd = td( "aligntop" );
+ trEl.appendChild( proptd );
+
+ if (prop.value )
+ {
+ var values = new String( prop.value ).split( "<br />" );
+ for (var i=0; i < values.length; i++)
+ {
+ if (i > 0) { proptd.appendChild( createElement( "br" ) ); }
+ addText( proptd, values[i] );
+ }
+ }
+ else
+ {
+ addText( proptd, "\u00a0" );
+ }
+
+ tbody.appendChild( trEl );
+ }
+ }
+
+
+function showDetails(bundleId)
+{
+ var span = document.getElementById('bundle' + bundleId + '_details');
+}
+
+
+function showDataEntryDetails( id )
+{
+ var span = document.getElementById( 'entry' + id + '_details' );
+ if (span)
+ {
+ if (span.firstChild)
+ {
+ clearChildren( span );
+ newLinkValue( id, appRoot + "/res/imgs/right.gif" );
+ }
+ else
+ {
+ sendRequest( 'POST', pluginRoot + '/' + id, displayDataEntryDetails );
+ newLinkValue( id, appRoot + "/res/imgs/down.gif" );
+ }
+ }
+}
+
+
+function newLinkValue( /* long */ id, /* String */ newLinkValue )
+{
+
+ var link = document.getElementById( "entry" + id + "_inline" );
+ if (link)
+ {
+ link.src = newLinkValue;
+ }
+}
+
+
+function displayDataEntryDetails( obj )
+{
+ var span = document.getElementById('entry' + obj.id + '_details');
+ if (span)
+ {
+ clearChildren( span );
+ getDataEntryDetails( span, obj.props );
+ }
+
+}
+
+
+function changeDataEntryState(/* long */ id, /* String */ action)
+{
+ var parm = pluginRoot + "/" + id + "?action=" + action;
+ sendRequest('POST', parm, dataEntryStateChanged);
+}
+
+
+function dataEntryStateChanged(obj)
+{
+ if (obj.reload)
+ {
+ document.location = document.location;
+ }
+ else
+ {
+ var id = obj.id;
+ if (obj.state)
+ {
+ // has status, so draw the line
+ if (obj.props)
+ {
+ var span = document.getElementById('entry' + id + '_details');
+ if (span && span.firstChild)
+ {
+ clearChildren( span );
+ getDataEntryDetails( span, obj.props );
+ }
+ else
+ {
+ obj.props = false;
+ }
+ }
+
+ var span = document.getElementById('entry' + id);
+ if (span)
+ {
+ clearChildren( span );
+ entryInternal( span, obj );
+ }
+ }
+ else
+ {
+ // no status, dataEntry has been removed/uninstalled
+ var span = document.getElementById('entry' + id);
+ if (span)
+ {
+ span.parentNode.removeChild(span);
+ }
+ var span = document.getElementById('entry' + id + '_details');
+ if (span)
+ {
+ span.parentNode.removeChild(span);
+ }
+ }
+ }
+}
+
+function renderPackage( /* Array of Data Objects */ bundleData )
+{
+
+ // number of actions plus 3 -- id, name and state
+ var columns = 4;
+
+ header( columns );
+
+ installForm( );
+
+ if (bundleData.error)
+ {
+ error( columns, bundleData.error );
+ }
+ else
+ {
+ data ( bundleData.data );
+ }
+
+ installForm( );
+
+ footer( columns );
+}
+
+function installForm( )
+{
+ document.write( "<form method='post' enctype='multipart/form-data'>" );
+ document.write( "<tr class='content'>" );
+ document.write( "<td class='content'> </td>" );
+ document.write( "<td class='content'>" );
+ document.write( "<input type='hidden' name='action' value='deploydp' />" );
+ document.write( "<input class='input' type='file' name='pckfile' size='50'>" );
+ document.write( "</td>" );
+ document.write( "<td class='content' align='right' colspan='5' noWrap>" );
+ document.write( "<input class='submit' style='width:auto' type='submit' value='Install or Update'>" );
+ document.write( "</td>" );
+ document.write( "</tr>" );
+ document.write( "</form>" );
+}