Author: ghenzler
Date: Thu Jan 17 20:52:13 2019
New Revision: 1851585

URL: http://svn.apache.org/viewvc?rev=1851585&view=rev
Log:
FELIX-6025 ScriptedHealthCheck initial version

Added:
    
felix/trunk/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/ScriptedHealthCheck.java
   (with props)
    
felix/trunk/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/util/ScriptEnginesTracker.java
   (with props)
    
felix/trunk/healthcheck/generalchecks/src/test/java/org/apache/felix/hc/generalchecks/ScriptedHealthCheckTest.java
   (with props)
    felix/trunk/healthcheck/generalchecks/src/test/resources/
    felix/trunk/healthcheck/generalchecks/src/test/resources/org/
    felix/trunk/healthcheck/generalchecks/src/test/resources/org/apache/
    felix/trunk/healthcheck/generalchecks/src/test/resources/org/apache/felix/
    
felix/trunk/healthcheck/generalchecks/src/test/resources/org/apache/felix/hc/
    
felix/trunk/healthcheck/generalchecks/src/test/resources/org/apache/felix/hc/generalchecks/
    
felix/trunk/healthcheck/generalchecks/src/test/resources/org/apache/felix/hc/generalchecks/testHcScript.groovy
Modified:
    felix/trunk/healthcheck/generalchecks/pom.xml

Modified: felix/trunk/healthcheck/generalchecks/pom.xml
URL: 
http://svn.apache.org/viewvc/felix/trunk/healthcheck/generalchecks/pom.xml?rev=1851585&r1=1851584&r2=1851585&view=diff
==============================================================================
--- felix/trunk/healthcheck/generalchecks/pom.xml (original)
+++ felix/trunk/healthcheck/generalchecks/pom.xml Thu Jan 17 20:52:13 2019
@@ -152,6 +152,12 @@
             <version>1.9.5</version>
             <scope>test</scope>
         </dependency>
+        <dependency>
+          <groupId>org.codehaus.groovy</groupId>
+          <artifactId>groovy-all</artifactId>
+          <version>2.4.13</version>
+          <scope>test</scope>
+        </dependency>
         <!-- END test scope dependencies -->
 
     </dependencies>

Added: 
felix/trunk/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/ScriptedHealthCheck.java
URL: 
http://svn.apache.org/viewvc/felix/trunk/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/ScriptedHealthCheck.java?rev=1851585&view=auto
==============================================================================
--- 
felix/trunk/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/ScriptedHealthCheck.java
 (added)
+++ 
felix/trunk/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/ScriptedHealthCheck.java
 Thu Jan 17 20:52:13 2019
@@ -0,0 +1,276 @@
+/*
+ * 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.generalchecks;
+
+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.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import javax.script.Bindings;
+import javax.script.ScriptContext;
+import javax.script.ScriptEngine;
+import javax.script.SimpleBindings;
+import javax.script.SimpleScriptContext;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.felix.hc.api.FormattingResultLog;
+import org.apache.felix.hc.api.HealthCheck;
+import org.apache.felix.hc.api.Result;
+import org.apache.felix.hc.api.ResultLog;
+import org.apache.felix.hc.generalchecks.util.ScriptEnginesTracker;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.InvalidSyntaxException;
+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;
+import org.osgi.service.component.annotations.Reference;
+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;
+
+/** {@link HealthCheck} that runs an arbitrary script. */
+@Component(service = HealthCheck.class, configurationPolicy = 
ConfigurationPolicy.REQUIRE)
+@Designate(ocd = ScriptedHealthCheck.Config.class, factory = true)
+public class ScriptedHealthCheck implements HealthCheck {
+
+    private static final Logger LOG = 
LoggerFactory.getLogger(ScriptedHealthCheck.class);
+
+    public static final String HC_LABEL = "Health Check: Script";
+
+    @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', 
'osgi' and 'bundleContext'. "
+            + "'log' is an instance of 
org.apache.felix.hc.api.FormattingResultLog and is used to define the result of 
the HC. "
+            + "'osgi.getService(classObj)' can be used as shortcut to retrieve 
a service."
+            + "'osgi.getServices(classObj, filter)' used to retrieve multiple 
services for a class using given filter. "
+            + "For all services retrieved via osgi binding, unget() is called 
automatically at the end of the script execution."
+            + "'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 {
+
+        @AttributeDefinition(name = "Name", description = "Name of this health 
check.")
+        String hc_name() default "Scripted Health Check";
+
+        @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.")
+        String language() default "groovy";
+
+        @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').")
+        String scriptUrl() default "";
+
+        @AttributeDefinition
+        String webconsole_configurationFactory_nameHint() default "Scripted 
HC: {hc.name} (tags: {hc.tags}) {scriptUrl} language: {language}";
+    }
+
+    private String language;
+    private String script;
+    private String scriptUrl;
+    
+    private BundleContext bundleContext;
+    
+    @Reference
+    private ScriptEnginesTracker scriptEnginesTracker;
+
+    @Activate
+    protected void activate(BundleContext context, Config config) {
+        this.bundleContext = context;
+        this.language = config.language().toLowerCase();
+        this.script = config.script();
+        this.scriptUrl = config.scriptUrl();
+        
+        if(StringUtils.isNotBlank(script) && 
StringUtils.isNotBlank(scriptUrl)) {
+            LOG.info("Both 'script' and 'scriptUrl' (=()) are configured, 
ignoring 'scriptUrl'", scriptUrl);
+            scriptUrl = null;
+        }
+
+        LOG.info("Activated Scripted HC "+config.hc_name()+" with "+ 
(StringUtils.isNotBlank(script)?"script "+script: "script url "+scriptUrl));
+
+    }
+
+    @Override
+    public Result execute() {
+        FormattingResultLog log = new FormattingResultLog();
+        
+        ScriptEngine scriptEngine = 
scriptEnginesTracker.getEngineByLanguage(language);
+        if(scriptEngine == null) {
+            log.healthCheckError("No ScriptEngineFactory found for language "+ 
language + " (available languages: 
"+scriptEnginesTracker.getLanguagesByBundle()+")");
+            return new Result(log);
+        }
+        
+        final Bindings bindings = new SimpleBindings();
+        final ScriptHelper osgi = new ScriptHelper(bundleContext);
+
+        StringWriter stdout = new StringWriter();
+        StringWriter stderr = new StringWriter();
+
+        bindings.put("osgi", osgi);
+        bindings.put("log", log);
+        bindings.put("bundleContext", bundleContext);
+
+        SimpleScriptContext scriptContext = new SimpleScriptContext();
+        scriptContext.setBindings(bindings, ScriptContext.ENGINE_SCOPE);
+        scriptContext.setWriter(stdout);
+        scriptContext.setErrorWriter(stderr);
+
+        try {
+            boolean urlIsUsed = StringUtils.isBlank(script);
+            String scriptToExecute = urlIsUsed ? getFileContents(scriptUrl): 
script;
+            log.info("Executing script {} ({} lines)...", 
(urlIsUsed?scriptUrl:" as configured"), scriptToExecute.split("\n").length);
+            log.debug(scriptToExecute);
+            Object scriptResult = scriptEngine.eval(scriptToExecute, 
scriptContext);
+            appendStreamsToResult(log, stdout, stderr, scriptContext);
+            
+            if(scriptResult instanceof Result) {
+                Result result = (Result) scriptResult;
+                for(ResultLog.Entry entry: result) {
+                    log.add(entry);
+                }
+            } else if(scriptResult != null){
+                log.info("Script result: {}", scriptResult);
+            }
+            
+            
+        }  catch (Exception e) {
+            log.healthCheckError("Exception during script execution: "+e, e);
+        } finally  {
+            osgi.ungetServices();
+        }
+
+        return new Result(log);
+    }
+
+    private void appendStreamsToResult(FormattingResultLog log, StringWriter 
stdout, StringWriter stderr, SimpleScriptContext scriptContext)
+            throws IOException {
+        scriptContext.getWriter().flush();
+        String stdoutStr = stdout.toString();
+        if(StringUtils.isNotBlank(stdoutStr)) {
+            log.info("stdout of script: {}", stdoutStr);
+        }
+        
+        scriptContext.getErrorWriter().flush();
+        String stderrStr = stderr.toString();
+        if(StringUtils.isNotBlank(stderrStr)) {
+            log.critical("stderr of script: {}", stderrStr);
+        }
+    }
+    
+    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"));
+            }
+            return content;
+        }catch(IOException e) {
+            throw new IllegalArgumentException("Could not read URL "+url+": 
"+e, e);
+        }
+    }
+
+    // Script Helper for OSGi available as binding 'osgi'
+    class ScriptHelper {
+        
+        private final BundleContext bundleContext;
+        private List<ServiceReference<?>> references;
+        private Map<String, Object> services;
+
+        public ScriptHelper(BundleContext bundleContext) {
+            this.bundleContext = bundleContext;
+        }
+
+        @SuppressWarnings("unchecked")
+        public <ServiceType> ServiceType getService(Class<ServiceType> type) {
+            ServiceType service = (this.services == null ? null  : 
(ServiceType) this.services.get(type.getName()));
+            if (service == null) {
+                final ServiceReference<?> ref = 
this.bundleContext.getServiceReference(type.getName());
+                if (ref != null) {
+                    service = (ServiceType) this.bundleContext.getService(ref);
+                    if (service != null) {
+                        if (this.services == null) {
+                            this.services = new HashMap<String, Object>();
+                        }
+                        if (this.references == null) {
+                            this.references = new 
ArrayList<ServiceReference<?>>();
+                        }
+                        this.references.add(ref);
+                        this.services.put(type.getName(), service);
+                    }
+                }
+            }
+            return service;
+        }
+
+        public <T> T[] getServices(Class<T> serviceType,  String filter) 
throws InvalidSyntaxException {
+            final ServiceReference<?>[] refs = 
this.bundleContext.getServiceReferences(serviceType.getName(), filter);
+            T[] result = null;
+            if (refs != null) {
+                final List<T> objects = new ArrayList<T>();
+                for (int i = 0; i < refs.length; i++) {
+                    @SuppressWarnings("unchecked")
+                    final T service = (T) 
this.bundleContext.getService(refs[i]);
+                    if (service != null) {
+                        if (this.references == null) {
+                            this.references = new 
ArrayList<ServiceReference<?>>();
+                        }
+                        this.references.add(refs[i]);
+                        objects.add(service);
+                    }
+                }
+                if (objects.size() > 0) {
+                    @SuppressWarnings("unchecked")
+                    T[] srv = (T[]) Array.newInstance(serviceType,  
objects.size());
+                    result = objects.toArray(srv);
+                }
+            }
+            return result;
+        }
+
+        public void ungetServices() {
+            if (this.references != null) {
+                final Iterator<ServiceReference<?>> i = 
this.references.iterator();
+                while (i.hasNext()) {
+                    final ServiceReference<?> ref = i.next();
+                    this.bundleContext.ungetService(ref);
+                }
+                this.references.clear();
+            }
+            if (this.services != null) {
+                this.services.clear();
+            }
+        }
+    }
+    
+}

Propchange: 
felix/trunk/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/ScriptedHealthCheck.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: 
felix/trunk/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/util/ScriptEnginesTracker.java
URL: 
http://svn.apache.org/viewvc/felix/trunk/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/util/ScriptEnginesTracker.java?rev=1851585&view=auto
==============================================================================
--- 
felix/trunk/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/util/ScriptEnginesTracker.java
 (added)
+++ 
felix/trunk/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/util/ScriptEnginesTracker.java
 Thu Jan 17 20:52:13 2019
@@ -0,0 +1,194 @@
+/*
+ * 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.generalchecks.util;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.script.ScriptEngine;
+import javax.script.ScriptEngineFactory;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleEvent;
+import org.osgi.framework.BundleListener;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Deactivate;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/** Simple service to track script engines available via osgi bundles that 
define META-INF/services/javax.script.ScriptEngineFactory, e.g. like 
groovy-all. */
+@Component(immediate=true, service = ScriptEnginesTracker.class)
+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();
+    private final Map<String, ScriptEngineFactory> enginesByLanguage = new 
ConcurrentHashMap<String, ScriptEngineFactory>();
+    private final Map<Bundle, List<String>> languagesByBundle = new 
ConcurrentHashMap<Bundle, List<String>>();
+
+    /** ServiceTracker for ScriptEngineFactory */
+    private BundleContext context;
+
+    @Activate
+    public void activate(BundleContext context) {
+        this.context = context;
+        this.context.addBundleListener(this);
+        registerInitialScriptEngineFactories();
+    }
+    
+    @Deactivate
+    public void deactivate() {
+        this.context.removeBundleListener(this);
+        
+        enginesByLanguage.clear();
+        languagesByBundle.clear();
+    }
+
+    public ScriptEngine getEngineByLanguage(String language) {
+        ScriptEngineFactory factory = 
enginesByLanguage.get(language.toLowerCase());
+        if (factory == null) {
+            return null;
+        }
+        
+        ScriptEngine engine = factory.getScriptEngine();
+        return engine;
+    }
+
+    public Map<Bundle, List<String>> getLanguagesByBundle() {
+        return languagesByBundle;
+    }
+
+    
+    public void bundleChanged(BundleEvent event) {
+        if (event.getType() == BundleEvent.STARTED && 
event.getBundle().getEntry(ENGINE_FACTORY_SERVICE) != null) {
+            registerFactories(event.getBundle());
+        } else if (event.getType() == BundleEvent.STOPPED) {
+            unregisterFactories(event.getBundle());
+        }
+    }
+
+
+    private void registerInitialScriptEngineFactories() {
+        Bundle[] bundles = this.context.getBundles();
+        for (Bundle bundle : bundles) {
+            if ( bundle.getState() == Bundle.ACTIVE  && 
bundle.getEntry(ENGINE_FACTORY_SERVICE)!=null) {
+                registerFactories(bundle);
+            }
+        }
+    }
+
+    private void registerFactories(Bundle bundle) {
+        List<ScriptEngineFactory> scriptEngineFactoriesForBundle = 
getScriptEngineFactoriesForBundle(bundle);
+        for (ScriptEngineFactory scriptEngineFactory : 
scriptEngineFactoriesForBundle) {
+            registerFactory(bundle, scriptEngineFactory);
+
+        }
+    }
+
+    private void unregisterFactories(Bundle bundle) {
+        List<String> languagesForBundle = languagesByBundle.get(bundle);
+        for (String lang : languagesForBundle) {
+            ScriptEngineFactory removed = enginesByLanguage.remove(lang);
+            LOG.info("Removing ScriptEngine {} for language {}", removed, 
lang);
+        }
+    }
+
+    
+    @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);
+                }
+            }
+
+        } catch (IOException ioe) {
+            LOG.warn("Exception while trying to load factories as defined in 
{}", ENGINE_FACTORY_SERVICE, ioe);
+        } finally {
+            closeQuietly(ins);
+        }
+        
+        return scriptEngineFactoriesInBundle;
+    }
+
+    private void closeQuietly(InputStream ins) {
+        if (ins != null) {
+            try {
+                ins.close();
+            } catch (IOException e) {
+                // ignore
+            }
+        }
+    }
+
+    private void registerFactory(Bundle bundle, final ScriptEngineFactory 
factory) {
+        LOG.info("Adding ScriptEngine {}, {} for language {}, {}",
+                factory.getEngineName(), factory.getEngineVersion(),
+                factory.getLanguageName(), factory.getLanguageVersion());
+
+        String scriptLang = factory.getLanguageName().toLowerCase();
+        
+        enginesByLanguage.put(scriptLang, factory);
+        
+        List<String> languages = languagesByBundle.get(bundle);
+        if(languages==null) {
+            languages = new ArrayList<String>();
+            languagesByBundle.put(bundle, languages);
+        }
+        languages.add(scriptLang);
+    }
+
+
+    static List<String> getClassNames(BufferedReader reader) throws 
IOException {
+        List<String> classNames = new ArrayList<String>();
+        String line;
+        while ((line = reader.readLine()) != null) {
+            if (!line.startsWith("#") && line.trim().length() > 0) {
+                int indexOfHash = line.indexOf('#');
+                if (indexOfHash >= 0) {
+                    line = line.substring(0, indexOfHash);
+                }
+                line = line.trim();
+                classNames.add(line);
+            }
+        }
+        return classNames;
+    }
+
+}

Propchange: 
felix/trunk/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/util/ScriptEnginesTracker.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: 
felix/trunk/healthcheck/generalchecks/src/test/java/org/apache/felix/hc/generalchecks/ScriptedHealthCheckTest.java
URL: 
http://svn.apache.org/viewvc/felix/trunk/healthcheck/generalchecks/src/test/java/org/apache/felix/hc/generalchecks/ScriptedHealthCheckTest.java?rev=1851585&view=auto
==============================================================================
--- 
felix/trunk/healthcheck/generalchecks/src/test/java/org/apache/felix/hc/generalchecks/ScriptedHealthCheckTest.java
 (added)
+++ 
felix/trunk/healthcheck/generalchecks/src/test/java/org/apache/felix/hc/generalchecks/ScriptedHealthCheckTest.java
 Thu Jan 17 20:52:13 2019
@@ -0,0 +1,234 @@
+/*
+ * 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.generalchecks;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import java.lang.annotation.Annotation;
+import java.net.URL;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
+
+import javax.script.ScriptEngine;
+import javax.script.ScriptEngineManager;
+
+import org.apache.felix.hc.api.Result;
+import org.apache.felix.hc.api.ResultLog.Entry;
+import org.apache.felix.hc.generalchecks.util.ScriptEnginesTracker;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.Spy;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+
+public class ScriptedHealthCheckTest {
+
+    private static final String GROOVY = "Groovy";
+
+    
+    @Spy
+    @InjectMocks
+    ScriptedHealthCheck scriptedHealthCheck;
+
+    @Spy
+    private ScriptEnginesTracker scriptEnginesTracker;
+
+    @Mock
+    private BundleContext bundleContext;
+    
+    @Mock
+    private ServiceReference<TestService> testServiceReference;
+    
+    @Mock
+    private TestService testService;
+    
+    @Before
+    public void setup() {
+        initMocks(this);
+        ScriptEngineManager factory = new ScriptEngineManager();
+        // create JavaScript engine
+        ScriptEngine groovyEngine = factory.getEngineByName(GROOVY);
+        
doReturn(groovyEngine).when(scriptEnginesTracker).getEngineByLanguage(GROOVY.toLowerCase());
+    }
+
+    
+    
+    @Test
+    public void testSimpleStatusValues() {
+        scriptedHealthCheck.activate(bundleContext, new TestConfig(GROOVY, 
"log.info('good')", ""));
+        assertEquals(Result.Status.OK, 
scriptedHealthCheck.execute().getStatus()); 
+
+        scriptedHealthCheck.activate(bundleContext, new TestConfig(GROOVY, 
"log.warn('not so good')", ""));
+        assertEquals(Result.Status.WARN, 
scriptedHealthCheck.execute().getStatus()); 
+
+        scriptedHealthCheck.activate(bundleContext, new TestConfig(GROOVY, 
"log.critical('bad')", ""));
+        assertEquals(Result.Status.CRITICAL, 
scriptedHealthCheck.execute().getStatus()); 
+
+        scriptedHealthCheck.activate(bundleContext, new TestConfig(GROOVY, 
"log.temporarilyUnavailable('tmp away')", ""));
+        assertEquals(Result.Status.TEMPORARILY_UNAVAILABLE, 
scriptedHealthCheck.execute().getStatus()); 
+
+    }
+    
+    @Test
+    public void testExceptionInScript() {
+        scriptedHealthCheck.activate(bundleContext, new TestConfig(GROOVY, 
"throw new IllegalStateException()", ""));
+        assertEquals(Result.Status.HEALTH_CHECK_ERROR, 
scriptedHealthCheck.execute().getStatus()); 
+    }
+    
+    @Test
+    public void testWithScriptUrl() {
+        URL scriptUrl = getClass().getResource("testHcScript.groovy");
+        scriptedHealthCheck.activate(bundleContext, new TestConfig(GROOVY, "", 
scriptUrl.toString()));
+        Result result = scriptedHealthCheck.execute();
+        assertEquals(Result.Status.OK, result.getStatus()); 
+        
+        List<Entry> entries = getEntries(result);
+        assertEquals(3, entries.size()); 
+        assertEquals("Test Script URL", 
entries.get(entries.size()-1).getMessage()); 
+    }
+
+    @Test
+    public void testStdOut() {
+        scriptedHealthCheck.activate(bundleContext, new TestConfig(GROOVY, 
"print 'test'", ""));
+        Result result = scriptedHealthCheck.execute();
+         
+        List<Entry> entries = getEntries(result);
+        assertEquals(3, entries.size()); 
+        assertEquals("stdout of script: test", 
entries.get(entries.size()-1).getMessage()); 
+    }
+
+    @Test
+    public void testCombinedResult() {
+        scriptedHealthCheck.activate(bundleContext, new TestConfig(GROOVY, 
+                "import org.apache.felix.hc.api.*\n" +
+                "log.info('test1')\n" + 
+                "return new Result(Result.Status.WARN, 'warn')",""));
+        Result result = scriptedHealthCheck.execute();
+        assertEquals(Result.Status.WARN, result.getStatus()); 
+
+        List<Entry> entries = getEntries(result);
+        assertEquals(4, entries.size()); 
+        assertEquals("test1", entries.get(entries.size()-2).getMessage()); 
+        assertEquals("warn", entries.get(entries.size()-1).getMessage()); 
+    }
+    
+    @Test
+    public void testArbitraryResultLogged() {
+        scriptedHealthCheck.activate(bundleContext, new TestConfig(GROOVY, 
+                "return 'ARBITRARY_RESULT_OBJECT'",""));
+        Result result = scriptedHealthCheck.execute();
+        assertEquals(Result.Status.OK, result.getStatus()); 
+
+        List<Entry> entries = getEntries(result);
+        assertEquals(3, entries.size()); 
+        assertEquals("Script result: ARBITRARY_RESULT_OBJECT", 
entries.get(entries.size()-1).getMessage()); 
+    }
+    
+    @Test
+    public void testOsgiBinding() {
+
+        TestService testService = new TestService();
+        
doReturn(testServiceReference).when(bundleContext).getServiceReference(TestService.class.getName());
+        
doReturn(testService).when(bundleContext).getService(testServiceReference);
+        
+        scriptedHealthCheck.activate(bundleContext, new TestConfig(GROOVY, 
+                "return 
osgi.getService(org.apache.felix.hc.generalchecks.ScriptedHealthCheckTest.TestService.class)",""));
+        
+        Result result = scriptedHealthCheck.execute();
+        assertEquals(Result.Status.OK, result.getStatus()); 
+        assertEquals("Script result: TestService", 
getEntries(result).get(2).getMessage()); 
+
+        verify(bundleContext, 
times(1)).getServiceReference(TestService.class.getName());
+        verify(bundleContext, times(1)).getService(testServiceReference);
+        verify(bundleContext, times(1)).ungetService(testServiceReference);
+    }
+    
+
+    private List<Entry> getEntries(Result execute) {
+        return StreamSupport.stream(execute.spliterator(), 
false).collect(Collectors.toList());
+    }
+
+    public class TestService {
+
+        @Override
+        public String toString() {
+            return "TestService";
+        }
+        
+    }
+    
+    private final class TestConfig implements ScriptedHealthCheck.Config {
+        
+        private final String language;
+        private final String script;
+        private final String scriptUrl;
+        
+        public TestConfig(String language, String script, String scriptUrl) {
+            super();
+            this.language = language;
+            this.script = script;
+            this.scriptUrl = scriptUrl;
+        }
+
+        @Override
+        public Class<? extends Annotation> annotationType() {
+            return ScriptedHealthCheck.Config.class;
+        }
+
+        @Override
+        public String webconsole_configurationFactory_nameHint() {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public String scriptUrl() {
+            return scriptUrl;
+        }
+
+        @Override
+        public String script() {
+            return script;
+        }
+
+        @Override
+        public String language() {
+            return language;
+        }
+
+        @Override
+        public String[] hc_tags() {
+            return new String[] {"test"};
+        }
+
+        @Override
+        public String hc_name() {
+            return "Test HC";
+        }
+    }
+
+}

Propchange: 
felix/trunk/healthcheck/generalchecks/src/test/java/org/apache/felix/hc/generalchecks/ScriptedHealthCheckTest.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: 
felix/trunk/healthcheck/generalchecks/src/test/resources/org/apache/felix/hc/generalchecks/testHcScript.groovy
URL: 
http://svn.apache.org/viewvc/felix/trunk/healthcheck/generalchecks/src/test/resources/org/apache/felix/hc/generalchecks/testHcScript.groovy?rev=1851585&view=auto
==============================================================================
--- 
felix/trunk/healthcheck/generalchecks/src/test/resources/org/apache/felix/hc/generalchecks/testHcScript.groovy
 (added)
+++ 
felix/trunk/healthcheck/generalchecks/src/test/resources/org/apache/felix/hc/generalchecks/testHcScript.groovy
 Thu Jan 17 20:52:13 2019
@@ -0,0 +1,19 @@
+/*
+ * 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.
+ */
+ 
+log.info('Test Script URL')
\ No newline at end of file


Reply via email to