diff --git a/pom.xml b/pom.xml index 825a5b2c..12afd42c 100644 --- a/pom.xml +++ b/pom.xml @@ -12,7 +12,7 @@ fr.wseduc web-utils - 3.2-SNAPSHOT + 3.2-develop-b2school-SNAPSHOT diff --git a/src/main/java/fr/wseduc/webutils/http/ProcessTemplateContext.java b/src/main/java/fr/wseduc/webutils/http/ProcessTemplateContext.java new file mode 100644 index 00000000..2ce1b3e1 --- /dev/null +++ b/src/main/java/fr/wseduc/webutils/http/ProcessTemplateContext.java @@ -0,0 +1,117 @@ +package fr.wseduc.webutils.http; + +import com.samskivert.mustache.Mustache; +import io.vertx.core.http.HttpServerRequest; +import io.vertx.core.json.JsonObject; + +import java.io.Reader; +import java.util.HashMap; +import java.util.Map; + +/** + * Container for all parameters needed for the template processing + */ +public final class ProcessTemplateContext { + + private final HttpServerRequest request; + private final JsonObject params; + private final String templateString; + private final Reader reader; + private final boolean escapeHtml; + private final String defaultValue; + private final Map lambdas; + + private ProcessTemplateContext(HttpServerRequest request, JsonObject params, String templateString, Reader reader, + boolean escapeHtml, String defaultValue, Map lambdas) { + this.request = request; + this.params = params; + this.templateString = templateString; + this.reader = reader; + this.escapeHtml = escapeHtml; + this.defaultValue = defaultValue; + this.lambdas = new HashMap<>(lambdas); + } + + public static class Builder { + + private HttpServerRequest request; + private JsonObject params; + private String templateString; + private Reader reader; + private boolean escapeHtml; + private String defaultValue; + private final Map lambdas = new HashMap<>(); + + public Builder request(HttpServerRequest request) { + this.request = request; + return this; + } + + public Builder escapeHtml(boolean escapeHtml) { + this.escapeHtml = escapeHtml; + return this; + } + + public Builder setDefaultValue(String defaultValue) { + this.defaultValue = defaultValue; + return this; + } + + public Builder params(JsonObject params) { + this.params = params; + return this; + } + + public Builder templateString(String templateString) { + this.templateString = templateString; + return this; + } + + public Builder reader(Reader reader) { + this.reader = reader; + return this; + } + + public Builder lambdas(Map lambdas) { + this.lambdas.putAll(lambdas); + return this; + } + + public HttpServerRequest request() { + return request; + } + + public ProcessTemplateContext build() { + return new ProcessTemplateContext(request, params, templateString, reader, escapeHtml, defaultValue, lambdas); + } + + } + + public HttpServerRequest request() { + return request; + } + + public JsonObject params() { + return params; + } + + public String templateString() { + return templateString; + } + + public Reader reader() { + return reader; + } + + public Map lambdas() { + return lambdas; + } + + public boolean escapeHtml() { + return escapeHtml; + } + + public String defaultValue() { + return defaultValue; + } +} diff --git a/src/main/java/fr/wseduc/webutils/http/Renders.java b/src/main/java/fr/wseduc/webutils/http/Renders.java index 520f973d..1ef598f9 100644 --- a/src/main/java/fr/wseduc/webutils/http/Renders.java +++ b/src/main/java/fr/wseduc/webutils/http/Renders.java @@ -20,8 +20,11 @@ import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import com.samskivert.mustache.Mustache; import fr.wseduc.webutils.template.TemplateProcessor; import fr.wseduc.webutils.template.FileTemplateProcessor; import fr.wseduc.webutils.template.lambdas.FormatBirthDateLambda; @@ -30,6 +33,7 @@ import fr.wseduc.webutils.template.lambdas.LocaleDateLambda; import fr.wseduc.webutils.template.lambdas.ModsLambda; import fr.wseduc.webutils.template.lambdas.StaticLambda; +import io.micrometer.common.util.StringUtils; import io.netty.handler.codec.http.HttpResponseStatus; import io.vertx.core.Handler; import io.vertx.core.Vertx; @@ -47,6 +51,12 @@ import static fr.wseduc.webutils.Utils.isNotEmpty; +/** + * Method with @deprecated, relative to template processing are NOT thread safe and should not be used. Alternative method not deprecated was developed. + * Lambda for template processing dependent of the user context (request, host, etc..) must not be shared and are local variable. + * Static lambda that doesn't change with the context can be used + * Parameter for the template are either NOT thread safe. EscapeHTML, defaultValue, should be passed as local variable + */ public class Renders { protected static final Logger log = LoggerFactory.getLogger(Renders.class); @@ -57,6 +67,7 @@ public class Renders { protected String staticHost; protected FileTemplateProcessor templateProcessor; protected static final List allowedHosts = new ArrayList<>(); + private final Map staticLambdas = new HashMap<>(); public Renders(Vertx vertx, JsonObject config) { this.config = config; @@ -66,8 +77,10 @@ public Renders(Vertx vertx, JsonObject config) { this.vertx = vertx; if (vertx != null) { this.templateProcessor = new FileTemplateProcessor(vertx, "view/", false); - this.templateProcessor.setLambda("formatBirthDate", new FormatBirthDateLambda()); - this.templateProcessor.setLambda("modVersion", new ModsLambda(vertx)); + staticLambdas.put("formatBirthDate", new FormatBirthDateLambda()); + staticLambdas.put("modVersion", new ModsLambda(vertx)); + + staticLambdas.forEach(templateProcessor::setLambda); } } @@ -84,13 +97,15 @@ protected void init(Vertx vertx, JsonObject config) if (templateProcessor == null && vertx != null) { this.templateProcessor = new FileTemplateProcessor(vertx, "view/", false); - this.templateProcessor.setLambda("formatBirthDate", new FormatBirthDateLambda()); - this.templateProcessor.setLambda("modVersion", new ModsLambda(vertx)); + staticLambdas.put("formatBirthDate", new FormatBirthDateLambda()); + staticLambdas.put("modVersion", new ModsLambda(vertx)); + + staticLambdas.forEach(templateProcessor::setLambda); } } - protected void setLambdaTemplateRequest(final HttpServerRequest request) - { + @Deprecated + protected void setLambdaTemplateRequest(final HttpServerRequest request) { String host = Renders.getHost(request); if(host == null) // This can happen for forged requests host = ""; @@ -104,6 +119,88 @@ protected void setLambdaTemplateRequest(final HttpServerRequest request) this.templateProcessor.setLambda("datetime", new LocaleDateLambda(I18n.acceptLanguage(request))); } + /** + * Create lambda from request for mustache. These lambdas should be used with TemplateProcessorContext and method + * that use it in order to avoid race condition on template processing + * @param request HttpRequest of the user to set various dependent request lambda (host, domain, language, etc..) + * @return Map with request dependent lambda + */ + protected Map getLambdasFromRequest(final HttpServerRequest request) { + Map lambdas = new HashMap<>(); + + String host = Renders.getHost(request); + if(host == null) // This can happen for forged requests + host = ""; + String sttcHost = this.staticHost != null ? this.staticHost : host; + + + lambdas.put("i18n", + new I18nLambda(I18n.acceptLanguage(request), host, I18n.getTheme(request))); + lambdas.put("static", + new StaticLambda(config.getBoolean("ssl", sttcHost.startsWith("https")), sttcHost, this.pathPrefix + "/public")); + lambdas.put("infra", + new InfraLambda(config.getBoolean("ssl", sttcHost.startsWith("https")), sttcHost, "/infra/public", request.headers().get("X-Forwarded-For") == null)); + lambdas.put("datetime", new LocaleDateLambda(I18n.acceptLanguage(request))); + return lambdas; + } + + public void renderTemplateView(HttpServerRequest request) { + renderTemplateView(request, new JsonObject(), new HashMap<>()); + } + + public void renderTemplateView(HttpServerRequest request, Map lambdas) { + renderTemplateView(request, new JsonObject(), lambdas); + } + + public void renderTemplateView(HttpServerRequest request, JsonObject params) { + renderTemplateView(request, params, new HashMap<>()); + } + + public void renderTemplateView(HttpServerRequest request, JsonObject params, Map lambdas) { + renderTemplateView(request, params, null, null, 200, lambdas); + } + + public void renderTemplateView(HttpServerRequest request, JsonObject params, String resourceName, + Reader r, Map lambdas) { + renderTemplateView(request, params, resourceName, r, 200, lambdas); + } + + public void renderTemplateView(final HttpServerRequest request, JsonObject params, + String resourceName, Reader r, final int status, Map lambdas) { + + Map mergeLambdas = getLambdasFromRequest(request); + mergeLambdas.putAll(lambdas); + mergeLambdas.putAll(staticLambdas); + + ProcessTemplateContext.Builder context = new ProcessTemplateContext.Builder() + .escapeHtml(true) + .reader(r) + .lambdas( mergeLambdas) + .request(request); + + context.templateString(genTemplateName(resourceName, request)); + + templateProcessor.processTemplate(context.build(), writer -> { + if (writer != null) { + request.response().putHeader("content-type", "text/html; charset=utf-8"); + request.response().setStatusCode(status); + if (hookRenderProcess != null) { + executeHandlersHookRender(request, new Handler() { + @Override + public void handle(Void v) { + request.response().end(writer.toString()); + } + }); + } else { + request.response().end(writer.toString()); + } + } else { + renderError(request); + } + }); + } + + @Deprecated public void renderView(HttpServerRequest request) { renderView(request, new JsonObject()); } @@ -112,14 +209,17 @@ public void renderView(HttpServerRequest request) { * Render a Mustache template : see http://mustache.github.com/mustache.5.html * TODO : isolate scope management */ + @Deprecated public void renderView(HttpServerRequest request, JsonObject params) { renderView(request, params, null, null, 200); } + @Deprecated public void renderView(HttpServerRequest request, JsonObject params, String resourceName, Reader r) { renderView(request, params, resourceName, r, 200); } + @Deprecated public void renderView(final HttpServerRequest request, JsonObject params, String resourceName, Reader r, final int status) { processTemplate(request, params, resourceName, r, new Handler() { @@ -161,12 +261,14 @@ public void handle(Void v) { handlers[0].handle(null); } + @Deprecated public void processTemplate(HttpServerRequest request, String template, JsonObject params, final Handler handler) { this.setLambdaTemplateRequest(request); this.templateProcessor.escapeHTML(true).processTemplate(this.genTemplateName(template, request), params, handler); } + @Deprecated public void processTemplate(final HttpServerRequest request, JsonObject p, String resourceName, Reader r, final Handler handler) { this.setLambdaTemplateRequest(request); @@ -174,24 +276,33 @@ public void processTemplate(final HttpServerRequest request, JsonObject p, Strin this.templateProcessor.processTemplate(this.genTemplateName(resourceName, request), p, r, handler); } + @Deprecated public void processTemplate(final HttpServerRequest request, JsonObject p, String resourceName, boolean escapeHTML, final Handler handler) { this.setLambdaTemplateRequest(request); this.templateProcessor.escapeHTML(escapeHTML).processTemplate(this.genTemplateName(resourceName, request), p, handler); } - private String genTemplateName(final String resourceName, final HttpServerRequest request) - { - if (resourceName != null && !resourceName.trim().isEmpty()) + public void processTemplateWithLambdas(ProcessTemplateContext.Builder context, Handler handler) { + context.lambdas(getLambdasFromRequest(context.request())); + this.templateProcessor.processTemplate(context.build(), handler); + } + + public void processTemplateWithLambdas(ProcessTemplateContext.Builder context, String resourceName, Handler handler) { + context.lambdas(getLambdasFromRequest(context.request())); + context.templateString(genTemplateName(resourceName, context.request())); + this.templateProcessor.processTemplate(context.build(), handler); + } + + private String genTemplateName(final String resourceName, final HttpServerRequest request) { + if (!StringUtils.isEmpty(resourceName)) { return resourceName; - else - { - String template = request.path().substring(pathPrefix.length()); - if (template.trim().isEmpty()) { - template = pathPrefix.substring(1); - } - return template + ".html"; } + String template = request.path().substring(pathPrefix.length()); + if (template.trim().isEmpty()) { + template = pathPrefix.substring(1); + } + return template + ".html"; } public static void ok(HttpServerRequest request) { diff --git a/src/main/java/fr/wseduc/webutils/security/SecuredAction.java b/src/main/java/fr/wseduc/webutils/security/SecuredAction.java index a7ecedb6..ca13383c 100644 --- a/src/main/java/fr/wseduc/webutils/security/SecuredAction.java +++ b/src/main/java/fr/wseduc/webutils/security/SecuredAction.java @@ -30,6 +30,14 @@ public SecuredAction(String qualifiedName, String displayName, String type, Stri this.right = right; } + public SecuredAction(String qualifiedName, String displayName, String type) { + this.name = qualifiedName; + this.displayName = displayName; + this.type = type; + this.right = qualifiedName; + } + + public String getName() { return name; } diff --git a/src/main/java/fr/wseduc/webutils/template/FileTemplateProcessor.java b/src/main/java/fr/wseduc/webutils/template/FileTemplateProcessor.java index 9bd2d871..b953a67c 100644 --- a/src/main/java/fr/wseduc/webutils/template/FileTemplateProcessor.java +++ b/src/main/java/fr/wseduc/webutils/template/FileTemplateProcessor.java @@ -29,6 +29,7 @@ import com.samskivert.mustache.Mustache; import com.samskivert.mustache.Template; import fr.wseduc.webutils.collections.JsonUtils; +import fr.wseduc.webutils.http.ProcessTemplateContext; import io.vertx.core.AsyncResult; import io.vertx.core.Handler; import io.vertx.core.Vertx; @@ -74,6 +75,7 @@ public void clearCache() // ============================================= TEMPLATE PROCESSING ============================================ + @Deprecated public void processTemplate(String resourceName, JsonObject params, Reader r, final Handler handler) { if(r != null) @@ -83,38 +85,50 @@ public void processTemplate(String resourceName, JsonObject params, Reader r, fi } @Override + @Deprecated protected void getTemplate(String resourceName, final Handler