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.Version;
040import ca.uhn.hl7v2.model.primitive.CommonTS;
041import ca.uhn.hl7v2.parser.DefaultModelClassFactory;
042import ca.uhn.hl7v2.parser.ModelClassFactory;
043import ca.uhn.hl7v2.parser.Parser;
044import ca.uhn.hl7v2.parser.PipeParser;
045import ca.uhn.hl7v2.util.ArrayUtil;
046import ca.uhn.hl7v2.util.ReflectionUtil;
047import ca.uhn.hl7v2.util.StringUtil;
048import ca.uhn.hl7v2.util.Terser;
049import ca.uhn.hl7v2.util.idgenerator.IDGenerator;
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 ValidationContext myContext;
060        private static final Pattern ourVersionPattern = Pattern.compile("\\.(v2[0-9][0-9]?)\\.");
061        private String myVersion;
062    private transient Parser myParser;
063        
064    /**
065     * @param theFactory factory for model classes (e.g. group, segment) for this message 
066     */
067    public AbstractMessage(ModelClassFactory theFactory) {
068        super(null, theFactory);
069    }
070    
071    /** 
072     * Returns this Message object.  
073     */
074    public Message getMessage() {
075       return this; 
076    }
077    
078        public Group getParent() {
079                return this;
080        }
081
082        /**
083     * Returns the version number.  This default implementation inspects 
084     * this.getClass().getName().  This should be overridden if you are putting
085     * a custom message definition in your own package, or it will default.  
086     * @see Message#getVersion()
087     * 
088     * @returns lowest available version if not obvious from package name
089     */
090    public String getVersion() {
091        if (myVersion != null) {
092                return myVersion;
093        }
094        
095        String version = null;
096        Pattern p = ourVersionPattern;
097        Matcher m = p.matcher(this.getClass().getName());
098        if (m.find()) {
099            String verFolder = m.group(1);
100            if (verFolder.length() > 0) {
101                char[] chars = verFolder.toCharArray();
102                StringBuffer buf = new StringBuffer();
103                for (int i = 1; i < chars.length; i++) { //start at 1 to avoid the 'v'
104                    buf.append(chars[i]);
105                    if (i < chars.length - 1) buf.append('.');
106                }
107                version = buf.toString();
108            }
109        }
110        
111        if (version == null) 
112            version = Version.lowestAvailableVersion().getVersion();
113        
114        myVersion = version;
115        return version;
116    }
117    
118    /**
119     * @return the set of validation rules that applies to this message
120     */
121    public ValidationContext getValidationContext() {
122        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        myContext = theContext;
130    }
131
132    /**
133     * {@inheritDoc }
134     */
135    public Character getFieldSeparatorValue() throws HL7Exception {
136        Segment firstSegment = (Segment) get(getNames()[0]);
137        Primitive value = (Primitive) firstSegment.getField(1, 0);
138        String valueString = value.getValue();
139        if (valueString == null || valueString.length() == 0) {
140            return null;
141        }
142        return valueString.charAt(0);
143    }
144
145
146    /**
147     * {@inheritDoc }
148     */
149    public String getEncodingCharactersValue() throws HL7Exception {
150        Segment firstSegment = (Segment) get(getNames()[0]);
151        Primitive value = (Primitive) firstSegment.getField(2, 0);
152        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        if (parser == null) {
171            throw new NullPointerException("Value may not be null");
172        }
173
174        myParser = parser;
175    }
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        if (myParser == null) {
187            myParser = new PipeParser();
188        }
189
190        return myParser;
191    }
192
193
194    /**
195     * {@inheritDoc }
196     */
197    public void parse(String string) throws HL7Exception {
198        clear();
199                getParser().parse(this, string);
200    }
201
202    
203    /**
204     * {@inheritDoc }
205     */
206    public String encode() throws HL7Exception {
207        return getParser().encode(this);
208    }
209
210    /**
211     * {@inheritDoc }
212     */
213    public Message generateACK() throws HL7Exception, IOException {
214        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        AcknowledgmentCode theCode = theAcknowledgementCode == null ? 
223                        AcknowledgmentCode.AA :
224                        AcknowledgmentCode.valueOf(theAcknowledgementCode);
225        return generateACK(theCode, theException);
226    }
227
228    /**
229     * {@inheritDoc }
230     */    
231    public Message generateACK(AcknowledgmentCode theAcknowledgementCode, HL7Exception theException) throws HL7Exception, IOException {
232                Message out = instantiateACK(theAcknowledgementCode);
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(AcknowledgmentCode theAcknowledgementCode) 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") == false) {
259                                out.addNonstandardSegment("MSA");
260                        }
261                        if (ArrayUtil.contains(out.getNames(), "ERR") == false) {
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        public Message fillResponseHeader(Message out, AcknowledgmentCode code)
277                        throws HL7Exception, IOException {
278                Segment mshIn = (Segment) get("MSH");
279                Segment mshOut = (Segment) out.get("MSH");
280
281                // get MSH data from incoming message ...
282                String fieldSep = Terser.get(mshIn, 1, 0, 1, 1);
283                String encChars = Terser.get(mshIn, 2, 0, 1, 1);
284                String procID = Terser.get(mshIn, 11, 0, 1, 1);
285
286                // populate outbound MSH using data from inbound message ...
287                Terser.set(mshOut, 1, 0, 1, 1, fieldSep);
288                Terser.set(mshOut, 2, 0, 1, 1, encChars);
289                GregorianCalendar now = new GregorianCalendar();
290                now.setTime(new Date());
291                Terser.set(mshOut, 7, 0, 1, 1, CommonTS.toHl7TSFormat(now));
292                Terser.set(mshOut, 9, 0, 1, 1, "ACK");
293                Terser.set(mshOut, 9, 0, 2, 1, Terser.get(mshIn, 9, 0, 2, 1));
294                String v = mshOut.getMessage().getVersion();
295                if (v != null) {
296                        Version version = Version.versionOf(v);
297                        if (version != null && !Version.V25.isGreaterThan(version)) {
298                                Terser.set(mshOut, 9, 0, 3, 1, "ACK");
299                        }
300                }
301                Terser.set(mshOut, 10, 0, 1, 1, mshIn.getMessage().getParser().getParserConfiguration().getIdGenerator().getID());
302                Terser.set(mshOut, 11, 0, 1, 1, procID);
303                
304                String versionId = Terser.get(mshIn, 12, 0, 1, 1);
305                if (StringUtil.isBlank(versionId)) {
306                        versionId = Version.highestAvailableVersionOrDefault().getVersion();
307                }
308                Terser.set(mshOut, 12, 0, 1, 1, versionId);
309
310                // revert sender and receiver
311                Terser.set(mshOut, 3, 0, 1, 1, Terser.get(mshIn, 5, 0, 1, 1));
312                Terser.set(mshOut, 4, 0, 1, 1, Terser.get(mshIn, 6, 0, 1, 1));
313                Terser.set(mshOut, 5, 0, 1, 1, Terser.get(mshIn, 3, 0, 1, 1));
314                Terser.set(mshOut, 6, 0, 1, 1, Terser.get(mshIn, 4, 0, 1, 1));
315                
316                // fill MSA for the happy case
317                Segment msaOut = (Segment) out.get("MSA");
318                Terser.set(msaOut, 1, 0, 1, 1, code.name());
319                Terser.set(msaOut, 2, 0, 1, 1, Terser.get(mshIn, 10, 0, 1, 1));
320                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            return encode();
331        } catch (HL7Exception e) {
332            return (getClass().getName() + " - Failed to create toString(): " + e.getMessage());
333        }
334    }
335
336    /**
337     * {@inheritDoc}
338     */
339    public String printStructure() throws HL7Exception {
340        StringBuilder builder = new StringBuilder();    
341        appendStructureDescription(builder, 0, false, false, true, true, true);
342        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        StringBuilder builder = new StringBuilder();    
351        appendStructureDescription(builder, 0, false, false, true, true, includeEmptyElements);
352        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        Segment msh = (Segment) get("MSH");
377        Terser.set(msh, 1, 0, 1, 1, "|");
378        Terser.set(msh, 2, 0, 1, 1, "^~\\&");
379        GregorianCalendar now = new GregorianCalendar();
380        Terser.set(msh, 7, 0, 1, 1, CommonTS.toHl7TSFormat(now));
381        Terser.set(msh, 9, 0, 1, 1, messageCode);
382        Terser.set(msh, 9, 0, 2, 1, messageTriggerEvent);
383        Terser.set(msh, 10, 0, 1, 1, getParser().getParserConfiguration().getIdGenerator().getID());
384        Terser.set(msh, 11, 0, 1, 1, processingId);
385        Terser.set(msh, 12, 0, 1, 1, getVersion());
386        
387        // Add structure information if version is 2.4 or better
388        if (!Version.V24.isGreaterThan(Version.versionOf(getVersion()))) {
389                if (this instanceof SuperStructure) {
390                        Version version = Version.versionOf(getVersion());
391                        Map<String, String> eventMap = new DefaultModelClassFactory().getEventMapForVersion(version);
392                        if (StringUtil.isNotBlank(messageCode) && StringUtil.isNotBlank(messageTriggerEvent)) {
393                                String structure = eventMap.get(messageCode + "_" + messageTriggerEvent);
394                                Terser.set(msh, 9, 0, 3, 1, structure);
395                        }                       
396                } else {
397                        String className = getClass().getName();
398                        int lastIndexOf = className.lastIndexOf('.');
399                                className = className.substring(lastIndexOf + 1);
400                                if (className.matches("[A-Z]{3}_[A-Z0-9]{3}")) {
401                                Terser.set(msh, 9, 0, 3, 1, className);
402                        }
403                }
404        }
405        
406    }
407    
408}