001/* ***** BEGIN LICENSE BLOCK ***** 002 * Version: MPL 1.1/GPL 2.0/LGPL 2.1 003 * 004 * The contents of this file are subject to the Mozilla Public License Version 005 * 1.1 (the "License"); you may not use this file except in compliance with 006 * the License. You may obtain a copy of the License at 007 * http://www.mozilla.org/MPL/ 008 * 009 * Software distributed under the License is distributed on an "AS IS" basis, 010 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 011 * for the specific language governing rights and limitations under the 012 * License. 013 * 014 * The Original Code is part of dcm4che, an implementation of DICOM(TM) in 015 * Java(TM), hosted at https://github.com/gunterze/dcm4che. 016 * 017 * The Initial Developer of the Original Code is 018 * Agfa Healthcare. 019 * Portions created by the Initial Developer are Copyright (C) 2012 020 * the Initial Developer. All Rights Reserved. 021 * 022 * Contributor(s): 023 * See @authors listed below 024 * 025 * Alternatively, the contents of this file may be used under the terms of 026 * either the GNU General Public License Version 2 or later (the "GPL"), or 027 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), 028 * in which case the provisions of the GPL or the LGPL are applicable instead 029 * of those above. If you wish to allow use of your version of this file only 030 * under the terms of either the GPL or the LGPL, and not to allow others to 031 * use your version of this file under the terms of the MPL, indicate your 032 * decision by deleting the provisions above and replace them with the notice 033 * and other provisions required by the GPL or the LGPL. If you do not delete 034 * the provisions above, a recipient may use your version of this file under 035 * the terms of any one of the MPL, the GPL or the LGPL. 036 * 037 * ***** END LICENSE BLOCK ***** */ 038 039package org.dcm4che3.tool.hl7rcv; 040 041import java.io.ByteArrayInputStream; 042import java.io.ByteArrayOutputStream; 043import java.io.File; 044import java.io.FileNotFoundException; 045import java.io.FileOutputStream; 046import java.io.IOException; 047import java.io.InputStreamReader; 048import java.io.OutputStreamWriter; 049import java.net.MalformedURLException; 050import java.net.Socket; 051import java.net.URL; 052import java.util.Date; 053import java.util.ResourceBundle; 054import java.util.concurrent.ExecutorService; 055import java.util.concurrent.Executors; 056import java.util.concurrent.ScheduledExecutorService; 057 058import javax.xml.transform.Templates; 059import javax.xml.transform.Transformer; 060import javax.xml.transform.TransformerFactory; 061import javax.xml.transform.sax.SAXResult; 062import javax.xml.transform.sax.SAXTransformerFactory; 063import javax.xml.transform.sax.TransformerHandler; 064import javax.xml.transform.stream.StreamSource; 065 066import org.apache.commons.cli.CommandLine; 067import org.apache.commons.cli.MissingOptionException; 068import org.apache.commons.cli.OptionBuilder; 069import org.apache.commons.cli.Options; 070import org.apache.commons.cli.ParseException; 071import org.dcm4che3.hl7.HL7Charset; 072import org.dcm4che3.hl7.HL7ContentHandler; 073import org.dcm4che3.hl7.HL7Exception; 074import org.dcm4che3.hl7.HL7Message; 075import org.dcm4che3.hl7.HL7Parser; 076import org.dcm4che3.hl7.HL7Segment; 077import org.dcm4che3.io.SAXTransformer; 078import org.dcm4che3.net.Connection; 079import org.dcm4che3.net.Device; 080import org.dcm4che3.net.Connection.Protocol; 081import org.dcm4che3.net.hl7.HL7Application; 082import org.dcm4che3.net.hl7.HL7DeviceExtension; 083import org.dcm4che3.net.hl7.HL7MessageListener; 084import org.dcm4che3.tool.common.CLIUtils; 085import org.dcm4che3.util.StringUtils; 086 087/** 088 * @author Gunter Zeilinger <gunterze@gmail.com> 089 * 090 */ 091public class HL7Rcv { 092 093 private static final ResourceBundle rb = 094 ResourceBundle.getBundle("org.dcm4che3.tool.hl7rcv.messages"); 095 private static SAXTransformerFactory factory = 096 (SAXTransformerFactory) TransformerFactory.newInstance(); 097 098 private final Device device = new Device("hl7rcv"); 099 private final HL7DeviceExtension hl7Ext = new HL7DeviceExtension(); 100 private final HL7Application hl7App = new HL7Application("*"); 101 private final Connection conn = new Connection(); 102 private String storageDir; 103 private String charset; 104 private Templates tpls; 105 private String[] xsltParams; 106 private final HL7MessageListener handler = new HL7MessageListener() { 107 108 @Override 109 public byte[] onMessage(HL7Application hl7App, Connection conn, 110 Socket s, HL7Segment msh, byte[] msg, int off, int len, 111 int mshlen) throws HL7Exception { 112 try { 113 return HL7Rcv.this.onMessage(msh, msg, off, len); 114 } catch (Exception e) { 115 throw new HL7Exception(HL7Exception.AE, e); 116 } 117 } 118 }; 119 120 public HL7Rcv() throws IOException { 121 conn.setProtocol(Protocol.HL7); 122 device.addDeviceExtension(hl7Ext); 123 device.addConnection(conn); 124 hl7Ext.addHL7Application(hl7App); 125 hl7App.setAcceptedMessageTypes("*"); 126 hl7App.addConnection(conn); 127 hl7App.setHL7MessageListener(handler); 128 } 129 130 public void setStorageDirectory(String storageDir) { 131 this.storageDir = storageDir; 132 } 133 134 public void setXSLT(URL xslt) throws Exception { 135 tpls = SAXTransformer.newTemplates( 136 new StreamSource(xslt.openStream(), xslt.toExternalForm())); 137 } 138 139 public void setXSLTParameters(String[] xsltParams) { 140 this.xsltParams = xsltParams; 141 } 142 143 public void setCharacterSet(String charset) { 144 this.charset = charset; 145 } 146 147 private static CommandLine parseComandLine(String[] args) 148 throws ParseException { 149 Options opts = new Options(); 150 addOptions(opts); 151 CLIUtils.addSocketOptions(opts); 152 CLIUtils.addTLSOptions(opts); 153 CLIUtils.addCommonOptions(opts); 154 return CLIUtils.parseComandLine(args, opts, rb, HL7Rcv.class); 155 } 156 157 @SuppressWarnings("static-access") 158 public static void addOptions(Options opts) { 159 opts.addOption(null, "ignore", false, rb.getString("ignore")); 160 opts.addOption(OptionBuilder 161 .hasArg() 162 .withArgName("path") 163 .withDescription(rb.getString("directory")) 164 .withLongOpt("directory") 165 .create(null)); 166 opts.addOption(OptionBuilder 167 .withLongOpt("xsl") 168 .hasArg() 169 .withArgName("xsl-file") 170 .withDescription(rb.getString("xsl")) 171 .create("x")); 172 opts.addOption(OptionBuilder 173 .withLongOpt("xsl-param") 174 .hasArgs() 175 .withValueSeparator('=') 176 .withArgName("name=value") 177 .withDescription(rb.getString("xsl-param")) 178 .create(null)); 179 opts.addOption(OptionBuilder 180 .withLongOpt("charset") 181 .hasArg() 182 .withArgName("name") 183 .withDescription(rb.getString("charset")) 184 .create(null)); 185 opts.addOption(OptionBuilder 186 .hasArg() 187 .withArgName("[ip:]port") 188 .withDescription(rb.getString("bind-server")) 189 .withLongOpt("bind") 190 .create("b")); 191 opts.addOption(OptionBuilder 192 .hasArg() 193 .withArgName("ms") 194 .withDescription(rb.getString("idle-timeout")) 195 .withLongOpt("idle-timeout") 196 .create(null)); 197 } 198 199 public static void main(String[] args) { 200 try { 201 CommandLine cl = parseComandLine(args); 202 HL7Rcv main = new HL7Rcv(); 203 configure(main, cl); 204 ExecutorService executorService = Executors.newCachedThreadPool(); 205 ScheduledExecutorService scheduledExecutorService = 206 Executors.newSingleThreadScheduledExecutor(); 207 main.device.setScheduledExecutor(scheduledExecutorService); 208 main.device.setExecutor(executorService); 209 main.device.bindConnections(); 210 } catch (ParseException e) { 211 System.err.println("hl7rcv: " + e.getMessage()); 212 System.err.println(rb.getString("try")); 213 System.exit(2); 214 } catch (Exception e) { 215 System.err.println("hl7rcv: " + e.getMessage()); 216 e.printStackTrace(); 217 System.exit(2); 218 } 219 } 220 221 private static void configure(HL7Rcv main, CommandLine cl) 222 throws Exception, MalformedURLException, ParseException, 223 IOException { 224 if (!cl.hasOption("ignore")) 225 main.setStorageDirectory( 226 cl.getOptionValue("directory", ".")); 227 if (cl.hasOption("x")) { 228 String s = cl.getOptionValue("x"); 229 main.setXSLT(new File(s).toURI().toURL()); 230 main.setXSLTParameters(cl.getOptionValues("xsl-param")); 231 } 232 main.setCharacterSet(cl.getOptionValue("charset")); 233 configureBindServer(main.conn, cl); 234 CLIUtils.configure(main.conn, cl); 235 } 236 237 private static void configureBindServer(Connection conn, CommandLine cl) 238 throws ParseException { 239 if (!cl.hasOption("b")) 240 throw new MissingOptionException( 241 CLIUtils.rb.getString("missing-bind-opt")); 242 String aeAtHostPort = cl.getOptionValue("b"); 243 String[] hostAndPort = StringUtils.split(aeAtHostPort, ':'); 244 int portIndex = hostAndPort.length - 1; 245 conn.setPort(Integer.parseInt(hostAndPort[portIndex])); 246 if (portIndex > 0) 247 conn.setHostname(hostAndPort[0]); 248 } 249 250 private byte[] onMessage(HL7Segment msh, byte[] msg, int off, int len) 251 throws Exception { 252 if (storageDir != null) 253 storeToFile(msg, off, len, 254 new File( 255 new File(storageDir, msh.getMessageType()), 256 msh.getField(9, "_NULL_"))); 257 return (tpls == null) 258 ? HL7Message.makeACK(msh, HL7Exception.AA, null).getBytes(null) 259 : xslt(msh, msg, off, len); 260 } 261 262 private void storeToFile(byte[] msg, int off, int len, File f) 263 throws FileNotFoundException, IOException { 264 Connection.LOG.info("M-WRITE {}", f); 265 f.getParentFile().mkdirs(); 266 FileOutputStream out = new FileOutputStream(f); 267 try { 268 out.write(msg, off, len); 269 } finally { 270 out.close(); 271 } 272 } 273 274 private byte[] xslt(HL7Segment msh, byte[] msg, int off, int len) 275 throws Exception { 276 String charsetName = HL7Charset.toCharsetName(msh.getField(17, charset)); 277 ByteArrayOutputStream out = new ByteArrayOutputStream(); 278 TransformerHandler th = factory.newTransformerHandler(tpls); 279 Transformer t = th.getTransformer(); 280 t.setParameter("MessageControlID", HL7Segment.nextMessageControlID()); 281 t.setParameter("DateTimeOfMessage", HL7Segment.timeStamp(new Date())); 282 if (xsltParams != null) 283 for (int i = 1; i < xsltParams.length; i++, i++) 284 t.setParameter(xsltParams[i-1], xsltParams[i]); 285 th.setResult(new SAXResult(new HL7ContentHandler( 286 new OutputStreamWriter(out, charsetName)))); 287 new HL7Parser(th).parse(new InputStreamReader( 288 new ByteArrayInputStream(msg, off, len), 289 charsetName)); 290 return out.toByteArray(); 291 } 292 293 public Device getDevice() { 294 return device; 295 } 296 297 public Connection getConn() { 298 return conn; 299 } 300}