You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by cz...@apache.org on 2010/02/05 15:12:00 UTC

svn commit: r906942 - in /sling/trunk/bundles/engine/src/main/java/org/apache/sling/engine/impl: EngineBundleActivator.java SlingMainServlet.java WebConsoleConfigPrinter.java request/RequestHistoryConsolePlugin.java

Author: cziegeler
Date: Fri Feb  5 14:11:59 2010
New Revision: 906942

URL: http://svn.apache.org/viewvc?rev=906942&view=rev
Log:
SLING-1354 : Dependency to web console is not optional

Added:
    sling/trunk/bundles/engine/src/main/java/org/apache/sling/engine/impl/WebConsoleConfigPrinter.java   (with props)
Modified:
    sling/trunk/bundles/engine/src/main/java/org/apache/sling/engine/impl/EngineBundleActivator.java
    sling/trunk/bundles/engine/src/main/java/org/apache/sling/engine/impl/SlingMainServlet.java
    sling/trunk/bundles/engine/src/main/java/org/apache/sling/engine/impl/request/RequestHistoryConsolePlugin.java

Modified: sling/trunk/bundles/engine/src/main/java/org/apache/sling/engine/impl/EngineBundleActivator.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/engine/src/main/java/org/apache/sling/engine/impl/EngineBundleActivator.java?rev=906942&r1=906941&r2=906942&view=diff
==============================================================================
--- sling/trunk/bundles/engine/src/main/java/org/apache/sling/engine/impl/EngineBundleActivator.java (original)
+++ sling/trunk/bundles/engine/src/main/java/org/apache/sling/engine/impl/EngineBundleActivator.java Fri Feb  5 14:11:59 2010
@@ -51,7 +51,11 @@
         serviceRegistration = context.registerService(SlingSettingsService.class.getName(),
                                                       service,
                                                       props);
-        RequestHistoryConsolePlugin.initPlugin(context);
+        try {
+            RequestHistoryConsolePlugin.initPlugin(context);
+        } catch (Throwable ignore) {
+            // we just ignore this
+        }
     }
 
     /**
@@ -62,6 +66,10 @@
             serviceRegistration.unregister();
             serviceRegistration = null;
         }
-        RequestHistoryConsolePlugin.destroyPlugin();
+        try {
+            RequestHistoryConsolePlugin.destroyPlugin();
+        } catch (Throwable ignore) {
+            // we just ignore this
+        }
     }
 }

Modified: sling/trunk/bundles/engine/src/main/java/org/apache/sling/engine/impl/SlingMainServlet.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/engine/src/main/java/org/apache/sling/engine/impl/SlingMainServlet.java?rev=906942&r1=906941&r2=906942&view=diff
==============================================================================
--- sling/trunk/bundles/engine/src/main/java/org/apache/sling/engine/impl/SlingMainServlet.java (original)
+++ sling/trunk/bundles/engine/src/main/java/org/apache/sling/engine/impl/SlingMainServlet.java Fri Feb  5 14:11:59 2010
@@ -46,7 +46,6 @@
 import javax.servlet.http.HttpServletRequestWrapper;
 import javax.servlet.http.HttpServletResponse;
 
-import org.apache.felix.webconsole.ConfigurationPrinter;
 import org.apache.sling.api.SlingException;
 import org.apache.sling.api.SlingHttpServletRequest;
 import org.apache.sling.api.SlingHttpServletResponse;
@@ -76,6 +75,7 @@
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.Constants;
 import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
 import org.osgi.framework.Version;
 import org.osgi.service.component.ComponentContext;
 import org.osgi.service.http.HttpContext;
@@ -88,7 +88,6 @@
  *
  * @scr.component immediate="true" label="%sling.name"
  *                description="%sling.description"
- * @scr.service interface="ConfigurationPrinter"
  * @scr.property name="service.vendor" value="The Apache Software Foundation"
  * @scr.property name="service.description" value="Sling Servlet"
  * @scr.reference name="Filter" interface="javax.servlet.Filter"
@@ -96,7 +95,7 @@
  */
 @SuppressWarnings("serial")
 public class SlingMainServlet extends GenericServlet implements ErrorHandler,
-        HttpContext, ConfigurationPrinter {
+        HttpContext {
 
     /** @scr.property valueRef="RequestData.DEFAULT_MAX_CALL_COUNTER" */
     public static final String PROP_MAX_CALL_COUNTER = "sling.max.calls";
@@ -106,7 +105,7 @@
 
     /** @scr.property valueRef="DEFAULT_ALLOW_TRACE" */
     public static final String PROP_ALLOW_TRACE = "sling.trace.allow";
-    
+
     public static final boolean DEFAULT_ALLOW_TRACE = false;
 
     /** default log */
@@ -182,6 +181,8 @@
 
     private boolean allowTrace = DEFAULT_ALLOW_TRACE;
 
+    private ServiceRegistration printerRegistration;
+
     // ---------- Servlet API -------------------------------------------------
 
     public void service(ServletRequest req, ServletResponse res)
@@ -572,10 +573,10 @@
         osgiComponentContext = componentContext;
 
         // setup server info
-        BundleContext bundleContext = componentContext.getBundleContext();
-        Dictionary<?, ?> props = bundleContext.getBundle().getHeaders();
-        Version bundleVersion = Version.parseVersion((String) props.get(Constants.BUNDLE_VERSION));
-        String productVersion = bundleVersion.getMajor() + "."
+        final BundleContext bundleContext = componentContext.getBundleContext();
+        final Dictionary<?, ?> props = bundleContext.getBundle().getHeaders();
+        final Version bundleVersion = Version.parseVersion((String) props.get(Constants.BUNDLE_VERSION));
+        final String productVersion = bundleVersion.getMajor() + "."
             + bundleVersion.getMinor();
         this.serverInfo = PRODUCT_NAME + "/" + productVersion + " ("
             + System.getProperty("java.vm.name") + " "
@@ -585,8 +586,8 @@
             + System.getProperty("os.arch") + ")";
 
         // prepare the servlet configuration from the component config
-        Hashtable<String, Object> configuration = new Hashtable<String, Object>();
-        Dictionary<?, ?> componentConfig = componentContext.getProperties();
+        final Hashtable<String, Object> configuration = new Hashtable<String, Object>();
+        final Dictionary<?, ?> componentConfig = componentContext.getProperties();
         for (Enumeration<?> cce = componentConfig.keys(); cce.hasMoreElements();) {
             Object key = cce.nextElement();
             configuration.put(String.valueOf(key), componentConfig.get(key));
@@ -597,7 +598,7 @@
             configuration.put("servlet-name", PRODUCT_NAME + " "
                 + productVersion);
         }
-        
+
         // configure method filter
         allowTrace = OsgiUtil.toBoolean(componentConfig.get(PROP_ALLOW_TRACE),
                 DEFAULT_ALLOW_TRACE);
@@ -625,7 +626,7 @@
 
         // now that the sling main servlet is registered with the HttpService
         // and initialized we can register the servlet context
-        SlingServletContext tmpServletContext = new SlingServletContext(this);
+        final SlingServletContext tmpServletContext = new SlingServletContext(this);
 
         // register render filters already registered after registration with
         // the HttpService as filter initialization may cause the servlet
@@ -649,11 +650,22 @@
                 initFilter(componentContext, serviceReference);
             }
         }
+
+        // try to setup configuration printer
+        try {
+            this.printerRegistration = WebConsoleConfigPrinter.register(bundleContext, requestFilterChain, innerFilterChain);
+
+        } catch (Throwable t) {
+            log.debug("Unable to register web console configuration printer.", t);
+        }
     }
 
     protected void deactivate(ComponentContext componentContext) {
-
         // this reverses the activation setup
+        if ( this.printerRegistration != null ) {
+            this.printerRegistration.unregister();
+            this.printerRegistration = null;
+        }
 
         // first destroy the filters
         destroyFilters(innerFilterChain);
@@ -888,43 +900,4 @@
         // return the previous thread name
         return oldThreadName;
     }
-
-    /**
-     * Return the title for the configuration printer
-     * @see org.apache.felix.webconsole.ConfigurationPrinter#getTitle()
-     */
-    public String getTitle() {
-        return "Servlet Filter";
-    }
-
-    /**
-     * Helper method for printing out a filter chain.
-     */
-    private void printFilterChain(final PrintWriter pw, final SlingFilterChainHelper.FilterListEntry[] entries) {
-        if ( entries == null ) {
-            pw.println("---");
-        } else {
-            for(final SlingFilterChainHelper.FilterListEntry entry : entries) {
-                pw.print(entry.getOrder());
-                pw.print(" : ");
-                pw.print(entry.getFilter().getClass());
-                pw.print(" (");
-                pw.print(entry.getFitlerId());
-                pw.println(")");
-            }
-        }
-    }
-    /**
-     * Print out the servlet filter chains.
-     * @see org.apache.felix.webconsole.ConfigurationPrinter#printConfiguration(java.io.PrintWriter)
-     */
-    public void printConfiguration(PrintWriter pw) {
-        pw.println("Current Apache Sling Servlet Filter Configuration");
-        pw.println();
-        pw.println("Request Filters:");
-        printFilterChain(pw, this.requestFilterChain.getFilterListEntries());
-        pw.println();
-        pw.println("Component Filters:");
-        printFilterChain(pw, this.innerFilterChain.getFilterListEntries());
-    }
 }

Added: sling/trunk/bundles/engine/src/main/java/org/apache/sling/engine/impl/WebConsoleConfigPrinter.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/engine/src/main/java/org/apache/sling/engine/impl/WebConsoleConfigPrinter.java?rev=906942&view=auto
==============================================================================
--- sling/trunk/bundles/engine/src/main/java/org/apache/sling/engine/impl/WebConsoleConfigPrinter.java (added)
+++ sling/trunk/bundles/engine/src/main/java/org/apache/sling/engine/impl/WebConsoleConfigPrinter.java Fri Feb  5 14:11:59 2010
@@ -0,0 +1,97 @@
+/*
+ * 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.sling.engine.impl;
+
+import java.io.PrintWriter;
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+import org.apache.felix.webconsole.ConfigurationPrinter;
+import org.apache.sling.engine.impl.filter.SlingFilterChainHelper;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceRegistration;
+
+/**
+ * This is a configuration printer for the web console which
+ * prints out the currently configured filter chains.
+ *
+ */
+public class WebConsoleConfigPrinter implements ConfigurationPrinter {
+
+    private final SlingFilterChainHelper requestFilterChain;
+    private final SlingFilterChainHelper innerFilterChain;
+
+    public WebConsoleConfigPrinter(final SlingFilterChainHelper requestFilterChain,
+            final SlingFilterChainHelper innerFilterChain) {
+        this.requestFilterChain = requestFilterChain;
+        this.innerFilterChain = innerFilterChain;
+    }
+
+    public static ServiceRegistration register(final BundleContext bundleContext,
+            final SlingFilterChainHelper requestFilterChain,
+            final SlingFilterChainHelper innerFilterChain) {
+        final Object service = new WebConsoleConfigPrinter(requestFilterChain, innerFilterChain);
+        final Dictionary<String, String> serviceProps = new Hashtable<String, String>();
+        serviceProps.put(Constants.SERVICE_DESCRIPTION,
+            "Apache Sling Servlet Filter Configuration Printer");
+        serviceProps.put(Constants.SERVICE_VENDOR, "The Apache Software Foundation");
+        return bundleContext.registerService("org.apache.felix.webconsole.ConfigurationPrinter",
+                                                      service,
+                                                      serviceProps);
+    }
+    /**
+     * Return the title for the configuration printer
+     * @see org.apache.felix.webconsole.ConfigurationPrinter#getTitle()
+     */
+    public String getTitle() {
+        return "Servlet Filter";
+    }
+
+    /**
+     * Helper method for printing out a filter chain.
+     */
+    private void printFilterChain(final PrintWriter pw, final SlingFilterChainHelper.FilterListEntry[] entries) {
+        if ( entries == null ) {
+            pw.println("---");
+        } else {
+            for(final SlingFilterChainHelper.FilterListEntry entry : entries) {
+                pw.print(entry.getOrder());
+                pw.print(" : ");
+                pw.print(entry.getFilter().getClass());
+                pw.print(" (");
+                pw.print(entry.getFitlerId());
+                pw.println(")");
+            }
+        }
+    }
+    /**
+     * Print out the servlet filter chains.
+     * @see org.apache.felix.webconsole.ConfigurationPrinter#printConfiguration(java.io.PrintWriter)
+     */
+    public void printConfiguration(PrintWriter pw) {
+        pw.println("Current Apache Sling Servlet Filter Configuration");
+        pw.println();
+        pw.println("Request Filters:");
+        printFilterChain(pw, requestFilterChain.getFilterListEntries());
+        pw.println();
+        pw.println("Component Filters:");
+        printFilterChain(pw, innerFilterChain.getFilterListEntries());
+    }
+}

Propchange: sling/trunk/bundles/engine/src/main/java/org/apache/sling/engine/impl/WebConsoleConfigPrinter.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sling/trunk/bundles/engine/src/main/java/org/apache/sling/engine/impl/WebConsoleConfigPrinter.java
------------------------------------------------------------------------------
    svn:keywords = author date id revision rev url

Propchange: sling/trunk/bundles/engine/src/main/java/org/apache/sling/engine/impl/WebConsoleConfigPrinter.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Modified: sling/trunk/bundles/engine/src/main/java/org/apache/sling/engine/impl/request/RequestHistoryConsolePlugin.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/engine/src/main/java/org/apache/sling/engine/impl/request/RequestHistoryConsolePlugin.java?rev=906942&r1=906941&r2=906942&view=diff
==============================================================================
--- sling/trunk/bundles/engine/src/main/java/org/apache/sling/engine/impl/request/RequestHistoryConsolePlugin.java (original)
+++ sling/trunk/bundles/engine/src/main/java/org/apache/sling/engine/impl/request/RequestHistoryConsolePlugin.java Fri Feb  5 14:11:59 2010
@@ -43,25 +43,16 @@
  * useful when testing or explaining things.
  */
 @SuppressWarnings("serial")
-public class RequestHistoryConsolePlugin extends AbstractWebConsolePlugin {
+public class RequestHistoryConsolePlugin {
 
     public static final String LABEL = "requests";
     public static final String INDEX = "index";
     public static final String CLEAR = "clear";
 
-    private static RequestHistoryConsolePlugin instance;
-
-    private ServiceRegistration serviceRegistration;
+    private static Plugin instance;
 
     public static final int STORED_REQUESTS_COUNT = 20;
 
-    private final SlingHttpServletRequest[] requests = new SlingHttpServletRequest[STORED_REQUESTS_COUNT];
-
-    /** Need to store methods separately, apparently requests clear this data when done processing */
-    private final String [] methods = new String[STORED_REQUESTS_COUNT];
-
-    private int lastRequestIndex = -1;
-
     private RequestHistoryConsolePlugin() {
     }
 
@@ -71,26 +62,9 @@
         }
     }
 
-    private synchronized void addRequest(SlingHttpServletRequest r) {
-        int index = lastRequestIndex + 1;
-        if (index >= requests.length) {
-            index = 0;
-        }
-        requests[index] = r;
-        methods[index] = r.getMethod();
-        lastRequestIndex = index;
-    }
-
-    private synchronized void clear() {
-        for(int i=0; i < requests.length; i++) {
-            requests[i] = null;
-        }
-        lastRequestIndex = -1;
-    }
-
     public static void initPlugin(BundleContext context) {
         if (instance == null) {
-            RequestHistoryConsolePlugin tmp = new RequestHistoryConsolePlugin();
+            Plugin tmp = new Plugin();
             tmp.activate(context);
             instance = tmp;
         }
@@ -106,185 +80,214 @@
         }
     }
 
-    public void activate(BundleContext context) {
-        super.activate(context);
+    public static final class Plugin extends AbstractWebConsolePlugin {
 
-        Dictionary<String, Object> props = new Hashtable<String, Object>();
-        props.put(Constants.SERVICE_DESCRIPTION,
-            "Web Console Plugin to display information about recent Sling requests");
-        props.put(Constants.SERVICE_VENDOR, "The Apache Software Foundation");
-        props.put(Constants.SERVICE_PID, getClass().getName());
-        props.put(WebConsoleConstants.PLUGIN_LABEL, LABEL);
+        private final SlingHttpServletRequest[] requests = new SlingHttpServletRequest[STORED_REQUESTS_COUNT];
 
-        serviceRegistration = context.registerService(
-            WebConsoleConstants.SERVICE_NAME, this, props);
-    }
+        /** Need to store methods separately, apparently requests clear this data when done processing */
+        private final String [] methods = new String[STORED_REQUESTS_COUNT];
+
+        private ServiceRegistration serviceRegistration;
+
+        private int lastRequestIndex = -1;
 
-    public void deactivate() {
-        if (serviceRegistration != null) {
-            serviceRegistration.unregister();
-            serviceRegistration = null;
+        private synchronized void addRequest(SlingHttpServletRequest r) {
+            int index = lastRequestIndex + 1;
+            if (index >= requests.length) {
+                index = 0;
+            }
+            requests[index] = r;
+            methods[index] = r.getMethod();
+            lastRequestIndex = index;
         }
-        super.deactivate();
-    }
 
-    @Override
-    public String getLabel() {
-        return LABEL;
-    }
+        private synchronized void clear() {
+            for(int i=0; i < requests.length; i++) {
+                requests[i] = null;
+            }
+            lastRequestIndex = -1;
+        }
 
-    @Override
-    public String getTitle() {
-        return "Recent requests";
-    }
+        public void activate(BundleContext context) {
+            super.activate(context);
 
-    private int getArrayIndex(int displayIndex) {
-        int result = lastRequestIndex - displayIndex;
-        if (result < 0) {
-            result += requests.length;
+            Dictionary<String, Object> props = new Hashtable<String, Object>();
+            props.put(Constants.SERVICE_DESCRIPTION,
+                "Web Console Plugin to display information about recent Sling requests");
+            props.put(Constants.SERVICE_VENDOR, "The Apache Software Foundation");
+            props.put(Constants.SERVICE_PID, getClass().getName());
+            props.put(WebConsoleConstants.PLUGIN_LABEL, LABEL);
+
+            serviceRegistration = context.registerService(
+                WebConsoleConstants.SERVICE_NAME, this, props);
         }
-        return result;
-    }
 
-    private String getLinksTable(int currentRequestIndex) {
-        final List<String> links = new ArrayList<String>();
-        for (int i = 0; i < requests.length; i++) {
-            final StringBuilder sb = new StringBuilder();
-            if (requests[i] != null) {
-                sb.append("<a href='" + LABEL + "?index=" + i + "'>");
-                if (i == currentRequestIndex) {
-                    sb.append("<b>");
-                }
-                sb.append(getRequestLabel(getArrayIndex(i)));
-                if (i == currentRequestIndex) {
-                    sb.append("</b>");
-                }
-                sb.append("</a> ");
-                links.add(sb.toString());
+        public void deactivate() {
+            if (serviceRegistration != null) {
+                serviceRegistration.unregister();
+                serviceRegistration = null;
             }
+            super.deactivate();
         }
 
-        final int nCols = 5;
-        while((links.size() % nCols) != 0) {
-            links.add("&nbsp;");
+        @Override
+        public String getLabel() {
+            return LABEL;
         }
 
-        final StringBuilder tbl = new StringBuilder();
+        @Override
+        public String getTitle() {
+            return "Recent requests";
+        }
 
-        tbl.append("<table>\n<tr>\n");
-        int i=0;
-        for(String str : links) {
-            if( (i++ % nCols) == 0) {
-                tbl.append("</tr>\n<tr>\n");
+        private int getArrayIndex(int displayIndex) {
+            int result = lastRequestIndex - displayIndex;
+            if (result < 0) {
+                result += requests.length;
             }
-            tbl.append("<td>");
-            tbl.append(str);
-            tbl.append("</td>\n");
+            return result;
         }
-        tbl.append("</tr>\n");
 
-        tbl.append("</table>\n");
-        return tbl.toString();
-    }
+        private String getLinksTable(int currentRequestIndex) {
+            final List<String> links = new ArrayList<String>();
+            for (int i = 0; i < requests.length; i++) {
+                final StringBuilder sb = new StringBuilder();
+                if (requests[i] != null) {
+                    sb.append("<a href='" + LABEL + "?index=" + i + "'>");
+                    if (i == currentRequestIndex) {
+                        sb.append("<b>");
+                    }
+                    sb.append(getRequestLabel(getArrayIndex(i)));
+                    if (i == currentRequestIndex) {
+                        sb.append("</b>");
+                    }
+                    sb.append("</a> ");
+                    links.add(sb.toString());
+                }
+            }
 
-    @Override
-    protected void renderContent(HttpServletRequest req, HttpServletResponse res)
-      throws ServletException, IOException {
-
-        // If so requested, clear our data
-        if(req.getParameter(CLEAR) != null) {
-            clear();
-            res.sendRedirect(LABEL);
-            return;
-        }
+            final int nCols = 5;
+            while((links.size() % nCols) != 0) {
+                links.add("&nbsp;");
+            }
+
+            final StringBuilder tbl = new StringBuilder();
+
+            tbl.append("<table>\n<tr>\n");
+            int i=0;
+            for(String str : links) {
+                if( (i++ % nCols) == 0) {
+                    tbl.append("</tr>\n<tr>\n");
+                }
+                tbl.append("<td>");
+                tbl.append(str);
+                tbl.append("</td>\n");
+            }
+            tbl.append("</tr>\n");
+
+            tbl.append("</table>\n");
+            return tbl.toString();
+        }
+
+        @Override
+        protected void renderContent(HttpServletRequest req, HttpServletResponse res)
+          throws ServletException, IOException {
+
+            // If so requested, clear our data
+            if(req.getParameter(CLEAR) != null) {
+                clear();
+                res.sendRedirect(LABEL);
+                return;
+            }
+
+            // Select request to display
+            int index = 0;
+            final String tmp = req.getParameter(INDEX);
+            if (tmp != null) {
+                try {
+                    index = Integer.parseInt(tmp);
+                } catch (NumberFormatException ignore) {
+                    // ignore
+                }
+            }
 
-        // Select request to display
-        int index = 0;
-        final String tmp = req.getParameter(INDEX);
-        if (tmp != null) {
+            // index is relative to lastRequestIndex
+            final int arrayIndex = getArrayIndex(index);
+
+            SlingHttpServletRequest r = null;
             try {
-                index = Integer.parseInt(tmp);
-            } catch (NumberFormatException ignore) {
+                r = requests[arrayIndex];
+            } catch (ArrayIndexOutOfBoundsException ignore) {
                 // ignore
             }
-        }
 
-        // index is relative to lastRequestIndex
-        final int arrayIndex = getArrayIndex(index);
+            final PrintWriter pw = res.getWriter();
 
-        SlingHttpServletRequest r = null;
-        try {
-            r = requests[arrayIndex];
-        } catch (ArrayIndexOutOfBoundsException ignore) {
-            // ignore
-        }
-
-        final PrintWriter pw = res.getWriter();
-
-        pw.println("<table class='content' cellpadding='0' cellspacing='0' width='100%'>");
-
-        // Links to other requests
-        pw.println("<thead>");
-        pw.println("<tr class='content'>");
-        pw.println("<th colspan='2'class='content container'>Recent Requests");
-        pw.println(" (<a href='" + LABEL + "?clear=clear'>Clear</a>)");
-        pw.println("</th>");
-        pw.println("</thead>");
-        pw.println("<tbody>");
-        pw.println("<tr class='content'><td>");
-        pw.println(getLinksTable(index));
-        pw.println("</td></tr>");
+            pw.println("<table class='content' cellpadding='0' cellspacing='0' width='100%'>");
 
-        if (r != null) {
-            // Request Progress Tracker Info
+            // Links to other requests
+            pw.println("<thead>");
             pw.println("<tr class='content'>");
-            pw.println("<th colspan='2'class='content container'>");
-            pw.print("Request " + index + " (" + getRequestLabel(index) + ") - RequestProgressTracker Info");
-            pw.println("</th></tr>");
-            pw.println("<tr><td colspan='2'>");
-            final Iterator<String> it = r.getRequestProgressTracker().getMessages();
-            pw.print("<pre>");
-            while (it.hasNext()) {
-                pw.print(escape(it.next()));
+            pw.println("<th colspan='2'class='content container'>Recent Requests");
+            pw.println(" (<a href='" + LABEL + "?clear=clear'>Clear</a>)");
+            pw.println("</th>");
+            pw.println("</thead>");
+            pw.println("<tbody>");
+            pw.println("<tr class='content'><td>");
+            pw.println(getLinksTable(index));
+            pw.println("</td></tr>");
+
+            if (r != null) {
+                // Request Progress Tracker Info
+                pw.println("<tr class='content'>");
+                pw.println("<th colspan='2'class='content container'>");
+                pw.print("Request " + index + " (" + getRequestLabel(index) + ") - RequestProgressTracker Info");
+                pw.println("</th></tr>");
+                pw.println("<tr><td colspan='2'>");
+                final Iterator<String> it = r.getRequestProgressTracker().getMessages();
+                pw.print("<pre>");
+                while (it.hasNext()) {
+                    pw.print(escape(it.next()));
+                }
+                pw.println("</pre></td></tr>");
             }
-            pw.println("</pre></td></tr>");
+            pw.println("</tbody></table>");
         }
-        pw.println("</tbody></table>");
-    }
 
-    private static String escape(String str) {
-        final StringBuilder sb = new StringBuilder();
-        for(int i=0; i < str.length(); i++) {
-            final char c = str.charAt(i);
-            if(c == '<') {
-                sb.append("&lt;");
-            } else if (c == '>') {
-                sb.append("&gt;");
-            } else if (c == '&') {
-                sb.append("&amp;");
-            } else {
-                sb.append(c);
+        private static String escape(String str) {
+            final StringBuilder sb = new StringBuilder();
+            for(int i=0; i < str.length(); i++) {
+                final char c = str.charAt(i);
+                if(c == '<') {
+                    sb.append("&lt;");
+                } else if (c == '>') {
+                    sb.append("&gt;");
+                } else if (c == '&') {
+                    sb.append("&amp;");
+                } else {
+                    sb.append(c);
+                }
             }
+            return sb.toString();
         }
-        return sb.toString();
-    }
 
-    private String getRequestLabel(int index) {
-        final StringBuilder sb = new StringBuilder();
-        String path = requests[index].getPathInfo();
-        if (path == null) {
-            path = "";
-        }
-
-        sb.append(methods[index]);
-        sb.append(' ');
-
-        final int pos = requests[index].getPathInfo().lastIndexOf('/');
-        if(pos < 0) {
-            sb.append(requests[index].getPathInfo());
-        } else {
-            sb.append(requests[index].getPathInfo().substring(pos+1));
+        private String getRequestLabel(int index) {
+            final StringBuilder sb = new StringBuilder();
+            String path = requests[index].getPathInfo();
+            if (path == null) {
+                path = "";
+            }
+
+            sb.append(methods[index]);
+            sb.append(' ');
+
+            final int pos = requests[index].getPathInfo().lastIndexOf('/');
+            if(pos < 0) {
+                sb.append(requests[index].getPathInfo());
+            } else {
+                sb.append(requests[index].getPathInfo().substring(pos+1));
+            }
+            return sb.toString();
         }
-        return sb.toString();
     }
 }
\ No newline at end of file