11package uk .nhs .adaptors .gp2gp .gpc ;
22
33import lombok .RequiredArgsConstructor ;
4+ import lombok .SneakyThrows ;
45import lombok .extern .slf4j .Slf4j ;
56
67import org .apache .commons .lang3 .StringUtils ;
1112import org .springframework .beans .factory .annotation .Autowired ;
1213import org .springframework .stereotype .Service ;
1314
15+ import org .w3c .dom .Document ;
16+ import org .w3c .dom .Node ;
17+ import org .w3c .dom .NodeList ;
18+ import org .xml .sax .InputSource ;
19+ import org .xml .sax .SAXException ;
1420import uk .nhs .adaptors .gp2gp .common .configuration .Gp2gpConfiguration ;
1521import uk .nhs .adaptors .gp2gp .common .service .RandomIdGeneratorService ;
1622import uk .nhs .adaptors .gp2gp .ehr .EhrExtractStatusService ;
2329import uk .nhs .adaptors .gp2gp .mhs .model .Identifier ;
2430import uk .nhs .adaptors .gp2gp .mhs .model .OutboundMessage ;
2531
32+ import javax .xml .parsers .DocumentBuilder ;
33+ import javax .xml .parsers .DocumentBuilderFactory ;
34+ import javax .xml .transform .Transformer ;
35+ import javax .xml .transform .TransformerException ;
36+ import javax .xml .transform .TransformerFactory ;
37+ import javax .xml .transform .dom .DOMSource ;
38+ import javax .xml .transform .stream .StreamResult ;
39+ import javax .xml .xpath .XPathConstants ;
40+ import javax .xml .xpath .XPathExpressionException ;
41+ import javax .xml .xpath .XPathFactory ;
42+ import java .io .IOException ;
43+ import java .io .StringReader ;
44+ import java .io .StringWriter ;
2645import java .util .List ;
2746import java .util .Optional ;
2847import java .util .stream .Collectors ;
@@ -39,26 +58,28 @@ public class StructuredRecordMappingService {
3958 private final SupportedContentTypes supportedContentTypes ;
4059 private final EhrExtractStatusService ehrExtractStatusService ;
4160
61+ private DocumentBuilder documentBuilder ;
62+
4263 public static final String DEFAULT_ATTACHMENT_CONTENT_TYPE = "text/plain" ;
4364
4465 public List <OutboundMessage .ExternalAttachment > getExternalAttachments (Bundle bundle ) {
4566 return ResourceExtractor .extractResourcesByType (bundle , DocumentReference .class )
46- .filter (documentReference -> !qualifiesAsAbsentAttachment (documentReference ))
47- .map (this ::buildExternalAttachment )
48- .collect (Collectors .toList ());
67+ .filter (documentReference -> !qualifiesAsAbsentAttachment (documentReference ))
68+ .map (this ::buildExternalAttachment )
69+ .collect (Collectors .toList ());
4970 }
5071
5172 public List <OutboundMessage .ExternalAttachment > getAbsentAttachments (Bundle bundle ) {
5273 return ResourceExtractor .extractResourcesByType (bundle , DocumentReference .class )
53- .filter (this ::qualifiesAsAbsentAttachment )
54- .map (this ::buildExternalAttachment )
55- .collect (Collectors .toList ());
74+ .filter (this ::qualifiesAsAbsentAttachment )
75+ .map (this ::buildExternalAttachment )
76+ .collect (Collectors .toList ());
5677 }
5778
5879 private OutboundMessage .ExternalAttachment buildExternalAttachment (DocumentReference documentReference ) {
5980 var attachment = DocumentReferenceUtils .extractAttachment (documentReference );
6081 var documentId = messageContext .getIdMapper ()
61- .newId (ResourceType .DocumentReference , documentReference .getIdElement ());
82+ .newId (ResourceType .DocumentReference , documentReference .getIdElement ());
6283 var messageId = randomIdGeneratorService .createNewId ();
6384
6485 String contentType = DocumentReferenceUtils .extractContentType (attachment );
@@ -71,30 +92,30 @@ private OutboundMessage.ExternalAttachment buildExternalAttachment(DocumentRefer
7192 }
7293
7394 return OutboundMessage .ExternalAttachment .builder ()
74- .documentId (documentId )
75- .messageId (messageId )
76- .description (OutboundMessage .AttachmentDescription .builder ()
77- .fileName (fileName )
78- .contentType (contentType )
79- .compressed (false ) // always false for GPC documents
80- .largeAttachment (isLargeAttachment (attachment ))
81- .originalBase64 (false )
8295 .documentId (documentId )
83- .build ()
84- .toString ()
85- )
86- .url (extractUrl (documentReference ).orElse (null ))
87- .title (documentReference .getContentFirstRep ().getAttachment ().getTitle ())
88- .identifier (documentReference .getIdentifier ().stream ()
89- .map (identifier -> Identifier .builder ()
90- .system (identifier .getSystem ())
91- .value (identifier .getValue ())
92- .build ())
93- .collect (Collectors .toList ()))
94- .filename (fileName )
95- .originalDescription (documentReference .getDescription ())
96- .contentType (contentType )
97- .build ();
96+ .messageId (messageId )
97+ .description (OutboundMessage .AttachmentDescription .builder ()
98+ .fileName (fileName )
99+ .contentType (contentType )
100+ .compressed (false ) // always false for GPC documents
101+ .largeAttachment (isLargeAttachment (attachment ))
102+ .originalBase64 (false )
103+ .documentId (documentId )
104+ .build ()
105+ .toString ()
106+ )
107+ .url (extractUrl (documentReference ).orElse (null ))
108+ .title (documentReference .getContentFirstRep ().getAttachment ().getTitle ())
109+ .identifier (documentReference .getIdentifier ().stream ()
110+ .map (identifier -> Identifier .builder ()
111+ .system (identifier .getSystem ())
112+ .value (identifier .getValue ())
113+ .build ())
114+ .collect (Collectors .toList ()))
115+ .filename (fileName )
116+ .originalDescription (documentReference .getDescription ())
117+ .contentType (contentType )
118+ .build ();
98119 }
99120
100121 private boolean qualifiesAsAbsentAttachment (DocumentReference documentReference ) {
@@ -106,19 +127,19 @@ private boolean qualifiesAsAbsentAttachment(DocumentReference documentReference)
106127
107128 private static Optional <String > extractUrl (DocumentReference documentReference ) {
108129 return documentReference .getContent ().stream ()
109- .map (DocumentReference .DocumentReferenceContentComponent ::getAttachment )
110- .map (Attachment ::getUrl )
111- .peek (url -> {
112- if (StringUtils .isBlank (url )) {
113- LOGGER .warn ("Empty URL on DocumentReference {}" , documentReference .getIdElement ().getIdPart ());
114- }
115- })
116- .filter (StringUtils ::isNotBlank )
117- .reduce ((a , b ) -> {
118- throw new IllegalStateException (String .format (
119- "There is more than 1 Attachment on DocumentReference %s" ,
120- documentReference .getIdElement ().getIdPart ()));
121- });
130+ .map (DocumentReference .DocumentReferenceContentComponent ::getAttachment )
131+ .map (Attachment ::getUrl )
132+ .peek (url -> {
133+ if (StringUtils .isBlank (url )) {
134+ LOGGER .warn ("Empty URL on DocumentReference {}" , documentReference .getIdElement ().getIdPart ());
135+ }
136+ })
137+ .filter (StringUtils ::isNotBlank )
138+ .reduce ((a , b ) -> {
139+ throw new IllegalStateException (String .format (
140+ "There is more than 1 Attachment on DocumentReference %s" ,
141+ documentReference .getIdElement ().getIdPart ()));
142+ });
122143 }
123144
124145 private boolean isLargeAttachment (Attachment attachment ) {
@@ -127,20 +148,61 @@ private boolean isLargeAttachment(Attachment attachment) {
127148
128149 public String mapStructuredRecordToEhrExtractXml (GetGpcStructuredTaskDefinition structuredTaskDefinition , Bundle bundle ) {
129150 var ehrExtractTemplateParameters = ehrExtractMapper
130- .mapBundleToEhrFhirExtractParams (structuredTaskDefinition , bundle );
151+ .mapBundleToEhrFhirExtractParams (structuredTaskDefinition , bundle );
131152 String ehrExtractContent = ehrExtractMapper .mapEhrExtractToXml (ehrExtractTemplateParameters );
132153
133154 ehrExtractStatusService .saveEhrExtractMessageId (structuredTaskDefinition .getConversationId (),
134- ehrExtractTemplateParameters .getEhrExtractId ());
155+ ehrExtractTemplateParameters .getEhrExtractId ());
135156
136157 return outputMessageWrapperMapper .map (structuredTaskDefinition , ehrExtractContent );
137158 }
138159
139- public String buildSkeletonEhrExtractXml (
140- GetGpcStructuredTaskDefinition structuredTaskDefinition , Bundle structuredRecord , String documentId
160+ @ SneakyThrows
161+ public String buildSkeletonEhrExtractXml ( String realEhrExtract , String documentId
141162 ) {
142- String ehrExtractContent = ehrExtractMapper .buildSkeletonEhrExtract ( structuredTaskDefinition , structuredRecord , documentId );
163+ var ehrCompositionWithNarrativeStatement = ehrExtractMapper .buildEhrCompositionForSkeletonEhrExtract ( documentId );
143164
144- return outputMessageWrapperMapper .map (structuredTaskDefinition , ehrExtractContent );
165+ var documentBuilderFactory = DocumentBuilderFactory .newInstance ();
166+ documentBuilder = documentBuilderFactory .newDocumentBuilder ();
167+
168+ var document = documentBuilder .parse (new InputSource (new StringReader (realEhrExtract )));
169+ var ehrFolder = removeComponentsFromEhrFolder (document );
170+ insertSkeletonEhrCompositionIntoEhrFolder (ehrCompositionWithNarrativeStatement , ehrFolder );
171+
172+ return toString (document );
173+ }
174+
175+ private static String toString (Document document ) throws TransformerException {
176+ TransformerFactory tf = TransformerFactory .newInstance ();
177+ Transformer transformer = tf .newTransformer ();
178+ StringWriter writer = new StringWriter ();
179+
180+ transformer .transform (new DOMSource (document ), new StreamResult (writer ));
181+ return writer .getBuffer ().toString ();
182+ }
183+
184+ private void insertSkeletonEhrCompositionIntoEhrFolder (String ehrCompositionWithNarrativeStatement , Node ehrFolder )
185+ throws SAXException , IOException {
186+ var skeletonNode = documentBuilder .parse (new InputSource (new StringReader (ehrCompositionWithNarrativeStatement )));
187+ var skeletonNodeDocumentElement = skeletonNode .getDocumentElement ();
188+
189+ ehrFolder .getOwnerDocument ().adoptNode (skeletonNodeDocumentElement );
190+ ehrFolder .appendChild (skeletonNodeDocumentElement );
191+ }
192+
193+ private static Node removeComponentsFromEhrFolder (Document document ) throws XPathExpressionException {
194+ var xpathFactory = XPathFactory .newInstance ();
195+ var xpath = xpathFactory .newXPath ();
196+ var xPathExpression = "//EhrExtract//component//ehrFolder//component" ;
197+ var parentXPathExpression = "//EhrExtract//component//ehrFolder" ;
198+
199+ var componentNodes = (NodeList ) xpath .compile (xPathExpression ).evaluate (document , XPathConstants .NODESET );
200+ var parent = (Node ) xpath .compile (parentXPathExpression ).evaluate (document , XPathConstants .NODE );
201+
202+ for (var i = 0 ; i < componentNodes .getLength (); i ++) {
203+ componentNodes .item (i ).getParentNode ().removeChild (componentNodes .item (i ));
204+ }
205+
206+ return parent ;
145207 }
146208}
0 commit comments