This is an automated email from the ASF dual-hosted git repository.

ghenzler pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/felix-dev.git


The following commit(s) were added to refs/heads/master by this push:
     new 37c7c4e  FELIX-6486 Support configuring scripted checks from JCR and 
stop exporting the util package
37c7c4e is described below

commit 37c7c4e4e6ffeea9f177113dae41af00704f1088
Author: georg.henzler <[email protected]>
AuthorDate: Wed Dec 15 19:14:01 2021 +0100

    FELIX-6486 Support configuring scripted checks from JCR and stop
    exporting the util package
---
 healthcheck/README.md                              |   2 +-
 healthcheck/generalchecks/bnd.bnd                  |   2 -
 healthcheck/generalchecks/pom.xml                  |   2 +-
 .../hc/generalchecks/ScriptedHealthCheck.java      | 145 +++++++++++++++++++--
 .../felix/hc/generalchecks/util/ScriptHelper.java  |  20 ++-
 .../felix/hc/generalchecks/util/package-info.java  |  23 ----
 6 files changed, 147 insertions(+), 47 deletions(-)

diff --git a/healthcheck/README.md b/healthcheck/README.md
index b40505b..f1eb927 100644
--- a/healthcheck/README.md
+++ b/healthcheck/README.md
@@ -161,7 +161,7 @@ CPU | org.apache.felix.hc.generalchecks.CpuCheck | no | 
Checks for CPU usage - `
 Thread Usage | org.apache.felix.hc.generalchecks.ThreadUsageCheck | no | 
Checks via `ThreadMXBean.findDeadlockedThreads()` for deadlocks and analyses 
the CPU usage of each thread via a configurable time period (`samplePeriodInMs` 
defaults to 200ms). Uses `cpuPercentageThresholdWarn` (default 95%) to `WARN` 
about high thread utilisation.   
 JMX Attribute Check | org.apache.felix.hc.generalchecks.JmxAttributeCheck | 
yes | Allows to check an arbitrary JMX attribute (using the configured mbean 
`mbean.name`'s attribute `attribute.name`) against a given constraint 
`attribute.value.constraint` (see [Constraints](#constraints)). Can check 
multiple attributes by providing additional config properties with numbers: 
`mbean2.name` (defaults to `mbean.name` if ommitted), `attribute2.name` and 
`attribute2.value.constraint` and `mbean3.n [...]
 Http Requests Check | org.apache.felix.hc.generalchecks.HttpRequestsCheck | 
yes | Allows to check a list of URLs against response code, response headers, 
timing, response content (plain content via RegEx or JSON via path expression). 
See [Request Spec Syntax](#request-spec-syntax)
-Scripted Check | org.apache.felix.hc.generalchecks.ScriptedHealthCheck | yes | 
Allows to run an arbitrary script. To configure use either `script` (to provide 
a script directly) or `scriptUrl` (to link to an external script, usually a 
file URL). Use `language` property to refer to a registered script engine (e.g. 
install bundle `groovy-all` to be able to use language `groovy`)
+Scripted Check | org.apache.felix.hc.generalchecks.ScriptedHealthCheck | yes | 
Allows to run an arbitrary script. To configure use either `script` to provide 
a script directly or `scriptUrl` to link to an external script (may be a file 
URL or a link to a JCR file if a Sling Repository exists, e.g. 
`jcr:/etc/hc/check1.groovy`). Use the `language` property to refer to a 
registered script engine (e.g. install bundle `groovy-all` to be able to use 
language `groovy`). The script has the bindi [...]
 
 ### Constraints
 
diff --git a/healthcheck/generalchecks/bnd.bnd 
b/healthcheck/generalchecks/bnd.bnd
index e42dd06..e120abc 100644
--- a/healthcheck/generalchecks/bnd.bnd
+++ b/healthcheck/generalchecks/bnd.bnd
@@ -10,6 +10,4 @@ Bundle-Vendor: The Apache Software Foundation
 
 Conditional-Package: 
org.apache.commons.cli.*,org.apache.felix.utils.*,org.apache.felix.hc.core.impl.util.lang.*
 
-Export-Package: org.apache.felix.hc.generalchecks.util
-
 Import-Package: org.apache.felix.rootcause*;resolution:="optional", *
\ No newline at end of file
diff --git a/healthcheck/generalchecks/pom.xml 
b/healthcheck/generalchecks/pom.xml
index 6102034..7186da9 100644
--- a/healthcheck/generalchecks/pom.xml
+++ b/healthcheck/generalchecks/pom.xml
@@ -28,7 +28,7 @@
     </parent>
 
     <artifactId>org.apache.felix.healthcheck.generalchecks</artifactId>
-    <version>2.0.15-SNAPSHOT</version>
+    <version>3.0.0-SNAPSHOT</version>
 
     <name>Apache Felix Health Check General Checks</name>
     <inceptionYear>2013</inceptionYear>
diff --git 
a/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/ScriptedHealthCheck.java
 
b/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/ScriptedHealthCheck.java
index 73f0868..36a203f 100644
--- 
a/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/ScriptedHealthCheck.java
+++ 
b/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/ScriptedHealthCheck.java
@@ -17,6 +17,15 @@
  */
 package org.apache.felix.hc.generalchecks;
 
+import java.io.BufferedReader;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.lang.reflect.InvocationTargetException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.stream.Collectors;
+
 import javax.script.ScriptEngine;
 
 import org.apache.felix.hc.api.FormattingResultLog;
@@ -26,6 +35,7 @@ import org.apache.felix.hc.core.impl.util.lang.StringUtils;
 import org.apache.felix.hc.generalchecks.util.ScriptEnginesTracker;
 import org.apache.felix.hc.generalchecks.util.ScriptHelper;
 import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
 import org.osgi.service.component.annotations.Activate;
 import org.osgi.service.component.annotations.Component;
 import org.osgi.service.component.annotations.ConfigurationPolicy;
@@ -45,6 +55,7 @@ public class ScriptedHealthCheck implements HealthCheck {
     private static final Logger LOG = 
LoggerFactory.getLogger(ScriptedHealthCheck.class);
 
     public static final String HC_LABEL = "Health Check: Script";
+    public static final String JCR_FILE_URL_PREFIX = "jcr:";
 
     @ObjectClassDefinition(name = HC_LABEL, description = "Runs an arbitrary 
script in given scriping language (via javax.script). "
             + "The script has the following default bindings available: 'log', 
'scriptHelper' and 'bundleContext'. "
@@ -52,6 +63,8 @@ public class ScriptedHealthCheck implements HealthCheck {
             + "'scriptHelper.getService(classObj)' can be used as shortcut to 
retrieve a service."
             + "'scriptHelper.getServices(classObj, filter)' used to retrieve 
multiple services for a class using given filter. "
             + "For all services retrieved via scriptHelper, unget() is called 
automatically at the end of the script execution."
+            + "If a Sling repository is available, the bindings 
'resourceResolver' and 'session' are available automatically ("
+            + "for this case a serivce user mapping for 
'org.apache.felix.healthcheck.generalchecks:scripted' is required). "
             + "'bundleContext' is available for advanced use cases. The script 
does not need to return any value, but if it does and it is "
             + "a org.apache.felix.hc.api.Result, that result and entries in 
'log' are combined then).")
     @interface Config {
@@ -68,7 +81,7 @@ public class ScriptedHealthCheck implements HealthCheck {
         @AttributeDefinition(name = "Script", description = "The script itself 
(either use 'script' or 'scriptUrl').")
         String script() default "log.info('ok'); log.warn('not so good'); 
log.critical('bad') // minimal example";
 
-        @AttributeDefinition(name = "Script Url", description = "Url to the 
script to be used as alternative source (either use 'script' or 'scriptUrl').")
+        @AttributeDefinition(name = "Script Url", description = "Url to the 
script to be used as alternative source (either use 'script' or 'scriptUrl'). 
Supported schemes are file: and jcr: (if a JCR repository is available)")
         String scriptUrl() default "";
 
         @AttributeDefinition
@@ -106,14 +119,27 @@ public class ScriptedHealthCheck implements HealthCheck {
     public Result execute() {
         FormattingResultLog log = new FormattingResultLog();
 
-
-        boolean urlIsUsed = StringUtils.isBlank(script);
-        String scriptToExecute = urlIsUsed ? 
scriptHelper.getFileContents(scriptUrl): script;
-        log.info("Executing script {} ({} lines)...", (urlIsUsed?scriptUrl:" 
as configured"), scriptToExecute.split("\n").length);
-
-        try {
+        try(OptionalSlingContext optionalSlingContext = new 
OptionalSlingContext(bundleContext)) {
+
+            boolean urlIsUsed = StringUtils.isBlank(script);
+            String scriptToExecute;
+            if (urlIsUsed) {
+                if (scriptUrl.startsWith(JCR_FILE_URL_PREFIX)) {
+                    String jcrPath = 
scriptUrl.substring(JCR_FILE_URL_PREFIX.length());
+                    scriptToExecute = 
optionalSlingContext.getScriptFromRepository(jcrPath);
+                } else {
+                    scriptToExecute = scriptHelper.getFileContents(scriptUrl);
+                }
+            } else {
+                scriptToExecute = script;
+            }
+
+            log.info("Executing script {} ({} lines)...", 
(urlIsUsed?scriptUrl:" as configured"), scriptToExecute.split("\n").length);
+            
             ScriptEngine scriptEngine = 
scriptHelper.getScriptEngine(scriptEnginesTracker, language);
-            scriptHelper.evalScript(bundleContext, scriptEngine, 
scriptToExecute, log, null, true);
+            scriptHelper.evalScript(bundleContext, scriptEngine, 
scriptToExecute, log, optionalSlingContext.getAdditionalBindings(), true);
+        } catch(IllegalStateException e) {
+            log.temporarilyUnavailable(e.getMessage()); // e.g. due to missing 
service during startup
         }  catch (Exception e) {
             log.healthCheckError("Exception while executing script: "+e, e);
         }
@@ -121,5 +147,108 @@ public class ScriptedHealthCheck implements HealthCheck {
         return new Result(log);
     }
 
+    // Provides an optional Sling context to use jcr: urls and to provide the 
bindings resourceResolver and session
+    // Using reflection to ensure this bundle can start at an early start 
level (e.g. 5) and the Sling bundles can start at a later start level (e.g. 20)
+    private static class OptionalSlingContext implements AutoCloseable {
+
+        private static final String JCR_CONTENT_SUFFIX = "/jcr:content";
+
+        private static final String BINDING_KEY_RESOURCE_RESOLVER = 
"resourceResolver";
+        private static final String BINDING_KEY_SESSION = "session";
+
+        private static final String CLASS_SESSION = "javax.jcr.Session";
+        private static final String CLASS_LOGIN_EXCEPTION = 
"org.apache.sling.api.resource.LoginException";
+        private static final String CLASS_RESOURCE_RESOLVER_FACTORY = 
"org.apache.sling.api.resource.ResourceResolverFactory";
+        private static final String METHOD_GET_RESOURCE = "getResource";
+        private static final String METHOD_GET_SERVICE_RESOURCE_RESOLVER = 
"getServiceResourceResolver";
+        private static final String METHOD_CLOSE = "close";
+        private static final String CLASS_ADAPTABLE = 
"org.apache.sling.api.adapter.Adaptable";
+        private static final String METHOD_ADAPT_TO = "adaptTo";
+
+        // ResourceResolverFactory.SUBSERVICE, but as copy here to not 
introduce dependency on maven level
+        private static final String SUBSERVICE = "sling.service.subservice";
+        private static final String SUBSERVICE_NAME = "scripted";
+        
+        private BundleContext bundleContext;
+        private ServiceReference<?> resourceResolverFactoryReference;
+        private Object resourceResolver;
+
+        private boolean serviceUserMappingMissing = false;
+
+        OptionalSlingContext(BundleContext bundleContext) {
+            this.bundleContext = bundleContext;
+
+            resourceResolverFactoryReference = 
bundleContext.getServiceReference(CLASS_RESOURCE_RESOLVER_FACTORY);
+
+            if (resourceResolverFactoryReference != null) {
+                try {
+                    Object resourceResolverFactory = 
bundleContext.getService(resourceResolverFactoryReference);
+                    resourceResolver = resourceResolverFactory.getClass()
+                            .getMethod(METHOD_GET_SERVICE_RESOURCE_RESOLVER, 
Map.class)
+                            .invoke(resourceResolverFactory, 
Collections.singletonMap(SUBSERVICE, SUBSERVICE_NAME));
+                } catch (Exception e) {
+                    if (e instanceof InvocationTargetException && 
((InvocationTargetException) e).getTargetException()
+                            
.getClass().getName().equals(CLASS_LOGIN_EXCEPTION)) {
+                        serviceUserMappingMissing = true;
+                    } else {
+                        LOG.warn("Could not get resourceResolver via 
reflection: " + e, e);
+                    }
+                }
+            }
+        }
+
+        String getScriptFromRepository(String jcrPath) {
+
+            if (resourceResolver == null) {
+                throw new IllegalStateException("Script URL with scheme " + 
JCR_FILE_URL_PREFIX + jcrPath
+                        + (serviceUserMappingMissing
+                                ? " require a service user mapping for bundle "
+                                        + 
bundleContext.getBundle().getSymbolicName() + ":" + SUBSERVICE_NAME
+                                : " cannot be used as resource resolver 
factory is not available"));
+            }
+
+            try {
+                Object jcrFileResource = 
resourceResolver.getClass().getMethod(METHOD_GET_RESOURCE, String.class)
+                        .invoke(resourceResolver, jcrPath + 
JCR_CONTENT_SUFFIX);
+                if (jcrFileResource == null) {
+                    throw new IllegalStateException("JCR Path " + jcrPath + " 
does not exist");
+                }
+                InputStream jcrFileInputStream = (InputStream) 
Class.forName(CLASS_ADAPTABLE).getMethod(METHOD_ADAPT_TO, Class.class)
+                        .invoke(jcrFileResource, InputStream.class);
+                return new BufferedReader(new 
InputStreamReader(jcrFileInputStream)).lines().collect(Collectors.joining("\n"));
+            } catch (IllegalStateException e) {
+                throw e;
+            } catch (Exception e) {
+                throw new IllegalStateException("Could not load script from 
path " + jcrPath + ": " + e, e);
+            }
+        }
+
+        Map<String, Object> getAdditionalBindings() {
+            if (resourceResolver != null) {
+                Map<String, Object> additionalBindings = new HashMap<>();
+                additionalBindings.put(BINDING_KEY_RESOURCE_RESOLVER, 
resourceResolver);
+                try {
+                    Object session = 
resourceResolver.getClass().getMethod(METHOD_ADAPT_TO, Class.class)
+                            .invoke(resourceResolver, 
Class.forName(CLASS_SESSION));
+                    additionalBindings.put(BINDING_KEY_SESSION, session);
+                } catch (Exception e) {
+                    throw new IllegalStateException("Could not add jcr session 
to bindings " + e, e);
+                }
+                return additionalBindings;
+            } else {
+                return null;
+            }
+        }
+
+        public void close() throws Exception {
+            if (resourceResolver != null) {
+                
resourceResolver.getClass().getMethod(METHOD_CLOSE).invoke(resourceResolver);
+            }
+            if (resourceResolverFactoryReference != null) {
+                bundleContext.ungetService(resourceResolverFactoryReference);
+            }
+        }
+
+    }
 
 }
diff --git 
a/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/util/ScriptHelper.java
 
b/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/util/ScriptHelper.java
index fc93558..393a9f3 100644
--- 
a/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/util/ScriptHelper.java
+++ 
b/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/util/ScriptHelper.java
@@ -17,14 +17,13 @@
  */
 package org.apache.felix.hc.generalchecks.util;
 
-import java.io.BufferedReader;
 import java.io.IOException;
-import java.io.InputStreamReader;
 import java.io.StringWriter;
 import java.lang.reflect.Array;
-import java.net.URL;
-import java.net.URLConnection;
-import java.nio.charset.StandardCharsets;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Iterator;
@@ -53,16 +52,13 @@ public class ScriptHelper {
     public String getFileContents(String url) {
         String content;
         try {
-            URLConnection conn = new URL(url).openConnection();
-            try (BufferedReader reader = new BufferedReader(new 
InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8))) {
-                content = reader.lines().collect(Collectors.joining("\n"));
-            }
+            content = Files.readAllLines(Paths.get(new 
URI(url))).stream().collect(Collectors.joining("\n"));
             return content;
-        }catch(IOException e) {
-            throw new IllegalArgumentException("Could not read URL "+url+": 
"+e, e);
+        }catch(IOException | URISyntaxException e) {
+            throw new IllegalArgumentException("Could not read file URL 
"+url+": "+e, e);
         }
     }
-    
+
     public ScriptEngine getScriptEngine(ScriptEnginesTracker 
scriptEnginesTracker, String language) {
         ScriptEngine scriptEngine = 
scriptEnginesTracker.getEngineByLanguage(language);
         if(scriptEngine == null) {
diff --git 
a/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/util/package-info.java
 
b/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/util/package-info.java
deleted file mode 100644
index 07879ea..0000000
--- 
a/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/util/package-info.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-@Version("2.0.0")
-package org.apache.felix.hc.generalchecks.util;
-
-import org.osgi.annotation.versioning.Version;

Reply via email to