mirror of
https://github.com/wahyd4/coreutils.git
synced 2026-08-09 04:46:47 +10:00
Add lightweight context
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
+141
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+16
-6
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
+31
@@ -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 { }
|
||||
+1
-1
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user