From 25ca31f089909cb4a4ab26ffeb7c369b768783e3 Mon Sep 17 00:00:00 2001 From: Georg Herdt Date: Fri, 8 May 2020 08:42:11 +0200 Subject: [PATCH] switch to filter bug5153 --- java/com.sap.sailing.gwt.ui/WEB-INF/web.xml | 12 +- .../test/support/WhitelabelSwitchServlet.java | 2 +- java/com.sap.sailing.www/WEB-INF/web.xml | 17 +- .../debranding/ClientConfigurationFilter.java | 245 +++++++++++++++--- .../sse/gwt/shared/ClientConfiguration.java | 4 +- java/com.sap.sse.security.ui/WEB-INF/web.xml | 16 +- 6 files changed, 222 insertions(+), 74 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/WEB-INF/web.xml b/java/com.sap.sailing.gwt.ui/WEB-INF/web.xml index 31c2e291d95..995b1ec6ddb 100755 --- a/java/com.sap.sailing.gwt.ui/WEB-INF/web.xml +++ b/java/com.sap.sailing.gwt.ui/WEB-INF/web.xml @@ -62,6 +62,7 @@ + ClientConfigurationFilter ClientConfigurationFilter com.sap.sse.debranding.ClientConfigurationFilter @@ -70,17 +71,6 @@ *.html - Status Status diff --git a/java/com.sap.sailing.server.gateway.test.support/src/com/sap/sailing/server/gateway/test/support/WhitelabelSwitchServlet.java b/java/com.sap.sailing.server.gateway.test.support/src/com/sap/sailing/server/gateway/test/support/WhitelabelSwitchServlet.java index 95ebeca49ae..1dd2147fa1c 100644 --- a/java/com.sap.sailing.server.gateway.test.support/src/com/sap/sailing/server/gateway/test/support/WhitelabelSwitchServlet.java +++ b/java/com.sap.sailing.server.gateway.test.support/src/com/sap/sailing/server/gateway/test/support/WhitelabelSwitchServlet.java @@ -10,7 +10,7 @@ import javax.servlet.http.HttpServletResponse; /** * Use this servlet to turn whitelabelling on and off during testing. - * @see com.sap.sse.debranding.ClientConfigurationServlet + * @see com.sap.sse.debranding.ClientConfigurationFilter * @author Georg Herdt * */ diff --git a/java/com.sap.sailing.www/WEB-INF/web.xml b/java/com.sap.sailing.www/WEB-INF/web.xml index a8cebe1b367..bc09033c787 100644 --- a/java/com.sap.sailing.www/WEB-INF/web.xml +++ b/java/com.sap.sailing.www/WEB-INF/web.xml @@ -17,16 +17,15 @@ CORSFilter com.sap.sailing.www.CORSFilter - - ClientConfigurationServlet - ClientConfigurationServlet - com.sap.sse.debranding.ClientConfigurationServlet - - - ClientConfigurationServlet + + ClientConfigurationFilter + ClientConfigurationFilter + com.sap.sse.debranding.ClientConfigurationFilter + + + ClientConfigurationFilter *.html - - + CORSFilter /fonts/* diff --git a/java/com.sap.sse.debranding/src/com/sap/sse/debranding/ClientConfigurationFilter.java b/java/com.sap.sse.debranding/src/com/sap/sse/debranding/ClientConfigurationFilter.java index aa709ff43c4..99cd5690efb 100644 --- a/java/com.sap.sse.debranding/src/com/sap/sse/debranding/ClientConfigurationFilter.java +++ b/java/com.sap.sse.debranding/src/com/sap/sse/debranding/ClientConfigurationFilter.java @@ -1,8 +1,9 @@ package com.sap.sse.debranding; -import java.io.ByteArrayOutputStream; +import java.io.FilterWriter; import java.io.IOException; import java.io.PrintWriter; +import java.io.Writer; import java.util.HashMap; import java.util.Map; @@ -16,9 +17,58 @@ import javax.servlet.ServletResponse; import javax.servlet.WriteListener; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponseWrapper; - +/** + * Use ${[variable name]} to get strings replaced within static pages. No escape syntax is currently available. All occurrences of the variables listed below + * that are found in the document will be replaced. The following variables are available at the moment: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Variablenamebranded valuedebranded/whitelabeled
"SAP""SAP """
"debrandingActive""false""true"
"whitelabeled""""-whitelabeled"
+ *

+ * + * Register as a filter for all the URLs that produce such static pages that you'd like to run replacements on. Example + * registration in a {@code web.xml} configuration file: + * + *

+ *   <filter>
+ *       <display-name>ClientConfigurationFilter</display-name>
+ *       <filter-name>ClientConfigurationFilter</filter-name>
+ *       <filter-class>com.sap.sse.debranding.ClientConfigurationFilter</filter-class>
+ *   </filter>
+ *   <filter-mapping>
+ *       <filter-name>ClientConfigurationFilter</filter-name>
+ *       <url-pattern>*.html</url-pattern>
+ *   </filter-mapping>
+ * 
+ *

+ * + * + * @see com.sap.sailing.server.gateway.test.support.WhitelabelSwitchServlet + * @author Georg Herdt + * + */ public class ClientConfigurationFilter implements Filter { + public static final String DEBRANDING_PROPERTY_NAME = "com.sap.sse.debranding"; + @Override public void init(FilterConfig filterConfig) throws ServletException { // intentionally left blank @@ -27,18 +77,66 @@ public class ClientConfigurationFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { - HttpServletResponseWrapper wrappedResponse = new CharResponseWrapper((HttpServletResponse) response); + final boolean deBrandingActive = Boolean.valueOf(System.getProperty(DEBRANDING_PROPERTY_NAME, "false")); + HttpServletResponseWrapper wrappedResponse = new CharResponseWrapper((HttpServletResponse) response, + createReplacementMap(deBrandingActive)); chain.doFilter(request, wrappedResponse); - String body = wrappedResponse.toString(); - String replaced = new String(body); - final boolean deBrandingActive = Boolean - .valueOf(System.getProperty(ClientConfigurationServlet.DEBRANDING_PROPERTY_NAME, "false")); - for (Map.Entry item : createReplacementMap(deBrandingActive).entrySet()) { - replaced = replaced.replace("${" + item.getKey() + "}", item.getValue()); - } - response.getWriter().write(replaced); + // String body = wrappedResponse.toString(); + // String replaced = new String(body); + // for (Map.Entry item : createReplacementMap(deBrandingActive).entrySet()) { + // replaced = replaced.replace("${" + item.getKey() + "}", item.getValue()); + // } + // response.getWriter().write(replaced); } + @Override + public void destroy() { + // intentionally left blank + } + + + private static class ContinuousReplacer { + StringBuffer buffer; + Consumer output; + Map replacementMap; + int bufferSize; + + public ContinuousReplacer(Map replacementMap, Consumer output) { + this.replacementMap = replacementMap; + this.output = output; + int maxKeyLength = this.replacementMap.keySet().stream().map(String::length) + .max((a, b) -> Integer.compare(a, b)).orElse(0); + this.bufferSize = maxKeyLength == 0 ? 0 : maxKeyLength + 3; + buffer = new StringBuffer(); + } + + public void push(String s) throws IOException { + buffer.append(s); + if (buffer.length()==bufferSize) { + int idxClosing; + if (buffer.substring(0, 2).equals("${") && (idxClosing = buffer.indexOf("}")) != -1) { + String replacement; + String key = buffer.substring(2, idxClosing); + if ((replacement = replacementMap.get(key)) != null) { + output.accept(replacement.getBytes()); + } else { + output.accept("${".getBytes()); + output.accept(key.getBytes()); + output.accept("}".getBytes()); + } + buffer.delete(0, idxClosing); + } else { + output.accept(buffer.substring(0, 1).getBytes()); + buffer.deleteCharAt(0); + } + } + } + + private interface Consumer { + void accept(T t) throws IOException; + } + } + private Map createReplacementMap(boolean deBrandingActive) { final Map map = new HashMap<>(); final String title; @@ -55,27 +153,104 @@ public class ClientConfigurationFilter implements Filter { map.put("whitelabeled", whitelabeled); return map; } - - @Override - public void destroy() { - // intentionally left blank - } - + private static class CharResponseWrapper extends HttpServletResponseWrapper { - private ByteArrayOutputStream output; + + private static final class MyWriter extends FilterWriter { + private MyWriter(Writer out, Map replacementMap) { + super(out); + new ContinuousReplacer(replacementMap, bytes -> out.write(new String(bytes))); + } - public String toString() { - return output.toString(); + @Override + public void write(char[] cbuf, int off, int len) throws IOException { + String s = new String(cbuf,off,len); + super.write(cbuf, off, len); + } + + @Override + public void write(int c) throws IOException { + // TODO Auto-generated method stub + super.write(c); + } + + @Override + public void write(String str, int off, int len) throws IOException { + // TODO Auto-generated method stub + super.write(str, off, len); + } + + @Override + public Writer append(char arg0) throws IOException { + // TODO Auto-generated method stub + return super.append(arg0); + } + + @Override + public Writer append(CharSequence arg0, int arg1, int arg2) throws IOException { + // TODO Auto-generated method stub + return super.append(arg0, arg1, arg2); + } + + @Override + public Writer append(CharSequence arg0) throws IOException { + // TODO Auto-generated method stub + return super.append(arg0); + } + + @Override + public void write(char[] arg0) throws IOException { + // TODO Auto-generated method stub + super.write(arg0); + } + + @Override + public void write(String arg0) throws IOException { + // TODO Auto-generated method stub + super.write(arg0); + } } - public CharResponseWrapper(HttpServletResponse response) { + private static final class MyServletOutputStream extends ServletOutputStream { + private final ServletOutputStream wrappedOutputStream; + private final ContinuousReplacer replacer; + + private MyServletOutputStream(ServletOutputStream wrappedOutputStream,Map replacementMap) { + this.wrappedOutputStream = wrappedOutputStream; + this.replacer = new ContinuousReplacer(replacementMap, bytes -> wrappedOutputStream.write(bytes)); + } + + @Override + public void write(int octet) throws IOException { + // System.out.print(); + String current = new String(new char[] { (char) octet }); + replacer.push(current); + } + + @Override + public void setWriteListener(WriteListener writeListener) { + // disable asynchronous writing, not needed for static pages + throw new IllegalStateException("not supported by ClientConfigurationFilter"); + } + + @Override + public boolean isReady() { + return wrappedOutputStream.isReady(); + } + } + + private Map replacementMap; + + public CharResponseWrapper(HttpServletResponse response, Map replacementMap) { super(response); + this.replacementMap = replacementMap; response.setBufferSize(0); // prevent buffering here to not need to think about it later - output = new ByteArrayOutputStream(); } - public PrintWriter getWriter() { - return new PrintWriter(output); + @Override + public PrintWriter getWriter() throws IOException { + FilterWriter filter = new MyWriter(super.getWriter(), replacementMap); + return new PrintWriter(filter); } @Override @@ -100,7 +275,7 @@ public class ClientConfigurationFilter implements Filter { @Override public void reset() { - // intentionally left blank, not supporting buffering here + throw new IllegalStateException("not supported by " + this.getClass().getCanonicalName()); } @Override @@ -110,24 +285,8 @@ public class ClientConfigurationFilter implements Filter { @Override public ServletOutputStream getOutputStream() throws IOException { - return new ServletOutputStream() { - - @Override - public void write(int octet) throws IOException { - output.write(octet); - } - - @Override - public void setWriteListener(WriteListener writeListener) { - // disable asynchronous writing, not needed for static pages - throw new IllegalStateException("not supported by ClientConfigurationServlet"); - } - - @Override - public boolean isReady() { - return true; - } - }; + final ServletOutputStream wrappedOutputStream = super.getOutputStream(); + return new MyServletOutputStream(wrappedOutputStream, replacementMap); } } } diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/shared/ClientConfiguration.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/shared/ClientConfiguration.java index e7524da569b..9dcb4f67772 100644 --- a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/shared/ClientConfiguration.java +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/shared/ClientConfiguration.java @@ -15,12 +15,12 @@ import com.sap.sse.gwt.client.context.impl.ClientConfigurationContextDataFactory * The data in this structure is integrated into the document object tree within the browser. * That can be accessed from here using the {@link ClientConfigurationContextDataJSO}. * - * The structure in the HTML is generated by the server see ClientConfigurationServlet for that. + * The structure in the HTML is generated by the server see ClientConfigurationFilter for that. * * To add new fields the following steps must be performed: *

    *
  • template within the HTML page must be extend by a new field declaration inside {@code document.clientConfigurationContext}.
  • - *
  • corresponding variable must be declared and filled with the appropriate value in {@link ClientConfigurationServlet}
  • + *
  • corresponding variable must be declared and filled with the appropriate value in {@link ClientConfigurationFilter}
  • *
  • class {@link ClientConfigurationContextDataJSO} must be extended with an getter for the new field
  • *
* diff --git a/java/com.sap.sse.security.ui/WEB-INF/web.xml b/java/com.sap.sse.security.ui/WEB-INF/web.xml index e960b82cd8a..260503fa386 100644 --- a/java/com.sap.sse.security.ui/WEB-INF/web.xml +++ b/java/com.sap.sse.security.ui/WEB-INF/web.xml @@ -53,15 +53,15 @@ /*
- - ClientConfigurationServlet - ClientConfigurationServlet - com.sap.sse.debranding.ClientConfigurationServlet - - - ClientConfigurationServlet + + ClientConfigurationFilter + ClientConfigurationFilter + com.sap.sse.debranding.ClientConfigurationFilter + + + ClientConfigurationFilter *.html - + LocaleInjectionFilter