/*
* 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.request;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Dictionary;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
import java.util.regex.Pattern;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.request.RequestProgressTracker;
import org.apache.sling.api.request.ResponseUtil;
import org.apache.sling.api.resource.ResourceUtil;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.ServiceRegistration;
/**
* Felix OSGi console plugin that displays info about recent requests processed
* by Sling. Info about all requests can be found in the logs, but this is
* useful when testing or explaining things.
*/
@SuppressWarnings("serial")
public class RequestHistoryConsolePlugin {
public static final String LABEL = "requests";
public static final String INDEX = "index";
public static final String CLEAR = "clear";
private static Plugin instance;
private static ServiceRegistration serviceRegistration;
public static final int STORED_REQUESTS_COUNT = 20;
private RequestHistoryConsolePlugin() {
}
public static void recordRequest(SlingHttpServletRequest r) {
if (instance != null) {
instance.addRequest(r);
}
}
public static void initPlugin(BundleContext context, int maxRequests, List<Pattern> storePatterns) {
if (instance == null) {
Plugin tmp = new Plugin(maxRequests, storePatterns);
final 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, tmp.getClass().getName());
props.put("felix.webconsole.label", LABEL);
props.put("felix.webconsole.title", "Recent requests");
props.put("felix.webconsole.category", "Sling");
serviceRegistration = context.registerService(
"javax.servlet.Servlet", tmp, props);
instance = tmp;
}
}
public static void destroyPlugin() {
if (instance != null) {
try {
if (serviceRegistration != null) {
serviceRegistration.unregister();
serviceRegistration = null;
}
} finally {
instance = null;
}
}
}
public static final class Plugin extends HttpServlet {
private final RequestInfoMap requests;
private final List<Pattern> storePatterns;
Plugin(int maxRequests, List<Pattern> storePatterns) {
this.requests = (maxRequests > 0)
? new RequestInfoMap(maxRequests)
: null;
this.storePatterns = storePatterns;
}
public void deactivate() {
if (serviceRegistration != null) {
serviceRegistration.unregister();
serviceRegistration = null;
}
clear();
}
private void addRequest(SlingHttpServletRequest r) {
if (requests != null) {
String requestPath = r.getPathInfo();
boolean accept = true;
if (storePatterns != null && storePatterns.size() > 0) {
accept = false;
for (Pattern pattern : storePatterns) {
if (pattern.matcher(requestPath).matches()) {
accept = true;
break;
}
}
}
if (accept) {
synchronized (requests) {
RequestInfo info = new RequestInfo(r);
requests.put(info.getKey(), info);
}
}
}
}
private void clear() {
if (requests != null) {
synchronized (requests) {
requests.clear();
}
}
}
private String getLinksTable(String currentRequestIndex) {
final List<String> links = new ArrayList<String>();
if (requests != null) {
synchronized (requests) {
for (RequestInfo info : requests.values()) {
final String key = ResponseUtil.escapeXml(info.getKey());
final boolean isCurrent = info.getKey().equals(
currentRequestIndex);
final StringBuilder sb = new StringBuilder();
sb.append("<span style='white-space: pre; text-align:right; font-size:80%'>");
sb.append(String.format("%1$8s", key));
sb.append("</span> ");
sb.append("<a href='" + LABEL + "?index=" + key + "'>");
if (isCurrent) {
sb.append("<b>");
}
sb.append(ResponseUtil.escapeXml(info.getLabel()));
if (isCurrent) {
sb.append("</b>");
}
sb.append("</a> ");
links.add(sb.toString());
}
}
}
final int nCols = 5;
while ((links.size() % nCols) != 0) {
links.add(" ");
}
final StringBuilder tbl = new StringBuilder();
tbl.append("<table class='nicetable ui-widget'>\n<tr>\n");
if (links.isEmpty()) {
tbl.append("No Requests recorded");
} else {
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 doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// Select request to display
RequestInfo info = null;
String key = req.getParameter(INDEX);
if (key != null && requests != null) {
synchronized (requests) {
info = requests.get(key);
}
}
final PrintWriter pw = resp.getWriter();
if (requests != null) {
pw.println("<p class='statline ui-state-highlight'>Recorded "
+ requests.size() + " requests (max: "
+ requests.getMaxSize() + ")</p>");
} else {
pw.println("<p class='statline ui-state-highlight'>Request Recording disabled</p>");
}
pw.println("<div class='ui-widget-header ui-corner-top buttonGroup'>");
pw.println("<span style='float: left; margin-left: 1em'>Recent Requests</span>");
pw.println("<form method='POST'><input type='hidden' name='clear' value='clear'><input type='submit' value='Clear' class='ui-state-default ui-corner-all'></form>");
pw.println("</div>");
pw.println(getLinksTable(key));
pw.println("<br/>");
if (info != null) {
pw.println("<table class='nicetable ui-widget'>");
// Links to other requests
pw.println("<thead>");
pw.println("<tr>");
pw.printf(
"<th class='ui-widget-header'>Request %s (%s %s) by %s - RequestProgressTracker Info</th>%n",
key, ResponseUtil.escapeXml(info.getMethod()),
ResponseUtil.escapeXml(info.getPathInfo()), ResponseUtil.escapeXml(info.getUser()));
pw.println("</tr>");
pw.println("</thead>");
pw.println("<tbody>");
// Request Progress Tracker Info
pw.println("<tr><td>");
final Iterator<String> it = info.getTracker().getMessages();
pw.print("<pre>");
while (it.hasNext()) {
pw.print(ResponseUtil.escapeXml(it.next()));
}
pw.println("</pre></td></tr>");
pw.println("</tbody></table>");
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws IOException {
if (req.getParameter(CLEAR) != null) {
clear();
resp.sendRedirect(req.getRequestURI());
}
}
}
private static class RequestInfo {
private static AtomicLong requestCounter = new AtomicLong(0);
private final String key;
private final String method;
private final String pathInfo;
private final String user;
private final RequestProgressTracker tracker;
RequestInfo(SlingHttpServletRequest request) {
this.key = String.valueOf(requestCounter.incrementAndGet());
this.method = request.getMethod();
this.pathInfo = request.getPathInfo();
this.user = request.getRemoteUser();
this.tracker = request.getRequestProgressTracker();
}
public String getKey() {
return key;
}
public String getMethod() {
return method;
}
public String getPathInfo() {
return pathInfo;
}
public String getUser() {
return user;
}
public String getLabel() {
final StringBuilder sb = new StringBuilder();
sb.append(getMethod());
sb.append(' ');
final String path = getPathInfo();
if (path != null && path.length() > 0) {
sb.append(ResourceUtil.getName(getPathInfo()));
} else {
sb.append('/');
}
return sb.toString();
}
public RequestProgressTracker getTracker() {
return tracker;
}
}
private static class RequestInfoMap extends
LinkedHashMap<String, RequestInfo> {
private int maxSize;
RequestInfoMap(int maxSize) {
this.maxSize = maxSize;
}
@Override
protected boolean removeEldestEntry(
java.util.Map.Entry<String, RequestInfo> eldest) {
return size() > maxSize;
}
public int getMaxSize() {
return maxSize;
}
}
}