You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by bd...@apache.org on 2015/08/12 15:02:46 UTC
svn commit: r1695501 - in /sling/trunk/bundles/extensions/healthcheck/core:
./ src/main/java/org/apache/sling/hc/api/execution/
src/main/java/org/apache/sling/hc/core/impl/servlet/
src/test/java/org/apache/sling/hc/core/impl/servlet/
Author: bdelacretaz
Date: Wed Aug 12 13:02:45 2015
New Revision: 1695501
URL: http://svn.apache.org/r1695501
Log:
SLING-4862 - Health Checks Servlet, contributed by Georg Henzler, thanks!
Added:
sling/trunk/bundles/extensions/healthcheck/core/src/main/java/org/apache/sling/hc/core/impl/servlet/
sling/trunk/bundles/extensions/healthcheck/core/src/main/java/org/apache/sling/hc/core/impl/servlet/HealthCheckExecutorServlet.java
sling/trunk/bundles/extensions/healthcheck/core/src/main/java/org/apache/sling/hc/core/impl/servlet/ResultHtmlSerializer.java
sling/trunk/bundles/extensions/healthcheck/core/src/main/java/org/apache/sling/hc/core/impl/servlet/ResultJsonSerializer.java
sling/trunk/bundles/extensions/healthcheck/core/src/test/java/org/apache/sling/hc/core/impl/servlet/
sling/trunk/bundles/extensions/healthcheck/core/src/test/java/org/apache/sling/hc/core/impl/servlet/HealthCheckExecutorServletTest.java
Modified:
sling/trunk/bundles/extensions/healthcheck/core/pom.xml
sling/trunk/bundles/extensions/healthcheck/core/src/main/java/org/apache/sling/hc/api/execution/HealthCheckExecutionOptions.java
Modified: sling/trunk/bundles/extensions/healthcheck/core/pom.xml
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/healthcheck/core/pom.xml?rev=1695501&r1=1695500&r2=1695501&view=diff
==============================================================================
--- sling/trunk/bundles/extensions/healthcheck/core/pom.xml (original)
+++ sling/trunk/bundles/extensions/healthcheck/core/pom.xml Wed Aug 12 13:02:45 2015
@@ -134,6 +134,20 @@
<version>2.5</version>
<scope>provided</scope>
</dependency>
+
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.http.servlet-api</artifactId>
+ <version>1.1.0</version>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>org.apache.sling.commons.json</artifactId>
+ <version>2.0.6</version>
+ <scope>provided</scope>
+ </dependency>
</dependencies>
</project>
Modified: sling/trunk/bundles/extensions/healthcheck/core/src/main/java/org/apache/sling/hc/api/execution/HealthCheckExecutionOptions.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/healthcheck/core/src/main/java/org/apache/sling/hc/api/execution/HealthCheckExecutionOptions.java?rev=1695501&r1=1695500&r2=1695501&view=diff
==============================================================================
--- sling/trunk/bundles/extensions/healthcheck/core/src/main/java/org/apache/sling/hc/api/execution/HealthCheckExecutionOptions.java (original)
+++ sling/trunk/bundles/extensions/healthcheck/core/src/main/java/org/apache/sling/hc/api/execution/HealthCheckExecutionOptions.java Wed Aug 12 13:02:45 2015
@@ -79,5 +79,32 @@ public class HealthCheckExecutionOptions
return overrideGlobalTimeout;
}
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + (combineTagsWithOr ? 1231 : 1237);
+ result = prime * result + (forceInstantExecution ? 1231 : 1237);
+ result = prime * result + overrideGlobalTimeout;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ HealthCheckExecutionOptions other = (HealthCheckExecutionOptions) obj;
+ if (combineTagsWithOr != other.combineTagsWithOr)
+ return false;
+ if (forceInstantExecution != other.forceInstantExecution)
+ return false;
+ if (overrideGlobalTimeout != other.overrideGlobalTimeout)
+ return false;
+ return true;
+ }
}
\ No newline at end of file
Added: sling/trunk/bundles/extensions/healthcheck/core/src/main/java/org/apache/sling/hc/core/impl/servlet/HealthCheckExecutorServlet.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/healthcheck/core/src/main/java/org/apache/sling/hc/core/impl/servlet/HealthCheckExecutorServlet.java?rev=1695501&view=auto
==============================================================================
--- sling/trunk/bundles/extensions/healthcheck/core/src/main/java/org/apache/sling/hc/core/impl/servlet/HealthCheckExecutorServlet.java (added)
+++ sling/trunk/bundles/extensions/healthcheck/core/src/main/java/org/apache/sling/hc/core/impl/servlet/HealthCheckExecutorServlet.java Wed Aug 12 13:02:45 2015
@@ -0,0 +1,254 @@
+/*
+ * 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 SF 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.hc.core.impl.servlet;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Dictionary;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.ConfigurationPolicy;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Modified;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.sling.hc.api.Result;
+import org.apache.sling.hc.api.Result.Status;
+import org.apache.sling.hc.api.execution.HealthCheckExecutionOptions;
+import org.apache.sling.hc.api.execution.HealthCheckExecutionResult;
+import org.apache.sling.hc.api.execution.HealthCheckExecutor;
+import org.osgi.service.component.ComponentContext;
+import org.osgi.service.http.HttpService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/** Servlet that triggers the health check executor to return results via http. <br/>
+ * <br/>
+ * Parameters:<br/>
+ * <br/>
+ * tags: The health check tags to take into account<br/>
+ * format: html|json|jsonp<br/>
+ * includeDebug: If true, debug messages from result log are included.<br/>
+ * callback: For jsonp, the JS callback function name (defaults to "processHealthCheckResults")<br/>
+ * httpStatus: health check status to http status mapping in format httpStatus=WARN:418,CRITICAL:503,HEALTH_CHECK_ERROR:500.
+ * For omitted health check status values the next best code will be used (e.g. for httpStatus=CRITICAL:503 a result WARN will
+ * return 200, CRITICAL 503 and HEALTH_CHECK_ERROR also 503). By default all requests answer with an http status of 200.
+ * Useful in combination with load balancers.<br/>
+ *
+ * NOTE: This servlet registers directly (low-level) at the HttpService and is not processed by sling (better performance, fewer dependencies, no authentication required, 503 can be sent without the progress tracker information). */
+@Service
+@Component(label = "Health Check Executor Servlet",
+ description = "Serializes health check results into html or json format",
+ policy = ConfigurationPolicy.REQUIRE, metatype = true, immediate = true)
+public class HealthCheckExecutorServlet extends HttpServlet {
+ private static final long serialVersionUID = 8013511523994541848L;
+
+ private static final Logger LOG = LoggerFactory.getLogger(HealthCheckExecutorServlet.class);
+
+ static final String PARAM_TAGS = "tags";
+ static final String PARAM_FORMAT = "format";
+ static final String PARAM_HTTP_STATUS = "httpStatus";
+
+ static final String PARAM_COMBINE_TAGS_WITH_OR = "combineTagsWithOr";
+ static final String PARAM_FORCE_INSTANT_EXECUTION = "forceInstantExecution";
+ static final String PARAM_OVERRIDE_GLOBAL_TIMEOUT = "timeout";
+
+ static final String PARAM_INCLUDE_DEBUG = "includeDebug";
+
+ static final String FORMAT_HTML = "html";
+ static final String FORMAT_JSON = "json";
+ static final String FORMAT_JSONP = "jsonp";
+
+ static final String PARAM_JSONP_CALLBACK = "callback";
+ static final String JSONP_CALLBACK_DEFAULT = "processHealthCheckResults";
+
+ private static final String CONTENT_TYPE_HTML = "text/html";
+ private static final String CONTENT_TYPE_JSON = "application/json";
+ private static final String CONTENT_TYPE_JSONP = "application/javascript";
+ private static final String STATUS_HEADER_NAME = "X-Health";
+
+ private static final String CACHE_CONTROL_KEY = "Cache-control";
+ private static final String CACHE_CONTROL_VALUE = "no-cache";
+
+ private static final String SERVLET_PATH_DEFAULT = "/system/health";
+ public static final String PROPERTY_SERVLET_PATH = "servletPath";
+ @Property(name = PROPERTY_SERVLET_PATH, label = "Path",
+ description = "Servlet path (defaults to " + SERVLET_PATH_DEFAULT + " in order to not be accessible via Apache/Internet)", value = SERVLET_PATH_DEFAULT)
+ private String servletPath;
+
+ @Reference
+ private HttpService httpService;
+
+ @Reference
+ HealthCheckExecutor healthCheckExecutor;
+
+ @Reference
+ ResultHtmlSerializer htmlSerializer;
+
+ @Reference
+ ResultJsonSerializer jsonSerializer;
+
+ @Activate
+ protected final void activate(final ComponentContext context) {
+ try {
+ final Dictionary<?, ?> properties = context.getProperties();
+ this.servletPath = (String) properties.get(PROPERTY_SERVLET_PATH);
+ LOG.debug("Registering {} to path {}", getClass().getSimpleName(), this.servletPath);
+ this.httpService.registerServlet(this.servletPath, this, null, null);
+ } catch (Exception e) {
+ LOG.error("Could not register health check servlet: "+e, e);
+ }
+ }
+
+ @Deactivate
+ public void deactivate(final ComponentContext componentContext) {
+ try {
+ LOG.debug("Unregistering path {}", this.servletPath);
+ this.httpService.unregister(this.servletPath);
+ } catch (Exception e) {
+ LOG.error("Could not unregister health check servlet: "+e, e);
+ }
+ }
+
+ @Override
+ protected void doGet(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException {
+
+ final String[] tags = StringUtils.defaultIfEmpty(request.getParameter(PARAM_TAGS), "").split("[, ;]+");
+
+ String format = StringUtils.substringAfterLast(request.getPathInfo(), ".");
+ if (StringUtils.isBlank(format)) {
+ // if not provided via extension use parameter or default
+ format = StringUtils.defaultIfEmpty(request.getParameter(PARAM_FORMAT), FORMAT_HTML);
+ }
+
+ final Boolean includeDebug = Boolean.valueOf(request.getParameter(PARAM_INCLUDE_DEBUG));
+ final Map<Result.Status, Integer> statusMapping = request.getParameter(PARAM_HTTP_STATUS) != null ? getStatusMapping(request
+ .getParameter(PARAM_HTTP_STATUS)) : null;
+
+ HealthCheckExecutionOptions options = new HealthCheckExecutionOptions();
+ options.setCombineTagsWithOr(Boolean.valueOf(StringUtils.defaultString(request.getParameter(PARAM_COMBINE_TAGS_WITH_OR), "true")));
+ options.setForceInstantExecution(Boolean.valueOf(request.getParameter(PARAM_FORCE_INSTANT_EXECUTION)));
+ String overrideGlobalTimeoutVal = request.getParameter(PARAM_OVERRIDE_GLOBAL_TIMEOUT);
+ if (StringUtils.isNumeric(overrideGlobalTimeoutVal)) {
+ options.setOverrideGlobalTimeout(Integer.valueOf(overrideGlobalTimeoutVal));
+ }
+
+ List<HealthCheckExecutionResult> executionResults = this.healthCheckExecutor.execute(options, tags);
+
+ Result.Status mostSevereStatus = Result.Status.DEBUG;
+ for (HealthCheckExecutionResult executionResult : executionResults) {
+ Status status = executionResult.getHealthCheckResult().getStatus();
+ if (status.ordinal() > mostSevereStatus.ordinal()) {
+ mostSevereStatus = status;
+ }
+ }
+ Result overallResult = new Result(mostSevereStatus, "Overall status " + mostSevereStatus);
+
+ sendNoCacheHeaders(response);
+
+ if (statusMapping != null) {
+ Integer httpStatus = statusMapping.get(overallResult.getStatus());
+ response.setStatus(httpStatus);
+ }
+
+ if (FORMAT_HTML.equals(format)) {
+ sendHtmlResponse(overallResult, executionResults, request, response, includeDebug);
+ } else if (FORMAT_JSON.equals(format)) {
+ sendJsonResponse(overallResult, executionResults, null, response, includeDebug);
+ } else if (FORMAT_JSONP.equals(format)) {
+ String jsonpCallback = StringUtils.defaultIfEmpty(request.getParameter(PARAM_JSONP_CALLBACK), JSONP_CALLBACK_DEFAULT);
+ sendJsonResponse(overallResult, executionResults, jsonpCallback, response, includeDebug);
+ } else {
+ response.setContentType("text/plain");
+ response.getWriter().println("Invalid format " + format + " - supported formats: html|json|jsonp");
+ }
+
+ }
+
+ private void sendJsonResponse(final Result overallResult, final List<HealthCheckExecutionResult> executionResults, final String jsonpCallback,
+ final HttpServletResponse response, boolean includeDebug)
+ throws IOException {
+ if (StringUtils.isNotBlank(jsonpCallback)) {
+ response.setContentType(CONTENT_TYPE_JSONP);
+ } else {
+ response.setContentType(CONTENT_TYPE_JSON);
+ }
+
+ String resultJson = this.jsonSerializer.serialize(overallResult, executionResults, jsonpCallback, includeDebug);
+ PrintWriter writer = response.getWriter();
+ writer.append(resultJson);
+ }
+
+ private void sendHtmlResponse(final Result overallResult, final List<HealthCheckExecutionResult> executionResults,
+ final HttpServletRequest request, final HttpServletResponse response, boolean includeDebug)
+ throws IOException {
+
+ String resultHtml = this.htmlSerializer.serialize(overallResult, executionResults, includeDebug);
+
+ response.setContentType(CONTENT_TYPE_HTML);
+ response.setHeader(STATUS_HEADER_NAME, overallResult.toString());
+
+ PrintWriter writer = response.getWriter();
+ writer.append(resultHtml);
+
+ }
+
+ private void sendNoCacheHeaders(final HttpServletResponse response) {
+ response.setHeader(CACHE_CONTROL_KEY, CACHE_CONTROL_VALUE);
+ }
+
+ Map<Result.Status, Integer> getStatusMapping(String mappingStr) throws ServletException {
+ Map<Result.Status, Integer> statusMapping = new HashMap<Result.Status, Integer>();
+ try {
+ String[] bits = mappingStr.split("[,]");
+ for (String bit : bits) {
+ String[] tuple = bit.split("[:]");
+ statusMapping.put(Result.Status.valueOf(tuple[0]), Integer.parseInt(tuple[1]));
+ }
+ } catch (Exception e) {
+ throw new ServletException("Invalid parameter httpStatus=" + mappingStr + " " + e, e);
+ }
+
+ if (!statusMapping.containsKey(Result.Status.OK)) {
+ statusMapping.put(Result.Status.OK, 200);
+ }
+ if (!statusMapping.containsKey(Result.Status.WARN)) {
+ statusMapping.put(Result.Status.WARN, statusMapping.get(Result.Status.OK));
+ }
+ if (!statusMapping.containsKey(Result.Status.CRITICAL)) {
+ statusMapping.put(Result.Status.CRITICAL, statusMapping.get(Result.Status.WARN));
+ }
+ if (!statusMapping.containsKey(Result.Status.HEALTH_CHECK_ERROR)) {
+ statusMapping.put(Result.Status.HEALTH_CHECK_ERROR, statusMapping.get(Result.Status.CRITICAL));
+ }
+ return statusMapping;
+ }
+
+
+}
Added: sling/trunk/bundles/extensions/healthcheck/core/src/main/java/org/apache/sling/hc/core/impl/servlet/ResultHtmlSerializer.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/healthcheck/core/src/main/java/org/apache/sling/hc/core/impl/servlet/ResultHtmlSerializer.java?rev=1695501&view=auto
==============================================================================
--- sling/trunk/bundles/extensions/healthcheck/core/src/main/java/org/apache/sling/hc/core/impl/servlet/ResultHtmlSerializer.java (added)
+++ sling/trunk/bundles/extensions/healthcheck/core/src/main/java/org/apache/sling/hc/core/impl/servlet/ResultHtmlSerializer.java Wed Aug 12 13:02:45 2015
@@ -0,0 +1,164 @@
+/*
+ * 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 SF 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.hc.core.impl.servlet;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.Dictionary;
+import java.util.List;
+
+import org.apache.commons.lang.StringEscapeUtils;
+import org.apache.commons.lang.StringUtils;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.sling.commons.osgi.PropertiesUtil;
+import org.apache.sling.hc.api.Result;
+import org.apache.sling.hc.api.ResultLog.Entry;
+import org.apache.sling.hc.api.execution.HealthCheckExecutionResult;
+import org.apache.sling.hc.util.FormattingResultLog;
+import org.osgi.service.component.ComponentContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/** Serializes health check results into html format. */
+@Service(ResultHtmlSerializer.class)
+@Component(metatype = true)
+public class ResultHtmlSerializer {
+ private static final Logger LOG = LoggerFactory.getLogger(ResultHtmlSerializer.class);
+
+ private static final String CSS_STYLE_DEFAULT = "body { font-size:12px; font-family:arial,verdana,sans-serif;background-color:#FFFDF1; }\n"
+ + "h1 { font-size:20px;}\n"
+ + "table { font-size:12px; border:#ccc 1px solid; border-radius:3px; }\n"
+ + "table th { padding:5px; text-align: left; background: #ededed; }\n"
+ + "table td { padding:5px; border-top: 1px solid #ffffff; border-bottom:1px solid #e0e0e0; border-left: 1px solid #e0e0e0; }\n"
+ + ".statusOK { background-color:#CCFFCC;}\n"
+ + ".statusWARN { background-color:#FFE569;}\n"
+ + ".statusCRITICAL { background-color:#F0975A;}\n"
+ + ".statusHEALTH_CHECK_ERROR { background-color:#F16D4E;}\n";
+ public static final String PROPERTY_CSS_STYLE = "styleString";
+ @Property(name = PROPERTY_CSS_STYLE, label = "CSS Style",
+ description = "CSS Style - can be configured to change the look and feel of the html result page.", value = CSS_STYLE_DEFAULT)
+ private String styleString;
+
+ @Activate
+ protected final void activate(final ComponentContext context) {
+ final Dictionary<?, ?> properties = context.getProperties();
+ this.styleString = PropertiesUtil.toString(properties.get(PROPERTY_CSS_STYLE), CSS_STYLE_DEFAULT);
+ }
+
+ public String serialize(final Result overallResult, final List<HealthCheckExecutionResult> executionResults, boolean includeDebug) {
+
+ StringWriter stringWriter = new StringWriter();
+ PrintWriter writer = new PrintWriter(stringWriter);
+
+ writer.println("<html><head><title>System Health</title>" +
+ "<meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /><style>" + styleString +
+ "</style></head><body><h1>System Health</h1>");
+
+ writer.println("<p><span class=\"" + getClassForStatus(overallResult.getStatus()) + "\"><strong>Overall Result: "
+ + overallResult.getStatus() + "</strong></span></p>");
+
+ final DateFormat dfShort = new SimpleDateFormat("HH:mm:ss.SSS");
+ final DateFormat dfLong = new SimpleDateFormat("yyyy-MM-dd HH:mm");
+
+ writer.println("<table id=\"healthCheckResults\" cellspacing=\"0\">");
+ writer.println("<thead><tr><th>Health Check</th><th>Status</th><th>Log</th><th colspan=\"2\">Execution Time</th></tr></thead>");
+ for (HealthCheckExecutionResult executionResult : executionResults) {
+ Result result = executionResult.getHealthCheckResult();
+ writer.println("<tr class=\"" + getClassForStatus(result.getStatus()) + "\" "
+ + "title=\"Tags: " + StringEscapeUtils.escapeHtml(StringUtils.join(executionResult.getHealthCheckMetadata().getTags(), ",")) + "\">");
+ writer.println("<td><span title=\"" + StringEscapeUtils.escapeHtml(executionResult.getHealthCheckMetadata().getName()) + "\">"
+ + StringEscapeUtils.escapeHtml(executionResult.getHealthCheckMetadata().getTitle()) + "</span></td>");
+ writer.println("<td style='font-weight:bold;'>" + result.getStatus() + "</td>");
+ writer.println("<td>");
+ boolean isFirst = true;
+
+ boolean isSingleResult = isSingleResult(result);
+
+ for (Entry entry : result) {
+ if(!includeDebug && entry.getStatus()==Result.Status.DEBUG) {
+ continue;
+ }
+
+ if (isFirst) {
+ isFirst = false;
+ } else {
+ writer.println("<br/>\n");
+ }
+
+ boolean showStatus = !isSingleResult && entry.getStatus()!=Result.Status.DEBUG && entry.getStatus() !=Result.Status.INFO;
+
+ String message = StringEscapeUtils.escapeHtml(entry.getMessage());
+ if(entry.getStatus()==Result.Status.DEBUG) {
+ message = "<span style='color:gray'/>"+message + "</span>";
+ }
+ writer.println((showStatus ? entry.getStatus() + " " : "") + message);
+
+ Exception exception = entry.getException();
+ if (exception != null) {
+ writer.println("<span style='width:20px'/>" + StringEscapeUtils.escapeHtml(exception.toString()));
+ writer.println("<!--");
+ exception.printStackTrace(writer);
+ writer.println("-->");
+ }
+ }
+ writer.println("</td>");
+ Date finishedAt = executionResult.getFinishedAt();
+ writer.println("<td>" + (isToday(finishedAt) ? dfShort.format(finishedAt) : dfLong.format(finishedAt)) + "</td>");
+ writer.println("<td>" + FormattingResultLog.msHumanReadable(executionResult.getElapsedTimeInMs()) + "</td>");
+
+ writer.println("</tr>");
+ }
+ writer.println("</table>");
+ writer.println("</body></html>");
+
+ return stringWriter.toString();
+
+ }
+
+ private String getClassForStatus(final Result.Status status) {
+ return "status" + status.name();
+ }
+
+ private boolean isSingleResult(final Result result) {
+ int count = 0;
+ for (Entry entry : result) {
+ count++;
+ if (count > 1) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private boolean isToday(Date date) {
+ Calendar cal1 = Calendar.getInstance();
+ Calendar cal2 = Calendar.getInstance();
+ cal2.setTime(date);
+ boolean isToday = cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR) &&
+ cal1.get(Calendar.DAY_OF_YEAR) == cal2.get(Calendar.DAY_OF_YEAR);
+ return isToday;
+
+ }
+}
Added: sling/trunk/bundles/extensions/healthcheck/core/src/main/java/org/apache/sling/hc/core/impl/servlet/ResultJsonSerializer.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/healthcheck/core/src/main/java/org/apache/sling/hc/core/impl/servlet/ResultJsonSerializer.java?rev=1695501&view=auto
==============================================================================
--- sling/trunk/bundles/extensions/healthcheck/core/src/main/java/org/apache/sling/hc/core/impl/servlet/ResultJsonSerializer.java (added)
+++ sling/trunk/bundles/extensions/healthcheck/core/src/main/java/org/apache/sling/hc/core/impl/servlet/ResultJsonSerializer.java Wed Aug 12 13:02:45 2015
@@ -0,0 +1,106 @@
+/*
+ * 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 SF 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.hc.core.impl.servlet;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.List;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.sling.commons.json.JSONArray;
+import org.apache.sling.commons.json.JSONException;
+import org.apache.sling.commons.json.JSONObject;
+import org.apache.sling.hc.api.Result;
+import org.apache.sling.hc.api.ResultLog;
+import org.apache.sling.hc.api.execution.HealthCheckExecutionResult;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/** Serializes health check results into json format. */
+@Service(ResultJsonSerializer.class)
+@Component(metatype = false)
+public class ResultJsonSerializer {
+
+ private static final Logger LOG = LoggerFactory.getLogger(ResultJsonSerializer.class);
+
+ static final String OVERALL_RESULT_KEY = "OverallResult";
+
+ public String serialize(final Result overallResult, final List<HealthCheckExecutionResult> executionResults, final String jsonpCallback,
+ boolean includeDebug) {
+
+ LOG.debug("Sending json response... ");
+
+ JSONObject result = new JSONObject();
+ try {
+
+ result.put("overallResult", overallResult.getStatus());
+ JSONArray resultsJsonArr = new JSONArray();
+ result.put("results", resultsJsonArr);
+
+ for (HealthCheckExecutionResult healthCheckResult : executionResults) {
+ resultsJsonArr.put(getJsonForSimpleResult(healthCheckResult, includeDebug));
+ }
+
+ } catch (JSONException e) {
+ LOG.info("Could not serialize health check result: " + e, e);
+ }
+
+ String resultStr;
+ if (StringUtils.isNotBlank(jsonpCallback)) {
+ resultStr = jsonpCallback + "(" + result.toString() + ");";
+ } else {
+ resultStr = result.toString();
+ }
+
+ return resultStr;
+
+ }
+
+ private JSONObject getJsonForSimpleResult(final HealthCheckExecutionResult healthCheckResult, boolean includeDebug) throws JSONException {
+
+ JSONObject result = new JSONObject();
+
+ result.put("name", healthCheckResult.getHealthCheckMetadata().getName());
+ result.put("status", healthCheckResult.getHealthCheckResult().getStatus());
+ result.put("timeInMs", healthCheckResult.getElapsedTimeInMs());
+ result.put("finishedAt", healthCheckResult.getFinishedAt());
+
+ JSONArray messagesArr = new JSONArray();
+ result.put("messages", messagesArr);
+ for (ResultLog.Entry entry : healthCheckResult.getHealthCheckResult()) {
+ if (!includeDebug && entry.getStatus() == Result.Status.DEBUG) {
+ continue;
+ }
+ JSONObject jsonEntry = new JSONObject();
+ jsonEntry.put("status", entry.getStatus());
+ jsonEntry.put("message", entry.getMessage());
+ Exception exception = entry.getException();
+ if (exception != null) {
+ StringWriter stringWriter = new StringWriter();
+ exception.printStackTrace(new PrintWriter(stringWriter));
+ jsonEntry.put("exception", stringWriter.toString());
+ }
+ messagesArr.put(jsonEntry);
+ }
+
+ return result;
+ }
+
+}
Added: sling/trunk/bundles/extensions/healthcheck/core/src/test/java/org/apache/sling/hc/core/impl/servlet/HealthCheckExecutorServletTest.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/healthcheck/core/src/test/java/org/apache/sling/hc/core/impl/servlet/HealthCheckExecutorServletTest.java?rev=1695501&view=auto
==============================================================================
--- sling/trunk/bundles/extensions/healthcheck/core/src/test/java/org/apache/sling/hc/core/impl/servlet/HealthCheckExecutorServletTest.java (added)
+++ sling/trunk/bundles/extensions/healthcheck/core/src/test/java/org/apache/sling/hc/core/impl/servlet/HealthCheckExecutorServletTest.java Wed Aug 12 13:02:45 2015
@@ -0,0 +1,208 @@
+/*
+ * 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 SF 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.hc.core.impl.servlet;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.argThat;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.sling.hc.api.Result;
+import org.apache.sling.hc.api.Result.Status;
+import org.apache.sling.hc.api.execution.HealthCheckExecutionOptions;
+import org.apache.sling.hc.api.execution.HealthCheckExecutionResult;
+import org.apache.sling.hc.api.execution.HealthCheckExecutor;
+import org.apache.sling.hc.core.impl.executor.ExecutionResult;
+import org.apache.sling.hc.util.HealthCheckMetadata;
+import org.hamcrest.Description;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentMatcher;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceReference;
+
+public class HealthCheckExecutorServletTest {
+
+ private HealthCheckExecutorServlet healthCheckExecutorServlet;
+
+ @Mock
+ private HttpServletRequest request;
+
+ @Mock
+ private HttpServletResponse response;
+
+ @Mock
+ private HealthCheckExecutor healthCheckExecutor;
+
+ @Mock
+ private ResultHtmlSerializer htmlSerializer;
+
+ @Mock
+ private ResultJsonSerializer jsonSerializer;
+
+ @Mock
+ private ServiceReference hcServiceRef;
+
+ @Mock
+ private PrintWriter writer;
+
+ @Before
+ public void setup() throws IOException {
+
+ healthCheckExecutorServlet = new HealthCheckExecutorServlet();
+
+ MockitoAnnotations.initMocks(this);
+ healthCheckExecutorServlet.healthCheckExecutor = healthCheckExecutor;
+ healthCheckExecutorServlet.htmlSerializer = htmlSerializer;
+ healthCheckExecutorServlet.jsonSerializer = jsonSerializer;
+
+ doReturn(500L).when(hcServiceRef).getProperty(Constants.SERVICE_ID);
+ doReturn(writer).when(response).getWriter();
+ }
+
+ @Test
+ public void testDoGetHtml() throws ServletException, IOException {
+
+ String testTag = "testTag";
+ doReturn(testTag).when(request).getParameter(HealthCheckExecutorServlet.PARAM_TAGS);
+ doReturn("false").when(request).getParameter(HealthCheckExecutorServlet.PARAM_COMBINE_TAGS_WITH_OR);
+ List<HealthCheckExecutionResult> executionResults = getExecutionResults(Result.Status.CRITICAL);
+ doReturn(executionResults).when(healthCheckExecutor).execute(new HealthCheckExecutionOptions(), testTag);
+
+ healthCheckExecutorServlet.doGet(request, response);
+
+ verifyZeroInteractions(jsonSerializer);
+ verify(htmlSerializer)
+ .serialize(resultEquals(new Result(Result.Status.CRITICAL, "Overall Status CRITICAL")), eq(executionResults), eq(false));
+ }
+
+ @Test
+ public void testDoGetJson() throws ServletException, IOException {
+
+ String testTag = "testTag";
+ doReturn(testTag).when(request).getParameter(HealthCheckExecutorServlet.PARAM_TAGS);
+ doReturn("true").when(request).getParameter(HealthCheckExecutorServlet.PARAM_COMBINE_TAGS_WITH_OR);
+ int timeout = 5000;
+ doReturn(timeout + "").when(request).getParameter(HealthCheckExecutorServlet.PARAM_OVERRIDE_GLOBAL_TIMEOUT);
+ doReturn("/result.json").when(request).getPathInfo();
+ List<HealthCheckExecutionResult> executionResults = getExecutionResults(Result.Status.WARN);
+ HealthCheckExecutionOptions options = new HealthCheckExecutionOptions();
+ options.setCombineTagsWithOr(true);
+ options.setOverrideGlobalTimeout(timeout);
+ doReturn(executionResults).when(healthCheckExecutor).execute(options, testTag);
+
+ healthCheckExecutorServlet.doGet(request, response);
+
+ verifyZeroInteractions(htmlSerializer);
+ verify(jsonSerializer).serialize(resultEquals(new Result(Result.Status.WARN, "Overall Status WARN")), eq(executionResults), anyString(),
+ eq(false));
+
+ }
+
+ private List<HealthCheckExecutionResult> getExecutionResults(Result.Status worstStatus) {
+ List<HealthCheckExecutionResult> results = new ArrayList<HealthCheckExecutionResult>();
+ results.add(new ExecutionResult(new HealthCheckMetadata(hcServiceRef), new Result(worstStatus, worstStatus.name()), 100));
+ results.add(new ExecutionResult(new HealthCheckMetadata(hcServiceRef), new Result(Result.Status.OK, "OK"), 100));
+ return results;
+ }
+
+ @Test
+ public void testGetStatusMapping() throws ServletException {
+
+ Map<Status, Integer> statusMapping = healthCheckExecutorServlet.getStatusMapping("CRITICAL:503");
+ assertEquals(statusMapping.get(Result.Status.OK), (Integer) 200);
+ assertEquals(statusMapping.get(Result.Status.WARN), (Integer) 200);
+ assertEquals(statusMapping.get(Result.Status.CRITICAL), (Integer) 503);
+ assertEquals(statusMapping.get(Result.Status.HEALTH_CHECK_ERROR), (Integer) 503);
+
+ statusMapping = healthCheckExecutorServlet.getStatusMapping("OK:333");
+ assertEquals(statusMapping.get(Result.Status.OK), (Integer) 333);
+ assertEquals(statusMapping.get(Result.Status.WARN), (Integer) 333);
+ assertEquals(statusMapping.get(Result.Status.CRITICAL), (Integer) 333);
+ assertEquals(statusMapping.get(Result.Status.HEALTH_CHECK_ERROR), (Integer) 333);
+
+ statusMapping = healthCheckExecutorServlet.getStatusMapping("OK:200,WARN:418,CRITICAL:503,HEALTH_CHECK_ERROR:500");
+ assertEquals(statusMapping.get(Result.Status.OK), (Integer) 200);
+ assertEquals(statusMapping.get(Result.Status.WARN), (Integer) 418);
+ assertEquals(statusMapping.get(Result.Status.CRITICAL), (Integer) 503);
+ assertEquals(statusMapping.get(Result.Status.HEALTH_CHECK_ERROR), (Integer) 500);
+
+ statusMapping = healthCheckExecutorServlet.getStatusMapping("CRITICAL:503,HEALTH_CHECK_ERROR:500");
+ assertEquals(statusMapping.get(Result.Status.OK), (Integer) 200);
+ assertEquals(statusMapping.get(Result.Status.WARN), (Integer) 200);
+ assertEquals(statusMapping.get(Result.Status.CRITICAL), (Integer) 503);
+ assertEquals(statusMapping.get(Result.Status.HEALTH_CHECK_ERROR), (Integer) 500);
+
+ }
+
+ @Test(expected = ServletException.class)
+ public void testGetStatusMappingInvalidToken() throws ServletException {
+ healthCheckExecutorServlet.getStatusMapping("CRITICAL");
+ }
+
+ @Test(expected = ServletException.class)
+ public void testGetStatusMappingInvalidStatus() throws ServletException {
+ healthCheckExecutorServlet.getStatusMapping("INVALID:200");
+ }
+
+ @Test(expected = ServletException.class)
+ public void testGetStatusMappingInvalidStatusCode() throws ServletException {
+ healthCheckExecutorServlet.getStatusMapping("CRITICAL:xxx");
+ }
+
+ static Result resultEquals(Result expected) {
+ return argThat(new ResultMatcher(expected));
+ }
+
+ static class ResultMatcher extends ArgumentMatcher<Result> {
+
+ private final Result expectedResult;
+
+ public ResultMatcher(Result expected) {
+ this.expectedResult = expected;
+ }
+
+ @Override
+ public boolean matches(Object actual) {
+ Result actualResult = (Result) actual;
+ return actualResult.getStatus().equals(expectedResult.getStatus()); // simple status matching only sufficient for this test case
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText(expectedResult == null ? null : expectedResult.toString());
+ }
+ }
+
+
+}