View Javadoc
1   /*
2    * Copyright 2012 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.openehealth.ipf.commons.ihe.xds.core.metadata;
17  
18  import ca.uhn.hl7v2.HL7Exception;
19  import ca.uhn.hl7v2.model.Composite;
20  import ca.uhn.hl7v2.model.Primitive;
21  import ca.uhn.hl7v2.model.Type;
22  import ca.uhn.hl7v2.model.v25.datatype.*;
23  import ca.uhn.hl7v2.parser.DefaultEscaping;
24  import ca.uhn.hl7v2.parser.EncodingCharacters;
25  import ca.uhn.hl7v2.parser.Escaping;
26  import org.apache.commons.lang3.StringUtils;
27  import java.util.*;
28  
29  /**
30   * A renderer of HL7 v2 elements which considers XDS-specific
31   * requirements regarding required and prohibited fields
32   * as prescribed in the ITI TF Volume 3 Chapter 4.
33   *
34   * @author Dmytro Rud
35   */
36  public abstract class XdsHl7v2Renderer {
37  
38      /**
39       * Encoding characters for HL7 v2 messages.
40       */
41      public static final EncodingCharacters ENCODING_CHARACTERS =
42              new EncodingCharacters('|', '^', '~', '\\', '&', '#');
43      public static final Escaping ESCAPING = new DefaultEscaping();
44  
45      /**
46       * Map from HL7 class name to indices of fields allowed for rendering.
47       * An optional modifier is the class name of the root XDS model object.
48       * So the type of the key is a tuple [HL7 class name, XDS class name].
49       * <p>
50       * Querying algorithm when rendering an HL7 element C which is
51       * contained (directly or indirectly) in an XDS model object T:
52       * <ol>
53       *      <li> Try to retrieve indices for [C class name, null].
54       *      <li> When step 1 fails, try to retrieve indices for [C class name, T class name].
55       *      <li> When step 2 fails, assume that all fields of C are allowed.
56       * </ol>
57       * <p>
58       * Note that the indices are one-based, as in the HL7 and IHE documentation.
59       */
60      private static final Map<String, Collection<Integer>> INCLUSIONS = new HashMap<>();
61  
62      private static void addInclusion(
63              Class<? extends Composite> hl7Class,
64              Class<? extends Hl7v2Based> xdsClass,
65              int... fieldNumbers)
66      {
67          Collection<Integer> collection = new HashSet<>(fieldNumbers.length);
68          for (int number : fieldNumbers) {
69              collection.add(number - 1);
70          }
71          StringBuilder key = new StringBuilder(hl7Class.getSimpleName());
72          if (xdsClass != null) {
73              key.append('\n').append(xdsClass.getSimpleName());
74          }
75          INCLUSIONS.put(key.toString(), collection);
76      }
77  
78      static {
79          addInclusion(CE.class,  null,                        1, 3);
80          addInclusion(CX.class,  Identifiable.class,          1, 4);
81          addInclusion(CX.class,  ReferenceId.class,           1, 4, 5);
82          addInclusion(HD.class,  AssigningAuthority.class,    2, 3);
83          addInclusion(HD.class,  CXiAssigningAuthority.class, 1, 2, 3);
84          addInclusion(HD.class,  Identifiable.class,          2, 3);
85          addInclusion(HD.class,  ReferenceId.class,           1, 2, 3);
86          addInclusion(XON.class, null,                        1, 6, 10);
87          addInclusion(XTN.class, null,                        2, 3, 4, 5, 6, 7, 8, 12);
88      }
89  
90  
91      private XdsHl7v2Renderer() {
92          throw new IllegalStateException("cannot instantiate helper class");
93      }
94  
95      public static boolean isEmpty(Hl7v2Based hl7v2based) {
96          try {
97              return isEmpty(hl7v2based.getHapiObject(), "\n" + hl7v2based.getClass().getSimpleName());
98          } catch (HL7Exception e) {
99              throw new RuntimeException(e);
100         }
101     }
102 
103     private static boolean isEmpty(Composite composite, String keyModifier) throws HL7Exception {
104         Type[] fields = composite.getComponents();
105 
106         String key = composite.getClass().getSimpleName();
107         Collection<Integer> inclusions = INCLUSIONS.get(key);
108         if (inclusions == null) {
109             inclusions = INCLUSIONS.get(key + keyModifier);
110         }
111 
112         for (int i = 0; i < fields.length; ++i) {
113             if ((inclusions == null) || inclusions.contains(i)) {
114                 if (fields[i] instanceof Composite) {
115                     if (!isEmpty((Composite) fields[i], keyModifier)) {
116                         return false;
117                     }
118                 } else if (fields[i] instanceof Primitive) {
119                     if (!fields[i].isEmpty()) {
120                         return false;
121                     }
122                 } else {
123                     // actually, this line should be unreachable
124                     throw new IllegalStateException("Don't know how to handle " + fields[i]);
125                 }
126             }
127         }
128 
129         return true;
130     }
131 
132     /**
133      * Encodes the given HL7-based XDS model object using specific
134      * rules regarding required and prohibited HL7 fields.
135      * @param hl7v2based
136      *      source HL7-based XDS object model to be rendered.
137      * @return
138      *      String representation of the given HL7 v2 composite field.
139      */
140     public static String encode(Hl7v2Based hl7v2based) {
141         return encodeComposite(
142                 hl7v2based.getHapiObject(),
143                 "\n" + hl7v2based.getClass().getSimpleName(),
144                 ENCODING_CHARACTERS.getComponentSeparator());
145     }
146 
147 
148     private static String encodeComposite(Composite composite, String keyModifier, char delimiter) {
149         StringBuilder sb = new StringBuilder();
150         Type[] fields = composite.getComponents();
151 
152         String key = composite.getClass().getSimpleName();
153         Collection<Integer> inclusions = INCLUSIONS.get(key);
154         if (inclusions == null) {
155             inclusions = INCLUSIONS.get(key + keyModifier);
156         }
157 
158         for (int i = 0; i < fields.length; ++i) {
159             if ((inclusions == null) || inclusions.contains(i)) {
160                 if (fields[i] instanceof Composite) {
161                     sb.append(encodeComposite((Composite) fields[i], keyModifier, ENCODING_CHARACTERS.getSubcomponentSeparator()));
162                 } else if (fields[i] instanceof Primitive) {
163                     sb.append(encodePrimitive((Primitive) fields[i]));
164                 } else {
165                     // actually, this line should be unreachable
166                     throw new IllegalStateException("Don't know how to handle " + fields[i]);
167                 }
168             }
169             sb.append(delimiter);
170         }
171 
172         return StringUtils.stripEnd(sb.toString(), String.valueOf(delimiter));
173     }
174 
175 
176     private static String encodePrimitive(Primitive p) {
177         String value = p.getValue();
178         return (value == null) ? "" : ESCAPING.escape(value, ENCODING_CHARACTERS);
179     }
180 
181 }