Skip to content

Commit 5bcc889

Browse files
Merge pull request #88 from SAP/refactor-servlet-filters
Refactor Servlet Filters
2 parents 65e25eb + 45af909 commit 5bcc889

25 files changed

+1386
-317
lines changed

cf-java-logging-support-servlet/src/main/java/com/sap/hcp/cf/logging/servlet/dynlog/DynLogEnvironment.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
import com.sap.hcp.cf.logging.common.helper.Environment;
1212

13-
public class DynLogEnvironment implements DynLogConfiguration {
13+
public class DynLogEnvironment implements DynamicLogLevelConfiguration {
1414

1515
private static final Logger LOGGER = LoggerFactory.getLogger(DynLogEnvironment.class);
1616
private final RSAPublicKey rsaPublicKey;

cf-java-logging-support-servlet/src/main/java/com/sap/hcp/cf/logging/servlet/dynlog/DynLogConfiguration.java renamed to cf-java-logging-support-servlet/src/main/java/com/sap/hcp/cf/logging/servlet/dynlog/DynamicLogLevelConfiguration.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,15 @@
44

55
import javax.servlet.http.HttpServletRequest;
66

7-
public interface DynLogConfiguration {
8-
9-
String getDynLogHeaderKey();
7+
@FunctionalInterface
8+
public interface DynamicLogLevelConfiguration {
109

1110
RSAPublicKey getRsaPublicKey();
1211

12+
default String getDynLogHeaderKey() {
13+
return "SAP-LOG-LEVEL";
14+
};
15+
1316
default String getDynLogHeaderValue(HttpServletRequest httpRequest) {
1417
return httpRequest.getHeader(getDynLogHeaderKey());
1518
}

cf-java-logging-support-servlet/src/main/java/com/sap/hcp/cf/logging/servlet/dynlog/DynamicLogLevelProcessor.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,11 @@ public class DynamicLogLevelProcessor {
3232
* {@link DynamicLogLevelProcessor#DynamicLogLevelProcessor(RSAPublicKey)}
3333
* instead.
3434
* @param dynLogConfig
35-
* the {@link DynLogConfiguration} to read the public RSA key for
35+
* the {@link DynamicLogLevelConfiguration} to read the public RSA key for
3636
* JWT validation from.
3737
*/
3838
@Deprecated
39-
public DynamicLogLevelProcessor(DynLogConfiguration dynLogConfig) {
39+
public DynamicLogLevelProcessor(DynamicLogLevelConfiguration dynLogConfig) {
4040
this(dynLogConfig.getRsaPublicKey());
4141
}
4242

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package com.sap.hcp.cf.logging.servlet.filter;
2+
3+
import java.io.IOException;
4+
5+
import javax.servlet.Filter;
6+
import javax.servlet.FilterChain;
7+
import javax.servlet.FilterConfig;
8+
import javax.servlet.ServletException;
9+
import javax.servlet.ServletRequest;
10+
import javax.servlet.ServletResponse;
11+
import javax.servlet.http.HttpServletRequest;
12+
import javax.servlet.http.HttpServletResponse;
13+
14+
public abstract class AbstractLoggingFilter implements Filter {
15+
16+
@Override
17+
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException,
18+
ServletException {
19+
if (request instanceof HttpServletRequest && response instanceof HttpServletResponse) {
20+
doFilterRequest((HttpServletRequest) request, (HttpServletResponse) response, chain);
21+
} else {
22+
chain.doFilter(request, response);
23+
}
24+
}
25+
26+
/**
27+
* Provides a default implementation for handling servlet requests already
28+
* cast to {@link HttpServletRequest} and {@link HttpServletResponse}.
29+
* Custom implementations of {@link AbstractLoggingFilter} should overwrite
30+
* {@link #beforeFilter(HttpServletRequest, HttpServletResponse)} and/or
31+
* {@link #cleanup(HttpServletRequest, HttpServletResponse)}.
32+
*
33+
* @param request
34+
* cast as {@link HttpServletRequest}
35+
* @param response
36+
* cast as {@link HttpServletResponse}
37+
* @param chain
38+
* the {@link FilterChain} to continue request handling
39+
* @throws IOException
40+
* @throws ServletException
41+
*/
42+
protected void doFilterRequest(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
43+
throws IOException,
44+
ServletException {
45+
try {
46+
beforeFilter(request, response);
47+
chain.doFilter(request, response);
48+
} finally {
49+
cleanup(request, response);
50+
51+
}
52+
}
53+
54+
/**
55+
* Processes the request/response before it is passed along the filter
56+
* chain. Even if {@link #beforeFilter} fails, {@link #cleanup} will be
57+
* executed.
58+
*
59+
* @param request
60+
* @param response
61+
*/
62+
protected void beforeFilter(HttpServletRequest request, HttpServletResponse response) {
63+
}
64+
65+
/**
66+
* Cleanup after the request/response was handled by the filter chain. This
67+
* is executed even in cases of failures during handling or
68+
* {@link #beforeFilter}. Use this method to reset or clean-up state, e.g.
69+
* MDC. Be aware, that there may be partially initalized state from
70+
* {@link #beforeFilter}.
71+
*
72+
* @param request
73+
* @param response
74+
*/
75+
protected void cleanup(HttpServletRequest request, HttpServletResponse response) {
76+
}
77+
78+
/**
79+
* This is an empty implementation for filters, that do not require
80+
* initialization.
81+
*/
82+
@Override
83+
public void init(FilterConfig filterConfig) throws ServletException {
84+
// nothing to do
85+
}
86+
87+
/**
88+
* this is an empty implementation for tolters, that do not require clean-up
89+
* of state.
90+
*/
91+
@Override
92+
public void destroy() {
93+
// nothing to do
94+
}
95+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package com.sap.hcp.cf.logging.servlet.filter;
2+
3+
import static java.util.Collections.unmodifiableList;
4+
import static java.util.stream.Collectors.toList;
5+
6+
import java.util.Arrays;
7+
import java.util.Collections;
8+
import java.util.List;
9+
import java.util.Objects;
10+
import java.util.stream.Stream;
11+
12+
import javax.servlet.http.HttpServletRequest;
13+
import javax.servlet.http.HttpServletResponse;
14+
15+
import com.sap.hcp.cf.logging.common.LogContext;
16+
import com.sap.hcp.cf.logging.common.request.HttpHeader;
17+
import com.sap.hcp.cf.logging.common.request.HttpHeaders;
18+
19+
/**
20+
* Extracts the HTTP headers from the request and adds them to the logging
21+
* context. It defaults to {@link HttpHeaders#propagated()}. Custom headers can
22+
* be used.
23+
*/
24+
public class AddHttpHeadersToLogContextFilter extends AbstractLoggingFilter {
25+
26+
private List<HttpHeader> headers;
27+
private List<String> fields;
28+
29+
/**
30+
* The default constructor uses {@link HttpHeaders#propagated()} to define
31+
* the HTTP headers, that are added to the logging context.
32+
*/
33+
public AddHttpHeadersToLogContextFilter() {
34+
this(HttpHeaders.propagated());
35+
}
36+
37+
public AddHttpHeadersToLogContextFilter(HttpHeader... headers) {
38+
this(Collections.emptyList(), headers);
39+
}
40+
41+
/**
42+
* Use this constructor to add your own HTTP headers to the default list.
43+
* You need to implement {@link HttpHeader}. Note, that
44+
* {@link HttpHeader#isPropagated} needs to be true. All other headers will
45+
* be ignored. Usage to add your own header would be:
46+
* {@code new AddHttpHeadersToLogContextFilter(HttpHeaders.propagated, yourHeader)}
47+
*
48+
* @param list
49+
* a list of {@link HttpHeader}, can be default
50+
* {@link HttpHeaders#propagated()}
51+
* @param custom
52+
* a single {@link HttpHeader} to add to the list
53+
*/
54+
public AddHttpHeadersToLogContextFilter(List<? extends HttpHeader> list, HttpHeader... custom) {
55+
Stream<HttpHeader> allHeaders = Stream.concat(list.stream(), Arrays.stream(custom));
56+
this.headers = unmodifiableList(allHeaders.filter(HttpHeader::isPropagated).collect(toList()));
57+
this.fields = unmodifiableList(headers.stream().map(HttpHeader::getField).filter(Objects::nonNull).collect(
58+
toList()));
59+
}
60+
61+
@Override
62+
protected void beforeFilter(HttpServletRequest request, HttpServletResponse response) {
63+
for (HttpHeader header: headers) {
64+
String headerValue = HttpHeaderUtilities.getHeaderValue(request, header);
65+
if (header.getField() != null && headerValue != null) {
66+
LogContext.add(header.getField(), headerValue);
67+
}
68+
}
69+
}
70+
71+
@Override
72+
protected void cleanup(HttpServletRequest request, HttpServletResponse response) {
73+
fields.forEach(LogContext::remove);
74+
}
75+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package com.sap.hcp.cf.logging.servlet.filter;
2+
3+
import javax.servlet.http.HttpServletRequest;
4+
import javax.servlet.http.HttpServletResponse;
5+
6+
import com.sap.hcp.cf.logging.common.LogContext;
7+
8+
/**
9+
* <p>
10+
* The {@link AddVcapEnvironmentToLogContextFilter} extracts CF specific
11+
* metadata about the running application and adds it to the log context. The
12+
* metadata includes the app, org and space names and ids. This allows to
13+
* attribute a generated log message to the correct source from the message
14+
* alone.
15+
* </p>
16+
*
17+
* <p>
18+
* <b>Note:</b> This metadata can usually also be obtained from the CF channel
19+
* shipping the logs, e.g. syslog structured data. Hence, depending on the
20+
* use-case, it may not be required to add the metadata with this filter.
21+
* </p>
22+
*/
23+
public class AddVcapEnvironmentToLogContextFilter extends AbstractLoggingFilter {
24+
25+
@Override
26+
protected void beforeFilter(HttpServletRequest request, HttpServletResponse response) {
27+
LogContext.loadContextFields();
28+
}
29+
30+
@Override
31+
protected void cleanup(HttpServletRequest request, HttpServletResponse response) {
32+
LogContext.resetContextFields();
33+
}
34+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package com.sap.hcp.cf.logging.servlet.filter;
2+
3+
import java.io.IOException;
4+
import java.util.ArrayList;
5+
import java.util.Collections;
6+
import java.util.Iterator;
7+
import java.util.List;
8+
9+
import javax.servlet.Filter;
10+
import javax.servlet.FilterChain;
11+
import javax.servlet.FilterConfig;
12+
import javax.servlet.ServletException;
13+
import javax.servlet.ServletRequest;
14+
import javax.servlet.ServletResponse;
15+
16+
/**
17+
* <p>
18+
* The {@link CompositeFilter} allows to group several servlet {@link Filter}
19+
* into one. This is used to allow customizable filters and provide a backwards
20+
* compatible {@link RequestLoggingFilter}. The {@link FilterConfig} is
21+
* forwarded to all filters, that are grouped. This may lead to
22+
* incompatibilities between those shared configurations.
23+
* </p>
24+
*
25+
* <p>
26+
* You can easily create a subclass of {@link CompositeFilter} and add all the
27+
* filters from com.sap.hcp.cf.logging.servlet.filter you wish. You can even
28+
* bring your own filters. Make sure, there are no incompatible
29+
* {@link FilterConfig} initializations.
30+
* </p>
31+
*/
32+
public class CompositeFilter implements Filter {
33+
34+
private final List<Filter> filters;
35+
36+
public CompositeFilter(Filter... filters) {
37+
this.filters = new ArrayList<>(filters.length);
38+
Collections.addAll(this.filters, filters);
39+
}
40+
41+
public List<Filter> getFilters() {
42+
return Collections.unmodifiableList(filters);
43+
}
44+
45+
/**
46+
* Forwards the {@link FilterConfig} to all grouped filters. Be care to use
47+
* only filters, that are compatible to each other.
48+
*/
49+
@Override
50+
public void init(FilterConfig config) throws ServletException {
51+
for (Filter filter: this.filters) {
52+
filter.init(config);
53+
}
54+
}
55+
56+
@Override
57+
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException,
58+
ServletException {
59+
new InternalFilterChain(chain, this.filters).doFilter(request, response);
60+
}
61+
62+
@Override
63+
public void destroy() {
64+
for (int i = this.filters.size() - 1; i >= 0; i--) {
65+
Filter filter = this.filters.get(i);
66+
filter.destroy();
67+
}
68+
}
69+
70+
private static class InternalFilterChain implements FilterChain {
71+
72+
private final FilterChain originalChain;
73+
private final Iterator<? extends Filter> current;
74+
75+
public InternalFilterChain(FilterChain chain, List<? extends Filter> additionalFilters) {
76+
this.originalChain = chain;
77+
this.current = additionalFilters.iterator();
78+
}
79+
80+
@Override
81+
public void doFilter(final ServletRequest request, final ServletResponse response) throws IOException,
82+
ServletException {
83+
if (current.hasNext()) {
84+
current.next().doFilter(request, response, this);
85+
} else {
86+
originalChain.doFilter(request, response);
87+
}
88+
}
89+
}
90+
91+
}

cf-java-logging-support-servlet/src/main/java/com/sap/hcp/cf/logging/servlet/filter/ContentLengthTrackingResponseWrapper.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public class ContentLengthTrackingResponseWrapper extends HttpServletResponseWra
1919
private WrappedOutputStream wrappedOS = null;
2020
private WrappedPrintWriter wrappedWriter = null;
2121

22-
public ContentLengthTrackingResponseWrapper(HttpServletResponse response) throws IOException {
22+
public ContentLengthTrackingResponseWrapper(HttpServletResponse response) {
2323
super(response);
2424
this.response = response;
2525
}

0 commit comments

Comments
 (0)