From b6718fa2e8720ff3251cbec2f9d5a8fd7d3a300a Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Thu, 18 Apr 2019 16:43:04 +0200 Subject: [PATCH 01/15] WIP: Helidon common context Signed-off-by: Tomas Langer --- common/context/pom.xml | 48 ++++ .../io/helidon/common/context/Context.java | 147 ++++++++++++ .../context/ContextAwareExecutorImpl.java | 127 ++++++++++ .../ContextAwareScheduledExecutorImpl.java | 51 ++++ .../io/helidon/common/context/Contexts.java | 120 ++++++++++ .../common/context/ExecutorException.java | 38 +++ .../helidon/common/context/ListContext.java | 221 ++++++++++++++++++ .../helidon/common/context/package-info.java | 19 ++ .../context/src/main/java9/module-info.java | 24 ++ .../context/ContextAwareExecutorTest.java | 186 +++++++++++++++ .../ContextAwareScheduledExecutorTest.java | 176 ++++++++++++++ .../helidon/common/context/ContextsTest.java | 211 +++++++++++++++++ .../common/context/ListContextTest.java | 164 +++++++++++++ common/http/pom.xml | 5 + .../common/http/ContextualRegistry.java | 82 +------ common/http/src/main/java9/module-info.java | 3 +- .../helidon/common/http/ListContextTest.java | 164 +++++++++++++ common/pom.xml | 1 + .../webserver/jersey/JerseySupport.java | 7 +- .../jersey/src/main/java9/module-info.java | 3 +- .../io/helidon/webserver/RequestRouting.java | 6 +- .../webserver/src/main/java9/module-info.java | 1 + 22 files changed, 1721 insertions(+), 83 deletions(-) create mode 100644 common/context/pom.xml create mode 100644 common/context/src/main/java/io/helidon/common/context/Context.java create mode 100644 common/context/src/main/java/io/helidon/common/context/ContextAwareExecutorImpl.java create mode 100644 common/context/src/main/java/io/helidon/common/context/ContextAwareScheduledExecutorImpl.java create mode 100644 common/context/src/main/java/io/helidon/common/context/Contexts.java create mode 100644 common/context/src/main/java/io/helidon/common/context/ExecutorException.java create mode 100644 common/context/src/main/java/io/helidon/common/context/ListContext.java create mode 100644 common/context/src/main/java/io/helidon/common/context/package-info.java create mode 100644 common/context/src/main/java9/module-info.java create mode 100644 common/context/src/test/java/io/helidon/common/context/ContextAwareExecutorTest.java create mode 100644 common/context/src/test/java/io/helidon/common/context/ContextAwareScheduledExecutorTest.java create mode 100644 common/context/src/test/java/io/helidon/common/context/ContextsTest.java create mode 100644 common/context/src/test/java/io/helidon/common/context/ListContextTest.java create mode 100644 common/http/src/test/java/io/helidon/common/http/ListContextTest.java diff --git a/common/context/pom.xml b/common/context/pom.xml new file mode 100644 index 00000000000..14b553b1d7d --- /dev/null +++ b/common/context/pom.xml @@ -0,0 +1,48 @@ + + + + + 4.0.0 + + io.helidon.common + helidon-common-project + 1.0.3-SNAPSHOT + + helidon-common-context + Helidon Common Context + + + + io.helidon.common + helidon-common-configurable + ${project.version} + test + + + org.junit.jupiter + junit-jupiter-api + test + + + org.hamcrest + hamcrest-all + test + + + \ No newline at end of file diff --git a/common/context/src/main/java/io/helidon/common/context/Context.java b/common/context/src/main/java/io/helidon/common/context/Context.java new file mode 100644 index 00000000000..7d825163ba0 --- /dev/null +++ b/common/context/src/main/java/io/helidon/common/context/Context.java @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * 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. + */ + +package io.helidon.common.context; + +import java.util.Optional; +import java.util.function.Supplier; + +/** + * A registry for context objects. Enables instance localization between several services / components / ... integrated in + * a particular known scope. ContextualRegistry instance is intended to be associated with a scope aware object such as + * WebServer, ServerRequest or ClientRequest. + * + *

Context contains also a notion of classifiers. Classifier is any object defining additional key for registered + * objects. To obtain such registered object, the same classifier (precisely, any equal object) has to be used. + * + *

Classifiers can be used as follows:

    + *
  1. As an additional identifier for registered objects of common types, like a {@link String}, ...
    + *
    {@code
    + * // User detail provider service
    + * registry.register("NAME_PARAM_ID", "Smith");
    + * registry.register("GENDER_PARAM_ID", "male");
    + * ...
    + * // User consumer service
    + * String name = registry.get("name", String.class);
    + * }
  2. + *
  3. As an access control mechanism where only owners of the classifier can retrieve such contextual instance.
    + *
    {@code
    + * // In some central security service.
    + * registry.register(securityFrameworkInternalInstance, new AuthenticatedInternalIdentity(...));
    + * ...
    + * // In some authorization filter known by a central security service
    + * AuthenticatedInternalIdentity auth = registry.get(securityFrameworkInternalInstance, AuthenticatedInternalIdentity.class);
    + * }
  4. + *
+ */ +public interface Context { + + /** + * Creates a new empty instance. + * + * @return new instance + */ + static Context create() { + return new ListContext(); + } + + /** + * Creates a new empty instance backed by its parent read-through {@link Context}. + * + *

Parent {@code registry} is used only for get methods and only if this registry doesn't have registered required type. + * + * @param parent a parent registry + * @return new instance + */ + static Context create(Context parent) { + return new ListContext(parent); + } + + /** + * Register a new instance. + * + * @param instance an instance to register + * @param a type of the registered instance + * @throws NullPointerException if the registered object is {@code null} + */ + void register(T instance); + + /** + * Register a new instance using a provided supplier. The supplier is guarantied to be called at most once when it's + * requested by the {@link #get(Class)} method. The returned value is then registered and the supplier is never used again. + * + * @param type a type of supplied instance + * @param supplier a supplier of the instance to register + * @param a type of supplied object + * @throws NullPointerException if the {@code type} or the {@code supplier} is {@code null} + */ + void supply(Class type, Supplier supplier); + + /** + * Optionally gets registered instance by its type. + * + *

More specifically, it returns the last registered instance without specified classifier which can be cast + * to the requested type. + * + * @param type a type of requested instance + * @param a type of requested instance + * @return The last registered instance compatible with the specified type + */ + Optional get(Class type); + + /** + * Register a new instance with specified classifier. + * + *

Registered instance can be obtained only using {@link #get(Object, Class)} method with a {@code classifier} equal with + * the one used during registration. + * + * @param classifier an additional registered instance classifier + * @param instance an instance to register + * @param a type of the registered instance + * @throws NullPointerException if {@code classifier} or registered object is {@code null} + */ + void register(Object classifier, T instance); + + /** + * Registers a new instance using a provided supplier. The supplier is guarantied to be called at most once when it's + * requested by the {@link #get(Object, Class)} method. The returned value gets registered and the supplier is never called + * again. + * + *

Registered instance can be obtained only using {@link #get(Object, Class)} method with a {@code classifier} equal with + * the one used during registration. + * + * @param classifier an additional registered instance classifier + * @param type a type of requested instance + * @param supplier a supplier of the instance to register + * @param a type of supplied object + * @throws NullPointerException If any parameter is {@code null}. + */ + void supply(Object classifier, Class type, Supplier supplier); + + /** + * Optionally gets a registered instance by its type. + * + *

More specifically, it returns the last registered instance with equal classifier which can be cast + * to the requested type. + * + * @param classifier an additional registered instance classifier + * @param type a type of requested instance + * @param a type of requested instance + * @return the last registered instance compatible with the specified type + * @throws NullPointerException If {@code classifier} is null. + */ + Optional get(Object classifier, Class type); +} diff --git a/common/context/src/main/java/io/helidon/common/context/ContextAwareExecutorImpl.java b/common/context/src/main/java/io/helidon/common/context/ContextAwareExecutorImpl.java new file mode 100644 index 00000000000..6d1907e9ff9 --- /dev/null +++ b/common/context/src/main/java/io/helidon/common/context/ContextAwareExecutorImpl.java @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * 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. + */ +package io.helidon.common.context; + +import java.util.Collection; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.stream.Collectors; + +class ContextAwareExecutorImpl implements ExecutorService { + private final ExecutorService delegate; + + ContextAwareExecutorImpl(ExecutorService toWrap) { + this.delegate = toWrap; + } + + @Override + public void shutdown() { + delegate.shutdown(); + } + + @Override + public List shutdownNow() { + return delegate.shutdownNow(); + } + + @Override + public boolean isShutdown() { + return delegate.isShutdown(); + } + + @Override + public boolean isTerminated() { + return delegate.isTerminated(); + } + + @Override + public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { + return delegate.awaitTermination(timeout, unit); + } + + @Override + public Future submit(Callable task) { + return delegate.submit(wrap(task)); + } + + @Override + public Future submit(Runnable task, T result) { + return delegate.submit(wrap(task), result); + } + + @Override + public Future submit(Runnable task) { + return delegate.submit(wrap(task)); + } + + @Override + public List> invokeAll(Collection> tasks) throws InterruptedException { + return delegate.invokeAll(wrap(tasks)); + } + + @Override + public List> invokeAll(Collection> tasks, long timeout, TimeUnit unit) + throws InterruptedException { + return delegate.invokeAll(wrap(tasks), timeout, unit); + } + + @Override + public T invokeAny(Collection> tasks) throws InterruptedException, ExecutionException { + return delegate.invokeAny(wrap(tasks)); + } + + @Override + public T invokeAny(Collection> tasks, long timeout, TimeUnit unit) + throws InterruptedException, ExecutionException, TimeoutException { + return delegate.invokeAny(wrap(tasks), timeout, unit); + } + + @Override + public void execute(Runnable command) { + delegate.execute(wrap(command)); + } + + protected Collection> wrap(Collection> tasks) { + return tasks.stream() + .map(this::wrap) + .collect(Collectors.toList()); + + } + + protected Callable wrap(Callable task) { + Optional context = Contexts.context(); + + return context.>map(value -> () -> Contexts.inContext(value, task)) + // no need to wrap, no context + .orElse(task); + } + + protected Runnable wrap(Runnable command) { + Optional context = Contexts.context(); + + // no need to wrap, no context + return context.map(value -> () -> Contexts.inContext(value, command)) + // no need to wrap, no context + .orElse(command); + + } +} diff --git a/common/context/src/main/java/io/helidon/common/context/ContextAwareScheduledExecutorImpl.java b/common/context/src/main/java/io/helidon/common/context/ContextAwareScheduledExecutorImpl.java new file mode 100644 index 00000000000..9923031b05d --- /dev/null +++ b/common/context/src/main/java/io/helidon/common/context/ContextAwareScheduledExecutorImpl.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * 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. + */ +package io.helidon.common.context; + +import java.util.concurrent.Callable; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +class ContextAwareScheduledExecutorImpl extends ContextAwareExecutorImpl implements ScheduledExecutorService { + + private final ScheduledExecutorService delegate; + + ContextAwareScheduledExecutorImpl(ScheduledExecutorService toWrap) { + super(toWrap); + this.delegate = toWrap; + } + + @Override + public ScheduledFuture schedule(Runnable command, long delay, TimeUnit unit) { + return delegate.schedule(wrap(command), delay, unit); + } + + @Override + public ScheduledFuture schedule(Callable callable, long delay, TimeUnit unit) { + return delegate.schedule(wrap(callable), delay, unit); + } + + @Override + public ScheduledFuture scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) { + return delegate.scheduleAtFixedRate(wrap(command), initialDelay, period, unit); + } + + @Override + public ScheduledFuture scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) { + return delegate.scheduleWithFixedDelay(wrap(command), initialDelay, delay, unit); + } +} diff --git a/common/context/src/main/java/io/helidon/common/context/Contexts.java b/common/context/src/main/java/io/helidon/common/context/Contexts.java new file mode 100644 index 00000000000..d381146957c --- /dev/null +++ b/common/context/src/main/java/io/helidon/common/context/Contexts.java @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * 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. + */ +package io.helidon.common.context; + +import java.util.Optional; +import java.util.Stack; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.ScheduledExecutorService; + +/** + * Support for handling {@link io.helidon.common.context.Context} across thread boundaries. + */ +public final class Contexts { + static final ThreadLocal> REGISTRY = ThreadLocal.withInitial(Stack::new); + + private Contexts() { + } + + static void clear() { + REGISTRY.get().clear(); + } + + static void push(Context context) { + REGISTRY.get().push(context); + } + + static Context pop() { + return REGISTRY.get().pop(); + } + + /** + * Get context registry associated with current thread. + * + * @return context that is associated with current thread or an empty context if none is + */ + public static Optional context() { + Stack contextStack = REGISTRY.get(); + + if (contextStack.isEmpty()) { + return Optional.empty(); + } + + return Optional.ofNullable(contextStack.peek()); + } + + /** + * Wrap an executor service to correctly propagate context to its threads. + * + * @param toWrap executor service + * @return a new executor service wrapping the provided one + */ + public static ExecutorService wrap(ExecutorService toWrap) { + return new ContextAwareExecutorImpl(toWrap); + } + + /** + * Wrap a scheduled executor service to correctly propagate context to its threads. + * Note that all scheduled methods are going to run in context of the thread scheduling the tasks. + * + * @param toWrap executor service + * @return a new executor service wrapping the provided one + */ + public static ScheduledExecutorService wrap(ScheduledExecutorService toWrap) { + return new ContextAwareScheduledExecutorImpl(toWrap); + } + + /** + * Run the runnable in the provided context. + * The runnable can use {@link #context()} to retrieve the context. + * + * @param context context to run in + * @param runnable runnable to execute in context + */ + public static void inContext(Context context, Runnable runnable) { + push(context); + try { + runnable.run(); + } finally { + pop(); + } + } + + /** + * Run the callable in the provided context. + * The callable can use {@link #context()} to retrieve the context. + * + * @param context context to run in + * @param callable callable to execute in context + * @param return type of the callable + * @return the result of the callable + * @throws java.lang.RuntimeException in case the {@link java.util.concurrent.Callable#call()} threw a + * runtime exception + */ + public static T inContext(Context context, Callable callable) { + push(context); + try { + return callable.call(); + } catch (RuntimeException e) { + throw e; + } catch (Exception e) { + throw new ExecutorException("Callable.call failed", e); + } finally { + pop(); + } + } +} diff --git a/common/context/src/main/java/io/helidon/common/context/ExecutorException.java b/common/context/src/main/java/io/helidon/common/context/ExecutorException.java new file mode 100644 index 00000000000..c00332e2964 --- /dev/null +++ b/common/context/src/main/java/io/helidon/common/context/ExecutorException.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * 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. + */ +package io.helidon.common.context; + +/** + * Exception related to execution of a task in context. + */ +public class ExecutorException extends RuntimeException { + /** + * Create exception with a descriptive message. + * @param message details about what happened + */ + public ExecutorException(String message) { + super(message); + } + + /** + * Create exception with a descriptive message and a cause. + * @param message details about what happened + * @param cause original exception caught + */ + public ExecutorException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/common/context/src/main/java/io/helidon/common/context/ListContext.java b/common/context/src/main/java/io/helidon/common/context/ListContext.java new file mode 100644 index 00000000000..79ff37589f7 --- /dev/null +++ b/common/context/src/main/java/io/helidon/common/context/ListContext.java @@ -0,0 +1,221 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * 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. + */ + +package io.helidon.common.context; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.function.Supplier; + +/** + * A {@link Context} implementation with deque registry. + */ +class ListContext implements Context { + + private final Context parent; + private final ConcurrentHashMap classifiers = new ConcurrentHashMap<>(); + private final ClassifiedRegistry registry = new ClassifiedRegistry(); + + /** + * Creates new instance with defined parent. + * + * @param parent a parent context or {@code null}. + */ + ListContext(Context parent) { + this.parent = parent; + } + + /** + * Creates new instance. + */ + ListContext() { + this(null); + } + + @Override + public void register(T instance) { + registry.register(instance); + } + + @Override + public void supply(Class type, Supplier supplier) { + registry.supply(type, supplier); + } + + @Override + public Optional get(Class type) { + T result = registry.get(type); + if (result == null) { + if (parent == null) { + return Optional.empty(); + } else { + return parent.get(type); + } + } else { + return Optional.of(result); + } + } + + @Override + public void register(Object classifier, T instance) { + Objects.requireNonNull(classifier, "Parameter 'classifier' is null!"); + ClassifiedRegistry cr = classifiers.computeIfAbsent(classifier, k -> new ClassifiedRegistry()); + cr.register(instance); + } + + @Override + public void supply(Object classifier, Class type, Supplier supplier) { + Objects.requireNonNull(classifier, "Parameter 'classifier' is null!"); + ClassifiedRegistry cr = classifiers.computeIfAbsent(classifier, k -> new ClassifiedRegistry()); + cr.supply(type, supplier); + } + + @Override + public Optional get(Object classifier, Class type) { + Objects.requireNonNull(classifier, "Parameter 'classifier' is null!"); + ClassifiedRegistry cr = classifiers.get(classifier); + if (cr != null) { + T result = cr.get(type); + if ((result == null) && (parent != null)) { + return parent.get(classifier, type); + } else { + return Optional.ofNullable(result); + } + } else { + if (parent != null) { + return parent.get(classifier, type); + } else { + return Optional.empty(); + } + } + } + + private interface RegisteredItem { + T get(); + + Class getType(); + } + + private static class ClassifiedRegistry { + private final ReadWriteLock lock = new ReentrantReadWriteLock(); + private final List> content = new ArrayList<>(); + + // we actually want to do an instance equality + @SuppressWarnings("ObjectEquality") + private void registerItem(RegisteredItem item) { + Lock l = lock.writeLock(); + try { + Class c = item.getType(); + l.lock(); + for (int i = 0; i < content.size(); i++) { + RegisteredItem reg = content.get(i); + if (c == reg.getType()) { + content.remove(i); + break; + } + } + content.add(item); + } finally { + l.unlock(); + } + } + + @SuppressWarnings("unchecked") + void register(T instance) { + Objects.requireNonNull(instance, "Parameter 'instance' is null!"); + registerItem(new RegisteredInstance(instance)); + } + + @SuppressWarnings("unchecked") + void supply(Class type, Supplier supplier) { + Objects.requireNonNull(type, "Parameter 'type' is null!"); + Objects.requireNonNull(supplier, "Parameter 'supplier' is null!"); + registerItem(new RegisteredSupplier(type, supplier)); + } + + T get(Class type) { + Objects.requireNonNull(type, "Parameter 'type' is null!"); + Lock l = lock.readLock(); + try { + l.lock(); + for (int i = content.size() - 1; i >= 0; i--) { + RegisteredItem item = content.get(i); + if (type.isAssignableFrom(item.getType())) { + return type.cast(item.get()); + } + } + } finally { + l.unlock(); + } + return null; + } + } + + private static class RegisteredSupplier implements RegisteredItem { + private final Class type; + private final Supplier supplier; + private volatile boolean missing = true; + private volatile T instance; + + RegisteredSupplier(Class type, Supplier supplier) { + this.type = type; + this.supplier = supplier; + } + + @Override + public T get() { + if (missing) { + synchronized (this) { + if (missing) { + missing = false; + instance = supplier.get(); + } + } + } + return instance; + } + + @Override + public Class getType() { + return type; + } + } + + private static class RegisteredInstance implements RegisteredItem { + private final T instance; + + RegisteredInstance(T instance) { + this.instance = instance; + } + + @Override + public T get() { + return instance; + } + + @Override + @SuppressWarnings("unchecked") + public Class getType() { + return (Class) instance.getClass(); + } + } +} diff --git a/common/context/src/main/java/io/helidon/common/context/package-info.java b/common/context/src/main/java/io/helidon/common/context/package-info.java new file mode 100644 index 00000000000..50254432ab7 --- /dev/null +++ b/common/context/src/main/java/io/helidon/common/context/package-info.java @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * 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. + */ +/** + * Support fo context propagation across executor boundaries. + */ +package io.helidon.common.context; diff --git a/common/context/src/main/java9/module-info.java b/common/context/src/main/java9/module-info.java new file mode 100644 index 00000000000..00de7c985c3 --- /dev/null +++ b/common/context/src/main/java9/module-info.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * 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. + */ + +/** + * Helidon Common Context library. + */ +module io.helidon.common.context { + requires java.logging; + + exports io.helidon.common.context; +} diff --git a/common/context/src/test/java/io/helidon/common/context/ContextAwareExecutorTest.java b/common/context/src/test/java/io/helidon/common/context/ContextAwareExecutorTest.java new file mode 100644 index 00000000000..71ce3e65923 --- /dev/null +++ b/common/context/src/test/java/io/helidon/common/context/ContextAwareExecutorTest.java @@ -0,0 +1,186 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * 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. + */ +package io.helidon.common.context; + +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +import io.helidon.common.configurable.ThreadPoolSupplier; + +import org.junit.jupiter.api.Test; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.collection.IsEmptyCollection.empty; + +/** + * Unit test for {@link io.helidon.common.context.ContextAwareExecutorImpl}. + */ +public class ContextAwareExecutorTest { + private static final String TEST_STRING = "someStringToTest"; + + @Test + void testLifecycle() { + ThreadPoolExecutor wrapped = ThreadPoolSupplier.create().get(); + ExecutorService wrapper = Contexts.wrap(wrapped); + + assertThat("Wrapped.isShutdown", wrapped.isShutdown(), is(false)); + assertThat("Wrapper.isShutdown", wrapper.isShutdown(), is(false)); + assertThat("Wrapped.isTerminated", wrapped.isTerminated(), is(false)); + assertThat("Wrapper.isTerminated", wrapper.isTerminated(), is(false)); + + List runnables = wrapper.shutdownNow(); + + assertThat("No runnables when shutting down", runnables, empty()); + + assertThat("Wrapped.isShutdown", wrapped.isShutdown(), is(true)); + assertThat("Wrapper.isShutdown", wrapper.isShutdown(), is(true)); + assertThat("Wrapped.isTerminated", wrapped.isTerminated(), is(true)); + assertThat("Wrapper.isTerminated", wrapper.isTerminated(), is(true)); + } + + @Test + void testLifecycleWithAwaitTermination() throws InterruptedException { + ThreadPoolExecutor wrapped = ThreadPoolSupplier.create().get(); + ExecutorService wrapper = Contexts.wrap(wrapped); + + assertThat("Wrapped.isShutdown", wrapped.isShutdown(), is(false)); + assertThat("Wrapper.isShutdown", wrapper.isShutdown(), is(false)); + assertThat("Wrapped.isTerminated", wrapped.isTerminated(), is(false)); + assertThat("Wrapper.isTerminated", wrapper.isTerminated(), is(false)); + + wrapper.shutdown(); + wrapper.awaitTermination(1, TimeUnit.SECONDS); + + + assertThat("Wrapped.isShutdown", wrapped.isShutdown(), is(true)); + assertThat("Wrapper.isShutdown", wrapper.isShutdown(), is(true)); + assertThat("Wrapped.isTerminated", wrapped.isTerminated(), is(true)); + assertThat("Wrapper.isTerminated", wrapper.isTerminated(), is(true)); + } + + @Test + void testInvokeAll() { + List toCall = new LinkedList<>(); + + Context context = Context.create(); + ExecutorService executor = Contexts.wrap(ThreadPoolSupplier.create().get()); + + for (int i = 0; i < 10; i++) { + context.register("key_" + i, TEST_STRING); + toCall.add(new TestCallable(i)); + } + + Contexts.inContext(context, () -> { + try { + List> futures = executor.invokeAll(toCall); + for (Future future : futures) { + assertThat(future.get(), is(TEST_STRING)); + } + } catch (Exception e) { + throw new ExecutorException("failed to execute", e); + } + }); + } + + @Test + void testInvokeAllWithTimeout() { + List toCall = new LinkedList<>(); + + Context context = Context.create(); + ExecutorService executor = Contexts.wrap(ThreadPoolSupplier.create().get()); + + for (int i = 0; i < 10; i++) { + context.register("key_" + i, TEST_STRING); + toCall.add(new TestCallable(i)); + } + + Contexts.inContext(context, () -> { + try { + List> futures = executor.invokeAll(toCall, 1, TimeUnit.SECONDS); + for (Future future : futures) { + assertThat(future.get(), is(TEST_STRING)); + } + } catch (Exception e) { + throw new ExecutorException("failed to execute", e); + } + }); + } + + @Test + void testInvokeAny() { + List toCall = new LinkedList<>(); + + Context context = Context.create(); + ExecutorService executor = Contexts.wrap(ThreadPoolSupplier.create().get()); + + for (int i = 0; i < 10; i++) { + context.register("key_" + i, TEST_STRING); + toCall.add(new TestCallable(i)); + } + + Contexts.inContext(context, () -> { + try { + String result = executor.invokeAny(toCall); + assertThat(result, is(TEST_STRING)); + } catch (Exception e) { + throw new ExecutorException("failed to execute", e); + } + }); + } + + @Test + void testInvokeAnyWithTimeout() { + List toCall = new LinkedList<>(); + + Context context = Context.create(); + ExecutorService executor = Contexts.wrap(ThreadPoolSupplier.create().get()); + + for (int i = 0; i < 10; i++) { + context.register("key_" + i, TEST_STRING); + toCall.add(new TestCallable(i)); + } + + Contexts.inContext(context, () -> { + try { + String result = executor.invokeAny(toCall, 1, TimeUnit.SECONDS); + assertThat(result, is(TEST_STRING)); + } catch (Exception e) { + throw new ExecutorException("failed to execute", e); + } + }); + } + + private static final class TestCallable implements Callable { + private final int index; + + private TestCallable(int index) { + this.index = index; + } + + @Override + public String call() throws Exception { + Context context = Contexts.context().get(); + return context.get("key_" + index, String.class).get(); + } + } +} diff --git a/common/context/src/test/java/io/helidon/common/context/ContextAwareScheduledExecutorTest.java b/common/context/src/test/java/io/helidon/common/context/ContextAwareScheduledExecutorTest.java new file mode 100644 index 00000000000..e708f5da02f --- /dev/null +++ b/common/context/src/test/java/io/helidon/common/context/ContextAwareScheduledExecutorTest.java @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * 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. + */ +package io.helidon.common.context; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +import io.helidon.common.configurable.ScheduledThreadPoolSupplier; + +import org.junit.jupiter.api.Test; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.fail; + +/** + * Unit test for {@link ContextAwareExecutorImpl}. + */ +public class ContextAwareScheduledExecutorTest { + private static final String TEST_STRING = "someStringToTest"; + + @Test + void testSchedule() throws Exception { + ScheduledExecutorService wrapped = ScheduledThreadPoolSupplier.create().get(); + ScheduledExecutorService wrapper = Contexts.wrap(wrapped); + + + CountDownLatch cdl = new CountDownLatch(1); + Context context = Context.create(); + context.register(cdl); + + AtomicReference> futureRef = new AtomicReference<>(); + AtomicReference exceptionRef = new AtomicReference<>(); + + Contexts.inContext(context, () -> { + futureRef.set(wrapper.schedule(() -> { + try { + Contexts.context() + .get() + .get(CountDownLatch.class) + .get() + .countDown(); + } catch (Exception e) { + exceptionRef.set(e); + } + }, 100, TimeUnit.MILLISECONDS)); + }); + + try { + if (exceptionRef.get() != null) { + throw exceptionRef.get(); + } + if (cdl.await(1, TimeUnit.SECONDS)) { + assertThat(futureRef.get().get(), nullValue()); + } else { + fail("Timed out after 1 second waiting for the scheduled task"); + } + } finally { + wrapper.shutdownNow(); + } + } + + @Test + void testScheduleCallable() throws Exception { + ScheduledExecutorService wrapped = ScheduledThreadPoolSupplier.create().get(); + ScheduledExecutorService wrapper = Contexts.wrap(wrapped); + + + Context context = Context.create(); + context.register(TEST_STRING); + + AtomicReference> futureRef = new AtomicReference<>(); + AtomicReference exceptionRef = new AtomicReference<>(); + + Contexts.inContext(context, () -> { + futureRef.set(wrapper.schedule(() -> { + try { + return Contexts.context().get().get(String.class).get(); + } catch (Exception e) { + exceptionRef.set(e); + return null; + } + }, 100, TimeUnit.MILLISECONDS)); + }); + + if (exceptionRef.get() != null) { + throw exceptionRef.get(); + } + + ScheduledFuture future = futureRef.get(); + assertThat(future.get(1, TimeUnit.SECONDS), is(TEST_STRING)); + + wrapper.shutdownNow(); + } + + @Test + void testScheduleAtFixedRate() throws InterruptedException, ExecutionException { + ScheduledExecutorService wrapped = ScheduledThreadPoolSupplier.create().get(); + ScheduledExecutorService wrapper = Contexts.wrap(wrapped); + + + CountDownLatch cdl = new CountDownLatch(2); + + Context context = Context.create(); + context.register(cdl); + + AtomicReference> futureRef = new AtomicReference<>(); + + Contexts.inContext(context, () -> { + futureRef.set(wrapper.scheduleAtFixedRate(() -> { + Contexts.context() + .get() + .get(CountDownLatch.class) + .get() + .countDown(); + }, 10, 100, TimeUnit.MILLISECONDS)); + }); + + if (cdl.await(1, TimeUnit.SECONDS)) { + futureRef.get().cancel(true); + } else { + fail("Timed out after 1 second waiting for the scheduled task"); + } + + wrapper.shutdownNow(); + } + + @Test + void testScheduleAtFixedDelay() throws InterruptedException, ExecutionException { + ScheduledExecutorService wrapped = ScheduledThreadPoolSupplier.create().get(); + ScheduledExecutorService wrapper = Contexts.wrap(wrapped); + + CountDownLatch cdl = new CountDownLatch(2); + + Context context = Context.create(); + context.register(cdl); + AtomicReference> futureRef = new AtomicReference<>(); + + + Contexts.inContext(context, () -> { + futureRef.set(wrapper.scheduleWithFixedDelay(() -> { + Contexts.context() + .get() + .get(CountDownLatch.class) + .get() + .countDown(); + }, 10, 100, TimeUnit.MILLISECONDS)); + }); + + if (cdl.await(1, TimeUnit.SECONDS)) { + futureRef.get().cancel(true); + } else { + fail("Timed out after 1 second waiting for the scheduled task"); + } + + wrapper.shutdownNow(); + } +} diff --git a/common/context/src/test/java/io/helidon/common/context/ContextsTest.java b/common/context/src/test/java/io/helidon/common/context/ContextsTest.java new file mode 100644 index 00000000000..9d6da58141c --- /dev/null +++ b/common/context/src/test/java/io/helidon/common/context/ContextsTest.java @@ -0,0 +1,211 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * 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. + */ + +package io.helidon.common.context; + +import java.util.Optional; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.Callable; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +/** + * Unit test for {@link Contexts}. + */ +class ContextsTest { + private static final String TEST_STRING = "yes!"; + private static final Optional TEST_STRING_OPTIONAL = Optional.of(TEST_STRING); + + private static ExecutorService service; + + @BeforeAll + static void initClass() { + service = Contexts.wrap(new ThreadPoolExecutor(2, + 2, + 10, + TimeUnit.SECONDS, + new ArrayBlockingQueue<>( + 10), + (ThreadFactory) Thread::new)); + } + + @Test + void testPlainExecuteRunnable() throws InterruptedException { + AtomicReference ref = new AtomicReference<>(); + CountDownLatch cdl = new CountDownLatch(1); + + Runnable runnable = () -> { + ref.set(TEST_STRING); + cdl.countDown(); + }; + + service.execute(runnable); + + cdl.await(); + + assertThat(ref.get(), is(TEST_STRING)); + } + + @Test + void testPlainSubmitRunnable() throws InterruptedException, ExecutionException { + AtomicReference ref = new AtomicReference<>(); + + Runnable runnable = () -> ref.set(TEST_STRING); + + Future future = service.submit(runnable); + future.get(); + + assertThat(ref.get(), is(TEST_STRING)); + } + + @Test + void testPlainSubmitRunnableWithResult() throws InterruptedException, ExecutionException { + AtomicReference ref = new AtomicReference<>(); + + Runnable runnable = () -> ref.set(TEST_STRING); + + Future future = service.submit(runnable, "Hello"); + String result = future.get(); + + assertThat(result, is("Hello")); + assertThat(ref.get(), is(TEST_STRING)); + } + + @Test + void testPlainSubmitCallable() throws InterruptedException, ExecutionException { + Callable callable = () -> TEST_STRING; + + Future future = service.submit(callable); + + assertThat(future.get(), is(TEST_STRING)); + } + + @Test + void testContextExecuteRunnable() throws InterruptedException { + AtomicReference ref = new AtomicReference<>(); + CountDownLatch cdl = new CountDownLatch(1); + Context ctx = Context.create(); + ctx.register("message", TEST_STRING); + + Runnable runnable = () -> { + ref.set(Contexts.context().get().get("message", String.class).orElse("No context found")); + cdl.countDown(); + }; + + Contexts.inContext(ctx, () -> service.execute(runnable)); + + cdl.await(); + + assertThat(ref.get(), is(TEST_STRING)); + } + + @Test + void testContextSubmitRunnable() throws InterruptedException, ExecutionException { + AtomicReference ref = new AtomicReference<>(); + Context ctx = Context.create(); + ctx.register("message", TEST_STRING + "_2"); + + Runnable runnable = () -> ref.set(Contexts.context().get().get("message", String.class).orElse("No context found")); + + Future future = Contexts.inContext(ctx, () -> service.submit(runnable)); + future.get(); + + assertThat(ref.get(), is(TEST_STRING + "_2")); + } + + @Test + void testContextSubmitRunnableWithResult() throws InterruptedException, ExecutionException { + AtomicReference ref = new AtomicReference<>(); + Context ctx = Context.create(); + ctx.register("message", TEST_STRING + "_3"); + + Runnable runnable = () -> ref.set(Contexts.context().get().get("message", String.class).orElse("No context found")); + + Future future = Contexts.inContext(ctx, () -> service.submit(runnable, "Hello")); + String result = future.get(); + + assertThat(result, is("Hello")); + assertThat(ref.get(), is(TEST_STRING + "_3")); + } + + @Test + void testContextSubmitCallable() throws ExecutionException, InterruptedException { + Callable callable = () -> Contexts.context().get().get("message", String.class).orElse("No context found"); + + Context ctx = Context.create(); + ctx.register("message", TEST_STRING + "_1"); + + Future future = Contexts.inContext(ctx, () -> service.submit(callable)); + + assertThat(future.get(), is(TEST_STRING + "_1")); + } + + @Test + void testMultipleContexts() { + Context topLevel = Context.create(); + topLevel.register("topLevel", TEST_STRING); + topLevel.register("first", TEST_STRING); + + Context firstLevel = Context.create(topLevel); + firstLevel.register("first", TEST_STRING + "_1"); + firstLevel.register("second", TEST_STRING); + + Contexts.inContext(topLevel, () -> { + Context myContext = Contexts.context().get(); + assertThat(myContext.get("topLevel", String.class), is(TEST_STRING_OPTIONAL)); + assertThat(myContext.get("first", String.class), is(TEST_STRING_OPTIONAL)); + + Contexts.inContext(firstLevel, () -> { + Context firstLevelContext = Contexts.context().get(); + assertThat(firstLevelContext.get("topLevel", String.class), is(TEST_STRING_OPTIONAL)); + assertThat(firstLevelContext.get("first", String.class), is(Optional.of(TEST_STRING + "_1"))); + assertThat(firstLevelContext.get("second", String.class), is(TEST_STRING_OPTIONAL)); + }); + + assertThat(myContext.get("topLevel", String.class), is(TEST_STRING_OPTIONAL)); + assertThat(myContext.get("first", String.class), is(TEST_STRING_OPTIONAL)); + }); + } + + @Test + void testClear() { + Context topLevel = Context.create(); + topLevel.register("topLevel", TEST_STRING); + topLevel.register("first", TEST_STRING); + + Contexts.push(topLevel); + + Context myContext = Contexts.context().get(); + assertThat(myContext.get("topLevel", String.class), is(TEST_STRING_OPTIONAL)); + assertThat(myContext.get("first", String.class), is(TEST_STRING_OPTIONAL)); + + Contexts.clear(); + + assertThat(Contexts.context(), is(Optional.empty())); + } +} \ No newline at end of file diff --git a/common/context/src/test/java/io/helidon/common/context/ListContextTest.java b/common/context/src/test/java/io/helidon/common/context/ListContextTest.java new file mode 100644 index 00000000000..7b566430360 --- /dev/null +++ b/common/context/src/test/java/io/helidon/common/context/ListContextTest.java @@ -0,0 +1,164 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * 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. + */ + +package io.helidon.common.context; + +import java.util.Date; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.jupiter.api.Test; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.MatcherAssert.assertThat; + +/** + * Tests {@link io.helidon.common.context.ListContext} and {@link Context}. + */ +public class ListContextTest { + + @Test + public void create() { + assertThat(Context.create(), notNullValue()); + assertThat(Context.create(null), notNullValue()); + assertThat(Context.create(Context.create()), notNullValue()); + } + + @Test + public void registerAndGetLast() { + Context context = Context.create(); + assertThat(context.get(String.class), is(Optional.empty())); + assertThat(context.get(Integer.class), is(Optional.empty())); + context.register("aaa"); + assertThat(context.get(String.class), is(Optional.of("aaa"))); + assertThat(context.get(Integer.class), is(Optional.empty())); + context.register(1); + assertThat(context.get(String.class), is(Optional.of("aaa"))); + assertThat(context.get(Integer.class), is(Optional.of(1))); + assertThat(context.get(Object.class), is(Optional.of(1))); + context.register("bbb"); + assertThat(context.get(String.class), is(Optional.of("bbb"))); + assertThat(context.get(Object.class), is(Optional.of("bbb"))); + } + + @Test + public void registerAndGetLastClassifier() { + Context context = Context.create(); + String classifier = "classifier"; + assertThat(context.get(classifier, String.class), is(Optional.empty())); + assertThat(context.get(classifier, Integer.class), is(Optional.empty())); + context.register(classifier, "aaa"); + assertThat(context.get(classifier, String.class), is(Optional.of("aaa"))); + assertThat(context.get(String.class), is(Optional.empty())); + assertThat(context.get(classifier, Integer.class), is(Optional.empty())); + context.register(classifier, 1); + assertThat(context.get(classifier, String.class), is(Optional.of("aaa"))); + assertThat(context.get(classifier, Integer.class), is(Optional.of(1))); + assertThat(context.get(classifier, Object.class), is(Optional.of(1))); + context.register(classifier, "bbb"); + assertThat(context.get(classifier, String.class), is(Optional.of("bbb"))); + context.register("ccc"); + assertThat(context.get(classifier, String.class), is(Optional.of("bbb"))); + assertThat(context.get(classifier, Object.class), is(Optional.of("bbb"))); + assertThat(context.get(String.class), is(Optional.of("ccc"))); + } + + @Test + public void emptyParent() { + Context parent = Context.create(); + Context context = Context.create(parent); + assertThat(context.get(String.class), is(Optional.empty())); + context.register("aaa"); + assertThat(context.get(String.class), is(Optional.of("aaa"))); + } + + @Test + public void testParent() { + Context parent = Context.create(); + parent.register("ppp"); + Context context = Context.create(parent); + assertThat(context.get(String.class), is(Optional.of("ppp"))); + context.register(1); + assertThat(context.get(String.class), is(Optional.of("ppp"))); + context.register("aaa"); + assertThat(context.get(String.class), is(Optional.of("aaa"))); + assertThat(parent.get(String.class), is(Optional.of("ppp"))); + } + + @Test + public void testParentWithClassifier() { + String classifier = "classifier"; + Context parent = Context.create(); + parent.register(classifier, "ppp"); + Context context = Context.create(parent); + assertThat(context.get(classifier, String.class), is(Optional.of("ppp"))); + context.register(classifier, 1); + assertThat(context.get(classifier, String.class), is(Optional.of("ppp"))); + context.register(classifier, "aaa"); + assertThat(context.get(classifier, String.class), is(Optional.of("aaa"))); + assertThat(parent.get(classifier, String.class), is(Optional.of("ppp"))); + } + + @Test + public void testSupply() { + AtomicInteger counter = new AtomicInteger(0); + Context context = Context.create(); + context.register(1); + Date date = new Date(); + context.register(date); + context.register("aaa"); + context.supply(String.class, () -> { + counter.incrementAndGet(); + return "bbb"; + }); + context.register(2); + assertThat(context.get(Date.class), is(Optional.of(date))); + assertThat(counter.get(), is(0)); + assertThat(context.get(String.class), is(Optional.of("bbb"))); + assertThat(counter.get(), is(1)); + assertThat(context.get(String.class), is(Optional.of("bbb"))); + assertThat(counter.get(), is(1)); + assertThat(context.get(Date.class), is(Optional.of(date))); + assertThat(context.get(String.class), is(Optional.of("bbb"))); + assertThat(counter.get(), is(1)); + } + + @Test + public void testSupplyClassifier() { + String classifier = "classifier"; + AtomicInteger counter = new AtomicInteger(0); + Context context = Context.create(); + context.register(classifier, 1); + Date date = new Date(); + context.register(classifier, date); + context.register(classifier, "aaa"); + context.supply(classifier, String.class, () -> { + counter.incrementAndGet(); + return "bbb"; + }); + context.register(classifier, 2); + assertThat(context.get(classifier, Date.class), is(Optional.of(date))); + assertThat(counter.get(), is(0)); + assertThat(context.get(classifier, String.class), is(Optional.of("bbb"))); + assertThat(counter.get(), is(1)); + assertThat(context.get(classifier, String.class), is(Optional.of("bbb"))); + assertThat(counter.get(), is(1)); + assertThat(context.get(classifier, Date.class), is(Optional.of(date))); + assertThat(context.get(classifier, String.class), is(Optional.of("bbb"))); + assertThat(counter.get(), is(1)); + } +} diff --git a/common/http/pom.xml b/common/http/pom.xml index 8637e41ee0d..39653bc00a8 100644 --- a/common/http/pom.xml +++ b/common/http/pom.xml @@ -39,6 +39,11 @@ helidon-common ${project.version} + + io.helidon.common + helidon-common-context + ${project.version} + org.junit.jupiter junit-jupiter-api diff --git a/common/http/src/main/java/io/helidon/common/http/ContextualRegistry.java b/common/http/src/main/java/io/helidon/common/http/ContextualRegistry.java index 04c2919c618..b4d0498ea3b 100644 --- a/common/http/src/main/java/io/helidon/common/http/ContextualRegistry.java +++ b/common/http/src/main/java/io/helidon/common/http/ContextualRegistry.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2019 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,8 +16,7 @@ package io.helidon.common.http; -import java.util.Optional; -import java.util.function.Supplier; +import io.helidon.common.context.Context; /** * A registry for context objects. Enables instance localization between several services / components / ... integrated in @@ -47,7 +46,7 @@ * } * */ -public interface ContextualRegistry { +public interface ContextualRegistry extends Context { /** * Creates a new empty instance. @@ -69,79 +68,4 @@ static ContextualRegistry create() { static ContextualRegistry create(ContextualRegistry parent) { return new ListContextualRegistry(parent); } - - /** - * Register a new instance. - * - * @param instance an instance to register - * @param a type of the registered instance - * @throws NullPointerException if the registered object is {@code null} - */ - void register(T instance); - - /** - * Register a new instance using a provided supplier. The supplier is guarantied to be called at most once when it's - * requested by the {@link #get(Class)} method. The returned value is then registered and the supplier is never used again. - * - * @param type a type of supplied instance - * @param supplier a supplier of the instance to register - * @param a type of supplied object - * @throws NullPointerException if the {@code type} or the {@code supplier} is {@code null} - */ - void supply(Class type, Supplier supplier); - - /** - * Optionally gets registered instance by its type. - * - *

More specifically, it returns the last registered instance without specified classifier which can be cast - * to the requested type. - * - * @param type a type of requested instance - * @param a type of requested instance - * @return The last registered instance compatible with the specified type - */ - Optional get(Class type); - - /** - * Register a new instance with specified classifier. - * - *

Registered instance can be obtained only using {@link #get(Object, Class)} method with a {@code classifier} equal with - * the one used during registration. - * - * @param classifier an additional registered instance classifier - * @param instance an instance to register - * @param a type of the registered instance - * @throws NullPointerException if {@code classifier} or registered object is {@code null} - */ - void register(Object classifier, T instance); - - /** - * Registers a new instance using a provided supplier. The supplier is guarantied to be called at most once when it's - * requested by the {@link #get(Object, Class)} method. The returned value gets registered and the supplier is never called - * again. - * - *

Registered instance can be obtained only using {@link #get(Object, Class)} method with a {@code classifier} equal with - * the one used during registration. - * - * @param classifier an additional registered instance classifier - * @param type a type of requested instance - * @param supplier a supplier of the instance to register - * @param a type of supplied object - * @throws NullPointerException If any parameter is {@code null}. - */ - void supply(Object classifier, Class type, Supplier supplier); - - /** - * Optionally gets a registered instance by its type. - * - *

More specifically, it returns the last registered instance with equal classifier which can be cast - * to the requested type. - * - * @param classifier an additional registered instance classifier - * @param type a type of requested instance - * @param a type of requested instance - * @return the last registered instance compatible with the specified type - * @throws NullPointerException If {@code classifier} is null. - */ - Optional get(Object classifier, Class type); } diff --git a/common/http/src/main/java9/module-info.java b/common/http/src/main/java9/module-info.java index 0efd026b923..466d044f37c 100644 --- a/common/http/src/main/java9/module-info.java +++ b/common/http/src/main/java9/module-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2019 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,7 @@ module io.helidon.common.http { requires java.logging; requires io.helidon.common; + requires io.helidon.common.context; requires io.helidon.common.reactive; exports io.helidon.common.http; diff --git a/common/http/src/test/java/io/helidon/common/http/ListContextTest.java b/common/http/src/test/java/io/helidon/common/http/ListContextTest.java new file mode 100644 index 00000000000..39af8ed1c0b --- /dev/null +++ b/common/http/src/test/java/io/helidon/common/http/ListContextTest.java @@ -0,0 +1,164 @@ +/* + * Copyright (c) 2017, 2019 Oracle and/or its affiliates. All rights reserved. + * + * 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. + */ + +package io.helidon.common.http; + +import java.util.Date; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.jupiter.api.Test; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.MatcherAssert.assertThat; + +/** + * Tests {@link ListContextualRegistry} and {@link ContextualRegistry}. + */ +public class ListContextTest { + + @Test + public void create() { + assertThat(ContextualRegistry.create(), notNullValue()); + assertThat(ContextualRegistry.create(null), notNullValue()); + assertThat(ContextualRegistry.create(ContextualRegistry.create()), notNullValue()); + } + + @Test + public void registerAndGetLast() { + ContextualRegistry context = ContextualRegistry.create(); + assertThat(context.get(String.class), is(Optional.empty())); + assertThat(context.get(Integer.class), is(Optional.empty())); + context.register("aaa"); + assertThat(context.get(String.class), is(Optional.of("aaa"))); + assertThat(context.get(Integer.class), is(Optional.empty())); + context.register(1); + assertThat(context.get(String.class), is(Optional.of("aaa"))); + assertThat(context.get(Integer.class), is(Optional.of(1))); + assertThat(context.get(Object.class), is(Optional.of(1))); + context.register("bbb"); + assertThat(context.get(String.class), is(Optional.of("bbb"))); + assertThat(context.get(Object.class), is(Optional.of("bbb"))); + } + + @Test + public void registerAndGetLastClassifier() { + ContextualRegistry context = ContextualRegistry.create(); + String classifier = "classifier"; + assertThat(context.get(classifier, String.class), is(Optional.empty())); + assertThat(context.get(classifier, Integer.class), is(Optional.empty())); + context.register(classifier, "aaa"); + assertThat(context.get(classifier, String.class), is(Optional.of("aaa"))); + assertThat(context.get(String.class), is(Optional.empty())); + assertThat(context.get(classifier, Integer.class), is(Optional.empty())); + context.register(classifier, 1); + assertThat(context.get(classifier, String.class), is(Optional.of("aaa"))); + assertThat(context.get(classifier, Integer.class), is(Optional.of(1))); + assertThat(context.get(classifier, Object.class), is(Optional.of(1))); + context.register(classifier, "bbb"); + assertThat(context.get(classifier, String.class), is(Optional.of("bbb"))); + context.register("ccc"); + assertThat(context.get(classifier, String.class), is(Optional.of("bbb"))); + assertThat(context.get(classifier, Object.class), is(Optional.of("bbb"))); + assertThat(context.get(String.class), is(Optional.of("ccc"))); + } + + @Test + public void emptyParent() { + ContextualRegistry parent = ContextualRegistry.create(); + ContextualRegistry context = ContextualRegistry.create(parent); + assertThat(context.get(String.class), is(Optional.empty())); + context.register("aaa"); + assertThat(context.get(String.class), is(Optional.of("aaa"))); + } + + @Test + public void testParent() { + ContextualRegistry parent = ContextualRegistry.create(); + parent.register("ppp"); + ContextualRegistry context = ContextualRegistry.create(parent); + assertThat(context.get(String.class), is(Optional.of("ppp"))); + context.register(1); + assertThat(context.get(String.class), is(Optional.of("ppp"))); + context.register("aaa"); + assertThat(context.get(String.class), is(Optional.of("aaa"))); + assertThat(parent.get(String.class), is(Optional.of("ppp"))); + } + + @Test + public void testParentWithClassifier() { + String classifier = "classifier"; + ContextualRegistry parent = ContextualRegistry.create(); + parent.register(classifier, "ppp"); + ContextualRegistry context = ContextualRegistry.create(parent); + assertThat(context.get(classifier, String.class), is(Optional.of("ppp"))); + context.register(classifier, 1); + assertThat(context.get(classifier, String.class), is(Optional.of("ppp"))); + context.register(classifier, "aaa"); + assertThat(context.get(classifier, String.class), is(Optional.of("aaa"))); + assertThat(parent.get(classifier, String.class), is(Optional.of("ppp"))); + } + + @Test + public void testSupply() { + AtomicInteger counter = new AtomicInteger(0); + ContextualRegistry context = ContextualRegistry.create(); + context.register(1); + Date date = new Date(); + context.register(date); + context.register("aaa"); + context.supply(String.class, () -> { + counter.incrementAndGet(); + return "bbb"; + }); + context.register(2); + assertThat(context.get(Date.class), is(Optional.of(date))); + assertThat(counter.get(), is(0)); + assertThat(context.get(String.class), is(Optional.of("bbb"))); + assertThat(counter.get(), is(1)); + assertThat(context.get(String.class), is(Optional.of("bbb"))); + assertThat(counter.get(), is(1)); + assertThat(context.get(Date.class), is(Optional.of(date))); + assertThat(context.get(String.class), is(Optional.of("bbb"))); + assertThat(counter.get(), is(1)); + } + + @Test + public void testSupplyClassifier() { + String classifier = "classifier"; + AtomicInteger counter = new AtomicInteger(0); + ContextualRegistry context = ContextualRegistry.create(); + context.register(classifier, 1); + Date date = new Date(); + context.register(classifier, date); + context.register(classifier, "aaa"); + context.supply(classifier, String.class, () -> { + counter.incrementAndGet(); + return "bbb"; + }); + context.register(classifier, 2); + assertThat(context.get(classifier, Date.class), is(Optional.of(date))); + assertThat(counter.get(), is(0)); + assertThat(context.get(classifier, String.class), is(Optional.of("bbb"))); + assertThat(counter.get(), is(1)); + assertThat(context.get(classifier, String.class), is(Optional.of("bbb"))); + assertThat(counter.get(), is(1)); + assertThat(context.get(classifier, Date.class), is(Optional.of(date))); + assertThat(context.get(classifier, String.class), is(Optional.of("bbb"))); + assertThat(counter.get(), is(1)); + } +} diff --git a/common/pom.xml b/common/pom.xml index fc67643272f..78bc80ff3d8 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -37,5 +37,6 @@ key-util http service-loader + context diff --git a/webserver/jersey/src/main/java/io/helidon/webserver/jersey/JerseySupport.java b/webserver/jersey/src/main/java/io/helidon/webserver/jersey/JerseySupport.java index c83f2d4860d..dbc264579b5 100644 --- a/webserver/jersey/src/main/java/io/helidon/webserver/jersey/JerseySupport.java +++ b/webserver/jersey/src/main/java/io/helidon/webserver/jersey/JerseySupport.java @@ -36,6 +36,7 @@ import javax.ws.rs.core.GenericType; import javax.ws.rs.core.SecurityContext; +import io.helidon.common.context.Contexts; import io.helidon.webserver.Handler; import io.helidon.webserver.Routing; import io.helidon.webserver.ServerRequest; @@ -113,7 +114,11 @@ public class JerseySupport implements Service { */ private JerseySupport(Application application, ExecutorService service) { this.appHandler = new ApplicationHandler(application, new WebServerBinder()); - this.service = service != null ? service : Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2); + ExecutorService executorService = (service != null) + ? service + : Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2); + + this.service = Contexts.wrap(executorService); } @Override diff --git a/webserver/jersey/src/main/java9/module-info.java b/webserver/jersey/src/main/java9/module-info.java index 1984dc2f8f5..3ddbe7ff291 100644 --- a/webserver/jersey/src/main/java9/module-info.java +++ b/webserver/jersey/src/main/java9/module-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2019 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,6 +26,7 @@ requires transitive jersey.server; requires transitive javax.inject; + requires io.helidon.common.context; requires reactor.core; requires java.logging; diff --git a/webserver/webserver/src/main/java/io/helidon/webserver/RequestRouting.java b/webserver/webserver/src/main/java/io/helidon/webserver/RequestRouting.java index 44974bada1d..345895a7b18 100644 --- a/webserver/webserver/src/main/java/io/helidon/webserver/RequestRouting.java +++ b/webserver/webserver/src/main/java/io/helidon/webserver/RequestRouting.java @@ -28,6 +28,7 @@ import java.util.stream.Collectors; import io.helidon.common.CollectionsHelper; +import io.helidon.common.context.Contexts; import io.helidon.common.http.AlreadyCompletedException; import io.helidon.common.http.Http; @@ -349,7 +350,10 @@ public void next() { RoutedRequest nextRequest = new RoutedRequest(this, nextResponse, nextItem.path, errorHandlers); LOGGER.finest(() -> "(reqID: " + requestId() + ") Routing next: " + nextItem.path); requestSpan.log(nextItem.handlerRoute.diagnosticEvent()); - nextItem.handlerRoute.handler().accept(nextRequest, nextResponse); + // execute in the context, so context can be retrieved with Contexts (runs in our thread) + Contexts.inContext(nextRequest.context(), () -> nextItem.handlerRoute + .handler() + .accept(nextRequest, nextResponse)); } catch (RuntimeException re) { nextNoCheck(re); } diff --git a/webserver/webserver/src/main/java9/module-info.java b/webserver/webserver/src/main/java9/module-info.java index 0562e4ac5aa..1f32aa987dd 100644 --- a/webserver/webserver/src/main/java9/module-info.java +++ b/webserver/webserver/src/main/java9/module-info.java @@ -26,6 +26,7 @@ requires transitive io.helidon.config; requires transitive opentracing.util; + requires io.helidon.common.context; requires java.logging; requires opentracing.api; requires opentracing.noop; From 37a69104b46a467fa087d2eccf1150d1740a378a Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Wed, 24 Apr 2019 15:42:52 +0200 Subject: [PATCH 02/15] Rebased on latest snapshot Signed-off-by: Tomas Langer --- common/context/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/context/pom.xml b/common/context/pom.xml index 14b553b1d7d..af131550192 100644 --- a/common/context/pom.xml +++ b/common/context/pom.xml @@ -22,7 +22,7 @@ io.helidon.common helidon-common-project - 1.0.3-SNAPSHOT + 1.0.4-SNAPSHOT helidon-common-context Helidon Common Context From ec5ea8929f51c1f18e4ef8425db0b43fe407739d Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Wed, 24 Apr 2019 16:11:40 +0200 Subject: [PATCH 03/15] Bom pom record for common-context. Signed-off-by: Tomas Langer --- bom/pom.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/bom/pom.xml b/bom/pom.xml index e58aae0fee9..e492ad0d984 100644 --- a/bom/pom.xml +++ b/bom/pom.xml @@ -323,6 +323,11 @@ helidon-common-service-loader ${project.version} + + io.helidon.common + helidon-common-context + ${project.version} + From 1b9fe9bd687cbdd64c18ff412df0fd8341b821b0 Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Thu, 25 Apr 2019 19:32:58 +0200 Subject: [PATCH 04/15] Added support for context propagation to our executor services. Signed-off-by: Tomas Langer --- common/configurable/pom.xml | 5 +++++ .../configurable/ScheduledThreadPoolSupplier.java | 13 +++++++++---- .../common/configurable/ThreadPoolSupplier.java | 14 +++++++++----- .../configurable/src/main/java9/module-info.java | 2 +- .../java/io/helidon/common/context/Contexts.java | 9 +++++++++ 5 files changed, 33 insertions(+), 10 deletions(-) diff --git a/common/configurable/pom.xml b/common/configurable/pom.xml index 8a116dd39b8..56b92d9c731 100644 --- a/common/configurable/pom.xml +++ b/common/configurable/pom.xml @@ -35,6 +35,11 @@ helidon-common ${project.version} + + io.helidon.common + helidon-common-context + ${project.version} + io.helidon.config helidon-config diff --git a/common/configurable/src/main/java/io/helidon/common/configurable/ScheduledThreadPoolSupplier.java b/common/configurable/src/main/java/io/helidon/common/configurable/ScheduledThreadPoolSupplier.java index 5aecd9145bb..19e021942d0 100644 --- a/common/configurable/src/main/java/io/helidon/common/configurable/ScheduledThreadPoolSupplier.java +++ b/common/configurable/src/main/java/io/helidon/common/configurable/ScheduledThreadPoolSupplier.java @@ -17,16 +17,19 @@ package io.helidon.common.configurable; import java.util.concurrent.ExecutorService; +import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Supplier; +import io.helidon.common.context.Contexts; import io.helidon.config.Config; /** * Supplier of a custom scheduled thread pool. + * The returned thread pool supports {@link io.helidon.common.context.Context} propagation. */ public final class ScheduledThreadPoolSupplier implements Supplier { private static final int EXECUTOR_DEFAULT_CORE_POOL_SIZE = 16; @@ -38,7 +41,7 @@ public final class ScheduledThreadPoolSupplier implements Supplier { private static final int EXECUTOR_DEFAULT_CORE_POOL_SIZE = 10; @@ -45,7 +47,7 @@ public final class ThreadPoolSupplier implements Supplier { private final boolean isDaemon; private final String threadNamePrefix; private final boolean prestart; - private volatile ThreadPoolExecutor instance; + private volatile ExecutorService instance; private ThreadPoolSupplier(Builder builder) { this.corePoolSize = builder.corePoolSize; @@ -87,15 +89,16 @@ public static ThreadPoolSupplier create() { } @Override - public synchronized ThreadPoolExecutor get() { + public synchronized ExecutorService get() { if (null == instance) { - instance = new ThreadPoolExecutor(corePoolSize, + ThreadPoolExecutor executor; + executor = new ThreadPoolExecutor(corePoolSize, maxPoolSize, keepAliveMinutes, TimeUnit.MINUTES, new LinkedBlockingQueue<>(queueCapacity), new ThreadFactory() { - private AtomicInteger value = new AtomicInteger(); + private final AtomicInteger value = new AtomicInteger(); @Override public Thread newThread(Runnable r) { @@ -106,8 +109,9 @@ public Thread newThread(Runnable r) { return t; } }); + instance = Contexts.wrap(executor); if (prestart) { - instance.prestartAllCoreThreads(); + executor.prestartAllCoreThreads(); } } return instance; diff --git a/common/configurable/src/main/java9/module-info.java b/common/configurable/src/main/java9/module-info.java index 54625a51c5b..65af8d25805 100644 --- a/common/configurable/src/main/java9/module-info.java +++ b/common/configurable/src/main/java9/module-info.java @@ -21,9 +21,9 @@ */ module io.helidon.common.configurable { requires java.logging; - requires transitive io.helidon.config; requires io.helidon.common; + requires io.helidon.common.context; exports io.helidon.common.configurable; } diff --git a/common/context/src/main/java/io/helidon/common/context/Contexts.java b/common/context/src/main/java/io/helidon/common/context/Contexts.java index d381146957c..9876db43d30 100644 --- a/common/context/src/main/java/io/helidon/common/context/Contexts.java +++ b/common/context/src/main/java/io/helidon/common/context/Contexts.java @@ -64,6 +64,12 @@ public static Optional context() { * @return a new executor service wrapping the provided one */ public static ExecutorService wrap(ExecutorService toWrap) { + if (toWrap instanceof ContextAwareExecutorImpl) { + return toWrap; + } + if (toWrap instanceof ContextAwareScheduledExecutorImpl) { + return toWrap; + } return new ContextAwareExecutorImpl(toWrap); } @@ -75,6 +81,9 @@ public static ExecutorService wrap(ExecutorService toWrap) { * @return a new executor service wrapping the provided one */ public static ScheduledExecutorService wrap(ScheduledExecutorService toWrap) { + if (toWrap instanceof ContextAwareScheduledExecutorImpl) { + return toWrap; + } return new ContextAwareScheduledExecutorImpl(toWrap); } From 4e1b8334fd0c5759940c1a946baef31d1c328936 Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Thu, 25 Apr 2019 19:45:09 +0200 Subject: [PATCH 05/15] Removed cyclic (test) dependency. Signed-off-by: Tomas Langer --- .../ScheduledThreadPoolSupplier.java | 33 ++++++++------- .../configurable/ThreadPoolSupplier.java | 41 ++++++++++--------- .../configurable/ThreadPoolSupplierTest.java | 8 ++-- common/context/pom.xml | 6 --- .../context/ContextAwareExecutorTest.java | 19 ++++----- .../ContextAwareScheduledExecutorTest.java | 11 +++-- 6 files changed, 57 insertions(+), 61 deletions(-) diff --git a/common/configurable/src/main/java/io/helidon/common/configurable/ScheduledThreadPoolSupplier.java b/common/configurable/src/main/java/io/helidon/common/configurable/ScheduledThreadPoolSupplier.java index 19e021942d0..3a4dc239729 100644 --- a/common/configurable/src/main/java/io/helidon/common/configurable/ScheduledThreadPoolSupplier.java +++ b/common/configurable/src/main/java/io/helidon/common/configurable/ScheduledThreadPoolSupplier.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2019 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -79,23 +79,26 @@ public static ScheduledThreadPoolSupplier create() { return builder().build(); } + ScheduledThreadPoolExecutor getThreadPool() { + return new ScheduledThreadPoolExecutor(corePoolSize, + new ThreadFactory() { + private final AtomicInteger value = new AtomicInteger(); + + @Override + public Thread newThread(Runnable r) { + Thread t = new Thread(null, + r, + threadNamePrefix + value.incrementAndGet()); + t.setDaemon(isDaemon); + return t; + } + }); + } + @Override public synchronized ScheduledExecutorService get() { if (null == instance) { - ScheduledThreadPoolExecutor service; - service = new ScheduledThreadPoolExecutor(corePoolSize, - new ThreadFactory() { - private AtomicInteger value = new AtomicInteger(); - - @Override - public Thread newThread(Runnable r) { - Thread t = new Thread(null, - r, - threadNamePrefix + value.incrementAndGet()); - t.setDaemon(isDaemon); - return t; - } - }); + ScheduledThreadPoolExecutor service = getThreadPool(); if (prestart) { service.prestartAllCoreThreads(); } diff --git a/common/configurable/src/main/java/io/helidon/common/configurable/ThreadPoolSupplier.java b/common/configurable/src/main/java/io/helidon/common/configurable/ThreadPoolSupplier.java index 3b2a9070294..2d5f371a8c7 100644 --- a/common/configurable/src/main/java/io/helidon/common/configurable/ThreadPoolSupplier.java +++ b/common/configurable/src/main/java/io/helidon/common/configurable/ThreadPoolSupplier.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2019 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -88,27 +88,30 @@ public static ThreadPoolSupplier create() { return builder().build(); } + ThreadPoolExecutor getThreadPool() { + return new ThreadPoolExecutor(corePoolSize, + maxPoolSize, + keepAliveMinutes, + TimeUnit.MINUTES, + new LinkedBlockingQueue<>(queueCapacity), + new ThreadFactory() { + private final AtomicInteger value = new AtomicInteger(); + + @Override + public Thread newThread(Runnable r) { + Thread t = new Thread(null, + r, + threadNamePrefix + value.incrementAndGet()); + t.setDaemon(isDaemon); + return t; + } + }); + } + @Override public synchronized ExecutorService get() { if (null == instance) { - ThreadPoolExecutor executor; - executor = new ThreadPoolExecutor(corePoolSize, - maxPoolSize, - keepAliveMinutes, - TimeUnit.MINUTES, - new LinkedBlockingQueue<>(queueCapacity), - new ThreadFactory() { - private final AtomicInteger value = new AtomicInteger(); - - @Override - public Thread newThread(Runnable r) { - Thread t = new Thread(null, - r, - threadNamePrefix + value.incrementAndGet()); - t.setDaemon(isDaemon); - return t; - } - }); + ThreadPoolExecutor executor = getThreadPool(); instance = Contexts.wrap(executor); if (prestart) { executor.prestartAllCoreThreads(); diff --git a/common/configurable/src/test/java/io/helidon/common/configurable/ThreadPoolSupplierTest.java b/common/configurable/src/test/java/io/helidon/common/configurable/ThreadPoolSupplierTest.java index 0255711f84a..3954d1903a8 100644 --- a/common/configurable/src/test/java/io/helidon/common/configurable/ThreadPoolSupplierTest.java +++ b/common/configurable/src/test/java/io/helidon/common/configurable/ThreadPoolSupplierTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2019 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -40,7 +40,7 @@ class ThreadPoolSupplierTest { @BeforeAll static void initClass() { - defaultInstance = ThreadPoolSupplier.create().get(); + defaultInstance = ThreadPoolSupplier.create().getThreadPool(); builtInstance = ThreadPoolSupplier.builder() .threadNamePrefix("thread-pool-unit-test-") @@ -49,10 +49,10 @@ static void initClass() { .prestart(true) .queueCapacity(10) .build() - .get(); + .getThreadPool(); configuredInstance = ThreadPoolSupplier.create(Config.create().get("unit.thread-pool")) - .get(); + .getThreadPool(); } @Test diff --git a/common/context/pom.xml b/common/context/pom.xml index af131550192..e2d7f3d01b0 100644 --- a/common/context/pom.xml +++ b/common/context/pom.xml @@ -28,12 +28,6 @@ Helidon Common Context - - io.helidon.common - helidon-common-configurable - ${project.version} - test - org.junit.jupiter junit-jupiter-api diff --git a/common/context/src/test/java/io/helidon/common/context/ContextAwareExecutorTest.java b/common/context/src/test/java/io/helidon/common/context/ContextAwareExecutorTest.java index 71ce3e65923..9a9739749df 100644 --- a/common/context/src/test/java/io/helidon/common/context/ContextAwareExecutorTest.java +++ b/common/context/src/test/java/io/helidon/common/context/ContextAwareExecutorTest.java @@ -18,14 +18,11 @@ import java.util.LinkedList; import java.util.List; import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.concurrent.Future; -import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; -import io.helidon.common.configurable.ThreadPoolSupplier; - import org.junit.jupiter.api.Test; import static org.hamcrest.CoreMatchers.is; @@ -40,7 +37,7 @@ public class ContextAwareExecutorTest { @Test void testLifecycle() { - ThreadPoolExecutor wrapped = ThreadPoolSupplier.create().get(); + ExecutorService wrapped = Executors.newFixedThreadPool(1); ExecutorService wrapper = Contexts.wrap(wrapped); assertThat("Wrapped.isShutdown", wrapped.isShutdown(), is(false)); @@ -60,7 +57,7 @@ void testLifecycle() { @Test void testLifecycleWithAwaitTermination() throws InterruptedException { - ThreadPoolExecutor wrapped = ThreadPoolSupplier.create().get(); + ExecutorService wrapped = Executors.newFixedThreadPool(1); ExecutorService wrapper = Contexts.wrap(wrapped); assertThat("Wrapped.isShutdown", wrapped.isShutdown(), is(false)); @@ -83,7 +80,7 @@ void testInvokeAll() { List toCall = new LinkedList<>(); Context context = Context.create(); - ExecutorService executor = Contexts.wrap(ThreadPoolSupplier.create().get()); + ExecutorService executor = Contexts.wrap(Executors.newFixedThreadPool(1)); for (int i = 0; i < 10; i++) { context.register("key_" + i, TEST_STRING); @@ -107,7 +104,7 @@ void testInvokeAllWithTimeout() { List toCall = new LinkedList<>(); Context context = Context.create(); - ExecutorService executor = Contexts.wrap(ThreadPoolSupplier.create().get()); + ExecutorService executor = Contexts.wrap(Executors.newFixedThreadPool(1)); for (int i = 0; i < 10; i++) { context.register("key_" + i, TEST_STRING); @@ -131,7 +128,7 @@ void testInvokeAny() { List toCall = new LinkedList<>(); Context context = Context.create(); - ExecutorService executor = Contexts.wrap(ThreadPoolSupplier.create().get()); + ExecutorService executor = Contexts.wrap(Executors.newFixedThreadPool(1)); for (int i = 0; i < 10; i++) { context.register("key_" + i, TEST_STRING); @@ -153,7 +150,7 @@ void testInvokeAnyWithTimeout() { List toCall = new LinkedList<>(); Context context = Context.create(); - ExecutorService executor = Contexts.wrap(ThreadPoolSupplier.create().get()); + ExecutorService executor = Contexts.wrap(Executors.newFixedThreadPool(1)); for (int i = 0; i < 10; i++) { context.register("key_" + i, TEST_STRING); @@ -178,7 +175,7 @@ private TestCallable(int index) { } @Override - public String call() throws Exception { + public String call() { Context context = Contexts.context().get(); return context.get("key_" + index, String.class).get(); } diff --git a/common/context/src/test/java/io/helidon/common/context/ContextAwareScheduledExecutorTest.java b/common/context/src/test/java/io/helidon/common/context/ContextAwareScheduledExecutorTest.java index e708f5da02f..f356ddcdeba 100644 --- a/common/context/src/test/java/io/helidon/common/context/ContextAwareScheduledExecutorTest.java +++ b/common/context/src/test/java/io/helidon/common/context/ContextAwareScheduledExecutorTest.java @@ -17,13 +17,12 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; -import io.helidon.common.configurable.ScheduledThreadPoolSupplier; - import org.junit.jupiter.api.Test; import static org.hamcrest.CoreMatchers.is; @@ -39,7 +38,7 @@ public class ContextAwareScheduledExecutorTest { @Test void testSchedule() throws Exception { - ScheduledExecutorService wrapped = ScheduledThreadPoolSupplier.create().get(); + ScheduledExecutorService wrapped = Executors.newSingleThreadScheduledExecutor(); ScheduledExecutorService wrapper = Contexts.wrap(wrapped); @@ -80,7 +79,7 @@ void testSchedule() throws Exception { @Test void testScheduleCallable() throws Exception { - ScheduledExecutorService wrapped = ScheduledThreadPoolSupplier.create().get(); + ScheduledExecutorService wrapped = Executors.newSingleThreadScheduledExecutor(); ScheduledExecutorService wrapper = Contexts.wrap(wrapped); @@ -113,7 +112,7 @@ void testScheduleCallable() throws Exception { @Test void testScheduleAtFixedRate() throws InterruptedException, ExecutionException { - ScheduledExecutorService wrapped = ScheduledThreadPoolSupplier.create().get(); + ScheduledExecutorService wrapped = Executors.newSingleThreadScheduledExecutor(); ScheduledExecutorService wrapper = Contexts.wrap(wrapped); @@ -145,7 +144,7 @@ void testScheduleAtFixedRate() throws InterruptedException, ExecutionException { @Test void testScheduleAtFixedDelay() throws InterruptedException, ExecutionException { - ScheduledExecutorService wrapped = ScheduledThreadPoolSupplier.create().get(); + ScheduledExecutorService wrapped = Executors.newSingleThreadScheduledExecutor(); ScheduledExecutorService wrapper = Contexts.wrap(wrapped); CountDownLatch cdl = new CountDownLatch(2); From afa55748bd4d85074c6908c1a65c5241de83d175 Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Thu, 25 Apr 2019 19:47:54 +0200 Subject: [PATCH 06/15] Copyright fix. Signed-off-by: Tomas Langer --- common/configurable/src/main/java9/module-info.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/configurable/src/main/java9/module-info.java b/common/configurable/src/main/java9/module-info.java index 65af8d25805..10aa0abc07b 100644 --- a/common/configurable/src/main/java9/module-info.java +++ b/common/configurable/src/main/java9/module-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2019 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. From f4121314b66ae1cdcfaab54cf0d1e71413aad0b6 Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Fri, 26 Apr 2019 00:57:03 +0200 Subject: [PATCH 07/15] Fixed test Signed-off-by: Tomas Langer --- .../configurable/ScheduledThreadPoolSupplierTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/common/configurable/src/test/java/io/helidon/common/configurable/ScheduledThreadPoolSupplierTest.java b/common/configurable/src/test/java/io/helidon/common/configurable/ScheduledThreadPoolSupplierTest.java index c1ec78eed38..73893bc6a6e 100644 --- a/common/configurable/src/test/java/io/helidon/common/configurable/ScheduledThreadPoolSupplierTest.java +++ b/common/configurable/src/test/java/io/helidon/common/configurable/ScheduledThreadPoolSupplierTest.java @@ -39,7 +39,7 @@ class ScheduledThreadPoolSupplierTest { @BeforeAll static void initClass() { - defaultInstance = ScheduledThreadPoolSupplier.create().get(); + defaultInstance = ScheduledThreadPoolSupplier.create().getThreadPool(); builtInstance = ScheduledThreadPoolSupplier.builder() .threadNamePrefix("scheduled-thread-pool-unit-test-") @@ -47,10 +47,10 @@ static void initClass() { .daemon(true) .prestart(true) .build() - .get(); + .getThreadPool(); configuredInstance = ScheduledThreadPoolSupplier.create(Config.create() - .get("unit.scheduled-thread-pool")).get(); + .get("unit.scheduled-thread-pool")).getThreadPool(); } @Test From 1c1b5414f70e140fcf60060d2f7dd9c685159e27 Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Fri, 26 Apr 2019 01:35:35 +0200 Subject: [PATCH 08/15] Add unwrapping capability to Context aware executor services. Signed-off-by: Tomas Langer --- .../ScheduledThreadPoolSupplier.java | 13 ++++---- .../configurable/ThreadPoolSupplier.java | 13 ++++---- .../ScheduledThreadPoolSupplierTest.java | 2 +- .../context/ContextAwareExecutorImpl.java | 7 ++++- .../context/ContextAwareExecutorService.java | 30 +++++++++++++++++++ .../faulttolerance/SchedulerConfigTest.java | 12 ++++++-- 6 files changed, 61 insertions(+), 16 deletions(-) create mode 100644 common/context/src/main/java/io/helidon/common/context/ContextAwareExecutorService.java diff --git a/common/configurable/src/main/java/io/helidon/common/configurable/ScheduledThreadPoolSupplier.java b/common/configurable/src/main/java/io/helidon/common/configurable/ScheduledThreadPoolSupplier.java index 3a4dc239729..30aab6859d2 100644 --- a/common/configurable/src/main/java/io/helidon/common/configurable/ScheduledThreadPoolSupplier.java +++ b/common/configurable/src/main/java/io/helidon/common/configurable/ScheduledThreadPoolSupplier.java @@ -80,7 +80,8 @@ public static ScheduledThreadPoolSupplier create() { } ScheduledThreadPoolExecutor getThreadPool() { - return new ScheduledThreadPoolExecutor(corePoolSize, + ScheduledThreadPoolExecutor result; + result = new ScheduledThreadPoolExecutor(corePoolSize, new ThreadFactory() { private final AtomicInteger value = new AtomicInteger(); @@ -93,16 +94,16 @@ public Thread newThread(Runnable r) { return t; } }); + if (prestart) { + result.prestartAllCoreThreads(); + } + return result; } @Override public synchronized ScheduledExecutorService get() { if (null == instance) { - ScheduledThreadPoolExecutor service = getThreadPool(); - if (prestart) { - service.prestartAllCoreThreads(); - } - instance = Contexts.wrap(service); + instance = Contexts.wrap(getThreadPool()); } return instance; } diff --git a/common/configurable/src/main/java/io/helidon/common/configurable/ThreadPoolSupplier.java b/common/configurable/src/main/java/io/helidon/common/configurable/ThreadPoolSupplier.java index 2d5f371a8c7..c08e9f87016 100644 --- a/common/configurable/src/main/java/io/helidon/common/configurable/ThreadPoolSupplier.java +++ b/common/configurable/src/main/java/io/helidon/common/configurable/ThreadPoolSupplier.java @@ -89,7 +89,8 @@ public static ThreadPoolSupplier create() { } ThreadPoolExecutor getThreadPool() { - return new ThreadPoolExecutor(corePoolSize, + ThreadPoolExecutor result; + result = new ThreadPoolExecutor(corePoolSize, maxPoolSize, keepAliveMinutes, TimeUnit.MINUTES, @@ -106,16 +107,16 @@ public Thread newThread(Runnable r) { return t; } }); + if (prestart) { + result.prestartAllCoreThreads(); + } + return result; } @Override public synchronized ExecutorService get() { if (null == instance) { - ThreadPoolExecutor executor = getThreadPool(); - instance = Contexts.wrap(executor); - if (prestart) { - executor.prestartAllCoreThreads(); - } + instance = Contexts.wrap(getThreadPool()); } return instance; } diff --git a/common/configurable/src/test/java/io/helidon/common/configurable/ScheduledThreadPoolSupplierTest.java b/common/configurable/src/test/java/io/helidon/common/configurable/ScheduledThreadPoolSupplierTest.java index 73893bc6a6e..861016336f0 100644 --- a/common/configurable/src/test/java/io/helidon/common/configurable/ScheduledThreadPoolSupplierTest.java +++ b/common/configurable/src/test/java/io/helidon/common/configurable/ScheduledThreadPoolSupplierTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2019 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/common/context/src/main/java/io/helidon/common/context/ContextAwareExecutorImpl.java b/common/context/src/main/java/io/helidon/common/context/ContextAwareExecutorImpl.java index 6d1907e9ff9..70b87126d5e 100644 --- a/common/context/src/main/java/io/helidon/common/context/ContextAwareExecutorImpl.java +++ b/common/context/src/main/java/io/helidon/common/context/ContextAwareExecutorImpl.java @@ -26,7 +26,7 @@ import java.util.concurrent.TimeoutException; import java.util.stream.Collectors; -class ContextAwareExecutorImpl implements ExecutorService { +class ContextAwareExecutorImpl implements ContextAwareExecutorService { private final ExecutorService delegate; ContextAwareExecutorImpl(ExecutorService toWrap) { @@ -100,6 +100,11 @@ public void execute(Runnable command) { delegate.execute(wrap(command)); } + @Override + public ExecutorService unwrap() { + return delegate; + } + protected Collection> wrap(Collection> tasks) { return tasks.stream() .map(this::wrap) diff --git a/common/context/src/main/java/io/helidon/common/context/ContextAwareExecutorService.java b/common/context/src/main/java/io/helidon/common/context/ContextAwareExecutorService.java new file mode 100644 index 00000000000..75b934bb84b --- /dev/null +++ b/common/context/src/main/java/io/helidon/common/context/ContextAwareExecutorService.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * 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. + */ +package io.helidon.common.context; + +import java.util.concurrent.ExecutorService; + +/** + * An interface for wrapped executor services. + */ +public interface ContextAwareExecutorService extends ExecutorService { + /** + * Unwrap the executor service. + * + * @return the instance that was used to create this wrapper. + */ + ExecutorService unwrap(); +} diff --git a/microprofile/fault-tolerance/src/test/java/io/helidon/microprofile/faulttolerance/SchedulerConfigTest.java b/microprofile/fault-tolerance/src/test/java/io/helidon/microprofile/faulttolerance/SchedulerConfigTest.java index 00c1c9b1034..7ec5c6d4a74 100644 --- a/microprofile/fault-tolerance/src/test/java/io/helidon/microprofile/faulttolerance/SchedulerConfigTest.java +++ b/microprofile/fault-tolerance/src/test/java/io/helidon/microprofile/faulttolerance/SchedulerConfigTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2019 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,11 @@ package io.helidon.microprofile.faulttolerance; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledThreadPoolExecutor; + import io.helidon.common.configurable.ScheduledThreadPoolSupplier; +import io.helidon.common.context.ContextAwareExecutorService; import io.helidon.microprofile.server.Server; import org.junit.jupiter.api.Test; @@ -44,7 +48,11 @@ void testNonDefaultConfig() { CommandScheduler commandScheduler = CommandScheduler.instance(); assertThat(commandScheduler, notNullValue()); ScheduledThreadPoolSupplier poolSupplier = commandScheduler.poolSupplier(); - assertThat(poolSupplier.get().getCorePoolSize(), is(8)); + + ScheduledExecutorService service = poolSupplier.get(); + ContextAwareExecutorService executorService = ((ContextAwareExecutorService) service); + ScheduledThreadPoolExecutor stpe = (ScheduledThreadPoolExecutor) executorService.unwrap(); + assertThat(stpe.getCorePoolSize(), is(8)); } finally { if (server != null) { server.stop(); From 0108902e100b7e404ff019ada28a409d85a96983 Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Sat, 4 May 2019 20:18:01 +0200 Subject: [PATCH 09/15] Review comments incorporated. Signed-off-by: Tomas Langer --- .../io/helidon/common/context/Context.java | 2 +- .../context/ContextAwareExecutorImpl.java | 18 ++++++++++-------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/common/context/src/main/java/io/helidon/common/context/Context.java b/common/context/src/main/java/io/helidon/common/context/Context.java index 7d825163ba0..038fb1f15ad 100644 --- a/common/context/src/main/java/io/helidon/common/context/Context.java +++ b/common/context/src/main/java/io/helidon/common/context/Context.java @@ -80,7 +80,7 @@ static Context create(Context parent) { void register(T instance); /** - * Register a new instance using a provided supplier. The supplier is guarantied to be called at most once when it's + * Register a new instance using a provided supplier. The supplier is guaranteed to be called at most once when it's * requested by the {@link #get(Class)} method. The returned value is then registered and the supplier is never used again. * * @param type a type of supplied instance diff --git a/common/context/src/main/java/io/helidon/common/context/ContextAwareExecutorImpl.java b/common/context/src/main/java/io/helidon/common/context/ContextAwareExecutorImpl.java index 70b87126d5e..1e3ff9bab49 100644 --- a/common/context/src/main/java/io/helidon/common/context/ContextAwareExecutorImpl.java +++ b/common/context/src/main/java/io/helidon/common/context/ContextAwareExecutorImpl.java @@ -115,18 +115,20 @@ protected Collection> wrap(Collection Callable wrap(Callable task) { Optional context = Contexts.context(); - return context.>map(value -> () -> Contexts.inContext(value, task)) - // no need to wrap, no context - .orElse(task); + if (context.isPresent()) { + return () -> Contexts.inContext(context.get(), task); + } else { + return task; + } } protected Runnable wrap(Runnable command) { Optional context = Contexts.context(); - // no need to wrap, no context - return context.map(value -> () -> Contexts.inContext(value, command)) - // no need to wrap, no context - .orElse(command); - + if (context.isPresent()) { + return () -> Contexts.inContext(context.get(), command); + } else { + return command; + } } } From 856ef41303e58c32d701660937f16e52e5f9b654 Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Sat, 4 May 2019 20:57:41 +0200 Subject: [PATCH 10/15] Added id of context. ListContextualRegistry now delegates to common context. Signed-off-by: Tomas Langer --- common/context/pom.xml | 5 + .../io/helidon/common/context/Context.java | 81 +++++++- .../helidon/common/context/ListContext.java | 25 +-- .../context/src/main/java9/module-info.java | 1 + .../common/context/ListContextTest.java | 24 +++ .../common/http/ListContextualRegistry.java | 173 ++---------------- 6 files changed, 141 insertions(+), 168 deletions(-) diff --git a/common/context/pom.xml b/common/context/pom.xml index e2d7f3d01b0..4981a5bc120 100644 --- a/common/context/pom.xml +++ b/common/context/pom.xml @@ -28,6 +28,11 @@ Helidon Common Context + + io.helidon.common + helidon-common + ${project.version} + org.junit.jupiter junit-jupiter-api diff --git a/common/context/src/main/java/io/helidon/common/context/Context.java b/common/context/src/main/java/io/helidon/common/context/Context.java index 038fb1f15ad..74c055f328f 100644 --- a/common/context/src/main/java/io/helidon/common/context/Context.java +++ b/common/context/src/main/java/io/helidon/common/context/Context.java @@ -17,6 +17,8 @@ package io.helidon.common.context; import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicLong; import java.util.function.Supplier; /** @@ -55,7 +57,7 @@ public interface Context { * @return new instance */ static Context create() { - return new ListContext(); + return builder().build(); } /** @@ -67,7 +69,16 @@ static Context create() { * @return new instance */ static Context create(Context parent) { - return new ListContext(parent); + return builder().parent(parent).build(); + } + + /** + * Fluent API builder for advanced configuration. + * + * @return a new builder + */ + static Builder builder() { + return new Builder(); } /** @@ -144,4 +155,70 @@ static Context create(Context parent) { * @throws NullPointerException If {@code classifier} is null. */ Optional get(Object classifier, Class type); + + /** + * A unique id of this context within this runtime. + * + * @return id of this context + */ + String id(); + + /** + * Fluent API builder for {@link Context}. + */ + class Builder implements io.helidon.common.Builder { + private static final AtomicLong PARENT_CONTEXT_COUNTER = new AtomicLong(1); + private Context parent; + private String id; + + @Override + public Context build() { + if (null == id) { + id = generateId(); + } + return new ListContext(this); + } + + private String generateId() { + if (null == parent) { + return String.valueOf(PARENT_CONTEXT_COUNTER.getAndIncrement()); + } + if (parent instanceof ListContext) { + return parent.id() + ":" + ((ListContext) parent).contextCounter().getAndIncrement(); + } + // we cannot depend on the parent, so let's use UUID + return parent.id() + ":" + UUID.randomUUID(); + } + + + /** + * Parent of the new context. + * @param parent parent context + * + * @return updated builder instance + */ + public Builder parent(Context parent) { + this.parent = parent; + return this; + } + + /** + * Identification of the new context, should be unique within this runtime. + * + * @param id context identification + * @return updated builder instance + */ + public Builder id(String id) { + this.id = id; + return this; + } + + Context parent() { + return parent; + } + + String id() { + return id; + } + } } diff --git a/common/context/src/main/java/io/helidon/common/context/ListContext.java b/common/context/src/main/java/io/helidon/common/context/ListContext.java index 79ff37589f7..502a0c0ec0e 100644 --- a/common/context/src/main/java/io/helidon/common/context/ListContext.java +++ b/common/context/src/main/java/io/helidon/common/context/ListContext.java @@ -21,6 +21,7 @@ import java.util.Objects; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; @@ -30,25 +31,21 @@ * A {@link Context} implementation with deque registry. */ class ListContext implements Context { + private final AtomicLong contextCounter = new AtomicLong(1); private final Context parent; private final ConcurrentHashMap classifiers = new ConcurrentHashMap<>(); private final ClassifiedRegistry registry = new ClassifiedRegistry(); + private final String contextId; - /** - * Creates new instance with defined parent. - * - * @param parent a parent context or {@code null}. - */ - ListContext(Context parent) { - this.parent = parent; + ListContext(Builder builder) { + this.parent = builder.parent(); + this.contextId = builder.id(); } - /** - * Creates new instance. - */ - ListContext() { - this(null); + @Override + public String id() { + return contextId; } @Override @@ -109,6 +106,10 @@ public Optional get(Object classifier, Class type) { } } + AtomicLong contextCounter() { + return contextCounter; + } + private interface RegisteredItem { T get(); diff --git a/common/context/src/main/java9/module-info.java b/common/context/src/main/java9/module-info.java index 00de7c985c3..04ccaf0726b 100644 --- a/common/context/src/main/java9/module-info.java +++ b/common/context/src/main/java9/module-info.java @@ -19,6 +19,7 @@ */ module io.helidon.common.context { requires java.logging; + requires io.helidon.common; exports io.helidon.common.context; } diff --git a/common/context/src/test/java/io/helidon/common/context/ListContextTest.java b/common/context/src/test/java/io/helidon/common/context/ListContextTest.java index 7b566430360..fab1c3e255f 100644 --- a/common/context/src/test/java/io/helidon/common/context/ListContextTest.java +++ b/common/context/src/test/java/io/helidon/common/context/ListContextTest.java @@ -25,12 +25,36 @@ import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.IsNot.not; /** * Tests {@link io.helidon.common.context.ListContext} and {@link Context}. */ public class ListContextTest { + @Test + public void testId() { + Context first = Context.create(); + Context second = Context.create(); + Context third = Context.create(first); + Context fourth = Context.create(second); + Context fifth = Context.create(fourth); + + assertThat(first.id(), not(second.id())); + assertThat(first.id(), not(third.id())); + assertThat(first.id(), not(fourth.id())); + assertThat(first.id(), not(fifth.id())); + + assertThat(second.id(), not(third.id())); + assertThat(second.id(), not(fourth.id())); + assertThat(second.id(), not(fifth.id())); + + assertThat(third.id(), not(fourth.id())); + assertThat(third.id(), not(fifth.id())); + + assertThat(fourth.id(), not(fifth.id())); + } + @Test public void create() { assertThat(Context.create(), notNullValue()); diff --git a/common/http/src/main/java/io/helidon/common/http/ListContextualRegistry.java b/common/http/src/main/java/io/helidon/common/http/ListContextualRegistry.java index f386c3162ce..8a876f200fd 100644 --- a/common/http/src/main/java/io/helidon/common/http/ListContextualRegistry.java +++ b/common/http/src/main/java/io/helidon/common/http/ListContextualRegistry.java @@ -16,24 +16,16 @@ package io.helidon.common.http; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; import java.util.Optional; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReadWriteLock; -import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.function.Supplier; +import io.helidon.common.context.Context; + /** * A {@link ContextualRegistry} implementation with deque registry. */ class ListContextualRegistry implements ContextualRegistry { - - private final ContextualRegistry parent; - private final ConcurrentHashMap classifiers = new ConcurrentHashMap<>(); - private final ClassifiedRegistry registry = new ClassifiedRegistry(); + private final Context delegate; /** * Creates new instance with defined parent. @@ -41,7 +33,11 @@ class ListContextualRegistry implements ContextualRegistry { * @param parent a parent context or {@code null}. */ ListContextualRegistry(ContextualRegistry parent) { - this.parent = parent; + if (parent instanceof ListContextualRegistry) { + this.delegate = Context.create(((ListContextualRegistry) parent).delegate); + } else { + this.delegate = Context.create(parent); + } } /** @@ -51,169 +47,38 @@ class ListContextualRegistry implements ContextualRegistry { this(null); } + @Override + public String id() { + return delegate.id(); + } + @Override public void register(T instance) { - registry.register(instance); + delegate.register(instance); } @Override public void supply(Class type, Supplier supplier) { - registry.supply(type, supplier); + delegate.supply(type, supplier); } @Override public Optional get(Class type) { - T result = registry.get(type); - if (result == null) { - if (parent == null) { - return Optional.empty(); - } else { - return parent.get(type); - } - } else { - return Optional.of(result); - } + return delegate.get(type); } @Override public void register(Object classifier, T instance) { - Objects.requireNonNull(classifier, "Parameter 'classifier' is null!"); - ClassifiedRegistry cr = classifiers.computeIfAbsent(classifier, k -> new ClassifiedRegistry()); - cr.register(instance); + delegate.register(classifier, instance); } @Override public void supply(Object classifier, Class type, Supplier supplier) { - Objects.requireNonNull(classifier, "Parameter 'classifier' is null!"); - ClassifiedRegistry cr = classifiers.computeIfAbsent(classifier, k -> new ClassifiedRegistry()); - cr.supply(type, supplier); + delegate.supply(classifier, type, supplier); } @Override public Optional get(Object classifier, Class type) { - Objects.requireNonNull(classifier, "Parameter 'classifier' is null!"); - ClassifiedRegistry cr = classifiers.get(classifier); - if (cr != null) { - T result = cr.get(type); - if (result == null && parent != null) { - return parent.get(classifier, type); - } else { - return Optional.ofNullable(result); - } - } else { - if (parent != null) { - return parent.get(classifier, type); - } else { - return Optional.empty(); - } - } - } - - private interface RegisteredItem { - T get(); - - Class getType(); - } - - private static class ClassifiedRegistry { - private final ReadWriteLock lock = new ReentrantReadWriteLock(); - private final List content = new ArrayList<>(); - - private void registerItem(RegisteredItem item) { - Lock l = lock.writeLock(); - try { - Class c = item.getType(); - l.lock(); - for (int i = 0; i < content.size(); i++) { - RegisteredItem reg = content.get(i); - if (c == reg.getType()) { - content.remove(i); - break; - } - } - content.add(item); - } finally { - l.unlock(); - } - } - - @SuppressWarnings("unchecked") - void register(T instance) { - Objects.requireNonNull(instance, "Parameter 'instance' is null!"); - registerItem(new RegisteredInstance(instance)); - } - - @SuppressWarnings("unchecked") - void supply(Class type, Supplier supplier) { - Objects.requireNonNull(type, "Parameter 'type' is null!"); - Objects.requireNonNull(supplier, "Parameter 'supplier' is null!"); - registerItem(new RegisteredSupplier(type, supplier)); - } - - T get(Class type) { - Objects.requireNonNull(type, "Parameter 'type' is null!"); - Lock l = lock.readLock(); - try { - l.lock(); - for (int i = content.size() - 1; i >= 0; i--) { - RegisteredItem item = content.get(i); - if (type.isAssignableFrom(item.getType())) { - return type.cast(item.get()); - } - } - } finally { - l.unlock(); - } - return null; - } - } - - private static class RegisteredSupplier implements RegisteredItem { - private final Class type; - private final Supplier supplier; - private volatile boolean supplied = false; - private volatile T instance; - - RegisteredSupplier(Class type, Supplier supplier) { - this.type = type; - this.supplier = supplier; - } - - @Override - public T get() { - if (!supplied) { - synchronized (this) { - if (!supplied) { - supplied = true; - instance = supplier.get(); - } - } - } - return instance; - } - - @Override - public Class getType() { - return type; - } - } - - private static class RegisteredInstance implements RegisteredItem { - private final T instance; - - RegisteredInstance(T instance) { - this.instance = instance; - } - - @Override - public T get() { - return instance; - } - - @Override - @SuppressWarnings("unchecked") - public Class getType() { - return (Class) instance.getClass(); - } + return delegate.get(classifier, type); } } From 229ec697c4f60e02cd0a7c117bd5b3514c4b6fa4 Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Sat, 4 May 2019 21:09:34 +0200 Subject: [PATCH 11/15] Copyright Signed-off-by: Tomas Langer --- .../java/io/helidon/common/http/ListContextualRegistry.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/http/src/main/java/io/helidon/common/http/ListContextualRegistry.java b/common/http/src/main/java/io/helidon/common/http/ListContextualRegistry.java index 8a876f200fd..79223930ac0 100644 --- a/common/http/src/main/java/io/helidon/common/http/ListContextualRegistry.java +++ b/common/http/src/main/java/io/helidon/common/http/ListContextualRegistry.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2019 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. From 0361dde27bb0192cef5f880629bbf803883df5e3 Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Mon, 6 May 2019 20:35:23 +0200 Subject: [PATCH 12/15] Useless instanceof removed. Signed-off-by: Tomas Langer --- .../src/main/java/io/helidon/common/context/Contexts.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/common/context/src/main/java/io/helidon/common/context/Contexts.java b/common/context/src/main/java/io/helidon/common/context/Contexts.java index 9876db43d30..bbe4823aaa0 100644 --- a/common/context/src/main/java/io/helidon/common/context/Contexts.java +++ b/common/context/src/main/java/io/helidon/common/context/Contexts.java @@ -64,12 +64,10 @@ public static Optional context() { * @return a new executor service wrapping the provided one */ public static ExecutorService wrap(ExecutorService toWrap) { + // as ContextAwareScheduledExecutorImpl extends ContextAwareExecutorImpl, this is sufficient if (toWrap instanceof ContextAwareExecutorImpl) { return toWrap; } - if (toWrap instanceof ContextAwareScheduledExecutorImpl) { - return toWrap; - } return new ContextAwareExecutorImpl(toWrap); } From d478f73b99961ec68b665caabf8a444309a8df89 Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Mon, 6 May 2019 23:45:35 +0200 Subject: [PATCH 13/15] Renamed methods for inContext to runInContext and invokeInContext Signed-off-by: Tomas Langer --- .../common/context/ContextAwareExecutorImpl.java | 4 ++-- .../java/io/helidon/common/context/Contexts.java | 4 ++-- .../common/context/ContextAwareExecutorTest.java | 8 ++++---- .../context/ContextAwareScheduledExecutorTest.java | 8 ++++---- .../java/io/helidon/common/context/ContextsTest.java | 12 ++++++------ 5 files changed, 18 insertions(+), 18 deletions(-) diff --git a/common/context/src/main/java/io/helidon/common/context/ContextAwareExecutorImpl.java b/common/context/src/main/java/io/helidon/common/context/ContextAwareExecutorImpl.java index 1e3ff9bab49..761f7325062 100644 --- a/common/context/src/main/java/io/helidon/common/context/ContextAwareExecutorImpl.java +++ b/common/context/src/main/java/io/helidon/common/context/ContextAwareExecutorImpl.java @@ -116,7 +116,7 @@ protected Callable wrap(Callable task) { Optional context = Contexts.context(); if (context.isPresent()) { - return () -> Contexts.inContext(context.get(), task); + return () -> Contexts.invokeInContext(context.get(), task); } else { return task; } @@ -126,7 +126,7 @@ protected Runnable wrap(Runnable command) { Optional context = Contexts.context(); if (context.isPresent()) { - return () -> Contexts.inContext(context.get(), command); + return () -> Contexts.runInContext(context.get(), command); } else { return command; } diff --git a/common/context/src/main/java/io/helidon/common/context/Contexts.java b/common/context/src/main/java/io/helidon/common/context/Contexts.java index bbe4823aaa0..477afce5c57 100644 --- a/common/context/src/main/java/io/helidon/common/context/Contexts.java +++ b/common/context/src/main/java/io/helidon/common/context/Contexts.java @@ -92,7 +92,7 @@ public static ScheduledExecutorService wrap(ScheduledExecutorService toWrap) { * @param context context to run in * @param runnable runnable to execute in context */ - public static void inContext(Context context, Runnable runnable) { + public static void runInContext(Context context, Runnable runnable) { push(context); try { runnable.run(); @@ -112,7 +112,7 @@ public static void inContext(Context context, Runnable runnable) { * @throws java.lang.RuntimeException in case the {@link java.util.concurrent.Callable#call()} threw a * runtime exception */ - public static T inContext(Context context, Callable callable) { + public static T invokeInContext(Context context, Callable callable) { push(context); try { return callable.call(); diff --git a/common/context/src/test/java/io/helidon/common/context/ContextAwareExecutorTest.java b/common/context/src/test/java/io/helidon/common/context/ContextAwareExecutorTest.java index 9a9739749df..8f993759a86 100644 --- a/common/context/src/test/java/io/helidon/common/context/ContextAwareExecutorTest.java +++ b/common/context/src/test/java/io/helidon/common/context/ContextAwareExecutorTest.java @@ -87,7 +87,7 @@ void testInvokeAll() { toCall.add(new TestCallable(i)); } - Contexts.inContext(context, () -> { + Contexts.runInContext(context, () -> { try { List> futures = executor.invokeAll(toCall); for (Future future : futures) { @@ -111,7 +111,7 @@ void testInvokeAllWithTimeout() { toCall.add(new TestCallable(i)); } - Contexts.inContext(context, () -> { + Contexts.runInContext(context, () -> { try { List> futures = executor.invokeAll(toCall, 1, TimeUnit.SECONDS); for (Future future : futures) { @@ -135,7 +135,7 @@ void testInvokeAny() { toCall.add(new TestCallable(i)); } - Contexts.inContext(context, () -> { + Contexts.runInContext(context, () -> { try { String result = executor.invokeAny(toCall); assertThat(result, is(TEST_STRING)); @@ -157,7 +157,7 @@ void testInvokeAnyWithTimeout() { toCall.add(new TestCallable(i)); } - Contexts.inContext(context, () -> { + Contexts.runInContext(context, () -> { try { String result = executor.invokeAny(toCall, 1, TimeUnit.SECONDS); assertThat(result, is(TEST_STRING)); diff --git a/common/context/src/test/java/io/helidon/common/context/ContextAwareScheduledExecutorTest.java b/common/context/src/test/java/io/helidon/common/context/ContextAwareScheduledExecutorTest.java index f356ddcdeba..e6029e2605d 100644 --- a/common/context/src/test/java/io/helidon/common/context/ContextAwareScheduledExecutorTest.java +++ b/common/context/src/test/java/io/helidon/common/context/ContextAwareScheduledExecutorTest.java @@ -49,7 +49,7 @@ void testSchedule() throws Exception { AtomicReference> futureRef = new AtomicReference<>(); AtomicReference exceptionRef = new AtomicReference<>(); - Contexts.inContext(context, () -> { + Contexts.runInContext(context, () -> { futureRef.set(wrapper.schedule(() -> { try { Contexts.context() @@ -89,7 +89,7 @@ void testScheduleCallable() throws Exception { AtomicReference> futureRef = new AtomicReference<>(); AtomicReference exceptionRef = new AtomicReference<>(); - Contexts.inContext(context, () -> { + Contexts.runInContext(context, () -> { futureRef.set(wrapper.schedule(() -> { try { return Contexts.context().get().get(String.class).get(); @@ -123,7 +123,7 @@ void testScheduleAtFixedRate() throws InterruptedException, ExecutionException { AtomicReference> futureRef = new AtomicReference<>(); - Contexts.inContext(context, () -> { + Contexts.runInContext(context, () -> { futureRef.set(wrapper.scheduleAtFixedRate(() -> { Contexts.context() .get() @@ -154,7 +154,7 @@ void testScheduleAtFixedDelay() throws InterruptedException, ExecutionException AtomicReference> futureRef = new AtomicReference<>(); - Contexts.inContext(context, () -> { + Contexts.runInContext(context, () -> { futureRef.set(wrapper.scheduleWithFixedDelay(() -> { Contexts.context() .get() diff --git a/common/context/src/test/java/io/helidon/common/context/ContextsTest.java b/common/context/src/test/java/io/helidon/common/context/ContextsTest.java index 9d6da58141c..994ca792c45 100644 --- a/common/context/src/test/java/io/helidon/common/context/ContextsTest.java +++ b/common/context/src/test/java/io/helidon/common/context/ContextsTest.java @@ -117,7 +117,7 @@ void testContextExecuteRunnable() throws InterruptedException { cdl.countDown(); }; - Contexts.inContext(ctx, () -> service.execute(runnable)); + Contexts.runInContext(ctx, () -> service.execute(runnable)); cdl.await(); @@ -132,7 +132,7 @@ void testContextSubmitRunnable() throws InterruptedException, ExecutionException Runnable runnable = () -> ref.set(Contexts.context().get().get("message", String.class).orElse("No context found")); - Future future = Contexts.inContext(ctx, () -> service.submit(runnable)); + Future future = Contexts.invokeInContext(ctx, () -> service.submit(runnable)); future.get(); assertThat(ref.get(), is(TEST_STRING + "_2")); @@ -146,7 +146,7 @@ void testContextSubmitRunnableWithResult() throws InterruptedException, Executio Runnable runnable = () -> ref.set(Contexts.context().get().get("message", String.class).orElse("No context found")); - Future future = Contexts.inContext(ctx, () -> service.submit(runnable, "Hello")); + Future future = Contexts.invokeInContext(ctx, () -> service.submit(runnable, "Hello")); String result = future.get(); assertThat(result, is("Hello")); @@ -160,7 +160,7 @@ void testContextSubmitCallable() throws ExecutionException, InterruptedException Context ctx = Context.create(); ctx.register("message", TEST_STRING + "_1"); - Future future = Contexts.inContext(ctx, () -> service.submit(callable)); + Future future = Contexts.invokeInContext(ctx, () -> service.submit(callable)); assertThat(future.get(), is(TEST_STRING + "_1")); } @@ -175,12 +175,12 @@ void testMultipleContexts() { firstLevel.register("first", TEST_STRING + "_1"); firstLevel.register("second", TEST_STRING); - Contexts.inContext(topLevel, () -> { + Contexts.runInContext(topLevel, () -> { Context myContext = Contexts.context().get(); assertThat(myContext.get("topLevel", String.class), is(TEST_STRING_OPTIONAL)); assertThat(myContext.get("first", String.class), is(TEST_STRING_OPTIONAL)); - Contexts.inContext(firstLevel, () -> { + Contexts.runInContext(firstLevel, () -> { Context firstLevelContext = Contexts.context().get(); assertThat(firstLevelContext.get("topLevel", String.class), is(TEST_STRING_OPTIONAL)); assertThat(firstLevelContext.get("first", String.class), is(Optional.of(TEST_STRING + "_1"))); From dc77e623b2feb08a56e86d59043e51e230e1e9a9 Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Tue, 7 May 2019 00:44:53 +0200 Subject: [PATCH 14/15] Renamed methods fix usage Signed-off-by: Tomas Langer --- .../src/main/java/io/helidon/webserver/RequestRouting.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webserver/webserver/src/main/java/io/helidon/webserver/RequestRouting.java b/webserver/webserver/src/main/java/io/helidon/webserver/RequestRouting.java index 345895a7b18..15850d65f7f 100644 --- a/webserver/webserver/src/main/java/io/helidon/webserver/RequestRouting.java +++ b/webserver/webserver/src/main/java/io/helidon/webserver/RequestRouting.java @@ -351,7 +351,7 @@ public void next() { LOGGER.finest(() -> "(reqID: " + requestId() + ") Routing next: " + nextItem.path); requestSpan.log(nextItem.handlerRoute.diagnosticEvent()); // execute in the context, so context can be retrieved with Contexts (runs in our thread) - Contexts.inContext(nextRequest.context(), () -> nextItem.handlerRoute + Contexts.runInContext(nextRequest.context(), () -> nextItem.handlerRoute .handler() .accept(nextRequest, nextResponse)); } catch (RuntimeException re) { From 6fb9d257be5c49c0004df6b985bb80a02fb5d11e Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Tue, 7 May 2019 16:23:33 +0200 Subject: [PATCH 15/15] Align naming of static methods. Signed-off-by: Tomas Langer --- .../io/helidon/common/context/ContextAwareExecutorImpl.java | 2 +- .../src/main/java/io/helidon/common/context/Contexts.java | 2 +- .../test/java/io/helidon/common/context/ContextsTest.java | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/common/context/src/main/java/io/helidon/common/context/ContextAwareExecutorImpl.java b/common/context/src/main/java/io/helidon/common/context/ContextAwareExecutorImpl.java index 761f7325062..b7173c6ae95 100644 --- a/common/context/src/main/java/io/helidon/common/context/ContextAwareExecutorImpl.java +++ b/common/context/src/main/java/io/helidon/common/context/ContextAwareExecutorImpl.java @@ -116,7 +116,7 @@ protected Callable wrap(Callable task) { Optional context = Contexts.context(); if (context.isPresent()) { - return () -> Contexts.invokeInContext(context.get(), task); + return () -> Contexts.runInContext(context.get(), task); } else { return task; } diff --git a/common/context/src/main/java/io/helidon/common/context/Contexts.java b/common/context/src/main/java/io/helidon/common/context/Contexts.java index 477afce5c57..e54fddac0cf 100644 --- a/common/context/src/main/java/io/helidon/common/context/Contexts.java +++ b/common/context/src/main/java/io/helidon/common/context/Contexts.java @@ -112,7 +112,7 @@ public static void runInContext(Context context, Runnable runnable) { * @throws java.lang.RuntimeException in case the {@link java.util.concurrent.Callable#call()} threw a * runtime exception */ - public static T invokeInContext(Context context, Callable callable) { + public static T runInContext(Context context, Callable callable) { push(context); try { return callable.call(); diff --git a/common/context/src/test/java/io/helidon/common/context/ContextsTest.java b/common/context/src/test/java/io/helidon/common/context/ContextsTest.java index 994ca792c45..3a1722fe168 100644 --- a/common/context/src/test/java/io/helidon/common/context/ContextsTest.java +++ b/common/context/src/test/java/io/helidon/common/context/ContextsTest.java @@ -132,7 +132,7 @@ void testContextSubmitRunnable() throws InterruptedException, ExecutionException Runnable runnable = () -> ref.set(Contexts.context().get().get("message", String.class).orElse("No context found")); - Future future = Contexts.invokeInContext(ctx, () -> service.submit(runnable)); + Future future = Contexts.runInContext(ctx, () -> service.submit(runnable)); future.get(); assertThat(ref.get(), is(TEST_STRING + "_2")); @@ -146,7 +146,7 @@ void testContextSubmitRunnableWithResult() throws InterruptedException, Executio Runnable runnable = () -> ref.set(Contexts.context().get().get("message", String.class).orElse("No context found")); - Future future = Contexts.invokeInContext(ctx, () -> service.submit(runnable, "Hello")); + Future future = Contexts.runInContext(ctx, () -> service.submit(runnable, "Hello")); String result = future.get(); assertThat(result, is("Hello")); @@ -160,7 +160,7 @@ void testContextSubmitCallable() throws ExecutionException, InterruptedException Context ctx = Context.create(); ctx.register("message", TEST_STRING + "_1"); - Future future = Contexts.invokeInContext(ctx, () -> service.submit(callable)); + Future future = Contexts.runInContext(ctx, () -> service.submit(callable)); assertThat(future.get(), is(TEST_STRING + "_1")); }