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) 2011
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;
040
041import org.dcm4che3.conf.core.api.ConfigurableClass;
042import org.dcm4che3.conf.core.api.ConfigurableProperty;
043import org.dcm4che3.conf.core.api.ConfigurableProperty.ConfigurablePropertyType;
044import org.dcm4che3.conf.core.api.ConfigurableProperty.Tag;
045import org.dcm4che3.conf.core.api.LDAP;
046import org.dcm4che3.data.Code;
047import org.dcm4che3.data.Issuer;
048import org.dcm4che3.util.StringUtils;
049
050import javax.net.ssl.KeyManager;
051import javax.net.ssl.SSLContext;
052import javax.net.ssl.TrustManager;
053import java.io.IOException;
054import java.io.Serializable;
055import java.security.GeneralSecurityException;
056import java.security.KeyManagementException;
057import java.security.cert.X509Certificate;
058import java.util.*;
059import java.util.Map.Entry;
060import java.util.concurrent.Executor;
061import java.util.concurrent.ScheduledExecutorService;
062import java.util.concurrent.ScheduledFuture;
063import java.util.concurrent.TimeUnit;
064
065/**
066 * DICOM Part 15, Annex H compliant description of a DICOM enabled system or
067 * device. This is used to describe a DICOM-enabled network endpoint in terms of
068 * its physical attributes (serial number, manufacturer, etc.), its context
069 * (issuer of patient ids used by the device, etc.), as well as its capabilities
070 * (TLS-enabled, AE titles used, etc.).
071 *
072 * @author Gunter Zeilinger <gunterze@gmail.com>
073 */
074@LDAP(
075        objectClasses = {"dcmDevice", "dicomDevice"},
076        distinguishingField = "dicomDeviceName")
077@ConfigurableClass(referable = true)
078public class Device implements Serializable {
079
080    private static final long serialVersionUID = -5816872456184522866L;
081
082    @ConfigurableProperty(name = "dicomDeviceName", label = "Device name", tags = Tag.PRIMARY)
083    private String deviceName;
084
085    /**
086     * Temporarily gets assigned the value of device name with a prefix
087     * @see Device#setDeviceName(String)
088     */
089    @ConfigurableProperty(type = ConfigurablePropertyType.UUID)
090    private String uuid;
091
092    @ConfigurableProperty(name = "dicomDescription")
093    private String description;
094
095    @ConfigurableProperty(type = ConfigurablePropertyType.OptimisticLockingHash)
096    private String olockHash;
097
098    @ConfigurableProperty(name = "dicomManufacturer")
099    private String manufacturer;
100
101    @ConfigurableProperty(name = "dicomManufacturerModelName")
102    private String manufacturerModelName;
103
104    @ConfigurableProperty(name = "dicomStationName")
105    private String stationName;
106
107    @ConfigurableProperty(name = "dicomDeviceSerialNumber")
108    private String deviceSerialNumber;
109
110    @ConfigurableProperty(name = "dcmTrustStoreURL")
111    private String trustStoreURL;
112
113    @ConfigurableProperty(name = "dcmTrustStoreType")
114    private String trustStoreType;
115
116    @ConfigurableProperty(name = "dcmTrustStorePin")
117    private String trustStorePin;
118
119    @ConfigurableProperty(name = "dcmTrustStorePinProperty")
120    private String trustStorePinProperty;
121
122    @ConfigurableProperty(name = "dcmKeyStoreURL")
123    private String keyStoreURL;
124
125    @ConfigurableProperty(name = "dcmKeyStoreType")
126    private String keyStoreType;
127
128    @ConfigurableProperty(name = "dcmKeyStorePin")
129    private String keyStorePin;
130
131    @ConfigurableProperty(name = "dcmKeyStorePinProperty")
132    private String keyStorePinProperty;
133
134    @ConfigurableProperty(name = "dcmKeyStoreKeyPin")
135    private String keyStoreKeyPin;
136
137    @ConfigurableProperty(name = "dcmKeyStoreKeyPinProperty")
138    private String keyStoreKeyPinProperty;
139
140    @ConfigurableProperty(name = "dicomIssuerOfPatientID")
141    private Issuer issuerOfPatientID;
142
143    @ConfigurableProperty(name = "dicomIssuerOfAccessionNumber")
144    private Issuer issuerOfAccessionNumber;
145
146    @ConfigurableProperty(name = "dicomOrderPlacerIdentifier")
147    private Issuer orderPlacerIdentifier;
148
149    @ConfigurableProperty(name = "dicomOrderFillerIdentifier")
150    private Issuer orderFillerIdentifier;
151
152    @ConfigurableProperty(name = "dicomIssuerOfAdmissionID")
153    private Issuer issuerOfAdmissionID;
154
155    @ConfigurableProperty(name = "dicomIssuerOfServiceEpisodeID")
156    private Issuer issuerOfServiceEpisodeID;
157
158    @ConfigurableProperty(name = "dicomIssuerOfContainerIdentifier")
159    private Issuer issuerOfContainerIdentifier;
160
161    @ConfigurableProperty(name = "dicomIssuerOfSpecimenIdentifier")
162    private Issuer issuerOfSpecimenIdentifier;
163
164    @ConfigurableProperty(name = "dicomSoftwareVersion")
165    private String[] softwareVersions = {};
166
167    @ConfigurableProperty(name = "dicomPrimaryDeviceType")
168    private String[] primaryDeviceTypes = {};
169
170    @ConfigurableProperty(name = "dicomInstitutionName")
171    private String[] institutionNames = {};
172
173    @ConfigurableProperty(name = "dicomInstitutionCode")
174    private Code[] institutionCodes = {};
175
176    @ConfigurableProperty(name = "dicomInstitutionAddress")
177    private String[] institutionAddresses = {};
178
179    @ConfigurableProperty(name = "dicomInstitutionDepartmentName")
180    private String[] institutionalDepartmentNames = {};
181
182    @ConfigurableProperty(name = "dicomRelatedDeviceReference")
183    private String[] relatedDeviceRefs = {};
184
185    @ConfigurableProperty(name = "dicomVendorData")
186    private byte[][] vendorData = {};
187
188    @ConfigurableProperty(name = "dcmLimitOpenAssociations")
189    private int limitOpenAssociations;
190
191    @ConfigurableProperty(name = "dicomInstalled")
192    private boolean installed = true;
193
194    @ConfigurableProperty(name = "dcmTimeZoneOfDevice")
195    private TimeZone timeZoneOfDevice;
196
197
198    //TODO: finalize and store x509 cretificates !!
199    private final LinkedHashMap<String, X509Certificate[]> authorizedNodeCertificates =
200            new LinkedHashMap<String, X509Certificate[]>();
201    private final LinkedHashMap<String, X509Certificate[]> thisNodeCertificates =
202            new LinkedHashMap<String, X509Certificate[]>();
203
204
205    @LDAP(noContainerNode = true)
206    @ConfigurableProperty(
207            name = "dicomConnection",
208            label = "Connections"
209    )
210    private final List<Connection> connections = new ArrayList<Connection>();
211
212    /**
213     * Note: This only maps the main AE titles to application entities.
214     * {@link #aliasApplicationEntitiesMap} will contain also alias AE titles.
215     */
216    @LDAP(noContainerNode = true)
217    @ConfigurableProperty(
218            name = "dicomNetworkAE",
219            label = "Application Entities"
220    )
221    private final Map<String, ApplicationEntity> applicationEntitiesMap =
222            new TreeMap<String, ApplicationEntity>();
223
224
225    @ConfigurableProperty(isReference = true,
226            name = "dcmDefaultAE",
227            tags = Tag.PRIMARY,
228            description = "Default AE to be used by both services running locally on this device as well as external services"
229    )
230    private ApplicationEntity defaultAE;
231
232    /**
233     * Maps alias AE titles ({@link ApplicationEntity#getAETitleAliases()}),
234     * including also the main AE title ({@link ApplicationEntity#getAETitle()}), to application entities.
235     */
236    private final transient Map<String, ApplicationEntity> aliasApplicationEntitiesMap = new TreeMap<String, ApplicationEntity>();
237
238    @ConfigurableProperty(name = "deviceExtensions", isExtensionsProperty = true)
239    private Map<Class<? extends DeviceExtension>, DeviceExtension> extensions =
240            new HashMap<Class<? extends DeviceExtension>, DeviceExtension>();
241
242    private transient AssociationHandler associationHandler = new AssociationHandler();
243    private transient DimseRQHandler dimseRQHandler;
244    private transient ConnectionMonitor connectionMonitor;
245
246    private transient int assocCount = 0;
247    private transient final Object assocCountLock = new Object();
248
249    private transient Executor executor;
250    private transient ScheduledExecutorService scheduledExecutor;
251    private transient volatile SSLContext sslContext;
252    private transient volatile KeyManager km;
253    private transient volatile TrustManager tm;
254
255    public Device() {
256    }
257
258    public Device(String name) {
259        setDeviceName(name);
260    }
261
262    private void checkNotEmpty(String name, String val) {
263        if (val != null && val.isEmpty())
264            throw new IllegalArgumentException(name + " cannot be empty");
265    }
266
267    public ApplicationEntity getDefaultAE() {
268        return defaultAE;
269    }
270
271    public void setDefaultAE(ApplicationEntity defaultAE) {
272        this.defaultAE = defaultAE;
273    }
274
275    /**
276     * Get the name of this device.
277     *
278     * @return A String containing the device name.
279     */
280    public final String getDeviceName() {
281        return deviceName;
282    }
283
284    /**
285     * Set the name of this device.
286     *
287     * @param name A String containing the device name.
288     */
289    public final void setDeviceName(String name) {
290        checkNotEmpty("Device Name", name);
291        this.deviceName = name;
292        // temporarily
293        this.uuid = "Device-" + name;
294    }
295
296    /**
297     * Get the description of this device.
298     *
299     * @return A String containing the device description.
300     */
301    public final String getDescription() {
302        return description;
303    }
304
305    /**
306     * Set the description of this device.
307     *
308     * @param description A String containing the device description.
309     */
310    public final void setDescription(String description) {
311        this.description = description;
312    }
313
314    /**
315     * Get the manufacturer of this device.
316     *
317     * @return A String containing the device manufacturer.
318     */
319    public final String getManufacturer() {
320        return manufacturer;
321    }
322
323    /**
324     * Set the manufacturer of this device.
325     * <p/>
326     * This should be the same as the value of Manufacturer (0008,0070) in SOP
327     * instances created by this device.
328     *
329     * @param manufacturer A String containing the device manufacturer.
330     */
331    public final void setManufacturer(String manufacturer) {
332        this.manufacturer = manufacturer;
333    }
334
335    /**
336     * Get the manufacturer model name of this device.
337     *
338     * @return A String containing the device manufacturer model name.
339     */
340    public final String getManufacturerModelName() {
341        return manufacturerModelName;
342    }
343
344    /**
345     * Set the manufacturer model name of this device.
346     * <p/>
347     * This should be the same as the value of Manufacturer Model Name
348     * (0008,1090) in SOP instances created by this device.
349     *
350     * @param manufacturerModelName A String containing the device manufacturer model name.
351     */
352    public final void setManufacturerModelName(String manufacturerModelName) {
353        this.manufacturerModelName = manufacturerModelName;
354    }
355
356    /**
357     * Get the software versions running on (or implemented by) this device.
358     *
359     * @return A String array containing the software versions.
360     */
361    public final String[] getSoftwareVersions() {
362        return softwareVersions;
363    }
364
365    /**
366     * Set the software versions running on (or implemented by) this device.
367     * <p/>
368     * This should be the same as the values of Software Versions (0018,1020) in
369     * SOP instances created by this device.
370     *
371     * @param softwareVersions A String array containing the software versions.
372     */
373    public final void setSoftwareVersions(String... softwareVersions) {
374        this.softwareVersions = softwareVersions;
375    }
376
377    /**
378     * Get the station name belonging to this device.
379     *
380     * @return A String containing the station name.
381     */
382    public final String getStationName() {
383        return stationName;
384    }
385
386    /**
387     * Set the station name belonging to this device.
388     * <p/>
389     * This should be the same as the value of Station Name (0008,1010) in SOP
390     * instances created by this device.
391     *
392     * @param stationName A String containing the station name.
393     */
394    public final void setStationName(String stationName) {
395        this.stationName = stationName;
396    }
397
398    /**
399     * Get the serial number belonging to this device.
400     *
401     * @return A String containing the serial number.
402     */
403    public final String getDeviceSerialNumber() {
404        return deviceSerialNumber;
405    }
406
407    /**
408     * Set the serial number of this device.
409     * <p/>
410     * This should be the same as the value of Device Serial Number (0018,1000)
411     * in SOP instances created by this device.
412     *
413     * @param deviceSerialNumber A String containing the serial number.
414     */
415    public final void setDeviceSerialNumber(String deviceSerialNumber) {
416        this.deviceSerialNumber = deviceSerialNumber;
417    }
418
419    /**
420     * Get the type codes associated with this device.
421     *
422     * @return A String array containing the type codes of this device.
423     */
424    public final String[] getPrimaryDeviceTypes() {
425        return primaryDeviceTypes;
426    }
427
428    /**
429     * Set the type codes associated with this device.
430     * <p/>
431     * Represents the kind of device and is most applicable for acquisition
432     * modalities. Types should be selected from the list of code values
433     * (0008,0100) for Context ID 30 in PS3.16 when applicable.
434     *
435     * @param primaryDeviceTypes
436     */
437    public void setPrimaryDeviceTypes(String... primaryDeviceTypes) {
438        this.primaryDeviceTypes = primaryDeviceTypes;
439    }
440
441    /**
442     * Get the institution name associated with this device; may be the site
443     * where it resides or is operating on behalf of.
444     *
445     * @return A String array containing the institution name values.
446     */
447    public final String[] getInstitutionNames() {
448        return institutionNames;
449    }
450
451    /**
452     * Set the institution name associated with this device; may be the site
453     * where it resides or is operating on behalf of.
454     * <p/>
455     * Should be the same as the value of Institution Name (0008,0080) in SOP
456     * Instances created by this device.
457     *
458     * @param names A String array containing the institution name values.
459     */
460    public void setInstitutionNames(String... names) {
461        institutionNames = names;
462    }
463
464    public final Code[] getInstitutionCodes() {
465        return institutionCodes;
466    }
467
468    public void setInstitutionCodes(Code... codes) {
469        institutionCodes = codes;
470    }
471
472    /**
473     * Set the address of the institution which operates this device.
474     *
475     * @return A String array containing the institution address values.
476     */
477    public final String[] getInstitutionAddresses() {
478        return institutionAddresses;
479    }
480
481    /**
482     * Get the address of the institution which operates this device.
483     * <p/>
484     * Should be the same as the value of Institution Address (0008,0081)
485     * attribute in SOP Instances created by this device.
486     *
487     * @param addresses A String array containing the institution address values.
488     */
489    public void setInstitutionAddresses(String... addresses) {
490        institutionAddresses = addresses;
491    }
492
493    /**
494     * Get the department name associated with this device.
495     *
496     * @return A String array containing the dept. name values.
497     */
498    public final String[] getInstitutionalDepartmentNames() {
499        return institutionalDepartmentNames;
500    }
501
502    /**
503     * Set the department name associated with this device.
504     * <p/>
505     * Should be the same as the value of Institutional Department Name
506     * (0008,1040) in SOP Instances created by this device.
507     *
508     * @param names A String array containing the dept. name values.
509     */
510    public void setInstitutionalDepartmentNames(String... names) {
511        institutionalDepartmentNames = names;
512    }
513
514    public final Issuer getIssuerOfPatientID() {
515        return issuerOfPatientID;
516    }
517
518    public final void setIssuerOfPatientID(Issuer issuerOfPatientID) {
519        this.issuerOfPatientID = issuerOfPatientID;
520    }
521
522    public final Issuer getIssuerOfAccessionNumber() {
523        return issuerOfAccessionNumber;
524    }
525
526    public final void setIssuerOfAccessionNumber(Issuer issuerOfAccessionNumber) {
527        this.issuerOfAccessionNumber = issuerOfAccessionNumber;
528    }
529
530    public final Issuer getOrderPlacerIdentifier() {
531        return orderPlacerIdentifier;
532    }
533
534    public final void setOrderPlacerIdentifier(Issuer orderPlacerIdentifier) {
535        this.orderPlacerIdentifier = orderPlacerIdentifier;
536    }
537
538    public final Issuer getOrderFillerIdentifier() {
539        return orderFillerIdentifier;
540    }
541
542    public final void setOrderFillerIdentifier(Issuer orderFillerIdentifier) {
543        this.orderFillerIdentifier = orderFillerIdentifier;
544    }
545
546    public final Issuer getIssuerOfAdmissionID() {
547        return issuerOfAdmissionID;
548    }
549
550    public final void setIssuerOfAdmissionID(Issuer issuerOfAdmissionID) {
551        this.issuerOfAdmissionID = issuerOfAdmissionID;
552    }
553
554    public final Issuer getIssuerOfServiceEpisodeID() {
555        return issuerOfServiceEpisodeID;
556    }
557
558    public final void setIssuerOfServiceEpisodeID(Issuer issuerOfServiceEpisodeID) {
559        this.issuerOfServiceEpisodeID = issuerOfServiceEpisodeID;
560    }
561
562    public final Issuer getIssuerOfContainerIdentifier() {
563        return issuerOfContainerIdentifier;
564    }
565
566    public final void setIssuerOfContainerIdentifier(Issuer issuerOfContainerIdentifier) {
567        this.issuerOfContainerIdentifier = issuerOfContainerIdentifier;
568    }
569
570    public final Issuer getIssuerOfSpecimenIdentifier() {
571        return issuerOfSpecimenIdentifier;
572    }
573
574    public final void setIssuerOfSpecimenIdentifier(Issuer issuerOfSpecimenIdentifier) {
575        this.issuerOfSpecimenIdentifier = issuerOfSpecimenIdentifier;
576    }
577
578    public X509Certificate[] getAuthorizedNodeCertificates(String ref) {
579        return authorizedNodeCertificates.get(ref);
580    }
581
582    public void setAuthorizedNodeCertificates(String ref, X509Certificate... certs) {
583        authorizedNodeCertificates.put(ref, certs);
584        setTrustManager(null);
585    }
586
587    public X509Certificate[] removeAuthorizedNodeCertificates(String ref) {
588        X509Certificate[] certs = authorizedNodeCertificates.remove(ref);
589        setTrustManager(null);
590        return certs;
591    }
592
593    public void removeAllAuthorizedNodeCertificates() {
594        authorizedNodeCertificates.clear();
595        setTrustManager(null);
596    }
597
598    public X509Certificate[] getAllAuthorizedNodeCertificates() {
599        return toArray(authorizedNodeCertificates.values());
600    }
601
602    public String[] getAuthorizedNodeCertificateRefs() {
603        return authorizedNodeCertificates.keySet().toArray(StringUtils.EMPTY_STRING);
604    }
605
606    public final String getTrustStoreURL() {
607        return trustStoreURL;
608    }
609
610    public final void setTrustStoreURL(String trustStoreURL) {
611        checkNotEmpty("trustStoreURL", trustStoreURL);
612        if (trustStoreURL == null
613                ? this.trustStoreURL == null
614                : trustStoreURL.equals(this.trustStoreURL))
615            return;
616
617        this.trustStoreURL = trustStoreURL;
618        setTrustManager(null);
619    }
620
621    public final String getTrustStoreType() {
622        return trustStoreType;
623    }
624
625    public final void setTrustStoreType(String trustStoreType) {
626        checkNotEmpty("trustStoreType", trustStoreType);
627        this.trustStoreType = trustStoreType;
628    }
629
630    public final String getTrustStorePin() {
631        return trustStorePin;
632    }
633
634    public final void setTrustStorePin(String trustStorePin) {
635        checkNotEmpty("trustStorePin", trustStorePin);
636        this.trustStorePin = trustStorePin;
637    }
638
639    public final String getTrustStorePinProperty() {
640        return trustStorePinProperty;
641    }
642
643    public final void setTrustStorePinProperty(String trustStorePinProperty) {
644        checkNotEmpty("keyPin", keyStoreKeyPin);
645        this.trustStorePinProperty = trustStorePinProperty;
646    }
647
648    public String getOlockHash() {
649        return olockHash;
650    }
651
652    public void setOlockHash(String olockHash) {
653        this.olockHash = olockHash;
654    }
655
656    public X509Certificate[] getThisNodeCertificates(String ref) {
657        return thisNodeCertificates.get(ref);
658    }
659
660    public void setThisNodeCertificates(String ref, X509Certificate... certs) {
661        thisNodeCertificates.put(ref, certs);
662    }
663
664    public X509Certificate[] removeThisNodeCertificates(String ref) {
665        return thisNodeCertificates.remove(ref);
666    }
667
668    public final String getKeyStoreURL() {
669        return keyStoreURL;
670    }
671
672    public final void setKeyStoreURL(String keyStoreURL) {
673        checkNotEmpty("keyStoreURL", keyStoreURL);
674        if (keyStoreURL == null
675                ? this.keyStoreURL == null
676                : keyStoreURL.equals(this.keyStoreURL))
677            return;
678
679        this.keyStoreURL = keyStoreURL;
680        setKeyManager(null);
681    }
682
683    public final String getKeyStoreType() {
684        return keyStoreType;
685    }
686
687    public final void setKeyStoreType(String keyStoreType) {
688        checkNotEmpty("keyStoreType", keyStoreURL);
689        this.keyStoreType = keyStoreType;
690    }
691
692    public final String getKeyStorePin() {
693        return keyStorePin;
694    }
695
696    public final void setKeyStorePin(String keyStorePin) {
697        checkNotEmpty("keyStorePin", keyStorePin);
698        this.keyStorePin = keyStorePin;
699    }
700
701    public final String getKeyStorePinProperty() {
702        return keyStorePinProperty;
703    }
704
705    public final void setKeyStorePinProperty(String keyStorePinProperty) {
706        checkNotEmpty("keyStorePinProperty", keyStorePinProperty);
707        this.keyStorePinProperty = keyStorePinProperty;
708    }
709
710    public final String getKeyStoreKeyPin() {
711        return keyStoreKeyPin;
712    }
713
714    public final void setKeyStoreKeyPin(String keyStorePin) {
715        checkNotEmpty("keyStoreKeyPin", keyStorePin);
716        this.keyStoreKeyPin = keyStorePin;
717    }
718
719    public final String getKeyStoreKeyPinProperty() {
720        return keyStoreKeyPinProperty;
721    }
722
723    public final void setKeyStoreKeyPinProperty(String keyStoreKeyPinProperty) {
724        checkNotEmpty("keyStoreKeyPinProperty", keyStoreKeyPinProperty);
725        this.keyStoreKeyPinProperty = keyStoreKeyPinProperty;
726    }
727
728    public void removeAllThisNodeCertificates() {
729        thisNodeCertificates.clear();
730    }
731
732    public X509Certificate[] getAllThisNodeCertificates() {
733        return toArray(thisNodeCertificates.values());
734    }
735
736    public String[] getThisNodeCertificateRefs() {
737        return thisNodeCertificates.keySet().toArray(StringUtils.EMPTY_STRING);
738    }
739
740    private static X509Certificate[] toArray(Collection<X509Certificate[]> c) {
741        int size = 0;
742        for (X509Certificate[] certs : c)
743            size += certs.length;
744
745        X509Certificate[] dest = new X509Certificate[size];
746        int destPos = 0;
747        for (X509Certificate[] certs : c) {
748            System.arraycopy(certs, 0, dest, destPos, certs.length);
749            destPos += certs.length;
750        }
751        return dest;
752    }
753
754
755    public final String[] getRelatedDeviceRefs() {
756        return relatedDeviceRefs;
757    }
758
759    public void setRelatedDeviceRefs(String... refs) {
760        relatedDeviceRefs = refs;
761    }
762
763    /**
764     * Get device specific vendor configuration information
765     *
766     * @return An Object of the device data.
767     */
768    public final byte[][] getVendorData() {
769        return vendorData;
770    }
771
772    /**
773     * Set device specific vendor configuration information
774     *
775     * @param vendorData An Object of the device data.
776     */
777    public void setVendorData(byte[]... vendorData) {
778        this.vendorData = vendorData;
779    }
780
781    /**
782     * Get a boolean to indicate whether this device is presently installed on
783     * the network. (This is useful for pre-configuration, mobile vans, and
784     * similar situations.)
785     *
786     * @return A boolean which will be true if this device is installed.
787     */
788    public final boolean isInstalled() {
789        return installed;
790    }
791
792    /**
793     * Get a boolean to indicate whether this device is presently installed on
794     * the network. (This is useful for pre-configuration, mobile vans, and
795     * similar situations.)
796     *
797     * @param installed A boolean which will be true if this device is installed.
798     * @throws IOException
799     * @throws GeneralSecurityException
800     * @throws KeyManagementException
801     */
802    public final void setInstalled(boolean installed) {
803        if (this.installed == installed)
804            return;
805
806        this.installed = installed;
807        needRebindConnections();
808    }
809
810    public void setTimeZoneOfDevice(TimeZone timeZoneOfDevice) {
811        this.timeZoneOfDevice = timeZoneOfDevice;
812    }
813
814    public TimeZone getTimeZoneOfDevice() {
815        return timeZoneOfDevice;
816    }
817
818    public final void setDimseRQHandler(DimseRQHandler dimseRQHandler) {
819        this.dimseRQHandler = dimseRQHandler;
820    }
821
822    public final DimseRQHandler getDimseRQHandler() {
823        return dimseRQHandler;
824    }
825
826    public final AssociationHandler getAssociationHandler() {
827        return associationHandler;
828    }
829
830    public void setAssociationHandler(AssociationHandler associationHandler) {
831        if (associationHandler == null)
832            throw new NullPointerException();
833        this.associationHandler = associationHandler;
834    }
835
836    public ConnectionMonitor getConnectionMonitor() {
837        return connectionMonitor;
838    }
839
840    public void setConnectionMonitor(ConnectionMonitor connectionMonitor) {
841        this.connectionMonitor = connectionMonitor;
842    }
843
844    public void bindConnections() throws IOException, GeneralSecurityException {
845        for (Connection con : connections)
846            con.bind();
847    }
848
849    public void rebindConnections() throws IOException, GeneralSecurityException {
850        for (Connection con : connections)
851            if (con.isRebindNeeded())
852                con.rebind();
853    }
854
855    private void needRebindConnections() {
856        for (Connection con : connections)
857            con.needRebind();
858    }
859
860
861    private void needReconfigureTLS() {
862        for (Connection con : connections)
863            if (con.isTls())
864                con.needRebind();
865        sslContext = null;
866    }
867
868    public void unbindConnections() {
869        // the needReconfigureTLS method is cool
870        for (Connection con : connections)
871            con.unbind();
872    }
873
874    public final Executor getExecutor() {
875        return executor;
876    }
877
878    public final void setExecutor(Executor executor) {
879        this.executor = executor;
880    }
881
882    public final ScheduledExecutorService getScheduledExecutor() {
883        return scheduledExecutor;
884    }
885
886    public final void setScheduledExecutor(ScheduledExecutorService executor) {
887        this.scheduledExecutor = executor;
888    }
889
890    public void addConnection(Connection conn) {
891        conn.setDevice(this);
892        connections.add(conn);
893        conn.needRebind();
894    }
895
896    public boolean removeConnection(Connection conn) {
897        for (ApplicationEntity ae : getApplicationEntities())
898            if (ae.getConnections().contains(conn))
899                throw new IllegalStateException(conn + " used by AE: " +
900                        ae.getAETitle());
901
902        for (DeviceExtension ext : extensions.values())
903            ext.verifyNotUsed(conn);
904
905        if (!connections.remove(conn))
906            return false;
907
908        conn.setDevice(null);
909        conn.unbind();
910        return true;
911    }
912
913    public List<Connection> listConnections() {
914        return Collections.unmodifiableList(connections);
915    }
916
917    public Connection connectionWithEqualsRDN(Connection other) {
918        for (Connection conn : connections)
919            if (conn.equalsRDN(other))
920                return conn;
921
922        return null;
923    }
924
925    public List<Connection> getConnections() {
926        return connections;
927    }
928
929    public void setConnections(List<Connection> connections) {
930        this.connections.clear();
931        for (Connection connection : connections) addConnection(connection);
932    }
933
934    public void setApplicationEntitiesMap(Map<String, ApplicationEntity> applicationEntitiesMap) {
935        this.applicationEntitiesMap.clear();
936        this.aliasApplicationEntitiesMap.clear();
937        for (Entry<String, ApplicationEntity> entry : applicationEntitiesMap.entrySet()) {
938            addApplicationEntity(entry.getValue());
939        }
940    }
941
942    /**
943     * This is a low-level access method. Do not use this method to lookup AEs,
944     * use {@link Device#getApplicationEntity(String)} instead - it will also handle aliases and special cases.
945     *
946     * @return
947     */
948    @Deprecated
949    public Map<String, ApplicationEntity> getApplicationEntitiesMap() {
950        return new HashMap<String, ApplicationEntity>(applicationEntitiesMap);
951    }
952
953    public void addApplicationEntity(ApplicationEntity ae) {
954        ae.setDevice(this);
955
956        applicationEntitiesMap.put(ae.getAETitle(), ae);
957
958        addAllAliasesForApplicationEntity(ae);
959    }
960
961    public ApplicationEntity removeApplicationEntity(ApplicationEntity ae) {
962        return removeApplicationEntity(ae.getAETitle());
963    }
964
965    public ApplicationEntity removeApplicationEntity(String aet) {
966        ApplicationEntity ae = applicationEntitiesMap.remove(aet);
967        if (ae != null) {
968            ae.setDevice(null);
969
970            removeAllAliasesForApplicationEntity(ae);
971        }
972
973        return ae;
974    }
975
976    private void addAllAliasesForApplicationEntity(ApplicationEntity ae) {
977        aliasApplicationEntitiesMap.put(ae.getAETitle(), ae);
978        for (String aliasAET : ae.getAETitleAliases()) {
979            aliasApplicationEntitiesMap.put(aliasAET, ae);
980        }
981    }
982
983    private void removeAllAliasesForApplicationEntity(ApplicationEntity ae) {
984        aliasApplicationEntitiesMap.remove(ae.getAETitle());
985        for (String aliasAET : ae.getAETitleAliases()) {
986            aliasApplicationEntitiesMap.remove(aliasAET);
987        }
988    }
989
990    public void setExtensions(Map<Class<? extends DeviceExtension>, DeviceExtension> extensions) {
991        this.extensions = extensions;
992    }
993
994    public Map<Class<? extends DeviceExtension>, DeviceExtension> getExtensions() {
995        return extensions;
996    }
997
998
999    public void addDeviceExtension(DeviceExtension ext) {
1000        Class<? extends DeviceExtension> clazz = ext.getClass();
1001        if (extensions.containsKey(clazz))
1002            throw new IllegalStateException(
1003                    "already contains Device Extension:" + clazz);
1004
1005        ext.setDevice(this);
1006        extensions.put(clazz, ext);
1007    }
1008
1009    public boolean removeDeviceExtension(DeviceExtension ext) {
1010        if (extensions.remove(ext.getClass()) == null)
1011            return false;
1012
1013        ext.setDevice(null);
1014        return true;
1015    }
1016
1017    public final int getLimitOpenAssociations() {
1018        return limitOpenAssociations;
1019    }
1020
1021    public final void setLimitOpenAssociations(int limit) {
1022        if (limit < 0)
1023            throw new IllegalArgumentException("limit: " + limit);
1024
1025        this.limitOpenAssociations = limit;
1026    }
1027
1028    public int getNumberOfOpenAssociations() {
1029        return assocCount;
1030    }
1031
1032    void incrementNumberOfOpenAssociations() {
1033        synchronized (assocCountLock) {
1034            assocCount++;
1035        }
1036    }
1037
1038    void decrementNumberOfOpenAssociations() {
1039        synchronized (assocCountLock) {
1040            if (--assocCount <= 0)
1041                assocCountLock.notifyAll();
1042        }
1043    }
1044
1045    public void waitForNoOpenConnections() throws InterruptedException {
1046        synchronized (assocCountLock) {
1047            while (assocCount > 0)
1048                assocCountLock.wait();
1049        }
1050    }
1051
1052    public boolean isLimitOfOpenAssociationsExceeded() {
1053        return limitOpenAssociations > 0 && getNumberOfOpenAssociations() > limitOpenAssociations;
1054    }
1055
1056    public ApplicationEntity getApplicationEntity(String aet) {
1057
1058        if(aet == null){
1059            throw new IllegalArgumentException("Application Entity Title (aet) is null");
1060        }
1061
1062        ApplicationEntity ae = aliasApplicationEntitiesMap.get(aet);
1063
1064        // special fallback: if one ApplicationEntity defines "*" as an alias AET (or even the main AET), it will get used as a fallback for unknown AETs
1065        if (ae == null)
1066            ae = aliasApplicationEntitiesMap.get("*");
1067
1068        return ae;
1069    }
1070
1071    /**
1072     * @return AE titles of this device, including alias AE titles
1073     */
1074    public Collection<String> getApplicationAETitles() {
1075        return aliasApplicationEntitiesMap.keySet();
1076    }
1077
1078    /**
1079     * This is a low-level access method. Do not use this method to lookup AEs,
1080     * use {@link Device#getApplicationEntity(String)} instead - it will also handle aliases and special cases.
1081     *
1082     * @return
1083     */
1084    public Collection<ApplicationEntity> getApplicationEntities() {
1085        return applicationEntitiesMap.values();
1086    }
1087
1088    public final void setKeyManager(KeyManager km) {
1089        this.km = km;
1090        needReconfigureTLS();
1091    }
1092
1093    public final KeyManager getKeyManager() {
1094        return km;
1095    }
1096
1097    private KeyManager km() throws GeneralSecurityException, IOException {
1098        KeyManager ret = km;
1099        if (ret != null || keyStoreURL == null)
1100            return ret;
1101        String keyStorePin = keyStorePin();
1102        km = ret = SSLManagerFactory.createKeyManager(keyStoreType(),
1103                StringUtils.replaceSystemProperties(keyStoreURL),
1104                keyStorePin(), keyPin(keyStorePin));
1105        return ret;
1106    }
1107
1108    private String keyStoreType() {
1109        if (keyStoreType == null)
1110            throw new IllegalStateException("keyStoreURL requires keyStoreType");
1111
1112        return keyStoreType;
1113    }
1114
1115    private String keyStorePin() {
1116        if (keyStorePin != null)
1117            return keyStorePin;
1118
1119        if (keyStorePinProperty == null)
1120            throw new IllegalStateException(
1121                    "keyStoreURL requires keyStorePin or keyStorePinProperty");
1122
1123        String pin = System.getProperty(keyStorePinProperty);
1124        if (pin == null)
1125            throw new IllegalStateException(
1126                    "No such keyStorePinProperty: " + keyStorePinProperty);
1127
1128        return pin;
1129    }
1130
1131    private String keyPin(String keyStorePin) {
1132        if (keyStoreKeyPin != null)
1133            return keyStoreKeyPin;
1134
1135        if (keyStoreKeyPinProperty == null)
1136            return keyStorePin;
1137
1138        String pin = System.getProperty(keyStoreKeyPinProperty);
1139        if (pin == null)
1140            throw new IllegalStateException(
1141                    "No such keyPinProperty: " + keyStoreKeyPinProperty);
1142
1143        return pin;
1144    }
1145
1146    public final void setTrustManager(TrustManager tm) {
1147        this.tm = tm;
1148        needReconfigureTLS();
1149    }
1150
1151    public final TrustManager getTrustManager() {
1152        return tm;
1153    }
1154
1155    private TrustManager tm() throws GeneralSecurityException, IOException {
1156        TrustManager ret = tm;
1157        if (ret != null
1158                || trustStoreURL == null && authorizedNodeCertificates.isEmpty())
1159            return ret;
1160
1161        tm = ret = trustStoreURL != null
1162                ? SSLManagerFactory.createTrustManager(trustStoreType(),
1163                StringUtils.replaceSystemProperties(trustStoreURL),
1164                trustStorePin())
1165                : SSLManagerFactory.createTrustManager(
1166                getAllAuthorizedNodeCertificates());
1167        return ret;
1168    }
1169
1170    private String trustStoreType() {
1171        if (trustStoreType == null)
1172            throw new IllegalStateException("trustStoreURL requires trustStoreType");
1173
1174        return trustStoreType;
1175    }
1176
1177    private String trustStorePin() {
1178        if (trustStorePin != null)
1179            return trustStorePin;
1180
1181        if (trustStorePinProperty == null)
1182            throw new IllegalStateException(
1183                    "trustStoreURL requires trustStorePin or trustStorePinProperty");
1184
1185        String pin = System.getProperty(trustStorePinProperty);
1186        if (pin == null)
1187            throw new IllegalStateException(
1188                    "No such trustStorePinProperty: " + trustStorePinProperty);
1189
1190        return pin;
1191    }
1192
1193    SSLContext sslContext() throws GeneralSecurityException, IOException {
1194        SSLContext ctx = sslContext;
1195        if (ctx != null)
1196            return ctx;
1197
1198        sslContext = ctx = createSSLContext(km(), tm());
1199        return ctx;
1200    }
1201
1202    private static SSLContext createSSLContext(KeyManager km, TrustManager tm)
1203            throws GeneralSecurityException {
1204        SSLContext ctx = SSLContext.getInstance("TLS");
1205        ctx.init(km != null ? new KeyManager[]{km} : null,
1206                tm != null ? new TrustManager[]{tm} : null, null);
1207        return ctx;
1208    }
1209
1210    public void execute(Runnable command) {
1211        if (executor == null)
1212            throw new IllegalStateException("executer not initalized");
1213
1214        executor.execute(command);
1215    }
1216
1217    public ScheduledFuture<?> schedule(Runnable command, long delay,
1218                                       TimeUnit unit) {
1219        if (scheduledExecutor == null)
1220            throw new IllegalStateException(
1221                    "scheduled executor service not initalized");
1222
1223        return scheduledExecutor.schedule(command, delay, unit);
1224    }
1225
1226    public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
1227                                                  long initialDelay, long period, TimeUnit unit) {
1228        if (scheduledExecutor == null)
1229            throw new IllegalStateException(
1230                    "scheduled executor service not initalized");
1231
1232        return scheduledExecutor.scheduleAtFixedRate(command,
1233                initialDelay, period, unit);
1234    }
1235
1236    public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
1237                                                     long initialDelay, long delay, TimeUnit unit) {
1238        if (scheduledExecutor == null)
1239            throw new IllegalStateException(
1240                    "scheduled executor service not initalized");
1241
1242        return scheduledExecutor.scheduleWithFixedDelay(command,
1243                initialDelay, delay, unit);
1244    }
1245
1246    @Override
1247    public String toString() {
1248        return promptTo(new StringBuilder(512), "").toString();
1249    }
1250
1251    public StringBuilder promptTo(StringBuilder sb, String indent) {
1252        String indent2 = indent + "  ";
1253        StringUtils.appendLine(sb, indent, "Device[name: ", deviceName);
1254        StringUtils.appendLine(sb, indent2, "desc: ", description);
1255        StringUtils.appendLine(sb, indent2, "installed: ", installed);
1256        for (Connection conn : connections)
1257            conn.promptTo(sb, indent2).append(StringUtils.LINE_SEPARATOR);
1258        for (ApplicationEntity ae : applicationEntitiesMap.values())
1259            ae.promptTo(sb, indent2).append(StringUtils.LINE_SEPARATOR);
1260        return sb.append(indent).append(']');
1261    }
1262
1263    public void reconfigure(Device from) throws IOException, GeneralSecurityException {
1264        setDeviceAttributes(from);
1265        reconfigureConnections(from);
1266        reconfigureApplicationEntities(from);
1267        reconfigureDeviceExtensions(from);
1268    }
1269
1270    protected void setDeviceAttributes(Device from) {
1271        setOlockHash(from.olockHash);
1272        setDescription(from.description);
1273        setManufacturer(from.manufacturer);
1274        setManufacturerModelName(from.manufacturerModelName);
1275        setSoftwareVersions(from.softwareVersions);
1276        setStationName(from.stationName);
1277        setUuid(from.getUuid());
1278        setDeviceSerialNumber(from.deviceSerialNumber);
1279        setTrustStoreURL(from.trustStoreURL);
1280        setTrustStoreType(from.trustStoreType);
1281        setTrustStorePin(from.trustStorePin);
1282        setKeyStoreURL(from.keyStoreURL);
1283        setKeyStoreType(from.keyStoreType);
1284        setKeyStorePin(from.keyStorePin);
1285        setKeyStoreKeyPin(from.keyStoreKeyPin);
1286        setTimeZoneOfDevice(from.timeZoneOfDevice);
1287        setIssuerOfPatientID(from.issuerOfPatientID);
1288        setIssuerOfAccessionNumber(from.issuerOfAccessionNumber);
1289        setOrderPlacerIdentifier(from.orderPlacerIdentifier);
1290        setOrderFillerIdentifier(from.orderFillerIdentifier);
1291        setIssuerOfAdmissionID(from.issuerOfAdmissionID);
1292        setIssuerOfServiceEpisodeID(from.issuerOfServiceEpisodeID);
1293        setIssuerOfContainerIdentifier(from.issuerOfContainerIdentifier);
1294        setIssuerOfSpecimenIdentifier(from.issuerOfSpecimenIdentifier);
1295        setInstitutionNames(from.institutionNames);
1296        setInstitutionCodes(from.institutionCodes);
1297        setInstitutionAddresses(from.institutionAddresses);
1298        setInstitutionalDepartmentNames(from.institutionalDepartmentNames);
1299        setPrimaryDeviceTypes(from.primaryDeviceTypes);
1300        setRelatedDeviceRefs(from.relatedDeviceRefs);
1301        setAuthorizedNodeCertificates(from.authorizedNodeCertificates);
1302        setThisNodeCertificates(from.thisNodeCertificates);
1303        setVendorData(from.vendorData);
1304        setLimitOpenAssociations(from.limitOpenAssociations);
1305        setInstalled(from.installed);
1306        setDefaultAE(from.getDefaultAE());
1307    }
1308
1309    private void setAuthorizedNodeCertificates(Map<String, X509Certificate[]> from) {
1310        if (update(authorizedNodeCertificates, from))
1311            setTrustManager(null);
1312    }
1313
1314    private void setThisNodeCertificates(Map<String, X509Certificate[]> from) {
1315        update(thisNodeCertificates, from);
1316    }
1317
1318    private boolean update(Map<String, X509Certificate[]> target,
1319                           Map<String, X509Certificate[]> from) {
1320        boolean updated = target.keySet().retainAll(from.keySet());
1321        for (Entry<String, X509Certificate[]> e : from.entrySet()) {
1322            String key = e.getKey();
1323            X509Certificate[] value = e.getValue();
1324            X509Certificate[] certs = target.get(key);
1325            if (certs == null || !Arrays.equals(value, certs)) {
1326                target.put(key, value);
1327                updated = true;
1328            }
1329        }
1330        return updated;
1331    }
1332
1333    private void reconfigureConnections(Device from) {
1334        Iterator<Connection> connIter = connections.iterator();
1335        while (connIter.hasNext()) {
1336            Connection conn = connIter.next();
1337            if (from.connectionWithEqualsRDN(conn) == null) {
1338                connIter.remove();
1339                conn.setDevice(null);
1340                conn.unbind();
1341            }
1342        }
1343        for (Connection src : from.connections) {
1344            Connection conn = connectionWithEqualsRDN(src);
1345            if (conn == null)
1346                this.addConnection(conn = new Connection());
1347            conn.reconfigure(src);
1348        }
1349    }
1350
1351    private void reconfigureApplicationEntities(Device from) {
1352        applicationEntitiesMap.keySet().retainAll(from.applicationEntitiesMap.keySet());
1353        for (ApplicationEntity src : from.applicationEntitiesMap.values()) {
1354            ApplicationEntity ae = applicationEntitiesMap.get(src.getAETitle());
1355            if (ae == null)
1356                addApplicationEntity(ae = new ApplicationEntity(src.getAETitle()));
1357            ae.reconfigure(src);
1358        }
1359
1360        aliasApplicationEntitiesMap.clear();
1361        for (ApplicationEntity ae : applicationEntitiesMap.values()) {
1362            addAllAliasesForApplicationEntity(ae);
1363        }
1364    }
1365
1366    public void reconfigureConnections(List<Connection> conns,
1367                                       List<Connection> src) {
1368        conns.clear();
1369        for (Connection conn : src)
1370            conns.add(connectionWithEqualsRDN(conn));
1371    }
1372
1373    private void reconfigureDeviceExtensions(Device from) {
1374        for (Iterator<Class<? extends DeviceExtension>> it =
1375             extensions.keySet().iterator(); it.hasNext(); ) {
1376            if (!from.extensions.containsKey(it.next()))
1377                it.remove();
1378        }
1379        for (DeviceExtension src : from.extensions.values()) {
1380            Class<? extends DeviceExtension> clazz = src.getClass();
1381            DeviceExtension ext = extensions.get(clazz);
1382            if (ext == null)
1383                try {
1384                    addDeviceExtension(ext = clazz.newInstance());
1385                } catch (Exception e) {
1386                    throw new RuntimeException(
1387                            "Failed to instantiate " + clazz.getName(), e);
1388                }
1389            ext.reconfigure(src);
1390        }
1391    }
1392
1393    public Collection<DeviceExtension> listDeviceExtensions() {
1394        return extensions.values();
1395    }
1396
1397    @SuppressWarnings("unchecked")
1398    public <T extends DeviceExtension> T getDeviceExtension(Class<T> clazz) {
1399        return (T) extensions.get(clazz);
1400    }
1401
1402    public <T extends DeviceExtension> T getDeviceExtensionNotNull(Class<T> clazz) {
1403        T devExt = getDeviceExtension(clazz);
1404        if (devExt == null)
1405            throw new IllegalStateException("No " + clazz.getName() + " configured for Device: " + deviceName);
1406        return devExt;
1407    }
1408
1409    public Collection<ApplicationEntity> getAEsSupportingTransferCapability(
1410            TransferCapability transferCapability, boolean onlyAbstractSyntax) {
1411        ArrayList<ApplicationEntity> aes = new ArrayList<ApplicationEntity>();
1412        for (ApplicationEntity ae : this.getApplicationEntities()) {
1413            if (ae.supportsTransferCapability(transferCapability,
1414                    onlyAbstractSyntax))
1415                aes.add(ae);
1416        }
1417        return aes;
1418    }
1419
1420    public ApplicationEntity getApplicationEntityNotNull(String aet) {
1421        ApplicationEntity applicationEntity = getApplicationEntity(aet);
1422        if (applicationEntity == null)
1423            throw new IllegalArgumentException("Device " + deviceName + " does not contain AET " + aet);
1424        return applicationEntity;
1425    }
1426
1427    public String getUuid() {
1428        return uuid;
1429    }
1430
1431    public void setUuid(String uuid) {
1432        this.uuid = uuid;
1433    }
1434}