package com.github.aesteve.vertx.nubes; import com.github.aesteve.vertx.nubes.context.RateLimit; import com.github.aesteve.vertx.nubes.handlers.AnnotationProcessor; import com.github.aesteve.vertx.nubes.handlers.AnnotationProcessorRegistry; import com.github.aesteve.vertx.nubes.handlers.Processor; import com.github.aesteve.vertx.nubes.marshallers.PayloadMarshaller; import com.github.aesteve.vertx.nubes.reflections.adapters.ParameterAdapterRegistry; import com.github.aesteve.vertx.nubes.reflections.factories.AnnotationProcessorFactory; import com.github.aesteve.vertx.nubes.reflections.injectors.annot.AnnotatedParamInjector; import com.github.aesteve.vertx.nubes.reflections.injectors.annot.AnnotatedParamInjectorRegistry; import com.github.aesteve.vertx.nubes.reflections.injectors.typed.ParamInjector; import com.github.aesteve.vertx.nubes.reflections.injectors.typed.TypedParamInjectorRegistry; import com.github.aesteve.vertx.nubes.services.ServiceRegistry; import io.vertx.core.Handler; import io.vertx.core.Vertx; import io.vertx.core.VertxException; import io.vertx.core.json.JsonArray; import io.vertx.core.json.JsonObject; import io.vertx.core.logging.Logger; import io.vertx.core.logging.LoggerFactory; import io.vertx.ext.auth.AuthProvider; import io.vertx.ext.auth.jdbc.JDBCAuth; import io.vertx.ext.auth.jwt.JWTAuth; import io.vertx.ext.auth.shiro.ShiroAuth; import io.vertx.ext.auth.shiro.ShiroAuthOptions; import io.vertx.ext.jdbc.JDBCClient; import io.vertx.ext.web.RoutingContext; import io.vertx.ext.web.handler.BodyHandler; import io.vertx.ext.web.handler.sockjs.SockJSHandlerOptions; import io.vertx.ext.web.templ.*; import java.lang.annotation.Annotation; import java.util.*; import java.util.concurrent.TimeUnit; public class Config { private static final Logger LOG = LoggerFactory.getLogger(Config.class); private final Map<Locale, ResourceBundle> bundlesByLocale; private final List<Handler<RoutingContext>> globalHandlers; private final Map<String, TemplateEngine> templateEngines; private final SockJSHandlerOptions sockJSOptions; private JsonObject json; private List<String> controllerPackages; private List<String> fixturePackages; private String verticlePackage; private String domainPackage; private RateLimit rateLimit; private String webroot; private String assetsPath; private String tplDir; private boolean displayErrors; private Vertx vertx; private AuthProvider authProvider; private String i18nDir; private AnnotationProcessorRegistry apRegistry; private Map<Class<? extends Annotation>, Set<Handler<RoutingContext>>> annotationHandlers; private Map<Class<?>, Processor> typeProcessors; private TypedParamInjectorRegistry typeInjectors; private AnnotatedParamInjectorRegistry annotInjectors; private ServiceRegistry serviceRegistry; private Map<Class<?>, Handler<RoutingContext>> paramHandlers; private Map<String, Handler<RoutingContext>> aopHandlerRegistry; private Map<String, PayloadMarshaller> marshallers; private Config() { bundlesByLocale = new HashMap<>(); globalHandlers = new ArrayList<>(); templateEngines = new HashMap<>(); sockJSOptions = new SockJSHandlerOptions(); marshallers = new HashMap<>(); annotationHandlers = new HashMap<>(); paramHandlers = new HashMap<>(); typeProcessors = new HashMap<>(); apRegistry = new AnnotationProcessorRegistry(); typeInjectors = new TypedParamInjectorRegistry(this); aopHandlerRegistry = new HashMap<>(); } /** * TODO : we should be consistent on single/multiple values * (controllers is an array, fixtures is a list, domain is a single value, verticle is a single value) : this is wrong * * @param json JsonObject describing the config * @return config a type safe config object */ public static Config fromJsonObject(JsonObject json, Vertx vertx) { Config instance = new Config(); instance.json = json; instance.vertx = vertx; instance.readPackages(); // Register services included in config instance.createServices(); // Register templateEngines for extensions added in config instance.createTemplateEngines(); instance.createRateLimit(); instance.createAuthHandlers(); instance.webroot = json.getString("webroot", "web/assets"); instance.assetsPath = json.getString("static-path", "/assets"); instance.tplDir = json.getString("views-dir", "web/views"); instance.displayErrors = json.getBoolean("display-errors", Boolean.FALSE); // TODO : read sockJSOptions from config instance.globalHandlers.add(BodyHandler.create()); return instance; } private void createAuthHandlers() { String auth = json.getString("auth-type"); JsonObject authProperties = json.getJsonObject("auth-properties"); // TODO : discuss it. I'm really not convinced about all the boilerplate needed in config (dbName only for JDBC, etc.) if (authProperties != null) { // For now, only JWT,Shiro and JDBC supported (same as for Vert.x web) switch (auth) { case "JWT":// For now only allow properties realm this.authProvider = JWTAuth.create(vertx, authProperties); break; case "Shiro": ShiroAuth.create(vertx, new ShiroAuthOptions(authProperties)); break; case "JDBC": String dbName = json.getString("db-name"); Objects.requireNonNull(dbName); JDBCClient client = JDBCClient.createShared(vertx, authProperties, dbName); this.authProvider = JDBCAuth.create(client); break; default: LOG.warn("Unknown type of auth : " + auth + " . Ignoring."); } } else if (auth != null) { LOG.warn("You have defined " + auth + " as auth type, but didn't provide any configuration, can't create authProvider"); } } private void createRateLimit() { JsonObject rateLimitJson = json.getJsonObject("throttling"); if (rateLimitJson != null) { int count = rateLimitJson.getInteger("count"); int value = rateLimitJson.getInteger("time-frame"); TimeUnit timeUnit = TimeUnit.valueOf(rateLimitJson.getString("time-unit")); this.rateLimit = new RateLimit(count, value, timeUnit); } } private void createTemplateEngines() { JsonArray templates = json.getJsonArray("templates", new JsonArray()); if (templates.contains("hbs")) { this.templateEngines.put("hbs", HandlebarsTemplateEngine.create()); } if (templates.contains("jade")) { this.templateEngines.put("jade", JadeTemplateEngine.create()); } if (templates.contains("templ")) { this.templateEngines.put("templ", MVELTemplateEngine.create()); } if (templates.contains("thymeleaf")) { this.templateEngines.put("html", ThymeleafTemplateEngine.create()); } } private void createServices() { JsonObject services = json.getJsonObject("services", new JsonObject()); this.serviceRegistry = new ServiceRegistry(vertx, this); services.forEach(entry -> { String name = entry.getKey(); String className = (String) entry.getValue(); try { Class<?> clazz = Class.forName(className); this.serviceRegistry.registerService(name, clazz.newInstance()); } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) { throw new VertxException(e); } }); } @SuppressWarnings("unchecked") private void readPackages() { this.i18nDir = json.getString("i18nDir", "web/i18n/"); if (!this.i18nDir.endsWith("/")) { this.i18nDir = this.i18nDir + "/"; } String srcPackage = json.getString("src-package"); JsonArray controllers = json.getJsonArray("controller-packages"); if (controllers == null) { controllers = new JsonArray(); if (srcPackage != null) { controllers.add(srcPackage + ".controllers"); } } this.controllerPackages = controllers.getList(); this.verticlePackage = json.getString("verticle-package"); if (this.verticlePackage == null && srcPackage != null) { this.verticlePackage = srcPackage + ".verticles"; } this.domainPackage = json.getString("domain-package"); if (this.domainPackage == null && srcPackage != null) { this.domainPackage = srcPackage + ".domains"; } JsonArray fixtures = json.getJsonArray("fixture-packages"); if (fixtures == null) { fixtures = new JsonArray(); if (srcPackage != null) { fixtures.add(srcPackage + ".fixtures"); } } this.fixturePackages = fixtures.getList(); } public ResourceBundle getResourceBundle(Locale loc) { return bundlesByLocale.get(loc); } public JsonObject json() { return json; } public List<String> getFixturePackages() { return fixturePackages; } public TypedParamInjectorRegistry getTypeInjectors() { return typeInjectors; } public AnnotatedParamInjectorRegistry getAnnotatedInjectors() { return annotInjectors; } public boolean isDisplayErrors() { return displayErrors; } public Map<String, TemplateEngine> getTemplateEngines() { return templateEngines; } public String getTplDir() { return tplDir; } public RateLimit getRateLimit() { return rateLimit; } void createAnnotInjectors(ParameterAdapterRegistry registry) { annotInjectors = new AnnotatedParamInjectorRegistry(marshallers, registry); } public String getDomainPackage() { return domainPackage; } public ServiceRegistry getServiceRegistry() { return serviceRegistry; } void registerTemplateEngine(String extension, TemplateEngine engine) { templateEngines.put(extension, engine); } void registerInterceptor(String name, Handler<RoutingContext> handler) { aopHandlerRegistry.put(name, handler); } void registerService(String name, Object service) { serviceRegistry.registerService(name, service); } Object getService(String name) { return serviceRegistry.get(name); } void registerParamHandler(Class<?> parameterClass, Handler<RoutingContext> handler) { paramHandlers.put(parameterClass, handler); } public Set<Handler<RoutingContext>> getAnnotationHandler(Class<? extends Annotation> annotation) { return annotationHandlers.get(annotation); } void registerAnnotationHandler(Class<? extends Annotation> annotation, Set<Handler<RoutingContext>> handlers) { annotationHandlers.put(annotation, handlers); } void registerTypeProcessor(Class<?> type, Processor processor) { typeProcessors.put(type, processor); } <T extends Annotation> void registerAnnotationProcessor(Class<T> annotation, AnnotationProcessorFactory<T> processor) { apRegistry.registerProcessor(annotation, processor); } <T extends Annotation> void registerAnnotationProcessor(Class<T> annotation, AnnotationProcessor<T> processor) { apRegistry.registerProcessor(annotation, processor); } <T> void registerInjector(Class<? extends T> clazz, ParamInjector<T> injector) { typeInjectors.registerInjector(clazz, injector); } <T extends Annotation> void registerInjector(Class<? extends T> clazz, AnnotatedParamInjector<T> injector) { annotInjectors.registerInjector(clazz, injector); } void addHandler(Handler<RoutingContext> handler) { globalHandlers.add(handler); } String getWebroot() { return webroot; } String getAssetsPath() { return assetsPath; } public AuthProvider getAuthProvider() { return authProvider; } void setAuthProvider(AuthProvider authProvider) { this.authProvider = authProvider; } String getI18nDir() { return i18nDir; } void createBundle(Locale loc, ResourceBundle bundle) { bundlesByLocale.put(loc, bundle); } public String getVerticlePackage() { return verticlePackage; } public void forEachControllerPackage(Handler<? super String> consumer) { controllerPackages.forEach(consumer::handle); } public Vertx getVertx() { return vertx; } public SockJSHandlerOptions getSockJSOptions() { return sockJSOptions; } public Processor getTypeProcessor(Class<?> parameterClass) { return typeProcessors.get(parameterClass); } public Handler<RoutingContext> getParamHandler(Class<?> parameterClass) { return paramHandlers.get(parameterClass); } public<T extends Annotation> AnnotationProcessor<T> getAnnotationProcessor(T methodAnnotation) { return apRegistry.getProcessor(methodAnnotation); } public Handler<RoutingContext> getAopHandler(String name) { return aopHandlerRegistry.get(name); } public void forEachGlobalHandler(Handler<Handler<RoutingContext>> handler) { globalHandlers.forEach(handler::handle); } public Map<String, PayloadMarshaller> getMarshallers() { return marshallers; } public void setMarshallers(Map<String, PayloadMarshaller> marshallers) { this.marshallers = marshallers; } public List<String> getControllerPackages() { return controllerPackages; } }