View Javadoc

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 ""  Description:
10   * ""
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  package ca.uhn.hl7v2.testpanel.model.msg;
27  
28  import java.beans.PropertyVetoException;
29  import java.io.IOException;
30  import java.util.List;
31  
32  import org.apache.commons.lang.ObjectUtils;
33  import org.apache.commons.lang.StringUtils;
34  import org.apache.commons.lang.Validate;
35  import org.slf4j.Logger;
36  import org.slf4j.LoggerFactory;
37  
38  import ca.uhn.hl7v2.HL7Exception;
39  import ca.uhn.hl7v2.conf.ProfileException;
40  import ca.uhn.hl7v2.model.Message;
41  import ca.uhn.hl7v2.model.Segment;
42  import ca.uhn.hl7v2.parser.EncodingCharacters;
43  import ca.uhn.hl7v2.parser.EncodingNotSupportedException;
44  import ca.uhn.hl7v2.parser.GenericParser;
45  import ca.uhn.hl7v2.preparser.PreParser;
46  import ca.uhn.hl7v2.testpanel.model.conf.ConformanceMessage;
47  import ca.uhn.hl7v2.testpanel.model.conf.ProfileGroup;
48  import ca.uhn.hl7v2.testpanel.model.conf.ProfileGroup.Entry;
49  import ca.uhn.hl7v2.testpanel.util.Range;
50  import ca.uhn.hl7v2.testpanel.util.SegmentAndComponentPath;
51  import ca.uhn.hl7v2.testpanel.xsd.Hl7V2EncodingTypeEnum;
52  import ca.uhn.hl7v2.testpanel.xsd.Hl7V2MessageDefinition;
53  import ca.uhn.hl7v2.util.Terser;
54  import ca.uhn.hl7v2.validation.impl.ValidationContextImpl;
55  
56  public abstract class Hl7V2MessageBase extends AbstractMessage<Message> {
57  
58  	private static final Logger ourLog = LoggerFactory.getLogger(Hl7V2MessageBase.class);
59  	private Hl7V2EncodingTypeEnum myEncoding = Hl7V2EncodingTypeEnum.ER_7;
60  	private int myIndexWithinCollection;
61  	private Message myParsedMessage;
62  	private GenericParser myParser;
63  	private ProfileGroup myRuntimeProfile;
64  	private String mySourceMessage;
65  
66  	/**
67  	 * Constructor
68  	 */
69  	public Hl7V2MessageBase() {
70  		super();
71  		initParser();
72  	}
73  
74  	public Hl7V2MessageBase(String theId) {
75  		super(theId);
76  		initParser();
77  	}
78  
79  	public abstract Hl7V2MessageBase asEncoding(Hl7V2EncodingTypeEnum theEncoding);
80  
81  	/**
82  	 * Subclasses should make use of this method to export AbstractInterface
83  	 * properties into the return value for {@link #exportConfigToXml()}
84  	 */
85  	protected Hl7V2MessageDefinition exportConfig(Hl7V2MessageDefinition theConfig) {
86  		super.exportConfig(theConfig);
87  		theConfig.setText(mySourceMessage);
88  
89  		return theConfig;
90  	}
91  
92  	/**
93  	 * {@inheritDoc }
94  	 */
95  	@Override
96  	public Hl7V2MessageDefinition exportConfigToXml() {
97  		return exportConfig(new Hl7V2MessageDefinition());
98  	}
99  
100 	/**
101 	 * @return the encoding
102 	 */
103 	public Hl7V2EncodingTypeEnum getEncoding() {
104 		return myEncoding;
105 	}
106 
107 	public abstract String getHighlitedPath();
108 
109 	public abstract Range getHighlitedRange();
110 
111 	/**
112 	 * @return the indexWithinCollection
113 	 */
114 	public int getIndexWithinCollection() {
115 		return myIndexWithinCollection;
116 	}
117 
118 	@Override
119 	public Class<Message> getMessageClass() {
120 		return Message.class;
121 	}
122 
123 	public String getMessageDescription() {
124 		StringBuilder retVal = new StringBuilder();
125 
126 		Terser t = new Terser(getParsedMessage());
127 
128 		try {
129 			retVal.append(t.get("/.MSH-9-1"));
130 			retVal.append("^");
131 			retVal.append(t.get("/.MSH-9-2"));
132 
133 			retVal.append(" ");
134 
135 			retVal.append(t.get("/.MSH-10"));
136 		} catch (HL7Exception e) {
137 			ourLog.error("Failed to retrieve message props: ", e);
138 		}
139 
140 		return retVal.toString();
141 	}
142 
143 	@Override
144 	public Message getParsedMessage() {
145 		return myParsedMessage;
146 	}
147 
148 	/**
149 	 * @return the runtimeProfile
150 	 */
151 	public ProfileGroup getRuntimeProfile() {
152 		return myRuntimeProfile;
153 	}
154 
155 	@Override
156 	public String getSourceMessage() {
157 		return mySourceMessage;
158 	}
159 
160 	private void initParser() {
161 		myParser = new GenericParser();
162 		myParser.setValidationContext(new ValidationContextImpl());
163 	}
164 
165 	/**
166 	 * @param theParsedMessage
167 	 *            the parsedMessage to set
168 	 */
169 	public void initWithParsedMessage(Message theParsedMessage) {
170 		myParsedMessage = theParsedMessage;
171 	}
172 
173 	/**
174 	 * Invoked when the source message is updated prior to notifying listeners
175 	 */
176 	protected abstract void recalculateIndexes();
177 
178 	/**
179 	 * @param theEncoding
180 	 *            the encoding to set
181 	 */
182 	public void setEncoding(Hl7V2EncodingTypeEnum theEncoding) {
183 		Validate.notNull(theEncoding);
184 		if (ObjectUtils.equals(myEncoding, theEncoding)) {
185 			return;
186 		}
187 
188 		myEncoding = theEncoding;
189 		updateParser();
190 
191 		if (myParsedMessage == null) {
192 			return;
193 		}
194 
195 		String prev = mySourceMessage;
196 		try {
197 			mySourceMessage = myParser.encode(getParsedMessage());
198 		} catch (HL7Exception e) {
199 			ourLog.error("Could not re-encode message: " + e.getMessage(), e);
200 		}
201 
202 		firePropertyChange(SOURCE_MESSAGE_PROPERTY, prev, mySourceMessage);
203 	}
204 
205 	public abstract void setHighlitedField(SegmentAndComponentPath thePath);
206 
207 	public abstract void setHighlitedPathBasedOnRange(Range theAdd);
208 
209 	public abstract void setHighlitedRangeBasedOnSegment(Segment... theSegment);
210 
211 	/**
212 	 * @param theIndexWithinCollection
213 	 *            the indexWithinCollection to set
214 	 */
215 	public void setIndexWithinCollection(int theIndexWithinCollection) {
216 		myIndexWithinCollection = theIndexWithinCollection;
217 	}
218 
219 	public void setRuntimeProfile(ProfileGroup theRuntimeProfile) {
220 		if (myRuntimeProfile == theRuntimeProfile) {
221 			return;
222 		}
223 		myRuntimeProfile = theRuntimeProfile;
224 
225 		if (mySourceMessage == null) {
226 			return;
227 		}
228 
229 		// Force a refresh
230 		String sourceMessage = mySourceMessage;
231 		mySourceMessage = null;
232 		try {
233 			setSourceMessage(sourceMessage);
234 		} catch (PropertyVetoException e) {
235 			ourLog.error("Failed to parse message", e);
236 		}
237 	}
238 
239 	@Override
240 	public void setSourceMessage(String theMessage) throws PropertyVetoException {
241 		theMessage = StringUtils.defaultString(theMessage);
242 
243 		String original = mySourceMessage;
244 		if (mySourceMessage != null && mySourceMessage.equals(theMessage)) {
245 			return;
246 		}
247 
248 		String sourceMessage = theMessage.trim();
249 		String text = sourceMessage.replaceAll("(\\r|\\n)+", "\r");
250 
251 		updateParser();
252 
253 		Message parsedMessage;
254 		try {
255 
256 			ourLog.info("About to parse message");
257 
258 			if (myRuntimeProfile != null) {
259 				Entry profile = determineRuntimeProfile(text);
260 
261 				if (profile != null) {
262 					parsedMessage = ConformanceMessage.newInstanceFromStaticDef(profile.getProfileProxy().getProfile().getMessage(), profile.getTablesId());
263 					parsedMessage.setParser(myParser);
264 					parsedMessage.parse(text);
265 				} else {
266 					parsedMessage = myParser.parse(text);
267 				}
268 			} else {
269 				parsedMessage = myParser.parse(text);
270 			}
271 
272 			ourLog.info("Done parsing message");
273 
274 		} catch (EncodingNotSupportedException e) {
275 			ourLog.error("Failed to parse message", e);
276 			throw new PropertyVetoException(e.getMessage(), null);
277 		} catch (HL7Exception e) {
278 			ourLog.error("Failed to parse message", e);
279 			throw new PropertyVetoException(e.getMessage(), null);
280 		} catch (IOException e) {
281 			ourLog.error("Failed to parse message", e);
282 			throw new PropertyVetoException(e.getMessage(), null);
283 		} catch (ProfileException e) {
284 			ourLog.error("Failed to parse message", e);
285 			throw new PropertyVetoException(e.getMessage(), null);
286 		}
287 
288 		myParsedMessage = parsedMessage;
289 		mySourceMessage = sourceMessage;
290 
291 		recalculateIndexes();
292 
293 		firePropertyChange(PARSED_MESSAGE_PROPERTY, original, text);
294 	}
295 
296 	private Entry determineRuntimeProfile(String text) throws HL7Exception {
297 		String[] fields = PreParser.getFields(text, "MSH-9-1", "MSH-9-2");
298 		Entry profile = null;
299 		try {
300 			profile = myRuntimeProfile.getProfileForMessage(fields[0], fields[1]);
301 			if (profile != null) {
302 				profile.getProfileProxy().getProfile();
303 			}
304 		} catch (IOException e) {
305 			ourLog.error("Failed to load profile", e);
306 			profile = null;
307 		} catch (ProfileException e) {
308 			ourLog.error("Failed to load profile", e);
309 			profile = null;
310 		}
311 
312 		return profile;
313 	}
314 
315 	private void updateParser() {
316 
317 		// Parser caches structure information, so can't reuse them for
318 		// structures generated from conformance profiles
319 		if (myRuntimeProfile != null) {
320 			initParser();
321 		}
322 
323 		switch (myEncoding) {
324 		case XML:
325 			myParser.setXMLParserAsPrimary();
326 			break;
327 		case ER_7:
328 		default:
329 			myParser.setPipeParserAsPrimary();
330 			break;
331 		}
332 	}
333 
334 	public void updateSourceMessage(String theNewSource, int theChangeStart, int theChangeEnd) throws PropertyVetoException {
335 
336 		theNewSource = theNewSource.replace('\n', '\r');
337 		theNewSource = theNewSource.replace("\n", "");
338 
339 		if (mySourceMessage.equals(theNewSource.trim())) {
340 			return;
341 		}
342 
343 		if (true) {
344 			setSourceMessage(theNewSource);
345 		}
346 
347 		mySourceMessage = theNewSource;
348 
349 		// System.out.println(theNewSource.replace("\r", "\n"));
350 
351 		try {
352 			Entry profile = determineRuntimeProfile(theNewSource);
353 			if (profile != null) {
354 				myParsedMessage = ConformanceMessage.newInstanceFromStaticDef(profile.getProfileProxy().getProfile().getMessage(), profile.getTablesId());
355 				myParsedMessage.setParser(myParser);
356 				myParsedMessage.parse(theNewSource);
357 			} else {
358 				myParsedMessage.parse(theNewSource);
359 			}
360 		} catch (HL7Exception e) {
361 			ourLog.error("Failed to parse message", e);
362 		} catch (IOException e) {
363 			ourLog.error("Failed to parse message", e);
364 		} catch (ProfileException e) {
365 			ourLog.error("Failed to parse message", e);
366 		}
367 
368 		firePropertyChange(PARSED_MESSAGE_PROPERTY, null, null);
369 	}
370 
371 	public void updateSourceMessageBasedOnParsedMessage() {
372 		String newMessage;
373 		try {
374 			newMessage = myParsedMessage.encode();
375 
376 			String oldValue = mySourceMessage;
377 			mySourceMessage = newMessage;
378 			firePropertyChange(SOURCE_MESSAGE_PROPERTY, oldValue, myParsedMessage);
379 
380 			recalculateIndexes();
381 
382 		} catch (HL7Exception e) {
383 			ourLog.error("Failed to update parsed message", e);
384 		}
385 	}
386 
387 	static Range findFieldRange(List<Integer> theField, int theRepNum, Range theSegmentRange, String theSourceMessage, Message theParsedMessage) {
388 		EncodingCharacters enc;
389 		try {
390 			enc = EncodingCharacters.getInstance(theParsedMessage);
391 		} catch (HL7Exception e) {
392 			ourLog.error("Failed to find field", e);
393 			return null;
394 		}
395 
396 		int componentPathIndex = 0;
397 		Range currentRange = theSegmentRange;
398 		for (Integer next : theField) {
399 
400 			char sep;
401 			int offset = next;
402 			switch (componentPathIndex) {
403 			case 0:
404 				sep = enc.getFieldSeparator();
405 				if (!theSegmentRange.applyTo(theSourceMessage).startsWith("MSH")) {
406 					offset++;
407 				}
408 				break;
409 			case 1:
410 				sep = enc.getComponentSeparator();
411 				break;
412 			case 2:
413 			default:
414 				sep = enc.getSubcomponentSeparator();
415 				break;
416 			}
417 
418 			while (offset > 0) {
419 
420 				int nextSeparatorIndex = theSourceMessage.indexOf(sep, currentRange.getStart());
421 				if (nextSeparatorIndex == -1 || nextSeparatorIndex > currentRange.getEnd()) {
422 					if (offset > 1) {
423 						nextSeparatorIndex = currentRange.getEnd() - 1;
424 					} else {
425 						nextSeparatorIndex = currentRange.getEnd();
426 					}
427 				}
428 
429 				if (offset > 1) {
430 					currentRange = new Range(nextSeparatorIndex + 1, currentRange.getEnd());
431 				} else {
432 
433 					if (componentPathIndex == 0) {
434 						char repSep = enc.getRepetitionSeparator();
435 						for (int i = 1; i <= theRepNum; i++) {
436 							int repIndex = theSourceMessage.indexOf(repSep, currentRange.getStart());
437 							if (repIndex == -1) {
438 								if (i == theRepNum) {
439 									currentRange = new Range(currentRange.getStart(), nextSeparatorIndex);
440 								} else {
441 									// rep is beyond what the message actually
442 									// has.. this probably shouldn't happen
443 									return new Range(currentRange.getEnd(), currentRange.getEnd());
444 								}
445 							} else if (i == theRepNum) {
446 								currentRange = new Range(currentRange.getStart(), repIndex);
447 							} else {
448 								currentRange = new Range(repIndex + 1, currentRange.getEnd());
449 							}
450 						}
451 					} else {
452 						
453 						currentRange = new Range(currentRange.getStart(), nextSeparatorIndex);
454 						
455 					}
456 				}
457 
458 				offset--;
459 
460 				if (ourLog.isDebugEnabled()) {
461 					String applyTo = currentRange.applyTo(theSourceMessage);
462 					ourLog.debug("New range is " + applyTo + " (" + applyTo.length() + " chars)");
463 				}
464 				
465 			}
466 			componentPathIndex++;
467 			
468 			if (ourLog.isDebugEnabled()) {
469 				String applyTo = currentRange.applyTo(theSourceMessage);
470 				ourLog.debug("New range is " + applyTo + " (" + applyTo.length() + " chars)");
471 			}
472 
473 		}
474 		return currentRange;
475 	}
476 
477 }