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.net.hl7; 040 041import org.dcm4che3.conf.core.api.ConfigurableClass; 042import org.dcm4che3.conf.core.api.ConfigurableProperty; 043import org.dcm4che3.conf.core.api.LDAP; 044import org.dcm4che3.hl7.HL7Exception; 045import org.dcm4che3.hl7.HL7Segment; 046import org.dcm4che3.hl7.MLLPConnection; 047import org.dcm4che3.net.CompatibleConnection; 048import org.dcm4che3.net.Connection; 049import org.dcm4che3.net.Device; 050import org.dcm4che3.net.IncompatibleConnectionException; 051 052import java.io.IOException; 053import java.io.Serializable; 054import java.net.Socket; 055import java.security.GeneralSecurityException; 056import java.util.*; 057 058/** 059 * @author Gunter Zeilinger <gunterze@gmail.com> 060 */ 061@LDAP(objectClasses = "hl7Application") 062@ConfigurableClass 063public class HL7Application implements Serializable { 064 065 private static final long serialVersionUID = -1765110968524548056L; 066 067 private Device device; 068 069 @ConfigurableProperty(name = "hl7ApplicationName", 070 label = "HL7 application name", 071 description = "HL7 Application and Facility name (Application^Facility)" 072 ) 073 private String applicationName; 074 075 @ConfigurableProperty(name = "hl7DefaultCharacterSet", 076 label = "Default character set", 077 description = "Character Set used to decode received messages if not specified by MSH-18, ASCII if absent") 078 private String HL7DefaultCharacterSet; 079 080 @ConfigurableProperty(name = "dicomInstalled") 081 private Boolean hl7Installed; 082 083 @ConfigurableProperty(name = "hl7AcceptedSendingApplication", 084 label = "Accepted applications", 085 description = "Application^Facility name of accepted Sending Application(s); any if absent") 086 private Set<String> acceptedSendingApplicationsSet = 087 new LinkedHashSet<String>(); 088 089 @ConfigurableProperty(name = "hl7AcceptedMessageType", 090 label = "Accepted message types", 091 description = "Message Type(s) (MessageType^TriggerEvent) of accepted messages") 092 private Set<String> acceptedMessageTypesSet = 093 new LinkedHashSet<String>(); 094 095 @ConfigurableProperty(name = "dicomNetworkConnectionReference", 096 label = "Connections", 097 description = "Which connections are used by this HL7 application", 098 collectionOfReferences = true) 099 private List<Connection> conns = new ArrayList<Connection>(1); 100 101 @ConfigurableProperty(name = "hl7AppExtensions", isExtensionsProperty = true) 102 private Map<Class<? extends HL7ApplicationExtension>, HL7ApplicationExtension> extensions = 103 new HashMap<Class<? extends HL7ApplicationExtension>, HL7ApplicationExtension>(); 104 105 private transient HL7MessageListener hl7MessageListener; 106 107 public HL7Application() { 108 } 109 110 public HL7Application(String applicationName) { 111 setApplicationName(applicationName); 112 } 113 114 public final Device getDevice() { 115 return device; 116 } 117 118 public Map<Class<? extends HL7ApplicationExtension>, HL7ApplicationExtension> getExtensions() { 119 return extensions; 120 } 121 122 public void setExtensions(Map<Class<? extends HL7ApplicationExtension>, HL7ApplicationExtension> extensions) { 123 this.extensions = extensions; 124 } 125 126 void setDevice(Device device) { 127 if (device != null) { 128 if (this.device != null) 129 throw new IllegalStateException("already owned by " + 130 this.device.getDeviceName()); 131 for (Connection conn : conns) 132 if (conn.getDevice() != device) 133 throw new IllegalStateException(conn + " not owned by " + 134 device.getDeviceName()); 135 } 136 this.device = device; 137 } 138 139 public String getApplicationName() { 140 return applicationName; 141 } 142 143 public void setApplicationName(String name) { 144 if (name.isEmpty()) 145 throw new IllegalArgumentException("name cannot be empty"); 146 this.applicationName = name; 147 } 148 149 public Set<String> getAcceptedMessageTypesSet() { 150 return acceptedMessageTypesSet; 151 } 152 153 public void setAcceptedMessageTypesSet(Set<String> acceptedMessageTypesSet) { 154 this.acceptedMessageTypesSet = acceptedMessageTypesSet; 155 } 156 157 public final String getHL7DefaultCharacterSet() { 158 return HL7DefaultCharacterSet; 159 } 160 161 public final void setHL7DefaultCharacterSet(String hl7DefaultCharacterSet) { 162 this.HL7DefaultCharacterSet = hl7DefaultCharacterSet; 163 } 164 165 public Set<String> getAcceptedSendingApplicationsSet() { 166 return acceptedSendingApplicationsSet; 167 } 168 169 public void setAcceptedSendingApplicationsSet(Set<String> acceptedSendingApplicationsSet) { 170 this.acceptedSendingApplicationsSet = acceptedSendingApplicationsSet; 171 } 172 173 public String[] getAcceptedSendingApplications() { 174 return acceptedSendingApplicationsSet.toArray( 175 new String[acceptedSendingApplicationsSet.size()]); 176 } 177 178 public void setAcceptedSendingApplications(String... names) { 179 acceptedSendingApplicationsSet.clear(); 180 Collections.addAll(acceptedSendingApplicationsSet, names); 181 } 182 183 public String[] getAcceptedMessageTypes() { 184 return acceptedMessageTypesSet.toArray( 185 new String[acceptedMessageTypesSet.size()]); 186 } 187 188 public void setAcceptedMessageTypes(String... types) { 189 acceptedMessageTypesSet.clear(); 190 Collections.addAll(acceptedMessageTypesSet, types); 191 } 192 193 public boolean isInstalled() { 194 return device != null && device.isInstalled() 195 && (hl7Installed == null || hl7Installed.booleanValue()); 196 } 197 198 public Boolean getHl7Installed() { 199 return hl7Installed; 200 } 201 202 public void setHl7Installed(Boolean hl7Installed) { 203 if (hl7Installed != null && hl7Installed.booleanValue() 204 && device != null && !device.isInstalled()) 205 throw new IllegalStateException("owning device not installed"); 206 this.hl7Installed = hl7Installed; 207 } 208 209 public HL7MessageListener getHL7MessageListener() { 210 HL7MessageListener listener = hl7MessageListener; 211 if (listener != null) 212 return listener; 213 214 HL7DeviceExtension hl7Ext = device.getDeviceExtension(HL7DeviceExtension.class); 215 return hl7Ext != null 216 ? hl7Ext.getHL7MessageListener() 217 : null; 218 } 219 220 public final void setHL7MessageListener(HL7MessageListener listener) { 221 this.hl7MessageListener = listener; 222 } 223 224 public void addConnection(Connection conn) { 225 if (conn.getProtocol() != Connection.Protocol.HL7) 226 throw new IllegalArgumentException( 227 "protocol != HL7 - " + conn.getProtocol()); 228 229 if (device != null && device != conn.getDevice()) 230 throw new IllegalStateException(conn + " not contained by " + 231 device.getDeviceName()); 232 conns.add(conn); 233 } 234 235 public boolean removeConnection(Connection conn) { 236 return conns.remove(conn); 237 } 238 239 public List<Connection> getConnections() { 240 return conns; 241 } 242 243 public List<Connection> getConns() { 244 return conns; 245 } 246 247 public void setConns(List<Connection> conns) { 248 this.conns.clear(); 249 for (Connection conn : conns) addConnection(conn); 250 } 251 252 byte[] onMessage(Connection conn, Socket s, HL7Segment msh, byte[] msg, int off, 253 int len, int mshlen) throws HL7Exception { 254 if (!(isInstalled() && conns.contains(conn))) 255 throw new HL7Exception(HL7Exception.AR, "Receiving Application not recognized"); 256 if (!(acceptedSendingApplicationsSet.isEmpty() 257 || acceptedSendingApplicationsSet.contains(msh.getSendingApplicationWithFacility()))) 258 throw new HL7Exception(HL7Exception.AR, "Sending Application not recognized"); 259 if (!(acceptedMessageTypesSet.contains("*") 260 || acceptedMessageTypesSet.contains(msh.getMessageType()))) 261 throw new HL7Exception(HL7Exception.AR, "Message Type not supported"); 262 263 HL7MessageListener listener = getHL7MessageListener(); 264 if (listener == null) 265 throw new HL7Exception(HL7Exception.AE, "No HL7 Message Listener configured"); 266 return listener.onMessage(this, conn, s, msh, msg, off, len, mshlen); 267 } 268 269 public MLLPConnection connect(Connection remote) 270 throws IOException, IncompatibleConnectionException, GeneralSecurityException { 271 return connect(findCompatibelConnection(remote), remote); 272 } 273 274 public MLLPConnection connect(HL7Application remote) 275 throws IOException, IncompatibleConnectionException, GeneralSecurityException { 276 CompatibleConnection cc = findCompatibelConnection(remote); 277 return connect(cc.getLocalConnection(), cc.getRemoteConnection()); 278 } 279 280 public MLLPConnection connect(Connection local, Connection remote) 281 throws IOException, IncompatibleConnectionException, GeneralSecurityException { 282 checkDevice(); 283 checkInstalled(); 284 Socket sock = local.connect(remote); 285 sock.setSoTimeout(local.getResponseTimeout()); 286 return new MLLPConnection(sock); 287 } 288 289 public CompatibleConnection findCompatibelConnection(HL7Application remote) 290 throws IncompatibleConnectionException { 291 for (Connection remoteConn : remote.conns) 292 if (remoteConn.isInstalled() && remoteConn.isServer()) 293 for (Connection conn : conns) 294 if (conn.isInstalled() && conn.isCompatible(remoteConn)) 295 return new CompatibleConnection(conn, remoteConn); 296 throw new IncompatibleConnectionException( 297 "No compatible connection to " + remote + " available on " + this); 298 } 299 300 public Connection findCompatibelConnection(Connection remoteConn) 301 throws IncompatibleConnectionException { 302 for (Connection conn : conns) 303 if (conn.isInstalled() && conn.isCompatible(remoteConn)) 304 return conn; 305 throw new IncompatibleConnectionException( 306 "No compatible connection to " + remoteConn + " available on " + this); 307 } 308 309 private void checkInstalled() { 310 if (!isInstalled()) 311 throw new IllegalStateException("Not installed"); 312 } 313 314 private void checkDevice() { 315 if (device == null) 316 throw new IllegalStateException("Not attached to Device"); 317 } 318 319 void reconfigure(HL7Application src) { 320 setHL7ApplicationAttributes(src); 321 device.reconfigureConnections(conns, src.conns); 322 reconfigureHL7ApplicationExtensions(src); 323 } 324 325 private void reconfigureHL7ApplicationExtensions(HL7Application from) { 326 for (Iterator<Class<? extends HL7ApplicationExtension>> it = 327 extensions.keySet().iterator(); it.hasNext(); ) { 328 if (!from.extensions.containsKey(it.next())) 329 it.remove(); 330 } 331 for (HL7ApplicationExtension src : from.extensions.values()) { 332 Class<? extends HL7ApplicationExtension> clazz = src.getClass(); 333 HL7ApplicationExtension ext = extensions.get(clazz); 334 if (ext == null) 335 try { 336 addHL7ApplicationExtension(ext = clazz.newInstance()); 337 } catch (Exception e) { 338 throw new RuntimeException( 339 "Failed to instantiate " + clazz.getName(), e); 340 } 341 ext.reconfigure(src); 342 } 343 } 344 345 protected void setHL7ApplicationAttributes(HL7Application src) { 346 setHL7DefaultCharacterSet(src.HL7DefaultCharacterSet); 347 setAcceptedSendingApplications(src.getAcceptedSendingApplications()); 348 setAcceptedMessageTypes(src.getAcceptedMessageTypes()); 349 setHl7Installed(src.hl7Installed); 350 } 351 352 public void addHL7ApplicationExtension(HL7ApplicationExtension ext) { 353 Class<? extends HL7ApplicationExtension> clazz = ext.getClass(); 354 if (extensions.containsKey(clazz)) 355 throw new IllegalStateException( 356 "already contains AE Extension:" + clazz); 357 358 ext.setHL7Application(this); 359 extensions.put(clazz, ext); 360 } 361 362 public boolean removeHL7ApplicationExtension(HL7ApplicationExtension ext) { 363 if (extensions.remove(ext.getClass()) == null) 364 return false; 365 366 ext.setHL7Application(null); 367 return true; 368 } 369 370 public Collection<HL7ApplicationExtension> listHL7ApplicationExtensions() { 371 return extensions.values(); 372 } 373 374 @SuppressWarnings("unchecked") 375 public <T extends HL7ApplicationExtension> T getHL7ApplicationExtension(Class<T> clazz) { 376 return (T) extensions.get(clazz); 377 } 378}