You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@felix.apache.org by gh...@apache.org on 2018/12/18 22:51:49 UTC
svn commit: r1849243 [3/5] - in /felix/trunk: api/ api/src/ api/src/main/
api/src/main/java/ api/src/main/java/org/ api/src/main/java/org/apache/
api/src/main/java/org/apache/felix/ api/src/main/java/org/apache/felix/hc/
api/src/main/java/org/apache/fe...
Added: felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/executor/HealthCheckResultCache.java
URL: http://svn.apache.org/viewvc/felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/executor/HealthCheckResultCache.java?rev=1849243&view=auto
==============================================================================
--- felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/executor/HealthCheckResultCache.java (added)
+++ felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/executor/HealthCheckResultCache.java Tue Dec 18 22:51:48 2018
@@ -0,0 +1,222 @@
+/*
+ * 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.felix.hc.core.impl.executor;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.felix.hc.api.Result;
+import org.apache.felix.hc.api.Result.Status;
+import org.apache.felix.hc.api.ResultLog;
+import org.apache.felix.hc.api.ResultLog.Entry;
+import org.apache.felix.hc.api.execution.HealthCheckExecutionResult;
+import org.apache.felix.hc.util.HealthCheckMetadata;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/** Caches health check results. */
+public class HealthCheckResultCache {
+
+ private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+ private static final List<Status> NOT_OK_STATUS_VALUES = Arrays.asList(Status.WARN, Status.CRITICAL, Status.HEALTH_CHECK_ERROR);
+
+ /** The map holding the cached results. */
+ private final Map<Long, HealthCheckExecutionResult> cache = new ConcurrentHashMap<Long, HealthCheckExecutionResult>();
+
+ @SuppressWarnings("serial")
+ private final Map<Result.Status, Map<Long, HealthCheckExecutionResult>> cacheOfNotOkResults = new ConcurrentHashMap<Result.Status, Map<Long, HealthCheckExecutionResult>>() {
+ {
+ for (Status status : NOT_OK_STATUS_VALUES) {
+ put(status, new ConcurrentHashMap<Long, HealthCheckExecutionResult>());
+ }
+ }
+ };
+
+ /** Update the cache with the result */
+ public void updateWith(HealthCheckExecutionResult result) {
+ final ExecutionResult executionResult = (ExecutionResult) result;
+ cache.put(executionResult.getServiceId(), result);
+
+ Status status = executionResult.getHealthCheckResult().getStatus();
+ if (status.ordinal() >= Result.Status.WARN.ordinal()) {
+ logger.debug("Caching {} result for HC {}", status, executionResult.getServiceId());
+ cacheOfNotOkResults.get(status).put(executionResult.getServiceId(), result);
+ }
+ }
+
+ /** Get the valid cache results */
+ public void useValidCacheResults(final List<HealthCheckMetadata> metadatas,
+ final Collection<HealthCheckExecutionResult> results,
+ final long resultCacheTtlInMs) {
+ final Set<HealthCheckExecutionResult> cachedResults = new TreeSet<HealthCheckExecutionResult>();
+ final Iterator<HealthCheckMetadata> checksIt = metadatas.iterator();
+ while (checksIt.hasNext()) {
+ final HealthCheckMetadata md = checksIt.next();
+ final HealthCheckExecutionResult result = getValidCacheResult(md, resultCacheTtlInMs);
+ if (result != null) {
+ cachedResults.add(result);
+ checksIt.remove();
+ }
+ }
+ logger.debug("Adding {} results from cache", cachedResults.size());
+ results.addAll(cachedResults);
+ }
+
+ /** Return the cached result if it's still valid. */
+ public HealthCheckExecutionResult getValidCacheResult(final HealthCheckMetadata metadata,
+ final long resultCacheTtlInMs) {
+ return get(metadata, resultCacheTtlInMs);
+ }
+
+ private HealthCheckExecutionResult get(final HealthCheckMetadata metadata, final long globalResultCacheTtlInMs) {
+ final Long key = metadata.getServiceId();
+ final HealthCheckExecutionResult cachedResult = cache.get(key);
+ if (cachedResult != null) {
+ Date finishedAt = cachedResult.getFinishedAt();
+ if (finishedAt == null) {
+ // never cache without proper meta data -> remove it
+ cache.remove(key);
+ return null;
+ }
+
+ long effectiveTtl = getEffectiveTtl(metadata, globalResultCacheTtlInMs);
+ long validUntilLong = finishedAt.getTime() + effectiveTtl;
+ if (validUntilLong < 0) { // if Long.MAX_VALUE is configured, this can become negative
+ validUntilLong = Long.MAX_VALUE;
+ }
+ Date validUntil = new Date(validUntilLong);
+ Date now = new Date();
+ if (validUntil.after(now)) {
+ logger.debug("Cache hit: validUntil={} cachedResult={}", validUntil, cachedResult);
+ return cachedResult;
+ } else {
+ logger.debug("Outdated result: validUntil={} cachedResult={}", validUntil, cachedResult);
+ // not removing result for key as out-dated results are shown for timed out checks if available
+ }
+ }
+
+ // null => no cache hit
+ return null;
+ }
+
+ /** Obtains the effective TTL for a given Metadata descriptor.
+ *
+ * @param metadata Metadata descriptor of health check
+ * @param globalTtl TTL from service configuration of health check executor (used as default)
+ * @return effective TTL */
+ private long getEffectiveTtl(HealthCheckMetadata metadata, long globalTtl) {
+ final long ttl;
+ Long hcTtl = metadata.getResultCacheTtlInMs();
+ if (hcTtl != null && hcTtl > 0) {
+ ttl = hcTtl;
+ } else {
+ ttl = globalTtl;
+ }
+ return ttl;
+ }
+
+ /** Creates a new execution result
+ *
+ * @param origResult
+ * @return */
+ public HealthCheckExecutionResult createExecutionResultWithStickyResults(HealthCheckExecutionResult origResult) {
+ HealthCheckExecutionResult result = origResult;
+
+ HealthCheckMetadata healthCheckMetadata = origResult.getHealthCheckMetadata();
+ Long warningsStickForMinutes = healthCheckMetadata.getWarningsStickForMinutes();
+ if (warningsStickForMinutes != null) {
+ logger.debug("Taking into account sticky results (up to {} min old) for health check ", warningsStickForMinutes,
+ healthCheckMetadata.getName());
+ List<HealthCheckExecutionResult> nonOkResultsFromPast = new ArrayList<HealthCheckExecutionResult>();
+ long cutOffTime = System.currentTimeMillis() - (warningsStickForMinutes * 60 * 1000);
+ for (Status status : NOT_OK_STATUS_VALUES) {
+ long hcServiceId = ((ExecutionResult) origResult).getServiceId();
+ HealthCheckExecutionResult nonOkResultFromPast = cacheOfNotOkResults.get(status).get(hcServiceId);
+ if (nonOkResultFromPast == null) {
+ logger.debug("no sticky result in cache for HC {}", hcServiceId);
+ continue;
+ }
+ if (nonOkResultFromPast == origResult) {
+ logger.debug("result already in cache: {} for HC {}, not adding sticky result", origResult, hcServiceId);
+ continue;
+ }
+ long pastHcTime = nonOkResultFromPast.getFinishedAt().getTime();
+ logger.debug("Time of old {} result: {}", status, pastHcTime);
+ logger.debug("Cut off time: {}", cutOffTime);
+ if (pastHcTime > cutOffTime) {
+ logger.debug("Found sticky result: {}", nonOkResultFromPast);
+ nonOkResultsFromPast.add(nonOkResultFromPast);
+ }
+ }
+
+ if (!nonOkResultsFromPast.isEmpty()) {
+ ResultLog resultLog = new ResultLog();
+ resultLog.add(new Entry(Result.Status.OK, "*** Current Result ***"));
+ for (ResultLog.Entry entry : origResult.getHealthCheckResult()) {
+ resultLog.add(entry);
+ }
+ DateFormat df = new SimpleDateFormat("HH:mm:ss.SSS");
+ for (HealthCheckExecutionResult nonOkResultFromPast : nonOkResultsFromPast) {
+ Status status = nonOkResultFromPast.getHealthCheckResult().getStatus();
+ resultLog.add(
+ new Entry(Result.Status.WARN,
+ "*** Sticky Result " + status + " from " + df.format(nonOkResultFromPast.getFinishedAt()) + " ***"));
+ for (ResultLog.Entry entry : nonOkResultFromPast.getHealthCheckResult()) {
+ resultLog.add(entry);
+ }
+ }
+ result = new ExecutionResult(healthCheckMetadata, new Result(resultLog), origResult.getElapsedTimeInMs());
+ }
+ }
+
+ return result;
+ }
+
+ /** Clear the whole cache */
+ public void clear() {
+ this.cache.clear();
+ for (Status status : NOT_OK_STATUS_VALUES) {
+ cacheOfNotOkResults.get(status).clear();
+ }
+ }
+
+ /** Remove entry from cache */
+ public void removeCachedResult(final Long serviceId) {
+ this.cache.remove(serviceId);
+ for (Status status : NOT_OK_STATUS_VALUES) {
+ cacheOfNotOkResults.get(status).remove(serviceId);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "[HealthCheckResultCache size=" + cache.size() + "]";
+ }
+
+}
Added: felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/executor/async/AsyncHealthCheckExecutor.java
URL: http://svn.apache.org/viewvc/felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/executor/async/AsyncHealthCheckExecutor.java?rev=1849243&view=auto
==============================================================================
--- felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/executor/async/AsyncHealthCheckExecutor.java (added)
+++ felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/executor/async/AsyncHealthCheckExecutor.java Tue Dec 18 22:51:48 2018
@@ -0,0 +1,249 @@
+/*
+ * 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.felix.hc.core.impl.executor.async;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.felix.hc.api.HealthCheck;
+import org.apache.felix.hc.api.Result;
+import org.apache.felix.hc.api.execution.HealthCheckExecutionResult;
+import org.apache.felix.hc.api.execution.HealthCheckSelector;
+import org.apache.felix.hc.core.impl.executor.ExecutionResult;
+import org.apache.felix.hc.core.impl.executor.HealthCheckExecutorThreadPool;
+import org.apache.felix.hc.core.impl.executor.HealthCheckResultCache;
+import org.apache.felix.hc.util.HealthCheckFilter;
+import org.apache.felix.hc.util.HealthCheckMetadata;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceEvent;
+import org.osgi.framework.ServiceListener;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.component.ComponentContext;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Deactivate;
+import org.osgi.service.component.annotations.Reference;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/** Runs health checks that are configured with a cron expression for asynchronous execution. Used by HealthCheckExecutor.
+ *
+ * This implementation uses quartz to support the cron syntax (which is not supported by executors from standard java java.util.concurrent
+ * package. */
+@Component(service = AsyncHealthCheckExecutor.class, immediate = true)
+public class AsyncHealthCheckExecutor implements ServiceListener {
+
+ private static final Logger LOG = LoggerFactory.getLogger(AsyncHealthCheckExecutor.class);
+
+ @Reference
+ HealthCheckExecutorThreadPool healthCheckExecutorThreadPool;
+
+ private Map<HealthCheckMetadata, ExecutionResult> asyncResultsByDescriptor = new ConcurrentHashMap<HealthCheckMetadata, ExecutionResult>();
+
+ private Map<HealthCheckMetadata, AsyncHealthCheckJob> registeredJobs = new HashMap<HealthCheckMetadata, AsyncHealthCheckJob>();
+
+ private BundleContext bundleContext;
+
+ private QuartzCronScheduler quartzCronScheduler = null;
+
+ @Activate
+ protected final void activate(final ComponentContext componentContext) {
+ this.bundleContext = componentContext.getBundleContext();
+ this.bundleContext.addServiceListener(this);
+
+ int count = 0;
+ HealthCheckFilter healthCheckFilter = new HealthCheckFilter(bundleContext);
+ final ServiceReference[] healthCheckReferences = healthCheckFilter.getHealthCheckServiceReferences(HealthCheckSelector.empty());
+ for (ServiceReference serviceReference : healthCheckReferences) {
+ HealthCheckMetadata healthCheckMetadata = new HealthCheckMetadata(serviceReference);
+ if (isAsync(healthCheckMetadata)) {
+ if (scheduleHealthCheck(healthCheckMetadata)) {
+ count++;
+ }
+ }
+ }
+ LOG.debug("Scheduled {} jobs for asynchronous health checks during bundle startup", count);
+ }
+
+ @Deactivate
+ protected final void deactivate(final ComponentContext componentContext) {
+ this.bundleContext.removeServiceListener(this);
+ this.bundleContext = null;
+
+ LOG.debug("Unscheduling {} jobs for asynchronous health checks", registeredJobs.size());
+ for (HealthCheckMetadata healthCheckDescriptor : new LinkedList<HealthCheckMetadata>(registeredJobs.keySet())) {
+ unscheduleHealthCheck(healthCheckDescriptor);
+ }
+
+ if (quartzCronScheduler != null) {
+ quartzCronScheduler.shutdown();
+ }
+ }
+
+ @Override
+ public void serviceChanged(ServiceEvent event) {
+ if (bundleContext == null) {
+ // already deactivated?
+ return;
+ }
+ ServiceReference serviceReference = event.getServiceReference();
+ final boolean isHealthCheck = serviceReference.isAssignableTo(bundleContext.getBundle(), HealthCheck.class.getName());
+
+ if (isHealthCheck) {
+ HealthCheckMetadata healthCheckMetadata = new HealthCheckMetadata(serviceReference);
+ int eventType = event.getType();
+ if (eventType == ServiceEvent.REGISTERED) {
+ LOG.debug("Received service event REGISTERED for health check {}", healthCheckMetadata);
+ scheduleHealthCheck(healthCheckMetadata);
+ } else if (eventType == ServiceEvent.UNREGISTERING) {
+ LOG.debug("Received service event UNREGISTERING for health check {}", healthCheckMetadata);
+ unscheduleHealthCheck(healthCheckMetadata);
+ } else if (eventType == ServiceEvent.MODIFIED) {
+ LOG.debug("Received service event MODIFIED for health check {}", healthCheckMetadata);
+ unscheduleHealthCheck(healthCheckMetadata);
+ scheduleHealthCheck(healthCheckMetadata);
+ }
+
+ }
+ }
+
+ private boolean scheduleHealthCheck(HealthCheckMetadata descriptor) {
+
+ try {
+ AsyncHealthCheckJob healthCheckAsyncJob = null;
+
+ if (isAsyncCron(descriptor)) {
+
+ if (quartzCronScheduler == null) {
+ if (classExists("org.quartz.CronTrigger")) {
+ quartzCronScheduler = new QuartzCronScheduler(healthCheckExecutorThreadPool);
+ LOG.info("Created quartz scheduler for async HC");
+ } else {
+ LOG.warn("Can not schedule async health check with cron expression since quartz library is not on classpath");
+ return false;
+ }
+ }
+
+ healthCheckAsyncJob = new AsyncHealthCheckQuartzCronJob(descriptor, this, bundleContext, quartzCronScheduler);
+ } else if (isAsyncInterval(descriptor)) {
+
+ healthCheckAsyncJob = new AsyncHealthCheckIntervalJob(descriptor, this, bundleContext, healthCheckExecutorThreadPool);
+ }
+
+ if (healthCheckAsyncJob != null) {
+ healthCheckAsyncJob.schedule();
+ registeredJobs.put(descriptor, healthCheckAsyncJob);
+ return true;
+ } else {
+ return false;
+ }
+
+ } catch (Exception e) {
+ LOG.warn("Could not schedule job for " + descriptor + ". Exception: " + e, e);
+ return false;
+ }
+
+ }
+
+ private boolean unscheduleHealthCheck(HealthCheckMetadata descriptor) {
+
+ // here no check for isAsync must be used to ensure previously
+ // scheduled async checks are correctly unscheduled if they have
+ // changed from async to sync.
+
+ AsyncHealthCheckJob job = registeredJobs.remove(descriptor);
+ if (job != null) {
+ return job.unschedule();
+ } else {
+ LOG.warn("No job was registered for descriptor {}", descriptor);
+ return false;
+ }
+ }
+
+ /** Called by the main Executor to get results from async HCs */
+ public void collectAsyncResults(List<HealthCheckMetadata> healthCheckDescriptors, Collection<HealthCheckExecutionResult> results,
+ HealthCheckResultCache cache) {
+ Iterator<HealthCheckMetadata> checksIt = healthCheckDescriptors.iterator();
+
+ Set<ExecutionResult> asyncResults = new TreeSet<ExecutionResult>();
+ while (checksIt.hasNext()) {
+ HealthCheckMetadata healthCheckMetadata = checksIt.next();
+ if (isAsync(healthCheckMetadata)) {
+ ExecutionResult result = asyncResultsByDescriptor.get(healthCheckMetadata);
+ if (result == null) {
+
+ result = new ExecutionResult(healthCheckMetadata,
+ new Result(Result.Status.OK, "Async Health Check with cron expression '"
+ + healthCheckMetadata.getAsyncCronExpression() + "' has not yet been executed."),
+ 0L);
+
+ asyncResults.add(result);
+ }
+ asyncResults.add(result);
+ // remove from HC collection to not execute the check in HealthCheckExecutorImpl
+ checksIt.remove();
+ }
+ }
+
+ LOG.debug("Caching {} results from async results", asyncResults.size());
+ for (ExecutionResult result : asyncResults) {
+ cache.updateWith(result);
+ }
+
+ LOG.debug("Adding {} results from async results", asyncResults.size());
+ results.addAll(asyncResults);
+
+ }
+
+ public void updateWith(HealthCheckExecutionResult result) {
+ if (isAsync(result.getHealthCheckMetadata())) {
+ asyncResultsByDescriptor.put(result.getHealthCheckMetadata(), (ExecutionResult) result);
+ LOG.debug("Updated result for async hc {} with {}", result.getHealthCheckMetadata(), result);
+ }
+ }
+
+ private boolean isAsync(HealthCheckMetadata healthCheckMetadata) {
+ return isAsyncCron(healthCheckMetadata) || isAsyncInterval(healthCheckMetadata);
+ }
+
+ private boolean isAsyncCron(HealthCheckMetadata healthCheckMetadata) {
+ return StringUtils.isNotBlank(healthCheckMetadata.getAsyncCronExpression());
+ }
+
+ private boolean isAsyncInterval(HealthCheckMetadata healthCheckMetadata) {
+ return healthCheckMetadata.getAsyncIntervalInSec() != null;
+ }
+
+ private boolean classExists(String className) {
+ try {
+ Class.forName(className);
+ return true;
+ } catch (ClassNotFoundException e) {
+ return false;
+ }
+ }
+
+}
Added: felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/executor/async/AsyncHealthCheckIntervalJob.java
URL: http://svn.apache.org/viewvc/felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/executor/async/AsyncHealthCheckIntervalJob.java?rev=1849243&view=auto
==============================================================================
--- felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/executor/async/AsyncHealthCheckIntervalJob.java (added)
+++ felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/executor/async/AsyncHealthCheckIntervalJob.java Tue Dec 18 22:51:48 2018
@@ -0,0 +1,59 @@
+/*
+ * 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.felix.hc.core.impl.executor.async;
+
+import java.util.concurrent.ScheduledFuture;
+
+import org.apache.felix.hc.core.impl.executor.HealthCheckExecutorThreadPool;
+import org.apache.felix.hc.util.HealthCheckMetadata;
+import org.osgi.framework.BundleContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class AsyncHealthCheckIntervalJob extends AsyncHealthCheckJob implements Runnable {
+ private static final Logger LOG = LoggerFactory.getLogger(AsyncHealthCheckExecutor.class);
+
+ private final HealthCheckExecutorThreadPool healthCheckExecutorThreadPool;
+ private ScheduledFuture<?> scheduleFuture = null;
+
+ public AsyncHealthCheckIntervalJob(HealthCheckMetadata healthCheckDescriptor, AsyncHealthCheckExecutor asyncHealthCheckExecutor,
+ BundleContext bundleContext, HealthCheckExecutorThreadPool healthCheckExecutorThreadPool) {
+ super(healthCheckDescriptor, asyncHealthCheckExecutor, bundleContext);
+ this.healthCheckExecutorThreadPool = healthCheckExecutorThreadPool;
+ }
+
+ public boolean schedule() {
+ Long asyncIntervalInSec = healthCheckDescriptor.getAsyncIntervalInSec();
+ scheduleFuture = healthCheckExecutorThreadPool.scheduleAtFixedRate(this, asyncIntervalInSec);
+ LOG.info("Scheduled job {} for execution every {}sec", this, asyncIntervalInSec);
+ return true;
+ }
+
+ @Override
+ public boolean unschedule() {
+
+ if (scheduleFuture != null) {
+ LOG.debug("Unscheduling async job for {}", healthCheckDescriptor);
+ return scheduleFuture.cancel(false);
+ } else {
+ LOG.debug("No scheduled future for {} exists", healthCheckDescriptor);
+ return false;
+ }
+ }
+
+}
\ No newline at end of file
Added: felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/executor/async/AsyncHealthCheckJob.java
URL: http://svn.apache.org/viewvc/felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/executor/async/AsyncHealthCheckJob.java?rev=1849243&view=auto
==============================================================================
--- felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/executor/async/AsyncHealthCheckJob.java (added)
+++ felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/executor/async/AsyncHealthCheckJob.java Tue Dec 18 22:51:48 2018
@@ -0,0 +1,67 @@
+/*
+ * 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.felix.hc.core.impl.executor.async;
+
+import org.apache.felix.hc.api.execution.HealthCheckExecutionResult;
+import org.apache.felix.hc.core.impl.executor.HealthCheckFuture;
+import org.apache.felix.hc.core.impl.executor.HealthCheckFuture.Callback;
+import org.apache.felix.hc.util.HealthCheckMetadata;
+import org.osgi.framework.BundleContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public abstract class AsyncHealthCheckJob implements Runnable {
+ private static final Logger LOG = LoggerFactory.getLogger(AsyncHealthCheckJob.class);
+
+ protected final HealthCheckMetadata healthCheckDescriptor;
+ protected final AsyncHealthCheckExecutor asyncHealthCheckExecutor;
+ protected final BundleContext bundleContext;
+
+ public AsyncHealthCheckJob(HealthCheckMetadata healthCheckDescriptor, AsyncHealthCheckExecutor asyncHealthCheckExecutor,
+ BundleContext bundleContext) {
+ this.healthCheckDescriptor = healthCheckDescriptor;
+ this.asyncHealthCheckExecutor = asyncHealthCheckExecutor;
+ this.bundleContext = bundleContext;
+ }
+
+ @Override
+ public void run() {
+
+ LOG.debug("Running job {}", this);
+ HealthCheckFuture healthCheckFuture = new HealthCheckFuture(healthCheckDescriptor, bundleContext, new Callback() {
+
+ @Override
+ public void finished(HealthCheckExecutionResult result) {
+ asyncHealthCheckExecutor.updateWith(result);
+ }
+ });
+
+ // run future in same thread (as we are already async via scheduler)
+ healthCheckFuture.run();
+
+ }
+
+ public abstract boolean schedule();
+
+ public abstract boolean unschedule();
+
+ @Override
+ public String toString() {
+ return "[Async job for " + this.healthCheckDescriptor + "]";
+ }
+}
\ No newline at end of file
Added: felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/executor/async/AsyncHealthCheckQuartzCronJob.java
URL: http://svn.apache.org/viewvc/felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/executor/async/AsyncHealthCheckQuartzCronJob.java?rev=1849243&view=auto
==============================================================================
--- felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/executor/async/AsyncHealthCheckQuartzCronJob.java (added)
+++ felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/executor/async/AsyncHealthCheckQuartzCronJob.java Tue Dec 18 22:51:48 2018
@@ -0,0 +1,112 @@
+/*
+ * 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.felix.hc.core.impl.executor.async;
+
+import static org.quartz.CronScheduleBuilder.cronSchedule;
+import static org.quartz.JobBuilder.newJob;
+import static org.quartz.TriggerBuilder.newTrigger;
+
+import org.apache.felix.hc.util.HealthCheckMetadata;
+import org.osgi.framework.BundleContext;
+import org.quartz.CronTrigger;
+import org.quartz.Job;
+import org.quartz.JobDataMap;
+import org.quartz.JobDetail;
+import org.quartz.JobExecutionContext;
+import org.quartz.JobExecutionException;
+import org.quartz.JobKey;
+import org.quartz.Scheduler;
+import org.quartz.SchedulerException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class AsyncHealthCheckQuartzCronJob extends AsyncHealthCheckJob implements Runnable {
+ private static final Logger LOG = LoggerFactory.getLogger(AsyncHealthCheckExecutor.class);
+
+ private static final String JOB_DATA_KEY_JOB = "asyncHcJob";
+
+ protected final QuartzCronScheduler quartzCronScheduler;
+ private JobKey jobKey = null;
+
+ public AsyncHealthCheckQuartzCronJob(HealthCheckMetadata healthCheckDescriptor, AsyncHealthCheckExecutor asyncHealthCheckExecutor,
+ BundleContext bundleContext, QuartzCronScheduler quartzScheduler) {
+ super(healthCheckDescriptor, asyncHealthCheckExecutor, bundleContext);
+ this.quartzCronScheduler = quartzScheduler;
+ }
+
+ public JobKey getJobKey() {
+ return jobKey;
+ }
+
+ private JobDetail getQuartzJobDetail() {
+ JobDataMap jobData = new JobDataMap();
+ jobData.put(JOB_DATA_KEY_JOB, this);
+
+ JobDetail job = newJob(AsyncHealthCheckQuartzCronJob.QuartzJob.class).setJobData(jobData)
+ .withIdentity("job-hc-" + healthCheckDescriptor.getServiceId(), "async-healthchecks")
+ .build();
+
+ jobKey = job.getKey();
+
+ return job;
+ }
+
+ public boolean schedule() {
+
+ try {
+ Scheduler scheduler = quartzCronScheduler.getScheduler();
+
+ JobDetail job = getQuartzJobDetail();
+ CronTrigger cronTrigger = newTrigger().withSchedule(cronSchedule(healthCheckDescriptor.getAsyncCronExpression())).forJob(job)
+ .build();
+
+ scheduler.scheduleJob(job, cronTrigger);
+ LOG.info("Scheduled job {} with trigger {}", job, cronTrigger);
+ return true;
+ } catch (SchedulerException e) {
+ LOG.error("Could not schedule job for " + healthCheckDescriptor + ": " + e, e);
+ return false;
+ }
+
+ }
+
+ @Override
+ public boolean unschedule() {
+ Scheduler scheduler = quartzCronScheduler.getScheduler();
+ LOG.debug("Unscheduling job {}", jobKey);
+ try {
+ scheduler.deleteJob(jobKey);
+ return true;
+ } catch (SchedulerException e) {
+ LOG.error("Could not unschedule job for " + jobKey + ": " + e, e);
+ return false;
+ }
+ }
+
+ // quartz forces to pass in a class object (and not an instance), hence this helper class is needed
+ public static class QuartzJob implements Job {
+
+ @Override
+ public void execute(JobExecutionContext context) throws JobExecutionException {
+ AsyncHealthCheckQuartzCronJob hc = (AsyncHealthCheckQuartzCronJob) context.getJobDetail().getJobDataMap().get(JOB_DATA_KEY_JOB);
+ hc.run();
+ }
+
+ }
+
+}
\ No newline at end of file
Added: felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/executor/async/QuartzCronScheduler.java
URL: http://svn.apache.org/viewvc/felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/executor/async/QuartzCronScheduler.java?rev=1849243&view=auto
==============================================================================
--- felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/executor/async/QuartzCronScheduler.java (added)
+++ felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/executor/async/QuartzCronScheduler.java Tue Dec 18 22:51:48 2018
@@ -0,0 +1,116 @@
+/*
+ * 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.felix.hc.core.impl.executor.async;
+
+import org.apache.felix.hc.core.impl.executor.HealthCheckExecutorThreadPool;
+import org.quartz.Scheduler;
+import org.quartz.SchedulerException;
+import org.quartz.impl.DirectSchedulerFactory;
+import org.quartz.simpl.RAMJobStore;
+import org.quartz.spi.ThreadPool;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class QuartzCronScheduler {
+ private static final Logger LOG = LoggerFactory.getLogger(QuartzCronScheduler.class);
+
+ private static final String HC_SCHEDULER_NAME = "quartz.hc.scheduler_name";
+
+ private Scheduler scheduler;
+
+ public QuartzCronScheduler(HealthCheckExecutorThreadPool healthCheckExecutorThreadPool) {
+ try {
+ DirectSchedulerFactory schedulerFactory = DirectSchedulerFactory.getInstance();
+ ThreadPool threadPool = new QuartzThreadPool(healthCheckExecutorThreadPool);
+ schedulerFactory.createScheduler(HC_SCHEDULER_NAME, "id_" + System.currentTimeMillis(), threadPool, new RAMJobStore());
+ scheduler = schedulerFactory.getScheduler(HC_SCHEDULER_NAME);
+ scheduler.start();
+ LOG.debug("Started quartz scheduler {}", scheduler);
+ } catch (SchedulerException e) {
+ throw new IllegalStateException("Could not initialise/start quartz scheduler " + HC_SCHEDULER_NAME, e);
+ }
+ }
+
+ public void shutdown() {
+ if (scheduler != null) {
+ try {
+ scheduler.shutdown(false);
+ LOG.debug("Shutdown of quartz scheduler finished: {}", scheduler);
+ } catch (SchedulerException e) {
+ throw new IllegalStateException("Could not shutdown quartz scheduler " + HC_SCHEDULER_NAME, e);
+ }
+ }
+ }
+
+ public Scheduler getScheduler() {
+ return scheduler;
+ }
+
+ public class QuartzThreadPool implements ThreadPool {
+
+ private final HealthCheckExecutorThreadPool healthCheckExecutorThreadPool;
+
+ public QuartzThreadPool(HealthCheckExecutorThreadPool healthCheckExecutorThreadPool) {
+ this.healthCheckExecutorThreadPool = healthCheckExecutorThreadPool;
+ }
+
+ /** @see org.quartz.spi.QuartzThreadPool#getPoolSize() */
+ @Override
+ public int getPoolSize() {
+ return healthCheckExecutorThreadPool.getPoolSize();
+ }
+
+ /** @see org.quartz.spi.QuartzThreadPool#initialize() */
+ @Override
+ public void initialize() {
+ // nothing to do
+ }
+
+ /** @see org.quartz.spi.ThreadPool#setInstanceId(java.lang.String) */
+ @Override
+ public void setInstanceId(final String id) {
+ // we ignore this
+ }
+
+ /** @see org.quartz.spi.ThreadPool#setInstanceName(java.lang.String) */
+ @Override
+ public void setInstanceName(final String name) {
+ // we ignore this
+ }
+
+ /** @see org.quartz.spi.QuartzThreadPool#runInThread(java.lang.Runnable) */
+ @Override
+ public boolean runInThread(final Runnable job) {
+ healthCheckExecutorThreadPool.execute(job);
+ return true;
+ }
+
+ /** @see org.quartz.spi.ThreadPool#blockForAvailableThreads() */
+ @Override
+ public int blockForAvailableThreads() {
+ return healthCheckExecutorThreadPool.getMaxCurrentlyAvailableThreads();
+ }
+
+ /** @see org.quartz.spi.QuartzThreadPool#shutdown(boolean) */
+ @Override
+ public void shutdown(final boolean waitForJobsToComplete) {
+ // this executor is bound to the SCR lifecycle of HealthCheckExecutorThreadPool
+ }
+ }
+
+}
Added: felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/servlet/HealthCheckExecutorServlet.java
URL: http://svn.apache.org/viewvc/felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/servlet/HealthCheckExecutorServlet.java?rev=1849243&view=auto
==============================================================================
--- felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/servlet/HealthCheckExecutorServlet.java (added)
+++ felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/servlet/HealthCheckExecutorServlet.java Tue Dec 18 22:51:48 2018
@@ -0,0 +1,405 @@
+/*
+ * 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.felix.hc.core.impl.servlet;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+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.lang3.StringEscapeUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.felix.hc.api.Result;
+import org.apache.felix.hc.api.Result.Status;
+import org.apache.felix.hc.api.execution.HealthCheckExecutionOptions;
+import org.apache.felix.hc.api.execution.HealthCheckExecutionResult;
+import org.apache.felix.hc.api.execution.HealthCheckExecutor;
+import org.apache.felix.hc.api.execution.HealthCheckSelector;
+import org.osgi.service.component.ComponentContext;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.ConfigurationPolicy;
+import org.osgi.service.component.annotations.Deactivate;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.http.HttpService;
+import org.osgi.service.metatype.annotations.Designate;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/** Servlet that triggers the health check executor to return results via http.
+ *
+ * Parameters:
+ * <ul>
+ * <li>tags: The health check tags to take into account
+ * <li>format: html|json|jsonp
+ * <li>includeDebug: If true, debug messages from result log are included.
+ * <li>callback: For jsonp, the JS callback function name (defaults to "processHealthCheckResults")
+ * <li>httpStatus: health check status to http status mapping in format httpStatus=WARN:418,CRITICAL:503,HEALTH_CHECK_ERROR:500.
+ * </ul>
+ *
+ * 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.
+ * <p>
+ * Useful in combination with load balancers. */
+@Component(configurationPolicy = ConfigurationPolicy.REQUIRE)
+@Designate(ocd = HealthCheckExecutorServletConfiguration.class)
+public class HealthCheckExecutorServlet extends HttpServlet {
+ private static final long serialVersionUID = 8013511523994541848L;
+
+ private static final Logger LOG = LoggerFactory.getLogger(HealthCheckExecutorServlet.class);
+ public static final String PARAM_SPLIT_REGEX = "[,;]+";
+
+ static class Param {
+ final String name;
+ final String description;
+
+ Param(String n, String d) {
+ name = n;
+ description = d;
+ }
+ }
+
+ static final Param PARAM_TAGS = new Param("tags",
+ "Comma-separated list of health checks tags to select - can also be specified via path, e.g. /system/health/tag1,tag2.json. Exclusions can be done by prepending '-' to the tag name");
+ static final Param PARAM_FORMAT = new Param("format", "Output format, html|json|jsonp|txt - an extension in the URL overrides this");
+ static final Param PARAM_HTTP_STATUS = new Param("httpStatus", "Specify HTTP result code, for example"
+ + " CRITICAL:503 (status 503 if result >= CRITICAL)"
+ + " or CRITICAL:503,HEALTH_CHECK_ERROR:500,OK:418 for more specific HTTP status");
+
+ static final Param PARAM_COMBINE_TAGS_WITH_OR = new Param("combineTagsWithOr",
+ "Combine tags with OR, active by default. Set to false to combine with AND");
+ static final Param PARAM_FORCE_INSTANT_EXECUTION = new Param("forceInstantExecution",
+ "If true, forces instant execution by executing async health checks directly, circumventing the cache (2sec by default) of the HealthCheckExecutor");
+ static final Param PARAM_OVERRIDE_GLOBAL_TIMEOUT = new Param("timeout",
+ "(msec) a timeout status is returned for any health check still running after this period. Overrides the default HealthCheckExecutor timeout");
+
+ static final Param PARAM_INCLUDE_DEBUG = new Param("hcDebug", "Include the DEBUG output of the Health Checks");
+
+ static final Param PARAM_NAMES = new Param("names",
+ "Comma-separated list of health check names to select. Exclusions can be done by prepending '-' to the health check name");
+
+ static final String JSONP_CALLBACK_DEFAULT = "processHealthCheckResults";
+ static final Param PARAM_JSONP_CALLBACK = new Param("callback",
+ "name of the JSONP callback function to use, defaults to " + JSONP_CALLBACK_DEFAULT);
+
+ static final Param[] PARAM_LIST = { PARAM_TAGS, PARAM_NAMES, PARAM_FORMAT, PARAM_HTTP_STATUS, PARAM_COMBINE_TAGS_WITH_OR,
+ PARAM_FORCE_INSTANT_EXECUTION, PARAM_OVERRIDE_GLOBAL_TIMEOUT, PARAM_INCLUDE_DEBUG, PARAM_JSONP_CALLBACK };
+
+ static final String FORMAT_HTML = "html";
+ static final String FORMAT_JSON = "json";
+ static final String FORMAT_JSONP = "jsonp";
+ static final String FORMAT_TXT = "txt";
+ static final String FORMAT_VERBOSE_TXT = "verbose.txt";
+
+ private static final String CONTENT_TYPE_HTML = "text/html";
+ private static final String CONTENT_TYPE_TXT = "text/plain";
+ 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 String[] servletPaths;
+
+ private boolean disabled;
+
+ private String servletPath;
+
+ private String corsAccessControlAllowOrigin;
+
+ private static final String CORS_ORIGIN_HEADER_NAME = "Access-Control-Allow-Origin";
+
+ @Reference
+ private HttpService httpService;
+
+ @Reference
+ HealthCheckExecutor healthCheckExecutor;
+
+ @Reference
+ ResultHtmlSerializer htmlSerializer;
+
+ @Reference
+ ResultJsonSerializer jsonSerializer;
+
+ @Reference
+ ResultTxtSerializer txtSerializer;
+
+ @Reference
+ ResultTxtVerboseSerializer verboseTxtSerializer;
+
+ @Activate
+ protected final void activate(final HealthCheckExecutorServletConfiguration configuration) {
+ this.servletPath = configuration.servletPath();
+ this.disabled = configuration.disabled();
+ this.corsAccessControlAllowOrigin = configuration.cors_accessControlAllowOrigin();
+
+ LOG.info("servletPath={}", servletPath);
+ LOG.info("disabled={}", disabled);
+ LOG.info("corsAccessControlAllowOrigin={}", corsAccessControlAllowOrigin);
+
+ Map<String, HttpServlet> servletsToRegister = new LinkedHashMap<String, HttpServlet>();
+ servletsToRegister.put(this.servletPath, this);
+ servletsToRegister.put(this.servletPath + "." + FORMAT_HTML, new ProxyServlet(FORMAT_HTML));
+ servletsToRegister.put(this.servletPath + "." + FORMAT_JSON, new ProxyServlet(FORMAT_JSON));
+ servletsToRegister.put(this.servletPath + "." + FORMAT_JSONP, new ProxyServlet(FORMAT_JSONP));
+ servletsToRegister.put(this.servletPath + "." + FORMAT_TXT, new ProxyServlet(FORMAT_TXT));
+ servletsToRegister.put(this.servletPath + "." + FORMAT_VERBOSE_TXT, new ProxyServlet(FORMAT_VERBOSE_TXT));
+
+ if (disabled) {
+ LOG.info("Health Check Servlet is disabled by configuration");
+ return;
+ }
+
+ for (final Map.Entry<String, HttpServlet> servlet : servletsToRegister.entrySet()) {
+ try {
+ LOG.info("Registering HC servlet {} to path {}", getClass().getSimpleName(), servlet.getKey());
+ this.httpService.registerServlet(servlet.getKey(), servlet.getValue(), null, null);
+ } catch (Exception e) {
+ LOG.error("Could not register health check servlet: " + e, e);
+ }
+ }
+ this.servletPaths = servletsToRegister.keySet().toArray(new String[0]);
+
+ }
+
+ @Deactivate
+ public void deactivate(final ComponentContext componentContext) {
+ if (disabled || this.servletPaths == null) {
+ return;
+ }
+
+ for (final String servletPath : this.servletPaths) {
+ try {
+ LOG.debug("Unregistering path {}", servletPath);
+ this.httpService.unregister(servletPath);
+ } catch (Exception e) {
+ LOG.error("Could not unregister health check servlet: " + e, e);
+ }
+ }
+ this.servletPaths = null;
+ }
+
+ protected void doGet(final HttpServletRequest request, final HttpServletResponse response, final String format)
+ throws ServletException, IOException {
+ HealthCheckSelector selector = HealthCheckSelector.empty();
+ String pathInfo = request.getPathInfo();
+ String pathTokensStr = StringUtils.removeStart(splitFormat(pathInfo)[0], "/");
+
+ List<String> tags = new ArrayList<String>();
+ List<String> names = new ArrayList<String>();
+
+ if (StringUtils.isNotBlank(pathTokensStr)) {
+ String[] pathTokens = pathTokensStr.split(PARAM_SPLIT_REGEX);
+ for (String pathToken : pathTokens) {
+ if (pathToken.indexOf(' ') >= 0) {
+ // token contains space. assume it is a name
+ names.add(pathToken);
+ } else {
+ tags.add(pathToken);
+ }
+ }
+ }
+ if (tags.size() == 0) {
+ // if not provided via path use parameter or default
+ tags = Arrays.asList(StringUtils.defaultIfEmpty(request.getParameter(PARAM_TAGS.name), "").split(PARAM_SPLIT_REGEX));
+ }
+ selector.withTags(tags.toArray(new String[0]));
+
+ if (names.size() == 0) {
+ // if not provided via path use parameter or default
+ names = Arrays.asList(StringUtils.defaultIfEmpty(request.getParameter(PARAM_NAMES.name), "").split(PARAM_SPLIT_REGEX));
+ }
+ selector.withNames(names.toArray(new String[0]));
+
+ final Boolean includeDebug = Boolean.valueOf(request.getParameter(PARAM_INCLUDE_DEBUG.name));
+ final Map<Result.Status, Integer> statusMapping = request.getParameter(PARAM_HTTP_STATUS.name) != null ? getStatusMapping(request
+ .getParameter(PARAM_HTTP_STATUS.name)) : null;
+
+ HealthCheckExecutionOptions executionOptions = new HealthCheckExecutionOptions();
+ executionOptions.setCombineTagsWithOr(
+ Boolean.valueOf(StringUtils.defaultString(request.getParameter(PARAM_COMBINE_TAGS_WITH_OR.name), "true")));
+ executionOptions.setForceInstantExecution(Boolean.valueOf(request.getParameter(PARAM_FORCE_INSTANT_EXECUTION.name)));
+ String overrideGlobalTimeoutVal = request.getParameter(PARAM_OVERRIDE_GLOBAL_TIMEOUT.name);
+ if (StringUtils.isNumeric(overrideGlobalTimeoutVal)) {
+ executionOptions.setOverrideGlobalTimeout(Integer.valueOf(overrideGlobalTimeoutVal));
+ }
+
+ List<HealthCheckExecutionResult> executionResults = this.healthCheckExecutor.execute(selector, executionOptions);
+
+ Result.Status mostSevereStatus = Result.Status.OK;
+ 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);
+ sendCorsHeaders(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.name), JSONP_CALLBACK_DEFAULT);
+ sendJsonResponse(overallResult, executionResults, jsonpCallback, response, includeDebug);
+ } else if (StringUtils.endsWith(format, FORMAT_TXT)) {
+ sendTxtResponse(overallResult, response, StringUtils.equals(format, FORMAT_VERBOSE_TXT), executionResults, includeDebug);
+ } else {
+ response.setContentType("text/plain");
+ response.getWriter().println("Invalid format " + format + " - supported formats: html|json|jsonp|txt|verbose.txt");
+ }
+ }
+
+ @Override
+ protected void doGet(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException {
+ String pathInfo = request.getPathInfo();
+ String format = splitFormat(pathInfo)[1];
+ if (StringUtils.isBlank(format)) {
+ // if not provided via extension use parameter or default
+ format = StringUtils.defaultIfEmpty(request.getParameter(PARAM_FORMAT.name), FORMAT_HTML);
+ }
+ doGet(request, response, format);
+ }
+
+ private String[] splitFormat(String pathInfo) {
+ for (String format : new String[] { FORMAT_HTML, FORMAT_JSON, FORMAT_JSONP, FORMAT_VERBOSE_TXT, FORMAT_TXT }) {
+ String formatWithDot = "." + format;
+ if (StringUtils.endsWith(pathInfo, formatWithDot)) {
+ return new String[] { StringUtils.substringBeforeLast(pathInfo, formatWithDot), format };
+ }
+ }
+ return new String[] { pathInfo, null };
+ }
+
+ private void sendTxtResponse(final Result overallResult, final HttpServletResponse response, boolean verbose,
+ List<HealthCheckExecutionResult> executionResults, boolean includeDebug) throws IOException {
+ response.setContentType(CONTENT_TYPE_TXT);
+ response.setCharacterEncoding("UTF-8");
+ if (verbose) {
+ response.getWriter().write(verboseTxtSerializer.serialize(overallResult, executionResults, includeDebug));
+ } else {
+ response.getWriter().write(txtSerializer.serialize(overallResult));
+ }
+ }
+
+ 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);
+ }
+ response.setCharacterEncoding("UTF-8");
+
+ 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 {
+ response.setContentType(CONTENT_TYPE_HTML);
+ response.setCharacterEncoding("UTF-8");
+ response.setHeader(STATUS_HEADER_NAME, overallResult.toString());
+ response.getWriter().append(this.htmlSerializer.serialize(overallResult, executionResults, getHtmlHelpText(), includeDebug));
+ }
+
+ private void sendNoCacheHeaders(final HttpServletResponse response) {
+ response.setHeader(CACHE_CONTROL_KEY, CACHE_CONTROL_VALUE);
+ }
+
+ private void sendCorsHeaders(final HttpServletResponse response) {
+ if (StringUtils.isNotBlank(corsAccessControlAllowOrigin)) {
+ response.setHeader(CORS_ORIGIN_HEADER_NAME, corsAccessControlAllowOrigin);
+ }
+ }
+
+ private String getHtmlHelpText() {
+ final StringBuilder sb = new StringBuilder();
+ sb.append("<h3>Supported URL parameters</h3>\n");
+ for (Param p : PARAM_LIST) {
+ sb.append("<b>").append(p.name).append("</b>:");
+ sb.append(StringEscapeUtils.escapeHtml4(p.description));
+ sb.append("<br/>");
+ }
+ return sb.toString();
+ }
+
+ 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;
+ }
+
+ private class ProxyServlet extends HttpServlet {
+
+ private final String format;
+
+ private ProxyServlet(final String format) {
+ this.format = format;
+ }
+
+ @Override
+ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+ HealthCheckExecutorServlet.this.doGet(req, resp, format);
+ }
+ }
+
+}
Added: felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/servlet/HealthCheckExecutorServletConfiguration.java
URL: http://svn.apache.org/viewvc/felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/servlet/HealthCheckExecutorServletConfiguration.java?rev=1849243&view=auto
==============================================================================
--- felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/servlet/HealthCheckExecutorServletConfiguration.java (added)
+++ felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/servlet/HealthCheckExecutorServletConfiguration.java Tue Dec 18 22:51:48 2018
@@ -0,0 +1,38 @@
+/*
+ * 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.felix.hc.core.impl.servlet;
+
+import org.osgi.service.metatype.annotations.AttributeDefinition;
+import org.osgi.service.metatype.annotations.ObjectClassDefinition;
+
+@ObjectClassDefinition(name = "Apache Felix Health Check Executor Servlet", description = "Serializes health check results into html, json or txt format")
+@interface HealthCheckExecutorServletConfiguration {
+
+ String SERVLET_PATH_DEFAULT = "/system/health";
+
+ @AttributeDefinition(name = "Disabled", description = "Allows to disable the servlet if required for security reasons")
+ boolean disabled() default false;
+
+ @AttributeDefinition(name = "Path", description = "Servlet path (defaults to " + SERVLET_PATH_DEFAULT
+ + " in order to not be accessible via Apache/Internet)")
+ String servletPath() default SERVLET_PATH_DEFAULT;
+
+ @AttributeDefinition(name = "CORS Access-Control-Allow-Origin", description = "Sets the Access-Control-Allow-Origin CORS header. If blank no header is sent.")
+ String cors_accessControlAllowOrigin() default "*";
+
+}
Added: felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/servlet/ResultHtmlSerializer.java
URL: http://svn.apache.org/viewvc/felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/servlet/ResultHtmlSerializer.java?rev=1849243&view=auto
==============================================================================
--- felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/servlet/ResultHtmlSerializer.java (added)
+++ felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/servlet/ResultHtmlSerializer.java Tue Dec 18 22:51:48 2018
@@ -0,0 +1,153 @@
+/*
+ * 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.felix.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.List;
+
+import org.apache.commons.lang3.StringEscapeUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.felix.hc.api.Result;
+import org.apache.felix.hc.api.ResultLog.Entry;
+import org.apache.felix.hc.api.execution.HealthCheckExecutionResult;
+import org.apache.felix.hc.util.FormattingResultLog;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+
+/** Serializes health check results into html format. */
+@Component(service = ResultHtmlSerializer.class)
+public class ResultHtmlSerializer {
+
+ private String styleString;
+
+ @Activate
+ protected final void activate(final ResultHtmlSerializerConfiguration configuration) {
+ this.styleString = configuration.styleString();
+ }
+
+ public String serialize(final Result overallResult, final List<HealthCheckExecutionResult> executionResults, String escapedHelpText,
+ 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 <span style='color:gray'>(tags)</span></th><th>Status</th><th>Log</th><th>Finished At</th><th>Time</th></tr></thead>");
+ for (HealthCheckExecutionResult executionResult : executionResults) {
+ Result result = executionResult.getHealthCheckResult();
+ List<String> tags = executionResult.getHealthCheckMetadata().getTags();
+ boolean hasTags = tags != null && tags.size() > 0 && StringUtils.isNotBlank(tags.get(0));
+ writer.print("<tr class=\"" + getClassForStatus(result.getStatus()) + "\">");
+ writer.print("<td><p title=\"" + StringEscapeUtils.escapeHtml4(executionResult.getHealthCheckMetadata().getName()) + "\">"
+ + StringEscapeUtils.escapeHtml4(executionResult.getHealthCheckMetadata().getTitle()) + "");
+ if (hasTags) {
+ writer.println("<br/><span style='color:gray'>" + StringEscapeUtils.escapeHtml4(StringUtils.join(tags, ", ")) + "</span>");
+ }
+ writer.println("</p></td>");
+ writer.println("<td style='font-weight:bold;'>" + StringEscapeUtils.escapeHtml4(result.getStatus().toString()) + "</td>");
+ writer.println("<td>");
+ boolean isFirst = true;
+
+ boolean isSingleResult = isSingleResult(result);
+
+ for (Entry entry : result) {
+ if (!includeDebug && entry.isDebug()) {
+ continue;
+ }
+
+ if (isFirst) {
+ isFirst = false;
+ } else {
+ writer.println("<br/>\n");
+ }
+
+ boolean showStatus = !isSingleResult && !entry.isDebug() && entry.getStatus() != Result.Status.OK;
+
+ String message = StringEscapeUtils.escapeHtml4(entry.getMessage());
+ if (entry.isDebug()) {
+ message = "<span style='color:gray'/>" + message + "</span>";
+ }
+ writer.println((showStatus ? StringEscapeUtils.escapeHtml4(entry.getStatus().toString()) + " " : "") + message);
+
+ Exception exception = entry.getException();
+ if (exception != null) {
+ writer.println("<span style='width:20px'/>" + StringEscapeUtils.escapeHtml4(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("<div class='helpText'>");
+ writer.println(escapedHelpText);
+ writer.println("</div>");
+ 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: felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/servlet/ResultHtmlSerializerConfiguration.java
URL: http://svn.apache.org/viewvc/felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/servlet/ResultHtmlSerializerConfiguration.java?rev=1849243&view=auto
==============================================================================
--- felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/servlet/ResultHtmlSerializerConfiguration.java (added)
+++ felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/servlet/ResultHtmlSerializerConfiguration.java Tue Dec 18 22:51:48 2018
@@ -0,0 +1,40 @@
+/*
+ * 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.felix.hc.core.impl.servlet;
+
+import org.osgi.service.metatype.annotations.AttributeDefinition;
+import org.osgi.service.metatype.annotations.ObjectClassDefinition;
+
+@ObjectClassDefinition(name = "Apache Felix Health Check Result HTML Serializer", description = "Serializer for health check results in HTML format")
+@interface ResultHtmlSerializerConfiguration {
+
+ 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"
+ + ".helpText { color:grey; font-size:80%; }\n";
+
+ @AttributeDefinition(name = "CSS Style", description = "CSS Style - can be configured to change the look and feel of the html result page.")
+ String styleString() default CSS_STYLE_DEFAULT;
+
+}
Added: felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/servlet/ResultJsonSerializer.java
URL: http://svn.apache.org/viewvc/felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/servlet/ResultJsonSerializer.java?rev=1849243&view=auto
==============================================================================
--- felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/servlet/ResultJsonSerializer.java (added)
+++ felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/servlet/ResultJsonSerializer.java Tue Dec 18 22:51:48 2018
@@ -0,0 +1,112 @@
+/*
+ * 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.felix.hc.core.impl.servlet;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.text.SimpleDateFormat;
+import java.util.List;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.felix.hc.api.Result;
+import org.apache.felix.hc.api.ResultLog;
+import org.apache.felix.hc.api.execution.HealthCheckExecutionResult;
+import org.apache.felix.utils.json.JSONWriter;
+import org.osgi.service.component.annotations.Component;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/** Serializes health check results into json format. */
+@Component(service = ResultJsonSerializer.class)
+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... ");
+
+ StringWriter writer = new StringWriter();
+ try {
+ JSONWriter jsonWriter = new JSONWriter(writer);
+ jsonWriter.object();
+ jsonWriter.key("overallResult");
+ jsonWriter.value(overallResult.getStatus().toString());
+ jsonWriter.key("results");
+ jsonWriter.array();
+ for (HealthCheckExecutionResult healthCheckResult : executionResults) {
+ writeResult(healthCheckResult, includeDebug, jsonWriter);
+ }
+ jsonWriter.endArray();
+ jsonWriter.endObject();
+ } catch(IOException e) {
+ LOG.error("Could not serialise health check result: e="+e, e);
+ writer.write("{error:'"+e.getMessage()+"'}");
+ }
+ String resultStr = writer.toString();
+
+ if (StringUtils.isNotBlank(jsonpCallback)) {
+ resultStr = jsonpCallback + "(" + resultStr + ");";
+ }
+
+ return resultStr;
+
+ }
+
+ private void writeResult(final HealthCheckExecutionResult healthCheckResult, boolean includeDebug, JSONWriter jsonWriter) throws IOException {
+
+ jsonWriter.object()
+ .key("name").value(healthCheckResult.getHealthCheckMetadata().getName())
+ .key("status").value(healthCheckResult.getHealthCheckResult().getStatus().toString())
+ .key("timeInMs").value(healthCheckResult.getElapsedTimeInMs())
+ .key("finishedAt").value(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS").format(healthCheckResult.getFinishedAt())) ;
+
+ jsonWriter.key("tags").array();
+ for(String tag: healthCheckResult.getHealthCheckMetadata().getTags()) {
+ jsonWriter.value(tag);
+ }
+ jsonWriter.endArray();
+
+ jsonWriter.key("messages").array();
+ for (ResultLog.Entry entry : healthCheckResult.getHealthCheckResult()) {
+ if (!includeDebug && entry.isDebug()) {
+ continue;
+ }
+ jsonWriter.object()
+ .key("status").value(entry.getStatus().toString())
+ .key("message").value(entry.getMessage());
+
+ Exception exception = entry.getException();
+ if (exception != null) {
+ StringWriter stringWriter = new StringWriter();
+ exception.printStackTrace(new PrintWriter(stringWriter));
+ jsonWriter.key("exception").value(stringWriter.toString());
+ }
+ jsonWriter.endObject();
+ }
+ jsonWriter.endArray();
+
+
+ jsonWriter.endObject();
+ }
+
+}
Added: felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/servlet/ResultTxtSerializer.java
URL: http://svn.apache.org/viewvc/felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/servlet/ResultTxtSerializer.java?rev=1849243&view=auto
==============================================================================
--- felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/servlet/ResultTxtSerializer.java (added)
+++ felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/servlet/ResultTxtSerializer.java Tue Dec 18 22:51:48 2018
@@ -0,0 +1,30 @@
+/*
+ * 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.felix.hc.core.impl.servlet;
+
+import org.apache.felix.hc.api.Result;
+import org.osgi.service.component.annotations.Component;
+
+/** Serializes health check results into a simple text message (ideal to be used by a load balancer that would discard further
+ * information). */
+@Component(service = ResultTxtSerializer.class)
+public class ResultTxtSerializer {
+ public String serialize(final Result overallResult) {
+ return overallResult.getStatus().toString();
+ }
+}
Added: felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/servlet/ResultTxtVerboseSerializer.java
URL: http://svn.apache.org/viewvc/felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/servlet/ResultTxtVerboseSerializer.java?rev=1849243&view=auto
==============================================================================
--- felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/servlet/ResultTxtVerboseSerializer.java (added)
+++ felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/servlet/ResultTxtVerboseSerializer.java Tue Dec 18 22:51:48 2018
@@ -0,0 +1,138 @@
+/*
+ * 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.felix.hc.core.impl.servlet;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.List;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.text.WordUtils;
+import org.apache.felix.hc.api.Result;
+import org.apache.felix.hc.api.ResultLog;
+import org.apache.felix.hc.api.execution.HealthCheckExecutionResult;
+import org.apache.felix.hc.util.FormattingResultLog;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/** Serializes health check results into a verbose text message. */
+@Component(service = ResultTxtVerboseSerializer.class)
+public class ResultTxtVerboseSerializer {
+
+ private static final Logger LOG = LoggerFactory.getLogger(ResultTxtVerboseSerializer.class);
+
+ private static final String NEWLINE = "\n"; // not using system prop 'line.separator' as not the local but the calling system is
+ // relevant.
+
+ private int totalWidth;
+
+ private int colWidthName;
+
+ private int colWidthResult;
+
+ private int colWidthTiming;
+
+ private int colWidthWithoutLog;
+ private int colWidthLog;
+
+ @Activate
+ protected final void activate(final ResultTxtVerboseSerializerConfiguration configuration) {
+ this.totalWidth = configuration.totalWidth();
+ this.colWidthName = configuration.colWidthName();
+ this.colWidthResult = configuration.colWidthResult();
+ this.colWidthTiming = configuration.colWidthTiming();
+ colWidthWithoutLog = colWidthName + colWidthResult + colWidthTiming;
+ colWidthLog = totalWidth - colWidthWithoutLog;
+ }
+
+ public String serialize(final Result overallResult, final List<HealthCheckExecutionResult> executionResults, boolean includeDebug) {
+
+ LOG.debug("Sending verbose txt response... ");
+
+ StringBuilder resultStr = new StringBuilder();
+
+ resultStr.append(StringUtils.repeat("-", totalWidth) + NEWLINE);
+ resultStr.append(StringUtils.center("Overall Health Result: " + overallResult.getStatus().toString(), totalWidth) + NEWLINE);
+ resultStr.append(StringUtils.repeat("-", totalWidth) + NEWLINE);
+ resultStr.append(StringUtils.rightPad("Name", colWidthName));
+ resultStr.append(StringUtils.rightPad("Result", colWidthResult));
+ resultStr.append(StringUtils.rightPad("Timing", colWidthTiming));
+ resultStr.append("Logs" + NEWLINE);
+ resultStr.append(StringUtils.repeat("-", totalWidth) + NEWLINE);
+
+ final DateFormat dfShort = new SimpleDateFormat("HH:mm:ss.SSS");
+
+ for (HealthCheckExecutionResult healthCheckResult : executionResults) {
+ appendVerboseTxtForResult(resultStr, healthCheckResult, includeDebug, dfShort);
+ }
+ resultStr.append(StringUtils.repeat("-", totalWidth) + NEWLINE);
+
+ return resultStr.toString();
+
+ }
+
+ private void appendVerboseTxtForResult(StringBuilder resultStr, HealthCheckExecutionResult healthCheckResult, boolean includeDebug,
+ DateFormat dfShort) {
+
+ String wrappedName = WordUtils.wrap(healthCheckResult.getHealthCheckMetadata().getName(), colWidthName);
+
+ String relevantNameStringForPadding = StringUtils.contains(wrappedName, "\n") ? StringUtils.substringAfterLast(wrappedName, "\n")
+ : wrappedName;
+ int paddingSize = colWidthName - relevantNameStringForPadding.length();
+
+ resultStr.append(wrappedName + StringUtils.repeat(" ", paddingSize));
+ resultStr.append(StringUtils.rightPad(healthCheckResult.getHealthCheckResult().getStatus().toString(), colWidthResult));
+ resultStr.append(StringUtils.rightPad("[" + dfShort.format(healthCheckResult.getFinishedAt())
+ + "|" + FormattingResultLog.msHumanReadable(healthCheckResult.getElapsedTimeInMs()) + "]", colWidthTiming));
+
+ boolean isFirst = true;
+ for (ResultLog.Entry logEntry : healthCheckResult.getHealthCheckResult()) {
+ if (!includeDebug && logEntry.isDebug()) {
+ continue;
+ }
+ if (isFirst) {
+ isFirst = false;
+ } else {
+ resultStr.append(StringUtils.repeat(" ", colWidthWithoutLog));
+ }
+
+ String oneLineMessage = getStatusForTxtLog(logEntry) + logEntry.getMessage();
+ String messageToPrint = WordUtils.wrap(oneLineMessage, colWidthLog, "\n" + StringUtils.repeat(" ", colWidthWithoutLog), true);
+
+ resultStr.append(messageToPrint);
+ resultStr.append(NEWLINE);
+ }
+
+ if (isFirst) {
+ // no log entry exists, ensure newline
+ resultStr.append(NEWLINE);
+ }
+
+ }
+
+ private String getStatusForTxtLog(ResultLog.Entry logEntry) {
+ if (logEntry.getStatus() == Result.Status.OK) {
+ return "";
+ } else {
+ return logEntry.getStatus().toString() + " ";
+ }
+ }
+
+}
Added: felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/servlet/ResultTxtVerboseSerializerConfiguration.java
URL: http://svn.apache.org/viewvc/felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/servlet/ResultTxtVerboseSerializerConfiguration.java?rev=1849243&view=auto
==============================================================================
--- felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/servlet/ResultTxtVerboseSerializerConfiguration.java (added)
+++ felix/trunk/core/src/main/java/org/apache/felix/hc/core/impl/servlet/ResultTxtVerboseSerializerConfiguration.java Tue Dec 18 22:51:48 2018
@@ -0,0 +1,38 @@
+/*
+ * 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.felix.hc.core.impl.servlet;
+
+import org.osgi.service.metatype.annotations.AttributeDefinition;
+import org.osgi.service.metatype.annotations.ObjectClassDefinition;
+
+@ObjectClassDefinition(name = "Apache Felix Health Check Verbose Text Serializer", description = "Serializes health check results to a verbose text format")
+@interface ResultTxtVerboseSerializerConfiguration {
+
+ @AttributeDefinition(name = "Total Width", description = "Total width of all columns in verbose txt rendering (in characters)")
+ int totalWidth() default 140;
+
+ @AttributeDefinition(name = "Name Column Width", description = "Column width of health check name (in characters)")
+ int colWidthName() default 30;
+
+ @AttributeDefinition(name = "Result Column Width", description = "Column width of health check result (in characters)")
+ int colWidthResult() default 9;
+
+ @AttributeDefinition(name = "Timing Column Width", description = "Column width of health check timing (in characters)")
+ int colWidthTiming() default 22;
+
+}