com.github.rmannibucau.playx.cdi.CdiLoader Maven / Gradle / Ivy
package com.github.rmannibucau.playx.cdi;
import static java.util.Collections.emptyMap;
import static java.util.Collections.list;
import static java.util.Objects.requireNonNull;
import static java.util.Optional.of;
import static java.util.Optional.ofNullable;
import static java.util.stream.Collectors.toList;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiFunction;
import java.util.function.Supplier;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.stream.Stream;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.context.Dependent;
import javax.enterprise.event.Observes;
import javax.enterprise.inject.Any;
import javax.enterprise.inject.Default;
import javax.enterprise.inject.se.SeContainer;
import javax.enterprise.inject.se.SeContainerInitializer;
import javax.enterprise.inject.spi.AfterBeanDiscovery;
import javax.enterprise.inject.spi.AnnotatedType;
import javax.enterprise.inject.spi.BeanManager;
import javax.enterprise.inject.spi.BeforeBeanDiscovery;
import javax.enterprise.inject.spi.Extension;
import javax.enterprise.inject.spi.InjectionTarget;
import javax.enterprise.inject.spi.ProcessBeanAttributes;
import javax.enterprise.inject.spi.configurator.BeanConfigurator;
import javax.inject.Singleton;
import org.slf4j.ILoggerFactory;
import org.slf4j.LoggerFactory;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigObject;
import com.typesafe.config.ConfigValueType;
import akka.actor.ActorSystem;
import akka.stream.Materializer;
import controllers.Assets;
import controllers.AssetsConfiguration;
import controllers.DefaultAssetsMetadata;
import play.Application;
import play.ApplicationLoader;
import play.BuiltInComponentsFromContext;
import play.LoggerConfigurator;
import play.api.Configuration;
import play.api.Environment;
import play.api.Mode;
import play.api.OptionalSourceMapper;
import play.api.http.ActionCompositionConfiguration;
import play.api.http.CookiesConfiguration;
import play.api.http.EnabledFilters;
import play.api.http.FileMimeTypesConfiguration;
import play.api.http.FlashConfiguration;
import play.api.http.HttpConfiguration;
import play.api.http.HttpErrorHandler;
import play.api.http.HttpRequestHandler;
import play.api.http.ParserConfiguration;
import play.api.http.SecretConfiguration;
import play.api.http.SessionConfiguration;
import play.api.i18n.DefaultLangsProvider;
import play.api.i18n.DefaultMessagesApiProvider;
import play.api.inject.BindingKey;
import play.api.inject.QualifierAnnotation;
import play.api.inject.QualifierClass;
import play.api.inject.QualifierInstance;
import play.api.inject.RoutesProvider;
import play.api.libs.Files;
import play.api.libs.concurrent.ExecutionContextProvider;
import play.api.mvc.BodyParsers;
import play.api.mvc.CookieHeaderEncoding;
import play.api.mvc.DefaultActionBuilderImpl;
import play.api.mvc.DefaultControllerComponents;
import play.api.mvc.DefaultCookieHeaderEncoding;
import play.api.mvc.DefaultMessagesActionBuilderImpl;
import play.api.mvc.DefaultMessagesControllerComponents;
import play.api.mvc.DefaultPlayBodyParsers;
import play.api.mvc.MessagesControllerComponents;
import play.api.mvc.request.DefaultRequestFactory;
import play.api.mvc.request.RequestFactory;
import play.core.WebCommands;
import play.core.j.DefaultJavaContextComponents;
import play.core.j.JavaContextComponents;
import play.core.j.JavaHttpErrorHandlerAdapter;
import play.core.j.JavaRouterAdapter;
import play.i18n.Langs;
import play.i18n.MessagesApi;
import play.inject.ApplicationLifecycle;
import play.inject.Injector;
import play.libs.concurrent.DefaultFutures;
import play.libs.concurrent.HttpExecutionContext;
import play.libs.crypto.CSRFTokenSigner;
import play.libs.crypto.CookieSigner;
import play.mvc.EssentialFilter;
import play.mvc.FileMimeTypes;
import play.routing.Router;
import scala.Option;
import scala.collection.JavaConverters;
import scala.compat.java8.OptionConverters;
import scala.concurrent.ExecutionContext;
import scala.concurrent.ExecutionContextExecutor;
import scala.concurrent.Future;
import scala.reflect.ClassTag;
public class CdiLoader implements ApplicationLoader {
final BiFunction> classLoader = (context, className) -> {
try {
return context.environment().classLoader().loadClass(className.trim());
} catch (final ClassNotFoundException e) {
throw new IllegalArgumentException(e);
}
};
@Override
public Application load(final Context context) {
final Config config = context.initialConfig();
final BiFunction> packageLoader = (recursive, pckName) -> {
final ClassLoader loader = context.environment().classLoader();
final String trimmed = pckName.trim();
try {
return Stream.of(loader.loadClass(trimmed + ".package-info.class").getPackage());
} catch (final ClassNotFoundException e) {
return ofNullable(Package.getPackage(trimmed))
.map(Stream::of)
.orElseGet(() -> of(trimmed)
.map(name -> { // try to find a class - more relevant to load
return findPackageFromClassLoader(loader, name, recursive).stream()
.map(it -> classLoader.apply(context, it))
.map(Class::getPackage);
})
.orElseThrow(() -> new IllegalArgumentException("can't find package: " + trimmed, e)));
}
};
final SeContainerInitializer initializer = SeContainerInitializer.newInstance()
.setClassLoader(context.environment().classLoader());
if (safeConfigAccess(config, "playx.cdi.container.disableDiscovery", Config::getBoolean).orElse(false)) {
initializer.disableDiscovery();
}
safeConfigAccess(config, "playx.cdi.container.beanClasses", Config::getStringList)
.map(list -> list.stream().map(c -> classLoader.apply(context, c)).toArray(Class>[]::new))
.ifPresent(initializer::addBeanClasses);
safeConfigAccess(config, "playx.cdi.container.packages", Config::getList).ifPresent(pcks -> pcks.forEach(value -> {
if (value.valueType() == ConfigValueType.OBJECT) {
final ConfigObject object = ConfigObject.class.cast(value);
final boolean recursive = safeConfigAccess(object.toConfig(), "recursive", Config::getBoolean).orElse(false);
final String pck = safeConfigAccess(object.toConfig(), "package", Config::getString)
.orElseThrow(() -> new IllegalArgumentException("Missing package value in " + value))
.trim();
packageLoader.apply(recursive, pck).forEach(pckIt -> initializer.addPackages(recursive, pckIt));
} else if (value.valueType() == ConfigValueType.STRING) {
packageLoader.apply(false, value.unwrapped().toString().trim()).forEach(initializer::addPackages);
} else {
throw new IllegalArgumentException("Unsupported configuration: " + value);
}
}));
safeConfigAccess(config, "playx.cdi.container.properties", Config::getObjectList).ifPresent(properties -> properties
.forEach(value -> initializer.addProperty(value.get("key").render(), value.get("value").unwrapped())));
safeConfigAccess(config, "playx.cdi.container.extensions", Config::getStringList).ifPresent(extensions -> {
final Class extends Extension>[] extInstances = extensions.stream().map(c -> classLoader.apply(context, c))
.toArray(Class[]::new);
initializer.addExtensions(extInstances);
});
safeConfigAccess(config, "playx.cdi.container.decorators", Config::getStringList).ifPresent(extensions -> initializer
.enableDecorators(extensions.stream().map(c -> classLoader.apply(context, c)).toArray(Class[]::new)));
safeConfigAccess(config, "playx.cdi.container.interceptors", Config::getStringList).ifPresent(extensions -> initializer
.enableInterceptors(extensions.stream().map(c -> classLoader.apply(context, c)).toArray(Class[]::new)));
safeConfigAccess(config, "playx.cdi.container.alternatives", Config::getStringList).ifPresent(extensions -> initializer
.selectAlternatives(extensions.stream().map(c -> classLoader.apply(context, c)).toArray(Class[]::new)));
safeConfigAccess(config, "playx.cdi.container.alternativeStereotypes", Config::getStringList).ifPresent(extensions -> {
final Class extends Annotation>[] alternativeStereotypeClasses = extensions.stream()
.map(c -> classLoader.apply(context, c)).toArray(Class[]::new);
initializer.selectAlternativeStereotypes(alternativeStereotypeClasses);
});
final CdiInjector injector = new CdiInjector();
final Application application = new CdiApplication(injector, context);
addProvidedBeans(context, initializer, injector, application);
final SeContainer container = initializer.initialize();
injector.container = container;
context.applicationLifecycle().addStopHook(() -> CompletableFuture.runAsync(container::close, Runnable::run));
return application;
}
private Collection findPackageFromClassLoader(final ClassLoader loader, final String name, final boolean recursive) {
final String pck = name.replace(".", "/");
try {
final Enumeration urls = loader.getResources(pck);
final Collection names = new ArrayList<>();
while (urls.hasMoreElements()) {
final File next = toFile(urls.nextElement());
if (next != null && next.exists() && !next.isDirectory()) {
try (final JarFile file = new JarFile(next)) {
final Collection entries = list(file.entries());
final Set packages = new HashSet<>();
final List classes = entries.stream()
.filter(it -> {
final String eName = it.getName();
return eName.startsWith(pck + '/') && eName.endsWith(".class") &&
(recursive || eName.lastIndexOf('/', pck.length()) == pck.length());
})
.map(JarEntry::getName)
.map(clazz -> clazz.replace('/', '.').substring(0, clazz.length() - ".class".length()))
.filter(s -> {
final String sPck = s.substring(0, s.lastIndexOf('.'));
return packages.add(sPck);
})
.sorted()
.collect(toList());
if (name.equals(classes.get(0).substring(0, classes.get(0).lastIndexOf('.')))) {
names.add(classes.iterator().next());
} else if (recursive) {
// here we don't have any class in the root package so need to list them all
final PackageCleaner packageCleaner = new PackageCleaner();
final Collection pcks = packageCleaner.removeOverlaps(packages);
names.addAll(classes
.stream().filter(
className -> pcks.stream()
.anyMatch(pack -> className.startsWith(pack)
&& !className.substring(pack.length() + 1).contains(".")))
.collect(toList()));
}
}
}
}
if (!names.isEmpty()) {
return names;
}
} catch (final IOException e1) {
// no-op
}
return null;
}
private void addProvidedBeans(final Context context, final SeContainerInitializer initializer, final Injector injector,
final Application application) {
final play.Environment environment = context.environment();
final Config config = application.config();
final Configuration configuration = new Configuration(config);
initializer.addExtensions(new Extension() { // todo: make it more configured and modular reusing
void addSingletons(@Observes final BeforeBeanDiscovery event, final BeanManager beanManager) {
// integration with servlet module
final Collection> extensions = new ArrayList<>();
try {
extensions.add(environment.classLoader()
.loadClass("com.github.rmannibucau.playx.servlet.servlet.api.ServletFilter"));
} catch (final Exception e) {
// no-op
}
if (safeConfigAccess(context.initialConfig(), "playx.cdi.beans.defaults", Config::getBoolean).orElse(true)) {
Stream.concat(Stream.of(Assets.class, Files.DefaultTemporaryFileCreator.class,
Files.DefaultTemporaryFileReaper.class, DefaultPlayBodyParsers.class, BodyParsers.Default.class,
DefaultActionBuilderImpl.class, DefaultControllerComponents.class, DefaultMessagesActionBuilderImpl.class,
DefaultMessagesControllerComponents.class, DefaultFutures.class,
play.api.libs.concurrent.DefaultFutures.class, HttpExecutionContext.class, DefaultAssetsMetadata.class),
extensions.stream()).forEach(it -> event.addAnnotatedType(beanManager.createAnnotatedType(it)));
}
}
void restrictTypesForDefaultMessagesControllerComponents(@Observes final ProcessBeanAttributes pba) {
pba.configureBeanAttributes().types(MessagesControllerComponents.class, Object.class);
}
void addProvidedBeans(@Observes final AfterBeanDiscovery event, final BeanManager beanManager) {
if (safeConfigAccess(context.initialConfig(), "playx.cdi.beans.defaults", Config::getBoolean).orElse(true)) {
addPlayBeans(event);
}
addCustomBeans(event, beanManager);
}
private void addCustomBeans(final AfterBeanDiscovery event, final BeanManager beanManager) {
safeConfigAccess(context.initialConfig(), "playx.cdi.beans.customs", Config::getObjectList)
.ifPresent(beans -> beans.forEach(bean -> {
final String className = requireNonNull(bean.get("className"), "className can't be null: " + bean)
.unwrapped().toString();
final Class beanClass = classLoader.apply(context, className);
final String scope = ofNullable(bean.get("scope")).map(s -> s.unwrapped().toString())
.orElse("javax.enterprise.context.Dependent");
final Class extends Annotation> scopeAnnotation;
switch (scope) {
case "javax.enterprise.context.ApplicationScoped":
scopeAnnotation = ApplicationScoped.class;
break;
case "javax.inject.Singleton":
scopeAnnotation = Singleton.class;
break;
case "javax.enterprise.context.Dependent":
scopeAnnotation = Dependent.class;
break;
default:
scopeAnnotation = (Class extends Annotation>) classLoader.apply(context, scope);
}
final BeanConfigurator
© 2015 - 2025 Weber Informatics LLC | Privacy Policy