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 "ConformanceProfileRule.java". Description: 010"A MessageRule that checks conformance to message profiles." 011 012The Initial Developer of the Original Code is University Health Network. Copyright (C) 0132005. 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*/ 026package ca.uhn.hl7v2.validation.impl; 027 028import java.io.IOException; 029import java.util.ArrayList; 030import java.util.Arrays; 031import java.util.LinkedHashMap; 032import java.util.List; 033import java.util.Map; 034 035import org.slf4j.Logger; 036import org.slf4j.LoggerFactory; 037 038import ca.uhn.hl7v2.HL7Exception; 039import ca.uhn.hl7v2.HapiContext; 040import ca.uhn.hl7v2.conf.ProfileException; 041import ca.uhn.hl7v2.conf.check.Validator; 042import ca.uhn.hl7v2.conf.parser.ProfileParser; 043import ca.uhn.hl7v2.conf.spec.RuntimeProfile; 044import ca.uhn.hl7v2.conf.store.ProfileStore; 045import ca.uhn.hl7v2.model.Message; 046import ca.uhn.hl7v2.util.Terser; 047import ca.uhn.hl7v2.validation.ValidationException; 048 049/** 050 * A MessageRule that checks conformance to message profiles. Messages can either be tested 051 * against the profiles they declare, or against a pre-defined profile. If you want both, 052 * use two <code>ConformanceProfileRule</code>s. 053 * 054 * @author Bryan Tripp 055 * @version $Revision: 1.1 $ updated on $Date: 2007-02-19 02:24:40 $ by $Author: jamesagnew $ 056 */ 057@SuppressWarnings("serial") 058public class ConformanceProfileRule extends AbstractMessageRule { 059 060 private static final Logger log = LoggerFactory.getLogger(ConformanceProfileRule.class); 061 private static final ProfileParser PARSER = new ProfileParser(true); 062 private String myProfileID; 063 private boolean enableCaching = true; 064 065 private static final LinkedHashMap<String, RuntimeProfile> PROFILE_CACHE = new LinkedHashMap<String, RuntimeProfile>(100, 0.75f, true) { 066 @Override 067 protected boolean removeEldestEntry(Map.Entry<String, RuntimeProfile> eldest) { 068 return size() > 100; 069 } 070 }; 071 072 /** 073 * Creates an instance that tests messages against whatever profiles they declare in 074 * MSH-21. The ID declared in MSH-21 is evaluated in a way that the corresponding 075 * profile file is expected to be BASEDIR/profiles/ID.xml. 076 */ 077 public ConformanceProfileRule() { 078 super(); 079 setDescription("Unknown segments found in message"); 080 setSectionReference("HL7 2.5 section 2.12"); 081 } 082 083 /** 084 * @param theProfileID the ID of a constant profile against which to test all messages 085 * (instead of the profiles they declare in MSH-21). The ID is evaluated in a way 086 * that the corresponding profile file is expected to be BASEDIR/profiles/ID.xml. 087 */ 088 public ConformanceProfileRule(String theProfileID) { 089 this(); 090 myProfileID = theProfileID; 091 } 092 093 094 /** 095 * @see ca.uhn.hl7v2.validation.MessageRule#test(ca.uhn.hl7v2.model.Message) 096 */ 097 public ValidationException[] apply(Message msg) { 098 List<ValidationException> problems = new ArrayList<ValidationException>(); 099 String[] ids = {myProfileID}; 100 101 try { 102 if (myProfileID == null) { 103 ids = getDeclaredProfileIDs(msg); 104 } 105 106 for (String id : ids) { 107 log.debug("Testing message against profile: {}", id); 108 try { 109 ValidationException[] shortList = testAgainstProfile(msg, id); 110 log.debug("{} non-conformances", shortList.length); 111 problems.addAll(Arrays.asList(shortList)); 112 } catch (ProfileException e) { 113 problems.add(new ValidationException("Can't validate against profile: " + e.getMessage(), e)); 114 } 115 } 116 } catch (HL7Exception e) { 117 problems.add(new ValidationException("Can't validate against profile: " + e.getMessage(), e)); 118 } 119 120 return problems.toArray(new ValidationException[problems.size()]); 121 } 122 123 private String[] getDeclaredProfileIDs(Message theMessage) throws HL7Exception { 124 Terser t = new Terser(theMessage); 125 boolean noMore = false; 126 int c = 0; 127 List<String> declaredProfiles = new ArrayList<String>(8); 128 while (!noMore) { 129 String path = "MSH-21(" + c++ + ")"; 130 String idRep = t.get(path); 131 //FIXME fails if empty rep precedes full rep ... should add getAll() to Terser and use that 132 if (idRep == null || idRep.equals("")) { 133 noMore = true; 134 } else { 135 declaredProfiles.add(idRep); 136 } 137 } 138 return declaredProfiles.toArray(new String[declaredProfiles.size()]); 139 } 140 141 private synchronized RuntimeProfile getProfile(String profileString) throws ProfileException { 142 RuntimeProfile profile = PROFILE_CACHE.get(profileString); 143 if (profile == null) { 144 profile = PARSER.parse(profileString); 145 if (enableCaching) PROFILE_CACHE.put(profileString, profile); 146 } 147 return profile; 148 } 149 150 private ValidationException[] testAgainstProfile(Message message, String id) throws ProfileException, HL7Exception { 151 HL7Exception[] exceptions; 152 HapiContext context = message.getParser().getHapiContext(); 153 Validator validator = context.getConformanceValidator(); 154 try { 155 ProfileStore profileStore = context.getProfileStore(); 156 String profileString = profileStore.getProfile(id); 157 if (profileString != null) { 158 RuntimeProfile profile = getProfile(profileString); 159 exceptions = validator.validate(message, profile.getMessage()); 160 } else { 161 throw new ProfileException("Unable to find the profile " + id); 162 } 163 } catch (IOException e) { 164 throw new ProfileException("Error retreiving profile " + id, e); 165 } 166 167 ValidationException[] result = new ValidationException[exceptions.length]; 168 for (int i = 0; i < exceptions.length; i++) { 169 result[i] = ValidationException.fromHL7Exception(exceptions[i]); 170 } 171 return result; 172 } 173 174 175 /** 176 * @see ca.uhn.hl7v2.validation.Rule#getDescription() 177 */ 178 public String getDescription() { 179 return "expected conformance to declared or predefined message profiles"; 180 } 181 182 /** 183 * @see ca.uhn.hl7v2.validation.Rule#getSectionReference() 184 */ 185 public String getSectionReference() { 186 return "HL7 2.5 section 2.12"; 187 } 188 189 public String getProfileID() { 190 return myProfileID; 191 } 192 193 public void setEnableCaching(boolean enableCaching) { 194 this.enableCaching = enableCaching; 195 } 196}