/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is part of dcm4che, an implementation of DICOM(TM) in
* Java(TM), hosted at https://github.com/gunterze/dcm4che.
*
* The Initial Developer of the Original Code is
* Agfa Healthcare.
* Portions created by the Initial Developer are Copyright (C) 2011
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* See @authors listed below
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
package org.dcm4che3.net;
import org.dcm4che3.conf.core.api.ConfigurableClass;
import org.dcm4che3.conf.core.api.ConfigurableProperty;
import org.dcm4che3.conf.core.api.ConfigurableProperty.ConfigurablePropertyType;
import org.dcm4che3.conf.core.api.ConfigurableProperty.Tag;
import org.dcm4che3.conf.core.api.LDAP;
import org.dcm4che3.data.Code;
import org.dcm4che3.data.Issuer;
import org.dcm4che3.util.StringUtils;
import javax.net.ssl.KeyManager;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import java.io.IOException;
import java.io.Serializable;
import java.security.GeneralSecurityException;
import java.security.KeyManagementException;
import java.security.cert.X509Certificate;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.Executor;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
/**
* DICOM Part 15, Annex H compliant description of a DICOM enabled system or
* device. This is used to describe a DICOM-enabled network endpoint in terms of
* its physical attributes (serial number, manufacturer, etc.), its context
* (issuer of patient ids used by the device, etc.), as well as its capabilities
* (TLS-enabled, AE titles used, etc.).
*
* @author Gunter Zeilinger <gunterze@gmail.com>
*/
@LDAP(
objectClasses = {"dcmDevice", "dicomDevice"},
distinguishingField = "dicomDeviceName")
@ConfigurableClass(referable = true)
public class Device implements Serializable {
private static final long serialVersionUID = -5816872456184522866L;
@ConfigurableProperty(name = "dicomDeviceName", label = "Device name", tags = Tag.PRIMARY)
private String deviceName;
/**
* Temporarily gets assigned the value of device name with a prefix
* @see Device#setDeviceName(String)
*/
@ConfigurableProperty(type = ConfigurablePropertyType.UUID)
private String uuid;
@ConfigurableProperty(name = "dicomDescription")
private String description;
@ConfigurableProperty(type = ConfigurablePropertyType.OptimisticLockingHash)
private String olockHash;
@ConfigurableProperty(name = "dicomManufacturer")
private String manufacturer;
@ConfigurableProperty(name = "dicomManufacturerModelName")
private String manufacturerModelName;
@ConfigurableProperty(name = "dicomStationName")
private String stationName;
@ConfigurableProperty(name = "dicomDeviceSerialNumber")
private String deviceSerialNumber;
@ConfigurableProperty(name = "dcmTrustStoreURL")
private String trustStoreURL;
@ConfigurableProperty(name = "dcmTrustStoreType")
private String trustStoreType;
@ConfigurableProperty(name = "dcmTrustStorePin")
private String trustStorePin;
@ConfigurableProperty(name = "dcmTrustStorePinProperty")
private String trustStorePinProperty;
@ConfigurableProperty(name = "dcmKeyStoreURL")
private String keyStoreURL;
@ConfigurableProperty(name = "dcmKeyStoreType")
private String keyStoreType;
@ConfigurableProperty(name = "dcmKeyStorePin")
private String keyStorePin;
@ConfigurableProperty(name = "dcmKeyStorePinProperty")
private String keyStorePinProperty;
@ConfigurableProperty(name = "dcmKeyStoreKeyPin")
private String keyStoreKeyPin;
@ConfigurableProperty(name = "dcmKeyStoreKeyPinProperty")
private String keyStoreKeyPinProperty;
@ConfigurableProperty(name = "dicomIssuerOfPatientID")
private Issuer issuerOfPatientID;
@ConfigurableProperty(name = "dicomIssuerOfAccessionNumber")
private Issuer issuerOfAccessionNumber;
@ConfigurableProperty(name = "dicomOrderPlacerIdentifier")
private Issuer orderPlacerIdentifier;
@ConfigurableProperty(name = "dicomOrderFillerIdentifier")
private Issuer orderFillerIdentifier;
@ConfigurableProperty(name = "dicomIssuerOfAdmissionID")
private Issuer issuerOfAdmissionID;
@ConfigurableProperty(name = "dicomIssuerOfServiceEpisodeID")
private Issuer issuerOfServiceEpisodeID;
@ConfigurableProperty(name = "dicomIssuerOfContainerIdentifier")
private Issuer issuerOfContainerIdentifier;
@ConfigurableProperty(name = "dicomIssuerOfSpecimenIdentifier")
private Issuer issuerOfSpecimenIdentifier;
@ConfigurableProperty(name = "dicomSoftwareVersion")
private String[] softwareVersions = {};
@ConfigurableProperty(name = "dicomPrimaryDeviceType")
private String[] primaryDeviceTypes = {};
@ConfigurableProperty(name = "dicomInstitutionName")
private String[] institutionNames = {};
@ConfigurableProperty(name = "dicomInstitutionCode")
private Code[] institutionCodes = {};
@ConfigurableProperty(name = "dicomInstitutionAddress")
private String[] institutionAddresses = {};
@ConfigurableProperty(name = "dicomInstitutionDepartmentName")
private String[] institutionalDepartmentNames = {};
@ConfigurableProperty(name = "dicomRelatedDeviceReference")
private String[] relatedDeviceRefs = {};
@ConfigurableProperty(name = "dicomVendorData")
private byte[][] vendorData = {};
@ConfigurableProperty(name = "dcmLimitOpenAssociations")
private int limitOpenAssociations;
@ConfigurableProperty(name = "dicomInstalled")
private boolean installed = true;
@ConfigurableProperty(name = "dcmTimeZoneOfDevice")
private TimeZone timeZoneOfDevice;
//TODO: finalize and store x509 cretificates !!
private final LinkedHashMap<String, X509Certificate[]> authorizedNodeCertificates =
new LinkedHashMap<String, X509Certificate[]>();
private final LinkedHashMap<String, X509Certificate[]> thisNodeCertificates =
new LinkedHashMap<String, X509Certificate[]>();
@LDAP(noContainerNode = true)
@ConfigurableProperty(
name = "dicomConnection",
label = "Connections"
)
private final List<Connection> connections = new ArrayList<Connection>();
/**
* Note: This only maps the main AE titles to application entities.
* {@link #aliasApplicationEntitiesMap} will contain also alias AE titles.
*/
@LDAP(noContainerNode = true)
@ConfigurableProperty(
name = "dicomNetworkAE",
label = "Application Entities"
)
private final Map<String, ApplicationEntity> applicationEntitiesMap =
new TreeMap<String, ApplicationEntity>();
@ConfigurableProperty(isReference = true,
name = "dcmDefaultAE",
tags = Tag.PRIMARY,
description = "Default AE to be used by both services running locally on this device as well as external services"
)
private ApplicationEntity defaultAE;
/**
* Maps alias AE titles ({@link ApplicationEntity#getAETitleAliases()}),
* including also the main AE title ({@link ApplicationEntity#getAETitle()}), to application entities.
*/
private final transient Map<String, ApplicationEntity> aliasApplicationEntitiesMap = new TreeMap<String, ApplicationEntity>();
@ConfigurableProperty(name = "deviceExtensions", isExtensionsProperty = true)
private Map<Class<? extends DeviceExtension>, DeviceExtension> extensions =
new HashMap<Class<? extends DeviceExtension>, DeviceExtension>();
private transient AssociationHandler associationHandler = new AssociationHandler();
private transient DimseRQHandler dimseRQHandler;
private transient ConnectionMonitor connectionMonitor;
private transient int assocCount = 0;
private transient final Object assocCountLock = new Object();
private transient Executor executor;
private transient ScheduledExecutorService scheduledExecutor;
private transient volatile SSLContext sslContext;
private transient volatile KeyManager km;
private transient volatile TrustManager tm;
public Device() {
}
public Device(String name) {
setDeviceName(name);
}
private void checkNotEmpty(String name, String val) {
if (val != null && val.isEmpty())
throw new IllegalArgumentException(name + " cannot be empty");
}
public ApplicationEntity getDefaultAE() {
return defaultAE;
}
public void setDefaultAE(ApplicationEntity defaultAE) {
this.defaultAE = defaultAE;
}
/**
* Get the name of this device.
*
* @return A String containing the device name.
*/
public final String getDeviceName() {
return deviceName;
}
/**
* Set the name of this device.
*
* @param name A String containing the device name.
*/
public final void setDeviceName(String name) {
checkNotEmpty("Device Name", name);
this.deviceName = name;
// temporarily
this.uuid = "Device-" + name;
}
/**
* Get the description of this device.
*
* @return A String containing the device description.
*/
public final String getDescription() {
return description;
}
/**
* Set the description of this device.
*
* @param description A String containing the device description.
*/
public final void setDescription(String description) {
this.description = description;
}
/**
* Get the manufacturer of this device.
*
* @return A String containing the device manufacturer.
*/
public final String getManufacturer() {
return manufacturer;
}
/**
* Set the manufacturer of this device.
* <p/>
* This should be the same as the value of Manufacturer (0008,0070) in SOP
* instances created by this device.
*
* @param manufacturer A String containing the device manufacturer.
*/
public final void setManufacturer(String manufacturer) {
this.manufacturer = manufacturer;
}
/**
* Get the manufacturer model name of this device.
*
* @return A String containing the device manufacturer model name.
*/
public final String getManufacturerModelName() {
return manufacturerModelName;
}
/**
* Set the manufacturer model name of this device.
* <p/>
* This should be the same as the value of Manufacturer Model Name
* (0008,1090) in SOP instances created by this device.
*
* @param manufacturerModelName A String containing the device manufacturer model name.
*/
public final void setManufacturerModelName(String manufacturerModelName) {
this.manufacturerModelName = manufacturerModelName;
}
/**
* Get the software versions running on (or implemented by) this device.
*
* @return A String array containing the software versions.
*/
public final String[] getSoftwareVersions() {
return softwareVersions;
}
/**
* Set the software versions running on (or implemented by) this device.
* <p/>
* This should be the same as the values of Software Versions (0018,1020) in
* SOP instances created by this device.
*
* @param softwareVersions A String array containing the software versions.
*/
public final void setSoftwareVersions(String... softwareVersions) {
this.softwareVersions = softwareVersions;
}
/**
* Get the station name belonging to this device.
*
* @return A String containing the station name.
*/
public final String getStationName() {
return stationName;
}
/**
* Set the station name belonging to this device.
* <p/>
* This should be the same as the value of Station Name (0008,1010) in SOP
* instances created by this device.
*
* @param stationName A String containing the station name.
*/
public final void setStationName(String stationName) {
this.stationName = stationName;
}
/**
* Get the serial number belonging to this device.
*
* @return A String containing the serial number.
*/
public final String getDeviceSerialNumber() {
return deviceSerialNumber;
}
/**
* Set the serial number of this device.
* <p/>
* This should be the same as the value of Device Serial Number (0018,1000)
* in SOP instances created by this device.
*
* @param deviceSerialNumber A String containing the serial number.
*/
public final void setDeviceSerialNumber(String deviceSerialNumber) {
this.deviceSerialNumber = deviceSerialNumber;
}
/**
* Get the type codes associated with this device.
*
* @return A String array containing the type codes of this device.
*/
public final String[] getPrimaryDeviceTypes() {
return primaryDeviceTypes;
}
/**
* Set the type codes associated with this device.
* <p/>
* Represents the kind of device and is most applicable for acquisition
* modalities. Types should be selected from the list of code values
* (0008,0100) for Context ID 30 in PS3.16 when applicable.
*
* @param primaryDeviceTypes
*/
public void setPrimaryDeviceTypes(String... primaryDeviceTypes) {
this.primaryDeviceTypes = primaryDeviceTypes;
}
/**
* Get the institution name associated with this device; may be the site
* where it resides or is operating on behalf of.
*
* @return A String array containing the institution name values.
*/
public final String[] getInstitutionNames() {
return institutionNames;
}
/**
* Set the institution name associated with this device; may be the site
* where it resides or is operating on behalf of.
* <p/>
* Should be the same as the value of Institution Name (0008,0080) in SOP
* Instances created by this device.
*
* @param names A String array containing the institution name values.
*/
public void setInstitutionNames(String... names) {
institutionNames = names;
}
public final Code[] getInstitutionCodes() {
return institutionCodes;
}
public void setInstitutionCodes(Code... codes) {
institutionCodes = codes;
}
/**
* Set the address of the institution which operates this device.
*
* @return A String array containing the institution address values.
*/
public final String[] getInstitutionAddresses() {
return institutionAddresses;
}
/**
* Get the address of the institution which operates this device.
* <p/>
* Should be the same as the value of Institution Address (0008,0081)
* attribute in SOP Instances created by this device.
*
* @param addresses A String array containing the institution address values.
*/
public void setInstitutionAddresses(String... addresses) {
institutionAddresses = addresses;
}
/**
* Get the department name associated with this device.
*
* @return A String array containing the dept. name values.
*/
public final String[] getInstitutionalDepartmentNames() {
return institutionalDepartmentNames;
}
/**
* Set the department name associated with this device.
* <p/>
* Should be the same as the value of Institutional Department Name
* (0008,1040) in SOP Instances created by this device.
*
* @param names A String array containing the dept. name values.
*/
public void setInstitutionalDepartmentNames(String... names) {
institutionalDepartmentNames = names;
}
public final Issuer getIssuerOfPatientID() {
return issuerOfPatientID;
}
public final void setIssuerOfPatientID(Issuer issuerOfPatientID) {
this.issuerOfPatientID = issuerOfPatientID;
}
public final Issuer getIssuerOfAccessionNumber() {
return issuerOfAccessionNumber;
}
public final void setIssuerOfAccessionNumber(Issuer issuerOfAccessionNumber) {
this.issuerOfAccessionNumber = issuerOfAccessionNumber;
}
public final Issuer getOrderPlacerIdentifier() {
return orderPlacerIdentifier;
}
public final void setOrderPlacerIdentifier(Issuer orderPlacerIdentifier) {
this.orderPlacerIdentifier = orderPlacerIdentifier;
}
public final Issuer getOrderFillerIdentifier() {
return orderFillerIdentifier;
}
public final void setOrderFillerIdentifier(Issuer orderFillerIdentifier) {
this.orderFillerIdentifier = orderFillerIdentifier;
}
public final Issuer getIssuerOfAdmissionID() {
return issuerOfAdmissionID;
}
public final void setIssuerOfAdmissionID(Issuer issuerOfAdmissionID) {
this.issuerOfAdmissionID = issuerOfAdmissionID;
}
public final Issuer getIssuerOfServiceEpisodeID() {
return issuerOfServiceEpisodeID;
}
public final void setIssuerOfServiceEpisodeID(Issuer issuerOfServiceEpisodeID) {
this.issuerOfServiceEpisodeID = issuerOfServiceEpisodeID;
}
public final Issuer getIssuerOfContainerIdentifier() {
return issuerOfContainerIdentifier;
}
public final void setIssuerOfContainerIdentifier(Issuer issuerOfContainerIdentifier) {
this.issuerOfContainerIdentifier = issuerOfContainerIdentifier;
}
public final Issuer getIssuerOfSpecimenIdentifier() {
return issuerOfSpecimenIdentifier;
}
public final void setIssuerOfSpecimenIdentifier(Issuer issuerOfSpecimenIdentifier) {
this.issuerOfSpecimenIdentifier = issuerOfSpecimenIdentifier;
}
public X509Certificate[] getAuthorizedNodeCertificates(String ref) {
return authorizedNodeCertificates.get(ref);
}
public void setAuthorizedNodeCertificates(String ref, X509Certificate... certs) {
authorizedNodeCertificates.put(ref, certs);
setTrustManager(null);
}
public X509Certificate[] removeAuthorizedNodeCertificates(String ref) {
X509Certificate[] certs = authorizedNodeCertificates.remove(ref);
setTrustManager(null);
return certs;
}
public void removeAllAuthorizedNodeCertificates() {
authorizedNodeCertificates.clear();
setTrustManager(null);
}
public X509Certificate[] getAllAuthorizedNodeCertificates() {
return toArray(authorizedNodeCertificates.values());
}
public String[] getAuthorizedNodeCertificateRefs() {
return authorizedNodeCertificates.keySet().toArray(StringUtils.EMPTY_STRING);
}
public final String getTrustStoreURL() {
return trustStoreURL;
}
public final void setTrustStoreURL(String trustStoreURL) {
checkNotEmpty("trustStoreURL", trustStoreURL);
if (trustStoreURL == null
? this.trustStoreURL == null
: trustStoreURL.equals(this.trustStoreURL))
return;
this.trustStoreURL = trustStoreURL;
setTrustManager(null);
}
public final String getTrustStoreType() {
return trustStoreType;
}
public final void setTrustStoreType(String trustStoreType) {
checkNotEmpty("trustStoreType", trustStoreType);
this.trustStoreType = trustStoreType;
}
public final String getTrustStorePin() {
return trustStorePin;
}
public final void setTrustStorePin(String trustStorePin) {
checkNotEmpty("trustStorePin", trustStorePin);
this.trustStorePin = trustStorePin;
}
public final String getTrustStorePinProperty() {
return trustStorePinProperty;
}
public final void setTrustStorePinProperty(String trustStorePinProperty) {
checkNotEmpty("keyPin", keyStoreKeyPin);
this.trustStorePinProperty = trustStorePinProperty;
}
public String getOlockHash() {
return olockHash;
}
public void setOlockHash(String olockHash) {
this.olockHash = olockHash;
}
public X509Certificate[] getThisNodeCertificates(String ref) {
return thisNodeCertificates.get(ref);
}
public void setThisNodeCertificates(String ref, X509Certificate... certs) {
thisNodeCertificates.put(ref, certs);
}
public X509Certificate[] removeThisNodeCertificates(String ref) {
return thisNodeCertificates.remove(ref);
}
public final String getKeyStoreURL() {
return keyStoreURL;
}
public final void setKeyStoreURL(String keyStoreURL) {
checkNotEmpty("keyStoreURL", keyStoreURL);
if (keyStoreURL == null
? this.keyStoreURL == null
: keyStoreURL.equals(this.keyStoreURL))
return;
this.keyStoreURL = keyStoreURL;
setKeyManager(null);
}
public final String getKeyStoreType() {
return keyStoreType;
}
public final void setKeyStoreType(String keyStoreType) {
checkNotEmpty("keyStoreType", keyStoreURL);
this.keyStoreType = keyStoreType;
}
public final String getKeyStorePin() {
return keyStorePin;
}
public final void setKeyStorePin(String keyStorePin) {
checkNotEmpty("keyStorePin", keyStorePin);
this.keyStorePin = keyStorePin;
}
public final String getKeyStorePinProperty() {
return keyStorePinProperty;
}
public final void setKeyStorePinProperty(String keyStorePinProperty) {
checkNotEmpty("keyStorePinProperty", keyStorePinProperty);
this.keyStorePinProperty = keyStorePinProperty;
}
public final String getKeyStoreKeyPin() {
return keyStoreKeyPin;
}
public final void setKeyStoreKeyPin(String keyStorePin) {
checkNotEmpty("keyStoreKeyPin", keyStorePin);
this.keyStoreKeyPin = keyStorePin;
}
public final String getKeyStoreKeyPinProperty() {
return keyStoreKeyPinProperty;
}
public final void setKeyStoreKeyPinProperty(String keyStoreKeyPinProperty) {
checkNotEmpty("keyStoreKeyPinProperty", keyStoreKeyPinProperty);
this.keyStoreKeyPinProperty = keyStoreKeyPinProperty;
}
public void removeAllThisNodeCertificates() {
thisNodeCertificates.clear();
}
public X509Certificate[] getAllThisNodeCertificates() {
return toArray(thisNodeCertificates.values());
}
public String[] getThisNodeCertificateRefs() {
return thisNodeCertificates.keySet().toArray(StringUtils.EMPTY_STRING);
}
private static X509Certificate[] toArray(Collection<X509Certificate[]> c) {
int size = 0;
for (X509Certificate[] certs : c)
size += certs.length;
X509Certificate[] dest = new X509Certificate[size];
int destPos = 0;
for (X509Certificate[] certs : c) {
System.arraycopy(certs, 0, dest, destPos, certs.length);
destPos += certs.length;
}
return dest;
}
public final String[] getRelatedDeviceRefs() {
return relatedDeviceRefs;
}
public void setRelatedDeviceRefs(String... refs) {
relatedDeviceRefs = refs;
}
/**
* Get device specific vendor configuration information
*
* @return An Object of the device data.
*/
public final byte[][] getVendorData() {
return vendorData;
}
/**
* Set device specific vendor configuration information
*
* @param vendorData An Object of the device data.
*/
public void setVendorData(byte[]... vendorData) {
this.vendorData = vendorData;
}
/**
* Get a boolean to indicate whether this device is presently installed on
* the network. (This is useful for pre-configuration, mobile vans, and
* similar situations.)
*
* @return A boolean which will be true if this device is installed.
*/
public final boolean isInstalled() {
return installed;
}
/**
* Get a boolean to indicate whether this device is presently installed on
* the network. (This is useful for pre-configuration, mobile vans, and
* similar situations.)
*
* @param installed A boolean which will be true if this device is installed.
* @throws IOException
* @throws GeneralSecurityException
* @throws KeyManagementException
*/
public final void setInstalled(boolean installed) {
if (this.installed == installed)
return;
this.installed = installed;
needRebindConnections();
}
public void setTimeZoneOfDevice(TimeZone timeZoneOfDevice) {
this.timeZoneOfDevice = timeZoneOfDevice;
}
public TimeZone getTimeZoneOfDevice() {
return timeZoneOfDevice;
}
public final void setDimseRQHandler(DimseRQHandler dimseRQHandler) {
this.dimseRQHandler = dimseRQHandler;
}
public final DimseRQHandler getDimseRQHandler() {
return dimseRQHandler;
}
public final AssociationHandler getAssociationHandler() {
return associationHandler;
}
public void setAssociationHandler(AssociationHandler associationHandler) {
if (associationHandler == null)
throw new NullPointerException();
this.associationHandler = associationHandler;
}
public ConnectionMonitor getConnectionMonitor() {
return connectionMonitor;
}
public void setConnectionMonitor(ConnectionMonitor connectionMonitor) {
this.connectionMonitor = connectionMonitor;
}
public void bindConnections() throws IOException, GeneralSecurityException {
for (Connection con : connections)
con.bind();
}
public void rebindConnections() throws IOException, GeneralSecurityException {
for (Connection con : connections)
if (con.isRebindNeeded())
con.rebind();
}
private void needRebindConnections() {
for (Connection con : connections)
con.needRebind();
}
private void needReconfigureTLS() {
for (Connection con : connections)
if (con.isTls())
con.needRebind();
sslContext = null;
}
public void unbindConnections() {
// the needReconfigureTLS method is cool
for (Connection con : connections)
con.unbind();
}
public final Executor getExecutor() {
return executor;
}
public final void setExecutor(Executor executor) {
this.executor = executor;
}
public final ScheduledExecutorService getScheduledExecutor() {
return scheduledExecutor;
}
public final void setScheduledExecutor(ScheduledExecutorService executor) {
this.scheduledExecutor = executor;
}
public void addConnection(Connection conn) {
conn.setDevice(this);
connections.add(conn);
conn.needRebind();
}
public boolean removeConnection(Connection conn) {
for (ApplicationEntity ae : getApplicationEntities())
if (ae.getConnections().contains(conn))
throw new IllegalStateException(conn + " used by AE: " +
ae.getAETitle());
for (DeviceExtension ext : extensions.values())
ext.verifyNotUsed(conn);
if (!connections.remove(conn))
return false;
conn.setDevice(null);
conn.unbind();
return true;
}
public List<Connection> listConnections() {
return Collections.unmodifiableList(connections);
}
public Connection connectionWithEqualsRDN(Connection other) {
for (Connection conn : connections)
if (conn.equalsRDN(other))
return conn;
return null;
}
public List<Connection> getConnections() {
return connections;
}
public void setConnections(List<Connection> connections) {
this.connections.clear();
for (Connection connection : connections) addConnection(connection);
}
public void setApplicationEntitiesMap(Map<String, ApplicationEntity> applicationEntitiesMap) {
this.applicationEntitiesMap.clear();
this.aliasApplicationEntitiesMap.clear();
for (Entry<String, ApplicationEntity> entry : applicationEntitiesMap.entrySet()) {
addApplicationEntity(entry.getValue());
}
}
/**
* This is a low-level access method. Do not use this method to lookup AEs,
* use {@link Device#getApplicationEntity(String)} instead - it will also handle aliases and special cases.
*
* @return
*/
@Deprecated
public Map<String, ApplicationEntity> getApplicationEntitiesMap() {
return new HashMap<String, ApplicationEntity>(applicationEntitiesMap);
}
public void addApplicationEntity(ApplicationEntity ae) {
ae.setDevice(this);
applicationEntitiesMap.put(ae.getAETitle(), ae);
addAllAliasesForApplicationEntity(ae);
}
public ApplicationEntity removeApplicationEntity(ApplicationEntity ae) {
return removeApplicationEntity(ae.getAETitle());
}
public ApplicationEntity removeApplicationEntity(String aet) {
ApplicationEntity ae = applicationEntitiesMap.remove(aet);
if (ae != null) {
ae.setDevice(null);
removeAllAliasesForApplicationEntity(ae);
}
return ae;
}
private void addAllAliasesForApplicationEntity(ApplicationEntity ae) {
aliasApplicationEntitiesMap.put(ae.getAETitle(), ae);
for (String aliasAET : ae.getAETitleAliases()) {
aliasApplicationEntitiesMap.put(aliasAET, ae);
}
}
private void removeAllAliasesForApplicationEntity(ApplicationEntity ae) {
aliasApplicationEntitiesMap.remove(ae.getAETitle());
for (String aliasAET : ae.getAETitleAliases()) {
aliasApplicationEntitiesMap.remove(aliasAET);
}
}
public void setExtensions(Map<Class<? extends DeviceExtension>, DeviceExtension> extensions) {
this.extensions = extensions;
}
public Map<Class<? extends DeviceExtension>, DeviceExtension> getExtensions() {
return extensions;
}
public void addDeviceExtension(DeviceExtension ext) {
Class<? extends DeviceExtension> clazz = ext.getClass();
if (extensions.containsKey(clazz))
throw new IllegalStateException(
"already contains Device Extension:" + clazz);
ext.setDevice(this);
extensions.put(clazz, ext);
}
public boolean removeDeviceExtension(DeviceExtension ext) {
if (extensions.remove(ext.getClass()) == null)
return false;
ext.setDevice(null);
return true;
}
public final int getLimitOpenAssociations() {
return limitOpenAssociations;
}
public final void setLimitOpenAssociations(int limit) {
if (limit < 0)
throw new IllegalArgumentException("limit: " + limit);
this.limitOpenAssociations = limit;
}
public int getNumberOfOpenAssociations() {
return assocCount;
}
void incrementNumberOfOpenAssociations() {
synchronized (assocCountLock) {
assocCount++;
}
}
void decrementNumberOfOpenAssociations() {
synchronized (assocCountLock) {
if (--assocCount <= 0)
assocCountLock.notifyAll();
}
}
public void waitForNoOpenConnections() throws InterruptedException {
synchronized (assocCountLock) {
while (assocCount > 0)
assocCountLock.wait();
}
}
public boolean isLimitOfOpenAssociationsExceeded() {
return limitOpenAssociations > 0 && getNumberOfOpenAssociations() > limitOpenAssociations;
}
public ApplicationEntity getApplicationEntity(String aet) {
if(aet == null){
throw new IllegalArgumentException("Application Entity Title (aet) is null");
}
ApplicationEntity ae = aliasApplicationEntitiesMap.get(aet);
// 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
if (ae == null)
ae = aliasApplicationEntitiesMap.get("*");
return ae;
}
/**
* @return AE titles of this device, including alias AE titles
*/
public Collection<String> getApplicationAETitles() {
return aliasApplicationEntitiesMap.keySet();
}
/**
* This is a low-level access method. Do not use this method to lookup AEs,
* use {@link Device#getApplicationEntity(String)} instead - it will also handle aliases and special cases.
*
* @return
*/
public Collection<ApplicationEntity> getApplicationEntities() {
return applicationEntitiesMap.values();
}
public final void setKeyManager(KeyManager km) {
this.km = km;
needReconfigureTLS();
}
public final KeyManager getKeyManager() {
return km;
}
private KeyManager km() throws GeneralSecurityException, IOException {
KeyManager ret = km;
if (ret != null || keyStoreURL == null)
return ret;
String keyStorePin = keyStorePin();
km = ret = SSLManagerFactory.createKeyManager(keyStoreType(),
StringUtils.replaceSystemProperties(keyStoreURL),
keyStorePin(), keyPin(keyStorePin));
return ret;
}
private String keyStoreType() {
if (keyStoreType == null)
throw new IllegalStateException("keyStoreURL requires keyStoreType");
return keyStoreType;
}
private String keyStorePin() {
if (keyStorePin != null)
return keyStorePin;
if (keyStorePinProperty == null)
throw new IllegalStateException(
"keyStoreURL requires keyStorePin or keyStorePinProperty");
String pin = System.getProperty(keyStorePinProperty);
if (pin == null)
throw new IllegalStateException(
"No such keyStorePinProperty: " + keyStorePinProperty);
return pin;
}
private String keyPin(String keyStorePin) {
if (keyStoreKeyPin != null)
return keyStoreKeyPin;
if (keyStoreKeyPinProperty == null)
return keyStorePin;
String pin = System.getProperty(keyStoreKeyPinProperty);
if (pin == null)
throw new IllegalStateException(
"No such keyPinProperty: " + keyStoreKeyPinProperty);
return pin;
}
public final void setTrustManager(TrustManager tm) {
this.tm = tm;
needReconfigureTLS();
}
public final TrustManager getTrustManager() {
return tm;
}
private TrustManager tm() throws GeneralSecurityException, IOException {
TrustManager ret = tm;
if (ret != null
|| trustStoreURL == null && authorizedNodeCertificates.isEmpty())
return ret;
tm = ret = trustStoreURL != null
? SSLManagerFactory.createTrustManager(trustStoreType(),
StringUtils.replaceSystemProperties(trustStoreURL),
trustStorePin())
: SSLManagerFactory.createTrustManager(
getAllAuthorizedNodeCertificates());
return ret;
}
private String trustStoreType() {
if (trustStoreType == null)
throw new IllegalStateException("trustStoreURL requires trustStoreType");
return trustStoreType;
}
private String trustStorePin() {
if (trustStorePin != null)
return trustStorePin;
if (trustStorePinProperty == null)
throw new IllegalStateException(
"trustStoreURL requires trustStorePin or trustStorePinProperty");
String pin = System.getProperty(trustStorePinProperty);
if (pin == null)
throw new IllegalStateException(
"No such trustStorePinProperty: " + trustStorePinProperty);
return pin;
}
SSLContext sslContext() throws GeneralSecurityException, IOException {
SSLContext ctx = sslContext;
if (ctx != null)
return ctx;
sslContext = ctx = createSSLContext(km(), tm());
return ctx;
}
private static SSLContext createSSLContext(KeyManager km, TrustManager tm)
throws GeneralSecurityException {
SSLContext ctx = SSLContext.getInstance("TLS");
ctx.init(km != null ? new KeyManager[]{km} : null,
tm != null ? new TrustManager[]{tm} : null, null);
return ctx;
}
public void execute(Runnable command) {
if (executor == null)
throw new IllegalStateException("executer not initalized");
executor.execute(command);
}
public ScheduledFuture<?> schedule(Runnable command, long delay,
TimeUnit unit) {
if (scheduledExecutor == null)
throw new IllegalStateException(
"scheduled executor service not initalized");
return scheduledExecutor.schedule(command, delay, unit);
}
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
long initialDelay, long period, TimeUnit unit) {
if (scheduledExecutor == null)
throw new IllegalStateException(
"scheduled executor service not initalized");
return scheduledExecutor.scheduleAtFixedRate(command,
initialDelay, period, unit);
}
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
long initialDelay, long delay, TimeUnit unit) {
if (scheduledExecutor == null)
throw new IllegalStateException(
"scheduled executor service not initalized");
return scheduledExecutor.scheduleWithFixedDelay(command,
initialDelay, delay, unit);
}
@Override
public String toString() {
return promptTo(new StringBuilder(512), "").toString();
}
public StringBuilder promptTo(StringBuilder sb, String indent) {
String indent2 = indent + " ";
StringUtils.appendLine(sb, indent, "Device[name: ", deviceName);
StringUtils.appendLine(sb, indent2, "desc: ", description);
StringUtils.appendLine(sb, indent2, "installed: ", installed);
for (Connection conn : connections)
conn.promptTo(sb, indent2).append(StringUtils.LINE_SEPARATOR);
for (ApplicationEntity ae : applicationEntitiesMap.values())
ae.promptTo(sb, indent2).append(StringUtils.LINE_SEPARATOR);
return sb.append(indent).append(']');
}
public void reconfigure(Device from) throws IOException, GeneralSecurityException {
setDeviceAttributes(from);
reconfigureConnections(from);
reconfigureApplicationEntities(from);
reconfigureDeviceExtensions(from);
}
protected void setDeviceAttributes(Device from) {
setOlockHash(from.olockHash);
setDescription(from.description);
setManufacturer(from.manufacturer);
setManufacturerModelName(from.manufacturerModelName);
setSoftwareVersions(from.softwareVersions);
setStationName(from.stationName);
setUuid(from.getUuid());
setDeviceSerialNumber(from.deviceSerialNumber);
setTrustStoreURL(from.trustStoreURL);
setTrustStoreType(from.trustStoreType);
setTrustStorePin(from.trustStorePin);
setKeyStoreURL(from.keyStoreURL);
setKeyStoreType(from.keyStoreType);
setKeyStorePin(from.keyStorePin);
setKeyStoreKeyPin(from.keyStoreKeyPin);
setTimeZoneOfDevice(from.timeZoneOfDevice);
setIssuerOfPatientID(from.issuerOfPatientID);
setIssuerOfAccessionNumber(from.issuerOfAccessionNumber);
setOrderPlacerIdentifier(from.orderPlacerIdentifier);
setOrderFillerIdentifier(from.orderFillerIdentifier);
setIssuerOfAdmissionID(from.issuerOfAdmissionID);
setIssuerOfServiceEpisodeID(from.issuerOfServiceEpisodeID);
setIssuerOfContainerIdentifier(from.issuerOfContainerIdentifier);
setIssuerOfSpecimenIdentifier(from.issuerOfSpecimenIdentifier);
setInstitutionNames(from.institutionNames);
setInstitutionCodes(from.institutionCodes);
setInstitutionAddresses(from.institutionAddresses);
setInstitutionalDepartmentNames(from.institutionalDepartmentNames);
setPrimaryDeviceTypes(from.primaryDeviceTypes);
setRelatedDeviceRefs(from.relatedDeviceRefs);
setAuthorizedNodeCertificates(from.authorizedNodeCertificates);
setThisNodeCertificates(from.thisNodeCertificates);
setVendorData(from.vendorData);
setLimitOpenAssociations(from.limitOpenAssociations);
setInstalled(from.installed);
setDefaultAE(from.getDefaultAE());
}
private void setAuthorizedNodeCertificates(Map<String, X509Certificate[]> from) {
if (update(authorizedNodeCertificates, from))
setTrustManager(null);
}
private void setThisNodeCertificates(Map<String, X509Certificate[]> from) {
update(thisNodeCertificates, from);
}
private boolean update(Map<String, X509Certificate[]> target,
Map<String, X509Certificate[]> from) {
boolean updated = target.keySet().retainAll(from.keySet());
for (Entry<String, X509Certificate[]> e : from.entrySet()) {
String key = e.getKey();
X509Certificate[] value = e.getValue();
X509Certificate[] certs = target.get(key);
if (certs == null || !Arrays.equals(value, certs)) {
target.put(key, value);
updated = true;
}
}
return updated;
}
private void reconfigureConnections(Device from) {
Iterator<Connection> connIter = connections.iterator();
while (connIter.hasNext()) {
Connection conn = connIter.next();
if (from.connectionWithEqualsRDN(conn) == null) {
connIter.remove();
conn.setDevice(null);
conn.unbind();
}
}
for (Connection src : from.connections) {
Connection conn = connectionWithEqualsRDN(src);
if (conn == null)
this.addConnection(conn = new Connection());
conn.reconfigure(src);
}
}
private void reconfigureApplicationEntities(Device from) {
applicationEntitiesMap.keySet().retainAll(from.applicationEntitiesMap.keySet());
for (ApplicationEntity src : from.applicationEntitiesMap.values()) {
ApplicationEntity ae = applicationEntitiesMap.get(src.getAETitle());
if (ae == null)
addApplicationEntity(ae = new ApplicationEntity(src.getAETitle()));
ae.reconfigure(src);
}
aliasApplicationEntitiesMap.clear();
for (ApplicationEntity ae : applicationEntitiesMap.values()) {
addAllAliasesForApplicationEntity(ae);
}
}
public void reconfigureConnections(List<Connection> conns,
List<Connection> src) {
conns.clear();
for (Connection conn : src)
conns.add(connectionWithEqualsRDN(conn));
}
private void reconfigureDeviceExtensions(Device from) {
for (Iterator<Class<? extends DeviceExtension>> it =
extensions.keySet().iterator(); it.hasNext(); ) {
if (!from.extensions.containsKey(it.next()))
it.remove();
}
for (DeviceExtension src : from.extensions.values()) {
Class<? extends DeviceExtension> clazz = src.getClass();
DeviceExtension ext = extensions.get(clazz);
if (ext == null)
try {
addDeviceExtension(ext = clazz.newInstance());
} catch (Exception e) {
throw new RuntimeException(
"Failed to instantiate " + clazz.getName(), e);
}
ext.reconfigure(src);
}
}
public Collection<DeviceExtension> listDeviceExtensions() {
return extensions.values();
}
@SuppressWarnings("unchecked")
public <T extends DeviceExtension> T getDeviceExtension(Class<T> clazz) {
return (T) extensions.get(clazz);
}
public <T extends DeviceExtension> T getDeviceExtensionNotNull(Class<T> clazz) {
T devExt = getDeviceExtension(clazz);
if (devExt == null)
throw new IllegalStateException("No " + clazz.getName() + " configured for Device: " + deviceName);
return devExt;
}
public Collection<ApplicationEntity> getAEsSupportingTransferCapability(
TransferCapability transferCapability, boolean onlyAbstractSyntax) {
ArrayList<ApplicationEntity> aes = new ArrayList<ApplicationEntity>();
for (ApplicationEntity ae : this.getApplicationEntities()) {
if (ae.supportsTransferCapability(transferCapability,
onlyAbstractSyntax))
aes.add(ae);
}
return aes;
}
public ApplicationEntity getApplicationEntityNotNull(String aet) {
ApplicationEntity applicationEntity = getApplicationEntity(aet);
if (applicationEntity == null)
throw new IllegalArgumentException("Device " + deviceName + " does not contain AET " + aet);
return applicationEntity;
}
public String getUuid() {
return uuid;
}
public void setUuid(String uuid) {
this.uuid = uuid;
}
}