001/** 002The contents of this file are subject to the Mozilla Public License Version 1.1 003(the "License"); you may not use this file except in compliance with the License. 004You may obtain a copy of the License at http://www.mozilla.org/MPL/ 005Software distributed under the License is distributed on an "AS IS" basis, 006WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the 007specific language governing rights and limitations under the License. 008 009The Original Code is "AbstractMessage.java". Description: 010"A default implementation of Message" 011 012The Initial Developer of the Original Code is University Health Network. Copyright (C) 0132001. All Rights Reserved. 014 015Contributor(s): ______________________________________. 016 017Alternatively, the contents of this file may be used under the terms of the 018GNU General Public License (the �GPL�), in which case the provisions of the GPL are 019applicable instead of those above. If you wish to allow use of your version of this 020file only under the terms of the GPL and not to allow others to use your version 021of this file under the MPL, indicate your decision by deleting the provisions above 022and replace them with the notice and other provisions required by the GPL License. 023If you do not delete the provisions above, a recipient may use your version of 024this file under either the MPL or the GPL. 025 026*/ 027 028package ca.uhn.hl7v2.model; 029 030import java.io.IOException; 031import java.util.Date; 032import java.util.GregorianCalendar; 033import java.util.Map; 034import java.util.regex.Matcher; 035import java.util.regex.Pattern; 036 037import ca.uhn.hl7v2.AcknowledgmentCode; 038import ca.uhn.hl7v2.HL7Exception; 039import ca.uhn.hl7v2.Location; 040import ca.uhn.hl7v2.Version; 041import ca.uhn.hl7v2.model.primitive.CommonTS; 042import ca.uhn.hl7v2.parser.DefaultModelClassFactory; 043import ca.uhn.hl7v2.parser.ModelClassFactory; 044import ca.uhn.hl7v2.parser.Parser; 045import ca.uhn.hl7v2.parser.PipeParser; 046import ca.uhn.hl7v2.util.ArrayUtil; 047import ca.uhn.hl7v2.util.ReflectionUtil; 048import ca.uhn.hl7v2.util.StringUtil; 049import ca.uhn.hl7v2.util.Terser; 050import ca.uhn.hl7v2.validation.ValidationContext; 051 052/** 053 * A default implementation of Message. 054 * @author Bryan Tripp (bryan_tripp@sourceforge.net) 055 */ 056@SuppressWarnings("serial") 057public abstract class AbstractMessage extends AbstractGroup implements Message { 058 059 private static final Pattern ourVersionPattern = Pattern.compile("\\.(v2[0-9][0-9]?)\\."); 060 private String myVersion; 061 private transient Parser myParser; 062 063 /** 064 * @param theFactory factory for model classes (e.g. group, segment) for this message 065 */ 066 public AbstractMessage(ModelClassFactory theFactory) { 067 super(null, theFactory); 068 } 069 070 /** 071 * Returns this Message object. 072 */ 073 public Message getMessage() { 074 return this; 075 } 076 077 public Group getParent() { 078 return this; 079 } 080 081 /** 082 * Returns the version number. This default implementation inspects 083 * this.getClass().getName(). This should be overridden if you are putting 084 * a custom message definition in your own package, or it will default. 085 * @see Message#getVersion() 086 * 087 * @return lowest available version if not obvious from package name 088 */ 089 public String getVersion() { 090 if (myVersion != null) { 091 return myVersion; 092 } 093 094 String version = null; 095 Pattern p = ourVersionPattern; 096 Matcher m = p.matcher(this.getClass().getName()); 097 if (m.find()) { 098 String verFolder = m.group(1); 099 if (verFolder.length() > 0) { 100 char[] chars = verFolder.toCharArray(); 101 StringBuilder buf = new StringBuilder(); 102 for (int i = 1; i < chars.length; i++) { //start at 1 to avoid the 'v' 103 buf.append(chars[i]); 104 if (i < chars.length - 1) buf.append('.'); 105 } 106 version = buf.toString(); 107 } 108 } 109 110 if (version == null) 111 version = Version.lowestAvailableVersion().getVersion(); 112 113 myVersion = version; 114 return version; 115 } 116 117 /** 118 * Returns the set of validation rules that applied to this message. If the parser 119 * was set to "not-validating", this method returns null 120 * 121 * @return the set of validation rules that applied to this message 122 */ 123 public ValidationContext getValidationContext() { 124 if (getParser() == null || !getParser().getParserConfiguration().isValidating()) return null; 125 return getParser().getHapiContext().getValidationContext(); 126 } 127 128 129 /** 130 * {@inheritDoc } 131 */ 132 public Character getFieldSeparatorValue() throws HL7Exception { 133 Segment firstSegment = (Segment) get(getNames()[0]); 134 Primitive value = (Primitive) firstSegment.getField(1, 0); 135 String valueString = value.getValue(); 136 if (valueString == null || valueString.length() == 0) { 137 return null; 138 } 139 return valueString.charAt(0); 140 } 141 142 143 /** 144 * {@inheritDoc } 145 */ 146 public String getEncodingCharactersValue() throws HL7Exception { 147 Segment firstSegment = (Segment) get(getNames()[0]); 148 Primitive value = (Primitive) firstSegment.getField(2, 0); 149 return value.getValue(); 150 } 151 152 153 /** 154 * <p>Sets the parser to be used when parse/encode methods are called on this 155 * Message, as well as its children. It is recommended that if these methods 156 * are going to be called, a parser be supplied with the validation context 157 * wanted. Where possible, the parser should be reused for best performance, 158 * unless thread safety is an issue.</p> 159 * 160 * <p>Note that not all parsers can be used. As of version 1.0, only {@link PipeParser} 161 * supports this functionality</p> 162 * 163 * <p>Serialization note: The message parser is marked as transient, so it will not 164 * survive serialization.</p> 165 */ 166 public void setParser(Parser parser) { 167 if (parser == null) { 168 throw new NullPointerException("Value may not be null"); 169 } 170 171 myParser = parser; 172 } 173 174 175 /** 176 * <p>Returns the parser to be used when parse/encode methods are called on this 177 * Message, as well as its children. The default value is a new {@link PipeParser}.</p> 178 * 179 * <p>Serialization note: The message parser is marked as transient, so it will not 180 * survive serialization.</p> 181 */ 182 public Parser getParser() { 183 if (myParser == null) { 184 myParser = new PipeParser(); 185 } 186 187 return myParser; 188 } 189 190 191 /** 192 * {@inheritDoc } 193 */ 194 public void parse(String string) throws HL7Exception { 195 clear(); 196 getParser().parse(this, string); 197 } 198 199 200 /** 201 * {@inheritDoc } 202 */ 203 public String encode() throws HL7Exception { 204 return getParser().encode(this); 205 } 206 207 /** 208 * {@inheritDoc } 209 */ 210 public Message generateACK() throws HL7Exception, IOException { 211 return generateACK(AcknowledgmentCode.AA, null); 212 } 213 214 /** 215 * {@inheritDoc } 216 * @deprecated 217 */ 218 public Message generateACK(String theAcknowledgementCode, HL7Exception theException) throws HL7Exception, IOException { 219 AcknowledgmentCode theCode = theAcknowledgementCode == null ? 220 AcknowledgmentCode.AA : 221 AcknowledgmentCode.valueOf(theAcknowledgementCode); 222 return generateACK(theCode, theException); 223 } 224 225 /** 226 * {@inheritDoc } 227 */ 228 public Message generateACK(AcknowledgmentCode theAcknowledgementCode, HL7Exception theException) throws HL7Exception, IOException { 229 if (theException != null && theException.getResponseMessage() != null) { 230 return theException.getResponseMessage(); 231 } 232 Message out = instantiateACK(); 233 out.setParser(getParser()); 234 fillResponseHeader(out, theAcknowledgementCode); 235 if (theException != null) { 236 theException.populateResponse(out, theAcknowledgementCode, 0); 237 } 238 return out; 239 } 240 241 private Message instantiateACK() throws HL7Exception { 242 ModelClassFactory mcf = getParser() != null ? 243 getParser().getFactory() : 244 new DefaultModelClassFactory(); 245 Version version = Version.versionOf(getVersion()); 246 Message out = null; 247 if (version != null && version.available()) { 248 Class<? extends Message> clazz = mcf.getMessageClass("ACK", version.getVersion(), false); 249 if (clazz != null) { 250 out = ReflectionUtil.instantiateMessage(clazz, mcf); 251 } 252 } 253 if (out == null) { 254 out = new GenericMessage.UnknownVersion(mcf); 255 } 256 257 if (out instanceof GenericMessage) { 258 if (!ArrayUtil.contains(out.getNames(), "MSA")) { 259 out.addNonstandardSegment("MSA"); 260 } 261 if (!ArrayUtil.contains(out.getNames(), "ERR")) { 262 out.addNonstandardSegment("ERR"); 263 } 264 } 265 266 return out; 267 } 268 269 /** 270 * Populates certain required fields in a response message header, using 271 * information from the corresponding inbound message. The current time is 272 * used for the message time field, and <code>MessageIDGenerator</code> is 273 * used to create a unique message ID. Version and message type fields are 274 * not populated. 275 * 276 * @param out outgoing message to be populated 277 * @param code acknowledgment code 278 * @return outgoing message 279 * @throws HL7Exception if header cannot be filled 280 * @throws IOException if message ID could not be generated 281 */ 282 public Message fillResponseHeader(Message out, AcknowledgmentCode code) 283 throws HL7Exception, IOException { 284 Segment mshIn = (Segment) get("MSH"); 285 Segment mshOut = (Segment) out.get("MSH"); 286 287 // get MSH data from incoming message ... 288 String fieldSep = Terser.get(mshIn, 1, 0, 1, 1); 289 String encChars = Terser.get(mshIn, 2, 0, 1, 1); 290 String procID = Terser.get(mshIn, 11, 0, 1, 1); 291 292 // populate outbound MSH using data from inbound message ... 293 Terser.set(mshOut, 1, 0, 1, 1, fieldSep); 294 Terser.set(mshOut, 2, 0, 1, 1, encChars); 295 GregorianCalendar now = new GregorianCalendar(); 296 now.setTime(new Date()); 297 Terser.set(mshOut, 7, 0, 1, 1, CommonTS.toHl7TSFormat(now)); 298 Terser.set(mshOut, 9, 0, 1, 1, "ACK"); 299 Terser.set(mshOut, 9, 0, 2, 1, Terser.get(mshIn, 9, 0, 2, 1)); 300 String v = mshOut.getMessage().getVersion(); 301 if (v != null) { 302 Version version = Version.versionOf(v); 303 if (version != null && !Version.V25.isGreaterThan(version)) { 304 Terser.set(mshOut, 9, 0, 3, 1, "ACK"); 305 } 306 } 307 Terser.set(mshOut, 10, 0, 1, 1, mshIn.getMessage().getParser().getParserConfiguration().getIdGenerator().getID()); 308 Terser.set(mshOut, 11, 0, 1, 1, procID); 309 310 String versionId = Terser.get(mshIn, 12, 0, 1, 1); 311 if (StringUtil.isBlank(versionId)) { 312 versionId = Version.highestAvailableVersionOrDefault().getVersion(); 313 } 314 Terser.set(mshOut, 12, 0, 1, 1, versionId); 315 316 // revert sender and receiver 317 Terser.set(mshOut, 3, 0, 1, 1, Terser.get(mshIn, 5, 0, 1, 1)); 318 Terser.set(mshOut, 4, 0, 1, 1, Terser.get(mshIn, 6, 0, 1, 1)); 319 Terser.set(mshOut, 5, 0, 1, 1, Terser.get(mshIn, 3, 0, 1, 1)); 320 Terser.set(mshOut, 6, 0, 1, 1, Terser.get(mshIn, 4, 0, 1, 1)); 321 322 // fill MSA for the happy case 323 Segment msaOut = (Segment) out.get("MSA"); 324 Terser.set(msaOut, 1, 0, 1, 1, code.name()); 325 Terser.set(msaOut, 2, 0, 1, 1, Terser.get(mshIn, 10, 0, 1, 1)); 326 return out; 327 } 328 329 330 /** 331 * Provides an overview of the type and structure of this message 332 */ 333 @Override 334 public String toString() { 335 try { 336 return encode(); 337 } catch (HL7Exception e) { 338 return (getClass().getName() + " - Failed to create toString(): " + e.getMessage()); 339 } 340 } 341 342 /** 343 * {@inheritDoc} 344 */ 345 public String printStructure() throws HL7Exception { 346 StringBuilder builder = new StringBuilder(); 347 appendStructureDescription(builder, 0, false, false, true, true, true); 348 return builder.toString(); 349 } 350 351 /** 352 * Prints the message structure in a similar way to {@link #printStructure()} but 353 * optionally excludes elements with no contents. 354 */ 355 public String printStructure(boolean includeEmptyElements) throws HL7Exception { 356 StringBuilder builder = new StringBuilder(); 357 appendStructureDescription(builder, 0, false, false, true, true, includeEmptyElements); 358 return builder.toString(); 359 } 360 361 /** 362 * Quickly initializes this message with common values in the first (MSH) segment. 363 * 364 * <p> 365 * Settings include: 366 * <ul> 367 * <li>MSH-1 (Field Separator) is set to "|"</li> 368 * <li>MSH-2 (Encoding Characters) is set to "^~\&"</li> 369 * <li>MSH-7 (Date/Time of Message) is set to current time</li> 370 * <li>MSH-10 (Control ID) is populated using next value generated by a 371 * {@link ca.uhn.hl7v2.util.idgenerator.IDGenerator IDGenerator}</li> 372 * </ul> 373 * </p> 374 * 375 * @param messageCode The message code (aka message type) to insert into MSH-9-1. Example: "ADT" 376 * @param messageTriggerEvent The message trigger event to insert into MSG-9-2. Example: "A01" 377 * @param processingId The message processing ID to insert into MSH-11. Examples: "T" (for TEST) or "P" for (PRODUCTION) 378 * 379 * @throws IOException If the message ID generation fails for some reason 380 * @throws HL7Exception If the message rejects any of the values which are generated to setting 381 */ 382 public void initQuickstart(String messageCode, String messageTriggerEvent, String processingId) throws HL7Exception, IOException { 383 Segment msh = (Segment) get("MSH"); 384 Version version = Version.versionOf(getVersion()); 385 Terser.set(msh, 1, 0, 1, 1, "|"); 386 Terser.set(msh, 2, 0, 1, 1, Version.V27.isGreaterThan(version) ? 387 "^~\\&" : "^~\\&#"); 388 GregorianCalendar now = new GregorianCalendar(); 389 Terser.set(msh, 7, 0, 1, 1, CommonTS.toHl7TSFormat(now)); 390 Terser.set(msh, 9, 0, 1, 1, messageCode); 391 Terser.set(msh, 9, 0, 2, 1, messageTriggerEvent); 392 Terser.set(msh, 10, 0, 1, 1, getParser().getParserConfiguration().getIdGenerator().getID()); 393 Terser.set(msh, 11, 0, 1, 1, processingId); 394 Terser.set(msh, 12, 0, 1, 1, getVersion()); 395 396 // Add structure information if version is 2.4 or better 397 if (!Version.V24.isGreaterThan(version)) { 398 if (this instanceof SuperStructure) { 399 Map<String, String> eventMap = new DefaultModelClassFactory().getEventMapForVersion(version); 400 if (StringUtil.isNotBlank(messageCode) && StringUtil.isNotBlank(messageTriggerEvent)) { 401 String structure = eventMap.get(messageCode + "_" + messageTriggerEvent); 402 Terser.set(msh, 9, 0, 3, 1, structure); 403 } 404 } else { 405 String className = getClass().getName(); 406 int lastIndexOf = className.lastIndexOf('.'); 407 className = className.substring(lastIndexOf + 1); 408 if (className.matches("[A-Z]{3}_[A-Z0-9]{3}")) { 409 Terser.set(msh, 9, 0, 3, 1, className); 410 } 411 } 412 } 413 414 } 415 416 @Override 417 public boolean accept(MessageVisitor visitor, Location location) throws HL7Exception { 418 if (visitor.start(this)) { 419 visitNestedStructures(visitor, location); 420 } 421 return visitor.end(this); 422 } 423 424 425}