From 2b90275ecd8cf1b53c577d8b3b70c12ea3484f6f Mon Sep 17 00:00:00 2001 From: etorres Date: Fri, 1 Jan 2016 00:24:51 +0100 Subject: [PATCH] Add lightweight context --- README.md | 11 + coreutils-common/pom.xml | 3 +- .../coreutils/common/CoreutilsContext.java | 230 ++++++++++++++++++ .../grycap/coreutils/common/ShutdownHook.java | 111 +++++++++ .../coreutils/common/ShutdownListener.java | 53 ++++ .../common/test/CoreutilsContextTest.java | 141 +++++++++++ coreutils-fiber/pom.xml | 4 +- .../coreutils/fiber/http/Http2Client.java | 65 +---- .../coreutils/fiber/http/Http2Clients.java | 116 +++++++++ .../fiber/test/HighlyConcurrencyTest.java | 22 +- .../coreutils/fiber/test/Http2ClientTest.java | 4 +- coreutils-logging/pom.xml | 3 +- coreutils-test/pom.xml | 2 +- .../test/category/CodeBenchmarks.java | 31 +++ .../test/category/IntegrationTests.java | 2 +- pom.xml | 64 ++++- 16 files changed, 789 insertions(+), 73 deletions(-) create mode 100644 coreutils-common/src/main/java/es/upv/grycap/coreutils/common/CoreutilsContext.java create mode 100644 coreutils-common/src/main/java/es/upv/grycap/coreutils/common/ShutdownHook.java create mode 100644 coreutils-common/src/main/java/es/upv/grycap/coreutils/common/ShutdownListener.java create mode 100644 coreutils-common/src/test/java/es/upv/grycap/coreutils/common/test/CoreutilsContextTest.java create mode 100644 coreutils-fiber/src/main/java/es/upv/grycap/coreutils/fiber/http/Http2Clients.java create mode 100644 coreutils-test/src/main/java/es/upv/grycap/coreutils/test/category/CodeBenchmarks.java diff --git a/README.md b/README.md index 80e08bb..ff46afd 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/coreutils-common/pom.xml b/coreutils-common/pom.xml index 78c21de..6464089 100644 --- a/coreutils-common/pom.xml +++ b/coreutils-common/pom.xml @@ -24,10 +24,11 @@ that you distribute must include a readable copy of the "NOTICE" text file. 4.0.0 + es.upv.grycap.coreutils coreutils - 0.1.0 + 0.2.0 coreutils-common diff --git a/coreutils-common/src/main/java/es/upv/grycap/coreutils/common/CoreutilsContext.java b/coreutils-common/src/main/java/es/upv/grycap/coreutils/common/CoreutilsContext.java new file mode 100644 index 0000000..3437d56 --- /dev/null +++ b/coreutils-common/src/main/java/es/upv/grycap/coreutils/common/CoreutilsContext.java @@ -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 + * @since 0.2.0 + */ +@ThreadSafe +public enum CoreutilsContext { + + COREUTILS_CONTEXT; + + private static final long TIMEOUT_MILLISECS = 2000l; + + private final Map> 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 null if no instance with the specified properties is + * found in the registry. + */ + @Nullable + public T getClient(final Class 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 null if no instance with the specified + * properties is found in the registry. + */ + @Nullable + public T getClient(final Class 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 getClient(final Class type, final @Nullable Supplier 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 getClient(final Class type, final @Nullable String classifier, final @Nullable Supplier 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 void addShutdownListener(final T listener, final Class 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 void addShutdownListener(final T listener, final Class 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 void removeShutdownListener(final Class type) { + removeShutdownListener(type, null); + } + + /** + * Unregister a shutdown listener. + * @param type - the type of the shutdown listener + * @param classifier - (optional) classifier + */ + public void removeShutdownListener(final Class type, final @Nullable String classifier) { + remove(ContextKey.SHUTDOWN_LISTENERS, type, classifier); + } + + @Nullable + @SuppressWarnings("unchecked") + private T get(final ContextKey key, final Class type, final @Nullable String classifier, final @Nullable Supplier 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 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 void remove(final ContextKey key, final Class 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 String instanceKey(final Class 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 + * @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 + * @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; + } + + } + +} \ No newline at end of file diff --git a/coreutils-common/src/main/java/es/upv/grycap/coreutils/common/ShutdownHook.java b/coreutils-common/src/main/java/es/upv/grycap/coreutils/common/ShutdownHook.java new file mode 100644 index 0000000..fc3b6b9 --- /dev/null +++ b/coreutils-common/src/main/java/es/upv/grycap/coreutils/common/ShutdownHook.java @@ -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 + * @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 listeners = synchronizedMap(new LinkedHashMap()); + + /** + * 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 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(); + } + +} \ No newline at end of file diff --git a/coreutils-common/src/main/java/es/upv/grycap/coreutils/common/ShutdownListener.java b/coreutils-common/src/main/java/es/upv/grycap/coreutils/common/ShutdownListener.java new file mode 100644 index 0000000..63d29e8 --- /dev/null +++ b/coreutils-common/src/main/java/es/upv/grycap/coreutils/common/ShutdownListener.java @@ -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 + * @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 true. + */ + public void init() { + isRunning.compareAndSet(false, true); + } + + /** + * Calling this method should set the value of {@link #isRunning} to false. Implementations should check that the value is + * true before entering the stop sequence. + */ + public abstract void stop(); + +} \ No newline at end of file diff --git a/coreutils-common/src/test/java/es/upv/grycap/coreutils/common/test/CoreutilsContextTest.java b/coreutils-common/src/test/java/es/upv/grycap/coreutils/common/test/CoreutilsContextTest.java new file mode 100644 index 0000000..126a2dd --- /dev/null +++ b/coreutils-common/src/test/java/es/upv/grycap/coreutils/common/test/CoreutilsContextTest.java @@ -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 + * @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 + * @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 + * @since 0.2.0 + */ + public static class FakeShutdownListener extends ShutdownListener { + + @Override + public void stop() { + if (isRunning.get()); + } + + } + +} \ No newline at end of file diff --git a/coreutils-fiber/pom.xml b/coreutils-fiber/pom.xml index b38a99e..8f07dbb 100644 --- a/coreutils-fiber/pom.xml +++ b/coreutils-fiber/pom.xml @@ -28,7 +28,7 @@ that you distribute must include a readable copy of the "NOTICE" text file. es.upv.grycap.coreutils coreutils - 0.1.0 + 0.2.0 coreutils-fiber @@ -138,7 +138,7 @@ that you distribute must include a readable copy of the "NOTICE" text file. ${m2e.lifecycle-mapping.version} - + org.apache.maven.plugins diff --git a/coreutils-fiber/src/main/java/es/upv/grycap/coreutils/fiber/http/Http2Client.java b/coreutils-fiber/src/main/java/es/upv/grycap/coreutils/fiber/http/Http2Client.java index 4a04c39..52c26e7 100644 --- a/coreutils-fiber/src/main/java/es/upv/grycap/coreutils/fiber/http/Http2Client.java +++ b/coreutils-fiber/src/main/java/es/upv/grycap/coreutils/fiber/http/Http2Client.java @@ -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 * @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); } } \ No newline at end of file diff --git a/coreutils-fiber/src/main/java/es/upv/grycap/coreutils/fiber/http/Http2Clients.java b/coreutils-fiber/src/main/java/es/upv/grycap/coreutils/fiber/http/Http2Clients.java new file mode 100644 index 0000000..f36752f --- /dev/null +++ b/coreutils-fiber/src/main/java/es/upv/grycap/coreutils/fiber/http/Http2Clients.java @@ -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 + * @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 + * @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); + } + } + + } + +} \ No newline at end of file diff --git a/coreutils-fiber/src/test/java/es/upv/grycap/coreutils/fiber/test/HighlyConcurrencyTest.java b/coreutils-fiber/src/test/java/es/upv/grycap/coreutils/fiber/test/HighlyConcurrencyTest.java index 0010eb5..abb97f8 100644 --- a/coreutils-fiber/src/test/java/es/upv/grycap/coreutils/fiber/test/HighlyConcurrencyTest.java +++ b/coreutils-fiber/src/test/java/es/upv/grycap/coreutils/fiber/test/HighlyConcurrencyTest.java @@ -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); - } + } } \ No newline at end of file diff --git a/coreutils-fiber/src/test/java/es/upv/grycap/coreutils/fiber/test/Http2ClientTest.java b/coreutils-fiber/src/test/java/es/upv/grycap/coreutils/fiber/test/Http2ClientTest.java index a625abf..762d6f2 100644 --- a/coreutils-fiber/src/test/java/es/upv/grycap/coreutils/fiber/test/Http2ClientTest.java +++ b/coreutils-fiber/src/test/java/es/upv/grycap/coreutils/fiber/test/Http2ClientTest.java @@ -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 diff --git a/coreutils-logging/pom.xml b/coreutils-logging/pom.xml index f207062..49e15a2 100644 --- a/coreutils-logging/pom.xml +++ b/coreutils-logging/pom.xml @@ -24,10 +24,11 @@ that you distribute must include a readable copy of the "NOTICE" text file. 4.0.0 + es.upv.grycap.coreutils coreutils - 0.1.0 + 0.2.0 coreutils-logging diff --git a/coreutils-test/pom.xml b/coreutils-test/pom.xml index 4ac756c..a9364b6 100644 --- a/coreutils-test/pom.xml +++ b/coreutils-test/pom.xml @@ -28,7 +28,7 @@ that you distribute must include a readable copy of the "NOTICE" text file. es.upv.grycap.coreutils coreutils - 0.1.0 + 0.2.0 coreutils-test diff --git a/coreutils-test/src/main/java/es/upv/grycap/coreutils/test/category/CodeBenchmarks.java b/coreutils-test/src/main/java/es/upv/grycap/coreutils/test/category/CodeBenchmarks.java new file mode 100644 index 0000000..ef2223c --- /dev/null +++ b/coreutils-test/src/main/java/es/upv/grycap/coreutils/test/category/CodeBenchmarks.java @@ -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 + * @since 0.1.0 + */ +public class CodeBenchmarks { } \ No newline at end of file diff --git a/coreutils-test/src/main/java/es/upv/grycap/coreutils/test/category/IntegrationTests.java b/coreutils-test/src/main/java/es/upv/grycap/coreutils/test/category/IntegrationTests.java index bd87ffe..28a82f9 100644 --- a/coreutils-test/src/main/java/es/upv/grycap/coreutils/test/category/IntegrationTests.java +++ b/coreutils-test/src/main/java/es/upv/grycap/coreutils/test/category/IntegrationTests.java @@ -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 * @since 0.1.0 */ diff --git a/pom.xml b/pom.xml index 8967b4a..114f39a 100644 --- a/pom.xml +++ b/pom.xml @@ -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"> 4.0.0 + + org.sonatype.oss + oss-parent + 9 + + es.upv.grycap.coreutils coreutils - 0.1.0 + 0.2.0 pom Core utils project @@ -72,9 +78,9 @@ that you distribute must include a readable copy of the "NOTICE" text file. UTF-8 - 1.1.3 - 4.12 + 1.1.3 ${project.version} + 4.12 1.3 3.10.2 1.7.13 @@ -122,7 +128,7 @@ that you distribute must include a readable copy of the "NOTICE" text file. com.typesafe config 1.3.0 - + @@ -206,6 +212,31 @@ that you distribute must include a readable copy of the "NOTICE" text file. + + + org.eclipse.m2e + lifecycle-mapping + ${m2e.lifecycle-mapping.version} + + + + + + org.apache.maven.plugins + maven-enforcer-plugin + [1.0.0,) + + enforce + + + + + + + + + + org.apache.maven.plugins @@ -305,6 +336,31 @@ that you distribute must include a readable copy of the "NOTICE" text file. true + + org.apache.maven.plugins + maven-release-plugin + 2.5.3 + + + org.apache.maven.plugins + maven-enforcer-plugin + 1.4.1 + + + org.apache.maven.plugins + maven-source-plugin + 2.4 + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.10.3 + + + org.apache.maven.plugins + maven-gpg-plugin + 1.6 +