You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@felix.apache.org by cz...@apache.org on 2022/06/28 03:58:43 UTC
[felix-dev] branch master updated: FELIX-6542 ScriptedHealthCheck should handle ScriptEngineFactory defined in a fragment bundle (#158)
This is an automated email from the ASF dual-hosted git repository.
cziegeler 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 4eeb6b449f FELIX-6542 ScriptedHealthCheck should handle ScriptEngineFactory defined in a fragment bundle (#158)
4eeb6b449f is described below
commit 4eeb6b449f83c38b5156b6419e4bef66783d9f92
Author: Eric Norman <en...@apache.org>
AuthorDate: Mon Jun 27 20:58:38 2022 -0700
FELIX-6542 ScriptedHealthCheck should handle ScriptEngineFactory defined in a fragment bundle (#158)
* FELIX-6542 handle ScriptEngineFactory defined in a fragment bundle
* FELIX-6542 fix spelling
* FELIX-6542 updated language description
groovy-all bundle doesn't exist after version 2.5 so change the text to
groovy-jsr223
---
.../hc/generalchecks/ScriptedHealthCheck.java | 4 +-
.../generalchecks/util/ScriptEnginesTracker.java | 52 ++++----
.../util/DummyScriptEngineFactory.java | 147 +++++++++++++++++++++
.../util/ScriptEnginesTrackerTest.java | 96 ++++++++++++++
4 files changed, 273 insertions(+), 26 deletions(-)
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 36a203f2cb..3d3e2de7ac 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
@@ -57,7 +57,7 @@ public class ScriptedHealthCheck implements HealthCheck {
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). "
+ @ObjectClassDefinition(name = HC_LABEL, description = "Runs an arbitrary script in given scripting language (via javax.script). "
+ "The script has the following default bindings available: 'log', 'scriptHelper' and 'bundleContext'. "
+ "'log' is an instance of org.apache.felix.hc.api.FormattingResultLog and is used to define the result of the HC. "
+ "'scriptHelper.getService(classObj)' can be used as shortcut to retrieve a service."
@@ -75,7 +75,7 @@ public class ScriptedHealthCheck implements HealthCheck {
@AttributeDefinition(name = "Tags", description = "List of tags for this health check, used to select subsets of health checks for execution e.g. by a composite health check.")
String[] hc_tags() default {};
- @AttributeDefinition(name = "Language", description = "The language the script is written in. To use e.g. 'groovy', ensure osgi bundle 'groovy-all' is available.")
+ @AttributeDefinition(name = "Language", description = "The language the script is written in. To use e.g. 'groovy', ensure osgi bundle 'groovy-jsr223' is available.")
String language() default "groovy";
@AttributeDefinition(name = "Script", description = "The script itself (either use 'script' or 'scriptUrl').")
diff --git a/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/util/ScriptEnginesTracker.java b/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/util/ScriptEnginesTracker.java
index 3ebd967301..aea2f4b0c1 100644
--- a/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/util/ScriptEnginesTracker.java
+++ b/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/util/ScriptEnginesTracker.java
@@ -23,6 +23,7 @@ import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.ArrayList;
+import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@@ -45,7 +46,9 @@ import org.slf4j.LoggerFactory;
public class ScriptEnginesTracker implements BundleListener {
private static final Logger LOG = LoggerFactory.getLogger(ScriptEnginesTracker.class);
- private static final String ENGINE_FACTORY_SERVICE = "META-INF/services/" + ScriptEngineFactory.class.getName();
+ static final String META_INF_SERVICES = "META-INF/services";
+ static final String FACTORY_NAME = ScriptEngineFactory.class.getName();
+ private static final String ENGINE_FACTORY_SERVICE = String.format("%s/%s", META_INF_SERVICES, FACTORY_NAME);
private final Map<String, ScriptEngineFactory> enginesByLanguage = new ConcurrentHashMap<String, ScriptEngineFactory>();
private final Map<Bundle, List<String>> languagesByBundle = new ConcurrentHashMap<Bundle, List<String>>();
@@ -83,7 +86,7 @@ public class ScriptEnginesTracker implements BundleListener {
public void bundleChanged(BundleEvent event) {
- if (event.getType() == BundleEvent.STARTED && event.getBundle().getEntry(ENGINE_FACTORY_SERVICE) != null) {
+ if (event.getType() == BundleEvent.STARTED && event.getBundle().findEntries(META_INF_SERVICES, FACTORY_NAME, false) != null) {
registerFactories(event.getBundle());
} else if (event.getType() == BundleEvent.STOPPED) {
unregisterFactories(event.getBundle());
@@ -94,7 +97,7 @@ public class ScriptEnginesTracker implements BundleListener {
private void registerInitialScriptEngineFactories() {
Bundle[] bundles = this.context.getBundles();
for (Bundle bundle : bundles) {
- if ( bundle.getState() == Bundle.ACTIVE && bundle.getEntry(ENGINE_FACTORY_SERVICE)!=null) {
+ if ( bundle.getState() == Bundle.ACTIVE && bundle.findEntries(META_INF_SERVICES, FACTORY_NAME, false) != null) {
registerFactories(bundle);
}
}
@@ -109,7 +112,7 @@ public class ScriptEnginesTracker implements BundleListener {
}
private void unregisterFactories(Bundle bundle) {
- List<String> languagesForBundle = languagesByBundle.get(bundle);
+ List<String> languagesForBundle = languagesByBundle.remove(bundle);
if(languagesForBundle != null) {
for (String lang : languagesForBundle) {
ScriptEngineFactory removed = enginesByLanguage.remove(lang);
@@ -120,29 +123,30 @@ public class ScriptEnginesTracker implements BundleListener {
@SuppressWarnings("unchecked")
private List<ScriptEngineFactory> getScriptEngineFactoriesForBundle(final Bundle bundle) {
- URL url = bundle.getEntry(ENGINE_FACTORY_SERVICE);
- InputStream ins = null;
-
List<ScriptEngineFactory> scriptEngineFactoriesInBundle = new ArrayList<ScriptEngineFactory>();
-
- try {
- ins = url.openStream();
- BufferedReader reader = new BufferedReader(new InputStreamReader(ins));
- for (String className : getClassNames(reader)) {
- try {
- Class<ScriptEngineFactory> clazz = (Class<ScriptEngineFactory>) bundle.loadClass(className);
- ScriptEngineFactory spi = clazz.newInstance();
- scriptEngineFactoriesInBundle.add(spi);
-
- } catch (Throwable t) {
- LOG.error("Cannot register ScriptEngineFactory {}", className, t);
+ Enumeration<URL> foundEntries = bundle.findEntries(META_INF_SERVICES, FACTORY_NAME, false);
+ while (foundEntries != null && foundEntries.hasMoreElements()) {
+ URL url = foundEntries.nextElement();
+ InputStream ins = null;
+ try {
+ ins = url.openStream();
+ BufferedReader reader = new BufferedReader(new InputStreamReader(ins));
+ for (String className : getClassNames(reader)) {
+ try {
+ Class<ScriptEngineFactory> clazz = (Class<ScriptEngineFactory>) bundle.loadClass(className);
+ ScriptEngineFactory spi = clazz.getDeclaredConstructor().newInstance();
+ scriptEngineFactoriesInBundle.add(spi);
+
+ } catch (Throwable t) {
+ LOG.error("Cannot register ScriptEngineFactory {}", className, t);
+ }
}
- }
- } catch (IOException ioe) {
- LOG.warn("Exception while trying to load factories as defined in {}", ENGINE_FACTORY_SERVICE, ioe);
- } finally {
- closeQuietly(ins);
+ } catch (IOException ioe) {
+ LOG.warn("Exception while trying to load factories as defined in {}", ENGINE_FACTORY_SERVICE, ioe);
+ } finally {
+ closeQuietly(ins);
+ }
}
return scriptEngineFactoriesInBundle;
diff --git a/healthcheck/generalchecks/src/test/java/org/apache/felix/hc/generalchecks/util/DummyScriptEngineFactory.java b/healthcheck/generalchecks/src/test/java/org/apache/felix/hc/generalchecks/util/DummyScriptEngineFactory.java
new file mode 100644
index 0000000000..f04c5555bc
--- /dev/null
+++ b/healthcheck/generalchecks/src/test/java/org/apache/felix/hc/generalchecks/util/DummyScriptEngineFactory.java
@@ -0,0 +1,147 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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.
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
+package org.apache.felix.hc.generalchecks.util;
+
+import java.io.Reader;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import javax.script.Bindings;
+import javax.script.ScriptContext;
+import javax.script.ScriptEngine;
+import javax.script.ScriptEngineFactory;
+import javax.script.ScriptException;
+import javax.script.SimpleBindings;
+
+/**
+ * A dummy script engine factory.
+ *
+ */
+public class DummyScriptEngineFactory implements ScriptEngineFactory {
+
+ class DummyScriptEngine implements ScriptEngine {
+
+ public Bindings createBindings() {
+ return new SimpleBindings();
+ }
+
+ public Object eval(String arg0) throws ScriptException {
+ throw new UnsupportedOperationException();
+ }
+
+ public Object eval(Reader arg0) throws ScriptException {
+ throw new UnsupportedOperationException();
+ }
+
+ public Object eval(String arg0, ScriptContext arg1) throws ScriptException {
+ throw new UnsupportedOperationException();
+ }
+
+ public Object eval(Reader arg0, ScriptContext arg1) throws ScriptException {
+ throw new UnsupportedOperationException();
+ }
+
+ public Object eval(String arg0, Bindings arg1) throws ScriptException {
+ throw new UnsupportedOperationException();
+ }
+
+ public Object eval(Reader arg0, Bindings arg1) throws ScriptException {
+ throw new UnsupportedOperationException();
+ }
+
+ public Object get(String arg0) {
+ throw new UnsupportedOperationException();
+ }
+
+ public Bindings getBindings(int arg0) {
+ throw new UnsupportedOperationException();
+ }
+
+ public ScriptContext getContext() {
+ throw new UnsupportedOperationException();
+ }
+
+ public ScriptEngineFactory getFactory() {
+ return DummyScriptEngineFactory.this;
+ }
+
+ public void put(String arg0, Object arg1) {
+ // NO-OP
+ }
+
+ public void setBindings(Bindings arg0, int arg1) {
+ // NO-OP
+ }
+
+ public void setContext(ScriptContext arg0) {
+ // NO-OP
+ }
+
+ }
+
+ public String getEngineName() {
+ return "Dummy Scripting Engine";
+ }
+
+ public String getEngineVersion() {
+ return "1.0";
+ }
+
+ public List<String> getExtensions() {
+ return Arrays.asList("dum", "dummy");
+ }
+
+ public String getLanguageName() {
+ return "dummy";
+ }
+
+ public String getLanguageVersion() {
+ return "2.0";
+ }
+
+ public String getMethodCallSyntax(String arg0, String arg1, String... arg2) {
+ throw new UnsupportedOperationException();
+ }
+
+ public List<String> getMimeTypes() {
+ return Collections.singletonList("application/x-dummy");
+ }
+
+ public List<String> getNames() {
+ return Arrays.asList("Dummy", "dummy");
+ }
+
+ public String getOutputStatement(String arg0) {
+ throw new UnsupportedOperationException();
+ }
+
+ public Object getParameter(String arg0) {
+ throw new UnsupportedOperationException();
+ }
+
+ public String getProgram(String... arg0) {
+ throw new UnsupportedOperationException();
+ }
+
+ public ScriptEngine getScriptEngine() {
+ return new DummyScriptEngine();
+ }
+
+}
diff --git a/healthcheck/generalchecks/src/test/java/org/apache/felix/hc/generalchecks/util/ScriptEnginesTrackerTest.java b/healthcheck/generalchecks/src/test/java/org/apache/felix/hc/generalchecks/util/ScriptEnginesTrackerTest.java
new file mode 100644
index 0000000000..522c995982
--- /dev/null
+++ b/healthcheck/generalchecks/src/test/java/org/apache/felix/hc/generalchecks/util/ScriptEnginesTrackerTest.java
@@ -0,0 +1,96 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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.
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
+package org.apache.felix.hc.generalchecks.util;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.net.URL;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.junit.Test;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleEvent;
+
+public class ScriptEnginesTrackerTest {
+
+ private static Class<?> SCRIPT_ENGINE_FACTORY = DummyScriptEngineFactory.class;
+
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ @Test
+ public void testBundledScriptEngineFactory() throws Exception {
+ final URL url = createFactoryFile().toURI().toURL();
+ Bundle bundle = mock(Bundle.class);
+ when(bundle.getBundleId()).thenReturn(1L);
+ when(bundle.loadClass(SCRIPT_ENGINE_FACTORY.getName())).thenReturn((Class)SCRIPT_ENGINE_FACTORY);
+ when(bundle.findEntries(ScriptEnginesTracker.META_INF_SERVICES, ScriptEnginesTracker.FACTORY_NAME, false)).thenReturn(Collections.enumeration(Collections.singleton(url)));
+
+ // simulate a bundle starting that declares a new ScriptEngineFactory
+ BundleEvent bundleEvent = new BundleEvent(BundleEvent.STARTED, bundle);
+ ScriptEnginesTracker scriptEngineTracker = new ScriptEnginesTracker(); // context.getService(ScriptEnginesTracker.class);
+ assertNotNull("Expected that the ScriptEnginesTracker would already be registered.", scriptEngineTracker);
+ scriptEngineTracker.bundleChanged(bundleEvent);
+ Map<Bundle, List<String>> languagesByBundle = scriptEngineTracker.getLanguagesByBundle();
+ AtomicInteger factoriesSize = new AtomicInteger(0);
+ languagesByBundle.values().stream()
+ .forEach(l -> factoriesSize.addAndGet(l.size()));
+ int expectedScriptEngineFactories = 1;
+ assertEquals("Expected " + expectedScriptEngineFactories + " ScriptEngineFactories.", expectedScriptEngineFactories, factoriesSize.intValue());
+ List<String> factoriesForBundle = languagesByBundle.get(bundle);
+ assertEquals(1, factoriesForBundle.size());
+ assertEquals("dummy", factoriesForBundle.get(0));
+ assertEquals("Dummy Scripting Engine", scriptEngineTracker.getEngineByLanguage("dummy").getFactory().getEngineName());
+
+ // simulate a bundle stopping that previously declared a new ScriptEngineFactory
+ bundleEvent = new BundleEvent(BundleEvent.STOPPED, bundle);
+ scriptEngineTracker.bundleChanged(bundleEvent);
+ expectedScriptEngineFactories--;
+
+ factoriesSize.set(0);
+ languagesByBundle = scriptEngineTracker.getLanguagesByBundle();
+ assertNotNull(languagesByBundle);
+ languagesByBundle.values().stream()
+ .forEach(l -> factoriesSize.addAndGet(l.size()));
+ assertFalse(languagesByBundle.containsKey(bundle));
+ assertEquals("Expected " + expectedScriptEngineFactories + " ScriptEngineFactory.", expectedScriptEngineFactories, factoriesSize.intValue());
+ assertNull("Did not expect references to the already unregistered DummyScriptEngineFactory",
+ scriptEngineTracker.getEngineByLanguage("dummy"));
+ }
+
+ private File createFactoryFile() throws IOException {
+ File tempFile = File.createTempFile("scriptEngine", "tmp");
+ tempFile.deleteOnExit();
+ try (FileOutputStream fos = new FileOutputStream(tempFile)) {
+ fos.write("#I'm a test-comment\n".getBytes());
+ fos.write(SCRIPT_ENGINE_FACTORY.getName().getBytes());
+ }
+ return tempFile;
+ }
+
+}