Coverage Report - ca.uhn.hl7v2.model.AbstractMessage
 
Classes in this File Line Coverage Branch Coverage Complexity
AbstractMessage
75%
106/140
45%
32/70
3
 
 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.Location;
 40  
 import ca.uhn.hl7v2.Version;
 41  
 import ca.uhn.hl7v2.model.primitive.CommonTS;
 42  
 import ca.uhn.hl7v2.parser.DefaultModelClassFactory;
 43  
 import ca.uhn.hl7v2.parser.ModelClassFactory;
 44  
 import ca.uhn.hl7v2.parser.Parser;
 45  
 import ca.uhn.hl7v2.parser.PipeParser;
 46  
 import ca.uhn.hl7v2.util.ArrayUtil;
 47  
 import ca.uhn.hl7v2.util.ReflectionUtil;
 48  
 import ca.uhn.hl7v2.util.StringUtil;
 49  
 import ca.uhn.hl7v2.util.Terser;
 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  5
         private static final Pattern ourVersionPattern = Pattern.compile("\\.(v2[0-9][0-9]?)\\.");
 60  
         private String myVersion;
 61  
     private transient Parser myParser;
 62  
         
 63  
     /**
 64  
      * @param theFactory factory for model classes (e.g. group, segment) for this message 
 65  
      */
 66  
     public AbstractMessage(ModelClassFactory theFactory) {
 67  21252
         super(null, theFactory);
 68  21252
     }
 69  
     
 70  
     /** 
 71  
      * Returns this Message object.  
 72  
      */
 73  
     public Message getMessage() {
 74  3866423
        return this; 
 75  
     }
 76  
     
 77  
         public Group getParent() {
 78  0
                 return this;
 79  
         }
 80  
 
 81  
         /**
 82  
      * Returns the version number.  This default implementation inspects 
 83  
      * this.getClass().getName().  This should be overridden if you are putting
 84  
      * a custom message definition in your own package, or it will default.  
 85  
      * @see Message#getVersion()
 86  
      * 
 87  
      * @return lowest available version if not obvious from package name
 88  
      */
 89  
     public String getVersion() {
 90  0
             if (myVersion != null) {
 91  0
                     return myVersion;
 92  
             }
 93  
             
 94  0
         String version = null;
 95  0
         Pattern p = ourVersionPattern;
 96  0
         Matcher m = p.matcher(this.getClass().getName());
 97  0
         if (m.find()) {
 98  0
             String verFolder = m.group(1);
 99  0
             if (verFolder.length() > 0) {
 100  0
                 char[] chars = verFolder.toCharArray();
 101  0
                 StringBuilder buf = new StringBuilder();
 102  0
                 for (int i = 1; i < chars.length; i++) { //start at 1 to avoid the 'v'
 103  0
                     buf.append(chars[i]);
 104  0
                     if (i < chars.length - 1) buf.append('.');
 105  
                 }
 106  0
                 version = buf.toString();
 107  
             }
 108  
         }
 109  
         
 110  0
         if (version == null) 
 111  0
             version = Version.lowestAvailableVersion().getVersion();
 112  
         
 113  0
         myVersion = version;
 114  0
         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  0
         if (getParser() == null || !getParser().getParserConfiguration().isValidating()) return null;
 125  0
         return getParser().getHapiContext().getValidationContext();
 126  
     }
 127  
 
 128  
 
 129  
     /**
 130  
      * {@inheritDoc }
 131  
      */
 132  
     public Character getFieldSeparatorValue() throws HL7Exception {
 133  9800
         Segment firstSegment = (Segment) get(getNames()[0]);
 134  9800
         Primitive value = (Primitive) firstSegment.getField(1, 0);
 135  9800
         String valueString = value.getValue();
 136  9800
         if (valueString == null || valueString.length() == 0) {
 137  0
             return null;
 138  
         }
 139  9800
         return valueString.charAt(0);
 140  
     }
 141  
 
 142  
 
 143  
     /**
 144  
      * {@inheritDoc }
 145  
      */
 146  
     public String getEncodingCharactersValue() throws HL7Exception {
 147  9805
         Segment firstSegment = (Segment) get(getNames()[0]);
 148  9805
         Primitive value = (Primitive) firstSegment.getField(2, 0);
 149  9805
         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  7873
         if (parser == null) {
 168  0
             throw new NullPointerException("Value may not be null");
 169  
         }
 170  
 
 171  7873
         myParser = parser;
 172  7873
     }
 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  186180
         if (myParser == null) {
 184  1455
             myParser = new PipeParser();
 185  
         }
 186  
 
 187  186180
         return myParser;
 188  
     }
 189  
 
 190  
 
 191  
     /**
 192  
      * {@inheritDoc }
 193  
      */
 194  
     public void parse(String string) throws HL7Exception {
 195  605
         clear();
 196  605
                 getParser().parse(this, string);
 197  545
     }
 198  
 
 199  
     
 200  
     /**
 201  
      * {@inheritDoc }
 202  
      */
 203  
     public String encode() throws HL7Exception {
 204  890
         return getParser().encode(this);
 205  
     }
 206  
 
 207  
     /**
 208  
      * {@inheritDoc }
 209  
      */
 210  
     public Message generateACK() throws HL7Exception, IOException {
 211  413
         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  0
             AcknowledgmentCode theCode = theAcknowledgementCode == null ? 
 220  
                             AcknowledgmentCode.AA :
 221  0
                             AcknowledgmentCode.valueOf(theAcknowledgementCode);
 222  0
         return generateACK(theCode, theException);
 223  
     }
 224  
 
 225  
     /**
 226  
      * {@inheritDoc }
 227  
      */    
 228  
     public Message generateACK(AcknowledgmentCode theAcknowledgementCode, HL7Exception theException) throws HL7Exception, IOException {
 229  803
         if (theException != null && theException.getResponseMessage() != null) {
 230  0
             return theException.getResponseMessage();
 231  
         }
 232  803
                 Message out = instantiateACK();
 233  803
                 out.setParser(getParser());
 234  803
                 fillResponseHeader(out, theAcknowledgementCode);
 235  803
         if (theException != null) {
 236  330
             theException.populateResponse(out, theAcknowledgementCode, 0);
 237  
         }
 238  803
         return out;
 239  
     }
 240  
 
 241  
         private Message instantiateACK() throws HL7Exception {
 242  803
                 ModelClassFactory mcf = getParser() != null ? 
 243  803
                                 getParser().getFactory() : 
 244  
                                 new DefaultModelClassFactory();
 245  803
                 Version version = Version.versionOf(getVersion());
 246  803
                 Message out = null;
 247  803
                 if (version != null && version.available()) {
 248  803
                         Class<? extends Message> clazz = mcf.getMessageClass("ACK", version.getVersion(), false);
 249  803
                         if (clazz != null) {
 250  803
                             out = ReflectionUtil.instantiateMessage(clazz, mcf);
 251  
                         }
 252  
                 }
 253  803
                 if (out == null) {
 254  0
                         out = new GenericMessage.UnknownVersion(mcf);
 255  
                 }
 256  
                 
 257  803
                 if (out instanceof GenericMessage) {
 258  0
                         if (!ArrayUtil.contains(out.getNames(), "MSA")) {
 259  0
                                 out.addNonstandardSegment("MSA");
 260  
                         }
 261  0
                         if (!ArrayUtil.contains(out.getNames(), "ERR")) {
 262  0
                                 out.addNonstandardSegment("ERR");
 263  
                         }
 264  
                 }
 265  
                 
 266  803
                 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  803
                 Segment mshIn = (Segment) get("MSH");
 285  803
                 Segment mshOut = (Segment) out.get("MSH");
 286  
 
 287  
                 // get MSH data from incoming message ...
 288  803
                 String fieldSep = Terser.get(mshIn, 1, 0, 1, 1);
 289  803
                 String encChars = Terser.get(mshIn, 2, 0, 1, 1);
 290  803
                 String procID = Terser.get(mshIn, 11, 0, 1, 1);
 291  
 
 292  
                 // populate outbound MSH using data from inbound message ...
 293  803
                 Terser.set(mshOut, 1, 0, 1, 1, fieldSep);
 294  803
                 Terser.set(mshOut, 2, 0, 1, 1, encChars);
 295  803
                 GregorianCalendar now = new GregorianCalendar();
 296  803
                 now.setTime(new Date());
 297  803
                 Terser.set(mshOut, 7, 0, 1, 1, CommonTS.toHl7TSFormat(now));
 298  803
                 Terser.set(mshOut, 9, 0, 1, 1, "ACK");
 299  803
                 Terser.set(mshOut, 9, 0, 2, 1, Terser.get(mshIn, 9, 0, 2, 1));
 300  803
                 String v = mshOut.getMessage().getVersion();
 301  803
                 if (v != null) {
 302  803
                         Version version = Version.versionOf(v);
 303  803
                         if (version != null && !Version.V25.isGreaterThan(version)) {
 304  195
                                 Terser.set(mshOut, 9, 0, 3, 1, "ACK");
 305  
                         }
 306  
                 }
 307  803
                 Terser.set(mshOut, 10, 0, 1, 1, mshIn.getMessage().getParser().getParserConfiguration().getIdGenerator().getID());
 308  803
                 Terser.set(mshOut, 11, 0, 1, 1, procID);
 309  
                 
 310  803
                 String versionId = Terser.get(mshIn, 12, 0, 1, 1);
 311  803
                 if (StringUtil.isBlank(versionId)) {
 312  10
                         versionId = Version.highestAvailableVersionOrDefault().getVersion();
 313  
                 }
 314  803
                 Terser.set(mshOut, 12, 0, 1, 1, versionId);
 315  
 
 316  
                 // revert sender and receiver
 317  803
                 Terser.set(mshOut, 3, 0, 1, 1, Terser.get(mshIn, 5, 0, 1, 1));
 318  803
                 Terser.set(mshOut, 4, 0, 1, 1, Terser.get(mshIn, 6, 0, 1, 1));
 319  803
                 Terser.set(mshOut, 5, 0, 1, 1, Terser.get(mshIn, 3, 0, 1, 1));
 320  803
                 Terser.set(mshOut, 6, 0, 1, 1, Terser.get(mshIn, 4, 0, 1, 1));
 321  
                 
 322  
                 // fill MSA for the happy case
 323  803
                 Segment msaOut = (Segment) out.get("MSA");
 324  803
                 Terser.set(msaOut, 1, 0, 1, 1, code.name());
 325  803
                 Terser.set(msaOut, 2, 0, 1, 1, Terser.get(mshIn, 10, 0, 1, 1));
 326  803
                 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  85
             return encode();
 337  0
         } catch (HL7Exception e) {
 338  0
             return (getClass().getName() + " - Failed to create toString(): " + e.getMessage());
 339  
         }
 340  
     }
 341  
 
 342  
     /**
 343  
      * {@inheritDoc}
 344  
      */
 345  
     public String printStructure() throws HL7Exception {
 346  485
         StringBuilder builder = new StringBuilder();    
 347  485
         appendStructureDescription(builder, 0, false, false, true, true, true);
 348  485
         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  5
         StringBuilder builder = new StringBuilder();    
 357  5
         appendStructureDescription(builder, 0, false, false, true, true, includeEmptyElements);
 358  5
         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  630
             Segment msh = (Segment) get("MSH");
 384  630
         Version version = Version.versionOf(getVersion());
 385  630
             Terser.set(msh, 1, 0, 1, 1, "|");
 386  630
         Terser.set(msh, 2, 0, 1, 1, Version.V27.isGreaterThan(version) ?
 387  
                 "^~\\&" : "^~\\&#");
 388  630
         GregorianCalendar now = new GregorianCalendar();
 389  630
         Terser.set(msh, 7, 0, 1, 1, CommonTS.toHl7TSFormat(now));
 390  630
         Terser.set(msh, 9, 0, 1, 1, messageCode);
 391  630
         Terser.set(msh, 9, 0, 2, 1, messageTriggerEvent);
 392  630
         Terser.set(msh, 10, 0, 1, 1, getParser().getParserConfiguration().getIdGenerator().getID());
 393  630
         Terser.set(msh, 11, 0, 1, 1, processingId);
 394  630
         Terser.set(msh, 12, 0, 1, 1, getVersion());
 395  
         
 396  
         // Add structure information if version is 2.4 or better
 397  630
         if (!Version.V24.isGreaterThan(version)) {
 398  600
                 if (this instanceof SuperStructure) {
 399  5
                         Map<String, String> eventMap = new DefaultModelClassFactory().getEventMapForVersion(version);
 400  5
                         if (StringUtil.isNotBlank(messageCode) && StringUtil.isNotBlank(messageTriggerEvent)) {
 401  5
                                 String structure = eventMap.get(messageCode + "_" + messageTriggerEvent);
 402  5
                                 Terser.set(msh, 9, 0, 3, 1, structure);
 403  
                         }                        
 404  5
                 } else {
 405  595
                         String className = getClass().getName();
 406  595
                         int lastIndexOf = className.lastIndexOf('.');
 407  595
                                 className = className.substring(lastIndexOf + 1);
 408  595
                                 if (className.matches("[A-Z]{3}_[A-Z0-9]{3}")) {
 409  575
                                 Terser.set(msh, 9, 0, 3, 1, className);
 410  
                         }
 411  
                 }
 412  
         }
 413  
         
 414  630
     }
 415  
 
 416  
     @Override
 417  
     public boolean accept(MessageVisitor visitor, Location location) throws HL7Exception {
 418  10
         if (visitor.start(this)) {
 419  10
             visitNestedStructures(visitor, location);
 420  
         }
 421  10
         return visitor.end(this);
 422  
     }
 423  
 
 424  
 
 425  
 }