001 package org.vmdb.hl7; 002 003 import java.util.*; 004 005 /** 006 * <p><Title:> ORDER_OBSERVATION Loop. </p> 007 * <p>Description: HL7 Network Connectivity For VMDB. </p> 008 * <p>Copyright: Copyright (c) 2002-2003. </p> 009 * <p>Company: Veterinary Medical Database (VMDB). </p> 010 * <p>The XML representation of HL7 2.x introduces the concepts of groups and lists 011 * that, while present in the construction rules of delimited HL7, are not explicitly 012 * named or labelled in the messages themselves. This class follows the XML 013 * version with explicitly constructed groups and lists 014 * which we've combined under the general term (borrowed from X12) of "loop."</p> 015 * <p>Most of the repetion and grouping facilitated by this "loop" logic is not 016 * used in the VMDB version of the ORU message but is maintained to retain full 017 * standard compliance and to allow extending this model without fear of losing 018 * interoperability.</p> 019 * <p>Loop and Segment Nesting: The outline below shows how the loops and minimum 020 * required segments nest. {OBX} indicates that any number of OBX segments may 021 * appear at this location.</p> 022 <pre> 023 MSH 024 PATIENT_RESULT 025 PATIENT 026 PID 027 PATIENT_VISIT 028 PV1 029 <b>ORDER_OBSERVATION</b> 030 [ORC] 031 OBR 032 OBSERVATION 033 OBX 034 </pre> 035 * <p>Besides maintaining the structure of the XML representation, this loop 036 * handles the task of keeping track of set id's and sub id's for the OBX segments 037 * (OBSERVATION Loops) contained within it.</p> 038 * @author Michael K. Martin 039 * @version 1.0 040 */ 041 042 public class ORDER_OBSERVATIONLoop extends HL7Loop { 043 private String sNm = "ORDER_OBSERVATION"; 044 private String sRl = "[ORC]OBR{[NTE]}[CTD]{OBSERVATION}[{FT1}]{[CTI]}"; 045 046 // Sequential id for obx's 047 private int iSetId; 048 // Sequential id for top obx followed by others that modify it 049 private int iTopSubId; 050 051 public ORDER_OBSERVATIONLoop() { 052 super(); 053 setName( sNm ); 054 setRule( sRl ); 055 iSetId = 1; 056 iTopSubId = 1; 057 } 058 059 /** 060 * Get the OBSERVATIONLoop loop that should be located in this loop. If the loop 061 * does not exist, take the necessary steps to create it in the appropriate 062 * location. 063 * @return OBSERVATIONLoop object from the correct location. 064 */ 065 public OBSERVATIONLoop getOBSERVATION() { 066 OBSERVATIONLoop loop = null; 067 if( vSegments == null ) vSegments = new Vector(); 068 for( Iterator i = vSegments.iterator(); i.hasNext(); ) { 069 HL7Object o = (HL7Object)i.next(); 070 if( o.getName().equals( "OBSERVATION" ) ) { 071 loop = (OBSERVATIONLoop)o; 072 break; 073 } 074 } 075 if( loop == null ) { 076 loop = new OBSERVATIONLoop(); 077 int iLoc = vSegments.size(); 078 // This little hack is to try to be sure we insert before 079 // either of these obscure segments should they exist. 080 if( findSegment("FT1") != null ) iLoc--; 081 if( findSegment("CTI") != null ) iLoc--; 082 vSegments.add(iLoc,loop); 083 } 084 return loop; 085 } 086 087 /** 088 * Get the OBR segment that should be located in this loop. If 089 * it does not exist, take the necessary steps to create it.<br><br> 090 * Note: The semantics here only work correctly for segments that exist singly 091 * in a message. We break with generic HL7 here in that only under VMDB are 092 * we restricted to a single patient per message. If we drop this limitation 093 * we will need to create more complex (addOBR) semantics. 094 * @return OBRSegment object located at the appropriate place in the loop. 095 */ 096 public OBRSegment getOBR() { 097 OBRSegment obr = (OBRSegment)findSegment( "OBR" ); 098 if( obr == null ) { 099 obr = new OBRSegment(); 100 obr.initialize(); 101 if( vSegments == null ) vSegments = new Vector(); 102 // locate after ORC if it exists. 103 int iLoc = (findSegment("ORC") == null) ? 0 : 1; 104 vSegments.add(iLoc,obr); 105 } 106 return obr; 107 } 108 109 /** 110 * Get the ORC segment that should be located in this loop. If 111 * it does not exist, take the necessary steps to create it.<br><br> 112 * Note: The semantics here only work correctly for segments that exist singly 113 * in a message. We break with generic HL7 here in that only under VMDB are 114 * we restricted to a single patient per message. If we drop this limitation 115 * we will need to create more complex (addOBR) semantics. 116 * @return OBRSegment object located at the appropriate place in the loop. 117 */ 118 public ORCSegment getORC() { 119 ORCSegment orc = (ORCSegment)findSegment( "ORC" ); 120 if( orc == null ) { 121 orc = new ORCSegment(); 122 orc.initialize(); 123 if( vSegments == null ) vSegments = new Vector(); 124 vSegments.add(0,orc); 125 } 126 return orc; 127 } 128 129 /** 130 * Add an OBX segment that should be located in this loop. If the loop 131 * does not exist, take the necessary steps to create it.<br> 132 * Note: Adding an OBX really means adding a whole OBSERVATION but 133 * since we usually omit the NTE segment, it is much more intuitive to add 134 * directly to the ORDER_OBSERVATION. If we add support for the NTE, it 135 * will need a method for locating the OBX it goes with. 136 * @return OBXSegment object newly created. 137 */ 138 public OBXSegment addOBX() { 139 OBSERVATIONLoop loop = new OBSERVATIONLoop(); 140 OBXSegment obx = loop.getOBX(); 141 obx.setSetId( Integer.toString( iSetId++ ) ); 142 obx.setSubId( Integer.toString( iTopSubId++ ) ); 143 if( vSegments == null ) vSegments = new Vector(); 144 int iLoc = vSegments.size(); 145 // This little hack is to try to be sure we insert before 146 // either of these obscure segments should they exist. 147 if( findSegment("FT1") != null ) iLoc--; 148 if( findSegment("CTI") != null ) iLoc--; 149 vSegments.add(iLoc,loop); 150 return obx; 151 } 152 153 /** 154 * Add an OBX segment that should be located in this loop. If the loop 155 * does not exist, take the necessary steps to create it. This should not 156 * normally be called by high-level code. Normally this is handled by 157 * modifyObservation(). 158 * @param sSubId String with explicitly defined subId 159 * @return OBXSegment object newly created. 160 */ 161 OBXSegment addOBX( String sSubId ) { 162 OBSERVATIONLoop loop = new OBSERVATIONLoop(); 163 OBXSegment obx = loop.getOBX(); 164 obx.setSetId( Integer.toString( iSetId++ ) ); 165 obx.setSubId( sSubId ); 166 if( vSegments == null ) vSegments = new Vector(); 167 int iLoc = vSegments.size(); 168 // This little hack is to try to be sure we insert before 169 // either of these obscure segments should they exist. 170 if( findSegment("FT1") != null ) iLoc--; 171 if( findSegment("CTI") != null ) iLoc--; 172 vSegments.add(iLoc,loop); 173 return obx; 174 } 175 176 /** 177 * Find all OBX segments with a given Observation Identifier as defined 178 * by a predefined CEElement object.<br> 179 * Note: This implementation <strong>only</strong> works because Java is 180 * garbage collected. A C++ port is going to have to find a better way 181 * to hold the collection of matched segment or perhaps create a specific 182 * derived Iterator class to walk the collection as the iterator is incremented. 183 * @param ceObsId CEElement with specific observation identifier. Almost 184 * always using a predefined constant from the Loinc class. 185 * @return Iterator over collection of OBX segments that match on 186 * observation identifier value. 187 */ 188 public Iterator findOBXSegments( CEElement ceObsId ) { 189 // vMatches will survive until the last reference to the returned 190 // iterator is out of scope. 191 Vector vMatches = new Vector(); // to hold references 192 if( vSegments != null ) { 193 for( Iterator i = vSegments.iterator(); i.hasNext(); ) { 194 HL7Object o = (HL7Object)i.next(); 195 if( o.getName().equals( "OBSERVATION" ) ) { 196 OBSERVATIONLoop loop = (OBSERVATIONLoop)o; 197 OBXSegment obx = loop.getOBX(); 198 if( obx.getObservationIdentifier().toString().equals( ceObsId.toString() ) ) { 199 vMatches.add( obx ); 200 } // End with correct obsId value 201 }// End is an OBX segment 202 }// End for each segment 203 }// End has segments 204 return vMatches.iterator(); 205 } 206 207 /** 208 * Create the sub id to be assigned the next modifier of the OBXSegment 209 * provided.<br><br> 210 * Based on the input OBX's subId, look for all modifiers and assign the next higher 211 * last digit to create the correct subId to assign the next obx that modifies 212 * this one. 213 * @param obxIn An OBXSegment who's subId field will be used to find all 214 * other obx's in this loop that modify it. 215 * @return String to be used as subId for the next modifier added. 216 */ 217 public String nextModifierSubId( OBXSegment obxIn ) { 218 // vMatches will survive until the last reference to the returned 219 // iterator is out of scope. 220 String sSubIdIn = obxIn.getSubId() + "."; 221 int iLen = sSubIdIn.length(); 222 int iModNo = 0; 223 if( vSegments != null ) { 224 for( Iterator i = vSegments.iterator(); i.hasNext(); ) { 225 HL7Object o = (HL7Object)i.next(); 226 if( o.getName().equals( "OBSERVATION" ) ) { 227 OBSERVATIONLoop loop = (OBSERVATIONLoop)o; 228 OBXSegment obx = loop.getOBX(); 229 String sSubId = obx.getSubId(); 230 // If the found obx has a subId, that starts with the current subId 231 // followed by a period and contains no more periods (ie modifies a modifier) 232 if( sSubId != null && 233 sSubIdIn.regionMatches( 0, sSubId, 0, iLen) && 234 sSubId.indexOf( '.',iLen ) == -1 ) { 235 String sModNo = sSubId.substring( iLen ); 236 try { 237 int iNewModNo = Integer.parseInt( sModNo ); 238 if( iNewModNo > iModNo ) 239 iModNo = iNewModNo; 240 } catch( NumberFormatException ne ) { 241 System.err.println( "Cannot parse " + sModNo + " as number" ); 242 ne.printStackTrace(); 243 } 244 245 } // End with correct obsId value 246 }// End is an OBX segment 247 }// End for each segment 248 }// End has segments 249 return sSubIdIn + Integer.toString(iModNo+1); 250 } 251 252 /** 253 * Find all OBX segments whos subId indicates they modify this OBX.<br><br> 254 * Note: This implementation <strong>only</strong> works because Java is 255 * garbage collected. A C++ port is going to have to find a better way 256 * to hold the collection of matched segment or perhaps create a specific 257 * derived Iterator class to walk the collection as the iterator is incremented. 258 * @param obxIn An OBXSegment whos subId field will be used to find all 259 * other obx's in this loop that modify it. 260 * @return Iterator over collection of OBX segments that modify the supplied 261 * obx. 262 */ 263 public Iterator findOBXModifiers( OBXSegment obxIn ) { 264 // vMatches will survive until the last reference to the returned 265 // iterator is out of scope. 266 String sSubIdIn = obxIn.getSubId() + "."; 267 int iLen = sSubIdIn.length(); 268 Vector vMatches = new Vector(); // to hold references 269 if( vSegments != null ) { 270 for( Iterator i = vSegments.iterator(); i.hasNext(); ) { 271 HL7Object o = (HL7Object)i.next(); 272 if( o.getName().equals( "OBSERVATION" ) ) { 273 OBSERVATIONLoop loop = (OBSERVATIONLoop)o; 274 OBXSegment obx = loop.getOBX(); 275 String sSubId = obx.getSubId(); 276 // If the found obx has a subId, that starts with the current subId 277 // followed by a period and contains no more periods (ie modifies a modifier) 278 if( sSubId != null && 279 sSubIdIn.regionMatches( 0, sSubId, 0, iLen ) && 280 sSubId.indexOf( '.',iLen ) == -1 ) { 281 vMatches.add( obx ); 282 } // End with correct obsId value 283 }// End is an OBX segment 284 }// End for each segment 285 }// End has segments 286 return vMatches.iterator(); 287 } 288 289 }// End class ORDER_OBSERVATIONLoop