1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package de.kaiserpfalzedv.commons.rest.workflow;
19
20 import java.io.IOException;
21 import java.time.Duration;
22 import java.time.OffsetDateTime;
23 import java.time.ZoneId;
24 import java.time.format.DateTimeParseException;
25 import java.time.temporal.ChronoUnit;
26 import java.time.temporal.TemporalAmount;
27
28 import org.slf4j.MDC;
29
30 import de.kaiserpfalzedv.commons.core.workflow.WorkflowDetailInfoImpl;
31 import de.kaiserpfalzedv.commons.core.workflow.WorkflowInfoImpl;
32 import jakarta.inject.Inject;
33 import jakarta.inject.Singleton;
34 import jakarta.servlet.Filter;
35 import jakarta.servlet.FilterChain;
36 import jakarta.servlet.ServletException;
37 import jakarta.servlet.ServletRequest;
38 import jakarta.servlet.ServletResponse;
39 import jakarta.servlet.http.HttpServletRequest;
40 import jakarta.servlet.http.HttpServletResponse;
41 import lombok.RequiredArgsConstructor;
42 import lombok.extern.slf4j.Slf4j;
43
44
45
46
47
48
49
50 @RequiredArgsConstructor(onConstructor = @__(@Inject))
51 @Singleton
52 @Slf4j
53 public class WorkflowFilter implements Filter {
54 public static final String WORKFLOW_DATA = "de.kaiserpfalzedv.commons.core.workflow.data";
55
56 private static final String WORKFLOW_USER = "X-wf-user";
57
58 private static final String NAME = "name";
59 private static final String ID = "id";
60 private static final String CREATED = "created";
61 private static final String TTL = "ttl";
62 private static final String RESPONSE = "response";
63
64 private static final String WORKFLOW_PREFIX = "X-wf-";
65 private static final String ACTION_PREFIX = "X-wf-action-";
66 private static final String CALL_PREFIX = "X-wf-call-";
67
68 private final WorkflowProvider provider;
69
70 @Override
71 public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) throws IOException, ServletException {
72 final HttpServletRequest req = this.checkForHttpServletRequestOrThrowException(request);
73 final HttpServletResponse res = this.checkForHttpServletResponseOrThrowException(response);
74
75 this.filter(req);
76 chain.doFilter(request, response);
77 this.filter(req, res);
78 }
79
80 private HttpServletRequest checkForHttpServletRequestOrThrowException(final ServletRequest request) throws ServletException {
81 if (! (request instanceof HttpServletRequest)) {
82 throw new ServletException("Wrong servlet type. This filter only works on HTTP servlet.");
83 }
84
85 return (HttpServletRequest) request;
86 }
87
88 private HttpServletResponse checkForHttpServletResponseOrThrowException(final ServletResponse response) throws ServletException {
89 if (! (response instanceof HttpServletResponse)) {
90 throw new ServletException("Wrong servlet type. This filter only works on HTTP servlet.");
91 }
92
93 return (HttpServletResponse) response;
94 }
95
96 private void filter(final HttpServletRequest context) {
97 final WorkflowInfoImpl info = this.getWorkflowInfo(context);
98 this.prepareMDC(info);
99
100 context.setAttribute(WORKFLOW_DATA, info);
101 this.provider.registerWorkflowInfo(info);
102
103 log.trace(
104 "Created the workflow info. workflow='{}', action='{}', call='{}', user='{}'",
105 info.getWorkflow().getId(),
106 info.getAction().getId(),
107 info.getCall().getId(),
108 info.getUser()
109 );
110 }
111
112 private WorkflowInfoImpl getWorkflowInfo(final HttpServletRequest context) {
113 return WorkflowInfoImpl.builder()
114 .user(this.checkValidHeader(context.getHeader(WORKFLOW_USER)))
115 .workflow(this.getWorkflowInfoDetail(context, WORKFLOW_PREFIX))
116 .action(this.getWorkflowInfoDetail(context, ACTION_PREFIX))
117 .call(this.getWorkflowInfoDetail(context, CALL_PREFIX))
118 .build();
119 }
120
121 private WorkflowDetailInfoImpl getWorkflowInfoDetail(final HttpServletRequest context, final String prefix) {
122 final String name = this.checkValidHeader(context.getHeader(prefix + NAME));
123 final String id = this.checkValidHeader(context.getHeader(prefix + ID));
124 final String response = this.checkValidHeader(context.getHeader(prefix + RESPONSE));
125
126 final WorkflowDetailInfoImpl.WorkflowDetailInfoImplBuilder result = WorkflowDetailInfoImpl.builder()
127 .created(this.checkValidTimeHeader(context.getHeader(prefix + CREATED), Duration.ofMillis(0)))
128 .ttl(this.checkValidTimeHeader(context.getHeader(prefix + TTL), Duration.of(10, ChronoUnit.YEARS)));
129
130 if (name != null) result.name(name);
131 if (id != null) result.id(id);
132 if (response != null) result.responseChannel(response);
133
134 return result.build();
135 }
136
137 private String checkValidHeader(final String value) {
138 if (value == null || "".equals(value)) {
139 return null;
140 }
141
142 return value;
143 }
144
145 private OffsetDateTime checkValidTimeHeader(final String value, final TemporalAmount futureOffset) {
146 try {
147 return OffsetDateTime.parse(value);
148 } catch (final DateTimeParseException e) {
149 return OffsetDateTime.now(ZoneId.of("UTC")).plus(futureOffset);
150 }
151 }
152
153
154 private void prepareMDC(final WorkflowInfoImpl info) {
155 MDC.put(WORKFLOW_USER, info.getUser());
156
157 this.putMDC(info.getWorkflow(), WORKFLOW_PREFIX);
158 this.putMDC(info.getAction(), ACTION_PREFIX);
159 this.putMDC(info.getCall(), CALL_PREFIX);
160 }
161
162 private void putMDC(final WorkflowDetailInfoImpl info, final String workflowPrefix) {
163 MDC.put(workflowPrefix + NAME, info.getName());
164 MDC.put(workflowPrefix + ID, info.getId());
165 MDC.put(workflowPrefix + CREATED, info.getCreated().toString());
166 MDC.put(workflowPrefix + TTL, info.getTtl().toString());
167 MDC.put(workflowPrefix + RESPONSE, info.getResponseChannel());
168 }
169
170
171 private void filter(final HttpServletRequest request, final HttpServletResponse context) {
172 this.provider.getWorkflowInfo().ifPresentOrElse(
173 info -> {
174 log.trace(
175 "Removing workflow data from request. workflow='{}', action='{}', call='{}', user='{}'",
176 info.getWorkflow().getId(),
177 info.getAction().getId(),
178 info.getCall().getId(),
179 info.getUser()
180 );
181
182 this.unsetWorkflowInfoInContext(request);
183 this.provider.unregisterWorkflowInfo();
184
185 this.removeMDC();
186 },
187 () -> {
188 log.trace("No workflow data to remove from request.");
189 }
190 );
191 }
192
193 private void unsetWorkflowInfoInContext(final HttpServletRequest requestContext) {
194 requestContext.setAttribute(WORKFLOW_DATA, null);
195 }
196
197 private void removeMDC() {
198 MDC.remove(WORKFLOW_USER);
199 this.removeMDC(WORKFLOW_PREFIX);
200 this.removeMDC(ACTION_PREFIX);
201 this.removeMDC(CALL_PREFIX);
202 }
203
204 private void removeMDC(final String prefix) {
205 MDC.remove(prefix + NAME);
206 MDC.remove(prefix + ID);
207 MDC.remove(prefix + CREATED);
208 MDC.remove(prefix + TTL);
209 MDC.remove(prefix + RESPONSE);
210 }
211 }