Add lightweight context

This commit is contained in:
etorres
2016-01-01 00:24:51 +01:00
parent e36feb10a2
commit 2b90275ecd
16 changed files with 789 additions and 73 deletions
+11
View File
@@ -31,3 +31,14 @@ Utilities to facilitate Java applications development.
``$ mvn clean verify coreutils``
## Examples (pending)
- Spring Boot + Undertow
## TO-DO list:
1. Improve documentation: this is not framework, this is not a utility library. If you need a framework, we recommend Spring (Spring Boot, Spring Data, Spring REST and Spring Cloud) as a full-featured framework. For small and medium-sized projects we recommend the Jodd micro-framework. http://jodd.org/ ...
2. Migrate part of the naming utilities.
3. HTTP2 client: get access to the original instance (OkHttpClient) using the clone method in the case of the manager client.
4. Check that query parameters are proxied in opengateway.
5. Add global shutdown hook and register tasks.
+2 -1
View File
@@ -24,10 +24,11 @@ that you distribute must include a readable copy of the "NOTICE" text file.
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>es.upv.grycap.coreutils</groupId>
<artifactId>coreutils</artifactId>
<version>0.1.0</version>
<version>0.2.0</version>
</parent>
<artifactId>coreutils-common</artifactId>
@@ -0,0 +1,230 @@
/*
* Core Utils - Common Utilities.
* Copyright 2015-2016 GRyCAP (Universitat Politecnica de Valencia)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* This product combines work with different licenses. See the "NOTICE" text
* file for details on the various modules and licenses.
*
* The "NOTICE" text file is part of the distribution. Any derivative works
* that you distribute must include a readable copy of the "NOTICE" text file.
*/
package es.upv.grycap.coreutils.common;
import static java.util.Collections.emptyMap;
import static java.util.Objects.requireNonNull;
import static java.util.Optional.ofNullable;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Supplier;
import javax.annotation.Nullable;
import javax.annotation.concurrent.ThreadSafe;
/**
* Lightweight and unopinionated central class for providing configuration information to a coreutils-powered application.
* @author Erik Torres <etserrano@gmail.com>
* @since 0.2.0
*/
@ThreadSafe
public enum CoreutilsContext {
COREUTILS_CONTEXT;
private static final long TIMEOUT_MILLISECS = 2000l;
private final Map<ContextKey, Map<String, Object>> registry = new EnumMap<>(ContextKey.class);
private final Lock mutex = new ReentrantLock();
private final BooleanReference register = new BooleanReference(false);
/**
* Gets a client from the registry.
* @param type - the expected type of client
* @return An instance that matches the specified type, or <tt>null</tt> if no instance with the specified properties is
* found in the registry.
*/
@Nullable
public <T> T getClient(final Class<T> type) {
return getClient(type, null, null);
}
/**
* Gets a client from the registry. Callers can optionally specify a classifier.
* @param type - the expected type of client
* @param classifier - (optional) only clients matching the classifier will be selected
* @return An instance that matches the specified type and classifier, or <tt>null</tt> if no instance with the specified
* properties is found in the registry.
*/
@Nullable
public <T> T getClient(final Class<T> type, final @Nullable String classifier) {
return getClient(type, classifier, null);
}
/**
* Gets a client from the registry. In case that no instance with the specified properties is found in the registry, the
* optional supplier will be used to create a new instance that will be registered.
* @param type - the expected type of client
* @param supplier - (optional) client factory
* @return An instance that matches the specified type, or a new instance created with the supplier if no instance with
* the specified properties is found in the registry.
*/
@Nullable
public <T> T getClient(final Class<T> type, final @Nullable Supplier<T> supplier) {
return getClient(type, null, supplier);
}
/**
* Gets a client from the registry. Callers can optionally specify a classifier. In case that no instance with the specified
* properties is found in the registry, the optional supplier will be used to create a new instance that will be registered.
* @param type - the expected type of client
* @param classifier - (optional) only clients matching the classifier will be selected
* @param supplier - (optional) client factory
* @return An instance that matches the specified type and classifier, or a new instance created with the supplier if no
* instance with the specified properties is found in the registry.
*/
@Nullable
public <T> T getClient(final Class<T> type, final @Nullable String classifier, final @Nullable Supplier<T> supplier) {
return get(ContextKey.CLIENTS, type, classifier, supplier);
}
/**
* Registers a new shutdown listener for the specified type.
* @param listener - the listener that will be registered
* @param type - the type of the shutdown listener
*/
public <T extends ShutdownListener> void addShutdownListener(final T listener, final Class<T> type) {
addShutdownListener(listener, type, null);
}
/**
* Registers a new shutdown listener for the specified type with an optional classifier.
* @param listener - the listener that will be registered
* @param type - the type of the shutdown listener
* @param classifier - (optional) add this classifier to the registry
*/
public <T extends ShutdownListener> void addShutdownListener(final T listener, final Class<T> type, final @Nullable String classifier) {
requireNonNull(listener, "A non-null listener expected");
final ShutdownListener listener2 = get(ContextKey.SHUTDOWN_LISTENERS, type, classifier, () -> listener);
if (listener != listener2) {
throw new IllegalStateException("A previous shutdown listener was registered, try with a different classifier");
}
}
/**
* Unregister a shutdown listener.
* @param type - the type of the shutdown listener
*/
public <T extends ShutdownListener> void removeShutdownListener(final Class<T> type) {
removeShutdownListener(type, null);
}
/**
* Unregister a shutdown listener.
* @param type - the type of the shutdown listener
* @param classifier - (optional) classifier
*/
public <T extends ShutdownListener> void removeShutdownListener(final Class<T> type, final @Nullable String classifier) {
remove(ContextKey.SHUTDOWN_LISTENERS, type, classifier);
}
@Nullable
@SuppressWarnings("unchecked")
private <T> T get(final ContextKey key, final Class<T> type, final @Nullable String classifier, final @Nullable Supplier<T> supplier) {
requireNonNull(type, "A non-null type expected");
try {
mutex.tryLock(TIMEOUT_MILLISECS, MILLISECONDS);
try {
// lazy initialization of the registry
register.set(false);
final Map<String, Object> map = ofNullable(registry.get(key)).orElseGet(() -> {
register.set(true);
return new HashMap<>(1);
});
if (register.get()) registry.put(key, map);
// get or create the instance
register.set(false);
final String instanceKey = instanceKey(type, classifier);
final T instance = (T)ofNullable(map.get(instanceKey)).orElseGet(() -> {
final T tmp = ofNullable(supplier).orElse(() -> null).get();
if (tmp != null) register.set(true);
return tmp;
});
if (register.get()) map.put(instanceKey, instance);
return instance;
} finally {
mutex.unlock();
}
} catch (InterruptedException e) {
throw new IllegalStateException("The operation was interrupted");
}
}
private <T> void remove(final ContextKey key, final Class<T> type, final @Nullable String classifier) {
requireNonNull(type, "A non-null type expected");
try {
mutex.tryLock(TIMEOUT_MILLISECS, MILLISECONDS);
try {
ofNullable(registry.get(key)).orElse(emptyMap()).remove(instanceKey(type, classifier));
} finally {
mutex.unlock();
}
} catch (InterruptedException e) {
throw new IllegalStateException("The operation was interrupted");
}
}
private <T> String instanceKey(final Class<T> type, final @Nullable String classifier) {
return String.format("%s@%s", type.getCanonicalName(), ofNullable(classifier).orElse(""));
}
/**
* The different types of objects that can be stored within this context.
* @author Erik Torres <etserrano@gmail.com>
* @since 0.2.0
*/
private enum ContextKey {
CLIENTS,
SHUTDOWN_LISTENERS
}
/**
* Provides a reference to a boolean variable but without the synchronization cost of a atomic type.
* @author Erik Torres <etserrano@gmail.com>
* @since 0.2.0
*/
private static class BooleanReference {
private boolean value = false;
public BooleanReference(final boolean value) {
this.value = value;
}
public boolean get() {
return value;
}
public void set(final boolean value) {
this.value = value;
}
}
}
@@ -0,0 +1,111 @@
/*
* Core Utils - Common Utilities.
* Copyright 2015-2016 GRyCAP (Universitat Politecnica de Valencia)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* This product combines work with different licenses. See the "NOTICE" text
* file for details on the various modules and licenses.
*
* The "NOTICE" text file is part of the distribution. Any derivative works
* that you distribute must include a readable copy of the "NOTICE" text file.
*/
package es.upv.grycap.coreutils.common;
import static java.util.Collections.synchronizedMap;
import static java.util.Objects.requireNonNull;
import static java.util.concurrent.Executors.newSingleThreadExecutor;
import static java.util.concurrent.TimeUnit.SECONDS;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import javax.annotation.concurrent.ThreadSafe;
/**
* Executes a shutdown sequence when the JVM shutdowns, calling the {@link ShutdownListener#stop()} method of the registered listeners
* and waiting until they finish or a timeout expires.
* @author Erik Torres <etserrano@gmail.com>
* @since 0.2.0
*/
@ThreadSafe
public class ShutdownHook {
private final int TIMEOUT_SECS = 8;
private final Thread hook;
/**
* Thread-safe map that preserves the insertion order of the entries.
*/
private final Map<String, ShutdownListener> listeners = synchronizedMap(new LinkedHashMap<String, ShutdownListener>());
/**
* Creates an instance of this class and registers it with the JVM.
*/
public ShutdownHook() {
hook = new Thread() {
@Override
public void run() {
final ExecutorService executor = newSingleThreadExecutor();
for (final Map.Entry<String, ShutdownListener> entry : listeners.entrySet()) {
final ShutdownListener listener = entry.getValue();
if (listener != null) {
executor.execute(new Runnable() {
@Override
public void run() {
try {
listener.stop();
} catch (Exception ignore) { }
}
});
}
}
try {
if (!executor.awaitTermination(TIMEOUT_SECS, SECONDS)) {
executor.shutdown();
}
} catch (Exception e) {
// force shutdown if current thread also interrupted, preserving interrupt status
executor.shutdown();
if (e instanceof InterruptedException) {
Thread.currentThread().interrupt();
}
}
}
};
Runtime.getRuntime().addShutdownHook(hook);
}
/**
* Registers a listener for shutdown.
* @param listener - the listener that will be added to the shutdown sequence
*/
public void register(final ShutdownListener listener) {
requireNonNull(listener, "A non-null listener expected");
final String name = listener.getClass().getCanonicalName();
listeners.put(name, listener);
}
/**
* Cancels the execution of the shutdown sequence specified in this class.
*/
public void cancel() {
try {
Runtime.getRuntime().removeShutdownHook(hook);
} catch (Exception ignore) { }
listeners.clear();
}
}
@@ -0,0 +1,53 @@
/*
* Core Utils - Common Utilities.
* Copyright 2015-2016 GRyCAP (Universitat Politecnica de Valencia)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* This product combines work with different licenses. See the "NOTICE" text
* file for details on the various modules and licenses.
*
* The "NOTICE" text file is part of the distribution. Any derivative works
* that you distribute must include a readable copy of the "NOTICE" text file.
*/
package es.upv.grycap.coreutils.common;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Shutdown listener.
* @author Erik Torres <etserrano@gmail.com>
* @since 0.2.0
*/
public abstract class ShutdownListener {
/**
* Maintains the status of the instance.
*/
protected final AtomicBoolean isRunning = new AtomicBoolean(false);
/**
* Sets the value of {@link #isRunning} to <tt>true</tt>.
*/
public void init() {
isRunning.compareAndSet(false, true);
}
/**
* Calling this method should set the value of {@link #isRunning} to <tt>false</tt>. Implementations should check that the value is
* <tt>true</tt> before entering the stop sequence.
*/
public abstract void stop();
}
@@ -0,0 +1,141 @@
/*
* Core Utils - Common Utilities.
* Copyright 2015-2016 GRyCAP (Universitat Politecnica de Valencia)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* This product combines work with different licenses. See the "NOTICE" text
* file for details on the various modules and licenses.
*
* The "NOTICE" text file is part of the distribution. Any derivative works
* that you distribute must include a readable copy of the "NOTICE" text file.
*/
package es.upv.grycap.coreutils.common.test;
import static es.upv.grycap.coreutils.common.CoreutilsContext.COREUTILS_CONTEXT;
import static org.hamcrest.CoreMatchers.allOf;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.MatcherAssert.assertThat;
import java.util.Objects;
import java.util.Random;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.junit.rules.TestRule;
import es.upv.grycap.coreutils.common.ShutdownListener;
import es.upv.grycap.coreutils.test.category.FunctionalTests;
import es.upv.grycap.coreutils.test.rules.TestPrinter;
import es.upv.grycap.coreutils.test.rules.TestWatcher2;
/**
* Tests the context.
* @author Erik Torres <etserrano@gmail.com>
* @since 0.2.0
*/
@Category(FunctionalTests.class)
public class CoreutilsContextTest {
@Rule
public TestPrinter pw = new TestPrinter(true);
@Rule
public TestRule watchman = new TestWatcher2(pw);
@BeforeClass
public static void setup() {
COREUTILS_CONTEXT.addShutdownListener(new FakeShutdownListener(), FakeShutdownListener.class, "duplicate");
}
@AfterClass
public static void cleanup() {
COREUTILS_CONTEXT.removeShutdownListener(FakeShutdownListener.class, "duplicate");
}
@Test
public void testRegistry() throws Exception {
assertThat("Client is null", COREUTILS_CONTEXT.getClient(FakeClient.class), nullValue());
final FakeClient client = COREUTILS_CONTEXT.getClient(FakeClient.class, FakeClient::new);
assertThat("Client is not null", client, notNullValue());
FakeClient client2 = COREUTILS_CONTEXT.getClient(FakeClient.class, FakeClient::new);
assertThat("Client coincides with expected", client2, allOf(notNullValue(), equalTo(client)));
client2 = COREUTILS_CONTEXT.getClient(FakeClient.class, "classifier", FakeClient::new);
assertThat("Client coincides with expected", client2, allOf(notNullValue(), not(equalTo(client))));
}
@Test
public void testShutdownhook() throws Exception {
COREUTILS_CONTEXT.addShutdownListener(new FakeShutdownListener(), FakeShutdownListener.class);
COREUTILS_CONTEXT.removeShutdownListener(FakeShutdownListener.class);
}
@Test(expected=IllegalStateException.class)
public void testDuplicateShutdownhook() {
COREUTILS_CONTEXT.addShutdownListener(new FakeShutdownListener(), FakeShutdownListener.class, "duplicate");
}
/**
* Client mock-up.
* @author Erik Torres <etserrano@gmail.com>
* @since 0.2.0
*/
public static class FakeClient {
private final int id = new Random().nextInt();
public int getId() {
return id;
}
@Override
public boolean equals(final Object obj) {
if (obj == null || !(obj instanceof FakeClient)) {
return false;
}
final FakeClient other = FakeClient.class.cast(obj);
return Objects.equals(id, other.id);
}
@Override
public int hashCode() {
return Objects.hash(id);
}
}
/**
* Shutdown listener mock-up.
* @author Erik Torres <etserrano@gmail.com>
* @since 0.2.0
*/
public static class FakeShutdownListener extends ShutdownListener {
@Override
public void stop() {
if (isRunning.get());
}
}
}
+2 -2
View File
@@ -28,7 +28,7 @@ that you distribute must include a readable copy of the "NOTICE" text file.
<parent>
<groupId>es.upv.grycap.coreutils</groupId>
<artifactId>coreutils</artifactId>
<version>0.1.0</version>
<version>0.2.0</version>
</parent>
<artifactId>coreutils-fiber</artifactId>
@@ -138,7 +138,7 @@ that you distribute must include a readable copy of the "NOTICE" text file.
<version>${m2e.lifecycle-mapping.version}</version>
<configuration>
<lifecycleMappingMetadata>
<pluginExecutions>
<pluginExecutions>
<pluginExecution>
<pluginExecutionFilter>
<groupId>org.apache.maven.plugins</groupId>
@@ -24,30 +24,19 @@
package es.upv.grycap.coreutils.fiber.http;
import static com.google.common.collect.Lists.newArrayList;
import static java.nio.file.Files.createTempDirectory;
import static java.nio.file.attribute.PosixFilePermissions.asFileAttribute;
import static java.nio.file.attribute.PosixFilePermissions.fromString;
import static java.util.Collections.emptyList;
import static java.util.Objects.requireNonNull;
import static java.util.Optional.ofNullable;
import static org.apache.commons.lang3.StringUtils.trimToNull;
import static org.slf4j.LoggerFactory.getLogger;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Supplier;
import javax.annotation.Nullable;
import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import com.squareup.okhttp.Cache;
import com.squareup.okhttp.CacheControl;
import com.squareup.okhttp.Callback;
import com.squareup.okhttp.MediaType;
@@ -55,7 +44,6 @@ import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.RequestBody;
import co.paralleluniverse.fibers.okhttp.FiberOkHttpClient;
import okio.BufferedSink;
/**
@@ -63,49 +51,16 @@ import okio.BufferedSink;
* @author Erik Torres <etserrano@gmail.com>
* @since 0.1.0
*/
public class Http2Client {
public final class Http2Client {
private static final Logger LOGGER = getLogger(Http2Client.class);
private static final int CACHE_SIZE_MIB = 32 * 1024 * 1024; // 32 MiB
private static OkHttpClient __client = null;
private Lock mutex = new ReentrantLock();
public static Http2Client getHttp2Client() {
return new Http2Client();
}
private final OkHttpClient client;
/**
* Creates a {@link OkHttpClient} instance and configures it with a cache. The same instance is used across the application to
* benefit from a common cache storage and to prevent cache corruption. The cache directory is created private to the user who
* runs the application and its content is deleted when the JVM starts its shutting down sequence.
* @return A {@link OkHttpClient} instance that can be used everywhere in the application.
* Access to this constructor is restricted to the classes in the same package. A factory method should be used to
* create new instances of this class.
*/
private OkHttpClient client() {
mutex.lock();
try {
if (__client == null) {
__client = new FiberOkHttpClient();
try {
final File cacheDir = createTempDirectory("coreutils-okhttp-cache-", asFileAttribute(fromString("rwx------"))).toFile();
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
FileUtils.deleteQuietly(cacheDir);
}
});
final Cache cache = new Cache(cacheDir, CACHE_SIZE_MIB);
__client.setCache(cache);
} catch (IOException e) {
LOGGER.error("Failed to create directory cache", e);
}
}
return __client;
} finally {
mutex.unlock();
}
Http2Client(final OkHttpClient client) {
this.client = requireNonNull(client, "A valid HTTP2 client expected");
}
/**
@@ -146,7 +101,7 @@ public class Http2Client {
final Request.Builder requestBuilder = new Request.Builder().cacheControl(cacheControlBuilder.build()).url(url2);
ofNullable(acceptableMediaTypes).orElse(emptyList()).stream().filter(Objects::nonNull).forEach(type -> requestBuilder.addHeader("Accept", type));
// submit request
client().newCall(requestBuilder.build()).enqueue(callback);
client.newCall(requestBuilder.build()).enqueue(callback);
}
/**
@@ -185,7 +140,7 @@ public class Http2Client {
}
}).build();
// submit request
client().newCall(request).enqueue(callback);
client.newCall(request).enqueue(callback);
}
/**
@@ -224,7 +179,7 @@ public class Http2Client {
}
}).build();
// submit request
client().newCall(request).enqueue(callback);
client.newCall(request).enqueue(callback);
}
/**
@@ -238,7 +193,7 @@ public class Http2Client {
// prepare request
final Request request = new Request.Builder().url(url2).delete().build();
// submit request
client().newCall(request).enqueue(callback);
client.newCall(request).enqueue(callback);
}
}
@@ -0,0 +1,116 @@
/*
* Core Utils - Fiber-enabled clients.
* Copyright 2015-2016 GRyCAP (Universitat Politecnica de Valencia)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* This product combines work with different licenses. See the "NOTICE" text
* file for details on the various modules and licenses.
*
* The "NOTICE" text file is part of the distribution. Any derivative works
* that you distribute must include a readable copy of the "NOTICE" text file.
*/
package es.upv.grycap.coreutils.fiber.http;
import static es.upv.grycap.coreutils.common.CoreutilsContext.COREUTILS_CONTEXT;
import static java.nio.file.Files.createTempDirectory;
import static java.nio.file.attribute.PosixFilePermissions.asFileAttribute;
import static java.nio.file.attribute.PosixFilePermissions.fromString;
import static org.apache.commons.io.FileUtils.deleteQuietly;
import static org.slf4j.LoggerFactory.getLogger;
import java.io.File;
import java.io.IOException;
import org.slf4j.Logger;
import com.squareup.okhttp.Cache;
import com.squareup.okhttp.OkHttpClient;
import co.paralleluniverse.fibers.okhttp.FiberOkHttpClient;
import es.upv.grycap.coreutils.common.ShutdownListener;
/**
* Factory class that creates new {@link Http2Client} instances.
* @author Erik Torres <etserrano@gmail.com>
* @since 0.2.0
*/
public final class Http2Clients {
private static final Logger LOGGER = getLogger(Http2Clients.class);
private static final int CACHE_SIZE_MIB = 32 * 1024 * 1024; // 32 MiB
/**
* Gets a HTTP2 client which is expected to integrate with other tasks managed by coreutils.
* @return An HTTP2 client managed by coreutils.
*/
public static Http2Client http2Client() {
return new Http2Client(client());
}
/**
* Gets a new instance of the HTTP2 client that is not managed by coreutils in any way. Use this client if your application
* manages its own threads or if you use any other kind of concurrent execution outside coreutils.
* @return An unmanaged instance of the HTTP2 client.
*/
public static Http2Client isolatedHttp2Client() {
return new Http2Client(new OkHttpClient());
}
/**
* Creates a {@link OkHttpClient} instance and configures it with a cache. The same instance is used across the application to
* benefit from a common cache storage and to prevent cache corruption. The cache directory is created private to the user who
* runs the application and its content is deleted when the JVM starts its shutting down sequence.
* @return A {@link OkHttpClient} instance that can be used everywhere in the application.
*/
private static OkHttpClient client() {
return COREUTILS_CONTEXT.getClient(OkHttpClient.class, "coreutils-fiber", () -> {
final OkHttpClient client = new FiberOkHttpClient();
try {
final File cacheDir = createTempDirectory("coreutils-okhttp-cache-", asFileAttribute(fromString("rwx------"))).toFile();
final Http2ClientShutdownListener shutdownListener = new Http2ClientShutdownListener(cacheDir);
COREUTILS_CONTEXT.addShutdownListener(shutdownListener, Http2ClientShutdownListener.class, "coreutils-fiber");
final Cache cache = new Cache(cacheDir, CACHE_SIZE_MIB);
client.setCache(cache);
} catch (IOException e) {
LOGGER.error("Failed to create directory cache", e);
}
return client;
});
}
/**
* Shutdown listener to delete cache directory on application exit.
* @author Erik Torres <etserrano@gmail.com>
* @since 0.2.0
*/
public static class Http2ClientShutdownListener extends ShutdownListener {
private final File cacheDir;
public Http2ClientShutdownListener(final File cacheDir) {
this.cacheDir = cacheDir;
}
@Override
public void stop() {
if (isRunning.getAndSet(false)) {
deleteQuietly(cacheDir);
}
}
}
}
@@ -24,7 +24,8 @@
package es.upv.grycap.coreutils.fiber.test;
import static com.google.common.collect.ImmutableList.of;
import static es.upv.grycap.coreutils.fiber.http.Http2Client.getHttp2Client;
import static es.upv.grycap.coreutils.fiber.http.Http2Clients.http2Client;
import static es.upv.grycap.coreutils.fiber.http.Http2Clients.isolatedHttp2Client;
import static es.upv.grycap.coreutils.fiber.test.mockserver.FiberExpectationInitializer.MOCK_SERVER_BASE_URL;
import static org.hamcrest.CoreMatchers.allOf;
import static org.hamcrest.CoreMatchers.equalTo;
@@ -64,11 +65,20 @@ public class HighlyConcurrencyTest {
public TestRule watchman = new TestWatcher2(pw);
@Test
public void test() throws Exception {
// create the client
final Http2Client client = getHttp2Client();
assertThat("HTTP+SPDY client was created", client, notNullValue());
public void testManagedClient() throws Exception {
final Http2Client client = http2Client();
assertThat("HTTP+SPDY managed client was created", client, notNullValue());
runTest(client);
}
@Test
public void testIsolatedClient() throws Exception {
final Http2Client client = isolatedHttp2Client();
assertThat("HTTP+SPDY isolated client was created", client, notNullValue());
runTest(client);
}
private void runTest(final Http2Client client) throws Exception {
// prepare the test
final Waiter waiter = new Waiter();
for (int i = 0; i < 100; i++) {
@@ -88,5 +98,5 @@ public class HighlyConcurrencyTest {
});
}
waiter.await(30l, TimeUnit.SECONDS, 100);
}
}
}
@@ -25,7 +25,7 @@ package es.upv.grycap.coreutils.fiber.test;
import static com.google.common.collect.ImmutableList.of;
import static com.google.common.collect.Lists.newArrayList;
import static es.upv.grycap.coreutils.fiber.http.Http2Client.getHttp2Client;
import static es.upv.grycap.coreutils.fiber.http.Http2Clients.http2Client;
import static es.upv.grycap.coreutils.fiber.test.mockserver.FiberExpectationInitializer.MOCK_SERVER_BASE_URL;
import static es.upv.grycap.coreutils.fiber.test.mockserver.ObjectResponseValidator.isValidJson;
import static es.upv.grycap.coreutils.fiber.test.mockserver.ObjectResponseValidator.isValidXml;
@@ -115,7 +115,7 @@ public class Http2ClientTest {
@Test
public void test() throws Exception {
// create the client
final Http2Client client = getHttp2Client();
final Http2Client client = http2Client();
assertThat("HTTP+SPDY client was created", client, notNullValue());
// prepare the test
+2 -1
View File
@@ -24,10 +24,11 @@ that you distribute must include a readable copy of the "NOTICE" text file.
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>es.upv.grycap.coreutils</groupId>
<artifactId>coreutils</artifactId>
<version>0.1.0</version>
<version>0.2.0</version>
</parent>
<artifactId>coreutils-logging</artifactId>
+1 -1
View File
@@ -28,7 +28,7 @@ that you distribute must include a readable copy of the "NOTICE" text file.
<parent>
<groupId>es.upv.grycap.coreutils</groupId>
<artifactId>coreutils</artifactId>
<version>0.1.0</version>
<version>0.2.0</version>
</parent>
<artifactId>coreutils-test</artifactId>
@@ -0,0 +1,31 @@
/*
* Core Utils - Testing utilities.
* Copyright 2015-2016 GRyCAP (Universitat Politecnica de Valencia)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* This product combines work with different licenses. See the "NOTICE" text
* file for details on the various modules and licenses.
*
* The "NOTICE" text file is part of the distribution. Any derivative works
* that you distribute must include a readable copy of the "NOTICE" text file.
*/
package es.upv.grycap.coreutils.test.category;
/**
* Categorize tests that are intended to evaluate the performance of the application code.
* @author Erik Torres <etserrano@gmail.com>
* @since 0.1.0
*/
public class CodeBenchmarks { }
@@ -24,7 +24,7 @@
package es.upv.grycap.coreutils.test.category;
/**
* Categorize unit tests that belongs to the integration testing group.
* Categorize tests that belongs to the integration testing group.
* @author Erik Torres <etserrano@gmail.com>
* @since 0.1.0
*/
+60 -4
View File
@@ -25,9 +25,15 @@ that you distribute must include a readable copy of the "NOTICE" text file.
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.sonatype.oss</groupId>
<artifactId>oss-parent</artifactId>
<version>9</version>
</parent>
<groupId>es.upv.grycap.coreutils</groupId>
<artifactId>coreutils</artifactId>
<version>0.1.0</version>
<version>0.2.0</version>
<packaging>pom</packaging>
<name>Core utils project</name>
@@ -72,9 +78,9 @@ that you distribute must include a readable copy of the "NOTICE" text file.
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<!-- Convenience properties to set library versions -->
<ch.qos.logback.version>1.1.3</ch.qos.logback.version>
<junit.version>4.12</junit.version> <!-- affects Hamcrest version -->
<ch.qos.logback.version>1.1.3</ch.qos.logback.version>
<es.upv.grycap.coreutils.version>${project.version}</es.upv.grycap.coreutils.version>
<junit.version>4.12</junit.version> <!-- affects Hamcrest version -->
<org.hamcrest.version>1.3</org.hamcrest.version> <!-- affected by JUnit version -->
<org.mock-server.version>3.10.2</org.mock-server.version>
<org.slf4j.version>1.7.13</org.slf4j.version>
@@ -122,7 +128,7 @@ that you distribute must include a readable copy of the "NOTICE" text file.
<groupId>com.typesafe</groupId>
<artifactId>config</artifactId>
<version>1.3.0</version>
</dependency>
</dependency>
<!-- Integrates with Quasar fibers via the Comsat APIs -->
<dependency>
@@ -206,6 +212,31 @@ that you distribute must include a readable copy of the "NOTICE" text file.
<build>
<pluginManagement>
<plugins>
<!-- Prevent Eclipse from executing unnecessary plugins during development -->
<plugin>
<groupId>org.eclipse.m2e</groupId>
<artifactId>lifecycle-mapping</artifactId>
<version>${m2e.lifecycle-mapping.version}</version>
<configuration>
<lifecycleMappingMetadata>
<pluginExecutions>
<pluginExecution>
<pluginExecutionFilter>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<versionRange>[1.0.0,)</versionRange>
<goals>
<goal>enforce</goal>
</goals>
</pluginExecutionFilter>
<action>
<ignore />
</action>
</pluginExecution>
</pluginExecutions>
</lifecycleMappingMetadata>
</configuration>
</plugin>
<!-- Maven Compiler Plugin -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
@@ -305,6 +336,31 @@ that you distribute must include a readable copy of the "NOTICE" text file.
<quiet>true</quiet>
</configuration>
</plugin>
<plugin> <!-- Override outdated versions from OSS -->
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-release-plugin</artifactId>
<version>2.5.3</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<version>1.4.1</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>2.4</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>2.10.3</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>1.6</version>
</plugin>
</plugins>
</pluginManagement>
<plugins>