| /* |
| * Copyright (c) 2010, 2020 Oracle and/or its affiliates. All rights reserved. |
| * |
| * This program and the accompanying materials are made available under the |
| * terms of the Eclipse Public License v. 2.0, which is available at |
| * http://www.eclipse.org/legal/epl-2.0. |
| * |
| * This Source Code may also be made available under the following Secondary |
| * Licenses when the conditions for such availability set forth in the |
| * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, |
| * version 2 with the GNU Classpath Exception, which is available at |
| * https://www.gnu.org/software/classpath/license.html. |
| * |
| * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 |
| */ |
| |
| package org.glassfish.jersey.test; |
| |
| import java.net.URI; |
| import java.security.AccessController; |
| import java.util.ArrayList; |
| import java.util.Enumeration; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.IdentityHashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Optional; |
| import java.util.Properties; |
| import java.util.Set; |
| import java.util.concurrent.atomic.AtomicReference; |
| import java.util.logging.Handler; |
| import java.util.logging.Level; |
| import java.util.logging.LogManager; |
| import java.util.logging.LogRecord; |
| import java.util.logging.Logger; |
| |
| import javax.net.ssl.SSLContext; |
| import javax.net.ssl.SSLParameters; |
| import javax.ws.rs.client.Client; |
| import javax.ws.rs.client.ClientBuilder; |
| import javax.ws.rs.client.WebTarget; |
| import javax.ws.rs.core.Application; |
| import javax.ws.rs.core.Response; |
| import javax.ws.rs.core.UriBuilder; |
| |
| import org.glassfish.jersey.client.ClientConfig; |
| import org.glassfish.jersey.internal.ServiceFinder; |
| import org.glassfish.jersey.internal.util.PropertiesHelper; |
| import org.glassfish.jersey.internal.util.ReflectionHelper; |
| import org.glassfish.jersey.logging.LoggingFeature; |
| import org.glassfish.jersey.test.spi.TestContainer; |
| import org.glassfish.jersey.test.spi.TestContainerException; |
| import org.glassfish.jersey.test.spi.TestContainerFactory; |
| |
| import org.junit.After; |
| import org.junit.Before; |
| |
| /** |
| * Parent class for testing JAX-RS and Jersey-based applications using Jersey test framework. |
| * <p> |
| * At construction this class will obtain a {@link org.glassfish.jersey.test.spi.TestContainerFactory |
| * test container factory} implementation. |
| * </p> |
| * <p> |
| * Before each test method in an extending class is run the test container factory is used to obtain |
| * a configured {@link org.glassfish.jersey.test.spi.TestContainer test container}. |
| * Then the {@link TestContainer#start()} method is invoked on the configured test container. After each test method |
| * has run, the {@link TestContainer#stop()} method is invoked on the test container. Stopped test container |
| * generally shouldn't be again started for another test, rather a new test container should be created. |
| * Every test method in the {@code JerseyTest} subclass can invoke the {@link #client()} to obtain a JAX-RS |
| * {@link javax.ws.rs.client.Client}, from which {@link javax.ws.rs.client.WebTarget} instances can be created |
| * to send arbitrary requests. |
| * Also, one of the {@code target} methods ({@link #target()} or {@link #target(String)}) may be invoked to obtain |
| * a JAX-RS {@link javax.ws.rs.client.WebTarget} instances from which requests can be sent to and responses |
| * received from the Web application under test. |
| * </p> |
| * <p> |
| * If a test container factory is not explicitly declared using the appropriate constructor |
| * (see {@link #JerseyTest(TestContainerFactory)}) or by overriding the {@link #getTestContainerFactory()} method, |
| * then a default test container factory will be obtained as follows: |
| * <ol> |
| * <li> |
| * If a system property <tt>{@value org.glassfish.jersey.test.TestProperties#CONTAINER_FACTORY}</tt> is set |
| * and the value is a fully qualified class name of a class that extends from {@code TestContainerFactory} |
| * then the test container factory used by default will be an instance of that class. |
| * A {@link TestContainerException} will be thrown if the class cannot be loaded or instantiated. |
| * </li> |
| * <li> |
| * Otherwise, {@code META-INF/services} locations on the class path will be scanned for implementation providers |
| * of {@code TestContainerFactory} SPI. If a single implementation is found, it will be used. If multiple implementations |
| * are found, the default <tt>{@value org.glassfish.jersey.test.TestProperties#CONTAINER_FACTORY}</tt> implementation |
| * will be selected if present, otherwise the first found implementation will be selected and a warning message will be logged. |
| * </li> |
| * <li> |
| * If no {@code TestContainerFactory} has been selected in the steps above, Jersey test framework will try to |
| * instantiate the default test container factory implementation ( |
| * <tt>{@value org.glassfish.jersey.test.TestProperties#DEFAULT_CONTAINER_FACTORY}</tt>) directly. |
| * A {@link TestContainerException} will be thrown if this class cannot be loaded or instantiated. |
| * </li> |
| * </ol> |
| * </p> |
| * <p> |
| * The test container is configured by a {@link DeploymentContext} that is either provided |
| * by subclass or automatically created by {@code JerseyTest} based on the provided JAX-RS / Jersey {@code Application} |
| * class or instance to be tested. A {@link TestContainerException} will be thrown if the configured test container |
| * factory cannot support the deployment context type. |
| * Two deployment context are provided: |
| * <ol> |
| * <li>A basic deployment context, of type {@link DeploymentContext}, compatible with all test containers that are not |
| * based on Servlet deployment model.</li> |
| * <li>A Servlet-based deployment context, of type {@link ServletDeploymentContext}, compatible with Servlet-based test |
| * containers.</li> |
| * </ol> |
| * </p> |
| * |
| * @author Paul Sandoz |
| * @author Srinivas Bhimisetty |
| * @author Pavel Bucek |
| * @author Michal Gajdos |
| * @author Marek Potociar |
| */ |
| @SuppressWarnings("UnusedDeclaration") |
| public abstract class JerseyTest { |
| |
| private static final Logger LOGGER = Logger.getLogger(JerseyTest.class.getName()); |
| |
| /** |
| * Holds the test container factory class to be used for running the tests by default |
| * (if testContainerFactory has not been set). |
| * This static field is initialized in {@link #getDefaultTestContainerFactory()} method |
| * and is reused by any instances of {@code JerseyTest} that are subsequently run. |
| * This is done to optimize the number of TestContainerFactory service provider look-ups |
| * and class loading. |
| */ |
| private static Class<? extends TestContainerFactory> defaultTestContainerFactoryClass; |
| |
| /** |
| * Configured deployment context for the tested application. |
| */ |
| private final DeploymentContext context; |
| |
| /** |
| * The test container factory which creates an instance of the test container |
| * on which the tests would be run. |
| */ |
| private TestContainerFactory testContainerFactory; |
| |
| /** |
| * The test container on which the tests would be run. |
| */ |
| private TestContainer testContainer; |
| |
| private final AtomicReference<Client> client = new AtomicReference<>(null); |
| |
| /** |
| * JerseyTest property bag that can be used to configure the test behavior. |
| * These properties can be overridden with a system property. |
| */ |
| private final Map<String, String> propertyMap = new HashMap<>(); |
| |
| /** |
| * JerseyTest forced property bag that can be used to configure the test behavior. |
| * These property cannot be overridden with a system property. |
| */ |
| private final Map<String, String> forcedPropertyMap = new HashMap<>(); |
| |
| private JerseyTestLogHandler logHandler; |
| private final Map<Logger, Level> logLevelMap = new IdentityHashMap<>(); |
| |
| /** |
| * Initialize JerseyTest instance. |
| * <p> |
| * This constructor can be used from an extending subclass. |
| * <p> |
| * When this constructor is used, the extending concrete subclass must implement one of the |
| * {@link #configure()} or {@link #configureDeployment()} methods to provide the tested application |
| * configuration and deployment context. |
| * </p> |
| */ |
| public JerseyTest() { |
| // Note: this must be the first call in the constructor to allow setting config |
| // properties (especially around logging) in the configure() or configureDeployment() |
| // method overridden in subclass, otherwise the properties set in the subclass would |
| // not be set soon enough |
| this.context = configureDeployment(); |
| this.testContainerFactory = getTestContainerFactory(); |
| registerLogHandlerIfEnabled(); |
| } |
| |
| /** |
| * Initialize JerseyTest instance and specify the test container factory to be used by this test. |
| * <p> |
| * This constructor can be used from an extending subclass. |
| * <p> |
| * When this constructor is used, the extending concrete subclass must implement one of the |
| * {@link #configure()} or {@link #configureDeployment()} methods to provide the tested application |
| * configuration and deployment context. |
| * </p> |
| * |
| * @param testContainerFactory the test container factory to use for testing. |
| */ |
| public JerseyTest(final TestContainerFactory testContainerFactory) { |
| // Note: this must be the first call in the constructor to allow setting config |
| // properties (especially around logging) in the configure() or configureDeployment() |
| // method overridden in subclass, otherwise the properties set in the subclass would |
| // not be set soon enough |
| this.context = configureDeployment(); |
| this.testContainerFactory = testContainerFactory; |
| registerLogHandlerIfEnabled(); |
| } |
| |
| /** |
| * Initialize JerseyTest instance. |
| * <p> |
| * This constructor can be used from an extending subclass. |
| * <p> |
| * When this constructor is used, the extending concrete subclass must implement one of the |
| * {@link #configure()} or {@link #configureDeployment()} methods are ignored. |
| * </p> |
| * <p> |
| * Please note that when this constructor is used, recording of startup logs as well as configuring |
| * other {@code JerseyTest} properties and features may not work properly. While using this constructor |
| * should generally be avoided, in certain scenarios it may be necessary to use this constructor. |
| * (E.g. when running parameterized tests in which application is created based on test parameters |
| * passed in by JUnit framework via test constructor - in such case it is not possible to propagate |
| * the necessary information to one of the overridden {@code JerseyTest.configure...} methods). |
| * </p> |
| * |
| * @param jaxrsApplication tested application. |
| */ |
| public JerseyTest(final Application jaxrsApplication) { |
| this.context = DeploymentContext.newInstance(jaxrsApplication); |
| this.testContainerFactory = getTestContainerFactory(); |
| registerLogHandlerIfEnabled(); |
| } |
| |
| /** |
| * Return currently used test container to run the tests in. This method can be overridden. |
| * |
| * @return a test container instance or {@code null} if the container is not set. |
| */ |
| /* package */ TestContainer getTestContainer() { |
| return testContainer; |
| } |
| |
| /** |
| * Returns old test container used to run the tests in and set a new one. This method can be overridden. |
| * |
| * @param testContainer a test container instance or {@code null} it the current test container should be released. |
| * @return old test container instance. |
| */ |
| /* package */ TestContainer setTestContainer(final TestContainer testContainer) { |
| final TestContainer old = this.testContainer; |
| this.testContainer = testContainer; |
| return old; |
| } |
| |
| private TestContainer createTestContainer(final DeploymentContext context) { |
| return getTestContainerFactory().create(getBaseUri(), context); |
| } |
| |
| /** |
| * Programmatically enable a feature with a given name. |
| * Enabling of the feature may be overridden via a system property. |
| * |
| * @param featureName name of the enabled feature. |
| */ |
| protected final void enable(final String featureName) { |
| // TODO: perhaps we could reuse the resource config for the test properties? |
| propertyMap.put(featureName, Boolean.TRUE.toString()); |
| } |
| |
| /** |
| * Programmatically disable a feature with a given name. |
| * Disabling of the feature may be overridden via a system property. |
| * |
| * @param featureName name of the disabled feature. |
| */ |
| protected final void disable(final String featureName) { |
| propertyMap.put(featureName, Boolean.FALSE.toString()); |
| } |
| |
| /** |
| * Programmatically force-enable a feature with a given name. |
| * Force-enabling of the feature cannot be overridden via a system property. |
| * Use with care! |
| * |
| * @param featureName name of the force-enabled feature. |
| */ |
| protected final void forceEnable(final String featureName) { |
| forcedPropertyMap.put(featureName, Boolean.TRUE.toString()); |
| } |
| |
| /** |
| * Programmatically force-disable a feature with a given name. |
| * Force-disabling of the feature cannot be overridden via a system property. |
| * Use with care! |
| * |
| * @param featureName name of the force-disabled feature. |
| */ |
| protected final void forceDisable(final String featureName) { |
| forcedPropertyMap.put(featureName, Boolean.FALSE.toString()); |
| } |
| |
| /** |
| * Programmatically set a value of a property with a given name. |
| * The property value may be overridden via a system property. |
| * |
| * @param propertyName name of the property. |
| * @param value property value. |
| */ |
| protected final void set(final String propertyName, final Object value) { |
| set(propertyName, value.toString()); |
| } |
| |
| /** |
| * Programmatically set a value of a property with a given name. |
| * The property value may be overridden via a system property. |
| * |
| * @param propertyName name of the property. |
| * @param value property value. |
| */ |
| protected final void set(final String propertyName, final String value) { |
| propertyMap.put(propertyName, value); |
| } |
| |
| /** |
| * Programmatically force-set a value of a property with a given name. |
| * The force-set property value cannot be overridden via a system property. |
| * |
| * @param propertyName name of the property. |
| * @param value property value. |
| */ |
| protected final void forceSet(final String propertyName, final String value) { |
| forcedPropertyMap.put(propertyName, value); |
| } |
| |
| /** |
| * Check if the Jersey test boolean property (flag) has been set to {@code true}. |
| * |
| * @param propertyName name of the Jersey test boolean property. |
| * @return {@code true} if the test property has been enabled, {@code false} otherwise. |
| */ |
| protected final boolean isEnabled(final String propertyName) { |
| return Boolean.valueOf(getProperty(propertyName)); |
| } |
| |
| private String getProperty(final String propertyName) { |
| if (forcedPropertyMap.containsKey(propertyName)) { |
| return forcedPropertyMap.get(propertyName); |
| } |
| |
| final Properties systemProperties = AccessController.doPrivileged(PropertiesHelper.getSystemProperties()); |
| if (systemProperties.containsKey(propertyName)) { |
| return systemProperties.getProperty(propertyName); |
| } |
| |
| if (propertyMap.containsKey(propertyName)) { |
| return propertyMap.get(propertyName); |
| } |
| |
| return null; |
| } |
| |
| private static String getSystemProperty(final String propertyName) { |
| final Properties systemProperties = AccessController.doPrivileged(PropertiesHelper.getSystemProperties()); |
| return systemProperties.getProperty(propertyName); |
| } |
| |
| /** |
| * Create the tested JAX-RS /Jersey application. |
| * <p> |
| * This method may be overridden by subclasses to provide the configured JAX-RS /Jersey application to be tested. |
| * The method may be also used to configure {@code JerseyTest} instance properties. |
| * <p> |
| * Unless {@link #configureDeployment()} method is overridden in the subclass, the {@code configure()} method is invoked |
| * by {@code configureDeployment()} to create default deployment context for the tested application. As such, the method |
| * is invoked in the scope of one of the {@code JerseyTest} constructors. |
| * Default implementation of this method throws {@link UnsupportedOperationException}, so that construction of |
| * {@code JerseyTest} instance fails unless one of the {@code configure()} or {@code configureDeployment()} methods is |
| * overridden in the subclass. |
| * </p> |
| * <p> |
| * Note that since the method is invoked from {@code JerseyTest} constructor, the overriding implementation of the method |
| * must not depend on any subclass fields as those will not be initialized yet when the method is invoked. |
| * </p> |
| * <p> |
| * Also note that in case the {@link #JerseyTest(javax.ws.rs.core.Application)} constructor is used, the method is never |
| * invoked. |
| * </p> |
| * |
| * @return tested JAX-RS /Jersey application. |
| */ |
| protected Application configure() { |
| throw new UnsupportedOperationException("The configure method must be implemented by the extending class"); |
| } |
| |
| /** |
| * Create and configure deployment context for the tested application. |
| * <p> |
| * This method may be overridden by subclasses to provide custom test container deployment context for the tested |
| * application. The method may be also used to configure {@code JerseyTest} instance properties. |
| * <p> |
| * The method is invoked from {@code JerseyTest} constructors to provide deployment context for the tested application. |
| * Default implementation of this method creates |
| * {@link DeploymentContext#newInstance(javax.ws.rs.core.Application) new deployment context} |
| * using JAX-RS application instance obtained by calling the {@link #configure()} method. |
| * </p> |
| * <p> |
| * Note that since the method is invoked from {@code JerseyTest} constructor, the overriding implementation of the method |
| * must not depend on any subclass fields as those will not be initialized yet when the method is invoked. |
| * </p> |
| * <p> |
| * Also note that in case the {@link #JerseyTest(javax.ws.rs.core.Application)} constructor is used, the method is never |
| * invoked. |
| * </p> |
| * |
| * @return configured deployment context for the tested application. |
| * @since 2.8 |
| */ |
| protected DeploymentContext configureDeployment() { |
| DeploymentContext.Builder contextBuilder = DeploymentContext.builder(configure()); |
| if (getSslContext().isPresent() && getSslParameters().isPresent()) { |
| contextBuilder.ssl(getSslContext().get(), getSslParameters().get()); |
| } |
| return contextBuilder.build(); |
| } |
| |
| /** |
| * Return an instance of {@link TestContainerFactory} class. |
| * <p> |
| * <p> |
| * This method is used only once during {@code JerseyTest} instance construction to retrieve the factory responsible |
| * for providing {@link org.glassfish.jersey.test.spi.TestContainer} that will be used to deploy the tested application. |
| * </p> |
| * <p> |
| * A default implementation first searches for the {@code TestContainerFactory} set via |
| * {@link #JerseyTest(org.glassfish.jersey.test.spi.TestContainerFactory) constructor}, then it looks for a |
| * {@code TestContainerFactory} implementation class name set via |
| * <tt>{@value org.glassfish.jersey.test.TestProperties#CONTAINER_FACTORY}</tt> system property with a fallback to |
| * searching for {@code TestContainerFactory} service providers on the class path. At last, if no |
| * {@code TestContainerFactory} has been found, the method attempts to create new default |
| * {@code TestContainerFactory} implementation instance |
| * (<tt>{@value org.glassfish.jersey.test.TestProperties#DEFAULT_CONTAINER_FACTORY}</tt>). |
| * </p> |
| * <p> |
| * Alternatively, this method may be overridden to directly provide a custom {@code TestContainerFactory} instance. |
| * Note that since the method is invoked from {@code JerseyTest} constructor, the overriding implementation of the method |
| * must not depend on any subclass fields as those will not be initialized yet when the method is invoked. |
| * </p> |
| * |
| * @return an instance of {@link TestContainerFactory} class. |
| * @throws TestContainerException if the initialization of {@link TestContainerFactory} instance is not successful. |
| */ |
| protected TestContainerFactory getTestContainerFactory() throws TestContainerException { |
| if (testContainerFactory == null) { |
| testContainerFactory = getDefaultTestContainerFactory(); |
| } |
| return testContainerFactory; |
| } |
| |
| /** |
| * Return an optional instance of {@link SSLContext} class. |
| * <p> |
| * <p> |
| * This method is used only once during {@code JerseyTest} instance construction to retrieve the ssl configuration. |
| * By default the ssl configuration is absent, to enable it please override this method and {@link JerseyTest#getSslParameters()} |
| * </p> |
| * </p> |
| * @return an optional instance of {@link SSLContext} class. |
| */ |
| protected Optional<SSLContext> getSslContext() { |
| return Optional.empty(); |
| } |
| |
| /** |
| * Return an optional instance of {@link SSLParameters} class. |
| * <p> |
| * <p> |
| * This method is used only once during {@code JerseyTest} instance construction to retrieve the ssl configuration. |
| * By default the ssl configuration is absent, to enable it please override this method and {@link JerseyTest#getSslContext()} ()} |
| * </p> |
| * </p> |
| * @return an optional instance of {@link SSLContext} class. |
| */ |
| protected Optional<SSLParameters> getSslParameters() { |
| return Optional.empty(); |
| } |
| |
| private static synchronized TestContainerFactory getDefaultTestContainerFactory() { |
| |
| if (defaultTestContainerFactoryClass == null) { |
| final String factoryClassName = getSystemProperty(TestProperties.CONTAINER_FACTORY); |
| if (factoryClassName != null) { |
| LOGGER.log(Level.CONFIG, |
| "Loading test container factory '{0}' specified in the '{1}' system property.", |
| new Object[] {factoryClassName, TestProperties.CONTAINER_FACTORY}); |
| |
| defaultTestContainerFactoryClass = loadFactoryClass(factoryClassName); |
| } else { |
| final TestContainerFactory[] factories = ServiceFinder.find(TestContainerFactory.class).toArray(); |
| if (factories.length > 0) { |
| // if there is only one factory instance, just return it |
| if (factories.length == 1) { |
| // cache the class for future reuse |
| defaultTestContainerFactoryClass = factories[0].getClass(); |
| LOGGER.log( |
| Level.CONFIG, |
| "Using the single found TestContainerFactory service provider '{0}'", |
| defaultTestContainerFactoryClass.getName()); |
| return factories[0]; |
| } |
| |
| // if default factory is present, use it. |
| for (final TestContainerFactory tcf : factories) { |
| if (TestProperties.DEFAULT_CONTAINER_FACTORY.equals(tcf.getClass().getName())) { |
| // cache the class for future reuse |
| defaultTestContainerFactoryClass = tcf.getClass(); |
| LOGGER.log( |
| Level.CONFIG, |
| "Found multiple TestContainerFactory service providers, using the default found '{0}'", |
| TestProperties.DEFAULT_CONTAINER_FACTORY); |
| return tcf; |
| } |
| } |
| |
| // default factory is not in the list - log warning and return the first found factory instance |
| // cache the class for future reuse |
| defaultTestContainerFactoryClass = factories[0].getClass(); |
| LOGGER.log( |
| Level.WARNING, |
| "Found multiple TestContainerFactory service providers, using the first found '{0}'", |
| defaultTestContainerFactoryClass.getName()); |
| return factories[0]; |
| } |
| |
| LOGGER.log( |
| Level.CONFIG, |
| "No TestContainerFactory configured, trying to load and instantiate the default implementation '{0}'", |
| TestProperties.DEFAULT_CONTAINER_FACTORY); |
| defaultTestContainerFactoryClass = loadFactoryClass(TestProperties.DEFAULT_CONTAINER_FACTORY); |
| } |
| } |
| |
| try { |
| return defaultTestContainerFactoryClass.newInstance(); |
| } catch (final Exception ex) { |
| throw new TestContainerException(String.format( |
| "Could not instantiate test container factory '%s'", defaultTestContainerFactoryClass.getName()), ex); |
| } |
| } |
| |
| private static Class<? extends TestContainerFactory> loadFactoryClass(final String factoryClassName) { |
| Class<? extends TestContainerFactory> factoryClass; |
| final Class<Object> loadedClass = AccessController.doPrivileged(ReflectionHelper.classForNamePA(factoryClassName, null)); |
| if (loadedClass == null) { |
| throw new TestContainerException(String.format( |
| "Test container factory class '%s' cannot be loaded", factoryClassName)); |
| } |
| try { |
| return loadedClass.asSubclass(TestContainerFactory.class); |
| } catch (final ClassCastException ex) { |
| throw new TestContainerException(String.format( |
| "Class '%s' does not implement TestContainerFactory SPI.", factoryClassName), ex); |
| } |
| } |
| |
| /** |
| * Create a JAX-RS web target whose URI refers to the {@link #getBaseUri() base URI} the tested |
| * JAX-RS / Jersey application is deployed at, plus the path specified in the {@code path} argument. |
| * <p> |
| * This method is an equivalent of calling <tt>client().target(getBaseUri())</tt>. |
| * </p> |
| * |
| * @return the created JAX-RS web target. |
| */ |
| public final WebTarget target() { |
| return client().target(getTestContainer().getBaseUri()); |
| } |
| |
| /** |
| * Create a JAX-RS web target whose URI refers to the {@link #getBaseUri() base URI} the tested |
| * JAX-RS / Jersey application is deployed at, plus the path specified in the {@code path} argument. |
| * <p> |
| * This method is an equivalent of calling {@code target().path(path)}. |
| * </p> |
| * |
| * @param path relative path (from tested application base URI) this web target should point to. |
| * @return the created JAX-RS web target. |
| */ |
| public final WebTarget target(final String path) { |
| return target().path(path); |
| } |
| |
| /** |
| * Get the JAX-RS test client that is {@link #configureClient(org.glassfish.jersey.client.ClientConfig) pre-configured} |
| * for this test. |
| * |
| * @return the configured test client. |
| */ |
| public final Client client() { |
| return getClient(); |
| } |
| |
| /** |
| * Set up the test by creating a test container instance, {@link TestContainer#start() starting} it and by creating a new |
| * {@link #configureClient(org.glassfish.jersey.client.ClientConfig) pre-configured} test client. |
| * The test container is obtained from the {@link #getTestContainerFactory() test container factory}. |
| * |
| * @throws TestContainerException if the default test container factory cannot be obtained, |
| * or the test application deployment context is not supported |
| * by the test container factory. |
| * @throws Exception if an exception is thrown during setting up the test environment. |
| */ |
| @Before |
| public void setUp() throws Exception { |
| final TestContainer testContainer = createTestContainer(context); |
| |
| // Set current instance of test container and start it. |
| setTestContainer(testContainer); |
| testContainer.start(); |
| |
| // Create an set new client. |
| setClient(getClient(testContainer.getClientConfig())); |
| } |
| |
| /** |
| * Tear down the test by {@link TestContainer#stop() stopping} the test container obtained from the |
| * {@link #getTestContainerFactory() test container factory} and by {@link javax.ws.rs.client.Client#close() closing} |
| * and discarding the {@link #configureClient(org.glassfish.jersey.client.ClientConfig) pre-configured} test client |
| * that was {@link #setUp() set up} for the test. |
| * |
| * @throws Exception if an exception is thrown during tearing down the test environment. |
| */ |
| @After |
| public void tearDown() throws Exception { |
| if (isLogRecordingEnabled()) { |
| unregisterLogHandler(); |
| } |
| |
| try { |
| TestContainer oldContainer = setTestContainer(null); |
| if (oldContainer != null) { |
| oldContainer.stop(); |
| } |
| } finally { |
| closeIfNotNull(setClient(null)); |
| } |
| } |
| |
| /** |
| * Get the JAX-RS test client that is {@link #configureClient(org.glassfish.jersey.client.ClientConfig) pre-configured} |
| * for this test. This method can be overridden. |
| * |
| * @return the configured test client. |
| */ |
| protected Client getClient() { |
| return client.get(); |
| } |
| |
| /** |
| * Get the old JAX-RS test client and set a new one. This method can be overridden. |
| * |
| * @param client the configured test client. |
| * @return old configured test client. |
| */ |
| protected Client setClient(final Client client) { |
| return this.client.getAndSet(client); |
| } |
| |
| /** |
| * Create an instance of test {@link Client} using the client configuration provided by the configured |
| * {@link org.glassfish.jersey.test.spi.TestContainer}. |
| * <p> |
| * If the {@code TestContainer} does not provide any client configuration (passed {@code clientConfig} is {@code null}), |
| * the default implementation of this method first creates an empty new {@link org.glassfish.jersey.client.ClientConfig} |
| * instance. The client configuration (provided by test container or created) is then passed to |
| * {@link #configureClient(org.glassfish.jersey.client.ClientConfig)} which can be overridden in the {@code JerseyTest} |
| * subclass to provide custom client configuration. At last, new JAX-RS {@link Client} instance is created based on the |
| * resulting client configuration. |
| * </p> |
| * |
| * @param clientConfig test client default configuration. May be {@code null}. |
| * @return A Client instance. |
| */ |
| private Client getClient(ClientConfig clientConfig) { |
| if (clientConfig == null) { |
| clientConfig = new ClientConfig(); |
| } |
| |
| //check if logging is required |
| if (isEnabled(TestProperties.LOG_TRAFFIC)) { |
| clientConfig.register(new LoggingFeature(LOGGER, isEnabled(TestProperties.DUMP_ENTITY) |
| ? LoggingFeature.Verbosity.PAYLOAD_ANY |
| : LoggingFeature.Verbosity.HEADERS_ONLY)); |
| |
| } |
| |
| configureClient(clientConfig); |
| |
| return ClientBuilder.newClient(clientConfig); |
| } |
| |
| /** |
| * Configure the test client. |
| * <p> |
| * The method can be overridden by {@code JerseyTest} subclasses to conveniently configure the test client instance |
| * used by Jersey test framework (either returned from {@link #client()} method or used to create |
| * {@link javax.ws.rs.client.WebTarget} instances returned from one of the {@code target} methods |
| * ({@link #target()} or {@link #target(String)}). |
| * <p> |
| * Prior to every test method run, a new client instance is configured and created using the client configuration |
| * provided by the {@link org.glassfish.jersey.test.spi.TestContainer} as well as any internal {@code JerseyTest} |
| * client configuration settings. |
| * </p> |
| * <p> |
| * Before the actual client instance creation, Jersey test framework invokes this method in order to allow the subclasses |
| * to further customize created client instance. |
| * </p> |
| * <p> |
| * After each test method is run, the existing client instance is {@link javax.ws.rs.client.Client#close() closed} |
| * and discarded. |
| * </p> |
| * <p> |
| * Default implementation of the method is "no-op". |
| * </p> |
| * |
| * @param config Jersey test client configuration that can be modified before the client is created. |
| */ |
| protected void configureClient(final ClientConfig config) { |
| // do nothing |
| } |
| |
| /** |
| * Returns the base URI of the tested application. |
| * |
| * @return the base URI of the tested application. |
| */ |
| // TODO make final |
| protected URI getBaseUri() { |
| final TestContainer container = getTestContainer(); |
| |
| if (container != null) { |
| // called from outside of JerseyTest constructor |
| return container.getBaseUri(); |
| } |
| |
| // called from within JerseyTest constructor |
| return UriBuilder.fromUri("http://localhost/").port(getPort()).build(); |
| } |
| |
| /** |
| * Get the port to be used for test application deployments. |
| * |
| * @return The HTTP port of the URI |
| */ |
| protected final int getPort() { |
| final TestContainer container = getTestContainer(); |
| |
| if (container != null) { |
| // called from outside of JerseyTest constructor |
| return container.getBaseUri().getPort(); |
| } |
| |
| // called from within JerseyTest constructor |
| final String value = getProperty(TestProperties.CONTAINER_PORT); |
| if (value != null) { |
| |
| try { |
| final int i = Integer.parseInt(value); |
| if (i < 0) { |
| throw new NumberFormatException("Value not positive."); |
| } |
| return i; |
| } catch (final NumberFormatException e) { |
| LOGGER.log(Level.CONFIG, |
| "Value of " + TestProperties.CONTAINER_PORT |
| + " property is not a valid positive integer [" + value + "]." |
| + " Reverting to default [" + TestProperties.DEFAULT_CONTAINER_PORT + "].", |
| e |
| ); |
| } |
| } |
| return TestProperties.DEFAULT_CONTAINER_PORT; |
| } |
| |
| /** |
| * Get stored {@link LogRecord log records} if enabled by setting {@link TestProperties#RECORD_LOG_LEVEL} or an empty list. |
| * |
| * @return list of log records or an empty list. |
| */ |
| protected final List<LogRecord> getLoggedRecords() { |
| return getLogHandler().getRecords(); |
| } |
| |
| /** |
| * Get last stored {@link LogRecord log record} if enabled by setting {@link TestProperties#RECORD_LOG_LEVEL} |
| * or {@code null}. |
| * |
| * @return last stored {@link LogRecord log record} or {@code null}. |
| */ |
| protected final LogRecord getLastLoggedRecord() { |
| final List<LogRecord> loggedRecords = getLoggedRecords(); |
| return loggedRecords.isEmpty() ? null : loggedRecords.get(loggedRecords.size() - 1); |
| } |
| |
| /** |
| * Retrieves a list of root loggers. |
| * |
| * @return list of root loggers. |
| */ |
| private Set<Logger> getRootLoggers() { |
| final LogManager logManager = LogManager.getLogManager(); |
| final Enumeration<String> loggerNames = logManager.getLoggerNames(); |
| |
| final Set<Logger> rootLoggers = new HashSet<>(); |
| |
| while (loggerNames.hasMoreElements()) { |
| Logger logger = logManager.getLogger(loggerNames.nextElement()); |
| if (logger != null) { |
| while (logger.getParent() != null) { |
| logger = logger.getParent(); |
| } |
| rootLoggers.add(logger); |
| } |
| } |
| |
| return rootLoggers; |
| } |
| |
| /** |
| * Register {@link Handler log handler} to the list of root loggers |
| * if log recording is enabled. |
| */ |
| private void registerLogHandlerIfEnabled() { |
| if (isLogRecordingEnabled()) { |
| registerLogHandler(); |
| } |
| } |
| |
| /** |
| * Register {@link Handler log handler} to the list of root loggers. |
| */ |
| private void registerLogHandler() { |
| final String recordLogLevel = getProperty(TestProperties.RECORD_LOG_LEVEL); |
| final int recordLogLevelInt = Integer.valueOf(recordLogLevel); |
| final Level level = Level.parse(recordLogLevel); |
| |
| logLevelMap.clear(); |
| |
| for (final Logger root : getRootLoggers()) { |
| logLevelMap.put(root, root.getLevel()); |
| |
| if (root.getLevel().intValue() > recordLogLevelInt) { |
| root.setLevel(level); |
| } |
| |
| root.addHandler(getLogHandler()); |
| } |
| } |
| |
| /** |
| * Un-register {@link Handler log handler} from the list of root loggers. |
| */ |
| private void unregisterLogHandler() { |
| for (final Logger root : getRootLoggers()) { |
| root.setLevel(logLevelMap.get(root)); |
| root.removeHandler(getLogHandler()); |
| } |
| logHandler = null; |
| } |
| |
| /** |
| * Return {@code true} if log recoding is enabled. |
| * |
| * @return {@code true} if log recoding is enabled, {@code false} otherwise. |
| */ |
| private boolean isLogRecordingEnabled() { |
| return getProperty(TestProperties.RECORD_LOG_LEVEL) != null; |
| } |
| |
| /** |
| * Retrieves {@link Handler log handler} capable of storing {@link LogRecord logged records}. |
| * |
| * @return log handler. |
| */ |
| private JerseyTestLogHandler getLogHandler() { |
| if (logHandler == null) { |
| logHandler = new JerseyTestLogHandler(); |
| } |
| return logHandler; |
| } |
| |
| /** |
| * Returns {@link TestProperties#ASYNC_TIMEOUT_MULTIPLIER} or {@code 1} if the property is not defined. |
| * |
| * @return Multiplier of the async timeout for async test. |
| */ |
| protected int getAsyncTimeoutMultiplier() { |
| final String property = getProperty(TestProperties.ASYNC_TIMEOUT_MULTIPLIER); |
| Integer multi = 1; |
| if (property != null) { |
| multi = Integer.valueOf(property); |
| if (multi <= 0) { |
| throw new NumberFormatException( |
| "Property " + TestProperties.ASYNC_TIMEOUT_MULTIPLIER + " must be a number greater than 0."); |
| } |
| } |
| return multi; |
| |
| } |
| |
| /** |
| * Utility method that safely closes a response without throwing an exception. |
| * |
| * @param responses responses to close. Each response may be {@code null}. |
| * @since 2.5 |
| */ |
| public final void close(final Response... responses) { |
| if (responses == null || responses.length == 0) { |
| return; |
| } |
| |
| for (final Response response : responses) { |
| if (response == null) { |
| continue; |
| } |
| try { |
| response.close(); |
| } catch (final Throwable t) { |
| LOGGER.log(Level.WARNING, "Error closing a response.", t); |
| } |
| } |
| } |
| |
| /** |
| * Utility method that safely closes a client instance without throwing an exception. |
| * |
| * @param clients client instances to close. Each instance may be {@code null}. |
| * @since 2.5 |
| */ |
| public static void closeIfNotNull(final Client... clients) { |
| if (clients == null || clients.length == 0) { |
| return; |
| } |
| |
| for (final Client c : clients) { |
| if (c == null) { |
| continue; |
| } |
| try { |
| c.close(); |
| } catch (final Throwable t) { |
| LOGGER.log(Level.WARNING, "Error closing a client instance.", t); |
| } |
| |
| } |
| } |
| |
| /** |
| * Custom logging handler used to store log records produces during an invocation of a test. |
| */ |
| private class JerseyTestLogHandler extends Handler { |
| |
| private final int logLevel; |
| private final List<LogRecord> records; |
| |
| private JerseyTestLogHandler() { |
| this.logLevel = Integer.parseInt(getProperty(TestProperties.RECORD_LOG_LEVEL)); |
| this.records = new ArrayList<>(); |
| } |
| |
| @Override |
| public void publish(final LogRecord record) { |
| final String loggerName = record.getLoggerName(); |
| |
| if (record.getLevel().intValue() >= logLevel |
| && loggerName.startsWith("org.glassfish.jersey") |
| && !loggerName.startsWith("org.glassfish.jersey.test")) { |
| records.add(record); |
| } |
| } |
| |
| @Override |
| public void flush() { |
| } |
| |
| @Override |
| public void close() throws SecurityException { |
| } |
| |
| public List<LogRecord> getRecords() { |
| return records; |
| } |
| } |
| } |