From 385b8f803fc5571fed6fedd104fa6f531236d795 Mon Sep 17 00:00:00 2001 From: Georg Herdt Date: Fri, 29 Jan 2021 13:29:24 +0100 Subject: [PATCH] add listener to add client configuration parameters bug 5153 --- .../debranding/ClientConfigurationFilter.java | 346 ------------------ .../ClientConfigurationListener.java | 96 +++++ .../ClientConfigurationServlet.java | 133 ------- 3 files changed, 96 insertions(+), 479 deletions(-) delete mode 100644 java/com.sap.sse.debranding/src/com/sap/sse/debranding/ClientConfigurationFilter.java create mode 100644 java/com.sap.sse.debranding/src/com/sap/sse/debranding/ClientConfigurationListener.java delete mode 100644 java/com.sap.sse.debranding/src/com/sap/sse/debranding/ClientConfigurationServlet.java 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 deleted file mode 100644 index 0bec2322079..00000000000 --- a/java/com.sap.sse.debranding/src/com/sap/sse/debranding/ClientConfigurationFilter.java +++ /dev/null @@ -1,346 +0,0 @@ -package com.sap.sse.debranding; - -import java.io.ByteArrayOutputStream; -import java.io.CharArrayWriter; -import java.io.FilterWriter; -import java.io.IOException; -import java.io.PrintWriter; -import java.io.UnsupportedEncodingException; -import java.io.Writer; -import java.nio.charset.Charset; -import java.util.HashMap; -import java.util.Map; - -import javax.servlet.Filter; -import javax.servlet.FilterChain; -import javax.servlet.FilterConfig; -import javax.servlet.ServletException; -import javax.servlet.ServletOutputStream; -import javax.servlet.ServletRequest; -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"; - public static final String CLIENT_CONFIGURATION_FILTER_MAX_BUFFER = "com.sap.sse.clientconfiguration.maxbuffer"; - private static int MAX_REPLACEMENT_BUFFER = Integer - .valueOf(System.getProperty(CLIENT_CONFIGURATION_FILTER_MAX_BUFFER, "1000000")); - - @Override - public void init(FilterConfig filterConfig) throws ServletException { - // intentionally left blank - } - - @Override - public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) - throws IOException, ServletException { - final boolean deBrandingActive = Boolean.valueOf(System.getProperty(DEBRANDING_PROPERTY_NAME, "false")); - CharResponseWrapper wrappedResponse = new CharResponseWrapper((HttpServletResponse) response, - createReplacementMap(deBrandingActive)); - chain.doFilter(request, wrappedResponse); - wrappedResponse.replaceAndWriteToUnderlying(wrappedResponse.getCharacterEncoding()); - } - - @Override - public void destroy() { - // intentionally left blank - } - - private Map createReplacementMap(boolean deBrandingActive) { - final Map map = new HashMap<>(); - final String title; - final String whitelabeled; - if (deBrandingActive) { - title = ""; - whitelabeled = "-whitelabeled"; - } else { - title = "SAP "; - whitelabeled = ""; - } - map.put("SAP", title); - map.put("debrandingActive", Boolean.toString(deBrandingActive)); - map.put("whitelabeled", whitelabeled); -// cachedMap = map; - return map; - } - - /** - * Replacement logic is implemented here. - * - * @author Georg Herdt - * - */ - private static class ContinuousReplacer { - StringBuffer buffer; - Consumer output; - Map replacementMap; - int bufferSize; - - /** - * - * @param replacementMap - * a map containing the key value mappings that will be replaced - * @param output - * a consumer that accepts the replaced content. Allows abstraction from writer or stream oriented - * processing. - */ - 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(char character) throws IOException { - buffer.append(character); - // wait until buffer is filled up - 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 + 1); - } else { - output.accept(buffer.substring(0, 1).getBytes()); - buffer.deleteCharAt(0); - } - } - } - - private interface Consumer { - void accept(T t) throws IOException; - } - } - - /** - * Keeps track of bytes buffered during request processing. - * - * @author Georg Herdt - * - */ - private static final class BufferingWriter extends FilterWriter { - int bytesWritten = 0; - - public BufferingWriter(Writer out) { - super(out); - } - - @Override - public void write(char[] cbuf, int off, int len) throws IOException { - super.write(cbuf, off, len); - count(len); - } - - @Override - public void write(int c) throws IOException { - super.write(c); - count(1); - } - - @Override - public void write(String str, int off, int len) throws IOException { - super.write(str, off, len); - count(len); - } - - private void count(int bytesWritten) { - this.bytesWritten += bytesWritten; - if (this.bytesWritten > MAX_REPLACEMENT_BUFFER) { - throw new IllegalStateException("buffersize exceeded " + MAX_REPLACEMENT_BUFFER); - } - } - } - - /** - * Keeps track of bytes buffered during request processing. - * - * @author Georg Herdt - * - */ - private static final class BufferingServletOutputStream extends ServletOutputStream { - private final ByteArrayOutputStream buffer; - - private BufferingServletOutputStream() { - this.buffer = new ByteArrayOutputStream(); - } - - @Override - public boolean isReady() { - // always ready to write into buffer - return true; - } - - @Override - public void setWriteListener(WriteListener writeListener) { - // intentionally left blank, no asynchronous writing supported - throw new IllegalStateException("async not supported"); - } - - @Override - public void write(int b) throws IOException { - buffer.write(b); - if (buffer.size() > MAX_REPLACEMENT_BUFFER) { - throw new IllegalStateException("buffersize exceeded " + MAX_REPLACEMENT_BUFFER); - } - } - - public String getBufferedText(Charset charset) throws UnsupportedEncodingException { - return buffer.toString(charset.name()); - } - } - - /** - * Wraps response and buffers for later replacement. - * - * @author Georg Herdt - * - */ - private static class CharResponseWrapper extends HttpServletResponseWrapper { - - private Map replacementMap; - - private BufferingServletOutputStream bufferedStream; - - private FilterWriter bufferedWriter; - - 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 - } - - public void replaceAndWriteToUnderlying(String encoding) throws IOException { - String text; - Charset charset; - try { - charset = Charset.forName(encoding); - } catch (IllegalArgumentException e) { - // default charset as specified by servlet api - charset = Charset.forName("ISO-8859-1"); - } - if (bufferedStream != null) { - text = bufferedStream.getBufferedText(charset); - } else if (bufferedWriter != null) { - text = bufferedWriter.toString(); - } else { - text = null; - } - if (text != null) { - ContinuousReplacer replacer = new ContinuousReplacer(replacementMap, - b -> this.getResponse().getOutputStream().write(b)); - for (char c : text.toCharArray()) { - replacer.push(c); - } - } - } - - @Override - public PrintWriter getWriter() throws IOException { - if (bufferedStream != null) { - throw new IllegalStateException("getOutputStream already called"); - } - this.bufferedWriter = new BufferingWriter(new CharArrayWriter()); - return new PrintWriter(this.bufferedWriter); - } - - @Override - public void setResponse(ServletResponse response) { - super.setResponse(response); - } - - @Override - public void setBufferSize(int size) { - super.setBufferSize(0); - } - - @Override - public int getBufferSize() { - return 0; // not supporting buffering here see API doc - } - - @Override - public void flushBuffer() throws IOException { - // intentionally left blank, not supporting buffering here - } - - @Override - public void reset() { - throw new IllegalStateException("not supported by " + this.getClass().getCanonicalName()); - } - - @Override - public void resetBuffer() { - // intentionally left blank, not supporting buffering here - } - - @Override - public ServletOutputStream getOutputStream() throws IOException { - if (bufferedWriter != null) { - throw new IllegalStateException("getWriter already called"); - } - this.bufferedStream = new BufferingServletOutputStream(); - return bufferedStream; - } - } -} diff --git a/java/com.sap.sse.debranding/src/com/sap/sse/debranding/ClientConfigurationListener.java b/java/com.sap.sse.debranding/src/com/sap/sse/debranding/ClientConfigurationListener.java new file mode 100644 index 00000000000..2aa3bb942b1 --- /dev/null +++ b/java/com.sap.sse.debranding/src/com/sap/sse/debranding/ClientConfigurationListener.java @@ -0,0 +1,96 @@ +package com.sap.sse.debranding; + +import java.util.HashMap; +import java.util.Map; + +import javax.servlet.ServletContext; +import javax.servlet.ServletRequestEvent; +import javax.servlet.http.HttpServletRequest; +/** + * JSP servlet is registered on *.html within web.xml . Use the following JSP expression + *

applicationScope['clientConfigurationContext.variableName']}
to get strings replaced within the page. The + * variables listed below are available for replacements: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Variablenamebranded valuedebranded/whitelabeled
"SAP""SAP """
"debrandingActive""false""true"
"whitelabeled""""-whitelabeled"
+ *

+ * + * Register a the jsp servlet 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: + * + *

+ *   <servlet-mapping>
+ *       <servlet-name>jsp</servlet-name>
+ *       <url-pattern>*.html</url-pattern>
+ *   </servlet-mapping>
+ * 
+ *

+ * + * + * @see com.sap.sailing.server.gateway.test.support.WhitelabelSwitchServlet + * @author Georg Herdt + * + */ +public class ClientConfigurationListener implements javax.servlet.ServletRequestListener { + public static final String DEBRANDING_PROPERTY_NAME = "com.sap.sse.debranding"; + + @Override + public void requestInitialized(ServletRequestEvent sre) { + if (sre.getServletRequest().getScheme().startsWith("http")) { + String path = ((HttpServletRequest) sre.getServletRequest()).getServletPath(); + if (path != null && (path.endsWith("/") || path.endsWith(".html"))) { + final ServletContext ctx = sre.getServletContext(); + final String ctxDebrandingActive = (String) ctx + .getAttribute("clientConfigurationContext.debrandingActive"); + final boolean deBrandingActive = Boolean.valueOf(System.getProperty(DEBRANDING_PROPERTY_NAME, "false")); + if (ctxDebrandingActive == null + || !Boolean.toString(deBrandingActive).equalsIgnoreCase(ctxDebrandingActive)) { + createReplacementMap(deBrandingActive).forEach((k, v) -> { + sre.getServletContext().setAttribute("clientConfigurationContext." + k, v); + }); + } + } + } + } + + @Override + public void requestDestroyed(ServletRequestEvent sre) { + // intentionally left blank + } + + private Map createReplacementMap(boolean deBrandingActive) { + final Map map = new HashMap<>(); + final String title; + final String whitelabeled; + if (deBrandingActive) { + title = ""; + whitelabeled = "-whitelabeled"; + } else { + title = "SAP "; + whitelabeled = ""; + } + map.put("SAP", title); + map.put("debrandingActive", Boolean.toString(deBrandingActive)); + map.put("whitelabeled", whitelabeled); + return map; + } +} diff --git a/java/com.sap.sse.debranding/src/com/sap/sse/debranding/ClientConfigurationServlet.java b/java/com.sap.sse.debranding/src/com/sap/sse/debranding/ClientConfigurationServlet.java deleted file mode 100644 index 952ab1df0e0..00000000000 --- a/java/com.sap.sse.debranding/src/com/sap/sse/debranding/ClientConfigurationServlet.java +++ /dev/null @@ -1,133 +0,0 @@ -package com.sap.sse.debranding; - -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.logging.Level; -import java.util.logging.Logger; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.ws.rs.core.MediaType; - -import org.apache.commons.io.IOUtils; -import org.apache.http.HttpStatus; - -/** - * 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 servlet 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: - * - *

- *   <servlet>
- *       <display-name>ClientConfigurationServlet</display-name>
- *       <servlet-name>ClientConfigurationServlet</servlet-name>
- *       <servlet-class>com.sap.sse.debranding.ClientConfigurationServlet</servlet-class>
- *   </servlet>
- *   <servlet-mapping>
- *       <servlet-name>ClientConfigurationServlet</servlet-name>
- *       <url-pattern>*.html</url-pattern>
- *   </servlet-mapping>
- * 
- *

- * - * The servlet caches the results, both, for the branded as well as the unbranded/replaced contents. - * - * @see com.sap.sailing.server.gateway.test.support.WhitelabelSwitchServlet - * @author Georg Herdt - * - */ -public class ClientConfigurationServlet extends HttpServlet { - - private static final Logger logger = Logger.getLogger(ClientConfigurationServlet.class.getName()); - - /** serial version uid */ - private static final long serialVersionUID = -2228462977010198686L; - - public static final String DEBRANDING_PROPERTY_NAME = "com.sap.sse.debranding"; - - private final ConcurrentHashMap cache = new ConcurrentHashMap<>(); - - @Override - protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - final boolean deBrandingActive = Boolean.valueOf(System.getProperty(DEBRANDING_PROPERTY_NAME, "false")); - final String servletPath = req.getServletPath(); - final byte[] cachedPage; - final String pageKey = generateKey(servletPath, deBrandingActive); - resp.setContentType(MediaType.TEXT_HTML); - if ((cachedPage = cache.get(pageKey)) != null) { - IOUtils.write(cachedPage, resp.getOutputStream()); - } else { - try (InputStream in = this.getServletContext().getResourceAsStream(servletPath)) { - if (in == null) { - throw new FileNotFoundException(servletPath); - } - byte[] buffer = IOUtils.toByteArray(in); - String content = new String(buffer); - for (Map.Entry item : createReplacementMap(deBrandingActive).entrySet()) { - content = content.replace("${" + item.getKey() + "}", item.getValue()); - } - byte[] bytes = content.getBytes(); - IOUtils.write(bytes, resp.getOutputStream()); - cache.computeIfAbsent(pageKey, key -> bytes); - } catch (RuntimeException e) { - logger.log(Level.WARNING, "could not process or read resource " + servletPath, e); - resp.sendError(HttpStatus.SC_INTERNAL_SERVER_ERROR); - } catch (FileNotFoundException f) { - logger.log(Level.WARNING, "could not find resource " + servletPath); - resp.sendError(HttpStatus.SC_NOT_FOUND); - } - } - } - - private String generateKey(String servletPath, boolean active) { - return servletPath + "_" + active; - } - - private Map createReplacementMap(boolean deBrandingActive) { - final Map map = new HashMap<>(); - final String title; - final String whitelabeled; - if (deBrandingActive) { - title = ""; - whitelabeled = "-whitelabeled"; - } else { - title = "SAP "; - whitelabeled = ""; - } - map.put("SAP", title); - map.put("debrandingActive", Boolean.toString(deBrandingActive)); - map.put("whitelabeled", whitelabeled); - return map; - } -}