Coverage Report - ca.uhn.hl7v2.model.AbstractMessage
 
Classes in this File Line Coverage Branch Coverage Complexity
AbstractMessage
77%
104/134
46%
27/58
2.619
 
 1  
 /**
 2  
 The contents of this file are subject to the Mozilla Public License Version 1.1 
 3  
 (the "License"); you may not use this file except in compliance with the License. 
 4  
 You may obtain a copy of the License at http://www.mozilla.org/MPL/ 
 5  
 Software distributed under the License is distributed on an "AS IS" basis, 
 6  
 WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the 
 7  
 specific language governing rights and limitations under the License. 
 8  
 
 9  
 The Original Code is "AbstractMessage.java".  Description: 
 10  
 "A default implementation of Message" 
 11  
 
 12  
 The Initial Developer of the Original Code is University Health Network. Copyright (C) 
 13  
 2001.  All Rights Reserved. 
 14  
 
 15  
 Contributor(s): ______________________________________. 
 16  
 
 17  
 Alternatively, the contents of this file may be used under the terms of the 
 18  
 GNU General Public License (the  �GPL�), in which case the provisions of the GPL are 
 19  
 applicable instead of those above.  If you wish to allow use of your version of this 
 20  
 file only under the terms of the GPL and not to allow others to use your version 
 21  
 of this file under the MPL, indicate your decision by deleting  the provisions above 
 22  
 and replace  them with the notice and other provisions required by the GPL License.  
 23  
 If you do not delete the provisions above, a recipient may use your version of 
 24  
 this file under either the MPL or the GPL. 
 25  
 
 26  
 */
 27  
 
 28  
 package ca.uhn.hl7v2.model;
 29  
 
 30  
 import java.io.IOException;
 31  
 import java.util.Date;
 32  
 import java.util.GregorianCalendar;
 33  
 import java.util.Map;
 34  
 import java.util.regex.Matcher;
 35  
 import java.util.regex.Pattern;
 36  
 
 37  
 import ca.uhn.hl7v2.AcknowledgmentCode;
 38  
 import ca.uhn.hl7v2.HL7Exception;
 39  
 import ca.uhn.hl7v2.Version;
 40  
 import ca.uhn.hl7v2.model.primitive.CommonTS;
 41  
 import ca.uhn.hl7v2.parser.DefaultModelClassFactory;
 42  
 import ca.uhn.hl7v2.parser.ModelClassFactory;
 43  
 import ca.uhn.hl7v2.parser.Parser;
 44  
 import ca.uhn.hl7v2.parser.PipeParser;
 45  
 import ca.uhn.hl7v2.util.ArrayUtil;
 46  
 import ca.uhn.hl7v2.util.ReflectionUtil;
 47  
 import ca.uhn.hl7v2.util.StringUtil;
 48  
 import ca.uhn.hl7v2.util.Terser;
 49  
 import ca.uhn.hl7v2.util.idgenerator.IDGenerator;
 50  
 import ca.uhn.hl7v2.validation.ValidationContext;
 51  
 
 52  
 /**
 53  
  * A default implementation of Message. 
 54  
  * @author Bryan Tripp (bryan_tripp@sourceforge.net)
 55  
  */
 56  
 @SuppressWarnings("serial")
 57  
 public abstract class AbstractMessage extends AbstractGroup implements Message {
 58  
     
 59  
     private ValidationContext myContext;
 60  1
         private static final Pattern ourVersionPattern = Pattern.compile("\\.(v2[0-9][0-9]?)\\.");
 61  
         private String myVersion;
 62  
     private transient Parser myParser;
 63  
         
 64  
     /**
 65  
      * @param theFactory factory for model classes (e.g. group, segment) for this message 
 66  
      */
 67  
     public AbstractMessage(ModelClassFactory theFactory) {
 68  3765
         super(null, theFactory);
 69  3765
     }
 70  
     
 71  
     /** 
 72  
      * Returns this Message object.  
 73  
      */
 74  
     public Message getMessage() {
 75  570925
        return this; 
 76  
     }
 77  
     
 78  
         public Group getParent() {
 79  0
                 return this;
 80  
         }
 81  
 
 82  
         /**
 83  
      * Returns the version number.  This default implementation inspects 
 84  
      * this.getClass().getName().  This should be overridden if you are putting
 85  
      * a custom message definition in your own package, or it will default.  
 86  
      * @see Message#getVersion()
 87  
      * 
 88  
      * @returns lowest available version if not obvious from package name
 89  
      */
 90  
     public String getVersion() {
 91  0
             if (myVersion != null) {
 92  0
                     return myVersion;
 93  
             }
 94  
             
 95  0
         String version = null;
 96  0
         Pattern p = ourVersionPattern;
 97  0
         Matcher m = p.matcher(this.getClass().getName());
 98  0
         if (m.find()) {
 99  0
             String verFolder = m.group(1);
 100  0
             if (verFolder.length() > 0) {
 101  0
                 char[] chars = verFolder.toCharArray();
 102  0
                 StringBuffer buf = new StringBuffer();
 103  0
                 for (int i = 1; i < chars.length; i++) { //start at 1 to avoid the 'v'
 104  0
                     buf.append(chars[i]);
 105  0
                     if (i < chars.length - 1) buf.append('.');
 106  
                 }
 107  0
                 version = buf.toString();
 108  
             }
 109  
         }
 110  
         
 111  0
         if (version == null) 
 112  0
             version = Version.lowestAvailableVersion().getVersion();
 113  
         
 114  0
         myVersion = version;
 115  0
         return version;
 116  
     }
 117  
     
 118  
     /**
 119  
      * @return the set of validation rules that applies to this message
 120  
      */
 121  
     public ValidationContext getValidationContext() {
 122  22918
         return myContext;
 123  
     }
 124  
     
 125  
     /**
 126  
      * @param theContext the set of validation rules that are to apply to this message
 127  
      */
 128  
     public void setValidationContext(ValidationContext theContext) {
 129  615
         myContext = theContext;
 130  615
     }
 131  
 
 132  
     /**
 133  
      * {@inheritDoc }
 134  
      */
 135  
     public Character getFieldSeparatorValue() throws HL7Exception {
 136  595
         Segment firstSegment = (Segment) get(getNames()[0]);
 137  595
         Primitive value = (Primitive) firstSegment.getField(1, 0);
 138  595
         String valueString = value.getValue();
 139  595
         if (valueString == null || valueString.length() == 0) {
 140  0
             return null;
 141  
         }
 142  595
         return valueString.charAt(0);
 143  
     }
 144  
 
 145  
 
 146  
     /**
 147  
      * {@inheritDoc }
 148  
      */
 149  
     public String getEncodingCharactersValue() throws HL7Exception {
 150  596
         Segment firstSegment = (Segment) get(getNames()[0]);
 151  596
         Primitive value = (Primitive) firstSegment.getField(2, 0);
 152  596
         return value.getValue();
 153  
     }
 154  
 
 155  
 
 156  
     /**
 157  
      * <p>Sets the parser to be used when parse/encode methods are called on this
 158  
      * Message, as well as its children. It is recommended that if these methods
 159  
      * are going to be called, a parser be supplied with the validation context
 160  
      * wanted. Where possible, the parser should be reused for best performance,
 161  
      * unless thread safety is an issue.</p>
 162  
      *
 163  
      * <p>Note that not all parsers can be used. As of version 1.0, only {@link PipeParser}
 164  
      * supports this functionality</p>
 165  
      * 
 166  
      * <p>Serialization note: The message parser is marked as transient, so it will not
 167  
      * survive serialization.</p>
 168  
      */
 169  
     public void setParser(Parser parser) {
 170  1341
         if (parser == null) {
 171  0
             throw new NullPointerException("Value may not be null");
 172  
         }
 173  
 
 174  1341
         myParser = parser;
 175  1341
     }
 176  
 
 177  
 
 178  
     /**
 179  
      * <p>Returns the parser to be used when parse/encode methods are called on this
 180  
      * Message, as well as its children. The default value is a new {@link PipeParser}.</p>
 181  
      * 
 182  
      * <p>Serialization note: The message parser is marked as transient, so it will not
 183  
      * survive serialization.</p>
 184  
      */
 185  
     public Parser getParser() {
 186  7915
         if (myParser == null) {
 187  126
             myParser = new PipeParser();
 188  
         }
 189  
 
 190  7915
         return myParser;
 191  
     }
 192  
 
 193  
 
 194  
     /**
 195  
      * {@inheritDoc }
 196  
      */
 197  
     public void parse(String string) throws HL7Exception {
 198  73
         clear();
 199  73
                 getParser().parse(this, string);
 200  59
     }
 201  
 
 202  
     
 203  
     /**
 204  
      * {@inheritDoc }
 205  
      */
 206  
     public String encode() throws HL7Exception {
 207  123
         return getParser().encode(this);
 208  
     }
 209  
 
 210  
     /**
 211  
      * {@inheritDoc }
 212  
      */
 213  
     public Message generateACK() throws HL7Exception, IOException {
 214  74
         return generateACK(AcknowledgmentCode.AA, null);
 215  
     }
 216  
 
 217  
     /**
 218  
      * {@inheritDoc }
 219  
      * @deprecated
 220  
      */
 221  
     public Message generateACK(String theAcknowledgementCode, HL7Exception theException) throws HL7Exception, IOException {
 222  0
             AcknowledgmentCode theCode = theAcknowledgementCode == null ? 
 223  
                             AcknowledgmentCode.AA :
 224  
                             AcknowledgmentCode.valueOf(theAcknowledgementCode);
 225  0
         return generateACK(theCode, theException);
 226  
     }
 227  
 
 228  
     /**
 229  
      * {@inheritDoc }
 230  
      */    
 231  
     public Message generateACK(AcknowledgmentCode theAcknowledgementCode, HL7Exception theException) throws HL7Exception, IOException {
 232  146
                 Message out = instantiateACK(theAcknowledgementCode);
 233  146
                 out.setParser(getParser());
 234  146
                 fillResponseHeader(out, theAcknowledgementCode);
 235  146
         if (theException != null) {
 236  68
             theException.populateResponse(out, theAcknowledgementCode, 0);
 237  
         }
 238  146
         return out;
 239  
     }
 240  
 
 241  
         private Message instantiateACK(AcknowledgmentCode theAcknowledgementCode) throws HL7Exception {
 242  146
                 ModelClassFactory mcf = getParser() != null ? 
 243  
                                 getParser().getFactory() : 
 244  
                                 new DefaultModelClassFactory();
 245  146
                 Version version = Version.versionOf(getVersion());
 246  146
                 Message out = null;
 247  146
                 if (version != null && version.available()) {
 248  146
                         Class<? extends Message> clazz = mcf.getMessageClass("ACK", version.getVersion(), false);
 249  146
                         if (clazz != null) {
 250  146
                             out = ReflectionUtil.instantiateMessage(clazz, mcf);
 251  
                         }
 252  
                 }
 253  146
                 if (out == null) {
 254  0
                         out = new GenericMessage.UnknownVersion(mcf);
 255  
                 }
 256  
                 
 257  146
                 if (out instanceof GenericMessage) {
 258  0
                         if (ArrayUtil.contains(out.getNames(), "MSA") == false) {
 259  0
                                 out.addNonstandardSegment("MSA");
 260  
                         }
 261  0
                         if (ArrayUtil.contains(out.getNames(), "ERR") == false) {
 262  0
                                 out.addNonstandardSegment("ERR");
 263  
                         }
 264  
                 }
 265  
                 
 266  146
                 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  
         public Message fillResponseHeader(Message out, AcknowledgmentCode code)
 277  
                         throws HL7Exception, IOException {
 278  146
                 Segment mshIn = (Segment) get("MSH");
 279  146
                 Segment mshOut = (Segment) out.get("MSH");
 280  
 
 281  
                 // get MSH data from incoming message ...
 282  146
                 String fieldSep = Terser.get(mshIn, 1, 0, 1, 1);
 283  146
                 String encChars = Terser.get(mshIn, 2, 0, 1, 1);
 284  146
                 String procID = Terser.get(mshIn, 11, 0, 1, 1);
 285  
 
 286  
                 // populate outbound MSH using data from inbound message ...
 287  146
                 Terser.set(mshOut, 1, 0, 1, 1, fieldSep);
 288  146
                 Terser.set(mshOut, 2, 0, 1, 1, encChars);
 289  146
                 GregorianCalendar now = new GregorianCalendar();
 290  146
                 now.setTime(new Date());
 291  146
                 Terser.set(mshOut, 7, 0, 1, 1, CommonTS.toHl7TSFormat(now));
 292  146
                 Terser.set(mshOut, 9, 0, 1, 1, "ACK");
 293  146
                 Terser.set(mshOut, 9, 0, 2, 1, Terser.get(mshIn, 9, 0, 2, 1));
 294  146
                 String v = mshOut.getMessage().getVersion();
 295  146
                 if (v != null) {
 296  146
                         Version version = Version.versionOf(v);
 297  146
                         if (version != null && !Version.V25.isGreaterThan(version)) {
 298  23
                                 Terser.set(mshOut, 9, 0, 3, 1, "ACK");
 299  
                         }
 300  
                 }
 301  146
                 Terser.set(mshOut, 10, 0, 1, 1, mshIn.getMessage().getParser().getParserConfiguration().getIdGenerator().getID());
 302  146
                 Terser.set(mshOut, 11, 0, 1, 1, procID);
 303  
                 
 304  146
                 String versionId = Terser.get(mshIn, 12, 0, 1, 1);
 305  146
                 if (StringUtil.isBlank(versionId)) {
 306  2
                         versionId = Version.highestAvailableVersionOrDefault().getVersion();
 307  
                 }
 308  146
                 Terser.set(mshOut, 12, 0, 1, 1, versionId);
 309  
 
 310  
                 // revert sender and receiver
 311  146
                 Terser.set(mshOut, 3, 0, 1, 1, Terser.get(mshIn, 5, 0, 1, 1));
 312  146
                 Terser.set(mshOut, 4, 0, 1, 1, Terser.get(mshIn, 6, 0, 1, 1));
 313  146
                 Terser.set(mshOut, 5, 0, 1, 1, Terser.get(mshIn, 3, 0, 1, 1));
 314  146
                 Terser.set(mshOut, 6, 0, 1, 1, Terser.get(mshIn, 4, 0, 1, 1));
 315  
                 
 316  
                 // fill MSA for the happy case
 317  146
                 Segment msaOut = (Segment) out.get("MSA");
 318  146
                 Terser.set(msaOut, 1, 0, 1, 1, code.name());
 319  146
                 Terser.set(msaOut, 2, 0, 1, 1, Terser.get(mshIn, 10, 0, 1, 1));
 320  146
                 return out;
 321  
         }
 322  
     
 323  
 
 324  
     /**
 325  
      * Provides an overview of the type and structure of this message
 326  
      */
 327  
     @Override
 328  
     public String toString() {
 329  
         try {
 330  17
             return encode();
 331  0
         } catch (HL7Exception e) {
 332  0
             return (getClass().getName() + " - Failed to create toString(): " + e.getMessage());
 333  
         }
 334  
     }
 335  
 
 336  
     /**
 337  
      * {@inheritDoc}
 338  
      */
 339  
     public String printStructure() throws HL7Exception {
 340  10
         StringBuilder builder = new StringBuilder();    
 341  10
         appendStructureDescription(builder, 0, false, false, true, true, true);
 342  10
         return builder.toString();
 343  
     }
 344  
     
 345  
     /**
 346  
      * Prints the message structure in a similar way to {@link #printStructure()} but
 347  
      * optionally excludes elements with no contents.
 348  
      */
 349  
     public String printStructure(boolean includeEmptyElements) throws HL7Exception {
 350  1
         StringBuilder builder = new StringBuilder();    
 351  1
         appendStructureDescription(builder, 0, false, false, true, true, includeEmptyElements);
 352  1
         return builder.toString();
 353  
     }
 354  
 
 355  
     /**
 356  
      * Quickly initializes this message with common values in the first (MSH) segment.
 357  
      * 
 358  
      * <p>
 359  
      * Settings include:
 360  
      *         <ul>
 361  
      *                 <li>MSH-1 (Field Separator) is set to "|"</li>
 362  
      *                 <li>MSH-2 (Encoding Characters) is set to "^~\&"</li>
 363  
      *                 <li>MSH-7 (Date/Time of Message) is set to current time</li>
 364  
      *                 <li>MSH-10 (Control ID) is populated using next value generated by a {@link IDGenerator}</li>
 365  
      *         </ul>
 366  
      * </p>
 367  
      * 
 368  
      * @param messageCode The message code (aka message type) to insert into MSH-9-1. Example: "ADT"
 369  
      * @param messageTriggerEvent The message trigger event to insert into MSG-9-2. Example: "A01"
 370  
      * @param processingId The message processing ID to insert into MSH-11. Examples: "T" (for TEST) or "P" for (PRODUCTION)
 371  
      * 
 372  
      * @throws IOException If the message ID generation fails for some reason 
 373  
      * @throws HL7Exception If the message rejects any of the values which are generated to setting
 374  
      */
 375  
     public void initQuickstart(String messageCode, String messageTriggerEvent, String processingId) throws HL7Exception, IOException {
 376  61
             Segment msh = (Segment) get("MSH");
 377  61
             Terser.set(msh, 1, 0, 1, 1, "|");
 378  61
         Terser.set(msh, 2, 0, 1, 1, "^~\\&");
 379  61
         GregorianCalendar now = new GregorianCalendar();
 380  61
         Terser.set(msh, 7, 0, 1, 1, CommonTS.toHl7TSFormat(now));
 381  61
         Terser.set(msh, 9, 0, 1, 1, messageCode);
 382  61
         Terser.set(msh, 9, 0, 2, 1, messageTriggerEvent);
 383  61
         Terser.set(msh, 10, 0, 1, 1, getParser().getParserConfiguration().getIdGenerator().getID());
 384  61
         Terser.set(msh, 11, 0, 1, 1, processingId);
 385  61
         Terser.set(msh, 12, 0, 1, 1, getVersion());
 386  
         
 387  
         // Add structure information if version is 2.4 or better
 388  61
         if (!Version.V24.isGreaterThan(Version.versionOf(getVersion()))) {
 389  56
                 if (this instanceof SuperStructure) {
 390  1
                         Version version = Version.versionOf(getVersion());
 391  1
                         Map<String, String> eventMap = new DefaultModelClassFactory().getEventMapForVersion(version);
 392  1
                         if (StringUtil.isNotBlank(messageCode) && StringUtil.isNotBlank(messageTriggerEvent)) {
 393  1
                                 String structure = eventMap.get(messageCode + "_" + messageTriggerEvent);
 394  1
                                 Terser.set(msh, 9, 0, 3, 1, structure);
 395  
                         }                        
 396  1
                 } else {
 397  55
                         String className = getClass().getName();
 398  55
                         int lastIndexOf = className.lastIndexOf('.');
 399  55
                                 className = className.substring(lastIndexOf + 1);
 400  55
                                 if (className.matches("[A-Z]{3}_[A-Z0-9]{3}")) {
 401  50
                                 Terser.set(msh, 9, 0, 3, 1, className);
 402  
                         }
 403  
                 }
 404  
         }
 405  
         
 406  61
     }
 407  
     
 408  
 }