ratpack.guice.ConfigurableModule Maven / Gradle / Ivy
Show all versions of ratpack-guice Show documentation
/*
* Copyright 2014 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.guice;
import com.google.common.reflect.Invokable;
import com.google.common.reflect.TypeToken;
import com.google.inject.AbstractModule;
import com.google.inject.Provides;
import ratpack.func.Action;
import ratpack.func.Factory;
import ratpack.server.ServerConfig;
import ratpack.util.Exceptions;
import javax.inject.Singleton;
import java.lang.reflect.Constructor;
/**
* Provides a standard approach for modules that require some parametrization / configuration.
*
* A configurable module provides a single, mutable, “config object” (type parameter {@code C}).
* The {@link ratpack.guice.BindingsSpec#module(Class, Action)} method can be used to add the module and configure it at the same time.
* It is conventional, but not required, for the config type to be a nested static class named {@code Config} of the module class.
*
{@code
* import com.google.inject.Provides;
* import ratpack.guice.ConfigurableModule;
* import ratpack.guice.Guice;
* import ratpack.test.embed.EmbeddedApp;
*
* import static org.junit.Assert.*;
*
* public class Example {
*
* public static class StringModule extends ConfigurableModule {
* public static class Config {
* private String value;
*
* public void value(String value) {
* this.value = value;
* }
* }
*
* protected void configure() {}
*
* {@literal @}Provides
* String provideString(Config config) {
* return config.value;
* }
* }
*
* public static void main(String... args) throws Exception {
* EmbeddedApp.of(s -> s
* .registry(Guice.registry(b -> b.module(StringModule.class, c -> c.value("foo"))))
* .handlers(chain -> chain.get(ctx -> ctx.render(ctx.get(String.class))))
* ).test(httpClient -> {
* assertEquals("foo", httpClient.getText());
* });
* }
* }
* }
*
* Alternatively, the config object can be provided as a separate binding.
*
{@code
* import com.google.inject.Provides;
* import ratpack.guice.ConfigurableModule;
* import ratpack.guice.Guice;
* import ratpack.test.embed.EmbeddedApp;
*
* import static org.junit.Assert.*;
*
* public class Example {
* public static class StringModule extends ConfigurableModule {
* public static class Config {
* private String value;
*
* public Config value(String value) {
* this.value = value;
* return this;
* }
* }
*
* protected void configure() {
* }
*
* {@literal @}Provides
* String provideString(Config config) {
* return config.value;
* }
* }
*
* public static void main(String... args) throws Exception {
* EmbeddedApp.of(s -> s
* .registry(Guice.registry(b -> b
* .module(StringModule.class)
* .bindInstance(new StringModule.Config().value("bar"))
* ))
* .handlers(chain -> chain
* .get(ctx -> ctx.render(ctx.get(String.class)))
* )
* ).test(httpClient -> {
* assertEquals("bar", httpClient.getText());
* });
* }
* }
* }
*
* @param the type of the config object
*/
public abstract class ConfigurableModule extends AbstractModule {
private Action super T> configurer = Action.noop();
private T config;
/**
* Registers the configuration action.
*
* This method is called by {@link ratpack.guice.BindingsSpec#module(Class, Action)}.
*
* @param configurer the configuration action.
*/
public void configure(Action super T> configurer) {
this.configurer = configurer;
}
/**
* Creates the configuration object.
*
* This implementation reflectively creates an instance of the type denoted by type param {@code C}.
* In order for this to succeed, the following needs to be met:
*
* - The type must be public.
* - Must have a public constructor that takes only a {@link ServerConfig}, or takes no args.
*
*
* If the config object cannot be created this way, override this method.
*
* @param serverConfig the application launch config
* @return a newly created config object
*/
protected T createConfig(ServerConfig serverConfig) {
TypeToken typeToken = new TypeToken(getClass()) {
};
if (typeToken.getType() instanceof Class) {
@SuppressWarnings("unchecked") Class clazz = (Class) typeToken.getRawType();
Factory factory;
try {
Constructor constructor = clazz.getConstructor(ServerConfig.class);
factory = () -> Invokable.from(constructor).invoke(null, serverConfig);
} catch (NoSuchMethodException ignore) {
try {
Constructor constructor = clazz.getConstructor();
factory = () -> Invokable.from(constructor).invoke(null);
} catch (NoSuchMethodException e) {
throw new IllegalStateException("No suitable constructor (no arg, or just ServerConfig) for module config type " + typeToken);
}
}
return Exceptions.uncheck(factory);
} else {
throw new IllegalStateException("Can't auto instantiate configuration type " + typeToken + " as it is not a simple class");
}
}
/**
* Hook for applying any default configuration to the configuration object created by {@link #createConfig(ServerConfig)}.
*
* This can be used if it's not possible to apply the configuration in the constructor.
*
* @param serverConfig the application server config
* @param config the config object
*/
protected void defaultConfig(ServerConfig serverConfig, T config) {
}
/**
* Binds the config object, after creating it via {@link #createConfig(ServerConfig)} and after giving it to {@link #defaultConfig(ServerConfig, Object)}.
*
* @param serverConfig the application server config
* @return the config object
*/
@Provides
@Singleton
T provideConfig(ServerConfig serverConfig) {
T config = this.config == null ? createConfig(serverConfig) : this.config;
defaultConfig(serverConfig, config);
try {
configurer.execute(config);
} catch (Exception e) {
throw Exceptions.uncheck(e);
}
return config;
}
/**
* Sets the config object for this module. This overrides the default config object that would be created otherwise.
*
* @param config the config object
* @see #createConfig(ratpack.server.ServerConfig)
*/
public void setConfig(T config) {
this.config = config;
}
}