/**
 * The MIT License (MIT)
 *
 * Copyright (c) 2014-2019 <mickael.jeanroy@gmail.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package com.github.mjeanroy.junit.servers.jetty;

import com.github.mjeanroy.junit.servers.commons.CompositeClassLoader;
import com.github.mjeanroy.junit.servers.commons.JavaUtils;
import com.github.mjeanroy.junit.servers.exceptions.ServerInitializationException;
import com.github.mjeanroy.junit.servers.exceptions.ServerStartException;
import com.github.mjeanroy.junit.servers.exceptions.ServerStopException;
import com.github.mjeanroy.junit.servers.servers.AbstractEmbeddedServer;
import org.eclipse.jetty.annotations.AnnotationConfiguration;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.util.resource.PathResource;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.webapp.Configuration;
import org.eclipse.jetty.webapp.FragmentConfiguration;
import org.eclipse.jetty.webapp.JettyWebXmlConfiguration;
import org.eclipse.jetty.webapp.MetaInfConfiguration;
import org.eclipse.jetty.webapp.WebAppContext;
import org.eclipse.jetty.webapp.WebInfConfiguration;
import org.eclipse.jetty.webapp.WebXmlConfiguration;

import javax.servlet.ServletContext;
import java.io.File;

import static com.github.mjeanroy.junit.servers.commons.Strings.isNotBlank;
import static com.github.mjeanroy.junit.servers.jetty.EmbeddedJettyConfiguration.defaultConfiguration;
import static org.eclipse.jetty.util.resource.Resource.newResource;

/**
 * Jetty Embedded Server.mv
 */
public class EmbeddedJetty extends AbstractEmbeddedServer<Server, EmbeddedJettyConfiguration> {

	/**
	 * Instance of Jetty Server.
	 */
	private final Server server;

	/**
	 * Jetty Web App Context.
	 */
	private volatile WebAppContext webAppContext;

	/**
	 * Server Connector, lazily initialized.
	 */
	private volatile ServerConnector connector;

	/**
	 * Build default embedded jetty server.
	 */
	public EmbeddedJetty() {
		this(defaultConfiguration());
	}

	/**
	 * Build embedded jetty server.
	 *
	 * @param configuration Server configuration.
	 */
	public EmbeddedJetty(EmbeddedJettyConfiguration configuration) {
		super(configuration);
		this.server = initServer();
	}

	private Server initServer() {
		Server server = new Server(configuration.getPort());
		server.setStopAtShutdown(configuration.isStopAtShutdown());
		server.setStopTimeout(configuration.getStopTimeout());
		return server;
	}

	private WebAppContext initContext() {
		try {
			return createdWebAppContext();
		}
		catch (Exception ex) {
			throw new ServerInitializationException(ex);
		}
	}

	@Override
	public Server getDelegate() {
		return server;
	}

	@Override
	protected void doStart() {
		try {
			webAppContext = initContext();
			server.start();
			connector = findConnector();
		}
		catch (Exception ex) {
			throw new ServerStartException(ex);
		}
	}

	/**
	 * Build web app context used to launch server.
	 * May be override by subclasses.
	 *
	 * @throws Exception May be thrown by web app context initialization (will be wrapped later).
	 */
	private WebAppContext createdWebAppContext() throws Exception {
		final String path = configuration.getPath();
		final String webapp = configuration.getWebapp();
		final String classpath = configuration.getClasspath();
		final ClassLoader parentClassLoader = configuration.getParentClassLoader();
		final String overrideDescriptor = configuration.getOverrideDescriptor();
		final Resource baseResource = configuration.getBaseResource();
		final String containerJarPattern = configuration.getContainerJarPattern();
		final String webInfJarPattern = configuration.getWebInfJarPattern();

		WebAppContext ctx = new WebAppContext();

		if (containerJarPattern != null) {
			ctx.setAttribute(WebInfConfiguration.CONTAINER_JAR_PATTERN, containerJarPattern);
		}
		else if (JavaUtils.isPostJdk9()) {
			// Fix to make TLD scanning works with Java >= 9
			ctx.setAttribute(WebInfConfiguration.CONTAINER_JAR_PATTERN, ".*\\.jar");
		}

		if (webInfJarPattern != null) {
			ctx.setAttribute(WebInfConfiguration.WEBINF_JAR_PATTERN, webInfJarPattern);
		}

		final ClassLoader systemClassLoader = Thread.currentThread().getContextClassLoader();
		final ClassLoader classLoader;

		if (parentClassLoader != null) {
			classLoader = new CompositeClassLoader(parentClassLoader, systemClassLoader);
		}
		else {
			classLoader = systemClassLoader;
		}

		ctx.setClassLoader(classLoader);
		ctx.setContextPath(path);

		if (baseResource == null) {
			// use default base resource
			ctx.setBaseResource(newResource(webapp));
		}
		else {
			ctx.setBaseResource(baseResource);
		}

		if (overrideDescriptor != null) {
			ctx.setOverrideDescriptor(overrideDescriptor);
		}

		ctx.setConfigurations(new Configuration[] {
			new WebInfConfiguration(),
			new WebXmlConfiguration(),
			new AnnotationConfiguration(),
			new JettyWebXmlConfiguration(),
			new MetaInfConfiguration(),
			new FragmentConfiguration()
		});

		if (isNotBlank(classpath)) {
			// Fix to scan Spring WebApplicationInitializer
			// This will add compiled classes to jetty classpath
			// See: http://stackoverflow.com/questions/13222071/spring-3-1-webapplicationinitializer-embedded-jetty-8-annotationconfiguration
			// And more precisely: http://stackoverflow.com/a/18449506/1215828
			File classes = new File(classpath);
			PathResource containerResources = new PathResource(classes.toURI());
			ctx.getMetaData().addContainerResource(containerResources);
		}

		ctx.setParentLoaderPriority(true);
		ctx.setWar(webapp);
		ctx.setServer(server);

		// Add server context
		server.setHandler(ctx);

		return ctx;
	}

	@Override
	protected void doStop() {
		try {
			server.stop();
			webAppContext = null;
			connector = null;
		}
		catch (Exception ex) {
			throw new ServerStopException(ex);
		}
	}

	@Override
	public String getScheme() {
		return isStarted() ? server.getURI().getScheme() : super.getScheme();
	}

	@Override
	public ServletContext getServletContext() {
		return webAppContext == null ? null : webAppContext.getServletContext();
	}

	@Override
	protected int doGetPort() {
		return connector.getLocalPort();
	}

	private ServerConnector findConnector() {
		for (Connector connector : server.getConnectors()) {
			if (connector instanceof ServerConnector) {
				return (ServerConnector) connector;
			}
		}
		return null;
	}
}


/**
 * The MIT License (MIT)
 *
 * Copyright (c) 2014-2019 <mickael.jeanroy@gmail.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package com.github.mjeanroy.junit.servers.servers;

import java.util.LinkedHashMap;
import java.util.Map;

import static com.github.mjeanroy.junit.servers.commons.Preconditions.notNull;
import static com.github.mjeanroy.junit.servers.commons.UrlUtils.ensureAbsolutePath;
import static java.lang.System.clearProperty;
import static java.lang.System.getProperty;
import static java.lang.System.setProperty;

/**
 * Partial implementation of an embedded server.
 *
 *

 *
 * Subclasses should implement {@link #doStart()} and {@link #doStop()} methods and synchronization is already
 * managed by this abstract implementation.
 */
public abstract class AbstractEmbeddedServer<S, T extends AbstractConfiguration> implements EmbeddedServer<T> {

	/**
	 * The default scheme returned by {@link AbstractEmbeddedServer#getScheme()}.
	 */
	private static final String DEFAULT_SCHEME = "http";

	/**
	 * The default host returned by {@link AbstractEmbeddedServer#getHost()}.
	 */
	private static final String DEFAULT_HOST = "localhost";
	public static final String SCHEME_SEPARATOR = "://";
	public static final String PORT_SEPARATOR = ":";

	/**
	 * Server configuration.
	 */
	protected final T configuration;

	/**
	 * Flag to keep server status:
	 *

	 *
Server can be started if and only if status is equal to {@link ServerStatus#STOPPED}.

	 *
Server can be stopped if and only if status is equal to {@link ServerStatus#STARTED}.

	 *

	 */
	private volatile ServerStatus status;

	/**
	 * Old properties used to restore initial environment properties values when server stops.
	 * It can be used to set a spring profile property or anything else.
	 */
	private final Map<String, String> oldProperties;

	// Lock used to synchronize start and stop tasks
	private static final Object lock = new Object();

	/**
	 * Build default embedded server.
	 *
	 * @param configuration Server configuration.
	 */
	protected AbstractEmbeddedServer(T configuration) {
		this.configuration = notNull(configuration, "configuration");
		this.status = ServerStatus.STOPPED;
		this.oldProperties = new LinkedHashMap<>();
	}

	@Override
	public void start() {
		if (status != ServerStatus.STARTED) {
			synchronized (lock) {
				if (status != ServerStatus.STARTED) {
					status = ServerStatus.STARTING;
					initEnvironment();
					execHooks(true);
					doStart();
					status = ServerStatus.STARTED;

					// Server is fully initialized
					onStarted();
				}
			}
		}
	}

	@Override
	public void stop() {
		if (status != ServerStatus.STOPPED) {
			synchronized (lock) {
				if (status != ServerStatus.STOPPED) {
					status = ServerStatus.STOPPING;
					execHooks(false);
					doStop();
					destroyEnvironment();
					status = ServerStatus.STOPPED;
				}
			}
		}
	}

	@Override
	public boolean isStarted() {
		return status == ServerStatus.STARTED;
	}

	@Override
	public void restart() {
		stop();
		start();
	}

	@Override
	public int getPort() {
		return isStarted() ? doGetPort() : configuration.getPort();
	}

	@Override
	public String getPath() {
		return configuration.getPath();
	}

	/**
	 * Add custom environment properties.
	 * Initial property value will be store in {@link #oldProperties} map
	 * and will be restore later.
	 */
	private void initEnvironment() {
		for (Map.Entry<String, String> property : configuration.getEnvProperties().entrySet()) {
			String name = property.getKey();
			String newValue = property.getValue();

			String oldValue = getProperty(property.getKey());
			oldProperties.put(name, oldValue);

			setProperty(name, newValue);
		}
	}

	/**
	 * Reset custom environment properties.
	 * Initial values stored in {@link #oldProperties} will be restored
	 * or clear.
	 */
	private void destroyEnvironment() {
		for (Map.Entry<String, String> property : configuration.getEnvProperties().entrySet()) {
			String name = property.getKey();

			String oldValue = oldProperties.get(name);
			oldProperties.remove(name);

			if (oldValue == null) {
				clearProperty(name);
			}
			else {
				setProperty(name, oldValue);
			}
		}
	}

	/**
	 * Exec hooks phase.
	 *
	 * @param pre Phase to execute (true => pre ; false => post).
	 */
	private void execHooks(boolean pre) {
		for (Hook hook : configuration.getHooks()) {
			if (pre) {
				hook.pre(this);
			}
			else {
				hook.post(this);
			}
		}
	}

	private void onStarted() {
		for (Hook hook : configuration.getHooks()) {
			hook.onStarted(this, getServletContext());
		}
	}

	@Override
	public String getScheme() {
		return DEFAULT_SCHEME;
	}

	@Override
	public String getHost() {
		return DEFAULT_HOST;
	}

	@Override
	public String getUrl() {
		final String scheme = getScheme();
		final String host = getHost();
		final String port = String.valueOf(getPort());
		final String path = ensureAbsolutePath(getPath());
		final int initialSize = scheme.length() + host.length() + port.length() + path.length() + SCHEME_SEPARATOR.length() + PORT_SEPARATOR.length();
		return new StringBuilder(initialSize)
			.append(scheme)
			.append(SCHEME_SEPARATOR)
			.append(host)
			.append(PORT_SEPARATOR)
			.append(port)
			.append(path)
			.toString();
	}

	@Override
	public T getConfiguration() {
		return configuration;
	}

	/**
	 * Get internal server implementation.
	 * Note that this method should not be used to start
	 * or stop internal server, use dedicated method instead.
	 *
	 * This method can be used to do some custom configuration
	 * on original implementation.
	 *
	 * @return Original server implementation.
	 */
	public abstract S getDelegate();

	/**
	 * Start embedded server.
	 * Must block until server is fully started.
	 */
	protected abstract void doStart();

	/**
	 * Stop embedded server.
	 * Must block until server is fully stopped.
	 */
	protected abstract void doStop();

	/**
	 * Get port once server is started.
	 *
	 * @return The port.
	 */
	protected abstract int doGetPort();
}


/**
 * The MIT License (MIT)
 *
 * Copyright (c) 2014-2019 <mickael.jeanroy@gmail.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package com.github.mjeanroy.junit.servers.engine;

import com.github.mjeanroy.junit.servers.annotations.TestServerConfiguration;
import com.github.mjeanroy.junit.servers.commons.Strings;
import com.github.mjeanroy.junit.servers.exceptions.DuplicateConfigurationException;
import com.github.mjeanroy.junit.servers.exceptions.ServerImplMissingException;
import com.github.mjeanroy.junit.servers.servers.AbstractConfiguration;
import com.github.mjeanroy.junit.servers.servers.EmbeddedServer;
import com.github.mjeanroy.junit.servers.servers.EmbeddedServerProvider;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.ServiceLoader;

import static com.github.mjeanroy.junit.servers.commons.ReflectionUtils.findStaticFieldsAnnotatedWith;
import static com.github.mjeanroy.junit.servers.commons.ReflectionUtils.findStaticMethodsAnnotatedWith;
import static com.github.mjeanroy.junit.servers.commons.ReflectionUtils.getter;
import static com.github.mjeanroy.junit.servers.commons.ReflectionUtils.invoke;
import static java.lang.System.lineSeparator;

/**
 * Static utilities for server instantiation
 * and configuration.
 */
public final class Servers {

	// Ensure non instantiation
	private Servers() {
	}

	/**
	 * Instantiate jetty or tomcat embedded server.
	 *
	 *

	 *
	 * Server configuration is automatically read on static field / methods
	 * available on given class (i.e field or method annotated
	 * with {@link com.github.mjeanroy.junit.servers.annotations.TestServerConfiguration} annotation).
	 *
	 *

	 *
	 * Server implementation is automatically detected (jetty or
	 * tomcat) with classpath detection.
	 *
	 * @param klass Class to inspect.
	 * @return Embedded server.
	 */
	public static EmbeddedServer<?> instantiate(Class<?> klass) {
		AbstractConfiguration configuration = findConfiguration(klass);
		return instantiate(configuration);
	}

	/**
	 * Instantiate jetty or tomcat embedded server.
	 *
	 *

	 *
	 * Configuration is an optional parameter and can be null.
	 * If configuration is null, empty constructor will be used to instantiate
	 * embedded server.
	 * Otherwise, constructor with one parameter (configuration) will
	 * be used.
	 *
	 *

	 *
	 * Server implementation is automatically detected (jetty or
	 * tomcat) with classpath detection.
	 *
	 * @param configuration Optional configuration.
	 * @return Embedded server.
	 */
	@SuppressWarnings("unchecked")
	public static EmbeddedServer<?> instantiate(AbstractConfiguration configuration) {
		ServiceLoader<EmbeddedServerProvider> serviceProviders = ServiceLoader.load(EmbeddedServerProvider.class);

		List<EmbeddedServerProvider> coreServerProviders = new ArrayList<>();
		List<EmbeddedServerProvider> customServerProviders = new ArrayList<>();

		for (EmbeddedServerProvider provider : serviceProviders) {
			if (provider.getClass().getName().startsWith("com.github.mjeanroy.junit.servers")) {
				coreServerProviders.add(provider);
			}
			else {
				customServerProviders.add(provider);
			}
		}

		// No match, fail fast.
		if (coreServerProviders.isEmpty() && customServerProviders.isEmpty()) {
			throw new ServerImplMissingException();
		}

		// Choose the first one available.
		if (customServerProviders.isEmpty()) {
			return instantiate(coreServerProviders.get(0), configuration);
		}

		// Try to instantiate the custom available implementation.
		if (customServerProviders.size() > 1) {
			throw new ServerImplMissingException();
		}

		return instantiate(coreServerProviders.get(0), configuration);
	}

	private static <T extends AbstractConfiguration> EmbeddedServer<T> instantiate(EmbeddedServerProvider<T> provider, T configuration) {
		return configuration == null ? provider.instantiate() : provider.instantiate(configuration);
	}

	/**
	 * Find configuration object on static field / method on
	 * running class. Configuration is read from static method or static field
	 * annotated with {@link com.github.mjeanroy.junit.servers.annotations.TestServerConfiguration} annotation.
	 *
	 * @param klass Class to inspect.
	 * @param <T> Type of configuration.
	 * @return Configuration.
	 */
	public static <T extends AbstractConfiguration> T findConfiguration(Class<?> klass) {
		// Look for static methods first
		List<Method> methods = findStaticMethodsAnnotatedWith(klass, TestServerConfiguration.class);
		List<Field> fields = findStaticFieldsAnnotatedWith(klass, TestServerConfiguration.class);
		int nbOfConfigurations = methods.size() + fields.size();

		if (nbOfConfigurations > 1) {
			failBecauseOfDuplicateConfiguration(klass, methods, fields);
		}

		if (!methods.isEmpty()) {
			return invoke(methods.get(0));
		}

		// Then, look for static field
		if (!fields.isEmpty()) {
			return getter(fields.get(0));
		}

		return null;
	}

	private static void failBecauseOfDuplicateConfiguration(Class<?> klass, List<Method> methods, List<Field> fields) {
		List<String> lines = new ArrayList<>(1 + methods.size() + fields.size());

		lines.add("It looks like multiple fields may be used to extract configuration: found:");

		for (Method method : methods) {
			lines.add("- " + klass.getName() + "#" + method.getName() + "()");
		}

		for (Field field : fields) {
			lines.add("- " + klass.getName() + "#" + field.getName());
		}

		throw new DuplicateConfigurationException(
			Strings.join(lineSeparator(), lines)
		);
	}
}


/**
 * The MIT License (MIT)
 *
 * Copyright (c) 2014-2019 <mickael.jeanroy@gmail.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package com.github.mjeanroy.junit.servers.client;

import com.github.mjeanroy.junit.servers.commons.ToStringBuilder;

import java.util.Objects;

/**
 * Default implementation for cookie object.
 *
 *

 *
 * To build a cookie, use:
 *

 *
The {@link Builder} API

 *
Or one of the static factories in {@link Cookies} class

 *

 *
 *

 *
 * Using the builder is easy:
 *
 *
{@code
 *   Cookie cookie = new Cookie.Builder("name", "value")
 *     .domain("domain")
 *     .path("path")
 *     .secure(true)   // Defaults to false.
 *     .httpOnly(true) // Defaults to false.
 *     .maxAge(3600)
 *     .expires(tt.getTime())
 *     .build();
 * }

 *
 * You can also use one of the static factories:
 *
 *
{@code
 *   Cookie c1 = Cookies.cookie("name", "value");
 *   Cookie c2 = Cookies.cookie("name", "value", "domain", "path", maxAge, expires, true, true);
 * }

 *
 * @see https://developer.mozilla.org/fr/docs/Web/HTTP/Headers/Set-Cookie
 */
public class Cookie {

	/**
	 * Create a cookie from header value.
	 *
	 * @param rawValue Header value.
	 * @return Cookie.
	 * @deprecated Use {@link Cookies#read(String)} instead.
	 */
	@Deprecated
	public static Cookie read(String rawValue) {
		return Cookies.read(rawValue);
	}

	/**
	 * Create cookie.
	 *
	 * @param name Cookie name.
	 * @param value Cookie value.
	 * @return Cookie.
	 * @throws NullPointerException if name or value is null.
	 * @throws IllegalArgumentException if name is empty or blank.
	 * @deprecated Use {@link Cookies#cookie(String, String)} instead.
	 */
	@Deprecated
	public static Cookie cookie(String name, String value) {
		return Cookies.cookie(name, value);
	}

	/**
	 * Create cookie.
	 *
	 * @param name Cookie name.
	 * @param value Cookie value.
	 * @param domain Cookie domain.
	 * @param path Cookie path.
	 * @param expires Cookie expires value.
	 * @param maxAge Cookie max age value.
	 * @param secure Secure flag.
	 * @param httpOnly Http flag.
	 * @return Cookie.
	 * @throws NullPointerException if name or value is null.
	 * @throws IllegalArgumentException if name is empty or blank.
	 * @deprecated Use {@link Cookies#cookie(String, String, String, String, Long, Long, boolean, boolean)} instead.
	 */
	@Deprecated
	public static Cookie cookie(String name, String value, String domain, String path, Long expires, Long maxAge, boolean secure, boolean httpOnly) {
		return Cookies.cookie(name, value, domain, path, expires, maxAge, secure, httpOnly);
	}

	/**
	 * Create a secured cookie, it means that cookie will have
	 * secure flag and http only flag set to true.
	 *
	 * @param name Cookie name.
	 * @param value Cookie value.
	 * @param domain Cookie domain.
	 * @param path Cookie path.
	 * @param expires Cookie expires value.
	 * @param maxAge Cookie max age.
	 * @return Cookie.
	 * @throws NullPointerException if name or value is null.
	 * @throws IllegalArgumentException if name is empty or blank.
	 * @deprecated Use {@link Cookies#secureCookie(String, String, String, String, Long, Long)} instead.
	 */
	@Deprecated
	public static Cookie secureCookie(String name, String value, String domain, String path, Long expires, Long maxAge) {
		return Cookies.secureCookie(name, value, domain, path, expires, maxAge);
	}

	/**
	 * Create a session cookie, it means that cookie will have
	 * secure flag and http only flag set to true and expires / max age values
	 * are set to zero.
	 *
	 * @param name Cookie name.
	 * @param value Cookie value.
	 * @param domain Cookie domain.
	 * @param path Cookie path.
	 * @return Cookie.
	 * @throws NullPointerException if name or value is null.
	 * @throws IllegalArgumentException if name is empty or blank.
	 * @deprecated Use {@link Cookies#sessionCookie(String, String, String, String)} instead.
	 */
	@Deprecated
	public static Cookie sessionCookie(String name, String value, String domain, String path) {
		return Cookies.sessionCookie(name, value, domain, path);
	}

	/**
	 * Cookie name.
	 * Must not be null, empty or blank.
	 */
	private final String name;

	/**
	 * Cookie value.
	 * Must not be null.
	 */
	private final String value;

	/**
	 * Cookie domain.
	 */
	private final String domain;

	/**
	 * Cookie path.
	 */
	private final String path;

	/**
	 * Cookie expires value.
	 */
	private final Long expires;

	/**
	 * Cookie max age.
	 */
	private final Long maxAge;

	/**
	 * Secure flag.
	 */
	private final boolean secure;

	/**
	 * Http Only flag.
	 */
	private final boolean httpOnly;

	// Use static factories
	private Cookie(String name, String value, String domain, String path, boolean secure, boolean httpOnly, Long expires, Long maxAge) {
		this.name = name;
		this.value = value;
		this.domain = domain;
		this.path = path;
		this.expires = expires;
		this.maxAge = maxAge;
		this.secure = secure;
		this.httpOnly = httpOnly;
	}

	/**
	 * Get cookie name.
	 *
	 * @return Cookie name.
	 */
	public String getName() {
		return name;
	}

	/**
	 * Get cookie value.
	 *
	 * @return Cookie value.
	 */
	public String getValue() {
		return value;
	}

	/**
	 * Get cookie domain.
	 *
	 * @return Cookie domain.
	 */
	public String getDomain() {
		return domain;
	}

	/**
	 * Get cookie path.
	 *
	 * @return Cookie path.
	 */
	public String getPath() {
		return path;
	}

	/**
	 * Get cookie expires value.
	 * The value is a timestamp.
	 * If value is lower than zero, it means that expires
	 * value has not been set on cookie.
	 *
	 * @return Expires value.
	 */
	public Long getExpires() {
		return expires;
	}

	/**
	 * Cookie max age.
	 * This is the max validity time in seconds.
	 *
	 * @return Max age value.
	 */
	public Long getMaxAge() {
		return maxAge;
	}

	/**
	 * Cookie secure flag.
	 *
	 * @return Secure flag.
	 */
	public boolean isSecure() {
		return secure;
	}

	/**
	 * Cookie http-only flag.
	 *
	 * @return Http-Only flag.
	 */
	public boolean isHttpOnly() {
		return httpOnly;
	}

	@Override
	public boolean equals(Object o) {
		if (o == this) {
			return true;
		}

		if (o instanceof Cookie) {
			Cookie c = (Cookie) o;
			return Objects.equals(name, c.name) &&
				Objects.equals(value, c.value) &&
				Objects.equals(domain, c.domain) &&
				Objects.equals(path, c.path) &&
				Objects.equals(expires, c.expires) &&
				Objects.equals(maxAge, c.maxAge) &&
				secure == c.secure &&
				httpOnly == c.httpOnly;
		}

		return false;
	}

	@Override
	public int hashCode() {
		return Objects.hash(name, value, domain, path, expires, maxAge, secure, httpOnly);
	}

	@Override
	public String toString() {
		return ToStringBuilder.create(getClass())
			.append("name", name)
			.append("value", value)
			.append("domain", domain)
			.append("path", path)
			.append("expires", expires)
			.append("maxAge", maxAge)
			.append("secure", secure)
			.append("httpOnly", httpOnly)
			.build();
	}

	/**
	 * Builder to create {@link Cookie} instances.
	 */
	public static class Builder {
		/**
		 * The cookie name.
		 * @see Cookie#name
		 */
		private final String name;

		/**
		 * The cookie value.
		 * @see Cookie#value
		 */
		private final String value;

		/**
		 * The cookie domain.
		 * @see Cookie#domain
		 */
		private String domain;

		/**
		 * The cookie path.
		 * @see Cookie#path
		 */
		private String path;

		/**
		 * The cookie max-age.
		 * @see Cookie#maxAge
		 */
		private Long maxAge;

		/**
		 * The cookie expires value.
		 * @see Cookie#expires
		 */
		private Long expires;

		/**
		 * The cookie secure flag.
		 * @see Cookie#secure
		 */
		private boolean secure;

		/**
		 * The cookie http-only flag.
		 * @see Cookie#httpOnly
		 */
		private boolean httpOnly;

		/**
		 * Create the builder with a cookie name and value.
		 *
		 * @param name Cookie name.
		 * @param value Cookie value.
		 */
		public Builder(String name, String value) {
			this.name = name;
			this.value = value;
		}

		/**
		 * Update cookie domain.
		 *
		 * @param domain Cookie domain.
		 * @return The builder (for chaining).
		 */
		public Builder domain(String domain) {
			this.domain = domain;
			return this;
		}

		/**
		 * Update cookie path.
		 *
		 * @param path Cookie path.
		 * @return The builder (for chaining).
		 */
		public Builder path(String path) {
			this.path = path;
			return this;
		}

		/**
		 * Update cookie secure flag.
		 *
		 * @param secure Cookie secure flag.
		 * @return The builder (for chaining).
		 */
		public Builder secure(boolean secure) {
			this.secure = secure;
			return this;
		}

		/**
		 * Update cookie http-only flag.
		 *
		 * @param httpOnly Cookie http-only flag.
		 * @return The builder (for chaining).
		 */
		public Builder httpOnly(boolean httpOnly) {
			this.httpOnly = httpOnly;
			return this;
		}

		/**
		 * Update cookie max-age.
		 *
		 * @param maxAge Cookie max-age.
		 * @return The builder (for chaining).
		 */
		public Builder maxAge(long maxAge) {
			this.maxAge = maxAge;
			return this;
		}

		/**
		 * Update cookie expires value.
		 *
		 * @param expires Cookie expires.
		 * @return The builder (for chaining).
		 */
		public Builder expires(long expires) {
			this.expires = expires;
			return this;
		}

		/**
		 * Create the cookie.
		 *
		 * @return The cookie.
		 */
		public Cookie build() {
			return new Cookie(name, value, domain, path, secure, httpOnly, expires, maxAge);
		}
	}
}


/**
 * The MIT License (MIT)
 *
 * Copyright (c) 2014-2019 <mickael.jeanroy@gmail.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package com.github.mjeanroy.junit.servers.engine;

import com.github.mjeanroy.junit.servers.servers.AbstractConfiguration;
import com.github.mjeanroy.junit.servers.servers.EmbeddedServer;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.List;

import static com.github.mjeanroy.junit.servers.commons.ReflectionUtils.findAllFields;
import static com.github.mjeanroy.junit.servers.engine.ConfigurationAnnotationHandler.newConfigurationAnnotationHandler;
import static com.github.mjeanroy.junit.servers.engine.HttpClientAnnotationHandler.newHttpClientAnnotationHandler;
import static com.github.mjeanroy.junit.servers.engine.ServerAnnotationHandler.newServerAnnotationHandler;
import static java.util.Arrays.asList;

/**
 * An engine that will handle various annotations defined by Junit-Servers:
 *
 *

 *
{@link com.github.mjeanroy.junit.servers.annotations.TestServer}

 *
{@link com.github.mjeanroy.junit.servers.annotations.TestServerConfiguration}

 *
{@link com.github.mjeanroy.junit.servers.annotations.TestHttpClient}

 *

 */
public class AnnotationsHandlerRunner extends AbstractTestRunner implements TestRunner {

	/**
	 * List of handlers.
	 */
	private final List<AnnotationHandler> handlers;

	/**
	 * Create test lifecycle engine that will setup following Junit-Servers annotations declared in test class:
	 *
	 *

	 *
{@link com.github.mjeanroy.junit.servers.annotations.TestServer}

	 *
{@link com.github.mjeanroy.junit.servers.annotations.TestServerConfiguration}

	 *
{@link com.github.mjeanroy.junit.servers.annotations.TestHttpClient}

	 *

	 *
	 * @param server The embedded server used in the tested class instance.
	 * @param configuration The embedded server configuration.
	 */
	public AnnotationsHandlerRunner(EmbeddedServer<?> server, AbstractConfiguration configuration) {
		this.handlers = asList(
			newServerAnnotationHandler(server),
			newConfigurationAnnotationHandler(configuration),
			newHttpClientAnnotationHandler(server)
		);
	}

	@Override
	public void beforeEach(Object target) {
		process(target, true);
	}

	@Override
	public void afterEach(Object target) {
		process(target, false);
	}

	/**
	 * Process handlers.
	 *
	 * @param target Target class (i.e tested class).
	 * @param before Flag to know if handler has to run "before" phase or "after" phase.
	 */
	private void process(Object target, boolean before) {
		List<Field> fields = findAllFields(target.getClass());
		for (Field field : fields) {
			for (AnnotationHandler handler : handlers) {
				processField(target, handler, field, before);
			}
		}
	}

	/**
	 * Process field for given handler.
	 *
	 * @param target Target class (i.e tested class).
	 * @param handler Handler.
	 * @param field Field.
	 * @param before Flag to know if handler has to run "before" phase or "after" phase.
	 */
	private void processField(Object target, AnnotationHandler handler, Field field, boolean before) {
		for (Annotation annotation : field.getAnnotations()) {
			if (handler.support(annotation)) {
				if (before) {
					handler.before(target, field);
				}
				else {
					handler.after(target, field);
				}
			}
		}
	}
}


/**
 * The MIT License (MIT)
 *
 * Copyright (c) 2014-2019 <mickael.jeanroy@gmail.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package com.github.mjeanroy.junit.servers.client.impl.apache;

import com.github.mjeanroy.junit.servers.client.HttpClient;
import com.github.mjeanroy.junit.servers.client.HttpClientConfiguration;
import com.github.mjeanroy.junit.servers.client.HttpMethod;
import com.github.mjeanroy.junit.servers.client.HttpRequest;
import com.github.mjeanroy.junit.servers.client.HttpUrl;
import com.github.mjeanroy.junit.servers.client.impl.AbstractHttpClient;
import com.github.mjeanroy.junit.servers.exceptions.HttpClientException;
import com.github.mjeanroy.junit.servers.servers.EmbeddedServer;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;

import java.io.IOException;
import java.util.concurrent.atomic.AtomicBoolean;

import static com.github.mjeanroy.junit.servers.commons.Preconditions.notNull;

/**
 * Implementation of {@link HttpClient} using apache http client
 * library under the hood.
 *
 * @see http://hc.apache.org/httpcomponents-client-ga/index.html
 * @see com.github.mjeanroy.junit.servers.client.HttpClientStrategy#APACHE_HTTP_CLIENT
 */
public class ApacheHttpClient extends AbstractHttpClient implements HttpClient {

	/**
	 * Create new http client using internal with custom internal client.
	 *
	 * @param server Embedded server.
	 * @param client Internal http client
	 * @return Http client.
	 * @throws NullPointerException If {@code server} of {@code client} are {@code null}.
	 * @deprecated Use {@link ApacheHttpClient#newApacheHttpClient(HttpClientConfiguration, EmbeddedServer)}
	 */
	@Deprecated
	public static ApacheHttpClient newApacheHttpClient(EmbeddedServer<?> server, CloseableHttpClient client) {
		return new ApacheHttpClient(HttpClientConfiguration.defaultConfiguration(), server, client);
	}

	/**
	 * Create new http client using default internal client.
	 *
	 * @param server Embedded server.
	 * @return Http client.
	 * @throws NullPointerException If {@code server} is {@code null}.
	 */
	public static ApacheHttpClient defaultApacheHttpClient(EmbeddedServer<?> server) {
		HttpClientConfiguration configuration = HttpClientConfiguration.defaultConfiguration();
		return newApacheHttpClient(configuration, server);
	}

	/**
	 * Create new http client using custom configuration.
	 *
	 * @param configuration Client configuration.
	 * @param server Embedded server.
	 * @return Http client.
	 * @throws NullPointerException If {@code server} or {@code configuration} are {@code null}.
	 */
	public static ApacheHttpClient newApacheHttpClient(HttpClientConfiguration configuration, EmbeddedServer<?> server) {
		HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
		if (!configuration.isFollowRedirect()) {
			httpClientBuilder.disableRedirectHandling();
		}

		CloseableHttpClient client = httpClientBuilder.build();
		return new ApacheHttpClient(configuration, server, client);
	}

	/**
	 * The {@code close} flag.
	 */
	private final AtomicBoolean destroyed;

	/**
	 * Internal apache http client.
	 * This http client will be closed when http client
	 * is destroyed.
	 */
	private final CloseableHttpClient client;

	// Use static factory
	private ApacheHttpClient(HttpClientConfiguration configuration, EmbeddedServer<?> server, CloseableHttpClient client) {
		super(configuration, server);
		this.client = notNull(client, "client");
		this.destroyed = new AtomicBoolean(false);
	}

	@Override
	protected HttpRequest buildRequest(HttpMethod httpMethod, HttpUrl endpoint) {
		return new ApacheHttpRequest(client, httpMethod, endpoint);
	}

	@Override
	public void destroy() {
		if (destroyed.compareAndSet(false, true)) {
			try {
				client.close();
			}
			catch (IOException ex) {
				throw new HttpClientException(ex);
			}
		}
	}

	@Override
	public boolean isDestroyed() {
		return destroyed.get();
	}
}


/**
 * The MIT License (MIT)
 *
 * Copyright (c) 2014-2019 <mickael.jeanroy@gmail.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package com.github.mjeanroy.junit.servers.client.impl.apache;

import com.github.mjeanroy.junit.servers.client.impl.DefaultHttpResponse;
import org.apache.http.HttpResponse;

/**
 * Factory to produce {@link com.github.mjeanroy.junit.servers.client.HttpResponse} from {@link HttpResponse}.
 *
 * @see http://hc.apache.org/httpcomponents-client-ga/index.html
 * @see com.github.mjeanroy.junit.servers.client.HttpClientStrategy#APACHE_HTTP_CLIENT
 */
final class ApacheHttpResponseFactory {

	// Ensure non instantiation.
	private ApacheHttpResponseFactory() {
	}

	/**
	 * Create the final {@link DefaultHttpResponse} instance.
	 *
	 * @param response The Apache response.
	 * @param duration The request duration.
	 * @return The HTTP response.
	 */
	static com.github.mjeanroy.junit.servers.client.HttpResponse of(HttpResponse response, long duration) {
		return new ApacheHttpResponse(response, duration);
	}
}


/**
 * The MIT License (MIT)
 *
 * Copyright (c) 2014-2019 <mickael.jeanroy@gmail.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package com.github.mjeanroy.junit.servers.client.impl.apache;

import com.github.mjeanroy.junit.servers.client.Cookies;
import com.github.mjeanroy.junit.servers.client.HttpHeader;
import com.github.mjeanroy.junit.servers.client.HttpMethod;
import com.github.mjeanroy.junit.servers.client.HttpParameter;
import com.github.mjeanroy.junit.servers.client.HttpRequest;
import com.github.mjeanroy.junit.servers.client.HttpResponse;
import com.github.mjeanroy.junit.servers.client.HttpUrl;
import com.github.mjeanroy.junit.servers.client.impl.AbstractHttpRequest;
import com.github.mjeanroy.junit.servers.commons.Mapper;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpHead;
import org.apache.http.client.methods.HttpPatch;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.StringEntity;
import org.apache.http.message.BasicNameValuePair;

import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.List;

import static com.github.mjeanroy.junit.servers.client.HttpHeaders.COOKIE;
import static com.github.mjeanroy.junit.servers.commons.CollectionUtils.map;
import static java.lang.System.nanoTime;

/**
 * Implementation for {@link HttpRequest} that use apache http-client
 * under the hood.
 *
 * @see http://hc.apache.org/httpcomponents-client-ga/index.html
 * @see com.github.mjeanroy.junit.servers.client.HttpClientStrategy#APACHE_HTTP_CLIENT
 */
class ApacheHttpRequest extends AbstractHttpRequest implements HttpRequest {

	private static final ApacheHttpRequestFactory FACTORY = new ApacheHttpRequestFactory();

	/**
	 * Original http client, will be used to execute http request.
	 */
	private final HttpClient client;

	/**
	 * Create apache http request.
	 *
	 * @param client Apache http client.
	 * @param httpMethod Http method.
	 * @param endpoint Http request url.
	 */
	ApacheHttpRequest(HttpClient client, HttpMethod httpMethod, HttpUrl endpoint) {
		super(endpoint, httpMethod);
		this.client = client;
	}

	@Override
	protected HttpResponse doExecute() throws Exception {
		final HttpMethod method = getMethod();

		final HttpRequestBase httpRequest = FACTORY.create(method);

		httpRequest.setURI(createRequestURI());
		handleHeaders(httpRequest);
		handleCookies(httpRequest);
		handleBody(httpRequest);

		final long start = nanoTime();
		final org.apache.http.HttpResponse httpResponse = client.execute(httpRequest);
		final long duration = nanoTime() - start;

		return ApacheHttpResponseFactory.of(httpResponse, duration);
	}

	/**
	 * Add request body.
	 *
	 * @param httpRequest The HTTP request.
	 */
	private void handleBody(HttpRequestBase httpRequest) {
		if (hasBody()) {
			HttpEntityEnclosingRequestBase rq = (HttpEntityEnclosingRequestBase) httpRequest;
			if (!formParams.isEmpty()) {
				handleFormParameters(rq);
			}
			else if (body != null) {
				handleRequestBody(rq);
			}
		}
	}

	/**
	 * Create request URI.
	 * Each additional query parameters will be appended to final URI.
	 *
	 * @return Created URI.
	 * @throws URISyntaxException If an error occurred while building URI.
	 * @see URIBuilder
	 */
	private URI createRequestURI() throws URISyntaxException {
		URI uri = getEndpoint().toURI();
		URIBuilder builder = new URIBuilder(uri).setCharset(StandardCharsets.UTF_8);
		for (HttpParameter parameter : queryParams.values()) {
			builder.addParameter(parameter.getName(), parameter.getValue());
		}

		return builder.build();
	}

	/**
	 * Add headers to http request.
	 *
	 * @param httpRequest Http request in creation.
	 * @see org.apache.http.HttpRequest#addHeader(Header)
	 */
	private void handleHeaders(HttpRequestBase httpRequest) {
		for (HttpHeader header : headers.values()) {
			httpRequest.addHeader(header.getName(), header.serializeValues());
		}
	}

	/**
	 * Add parameters as form url encoded content.
	 * Each parameter is set as a key value entry to request
	 * body.
	 *
	 * @param httpRequest Http request in creation.
	 */
	private void handleFormParameters(HttpEntityEnclosingRequestBase httpRequest) {
		List<NameValuePair> pairs = map(formParams.values(), PARAM_MAPPER);
		HttpEntity entity = new UrlEncodedFormEntity(pairs, Charset.defaultCharset());
		httpRequest.setEntity(entity);
	}

	/**
	 * Set request body value to http request.
	 *
	 * @param httpRequest Http request in creation.
	 */
	private void handleRequestBody(HttpEntityEnclosingRequestBase httpRequest) {
		HttpEntity entity = new StringEntity(body, Charset.defaultCharset());
		httpRequest.setEntity(entity);
	}

	/**
	 * Add cookies to http request.
	 *
	 * @param httpRequest Http request in creation.
	 */
	private void handleCookies(HttpRequestBase httpRequest) {
		if (!cookies.isEmpty()) {
			httpRequest.addHeader(COOKIE, Cookies.serialize(cookies));
		}
	}

	private static class ApacheHttpRequestFactory {

		HttpRequestBase create(HttpMethod httpMethod) {
			if (httpMethod == HttpMethod.GET) {
				return new HttpGet();
			}

			if (httpMethod == HttpMethod.POST) {
				return new HttpPost();
			}

			if (httpMethod == HttpMethod.PUT) {
				return new HttpPut();
			}

			if (httpMethod == HttpMethod.DELETE) {
				return new HttpDelete();
			}

			if (httpMethod == HttpMethod.PATCH) {
				return new HttpPatch();
			}

			if (httpMethod == HttpMethod.HEAD) {
				return new HttpHead();
			}

			throw new UnsupportedOperationException("Method " + httpMethod + " is not supported by apache http-client");
		}
	}

	private static final Mapper<HttpParameter, NameValuePair> PARAM_MAPPER = new Mapper<HttpParameter, NameValuePair>() {
		@Override
		public NameValuePair apply(HttpParameter parameter) {
			return new BasicNameValuePair(parameter.getName(), parameter.getValue());
		}
	};
}


/**
 * The MIT License (MIT)
 *
 * Copyright (c) 2014-2019 <mickael.jeanroy@gmail.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package com.github.mjeanroy.junit.servers.client.impl.async;

import com.github.mjeanroy.junit.servers.client.HttpClient;
import com.github.mjeanroy.junit.servers.client.HttpClientConfiguration;
import com.github.mjeanroy.junit.servers.client.HttpMethod;
import com.github.mjeanroy.junit.servers.client.HttpRequest;
import com.github.mjeanroy.junit.servers.client.HttpUrl;
import com.github.mjeanroy.junit.servers.client.impl.AbstractHttpClient;
import com.github.mjeanroy.junit.servers.exceptions.HttpClientException;
import com.github.mjeanroy.junit.servers.servers.EmbeddedServer;
import org.asynchttpclient.AsyncHttpClientConfig;
import org.asynchttpclient.DefaultAsyncHttpClient;
import org.asynchttpclient.DefaultAsyncHttpClientConfig;

import java.io.IOException;

import static com.github.mjeanroy.junit.servers.commons.Preconditions.notNull;

/**
 * Implementation of {@link HttpClient} using async-http-client
 * under the hood.
 *
 * @see https://asynchttpclient.github.io/
 * @see com.github.mjeanroy.junit.servers.client.HttpClientStrategy#ASYNC_HTTP_CLIENT
 */
public class AsyncHttpClient extends AbstractHttpClient implements HttpClient {

	/**
	 * Create new http client using custom internal http client.
	 *
	 * @param server Embedded server.
	 * @param client Internal http client
	 * @return Http client.
	 * @throws NullPointerException If {@code server} or {@code client} are {@code null}.
	 * @deprecated Use {@link AsyncHttpClient#newAsyncHttpClient(HttpClientConfiguration, EmbeddedServer)}
	 */
	@Deprecated
	public static AsyncHttpClient newAsyncHttpClient(EmbeddedServer<?> server, org.asynchttpclient.AsyncHttpClient client) {
		return new AsyncHttpClient(HttpClientConfiguration.defaultConfiguration(), server, client);
	}

	/**
	 * Create new http client using default internal http client.
	 *
	 * @param server Embedded server.
	 * @return Http client.
	 * @throws NullPointerException If {@code server} is {@code null}.
	 */
	public static AsyncHttpClient defaultAsyncHttpClient(EmbeddedServer<?> server) {
		HttpClientConfiguration configuration = HttpClientConfiguration.defaultConfiguration();
		return newAsyncHttpClient(configuration, server);
	}

	/**
	 * Create new http client using custom configuration.
	 *
	 * @param configuration Client configuration.
	 * @param server Embedded server.
	 * @return Http client.
	 * @throws NullPointerException If {@code server} or {@code configuration} are {@code null}.
	 */
	public static AsyncHttpClient newAsyncHttpClient(HttpClientConfiguration configuration, EmbeddedServer<?> server) {
		AsyncHttpClientConfig config = new DefaultAsyncHttpClientConfig.Builder()
			.setFollowRedirect(configuration.isFollowRedirect())
			.build();

		DefaultAsyncHttpClient client = new DefaultAsyncHttpClient(config);
		return new AsyncHttpClient(configuration, server, client);
	}

	/**
	 * Original http client.
	 */
	private final org.asynchttpclient.AsyncHttpClient client;

	// Use static factory
	private AsyncHttpClient(HttpClientConfiguration configuration, EmbeddedServer<?> server, org.asynchttpclient.AsyncHttpClient client) {
		super(configuration, server);
		this.client = notNull(client, "client");
	}

	@Override
	protected HttpRequest buildRequest(HttpMethod httpMethod, HttpUrl endpoint) {
		return new AsyncHttpRequest(client, httpMethod, endpoint);
	}

	@Override
	public void destroy() {
		try {
			client.close();
		}
		catch (IOException ex) {
			throw new HttpClientException(ex);
		}
	}

	@Override
	public boolean isDestroyed() {
		return client.isClosed();
	}
}


/**
 * The MIT License (MIT)
 *
 * Copyright (c) 2014-2019 <mickael.jeanroy@gmail.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package com.github.mjeanroy.junit.servers.jetty.jupiter;

import com.github.mjeanroy.junit.servers.jetty.EmbeddedJetty;
import com.github.mjeanroy.junit.servers.jetty.EmbeddedJettyConfiguration;
import com.github.mjeanroy.junit.servers.jetty.exceptions.IllegalJettyConfigurationException;
import com.github.mjeanroy.junit.servers.jupiter.JunitServerExtension;
import com.github.mjeanroy.junit.servers.servers.AbstractConfiguration;
import com.github.mjeanroy.junit.servers.servers.EmbeddedServer;

import static com.github.mjeanroy.junit.servers.engine.Servers.findConfiguration;

/**
 * A specialized {@link JunitServerExtension} that will instantiate an {@link EmbeddedJetty}
 * server automatically instead of using the Service Provider API.
 *
 * Since this jupiter extends {@link JunitServerExtension}, it has exactly the same features (parameter
 * injections, etc.).
 *
 * @see JunitServerExtension
 */
public class JettyServerExtension extends JunitServerExtension {

	/**
	 * Create the jupiter with default behavior.
	 */
	public JettyServerExtension() {
		super();
	}

	/**
	 * Create the jupiter and specify the embedded jetty instance to use.
	 *
	 * @param jetty The embedded jetty instance to use.
	 * @throws NullPointerException If {@code jetty} is {@code null}.
	 */
	public JettyServerExtension(EmbeddedJetty jetty) {
		super(jetty);
	}

	/**
	 * Create the jupiter and specify the embedded jetty configuration to use (when using
	 * 	 * jupiter with {@link org.junit.jupiter.api.extension.RegisterExtension}).
	 *
	 * @param configuration The embedded jetty configuration to use.
	 * @throws NullPointerException If {@code configuration} is {@code null}.
	 */
	public JettyServerExtension(EmbeddedJettyConfiguration configuration) {
		super(configuration);
	}

	@Override
	protected EmbeddedServer<?> instantiateServer(Class<?> testClass, AbstractConfiguration configuration) {
		final EmbeddedJettyConfiguration configurationToUse = extractConfiguration(testClass, configuration);
		return configurationToUse == null ? new EmbeddedJetty() : new EmbeddedJetty(configurationToUse);
	}

	/**
	 * Try to extract {@link EmbeddedJetty} configuration from:
	 *

	 *
The given {@code configuration} if it is not {@code null}.

	 *
A class field/method annotated with {@link com.github.mjeanroy.junit.servers.annotations.TestServerConfiguration} on given {@code testClass} otherwise.

	 *

	 *
	 * @param testClass The test class to analyze.
	 * @param configuration The configuration to use if not {@code null}.
	 * @return The {@link EmbeddedJetty} configuration.
	 * @throws IllegalJettyConfigurationException If extracted {@code configuration} is not an instance of {@link EmbeddedJettyConfiguration}.
	 */
	private static EmbeddedJettyConfiguration extractConfiguration(Class<?> testClass, AbstractConfiguration configuration) {
		final AbstractConfiguration configurationToUse = configuration == null ? findConfiguration(testClass) : configuration;

		if (configurationToUse == null) {
			return null;
		}

		if (!(configurationToUse instanceof EmbeddedJettyConfiguration)) {
			throw new IllegalJettyConfigurationException();
		}

		return (EmbeddedJettyConfiguration) configurationToUse;
	}
}


/**
 * The MIT License (MIT)
 *
 * Copyright (c) 2014-2019 <mickael.jeanroy@gmail.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package com.github.mjeanroy.junit.servers.commons;

/**
 * Static Java Utilities.
 *
 * Internal API: these methods are part of the internal API and may be removed, have their signature change,
 * or have their access level decreased from public to protected, package, or private in future versions without notice.
 */
public final class JavaUtils {

	// Ensure non instantiation.
	private JavaUtils() {
	}

	/**
	 * The system property name that will be read to get Java version.
	 */
	private static final String JAVA_SPECIFICATION_VERSION_PROP = "java.specification.version";

	/**
	 * The Java Specification version.
	 */
	private static final String JAVA_SPECIFICATION_VERSION = System.getProperty(JAVA_SPECIFICATION_VERSION_PROP);

	/**
	 * The parsed Java Version.
	 */
	private static final int JAVA_MAJOR_VERSION = parseJavaVersion();

	/**
	 * Check if runtime java version is at least Java 9.
	 *
	 * @return {@code true} if current Java version is at least Java 9, {@code false} otherwise.
	 */
	public static boolean isPostJdk9() {
		return JAVA_MAJOR_VERSION >= 9;
	}

	/**
	 * Parse java version.
	 *
	 * @return The JAVA Version.
	 */
	private static int parseJavaVersion() {
		String[] parts = JAVA_SPECIFICATION_VERSION.split("\\.");
		int nbParts = parts.length;
		int majorIndex = nbParts > 1 ? 1 : 0;
		return Integer.parseInt(parts[majorIndex]);
	}
}


/**
 * The MIT License (MIT)
 *
 * Copyright (c) 2014-2019 <mickael.jeanroy@gmail.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package com.github.mjeanroy.junit.servers.commons;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
 * Static collection utilities.
 *
 *

 *
 * Internal API: these methods are part of the internal API and may be removed, have their signature change,
 * or have their access level decreased from public to protected, package, or private in future versions without notice.
 */
public final class CollectionUtils {

	// Ensure non instantiation
	private CollectionUtils() {
	}

	/**
	 * Check if a collection is null or empty.
	 *
	 * @param collection Collection to check.
	 * @return True if collection is null or empty, false otherwise.
	 */
	public static boolean isEmpty(Collection<?> collection) {
		return collection == null || collection.isEmpty();
	}

	/**
	 * Join elements of a collection to a final string using a given separator. Note that:
	 *

	 *
If {@code values} is {@code null}, {@code null} will be returned.

	 *
If {@code separator} is {@code null}, an empty string is used instead.

	 *

	 *
	 * @param values The values to join.
	 * @param separator Value separator.
	 * @param <T> Type of elements in the collection.
	 * @return The joined elements.
	 */
	public static <T> String join(Iterable<T> values, String separator) {
		if (values == null) {
			return null;
		}

		String sep = separator == null ? "" : separator;
		StringBuilder sb = new StringBuilder();
		boolean first = true;

		for (T value : values) {
			if (!first) {
				sb.append(sep);
			}

			sb.append(value == null ? null : value.toString());
			first = false;
		}

		return sb.toString();
	}

	/**
	 * Map input values to output values.
	 *
	 * @param inputs Input values.
	 * @param mapper Mapper function.
	 * @param <T> Type of input values.
	 * @param  Type of output values.
	 * @return Output values.
	 */
	public static <T, U> List map(Collection<T> inputs, Mapper<T, U> mapper) {
		if (inputs == null) {
			return null;
		}

		List outputs = new ArrayList<>(inputs.size());
		for (T input : inputs) {
			outputs.add(mapper.apply(input));
		}

		return outputs;
	}

	/**
	 * Concat new value to existing inputs and returns new outputs.
	 * Note that input is not modified.
	 *
	 * @param inputs Input list.
	 * @param newValue The new value to add.
	 * @param <T> The type of object.
	 * @return The outputs.
	 */
	public static <T> List<T> concat(List<T> inputs, T newValue) {
		List<T> outputs = new ArrayList<>(inputs.size() + 1);
		outputs.addAll(inputs);
		outputs.add(newValue);
		return outputs;
	}

	/**
	 * Filter input by using given predicate and return
	 * filtered outputs.
	 *
	 * @param list List input.
	 * @param predicate Predicate.
	 * @param <T> Type of elements.
	 * @return Filtered outputs.
	 */
	static <T> List<T> filter(List<T> list, Predicate<T> predicate) {
		List<T> results = new ArrayList<>();
		for (T current : list) {
			if (predicate.apply(current)) {
				results.add(current);
			}
		}
		return results;
	}
}


/**
 * The MIT License (MIT)
 *
 * Copyright (c) 2014-2019 <mickael.jeanroy@gmail.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package com.github.mjeanroy.junit.servers.tomcat.jupiter;

import com.github.mjeanroy.junit.servers.jupiter.JunitServerExtension;
import com.github.mjeanroy.junit.servers.servers.AbstractConfiguration;
import com.github.mjeanroy.junit.servers.servers.EmbeddedServer;
import com.github.mjeanroy.junit.servers.tomcat.EmbeddedTomcat;
import com.github.mjeanroy.junit.servers.tomcat.EmbeddedTomcatConfiguration;
import com.github.mjeanroy.junit.servers.tomcat.exceptions.IllegalTomcatConfigurationException;
import org.junit.jupiter.api.extension.RegisterExtension;

import static com.github.mjeanroy.junit.servers.engine.Servers.findConfiguration;

/**
 * A specialized {@link JunitServerExtension} that will instantiate an {@link EmbeddedTomcat}
 * server automatically instead of using the Service Provider API.
 *
 * Since this jupiter extends {@link JunitServerExtension}, it has exactly the same features (parameter
 * injections, etc.).
 *
 * @see JunitServerExtension
 */
public class TomcatServerExtension extends JunitServerExtension {

	/**
	 * Create the jupiter with default behavior.
	 */
	public TomcatServerExtension() {
		super();
	}

	/**
	 * Create the jupiter and specify the embedded tomcat instance to use.
	 *
	 * @param tomcat The embedded tomcat instance to use.
	 * @throws NullPointerException If {@code tomcat} is {@code null}.
	 */
	public TomcatServerExtension(EmbeddedTomcat tomcat) {
		super(tomcat);
	}

	/**
	 * Create the jupiter and specify the embedded tomcat configuration to use (when using
	 * jupiter with {@link RegisterExtension}).
	 *
	 * @param configuration The embedded tomcat configuration to use.
	 * @throws NullPointerException If {@code configuration} is {@code null}.
	 */
	public TomcatServerExtension(EmbeddedTomcatConfiguration configuration) {
		super(configuration);
	}

	@Override
	protected EmbeddedServer<?> instantiateServer(Class<?> testClass, AbstractConfiguration configuration) {
		final EmbeddedTomcatConfiguration tomcatConfiguration = extractConfiguration(testClass, configuration);
		return tomcatConfiguration == null ? new EmbeddedTomcat() : new EmbeddedTomcat(tomcatConfiguration);
	}

	/**
	 * Try to extract {@link EmbeddedTomcat} configuration from:
	 *

	 *
The given {@code configuration} if it is not {@code null}.

	 *
A class field/method annotated with {@link com.github.mjeanroy.junit.servers.annotations.TestServerConfiguration} on given {@code testClass} otherwise.

	 *

	 *
	 * @param testClass The test class to analyze.
	 * @param configuration The configuration to use if not {@code null}.
	 * @return The {@link EmbeddedTomcat} configuration.
	 * @throws IllegalTomcatConfigurationException If extracted {@code configuration} is not an instance of {@link EmbeddedTomcatConfiguration}.
	 */
	private EmbeddedTomcatConfiguration extractConfiguration(Class<?> testClass, AbstractConfiguration configuration) {
		final AbstractConfiguration configurationToUse = configuration == null ? findConfiguration(testClass) : configuration;

		if (configurationToUse == null) {
			return null;
		}

		if (!(configurationToUse instanceof EmbeddedTomcatConfiguration)) {
			throw new IllegalTomcatConfigurationException();
		}

		return (EmbeddedTomcatConfiguration) configurationToUse;
	}
}


/**
 * The MIT License (MIT)
 *
 * Copyright (c) 2014-2019 <mickael.jeanroy@gmail.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package com.github.mjeanroy.junit.servers.client.impl;

import com.github.mjeanroy.junit.servers.client.HttpHeader;
import com.github.mjeanroy.junit.servers.client.HttpResponse;
import com.github.mjeanroy.junit.servers.commons.ToStringBuilder;

import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;

/**
 * A simple implementation of {@link HttpResponse}.
 */
public final class DefaultHttpResponse extends AbstractHttpResponse implements HttpResponse {

	/**
	 * Create a default HTTP Response from given values.
	 *
	 * @param duration Request-Response duration.
	 * @param status The response status code.
	 * @param body The response body.
	 * @param headers The response headers.
	 * @return The HTTP response.
	 */
	public static DefaultHttpResponse of(long duration, int status, String body, Collection<HttpHeader> headers) {
		return new DefaultHttpResponse(duration, status, body, headers);
	}

	/**
	 * The response status code.
	 */
	private final int status;

	/**
	 * The response body.
	 */
	private final String body;

	/**
	 * The response headers.
	 */
	private final Map<String, HttpHeader> headers;

	private DefaultHttpResponse(long duration, int status, String body, Collection<HttpHeader> headers) {
		super(duration);
		this.status = status;
		this.body = body;

		this.headers = new LinkedHashMap<>();
		for (HttpHeader header : headers) {
			this.headers.put(header.getName().toLowerCase(), header);
		}
	}

	@Override
	public int status() {
		return status;
	}

	@Override
	protected String readResponseBody() {
		return body;
	}

	@Override
	public Collection<HttpHeader> getHeaders() {
		return headers.values();
	}

	@Override
	public HttpHeader getHeader(String name) {
		return headers.get(name.toLowerCase());
	}

	@Override
	public boolean equals(Object o) {
		if (o == this) {
			return true;
		}

		if (o instanceof DefaultHttpResponse) {
			DefaultHttpResponse r = (DefaultHttpResponse) o;
			return super.equals(o)
				&& Objects.equals(status, r.status)
				&& Objects.equals(body, r.body)
				&& Objects.equals(headers, r.headers);
		}

		return false;
	}

	@Override
	public int hashCode() {
		return Objects.hash(super.hashCode(), status, body, headers);
	}

	@Override
	public String toString() {
		return ToStringBuilder.create(getClass())
			.append("duration", getRequestDuration())
			.append("status", status)
			.append("body", body)
			.append("headers", headers)
			.build();
	}

	@Override
	protected boolean canEqual(AbstractHttpResponse o) {
		return o instanceof DefaultHttpResponse;
	}
}


/**
 * The MIT License (MIT)
 *
 * Copyright (c) 2014-2019 <mickael.jeanroy@gmail.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package com.github.mjeanroy.junit.servers.jetty.junit4;

import com.github.mjeanroy.junit.servers.jetty.EmbeddedJetty;
import com.github.mjeanroy.junit.servers.jetty.EmbeddedJettyConfiguration;
import com.github.mjeanroy.junit.servers.jetty.exceptions.IllegalJettyConfigurationException;
import com.github.mjeanroy.junit.servers.junit4.JunitServerRunner;
import com.github.mjeanroy.junit.servers.servers.AbstractConfiguration;
import org.junit.runners.model.InitializationError;

import static com.github.mjeanroy.junit.servers.engine.Servers.findConfiguration;

/**
 * Rule that can be used to start and stop embedded jetty server.
 */
public class JettyServerJunit4Runner extends JunitServerRunner {

	/**
	 * Create runner.
	 *
	 * @param klass Running class.
	 * @throws InitializationError If an error occurred while starting embedded server.
	 */
	public JettyServerJunit4Runner(Class<?> klass) throws InitializationError {
		super(klass, instantiate(klass));
	}

	/**
	 * Instantiate embedded jetty to be used in tests.
	 *
	 * @param klass The tested class.
	 * @return The embedded jetty.
	 */
	private static EmbeddedJetty instantiate(Class<?> klass) {
		final EmbeddedJettyConfiguration configuration = extractConfiguration(klass);
		return configuration == null ? new EmbeddedJetty() : new EmbeddedJetty(configuration);
	}

	/**
	 * Try to extract {@link EmbeddedJetty} configuration from the test class: search for a static
	 * field/method annotated with {@link com.github.mjeanroy.junit.servers.annotations.TestServerConfiguration}).
	 *
	 * @param klass The test class.
	 * @return The {@link EmbeddedJetty} configuration.
	 * @throws IllegalJettyConfigurationException If extracted annotation is not an instance of {@link EmbeddedJetty}.
	 */
	private static EmbeddedJettyConfiguration extractConfiguration(Class<?> klass) {
		final AbstractConfiguration configuration = findConfiguration(klass);

		if (configuration == null) {
			return null;
		}

		if (!(configuration instanceof EmbeddedJettyConfiguration)) {
			throw new IllegalJettyConfigurationException();
		}

		return (EmbeddedJettyConfiguration) configuration;
	}
}


/**
 * The MIT License (MIT)
 *
 * Copyright (c) 2014-2019 <mickael.jeanroy@gmail.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package com.github.mjeanroy.junit.servers.client.impl.okhttp3;

import com.github.mjeanroy.junit.servers.client.HttpClient;
import com.github.mjeanroy.junit.servers.client.HttpClientConfiguration;
import com.github.mjeanroy.junit.servers.client.HttpMethod;
import com.github.mjeanroy.junit.servers.client.HttpRequest;
import com.github.mjeanroy.junit.servers.client.HttpUrl;
import com.github.mjeanroy.junit.servers.client.impl.AbstractHttpClient;
import com.github.mjeanroy.junit.servers.servers.EmbeddedServer;

import java.util.concurrent.atomic.AtomicBoolean;

/**
 * Implementation of {@link HttpClient} using OkHttp library from Square.
 *
 * @see com.github.mjeanroy.junit.servers.client.HttpClientStrategy#OK_HTTP3
 * @see http://square.github.io/okhttp
 */
public class OkHttpClient extends AbstractHttpClient implements HttpClient {

	/**
	 * Create new http client using internal
	 * http client from ok-http library.
	 *
	 * @param server Embedded server.
	 * @return Http client.
	 * @throws NullPointerException If {@code server} is {@code null}.
	 */
	public static OkHttpClient defaultOkHttpClient(EmbeddedServer<?> server) {
		HttpClientConfiguration configuration = HttpClientConfiguration.defaultConfiguration();
		return newOkHttpClient(configuration, server);
	}

	/**
	 * Create new http client using custom configuration.
	 *
	 * @param configuration Client configuration.
	 * @param server Embedded server.
	 * @return Http client.
	 * @throws NullPointerException If {@code server} or {@code configuration} are {@code null}.
	 */
	public static OkHttpClient newOkHttpClient(HttpClientConfiguration configuration, EmbeddedServer<?> server) {
		okhttp3.OkHttpClient client = new okhttp3.OkHttpClient.Builder()
			.followRedirects(configuration.isFollowRedirect())
			.build();

		return new OkHttpClient(configuration, server, client);
	}

	/**
	 * Create new http client using custom internal
	 * http client from ok-http library.
	 *
	 * @param server Embedded server.
	 * @param client The custom client.
	 * @return Http client.
	 * @throws NullPointerException If {@code server} or {@code client} are {@code null}.
	 * @deprecated Use {@link OkHttpClient#newOkHttpClient(HttpClientConfiguration, EmbeddedServer)}
	 */
	@Deprecated
	public static OkHttpClient newOkHttpClient(EmbeddedServer<?> server, okhttp3.OkHttpClient client) {
		return new OkHttpClient(HttpClientConfiguration.defaultConfiguration(), server, client);
	}

	/**
	 * Flag to ensure that the http client has been destroyed or not.
	 */
	private final AtomicBoolean destroyed;

	/**
	 * The native OkHttp client.
	 */
	private final okhttp3.OkHttpClient client;

	/**
	 * Create the client.
	 * @param server The embedded server that will be queried.
	 * @param client The internal client.
	 */
	private OkHttpClient(HttpClientConfiguration configuration, EmbeddedServer<?> server, okhttp3.OkHttpClient client) {
		super(configuration, server);
		this.client = client;
		this.destroyed = new AtomicBoolean(false);
	}

	@Override
	protected HttpRequest buildRequest(HttpMethod httpMethod, HttpUrl endpoint) {
		return new OkHttpRequest(this.client, httpMethod, endpoint);
	}

	@Override
	public void destroy() {
		if (destroyed.compareAndSet(false, true)) {
			client.dispatcher().executorService().shutdown();
			client.connectionPool().evictAll();
		}
	}

	@Override
	public boolean isDestroyed() {
		return destroyed.get();
	}
}


/**
 * The MIT License (MIT)
 *
 * Copyright (c) 2014-2019 <mickael.jeanroy@gmail.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package com.github.mjeanroy.junit.servers.client.impl.ning;

import com.github.mjeanroy.junit.servers.client.HttpClient;
import com.github.mjeanroy.junit.servers.client.HttpClientConfiguration;
import com.github.mjeanroy.junit.servers.client.HttpMethod;
import com.github.mjeanroy.junit.servers.client.HttpRequest;
import com.github.mjeanroy.junit.servers.client.HttpUrl;
import com.github.mjeanroy.junit.servers.client.impl.AbstractHttpClient;
import com.github.mjeanroy.junit.servers.servers.EmbeddedServer;
import com.ning.http.client.AsyncHttpClient;
import com.ning.http.client.AsyncHttpClientConfig;

import static com.github.mjeanroy.junit.servers.commons.Preconditions.notNull;

/**
 * Implementation of {@link HttpClient} using (Ning) async-http-client
 * under the hood.
 *
 * @see https://github.com/ning/async-http-client
 * @see com.github.mjeanroy.junit.servers.client.HttpClientStrategy#NING_ASYNC_HTTP_CLIENT
 */
public class NingAsyncHttpClient extends AbstractHttpClient implements HttpClient {

	/**
	 * Create new http client using custom internal http client.
	 *
	 * @param server Embedded server.
	 * @param client Internal http client
	 * @return Http client.
	 * @throws NullPointerException If {@code server} or {@code client} are {@code null}.
	 * @deprecated Use {@link NingAsyncHttpClient#newAsyncHttpClient(HttpClientConfiguration, EmbeddedServer)}
	 */
	@Deprecated
	public static NingAsyncHttpClient newAsyncHttpClient(EmbeddedServer<?> server, com.ning.http.client.AsyncHttpClient client) {
		return new NingAsyncHttpClient(HttpClientConfiguration.defaultConfiguration(), server, client);
	}

	/**
	 * Create new http client using internal
	 * http client from async-http-client library.
	 * An instance of {com.ning.http.client.NingAsyncHttpClient} will be automatically
	 * created.
	 *
	 * @param server Embedded server.
	 * @return Http client.
	 * @throws NullPointerException If {@code server} is {@code null}.
	 */
	public static NingAsyncHttpClient defaultAsyncHttpClient(EmbeddedServer<?> server) {
		HttpClientConfiguration configuration = HttpClientConfiguration.defaultConfiguration();
		AsyncHttpClientConfig config = new AsyncHttpClientConfig.Builder()
			.setFollowRedirect(configuration.isFollowRedirect())
			.build();

		return new NingAsyncHttpClient(configuration, server, new com.ning.http.client.AsyncHttpClient(config));
	}

	/**
	 * Create new http client using custom configuration.
	 *
	 * @param configuration Client configuration.
	 * @param server Embedded server.
	 * @return Http client.
	 * @throws NullPointerException If {@code server} or {@code configuration} is {@code null}.
	 */
	public static NingAsyncHttpClient newAsyncHttpClient(HttpClientConfiguration configuration, EmbeddedServer<?> server) {
		AsyncHttpClientConfig config = new AsyncHttpClientConfig.Builder()
			.setFollowRedirect(configuration.isFollowRedirect())
			.build();

		AsyncHttpClient client = new AsyncHttpClient(config);
		return new NingAsyncHttpClient(configuration, server, client);
	}

	/**
	 * Original http client.
	 * This client will be used under the hood.
	 */
	private final com.ning.http.client.AsyncHttpClient client;

	// Use static factory
	private NingAsyncHttpClient(HttpClientConfiguration configuration, EmbeddedServer<?> server, com.ning.http.client.AsyncHttpClient client) {
		super(configuration, server);
		this.client = notNull(client, "client");
	}

	@Override
	protected HttpRequest buildRequest(HttpMethod httpMethod, HttpUrl endpoint) {
		return new NingAsyncHttpRequest(client, httpMethod, endpoint);
	}

	@Override
	public void destroy() {
		client.close();
	}

	@Override
	public boolean isDestroyed() {
		return client.isClosed();
	}
}


/**
 * The MIT License (MIT)
 *
 * Copyright (c) 2014-2019 <mickael.jeanroy@gmail.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package com.github.mjeanroy.junit.servers.commons;

import com.github.mjeanroy.junit.servers.exceptions.ReflectionException;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.LinkedList;
import java.util.List;

import static com.github.mjeanroy.junit.servers.commons.CollectionUtils.filter;
import static java.lang.reflect.Modifier.isStatic;
import static java.util.Arrays.asList;
import static java.util.Collections.addAll;

/**
 * Static reflection utilities.
 *
 *

 *
 * Internal API: these methods are part of the internal API and may be removed, have their signature change,
 * or have their access level decreased from public to protected, package, or private in future versions without notice.
 */
public final class ReflectionUtils {

	// Ensure non instantiation.
	private ReflectionUtils() {
	}

	/**
	 * Get all fields on given object and look for fields of
	 * super classes.
	 *
	 * @param type Class to inspect.
	 * @return Fields.
	 */
	public static List<Field> findAllFields(Class<?> type) {
		List<Field> fields = new LinkedList<>();
		if (type != null) {
			addAll(fields, type.getDeclaredFields());

			if (type.getSuperclass() != null) {
				fields.addAll(findAllFields(type.getSuperclass()));
			}
		}
		return fields;
	}

	/**
	 * Get all static fields on given class object.
	 *
	 * @param type Class to inspect.
	 * @return Fields.
	 */
	private static List<Field> findStaticFields(Class<?> type) {
		return filter(asList(type.getDeclaredFields()), STATIC_FIELD_PREDICATE);
	}

	/**
	 * Get all static methods on given class object.
	 *
	 * @param type Class to inspect.
	 * @return Fields.
	 */
	private static List<Method> findStaticMethods(Class<?> type) {
		return filter(asList(type.getDeclaredMethods()), STATIC_METHOD_PREDICATE);
	}

	/**
	 * Get all static fields on given class object
	 * annotated with given annotation.
	 *
	 * @param type Class to inspect.
	 * @param klass Annotation class.
	 * @return Fields.
	 */
	public static List<Field> findStaticFieldsAnnotatedWith(Class<?> type, Class<? extends Annotation> klass) {
		List<Field> fields = findStaticFields(type);
		return filter(fields, new FieldAnnotatedWithPredicate(klass));
	}

	/**
	 * Get all static methods on given class object
	 * annotated with given annotation.
	 *
	 * @param type Class to inspect.
	 * @param klass Annotation class.
	 * @return Fields.
	 */
	public static List<Method> findStaticMethodsAnnotatedWith(Class<?> type, Class<? extends Annotation> klass) {
		List<Method> methods = findStaticMethods(type);
		return filter(methods, new MethodAnnotatedWithPredicate(klass));
	}

	/**
	 * Set value of given field on given instance.
	 *
	 * @param instance Instance.
	 * @param field Field.
	 * @param value Field value.
	 * @throws ReflectionException if set operation is not permitted.
	 */
	public static void setter(Object instance, Field field, Object value) {
		boolean forceAccess = false;

		try {
			if (!field.isAccessible()) {
				forceAccess = true;
				field.setAccessible(true);
			}

			field.set(instance, value);
		}
		catch (IllegalAccessException ex) {
			throw new ReflectionException(ex);
		}
		finally {
			if (forceAccess) {
				field.setAccessible(false);
			}
		}
	}

	/**
	 * Get value of static field.
	 *
	 * @param field Field.
	 * @param <T> Type of expected value.
	 * @return Value of static field.
	 */
	public static <T> T getter(Field field) {
		return getter(null, field);
	}

	/**
	 * Get value of field on target object.
	 * If target is null, it means that field is static and do not
	 * need any target instance.
	 *
	 * @param target Target object.
	 * @param field Field.
	 * @param <T> Type of expected value.
	 * @return Field value.
	 */
	@SuppressWarnings("unchecked")
	public static <T> T getter(Object target, Field field) {
		boolean forceAccess = false;

		try {
			if (!field.isAccessible()) {
				field.setAccessible(true);
				forceAccess = true;
			}

			return (T) field.get(target);
		}
		catch (IllegalAccessException ex) {
			throw new ReflectionException(ex);
		}
		finally {
			if (forceAccess) {
				field.setAccessible(false);
			}
		}
	}

	/**
	 * Invoke the static method without any arguments.
	 *
	 * @param method The method to invoke.
	 * @param <T> Return type.
	 * @return The result of the method invocation.
	 */
	@SuppressWarnings("unchecked")
	public static <T> T invoke(Method method) {
		boolean forceAccess = false;

		try {
			if (!method.isAccessible()) {
				method.setAccessible(true);
				forceAccess = true;
			}

			return (T) method.invoke(null);
		}
		catch (InvocationTargetException | IllegalAccessException ex) {
			throw new ReflectionException(ex);
		}
		finally {
			if (forceAccess) {
				method.setAccessible(false);
			}
		}
	}

	/**
	 * Predicate that will return {@code true} if the field in parameter is annotated with a
	 * given {@link Annotation}.
	 */
	private static class FieldAnnotatedWithPredicate implements Predicate<Field> {

		/**
		 * The annotation to check.
		 */
		private final Class<? extends Annotation> annotationKlass;

		/**
		 * Create the predicate.
		 *
		 * @param annotationKlass The annotation to check.
		 */
		private FieldAnnotatedWithPredicate(Class<? extends Annotation> annotationKlass) {
			this.annotationKlass = annotationKlass;
		}

		@Override
		public boolean apply(Field field) {
			return field.isAnnotationPresent(annotationKlass);
		}
	}

	/**
	 * Predicate that will return {@code true} if the method in parameter is annotated with a
	 * given {@link Annotation}.
	 */
	private static class MethodAnnotatedWithPredicate implements Predicate<Method> {

		/**
		 * The annotation to check.
		 */
		private final Class<? extends Annotation> annotationKlass;

		/**
		 * Create the predicate.
		 *
		 * @param annotationKlass The annotation to check.
		 */
		private MethodAnnotatedWithPredicate(Class<? extends Annotation> annotationKlass) {
			this.annotationKlass = annotationKlass;
		}

		@Override
		public boolean apply(Method method) {
			return method.isAnnotationPresent(annotationKlass);
		}
	}

	/**
	 * Predicate that will return {@code true} if the field in parameter is a static field.
	 */
	private static final Predicate<Field> STATIC_FIELD_PREDICATE = new Predicate<Field>() {
		@Override
		public boolean apply(Field field) {
			return isStatic(field.getModifiers());
		}
	};

	/**
	 * Predicate that will return {@code true} if the method in parameter is a static method.
	 */
	private static final Predicate<Method> STATIC_METHOD_PREDICATE = new Predicate<Method>() {
		@Override
		public boolean apply(Method object) {
			return isStatic(object.getModifiers());
		}
	};
}