Author: ghenzler
Date: Wed Jan  9 20:29:37 2019
New Revision: 1850906

URL: http://svn.apache.org/viewvc?rev=1850906&view=rev
Log:
FELIX-6017 New AdhocResultDuringRequestProcessingFilter (e.g. useful for 
registering an adhoc HC during a deployment)

Added:
    
felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/filter/
    
felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/filter/AdhocResultDuringRequestProcessingFilter.java
   (with props)

Added: 
felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/filter/AdhocResultDuringRequestProcessingFilter.java
URL: 
http://svn.apache.org/viewvc/felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/filter/AdhocResultDuringRequestProcessingFilter.java?rev=1850906&view=auto
==============================================================================
--- 
felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/filter/AdhocResultDuringRequestProcessingFilter.java
 (added)
+++ 
felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/filter/AdhocResultDuringRequestProcessingFilter.java
 Wed Jan  9 20:29:37 2019
@@ -0,0 +1,264 @@
+/*
+ * 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.filter;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.regex.Pattern;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+
+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.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.apache.felix.hc.core.impl.executor.CombinedExecutionResult;
+import org.apache.felix.hc.core.impl.servlet.ResultTxtVerboseSerializer;
+import org.apache.felix.hc.core.impl.util.AdhocStatusHealthCheck;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceRegistration;
+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.Reference;
+import org.osgi.service.http.whiteboard.HttpWhiteboardConstants;
+import org.osgi.service.metatype.annotations.AttributeDefinition;
+import org.osgi.service.metatype.annotations.Designate;
+import org.osgi.service.metatype.annotations.ObjectClassDefinition;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/** Dynamically adds a HC result for configured requests. */
+@Component(configurationPolicy = ConfigurationPolicy.REQUIRE)
+@Designate(ocd = AdhocResultDuringRequestProcessingFilter.Config.class, 
factory = true)
+public class AdhocResultDuringRequestProcessingFilter implements Filter {
+    private static final Logger LOG = 
LoggerFactory.getLogger(AdhocResultDuringRequestProcessingFilter.class);
+
+
+    @ObjectClassDefinition(name = "Health Check Adhoc Result during Request 
Processing", description = "Registers an health check with an adhoc result 
during request processing")
+    public @interface Config {
+
+        @AttributeDefinition(name = "Filter Request Path RegEx", description = 
"Regex to be matched against request path")
+        String osgi_http_whiteboard_filter_regex();
+        
+        @AttributeDefinition(name = "Filter Context", description = "Needs to 
be set to correct whiteboard context filter (e.g. 
'(osgi.http.whiteboard.context.name=default)'")
+        String osgi_http_whiteboard_context_select() default "(" + 
HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_NAME + "=*)";
+
+        @AttributeDefinition(name = "Request Method", description = "Relevant 
request method (leave empty to not restrict to a method)")
+        String method() default "";
+
+        @AttributeDefinition(name = "User Agent RegEx", description = 
"Relevant user agent header (leave emtpy to not restrict to a user agent)")
+        String userAgentRegEx() default "";
+        
+        @AttributeDefinition(name = "Health Check Name", description = "Name 
of health check during request processing")
+        String hcName() default "Ongoing request";
+        
+        @AttributeDefinition(name = "Tags to register", description = "List of 
tags the adhoc result shall be registered for (tags are not active during 
configured delay in case 'delayProcessingInSec' is configured)")
+        String[] tags() default {};
+
+        @AttributeDefinition(name = "Status during request processing", 
description = "Status to be sent during request processing")
+        Result.Status statusDuringRequestProcessing() default 
Result.Status.TEMPORARILY_UNAVAILABLE;
+
+        @AttributeDefinition(name = "Delay before request processing", 
description = "Time to delay processing of request in sec (the default 0 turns 
the delay off). Use together with 'tagsDuringDelayedProcessing' advertise 
request processing before actual action (e.g. to signal a deployment request to 
a regularly querying load balancer before deployment starts)")
+        long delayProcessingInSec() default 0;
+
+        @AttributeDefinition(name = "Tags to register during delay before 
processing", description = "List of tags the adhoc result is be registered also 
during waiting for the configured delay")
+        String[] tagsDuringDelayedProcessing() default {};
+
+        @AttributeDefinition(name = "Tags to wait for after processing", 
description = "List of tags to be waited for after processing (leave empty to 
not wait). While waiting the tags from property 'tags' remain in configured 
state.")
+        String[] tagsToWaitForAfterProcessing() default {};
+
+        @AttributeDefinition(name = "Maximum waiting time", description = 
"Maximum waiting time for 'tagsToWaitForAfterProcessing' after actual request 
has been processed")
+        long maxWaitForTagsAfterProcessingInSec() default 120;
+
+        @AttributeDefinition
+        String webconsole_configurationFactory_nameHint() default "{hc.name} 
({osgi.http.whiteboard.filter.regex} {method} {userAgentRegEx}) -> 
{statusDuringRequestProcessing} for tags {tags} {tagsDuringDelayedProcessing}";
+    }
+    
+    private BundleContext bundleContext;
+    
+    private Result.Status statusDuringRequestProcessing;
+    private String hcNameDuringRequestProcessing;
+    private String[] hcTagsDuringRequestProcessing;
+
+    private Long delayProcessingInSec;
+    private String[] tagsDuringDelayedProcessing;
+
+    private String[] tagsToWaitForAfterProcessing;
+    private long maxWaitForTagsAfterProcessingInSec;
+    
+    private String requiredMethod;
+    private Pattern userAgentRegEx;
+    
+    
+    @Reference 
+    private HealthCheckExecutor executor;
+    
+    @Reference
+    ResultTxtVerboseSerializer verboseTxtSerializer;
+    
+    @Activate
+    protected final void activate(ComponentContext context, Config config) {
+        this.bundleContext = context.getBundleContext();
+        this.statusDuringRequestProcessing = 
config.statusDuringRequestProcessing();
+        this.hcNameDuringRequestProcessing = config.hcName();
+        this.hcTagsDuringRequestProcessing = config.tags();
+        
+        this.delayProcessingInSec = config.delayProcessingInSec() > 0 ? 
config.delayProcessingInSec(): null;
+        this.tagsDuringDelayedProcessing = 
config.tagsDuringDelayedProcessing();
+        
+        this.tagsToWaitForAfterProcessing = 
config.tagsToWaitForAfterProcessing();
+        this.maxWaitForTagsAfterProcessingInSec = 
config.maxWaitForTagsAfterProcessingInSec();
+        
+        this.requiredMethod = StringUtils.defaultIfBlank(config.method(), 
null);
+        this.userAgentRegEx = StringUtils.isNotBlank(config.userAgentRegEx()) 
? Pattern.compile(config.userAgentRegEx()) : null;
+    }
+    
+    @Override
+    public void doFilter(ServletRequest request, ServletResponse response, 
FilterChain filterChain) throws IOException, ServletException {
+
+        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
+        String requestPath = httpServletRequest.getRequestURI();
+        String userAgent = httpServletRequest.getHeader("User-Agent");
+        String method = httpServletRequest.getMethod();
+        boolean isRelevantRequest = /* path is checked by filter pattern */
+                (requiredMethod==null || requiredMethod.equals(method))
+                && (userAgentRegEx==null || 
userAgentRegEx.matcher(userAgent).matches());
+        if (isRelevantRequest) {
+            processRelevantRequest(request, response, filterChain, 
requestPath);
+            
+        } else {
+            // regular request processing
+            filterChain.doFilter(request, response);
+        }
+    }
+
+    private void processRelevantRequest(ServletRequest request, 
ServletResponse response, FilterChain filterChain, String requestPath)
+            throws IOException, ServletException {
+        
+        AdhocStatusHealthCheck adhocStatusHealthCheck = null;
+        try {
+            
+            String mainHcMsg = "Request "+requestPath+" is being processed";
+            if(delayProcessingInSec!=null) {
+                
+                String hcNameDuringDelay = hcNameDuringRequestProcessing +" 
(waiting)";
+                AdhocStatusHealthCheck adhocStatusHealthCheckDelayedProcessing 
= null;
+                try {
+                    adhocStatusHealthCheckDelayedProcessing = 
registerDynamicHealthCheck(statusDuringRequestProcessing,tagsDuringDelayedProcessing,
 hcNameDuringDelay, "Waiting "+delayProcessingInSec+"sec until continuing 
request "+requestPath);
+                    
+                    LOG.info("Delaying processing of request {} for {}sec", 
requestPath, delayProcessingInSec);
+                    try {
+                        Thread.sleep(delayProcessingInSec * 1000);
+                    } catch (InterruptedException e) {
+                        LOG.warn("Exception during delaying processing of 
request {} for {}sec", requestPath, delayProcessingInSec, e);
+                    }
+                } finally {
+                    // for the case delayProcessingInSec is set, register 
regular HC first and then unregister delay HC to ensure there is not even a 
short time span without result
+                    adhocStatusHealthCheck = 
registerDynamicHealthCheck(statusDuringRequestProcessing, 
hcTagsDuringRequestProcessing, hcNameDuringRequestProcessing, mainHcMsg);
+                    
unregisterDynamicHealthCheck(adhocStatusHealthCheckDelayedProcessing);
+                }
+            } else {
+                adhocStatusHealthCheck = 
registerDynamicHealthCheck(statusDuringRequestProcessing, 
hcTagsDuringRequestProcessing, hcNameDuringRequestProcessing, mainHcMsg);
+            }
+            
+            filterChain.doFilter(request, response);
+            LOG.info("Request {} is processed", requestPath);
+            
+            if(tagsToWaitForAfterProcessing.length > 0) {
+                
+                long startTime = System.currentTimeMillis();
+                for(;;) {
+                    List<HealthCheckExecutionResult> executionResults = 
executor.execute(HealthCheckSelector.tags(tagsToWaitForAfterProcessing).withNames("-"+hcNameDuringRequestProcessing),
 new 
HealthCheckExecutionOptions().setCombineTagsWithOr(true).setForceInstantExecution(true));
+                    CombinedExecutionResult combinedExecutionResult = new 
CombinedExecutionResult(executionResults);
+                    Result overallResult = 
combinedExecutionResult.getHealthCheckResult();
+                    String verboseTxtResult = 
verboseTxtSerializer.serialize(overallResult, executionResults, false);
+
+                    String msg = "Waiting for tags 
"+Arrays.asList(tagsToWaitForAfterProcessing)+": "+ overallResult.getStatus();
+                    LOG.info(msg);
+                    if(LOG.isDebugEnabled()) {
+                        LOG.debug("\n"+verboseTxtResult);
+                    }
+                    adhocStatusHealthCheck.updateMessage(msg);
+                    if(overallResult.isOk()) {
+                        break;
+                    }
+                    if((System.currentTimeMillis() - startTime) > 
(maxWaitForTagsAfterProcessingInSec*1000)) {
+                        LOG.warn("Maximum wait time {}sec for tags {} exceeded 
- continuing anyway", maxWaitForTagsAfterProcessingInSec, 
Arrays.asList(tagsToWaitForAfterProcessing));
+                        throw new ServletException("Maximum wait time 
"+maxWaitForTagsAfterProcessingInSec+"sec for tags 
"+Arrays.asList(tagsToWaitForAfterProcessing)+" exceeded:\n"+verboseTxtResult);
+                    }
+                    
+                    LOG.info("Waiting for tags {} before returning from {}", 
tagsToWaitForAfterProcessing, requestPath);
+                    try {
+                        Thread.sleep(500);
+                    } catch (InterruptedException e) {
+                        LOG.warn("Exception during delaying processing of 
request {} for {}sec", requestPath, delayProcessingInSec, e);
+                    }
+                }
+            }
+            
+        } finally {
+            unregisterDynamicHealthCheck(adhocStatusHealthCheck);
+        }
+    }
+
+    @Override
+    public void init(FilterConfig filterConfig) throws ServletException {
+        // no action required
+    }
+
+    @Override
+    public void destroy() {
+        // no action required
+    }
+
+    private AdhocStatusHealthCheck registerDynamicHealthCheck(Result.Status 
status, String[] tags, String hcName, String msg) {
+        AdhocStatusHealthCheck healthCheck = new 
AdhocStatusHealthCheck(status, msg);
+        Dictionary<String, Object> props = new Hashtable<String, Object>();
+        props.put(HealthCheck.NAME, hcName);
+        props.put(HealthCheck.TAGS, tags);
+
+        ServiceRegistration<HealthCheck> registration = 
bundleContext.registerService(HealthCheck.class, healthCheck, props);
+        healthCheck.setServiceRegistration(registration);
+
+        return healthCheck;
+    }
+
+    private synchronized void 
unregisterDynamicHealthCheck(AdhocStatusHealthCheck healthCheck) {
+        ServiceRegistration<HealthCheck> serviceRegistration = healthCheck 
!=null ? healthCheck.getServiceRegistration() : null;
+        if (serviceRegistration != null) {
+            serviceRegistration.unregister();
+            LOG.debug("Unregistered adhoc HC");
+        }
+    }
+
+
+}

Propchange: 
felix/trunk/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/filter/AdhocResultDuringRequestProcessingFilter.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain


Reply via email to