switch to filter

bug5153
This commit is contained in:
Georg Herdt
2020-05-08 08:43:03 +02:00
parent 32589e26d6
commit 6e0d6452b1
9 changed files with 231 additions and 207 deletions
@@ -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:
* <table border="1">
* <tr>
* <th>Variablename</th>
* <th>branded value</th>
* <th>debranded/whitelabeled</th>
* </tr>
* <tr>
* <td>"SAP"</td>
* <td>"SAP&nbsp;"</td>
* <td>""</td>
* </tr>
* <tr>
* <td>"debrandingActive"</td>
* <td>"false"</td>
* <td>"true"</td>
* </tr>
* <tr>
* <td>"whitelabeled"</td>
* <td>""</td>
* <td>"-whitelabeled"</td>
* </tr>
* </table>
* <p>
*
* 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:
*
* <pre>
* &lt;filter&gt;
* &lt;display-name&gt;ClientConfigurationFilter&lt;/display-name&gt;
* &lt;filter-name&gt;ClientConfigurationFilter&lt;/filter-name&gt;
* &lt;filter-class&gt;com.sap.sse.debranding.ClientConfigurationFilter&lt;/filter-class&gt;
* &lt;/filter&gt;
* &lt;filter-mapping&gt;
* &lt;filter-name&gt;ClientConfigurationFilter&lt;/filter-name&gt;
* &lt;url-pattern&gt;*.html&lt;/url-pattern&gt;
* &lt;/filter-mapping&gt;
* </pre>
* <p>
*
*
* @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<String, String> 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<String, String> 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<byte[]> output;
Map<String, String> replacementMap;
int bufferSize;
public ContinuousReplacer(Map<String,String> replacementMap, Consumer<byte[]> 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<T> {
void accept(T t) throws IOException;
}
}
private Map<String, String> createReplacementMap(boolean deBrandingActive) {
final Map<String, String> 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<String,String> 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<String,String> 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<String, String> replacementMap;
public CharResponseWrapper(HttpServletResponse response, Map<String,String> 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);
}
}
}
@@ -1,125 +0,0 @@
package com.sap.sse.debranding;
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 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:
* <table border="1">
* <tr>
* <th>Variablename</th>
* <th>branded value</th>
* <th>debranded/whitelabeled</th>
* </tr>
* <tr>
* <td>"SAP"</td>
* <td>"SAP&nbsp;"</td>
* <td>""</td>
* </tr>
* <tr>
* <td>"debrandingActive"</td>
* <td>"false"</td>
* <td>"true"</td>
* </tr>
* <tr>
* <td>"whitelabeled"</td>
* <td>""</td>
* <td>"-whitelabeled"</td>
* </tr>
* </table>
* <p>
*
* 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:
*
* <pre>
* &lt;servlet&gt;
* &lt;display-name&gt;ClientConfigurationServlet&lt;/display-name&gt;
* &lt;servlet-name&gt;ClientConfigurationServlet&lt;/servlet-name&gt;
* &lt;servlet-class&gt;com.sap.sse.debranding.ClientConfigurationServlet&lt;/servlet-class&gt;
* &lt;/servlet&gt;
* &lt;servlet-mapping&gt;
* &lt;servlet-name&gt;ClientConfigurationServlet&lt;/servlet-name&gt;
* &lt;url-pattern&gt;*.html&lt;/url-pattern&gt;
* &lt;/servlet-mapping&gt;
* </pre>
* <p>
*
* 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<String, byte[]> 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("text/html;charset=UTF-8");
if ((cachedPage = cache.get(pageKey)) != null) {
IOUtils.write(cachedPage, resp.getOutputStream());
} else {
try (InputStream in = this.getServletContext().getResourceAsStream(servletPath)) {
byte[] buffer = IOUtils.toByteArray(in);
String content = new String(buffer);
for (Map.Entry<String, String> 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);
}
}
}
private String generateKey(String servletPath, boolean active) {
return servletPath + "_" + active;
}
private Map<String,String> createReplacementMap(boolean deBrandingActive) {
final Map<String,String> 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;
}
}