ratpack.registry.Registry Maven / Gradle / Ivy
/*
* Copyright 2013 the original author or authors.
*
* 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 ratpack.registry;
import com.google.common.reflect.TypeToken;
import ratpack.func.Action;
import ratpack.func.Function;
import ratpack.registry.internal.*;
import java.util.Optional;
import java.util.function.Supplier;
/**
* An object registry.
*
* Registries are primarily used for inter {@link ratpack.handling.Handler} communication in request processing.
* The {@link ratpack.handling.Context} object that handlers operate on implements the {@link ratpack.registry.Registry} interface.
*
* import ratpack.handling.Handler;
* import ratpack.handling.Context;
* import ratpack.registry.Registry;
*
* import static org.junit.Assert.assertTrue;
*
* public class Thing {
* private final String name
* public Thing(String name) { this.name = name; }
* public String getName() { return name; }
* }
*
* public class UpstreamHandler implements Handler {
* public void handle(Context context) {
* context.next(Registry.single(new Thing("foo")));
* }
* }
*
* public class DownstreamHandler implements Handler {
* public void handle(Context context) {
* assertTrue(context instanceof Registry);
* Thing thing = context.get(Thing.class);
* context.render(thing.getName());
* }
* }
*
* import ratpack.test.handling.HandlingResult;
* import ratpack.test.handling.RequestFixture;
*
* import static ratpack.handling.Handlers.chain;
* import static ratpack.func.Action.noop;
*
* import static org.junit.Assert.assertEquals;
*
* Handler chain = chain(new UpstreamHandler(), new DownstreamHandler());
* HandlingResult result = RequestFixture.handle(chain, noop());
*
* assertEquals("foo", result.rendered(String.class));
*
* Thread safety
*
* Registry objects are assumed to be thread safe.
* No external synchronization is performed around registry access.
* As registry objects may be used across multiple requests, they should be thread safe.
*
* Registries that are created per request however do not need to be thread safe.
*
*
Ordering
*
* Registry objects are returned in the reverse order that they were added (i.e. Last-In-First-Out).
*
*
{@code
* import com.google.common.base.Joiner;
* import ratpack.registry.Registry;
*
* import static org.junit.Assert.assertEquals;
*
* public class Example {
* public static void main(String... args) throws Exception {
* Registry registry = Registry.of(r -> r
* .add("Ratpack")
* .add("foo")
* .add("bar")
* );
*
* assertEquals("bar", registry.get(String.class));
*
* String joined = Joiner.on(", ").join(registry.getAll(String.class));
* assertEquals("bar, foo, Ratpack", joined);
* }
* }
* }
*
* While this is strictly the case for the core registry implementations in Ratpack, adapted implementations (e.g. Guice, Spring etc.) may have more nuanced ordering semantics.
* To the greatest extent possible, registry implementations should strive to honor LIFO ordering.
*/
public interface Registry {
/**
* Provides an object of the specified type, or throws an exception if no object of that type is available.
*
* @param type The type of the object to provide
* @param The type of the object to provide
* @return An object of the specified type
* @throws NotInRegistryException If no object of this type can be returned
*/
default O get(Class type) throws NotInRegistryException {
return get(TypeCaching.typeToken(type));
}
/**
* Provides an object of the specified type, or throws an exception if no object of that type is available.
*
* @param type The type of the object to provide
* @param The type of the object to provide
* @return An object of the specified type
* @throws NotInRegistryException If no object of this type can be returned
*/
default O get(TypeToken type) throws NotInRegistryException {
Optional o = maybeGet(type);
if (o.isPresent()) {
return o.get();
} else {
throw new NotInRegistryException(type);
}
}
/**
* Does the same thing as {@link #get(Class)}, except returns null instead of throwing an exception.
*
* @param type The type of the object to provide
* @param The type of the object to provide
* @return An object of the specified type, or null if no object of this type is available.
*/
default Optional maybeGet(Class type) {
return maybeGet(TypeCaching.typeToken(type));
}
/**
* Does the same thing as {@link #get(Class)}, except returns null instead of throwing an exception.
*
* @param type The type of the object to provide
* @param The type of the object to provide
* @return An optional of an object of the specified type
*/
Optional maybeGet(TypeToken type);
/**
* Returns all of the objects whose declared type is assignment compatible with the given type.
*
* @param type the type of objects to search for
* @param the type of objects to search for
* @return All objects of the given type
*/
default Iterable extends O> getAll(Class type) {
return getAll(TypeCaching.typeToken(type));
}
/**
* Returns all of the objects whose declared type is assignment compatible with the given type.
*
* @param type the type of objects to search for
* @param the type of objects to search for
* @return All objects of the given type
*/
Iterable extends O> getAll(TypeToken type);
/**
* Find and transform an item.
*
* This method will apply the given function to items in the order returned by {@link #getAll(TypeToken)} until the function returns a non null value.
* The first non null value will be returned by this method immediately (as an optional).
* If the function returns a null value for every item, an empty optional is returned.
*
* @param type the type of object to search for
* @param function a function to apply to each item
* @param the type of the object to search for
* @param the type of transformed object
* @throws Exception any thrown by the function
* @return An optional of the object of the specified type that satisfied the specified predicate
*/
default Optional first(TypeToken type, Function super T, ? extends O> function) throws Exception {
Iterable extends T> all = getAll(type);
for (T t : all) {
O out = function.apply(t);
if (out != null) {
return Optional.of(out);
}
}
return Optional.empty();
}
/**
* A convenience method for {@link #first(TypeToken, Function)}.
*
* @param type the type of object to search for
* @param function a function to apply to each item
* @param the type of the object to search for
* @param the type of transformed object
* @throws Exception any thrown by the function
* @return An optional of the object of the specified type that satisfied the specified predicate
* @see #first(TypeToken, Function)
*/
default Optional first(Class type, Function super T, ? extends O> function) throws Exception {
return first(TypeCaching.typeToken(type), function);
}
/**
* Creates a new registry by joining {@code this} registry with the given registry
*
* The returned registry is effectively the union of the two registries, with the {@code child} registry taking precedence.
* This means that child entries are effectively “returned first”.
*
{@code
* import ratpack.registry.Registry;
*
* import java.util.List;
* import com.google.common.collect.Lists;
*
* import static org.junit.Assert.assertEquals;
*
* public class Example {
*
* public static interface Thing {
* String getName();
* }
*
* public static class ThingImpl implements Thing {
* private final String name;
*
* public ThingImpl(String name) {
* this.name = name;
* }
*
* public String getName() {
* return name;
* }
* }
*
* public static void main(String[] args) {
* Registry child = Registry.builder().add(Thing.class, new ThingImpl("child-1")).add(Thing.class, new ThingImpl("child-2")).build();
* Registry parent = Registry.builder().add(Thing.class, new ThingImpl("parent-1")).add(Thing.class, new ThingImpl("parent-2")).build();
* Registry joined = parent.join(child);
*
* assertEquals("child-2", joined.get(Thing.class).getName());
* List all = Lists.newArrayList(joined.getAll(Thing.class));
* assertEquals("child-2", all.get(0).getName());
* assertEquals("child-1", all.get(1).getName());
* assertEquals("parent-2", all.get(2).getName());
* assertEquals("parent-1", all.get(3).getName());
* }
* }
* }
*
* @param child the child registry
* @return a registry which is the combination of the {@code this} and the given child
*/
default Registry join(Registry child) {
if (this == EmptyRegistry.INSTANCE) {
return child;
} else if (child == EmptyRegistry.INSTANCE) {
return this;
} else {
return new HierarchicalRegistry(this, child);
}
}
/**
* Returns an empty registry.
*
* @return an empty registry
*/
static Registry empty() {
return EmptyRegistry.INSTANCE;
}
/**
* Creates a new {@link RegistryBuilder registry builder}.
*
* @return a new registry builder
* @see RegistryBuilder
*/
static RegistryBuilder builder() {
return new DefaultRegistryBuilder();
}
/**
* Builds a registry from the given action.
*
* @param action the action that defines the registry
* @return a registry created by the given action
* @throws Exception any thrown by the action
*/
static Registry of(Action super RegistrySpec> action) throws Exception {
RegistryBuilder builder = builder();
action.execute(builder);
return builder.build();
}
/**
* Creates a new registry instance that is backed by a RegistryBacking implementation.
* The registry instance caches lookups.
*
* @param registryBacking the implementation that returns instances for the registry
* @return a new registry
*/
static Registry backedBy(final RegistryBacking registryBacking) {
return new CachingBackedRegistry(registryBacking);
}
/**
* Deprecated, use {@link #singleLazy(Class, Supplier)}.
*
* @param publicType the public type of the entry
* @param supplier the supplier for the object
* @param the public type of the entry
* @return a new single entry registry
* @see RegistryBuilder#addLazy(Class, Supplier)
* @deprecated use {@link #singleLazy(Class, Supplier)}
*/
@Deprecated
static Registry single(Class publicType, Supplier extends T> supplier) {
return builder().addLazy(publicType, supplier).build();
}
/**
* Creates a single lazily created entry registry, using {@link RegistryBuilder#addLazy(Class, Supplier)}.
*
* @param publicType the public type of the entry
* @param supplier the supplier for the object
* @param the public type of the entry
* @return a new single entry registry
* @see RegistryBuilder#addLazy(Class, Supplier)
* @since 1.1
*/
static Registry singleLazy(Class publicType, Supplier extends T> supplier) {
return builder().addLazy(publicType, supplier).build();
}
/**
* Creates a single entry registry, using {@link RegistryBuilder#add(Object)}.
*
* @param object the entry object
* @return a new single entry registry
* @see RegistryBuilder#add(java.lang.Object)
*/
static Registry single(Object object) {
return builder().add(object).build();
}
/**
* Creates a single entry registry, using {@link RegistryBuilder#add(Class, Object)}.
*
* @param publicType the public type of the entry
* @param implementation the entry object
* @param the public type of the entry
* @return a new single entry registry
* @see RegistryBuilder#add(Class, Object)
*/
static Registry single(Class super T> publicType, T implementation) {
return builder().add(publicType, implementation).build();
}
/**
* Creates a new, empty, mutable registry.
*
* Mutable registries are not concurrent safe when being mutated.
* Concurrent reading is supported as long as no mutations are occurring.
*
* @return a new, empty, mutable registry.
* @since 1.4
*/
static MutableRegistry mutable() {
return new SimpleMutableRegistry();
}
}