1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.openehealth.ipf.commons.ihe.hl7v2;
17
18 import ca.uhn.hl7v2.ErrorCode;
19 import ca.uhn.hl7v2.HL7Exception;
20 import ca.uhn.hl7v2.HapiContext;
21 import ca.uhn.hl7v2.Version;
22 import ca.uhn.hl7v2.model.Message;
23 import ca.uhn.hl7v2.model.Segment;
24 import ca.uhn.hl7v2.parser.Parser;
25 import ca.uhn.hl7v2.util.Terser;
26 import lombok.Getter;
27 import org.apache.commons.lang3.ArrayUtils;
28 import org.apache.commons.lang3.StringUtils;
29 import org.openehealth.ipf.commons.ihe.core.TransactionConfiguration;
30 import org.openehealth.ipf.commons.ihe.core.atna.AuditStrategy;
31 import org.openehealth.ipf.commons.ihe.hl7v2.audit.MllpAuditDataset;
32 import org.openehealth.ipf.modules.hl7.HL7v2Exception;
33 import org.openehealth.ipf.modules.hl7.message.MessageUtils;
34
35 import java.util.*;
36
37 import static org.apache.commons.lang3.Validate.*;
38
39
40
41
42
43
44 public class Hl7v2TransactionConfiguration<T extends MllpAuditDataset> extends TransactionConfiguration<T> {
45
46 private static class Definition {
47 private final Set<String> triggerEvents;
48 private final boolean auditable;
49 private final boolean responseContinuable;
50
51 Definition(String triggerEventsString, boolean auditable, boolean responseContinuable) {
52 this.triggerEvents = new HashSet<>(Arrays.asList(StringUtils.split(triggerEventsString, ' ')));
53 this.auditable = auditable;
54 this.responseContinuable = responseContinuable;
55 }
56
57 boolean isAllowedTriggerEvent(String triggerEvent) {
58 return triggerEvents.contains("*") || triggerEvents.contains(triggerEvent);
59 }
60 }
61
62
63 @Getter private final String sendingApplication;
64 @Getter private final String sendingFacility;
65
66 @Getter private final ErrorCode requestErrorDefaultErrorCode;
67 @Getter private final ErrorCode responseErrorDefaultErrorCode;
68
69 @Getter private final HapiContext hapiContext;
70 @Getter private final String[] allowedRequestMessageTypes;
71 @Getter private final String[] allowedRequestTriggerEvents;
72 @Getter private final String[] allowedResponseMessageTypes;
73 @Getter private final String[] allowedResponseTriggerEvents;
74 @Getter private final Version[] hl7Versions;
75
76
77 private final Map<Boolean, Map<String, Definition>> definitions;
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104 public Hl7v2TransactionConfiguration(
105 String name,
106 String description,
107 boolean isQuery,
108 AuditStrategy<T> clientAuditStrategy,
109 AuditStrategy<T> serverAuditStrategy,
110 Version[] hl7Versions,
111 String sendingApplication,
112 String sendingFacility,
113 ErrorCode requestErrorDefaultErrorCode,
114 ErrorCode responseErrorDefaultErrorCode,
115 String[] allowedRequestMessageTypes,
116 String[] allowedRequestTriggerEvents,
117 String[] allowedResponseMessageTypes,
118 String[] allowedResponseTriggerEvents,
119 boolean[] auditabilityFlags,
120 boolean[] responseContinuabilityFlags,
121 HapiContext hapiContext)
122 {
123 super(name, description, isQuery, clientAuditStrategy, serverAuditStrategy);
124
125 notNull(hl7Versions);
126 notNull(sendingApplication);
127 notNull(sendingFacility);
128
129 noNullElements(allowedRequestMessageTypes);
130 noNullElements(allowedRequestTriggerEvents);
131 noNullElements(allowedResponseMessageTypes);
132 noNullElements(allowedResponseTriggerEvents);
133 notNull(hapiContext);
134
135 notEmpty(allowedRequestMessageTypes);
136 isTrue(allowedRequestMessageTypes.length == allowedRequestTriggerEvents.length);
137 isTrue(allowedRequestMessageTypes.length == allowedResponseMessageTypes.length);
138 isTrue(allowedRequestMessageTypes.length == allowedResponseTriggerEvents.length);
139 if (auditabilityFlags != null) {
140 isTrue(allowedRequestMessageTypes.length == auditabilityFlags.length);
141 }
142 if (responseContinuabilityFlags != null) {
143 isTrue(allowedRequestMessageTypes.length == responseContinuabilityFlags.length);
144 }
145
146
147
148 this.hl7Versions = hl7Versions;
149 this.allowedRequestMessageTypes = allowedRequestMessageTypes;
150 this.allowedRequestTriggerEvents = allowedRequestTriggerEvents;
151 this.allowedResponseMessageTypes = allowedResponseMessageTypes;
152 this.allowedResponseTriggerEvents = allowedResponseTriggerEvents;
153 this.sendingApplication = sendingApplication;
154 this.sendingFacility = sendingFacility;
155
156 this.requestErrorDefaultErrorCode = requestErrorDefaultErrorCode;
157 this.responseErrorDefaultErrorCode = responseErrorDefaultErrorCode;
158
159 this.hapiContext = hapiContext;
160
161 this.definitions = new HashMap<>();
162 definitions.put(true, createDefinitionsMap(allowedRequestMessageTypes, allowedRequestTriggerEvents,
163 auditabilityFlags, responseContinuabilityFlags));
164 definitions.put(false, createDefinitionsMap(allowedResponseMessageTypes, allowedResponseTriggerEvents,
165 auditabilityFlags, responseContinuabilityFlags));
166
167 if (! definitions.get(false).containsKey("ACK")) {
168 definitions.get(false).put("ACK", new Definition("*", false, false));
169 }
170 }
171
172 public Hl7v2TransactionConfiguration(
173 String name,
174 String description,
175 boolean isQuery,
176 AuditStrategy<T> clientAuditStrategy,
177 AuditStrategy<T> serverAuditStrategy,
178 Version hl7Version,
179 String sendingApplication,
180 String sendingFacility,
181 ErrorCode requestErrorDefaultErrorCode,
182 ErrorCode responseErrorDefaultErrorCode,
183 String allowedRequestMessageType,
184 String allowedRequestTriggerEvent,
185 String allowedResponseMessageType,
186 String allowedResponseTriggerEvent,
187 boolean auditabilityFlag,
188 boolean responseContinuabilityFlag,
189 HapiContext hapiContext)
190 {
191 this(name, description, isQuery, clientAuditStrategy, serverAuditStrategy,
192 new Version[]{hl7Version}, sendingApplication, sendingFacility, requestErrorDefaultErrorCode, responseErrorDefaultErrorCode,
193 new String[]{allowedRequestMessageType}, new String[]{allowedRequestTriggerEvent},
194 new String[]{allowedResponseMessageType}, new String[]{allowedResponseTriggerEvent},
195 new boolean[]{auditabilityFlag},
196 new boolean[]{responseContinuabilityFlag},
197 hapiContext);
198 }
199
200 private static Map<String, Definition> createDefinitionsMap(
201 String[] allowedMessageTypes,
202 String[] allowedTriggerEvents,
203 boolean[] auditabilityFlags,
204 boolean[] responseContinuabilityFlags)
205 {
206 Map<String, Definition> result = new HashMap<>();
207 for (int i = 0; i < allowedMessageTypes.length; ++i) {
208 Definition definition = new Definition(
209 allowedTriggerEvents[i],
210 (auditabilityFlags != null) && auditabilityFlags[i],
211 (responseContinuabilityFlags != null) && responseContinuabilityFlags[i]);
212 result.put(allowedMessageTypes[i], definition);
213 }
214 return result;
215 }
216
217
218
219
220
221 public boolean isAuditable(String messageType) {
222 try {
223 Definition definition = getDefinitionForMessageType(messageType, true);
224 return definition.auditable;
225 } catch (Hl7v2AcceptanceException e) {
226 throw new IllegalArgumentException(e);
227 }
228
229 }
230
231
232
233
234
235
236
237
238
239
240 public boolean isContinuable(String messageType) {
241 try {
242 Definition definition = getDefinitionForMessageType(messageType, true);
243 return definition.responseContinuable;
244 } catch (Hl7v2AcceptanceException e) {
245 throw new IllegalArgumentException(e);
246 }
247 }
248
249
250
251
252
253
254 public boolean isDataStartSegment(List<String> segments, int index) {
255 return false;
256 }
257
258
259
260
261
262
263
264 public boolean isFooterStartSegment(List<String> segments, int index) {
265 return false;
266 }
267
268
269
270
271
272
273
274 public void checkRequestAcceptance(Message message) throws Hl7v2AcceptanceException {
275 checkMessageAcceptance(message, true);
276 }
277
278
279
280
281
282
283
284 public void checkResponseAcceptance(Message message) throws Hl7v2AcceptanceException {
285 checkMessageAcceptance(message, false);
286
287 try {
288 if (!ArrayUtils.contains(
289 new String[]{"AA", "AR", "AE", "CA", "CR", "CE"},
290 new Terser(message).get("MSA-1")))
291 {
292 throw new Hl7v2AcceptanceException("Bad response: missing or invalid MSA segment", ErrorCode.REQUIRED_FIELD_MISSING);
293 }
294 } catch (HL7Exception e) {
295 throw new Hl7v2AcceptanceException("Bad response: missing or invalid MSA segment", ErrorCode.APPLICATION_INTERNAL_ERROR);
296 }
297 }
298
299
300
301
302
303
304
305
306
307 public void checkMessageAcceptance(
308 Message message,
309 boolean isRequest) throws Hl7v2AcceptanceException {
310 try {
311 Segment msh = (Segment) message.get("MSH");
312 checkMessageAcceptance(
313 Terser.get(msh, 9, 0, 1, 1),
314 Terser.get(msh, 9, 0, 2, 1),
315 Terser.get(msh, 9, 0, 3, 1),
316 Terser.get(msh, 12, 0, 1, 1),
317 isRequest);
318 } catch (Hl7v2AcceptanceException e) {
319 throw e;
320 } catch (HL7Exception e) {
321 throw new Hl7v2AcceptanceException("Missing or invalid MSH segment: " + e.getMessage(), ErrorCode.APPLICATION_INTERNAL_ERROR);
322 }
323 }
324
325
326
327
328
329
330
331
332
333
334
335
336 public void checkMessageAcceptance(
337 String messageType,
338 String triggerEvent,
339 String messageStructure,
340 String version,
341 boolean isRequest) throws Hl7v2AcceptanceException
342 {
343 checkMessageVersion(version);
344
345 Definition definition = getDefinitionForMessageType(messageType, isRequest);
346
347 if (! definition.isAllowedTriggerEvent(triggerEvent)) {
348 throw new Hl7v2AcceptanceException("Invalid trigger event " + triggerEvent + ", must be one of " +
349 join(definition.triggerEvents), ErrorCode.UNSUPPORTED_EVENT_CODE);
350 }
351
352 if (!StringUtils.isEmpty(messageStructure)) {
353
354
355 String event = messageType + "_" + triggerEvent;
356 String expectedMessageStructure;
357 try {
358 expectedMessageStructure = hapiContext.getModelClassFactory().getMessageStructureForEvent(event, Version.versionOf(version));
359 } catch (HL7Exception e) {
360 throw new Hl7v2AcceptanceException("Acceptance check failed", ErrorCode.UNKNOWN_KEY_IDENTIFIER);
361 }
362
363
364 if ("QBP_ZV1".equals(event)) {
365 expectedMessageStructure = "QBP_Q21";
366 } else if ("RSP_ZV2".equals(event)) {
367 expectedMessageStructure = "RSP_ZV2";
368 }
369
370
371
372 boolean bothAreEqual = messageStructure.equals(expectedMessageStructure);
373 boolean bothAreAcks = (messageStructure.startsWith("ACK") && expectedMessageStructure != null && expectedMessageStructure.startsWith("ACK"));
374 if (!(bothAreEqual || bothAreAcks)) {
375 throw new Hl7v2AcceptanceException("Invalid message structure " + messageStructure +
376 ", must be " + expectedMessageStructure, ErrorCode.APPLICATION_INTERNAL_ERROR);
377 }
378 }
379 }
380
381 private Definition getDefinitionForMessageType(String messageType, boolean isRequest) throws Hl7v2AcceptanceException {
382 Definition definition = definitions.get(isRequest).get(messageType);
383 if (definition == null) {
384 definition = definitions.get(isRequest).get("*");
385 if (definition == null) {
386 throw new Hl7v2AcceptanceException("Invalid message type " + messageType + ", must be one of " +
387 join(definitions.get(isRequest).keySet()), ErrorCode.UNSUPPORTED_MESSAGE_TYPE);
388 }
389 }
390 return definition;
391 }
392
393 private void checkMessageVersion(String version) throws Hl7v2AcceptanceException {
394 Version messageVersion = Version.versionOf(version);
395 if (! ArrayUtils.contains(hl7Versions, messageVersion)) {
396 throw new Hl7v2AcceptanceException("Invalid HL7 version " + version + ", must be one of " + supportedVersions(hl7Versions),
397 ErrorCode.UNSUPPORTED_VERSION_ID);
398 }
399 }
400
401 private String supportedVersions(Version... hl7versions) {
402 StringBuilder builder = new StringBuilder();
403 for (Version v : hl7versions) {
404 builder.append(v.getVersion()).append(' ');
405 }
406 return builder.toString();
407 }
408
409 public Parser getParser() {
410 return getHapiContext().getPipeParser();
411 }
412
413 private static String join(Collection<String> collection) {
414 return StringUtils.join(collection, ' ');
415 }
416
417
418
419
420
421
422
423
424
425
426 public <M extends Message> M request(String messageType, String trigger) {
427 Message message = MessageUtils.makeMessage(
428 getHapiContext(), messageType, trigger, getHl7Versions()[0].getVersion());
429 try {
430 checkRequestAcceptance(message);
431 return (M) message;
432 } catch (Hl7v2AcceptanceException e) {
433 throw new HL7v2Exception(e);
434 }
435 }
436
437
438
439
440 public <M extends Message> M request(String trigger) {
441 return request(allowedRequestMessageTypes[0], trigger);
442 }
443
444
445
446
447
448 public <M extends Message> M request() {
449 return request(allowedRequestMessageTypes[0], allowedRequestTriggerEvents[0]);
450 }
451 }
452