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;