type) {
+ Object val = propertyValue(name, type);
+ if (val == null || type.isAssignableFrom(val.getClass())) {
+ return (E) val;
+ }
+ if (val instanceof String) {
+ return Enum.valueOf(type, (String)val);
+ }
+ fail("Unexpected value type for enum: " + name);
+ return null;
+ }
+
+ public final Object propertyValue(String name) {
+ return propertyValue(name, Object.class);
+ }
+
+ public final Object propertyValue(String name, Class> type) {
+ String errorText = "Failed to get " + type.getSimpleName() + " value for property " + name;
+ return name.startsWith("get") || name.startsWith("is") || bean instanceof Annotation
+ ? callMethod(name, errorText)
+ : accessField(name, errorText);
+ }
+
+ public final Object callMethod(String name, String errorText) {
+ try {
+ Method getter = bean.getClass().getMethod(name, new Class[0]);
+ return getter.invoke(bean);
+ } catch (Throwable e) {
+ throw asAssertionError(errorText + " for method " + name, e);
+ }
+ }
+
+ public final Object callMethod(String name, String errorText, Class
parameter, P argument) {
+ try {
+ Method getter = bean.getClass().getMethod(name, new Class[] { parameter });
+ return getter.invoke(bean, argument);
+ } catch (Throwable e) {
+ throw asAssertionError(errorText + " for method " + name, e);
+ }
+ }
+
+ public final Object accessField(String name, String errorText) {
+ try {
+ Field field = bean.getClass().getDeclaredField(name);
+ field.setAccessible(true);
+ return field.get(bean);
+ } catch (Throwable e) {
+ throw asAssertionError(errorText + " for field " + name, e);
+ }
+ }
+
+ public static AssertionError asAssertionError(String msg, Throwable e) {
+ return e.getClass() == AssertionError.class ? (AssertionError)e : new AssertionError(msg, e);
+ }
+}
diff --git a/appserver/tests/test-micro-jar/src/test/java/fish/payara/test/util/IntegrationTest.java b/appserver/tests/test-micro-jar/src/test/java/fish/payara/test/util/IntegrationTest.java
new file mode 100644
index 00000000000..43f14c57791
--- /dev/null
+++ b/appserver/tests/test-micro-jar/src/test/java/fish/payara/test/util/IntegrationTest.java
@@ -0,0 +1,48 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright (c) 2019 Payara Foundation and/or its affiliates. All rights reserved.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common Development
+ * and Distribution License("CDDL") (collectively, the "License"). You
+ * may not use this file except in compliance with the License. You can
+ * obtain a copy of the License at
+ * https://github.com/payara/Payara/blob/master/LICENSE.txt
+ * See the License for the specific
+ * language governing permissions and limitations under the License.
+ *
+ * When distributing the software, include this License Header Notice in each
+ * file and include the License file at glassfish/legal/LICENSE.txt.
+ *
+ * GPL Classpath Exception:
+ * The Payara Foundation designates this particular file as subject to the "Classpath"
+ * exception as provided by the Payara Foundation in the GPL Version 2 section of the License
+ * file that accompanied this code.
+ *
+ * Modifications:
+ * If applicable, add the following below the License Header, with the fields
+ * enclosed by brackets [] replaced by your own identifying information:
+ * "Portions Copyright [year] [name of copyright owner]"
+ *
+ * Contributor(s):
+ * If you wish your version of this file to be governed by only the CDDL or
+ * only the GPL Version 2, indicate your decision by adding "[Contributor]
+ * elects to include this software in this distribution under the [CDDL or GPL
+ * Version 2] license." If you don't indicate a single choice of license, a
+ * recipient has the option to distribute your version of this file under
+ * either the CDDL, the GPL Version 2 or to extend the choice of license to
+ * its licensees as provided above. However, if you add GPL Version 2 code
+ * and therefore, elected the GPL Version 2 license, then the option applies
+ * only if the new code is made subject to such option by the copyright
+ * holder.
+ */
+package fish.payara.test.util;
+
+/**
+ * Marker interface to be used together with junit's {@link org.junit.experimental.categories.Category} to mark
+ * integration tests.
+ */
+public interface IntegrationTest {
+ // marker
+}
diff --git a/appserver/tests/test-micro-jar/src/test/java/fish/payara/test/util/PayaraMicroServer.java b/appserver/tests/test-micro-jar/src/test/java/fish/payara/test/util/PayaraMicroServer.java
new file mode 100644
index 00000000000..f901829a449
--- /dev/null
+++ b/appserver/tests/test-micro-jar/src/test/java/fish/payara/test/util/PayaraMicroServer.java
@@ -0,0 +1,241 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright (c) 2019 Payara Foundation and/or its affiliates. All rights reserved.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common Development
+ * and Distribution License("CDDL") (collectively, the "License"). You
+ * may not use this file except in compliance with the License. You can
+ * obtain a copy of the License at
+ * https://github.com/payara/Payara/blob/master/LICENSE.txt
+ * See the License for the specific
+ * language governing permissions and limitations under the License.
+ *
+ * When distributing the software, include this License Header Notice in each
+ * file and include the License file at glassfish/legal/LICENSE.txt.
+ *
+ * GPL Classpath Exception:
+ * The Payara Foundation designates this particular file as subject to the "Classpath"
+ * exception as provided by the Payara Foundation in the GPL Version 2 section of the License
+ * file that accompanied this code.
+ *
+ * Modifications:
+ * If applicable, add the following below the License Header, with the fields
+ * enclosed by brackets [] replaced by your own identifying information:
+ * "Portions Copyright [year] [name of copyright owner]"
+ *
+ * Contributor(s):
+ * If you wish your version of this file to be governed by only the CDDL or
+ * only the GPL Version 2, indicate your decision by adding "[Contributor]
+ * elects to include this software in this distribution under the [CDDL or GPL
+ * Version 2] license." If you don't indicate a single choice of license, a
+ * recipient has the option to distribute your version of this file under
+ * either the CDDL, the GPL Version 2 or to extend the choice of license to
+ * its licensees as provided above. However, if you add GPL Version 2 code
+ * and therefore, elected the GPL Version 2 license, then the option applies
+ * only if the new code is made subject to such option by the copyright
+ * holder.
+ */
+package fish.payara.test.util;
+
+import static fish.payara.test.util.BeanProxy.asAssertionError;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import fish.payara.micro.PayaraInstance;
+import fish.payara.micro.PayaraMicro;
+import fish.payara.micro.PayaraMicroRuntime;
+
+/**
+ * A wrapper on the {@link PayaraMicroRuntime} and {@link PayaraInstance} to ease test setup and verification.
+ *
+ * This test setup works directly with the {@code payara-micro.jar} as its only dependency. While this has the benefit
+ * of testing the actual artefact this comes as the downside that there are only a few API classes available at compile
+ * time as the server itself is packaged within micro and only accessible at runtime using the right
+ * {@link ClassLoader}.
+ *
+ * Tests using the {@link PayaraMicroServer} should call {@link #start()} before the test(s) and {@link #stop()} after
+ * even when using the {@link #DEFAULT} instance.
+ */
+public final class PayaraMicroServer {
+
+ static {
+ System.setProperty("java.util.logging.config.file", "src/test/resources/logging.properties");
+ }
+
+ /**
+ * A instance to reuse during tests running in the same VM that do not have a problem with unknown start condition.
+ *
+ * Tests using this instance may change it but should not perform operations that make further usage hard or
+ * impossible, like for instance shutting down the server would.
+ */
+ public final static PayaraMicroServer DEFAULT = new PayaraMicroServer(true);
+
+ public static PayaraMicroServer newInstance() {
+ return new PayaraMicroServer(false);
+ }
+
+ private static final String GALSSFISH_CLASS_NAME = "org.glassfish.embeddable.GlassFish";
+ private static final String SERVICE_LOCATOR_CLASS_NAME = "org.glassfish.hk2.api.ServiceLocator";
+ private static final String TARGET_CLASS_NAME = "org.glassfish.internal.api.Target";
+ private static final String DOMAIN_CLASS_NAME = "com.sun.enterprise.config.serverbeans.Domain";
+
+ private final static String DEPLOYMENT_DIR = System.getProperty("user.dir") + File.separator + "target/deployments";
+
+ /**
+ * The port number to use next if a {@link PayaraMicroServer} instance is started.
+ */
+ private static AtomicInteger nextPort = new AtomicInteger(28989);
+
+ private final boolean defaultInstance;
+ private final AtomicBoolean started = new AtomicBoolean(false);
+ private final AtomicBoolean startedSuccessfully = new AtomicBoolean(false);
+ private PayaraMicro boot;
+ private PayaraMicroRuntime runtime;
+ private PayaraInstance instance;
+
+ private ClassLoader serverClassLoader;
+ private Object glassfish;
+ private Object serviceLocator;
+ private Object targetUtil;
+ private BeanProxy domain;
+
+ private PayaraMicroServer(boolean defaultInstance) {
+ this.defaultInstance = defaultInstance;
+ }
+
+ public PayaraInstance getInstance() {
+ return instance;
+ }
+
+ public PayaraMicroRuntime getRuntime() {
+ return runtime;
+ }
+
+ public void start() {
+ if (!started.compareAndSet(false, true)) {
+ return; // only start it once
+ }
+ try {
+ boot = PayaraMicro.getInstance();
+ File dir = new File(DEPLOYMENT_DIR);
+ createDummyFile(dir);
+ int port = nextPort.getAndIncrement();
+ runtime = boot.setInstanceName("micro-test")
+ .setDeploymentDir(dir)
+ .setHttpPort(port)
+ .setHttpAutoBind(true)
+ .bootStrap();
+ startedSuccessfully.compareAndSet(false, true);
+ instance = getField(PayaraInstance.class, runtime);
+ // classes are on the CP of the server but not of the test
+ serverClassLoader = instance.getClass().getClassLoader();
+ glassfish = getField(getClass(GALSSFISH_CLASS_NAME), runtime);
+ serviceLocator = getField(getClass(SERVICE_LOCATOR_CLASS_NAME), glassfish);
+ targetUtil = getService(getClass(TARGET_CLASS_NAME));
+ domain = new BeanProxy(getService(getClass(DOMAIN_CLASS_NAME)));
+ if (defaultInstance) {
+ Runtime.getRuntime().addShutdownHook(new Thread(() -> doStop()));
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ asAssertionError("Failed to start micro server", e);
+ }
+ }
+
+ public void stop() {
+ if (defaultInstance) {
+ return; // is stopped by JVM shutdown hook as it is shared amoung many tests
+ }
+ doStop();
+ }
+
+ private void doStop() {
+ if (!isStarted()) {
+ return; // don't try to stop if not started successfully
+ }
+ try {
+ boot.shutdown();
+ } catch (Exception e) {
+ // ignore...
+ }
+ }
+
+ private boolean isStarted() {
+ return started.get() && startedSuccessfully.get();
+ }
+
+ public T getDomainExtensionByType(Class type) {
+ return type.cast(domain.callMethod("getExtensionByType",
+ "Failed to get Domain extension of type " + type.getName(), Class.class, type));
+ }
+
+ public T getExtensionByType(String target, Class type) {
+ try {
+ Method getConfig = targetUtil.getClass().getMethod("getConfig", String.class);
+ Object config = getConfig.invoke(targetUtil, target);
+ Method getExtensionByType = config.getClass().getMethod("getExtensionByType", Class.class);
+ return type.cast(getExtensionByType.invoke(config, type));
+ } catch (Throwable e) {
+ throw asAssertionError("Failed to get extension", e);
+ }
+ }
+
+ public Class> getClass(String name) {
+ try {
+ return serverClassLoader.loadClass(name);
+ } catch (Throwable e) {
+ throw asAssertionError("Failed to load class for name: " + name, e);
+ }
+ }
+
+ public T getService(Class type) {
+ try {
+ Method getService = serviceLocator.getClass().getMethod("getService", Class.class, Annotation[].class);
+ return type.cast(getService.invoke(serviceLocator, type, new Annotation[0]));
+ } catch (Throwable e) {
+ throw asAssertionError("Failed to resolve service", e);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public List getAllServices(Class type) {
+ try {
+ Method getAllServices = serviceLocator.getClass().getMethod("getAllServices", Class.class, Annotation[].class);
+ return (List) getAllServices.invoke(serviceLocator, type, new Annotation[0]);
+ } catch (Throwable e) {
+ throw asAssertionError("Failed to resolve services", e);
+ }
+ }
+
+ private static T getField(Class fieldType, Object obj) {
+ for (Field f : obj.getClass().getDeclaredFields()) {
+ if (fieldType.isAssignableFrom(f.getType())) {
+ try {
+ f.setAccessible(true);
+ return fieldType.cast(f.get(obj));
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+ throw new IllegalStateException("Could not find instance field in runtime");
+ }
+
+ /**
+ * Currently the deployment dir is not allowed to be empty
+ */
+ private static void createDummyFile(File dir) throws IOException {
+ File dummy = new File(dir, "dummy.txt");
+ dummy.getParentFile().mkdirs();
+ dummy.createNewFile();
+ }
+}
diff --git a/appserver/tests/test-micro-jar/src/test/resources/logging.properties b/appserver/tests/test-micro-jar/src/test/resources/logging.properties
new file mode 100644
index 00000000000..d8642566f72
--- /dev/null
+++ b/appserver/tests/test-micro-jar/src/test/resources/logging.properties
@@ -0,0 +1,5 @@
+# Logging setup for testing that makes the output a lot less verbose
+handlers=java.util.logging.ConsoleHandler
+java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter
+com.sun.enterprise.server.logging.ODLLogFormatter.ansiColor=false
+java.util.logging.ConsoleHandler.level=INFO
\ No newline at end of file
diff --git a/nucleus/payara-modules/healthcheck-core/src/main/java/fish/payara/nucleus/healthcheck/HealthCheckService.java b/nucleus/payara-modules/healthcheck-core/src/main/java/fish/payara/nucleus/healthcheck/HealthCheckService.java
index b0f81bd7e50..48e3b066609 100644
--- a/nucleus/payara-modules/healthcheck-core/src/main/java/fish/payara/nucleus/healthcheck/HealthCheckService.java
+++ b/nucleus/payara-modules/healthcheck-core/src/main/java/fish/payara/nucleus/healthcheck/HealthCheckService.java
@@ -46,6 +46,7 @@
import fish.payara.nucleus.notification.configuration.Notifier;
import fish.payara.nucleus.notification.configuration.NotifierConfigurationType;
import fish.payara.nucleus.notification.domain.NotifierExecutionOptions;
+import fish.payara.nucleus.notification.domain.NotifierExecutionOptionsFactory;
import fish.payara.nucleus.notification.domain.NotifierExecutionOptionsFactoryStore;
import fish.payara.nucleus.notification.log.LogNotifier;
import fish.payara.nucleus.notification.log.LogNotifierExecutionOptions;
@@ -208,13 +209,16 @@ public void bootstrapHealthCheck() {
/**
* Starts all notifiers that have been enable with the healthcheck service.
*/
- public void bootstrapNotifierList() {
+ public synchronized void bootstrapNotifierList() {
notifierExecutionOptionsList = new ArrayList<>();
if (configuration.getNotifierList() != null) {
for (Notifier notifier : configuration.getNotifierList()) {
ConfigView view = ConfigSupport.getImpl(notifier);
NotifierConfigurationType annotation = view.getProxyType().getAnnotation(NotifierConfigurationType.class);
- notifierExecutionOptionsList.add(executionOptionsFactoryStore.get(annotation.type()).build(notifier));
+ NotifierExecutionOptionsFactory factory = executionOptionsFactoryStore.get(annotation.type());
+ if (factory != null) {
+ notifierExecutionOptionsList.add(factory.build(notifier));
+ }
}
}
if (notifierExecutionOptionsList.isEmpty()) {
@@ -300,7 +304,8 @@ public void shutdownHealthCheck() {
}
public BaseHealthCheck getCheck(String serviceName) {
- return registeredTasks.get(serviceName).getCheck();
+ HealthCheckTask task = registeredTasks.get(serviceName);
+ return task == null ? null : task.getCheck();
}
/**
diff --git a/nucleus/payara-modules/healthcheck-core/src/main/java/fish/payara/nucleus/healthcheck/admin/SetHealthCheckConfiguration.java b/nucleus/payara-modules/healthcheck-core/src/main/java/fish/payara/nucleus/healthcheck/admin/SetHealthCheckConfiguration.java
index afd4ec8a800..a64bf8be0db 100644
--- a/nucleus/payara-modules/healthcheck-core/src/main/java/fish/payara/nucleus/healthcheck/admin/SetHealthCheckConfiguration.java
+++ b/nucleus/payara-modules/healthcheck-core/src/main/java/fish/payara/nucleus/healthcheck/admin/SetHealthCheckConfiguration.java
@@ -120,7 +120,7 @@ public class SetHealthCheckConfiguration implements AdminCommand {
private String target;
@Param(name = "enabled")
- private boolean enabled;
+ private Boolean enabled;
@Param(name = "historical-trace-enabled", optional = true)
private Boolean historicalTraceEnabled;
@@ -157,7 +157,7 @@ private void updateConfig(HealthCheckServiceConfiguration config, AdminCommandCo
final ActionReport report = initActionReport(context);
try {
ConfigSupport.apply(configProxy -> {
- configProxy.enabled(String.valueOf(enabled));
+ configProxy.enabled(enabled.toString());
configProxy.setHistoricalTraceStoreSize(String.valueOf(historicalTraceStoreSize));
if (historicalTraceEnabled != null) {
configProxy.setHistoricalTraceEnabled(historicalTraceEnabled.toString());
@@ -183,7 +183,7 @@ private void updateLogNotifier(AdminCommandContext context) {
ParameterMap params = new ParameterMap();
params.add("target", target);
params.add("dynamic", String.valueOf(dynamic));
- params.add("enabled", String.valueOf(enabled));
+ params.add("enabled", enabled.toString());
params.add("notifier", NotifierType.LOG.name().toLowerCase());
// noisy will default to the notifier's config when not set so we do not set it as this is what we want
inv.parameters(params);
diff --git a/nucleus/payara-modules/healthcheck-core/src/main/java/fish/payara/nucleus/healthcheck/admin/SetHealthCheckServiceConfiguration.java b/nucleus/payara-modules/healthcheck-core/src/main/java/fish/payara/nucleus/healthcheck/admin/SetHealthCheckServiceConfiguration.java
index d9d116e81f9..43b1535279a 100644
--- a/nucleus/payara-modules/healthcheck-core/src/main/java/fish/payara/nucleus/healthcheck/admin/SetHealthCheckServiceConfiguration.java
+++ b/nucleus/payara-modules/healthcheck-core/src/main/java/fish/payara/nucleus/healthcheck/admin/SetHealthCheckServiceConfiguration.java
@@ -143,7 +143,7 @@ public class SetHealthCheckServiceConfiguration implements AdminCommand {
// general properties params:
@Param(name = "enabled", optional = false)
- private boolean enabled;
+ private Boolean enabled;
@Param(name = "time", optional = true)
@Min(value = 1, message = "Time period must be 1 or more")
@@ -293,9 +293,7 @@ private void updateSe
private void configureDynamically(
BaseHealthCheck service, C config) {
- if (service.getOptions() == null) {
- service.setOptions(service.constructOptions(config));
- }
+ service.setOptions(service.constructOptions(config));
healthCheckService.registerCheck(config.getName(), service);
healthCheckService.reboot();
if (service instanceof BaseThresholdHealthCheck) {
@@ -326,7 +324,7 @@ private void configureDynamically(BaseThresholdHealthCheck, ?> service) {
}
private Checker updateProperties(Checker config, Class type) throws PropertyVetoException {
- updateProperty(config, "enabled", config.getEnabled(), String.valueOf(enabled), Checker::setEnabled);
+ updateProperty(config, "enabled", config.getEnabled(), enabled.toString(), Checker::setEnabled);
updateProperty(config, "time", config.getTime(), time, Checker::setTime);
updateProperty(config, "time-unit", config.getUnit(), timeUnit, Checker::setUnit);
if (HoggingThreadsChecker.class.isAssignableFrom(type)) {
diff --git a/nucleus/payara-modules/healthcheck-core/src/main/java/fish/payara/nucleus/healthcheck/admin/SetHealthCheckServiceNotifierConfiguration.java b/nucleus/payara-modules/healthcheck-core/src/main/java/fish/payara/nucleus/healthcheck/admin/SetHealthCheckServiceNotifierConfiguration.java
index 6645321f0cd..ee70a23babb 100644
--- a/nucleus/payara-modules/healthcheck-core/src/main/java/fish/payara/nucleus/healthcheck/admin/SetHealthCheckServiceNotifierConfiguration.java
+++ b/nucleus/payara-modules/healthcheck-core/src/main/java/fish/payara/nucleus/healthcheck/admin/SetHealthCheckServiceNotifierConfiguration.java
@@ -127,7 +127,7 @@ public class SetHealthCheckServiceNotifierConfiguration implements AdminCommand
private Config targetConfig;
@Param(name = "enabled")
- private boolean enabled;
+ private Boolean enabled;
@Param(name = "noisy", optional = true)
private Boolean noisy;
@@ -186,7 +186,7 @@ public void execute(AdminCommandContext context) {
}
private void applyValues(Notifier notifier) throws PropertyVetoException {
- if (Boolean.parseBoolean(notifier.getEnabled()) != enabled) {
+ if (Boolean.parseBoolean(notifier.getEnabled()) != enabled.booleanValue()) {
report.appendMessage(notifierName + ".enabled was " + notifier.getEnabled() + " set to " + enabled + "\n");
notifier.enabled(enabled);
}
diff --git a/nucleus/payara-modules/notification-core/src/main/java/fish/payara/nucleus/notification/domain/NotifierExecutionOptionsFactoryStore.java b/nucleus/payara-modules/notification-core/src/main/java/fish/payara/nucleus/notification/domain/NotifierExecutionOptionsFactoryStore.java
index f7248a553dc..95ba16bef1f 100644
--- a/nucleus/payara-modules/notification-core/src/main/java/fish/payara/nucleus/notification/domain/NotifierExecutionOptionsFactoryStore.java
+++ b/nucleus/payara-modules/notification-core/src/main/java/fish/payara/nucleus/notification/domain/NotifierExecutionOptionsFactoryStore.java
@@ -47,6 +47,7 @@
import java.util.HashMap;
import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
/**
* @author mertcaliskan
@@ -56,7 +57,7 @@
public class NotifierExecutionOptionsFactoryStore {
private Map execOptionsFactoryStore =
- new HashMap();
+ new ConcurrentHashMap();
public NotifierExecutionOptionsFactory get(NotifierType type) {
return execOptionsFactoryStore.get(type);