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}