Modify the pom to include 'res'
Add the factories template
Add the instances template
Add the ipojo,js retrieving the instance list
Add the factories.js retrieving the factory list
Modify the IPOJOPlugin to support the factories and
instances template as well as the JSON messages
to retrives the lists.

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@963381 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/ipojo/webconsole-plugin/src/main/java/org/apache/felix/ipojo/webconsole/IPOJOPlugin.java b/ipojo/webconsole-plugin/src/main/java/org/apache/felix/ipojo/webconsole/IPOJOPlugin.java
index 1c6bfd8..915b653 100644
--- a/ipojo/webconsole-plugin/src/main/java/org/apache/felix/ipojo/webconsole/IPOJOPlugin.java
+++ b/ipojo/webconsole-plugin/src/main/java/org/apache/felix/ipojo/webconsole/IPOJOPlugin.java
@@ -1,12 +1,17 @@
 package org.apache.felix.ipojo.webconsole;
 
+import java.io.ByteArrayOutputStream;
 import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintWriter;
+import java.net.URL;
 import java.util.List;
 
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
+import org.apache.felix.ipojo.ComponentInstance;
 import org.apache.felix.ipojo.Factory;
 import org.apache.felix.ipojo.HandlerFactory;
 import org.apache.felix.ipojo.annotations.Component;
@@ -16,12 +21,23 @@
 import org.apache.felix.ipojo.annotations.ServiceProperty;
 import org.apache.felix.ipojo.architecture.Architecture;
 import org.apache.felix.webconsole.AbstractWebConsolePlugin;
+import org.apache.felix.webconsole.DefaultVariableResolver;
+import org.apache.felix.webconsole.SimpleWebConsolePlugin;
+import org.apache.felix.webconsole.WebConsoleUtil;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
 
-@Component
+@Component(immediate=true)
 @Provides
 @Instantiate
 public class IPOJOPlugin extends AbstractWebConsolePlugin {
+    
+    private static final String CSS[] = { "/res/ui/bundles.css" };
 
+    private final String INSTANCES;
+    private final String FACTORIES;
+    
     /**
      * Label used by the web console.
      */
@@ -33,6 +49,9 @@
      */
     @ServiceProperty(name = "felix.webconsole.title")
     private String m_title = "iPOJO_2";  // TODO CHANGE
+    
+    @ServiceProperty(name= "felix.webconsole.css")
+    private String m_css = CSS[0];
 
     /**
      * List of available Architecture service.
@@ -52,21 +71,325 @@
     @Requires(optional = true, specification = "org.apache.felix.ipojo.HandlerFactory")
     private List<HandlerFactory> m_handlers;
     
+    public IPOJOPlugin() {
+        INSTANCES = readTemplateFile(this.getClass(), "/res/instances.html" );
+        FACTORIES = readTemplateFile(this.getClass(), "/res/factories.html" );
+
+    }
+    
+    private final String readTemplateFile(final Class clazz, final String templateFile)
+    {
+        InputStream templateStream = getClass().getResourceAsStream(templateFile);
+        if (templateStream != null)
+        {
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            byte[] data = new byte[1024];
+            try
+            {
+                int len = 0;
+                while ((len = templateStream.read(data)) > 0)
+                {
+                    baos.write(data, 0, len);
+                }
+                return baos.toString("UTF-8");
+            }
+            catch (IOException e)
+            {
+                // don't use new Exception(message, cause) because cause is 1.4+
+                throw new RuntimeException("readTemplateFile: Error loading "
+                    + templateFile + ": " + e);
+            }
+            finally
+            {
+                try
+                {
+                    templateStream.close();
+                }
+                catch (IOException e)
+                {
+                    /* ignore */
+                }
+
+            }
+        }
+
+        // template file does not exist, return an empty string
+        log("readTemplateFile: File '" + templateFile + "' not found through class "
+            + clazz);
+        return "";
+    }
+
+    @Override
+    protected void renderContent(HttpServletRequest request, HttpServletResponse response)
+            throws ServletException, IOException {
+        // get request info from request attribute
+        final RequestInfo reqInfo = getRequestInfo(request);
+        // prepare variables
+        DefaultVariableResolver vars = ( ( DefaultVariableResolver ) WebConsoleUtil.getVariableResolver( request ) );
+        String view = request.getParameter("view");
+        
+        if (view == null || view.equals("instances")) {
+            response.getWriter().print( INSTANCES );
+        } else if (view.equals("factories")) {
+            response.getWriter().print( FACTORIES );
+        }                
+    }
+    
+    private void renderAllInstances(PrintWriter pw) {
+        try {
+            JSONObject resp = new JSONObject();
+            resp.put("count", m_archs.size());
+            resp.put("valid_count", getValidCount());
+            resp.put("invalid_count", getInvalidCount());
+            
+            JSONArray instances = new JSONArray();
+            for (Architecture arch : m_archs) {
+                JSONObject instance = new JSONObject();
+                instance.put("name", arch.getInstanceDescription().getName());
+                instance.put("factory", arch.getInstanceDescription().getComponentDescription().getName());
+                instance.put("state", getInstanceState(arch.getInstanceDescription().getState()));
+                instances.put(instance);
+            }
+            resp.put("data", instances);
+            
+            pw.print(resp.toString());
+        } catch (JSONException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        }
+    }
+    
+    private void renderAllFactories(PrintWriter pw) {
+        try {
+            JSONObject resp = new JSONObject();
+            resp.put("count", m_factories.size());
+            resp.put("valid_count", getValidFactoriesCount());
+            resp.put("invalid_count", getValidFactoriesCount());
+            
+            JSONArray factories = new JSONArray();
+            for (Factory factory : m_factories) {
+                String version = factory.getVersion();
+                String name = factory.getName();
+                
+                String state = getFactoryState(factory.getState());
+                String bundle = factory.getBundleContext().getBundle().getSymbolicName()
+                    + " (" + factory.getBundleContext().getBundle().getBundleId() + ")";
+                JSONObject fact = new JSONObject();
+                fact.put("name", name);
+                if (version != null) {
+                    fact.put("version", version);
+                }
+                fact.put("bundle", bundle);
+                fact.put("state", state);
+                factories.put(fact);
+            }
+            resp.put("data", factories);
+            
+            pw.print(resp.toString());
+        } catch (JSONException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        }
+    }
+    
+    @Override
+    protected void doGet(HttpServletRequest request,
+            HttpServletResponse response) throws ServletException, IOException {
+        final RequestInfo reqInfo = new RequestInfo(request);
+        
+        if ( reqInfo.extension.equals("json")  )
+        {
+            response.setContentType( "application/json" );
+            //response.setCharacterEncoding( "UTF-8" );
+            if (reqInfo.instances) {
+                if (reqInfo.name == null) {
+                    this.renderAllInstances(response.getWriter());
+                    return;
+                } else {
+                    // TODO
+                    return;
+                }
+            }
+            
+            if (reqInfo.factories) {
+                if (reqInfo.name == null) {
+                    this.renderAllFactories(response.getWriter());
+                    return;
+                } else {
+                    // TODO
+                    return;
+                }
+            }
+            
+            if (reqInfo.handlers) {
+                //TODO
+                return;
+            }
+            // nothing more to do
+            return;
+        }
+        super.doGet( request, response );
+    }
+    
+    public URL getResource(String path) {
+        if (path.contains("/res/ui/")) {
+            return this.getClass().getResource(
+                    path.substring(m_label.length() + 1));
+        }
+        return null;
+    }
+    
+    /**
+     * Gets the number of valid instances.
+     * @return the number of valid instances.
+     */
+    private int getValidCount() {
+        int i = 0;
+        for (Architecture a : m_archs) { // Cannot be null, an empty list is returned.
+            if (a.getInstanceDescription().getState() == ComponentInstance.VALID) {
+                i ++;
+            }
+        }
+        return i;
+    }
+
+    /**
+     * Gets the number of invalid instances.
+     * @return the number of invalid instances.
+     */
+    private int getInvalidCount() {
+        int i = 0;
+        for (Architecture a : m_archs) {  // Cannot be null, an empty list is returned.
+            if (a.getInstanceDescription().getState() == ComponentInstance.INVALID) {
+                i ++;
+            }
+        }
+        return i;
+    }
+    
+    private int getValidFactoriesCount() {
+        int i = 0;
+        for (Factory a : m_factories) { // Cannot be null, an empty list is returned.
+            if (a.getState() == Factory.VALID) {
+                i ++;
+            }
+        }
+        return i;
+    }
+    
+    private int getInvalidFactoriesCount() {
+        int i = 0;
+        for (Factory a : m_factories) { // Cannot be null, an empty list is returned.
+            if (a.getState() == Factory.INVALID) {
+                i ++;
+            }
+        }
+        return i;
+    }
+    
+    /**
+     * Gets the instance state as a String.
+     * @param state the state.
+     * @return the String form of the state.
+     */
+    private static String getInstanceState(int state) {
+        switch(state) {
+            case ComponentInstance.VALID :
+                return "valid";
+            case ComponentInstance.INVALID :
+                return "invalid";
+            case ComponentInstance.DISPOSED :
+                return "disposed";
+            case ComponentInstance.STOPPED :
+                return "stopped";
+            default :
+                return "unknown";
+        }
+    }
+
+    /**
+     * Gets the factory state as a String.
+     * @param state the state.
+     * @return the String form of the state.
+     */
+    private static String getFactoryState(int state) {
+        switch(state) {
+            case Factory.VALID :
+                return "valid";
+            case Factory.INVALID :
+                return "invalid";
+            default :
+                return "unknown";
+        }
+    }
+    
+    private final class RequestInfo {
+        public final String extension;
+        public final String path;
+        public final boolean instances;
+        public final boolean factories;
+        public final boolean handlers;
+        
+        public final String name;
+
+        
+        protected RequestInfo( final HttpServletRequest request ) {
+            String info = request.getPathInfo();
+            // remove label and starting slash
+            info = info.substring(getLabel().length() + 1);
+
+            // get extension
+            if (info.endsWith(".json")) {
+                extension = "json";
+                info = info.substring(0, info.length() - 5);
+            } else {
+                extension = "html";
+            }
+
+            if (info.startsWith("/")) {
+                path = info.substring(1);
+                instances = path.startsWith("instances");
+                factories = path.startsWith("factories");
+                handlers = path.startsWith("handlers");
+               
+                
+                if (instances  && path.startsWith("instances/")) {
+                    name = path.substring(0, "instances".length() + 1);
+                } else if (factories  && path.startsWith("factories/")) {
+                    name = path.substring(0, "factories".length() + 1);
+                } else {
+                    name = null;
+                }
+            } else {
+                path = null;
+                name = null;
+                instances = false;
+                factories = false;
+                handlers = false;
+            }
+           
+            request.setAttribute(IPOJOPlugin.class.getName(), this);
+        }
+
+    }
+
+    static RequestInfo getRequestInfo(final HttpServletRequest request) {
+        return (RequestInfo) request.getAttribute(IPOJOPlugin.class.getName());
+    }
+
     @Override
     public String getLabel() {
-        return m_label;
+       return m_label;
     }
 
     @Override
     public String getTitle() {
         return m_title;
     }
-
+    
     @Override
-    protected void renderContent(HttpServletRequest req, HttpServletResponse res)
-            throws ServletException, IOException {
-        // TODO Auto-generated method stub
-        
+    protected String[] getCssReferences() {
+        return CSS;
     }
 
 }
diff --git a/ipojo/webconsole-plugin/src/main/resources/res/factories.html b/ipojo/webconsole-plugin/src/main/resources/res/factories.html
new file mode 100644
index 0000000..16a186a
--- /dev/null
+++ b/ipojo/webconsole-plugin/src/main/resources/res/factories.html
@@ -0,0 +1,43 @@
+<script type="text/javascript" src="${pluginRoot}/res/ui/factory.js"></script>
+
+
+<!-- status line -->
+<p class="statline">&nbsp;</p>
+
+<!-- top header -->
+<form method="post" enctype="multipart/form-data" action="">
+	<div class="ui-widget-header ui-corner-top buttonGroup">
+		<button class="instancesButton" type="button">Instances</button>
+		<button class="factoriesButton" type="button">Factories</button>
+		<button class="handlersButton" type="button">Handlers</button>
+	</div>
+</form>
+
+<table id="plugin_table" class="tablesorter nicetable noauto">
+	<thead>
+		<tr>
+			<th class="col_Name">Factory Name</th>
+			<th class="col_Factory">Bundle</th>
+			<th class="col_State">State</th>
+		</tr>
+	</thead>
+	<tbody>
+		<tr><!-- template -->
+			<td class="name">&nbsp;	</td>
+			<td class="bundle">&nbsp;</td><!-- factory -->
+			<td class="state">&nbsp;</td><!-- state -->
+		</tr>
+	</tbody>
+</table>
+
+<!-- bottom header -->
+<form method="post" enctype="multipart/form-data" action="">
+    <div class="ui-widget-header ui-corner-bottom buttonGroup">
+        <button class="instancesButton" type="button">Instances</button>
+        <button class="factoriesButton" type="button">Factories</button>
+        <button class="handlersButton" type="button">Handlers</button>
+    </div>
+</form>
+
+<!-- status line -->
+<p class="statline">&nbsp;</p>
diff --git a/ipojo/webconsole-plugin/src/main/resources/res/instances.html b/ipojo/webconsole-plugin/src/main/resources/res/instances.html
new file mode 100644
index 0000000..42c3cc0
--- /dev/null
+++ b/ipojo/webconsole-plugin/src/main/resources/res/instances.html
@@ -0,0 +1,42 @@
+<script type="text/javascript" src="${pluginRoot}/res/ui/ipojo.js"></script>
+
+<!-- status line -->
+<p class="statline">&nbsp;</p>
+
+<!-- top header -->
+<form method="post" enctype="multipart/form-data" action="">
+	<div class="ui-widget-header ui-corner-top buttonGroup">
+		<button class="instancesButton" type="button">Instances</button>
+		<button class="factoriesButton" type="button">Factories</button>
+		<button class="handlersButton" type="button">Handlers</button>
+	</div>
+</form>
+
+<table id="plugin_table" class="tablesorter nicetable noauto">
+	<thead>
+		<tr>
+			<th class="col_Name">Instance Name</th>
+			<th class="col_Factory">Factory</th>
+			<th class="col_State">State</th>
+		</tr>
+	</thead>
+	<tbody>
+		<tr><!-- template -->
+			<td class="name">&nbsp;	</td>
+			<td class="factory">&nbsp;</td><!-- factory -->
+			<td class="state">&nbsp;</td><!-- state -->
+		</tr>
+	</tbody>
+</table>
+
+<!-- bottom header -->
+<form method="post" enctype="multipart/form-data" action="">
+    <div class="ui-widget-header ui-corner-bottom buttonGroup">
+        <button class="instancesButton" type="button">Instances</button>
+        <button class="factoriesButton" type="button">Factories</button>
+        <button class="handlersButton" type="button">Handlers</button>
+    </div>
+</form>
+
+<!-- status line -->
+<p class="statline">&nbsp;</p>
diff --git a/ipojo/webconsole-plugin/src/main/resources/res/ui/factory.js b/ipojo/webconsole-plugin/src/main/resources/res/ui/factory.js
new file mode 100644
index 0000000..67f03e6
--- /dev/null
+++ b/ipojo/webconsole-plugin/src/main/resources/res/ui/factory.js
@@ -0,0 +1,88 @@
+/*
+ * 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 renderFactoriesData(factories)  {
+    $(".statline").html(getFactoriesStatLine(factories));
+    tableBody.empty();
+    for ( var idx in factories.data ) {
+        factoriesEntry( factories.data[idx] );
+    }
+    $("#plugin_table").trigger("update");
+}
+
+function getFactoriesStatLine(factories) {
+    return factories.count + " factories in total, "
+        + factories.valid_count + " valid factories, "
+        + factories.invalid_count + " invalid factories.";
+}
+
+function factoriesEntry(factory) {
+    var name = factory.name;
+    var state = factory.state;
+    var bundle = factory.bundle;
+
+    console.log("Create entry : " + factory);
+
+    var _ = tableEntryTemplate.clone().appendTo(tableBody).attr('id', 'factory-' + factory.name);
+
+    _.find('td.name').html('<a href="' + window.location.pathname + '/factories/' + name + '">' + name + '</a>');
+    _.find('td.bundle').text(bundle);
+    _.find('td.state').text(state);
+}
+
+
+function loadFactoriesData() {
+    console.log("Load factories data");
+	$.get(pluginRoot + "/factories.json", null, function(data) {
+		renderFactoriesData(data);
+	}, "json");	
+}
+
+function loadInstancesData() {
+    console.log("Go to instances"); 
+    window.location=window.location.pathname + "?view=instances"
+}
+
+function loadHandlersData() {
+    console.log("Load handlers data"); 
+}
+
+var tableBody = false;
+var tableEntryTemplate = false;
+
+$(document).ready(function(){
+	tableBody = $('#plugin_table tbody');
+	tableEntryTemplate = tableBody.find('tr').clone();
+
+    loadFactoriesData();
+	
+    $(".instancesButton").click(loadInstancesData);
+    $(".factoriesButton").click(loadFactoriesData);
+    $(".handlersButton").click(loadHandlersData);
+
+	var extractMethod = function(node) {
+		var link = node.getElementsByTagName("a");
+		if ( link && link.length == 1 ) {
+			return link[0].innerHTML;
+		}
+		return node.innerHTML;
+	};
+	$("#plugin_table").tablesorter({
+		sortList: [[1,0]],
+		textExtraction:extractMethod
+	});
+});
+
diff --git a/ipojo/webconsole-plugin/src/main/resources/res/ui/ipojo.js b/ipojo/webconsole-plugin/src/main/resources/res/ui/ipojo.js
new file mode 100644
index 0000000..6504308
--- /dev/null
+++ b/ipojo/webconsole-plugin/src/main/resources/res/ui/ipojo.js
@@ -0,0 +1,85 @@
+/*
+ * 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 renderInstancesData(instances)  {
+    $(".statline").html(getInstancesStatLine(instances));
+    tableBody.empty();
+    for ( var idx in instances.data ) {
+        instancesEntry( instances.data[idx] );
+    }
+    $("#plugin_table").trigger("update");
+}
+
+function getInstancesStatLine(instances) {
+    return instances.count + " instances in total, "
+        + instances.valid_count + " valid instances, "
+        + instances.invalid_count + " invalid instances.";
+}
+
+function instancesEntry(instance) {
+    var name = instance.name;
+    var state = instance.state;
+    var factory = instance.factory;
+
+    var _ = tableEntryTemplate.clone().appendTo(tableBody).attr('id', 'instance-' + instance.name);
+
+    _.find('td.name').html('<a href="' + window.location.pathname + '/instance/' + name + '">' + name + '</a>');
+    _.find('td.factory').html('<a href="' + window.location.pathname + '/factory/' + factory + '">' + factory + '</a>');;
+    _.find('td.state').text(state);
+}
+
+
+function loadInstancesData() {
+	$.get(pluginRoot + "/instances.json", null, function(data) {
+		renderInstancesData(data);
+	}, "json");	
+}
+
+function loadFactoriesData() {
+    console.log("Go to factories data");
+    window.location=window.location.pathname + '?view=factories'; 
+}
+
+function loadHandlersData() {
+    console.log("Load handlers data"); 
+}
+
+var tableBody = false;
+var tableEntryTemplate = false;
+
+$(document).ready(function(){
+	tableBody = $('#plugin_table tbody');
+	tableEntryTemplate = tableBody.find('tr').clone();
+
+    loadInstancesData();
+	
+    $(".instancesButton").click(loadInstancesData);
+    $(".factoriesButton").click(loadFactoriesData);
+    $(".handlersButton").click(loadHandlersData);
+
+	var extractMethod = function(node) {
+		var link = node.getElementsByTagName("a");
+		if ( link && link.length == 1 ) {
+			return link[0].innerHTML;
+		}
+		return node.innerHTML;
+	};
+	$("#plugin_table").tablesorter({
+		sortList: [[1,0]],
+		textExtraction:extractMethod
+	});
+});
+