Package org.dcm4che3.conf.prefs

Source Code of org.dcm4che3.conf.prefs.PreferencesDicomConfiguration

/* ***** 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.conf.prefs;

import java.io.ByteArrayInputStream;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;
import java.util.prefs.BackingStoreException;
import java.util.prefs.Preferences;

import org.dcm4che3.data.UID;
import org.dcm4che3.conf.api.AttributeCoercion;
import org.dcm4che3.conf.api.AttributeCoercions;
import org.dcm4che3.conf.api.ConfigurationAlreadyExistsException;
import org.dcm4che3.conf.api.ConfigurationException;
import org.dcm4che3.conf.api.ConfigurationNotFoundException;
import org.dcm4che3.conf.api.DicomConfiguration;
import org.dcm4che3.data.Issuer;
import org.dcm4che3.net.ApplicationEntity;
import org.dcm4che3.net.Connection;
import org.dcm4che3.net.Device;
import org.dcm4che3.net.DeviceInfo;
import org.dcm4che3.net.Dimse;
import org.dcm4che3.net.QueryOption;
import org.dcm4che3.net.StorageOptions;
import org.dcm4che3.net.TransferCapability;
import org.dcm4che3.net.Connection.Protocol;
import org.dcm4che3.net.TransferCapability.Role;
import org.dcm4che3.util.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* @author Gunter Zeilinger <gunterze@gmail.com>
* @author Michael Backhaus <michael.backhaus@agfa.com>
*/
public final class PreferencesDicomConfiguration implements DicomConfiguration {

    private static final String DICOM_CONFIGURATION_ROOT =
            "dicomConfigurationRoot";
    private static final String DICOM_DEVICES_ROOT =
            "dicomConfigurationRoot/dicomDevicesRoot";
    private static final String DICOM_UNIQUE_AE_TITLES_REGISTRY_ROOT =
            "dicomConfigurationRoot/dicomUniqueAETitlesRegistryRoot";
    private static final String CONF_ROOT_PROPERTY =
            "org.dcm4che.conf.prefs.configurationRoot";
    private static final String USER_CERTIFICATE = "userCertificate";
    private static final X509Certificate[] EMPTY_X509_CERTIFICATES = {};

    private static final Logger LOG = LoggerFactory.getLogger(
            PreferencesDicomConfiguration.class);

    private final Preferences rootPrefs;
    private final List<PreferencesDicomConfigurationExtension> extensions =
            new ArrayList<PreferencesDicomConfigurationExtension>();
    /**
     * Needed for avoiding infinite loops when dealing with extensions containing circular references
     * e.g., one device extension references another device which has an extension that references the former device.
     * Devices that have been created but not fully loaded are added to this threadlocal. See loadDevice.
     */
    private ThreadLocal<Map<String,Device>> currentlyLoadedDevicesLocal = new ThreadLocal<Map<String,Device>>();

   
    public PreferencesDicomConfiguration() {
        this(rootPrefs());
    }

    private static Preferences rootPrefs() {
        Preferences prefs = Preferences.userRoot();
        String pathName = System.getProperty(CONF_ROOT_PROPERTY);
        return pathName != null ? prefs.node(pathName) : prefs;
    }

    public PreferencesDicomConfiguration(Preferences rootPrefs) {
        this.rootPrefs = rootPrefs;
    }

    public final Preferences getRootPrefs() {
        return rootPrefs;
    }

    public void addDicomConfigurationExtension(PreferencesDicomConfigurationExtension ext) {
        ext.setDicomConfiguration(this);
        extensions.add(ext);
    }

    public boolean removeDicomConfigurationExtension(
            PreferencesDicomConfigurationExtension ext) {
        if (!extensions.remove(ext))
            return false;

        ext.setDicomConfiguration(null);
        return true;
    }

    @SuppressWarnings("unchecked")
    @Override
    public <T> T getDicomConfigurationExtension(Class<T> clazz) {
        for (PreferencesDicomConfigurationExtension ext : extensions) {
            if (clazz.isInstance(ext))
                return (T) ext;
        }
        return null;
    }

    @Override
    public boolean configurationExists() throws ConfigurationException {
        return PreferencesUtils.nodeExists(rootPrefs, DICOM_CONFIGURATION_ROOT);
    }

    public Preferences getDicomConfigurationRoot() throws ConfigurationException {
        if (!PreferencesUtils.nodeExists(rootPrefs, DICOM_CONFIGURATION_ROOT))
            throw new ConfigurationNotFoundException();

        return rootPrefs.node(DICOM_CONFIGURATION_ROOT);
    }

    @Override
    public synchronized boolean purgeConfiguration() throws ConfigurationException {
        if (!configurationExists())
            return false;

        try {
            Preferences node = rootPrefs.node(DICOM_CONFIGURATION_ROOT);
            node.removeNode();
            node.flush();
            LOG.info("Purge DICOM Configuration {}", node);
        } catch (BackingStoreException e) {
            throw new ConfigurationException(e);
        }
        return true;
    }

    @Override
    public synchronized boolean registerAETitle(String aet) throws ConfigurationException {
        String pathName = aetRegistryPathNameOf(aet);
        if (PreferencesUtils.nodeExists(rootPrefs, pathName))
            return false;
        try {
            rootPrefs.node(pathName).flush();
            return true;
        } catch (BackingStoreException e) {
            throw new ConfigurationException(e);
        }
    }

    @Override
    public synchronized void unregisterAETitle(String aet)
            throws ConfigurationException {
        PreferencesUtils.removeNode(rootPrefs, aetRegistryPathNameOf(aet));
    }

    private String aetRegistryPathNameOf(String aet) {
        return DICOM_UNIQUE_AE_TITLES_REGISTRY_ROOT + '/' + aet;
    }

    @Override
    public String deviceRef(String name) {
        return DICOM_DEVICES_ROOT + '/' + name;
    }

    @Override
    public synchronized ApplicationEntity findApplicationEntity(String aet)
            throws ConfigurationException {
        return findDevice("dcmNetworkAE", aet).getApplicationEntity(aet);
    }

    public synchronized Device findDevice(String nodeName, String aet)
            throws ConfigurationException {
         if (!PreferencesUtils.nodeExists(rootPrefs, DICOM_DEVICES_ROOT))
            throw new ConfigurationNotFoundException();
       
        try {
            Preferences devicePrefs = rootPrefs.node(DICOM_DEVICES_ROOT);
            for (String deviceName : devicePrefs.childrenNames()) {
                Preferences deviceNode = devicePrefs.node(deviceName);
                for (String aet2 : deviceNode.node(nodeName).childrenNames())
                    if (aet.equals(aet2))
                        return loadDevice(deviceNode);
            }
        } catch (BackingStoreException e) {
            throw new ConfigurationException(e);
        }
        throw new ConfigurationNotFoundException(aet);
    }

    @Override
    public synchronized Device findDevice(String name) throws ConfigurationException {
        return loadDevice(deviceRef(name));
    }


    @Override
    public DeviceInfo[] listDeviceInfos(DeviceInfo keys)
            throws ConfigurationException {
        if (!PreferencesUtils.nodeExists(rootPrefs, DICOM_DEVICES_ROOT))
            return new DeviceInfo[0];

        ArrayList<DeviceInfo> results = new ArrayList<DeviceInfo>();
        try {
            Preferences devicePrefs = rootPrefs.node(DICOM_DEVICES_ROOT);
            for (String deviceName : devicePrefs.childrenNames()) {
                Preferences deviceNode = devicePrefs.node(deviceName);
                DeviceInfo deviceInfo = new DeviceInfo();
                deviceInfo.setDeviceName(deviceName);
                loadFrom(deviceInfo, deviceNode);
                if (match(deviceInfo, keys))
                    results.add(deviceInfo);
            }
        } catch (BackingStoreException e) {
            throw new ConfigurationException(e);
        }
        return results.toArray(new DeviceInfo[results.size()]) ;
    }

    private boolean match(DeviceInfo deviceInfo, DeviceInfo keys) {
        if (keys == null)
            return true;

        return StringUtils.matches(
                deviceInfo.getDeviceName(),
                keys.getDeviceName(),
                true, true)
            && StringUtils.matches(
                deviceInfo.getDescription(),
                keys.getDescription(),
                true, true)
            && StringUtils.matches(
                deviceInfo.getManufacturer(),
                keys.getManufacturer(),
                true, true)
            && StringUtils.matches(
                deviceInfo.getManufacturerModelName(),
                keys.getManufacturerModelName(),
                true, true)
            && matches(
                deviceInfo.getSoftwareVersions(),
                keys.getSoftwareVersions())
            && StringUtils.matches(
                deviceInfo.getStationName(),
                keys.getStationName(),
                true, true)
            && matches(
                deviceInfo.getInstitutionNames(),
                keys.getInstitutionNames())
            && matches(
                deviceInfo.getInstitutionalDepartmentNames(),
                keys.getInstitutionalDepartmentNames())
            && matches(
                deviceInfo.getPrimaryDeviceTypes(),
                keys.getPrimaryDeviceTypes())
            && (keys.getInstalled() == null
             || keys.getInstalled().equals(deviceInfo.getInstalled()));
    }

    private boolean matches(String[] values, String[] keys) {
        if (keys.length == 0)
            return true;

        for (String key : keys)
            for (String value : values)
                if (StringUtils.matches(value, key, true, true))
                    return true;

        return false;
    }

    private void loadFrom(DeviceInfo deviceInfo, Preferences prefs) {
        deviceInfo.setDescription(prefs.get("dicomDescription", null));
        deviceInfo.setManufacturer(prefs.get("dicomManufacturer", null));
        deviceInfo.setManufacturerModelName(
                prefs.get("dicomManufacturerModelName", null));
        deviceInfo.setSoftwareVersions(
                PreferencesUtils.stringArray(prefs, "dicomSoftwareVersion"));
        deviceInfo.setStationName(prefs.get("dicomStationName", null));
        deviceInfo.setInstitutionNames(
                PreferencesUtils.stringArray(prefs, "dicomInstitutionName"));
        deviceInfo.setInstitutionalDepartmentNames(
                PreferencesUtils.stringArray(prefs, "dicomInstitutionalDepartmentName"));
        deviceInfo.setPrimaryDeviceTypes(
                PreferencesUtils.stringArray(prefs, "dicomPrimaryDeviceType"));
        deviceInfo.setInstalled(prefs.getBoolean("dicomInstalled", false));
    }

    public synchronized Device loadDevice(String pathName) throws ConfigurationException,
            ConfigurationNotFoundException {
        if (!PreferencesUtils.nodeExists(rootPrefs, pathName))
            throw new ConfigurationNotFoundException("Device "+pathName+" not found in configuration");

        return loadDevice(rootPrefs.node(pathName));
    }

    @Override
    public String[] listDeviceNames() throws ConfigurationException {
        if (!PreferencesUtils.nodeExists(rootPrefs, DICOM_DEVICES_ROOT))
            return StringUtils.EMPTY_STRING;

        try {
            return rootPrefs.node(DICOM_DEVICES_ROOT).childrenNames();
        } catch (BackingStoreException e) {
            throw new ConfigurationException(e);
        }
    }

    @Override
    public String[] listRegisteredAETitles() throws ConfigurationException {
        if (!PreferencesUtils.nodeExists(rootPrefs, DICOM_UNIQUE_AE_TITLES_REGISTRY_ROOT))
            return StringUtils.EMPTY_STRING;

        try {
            return rootPrefs.node(DICOM_UNIQUE_AE_TITLES_REGISTRY_ROOT).childrenNames();
        } catch (BackingStoreException e) {
            throw new ConfigurationException(e);
        }
    }

    @Override
    public synchronized void persist(Device device) throws ConfigurationException {
        String deviceName = device.getDeviceName();
        String pathName = deviceRef(deviceName);
        if (PreferencesUtils.nodeExists(rootPrefs, pathName))
            throw new ConfigurationAlreadyExistsException(pathName);

        Preferences deviceNode = rootPrefs.node(pathName);
        storeTo(device, deviceNode);
        storeChilds(device, deviceNode);
        try {
            updateCertificates(device);
            deviceNode.flush();
            deviceNode = null;
        } catch (BackingStoreException e) {
            throw new ConfigurationException(e);
        } catch (CertificateException e) {
            throw new ConfigurationException(e);
        } finally {
            if (deviceNode != null)
                try {
                    deviceNode.removeNode();
                    deviceNode.flush();
                } catch (BackingStoreException e) {
                    LOG.warn("Rollback failed:", e);
                }
        }
    }

    private void updateCertificates(Device device)
            throws CertificateException, BackingStoreException {
        for (String dn : device.getAuthorizedNodeCertificateRefs())
            updateCertificates(dn, loadCertificates(dn),
                    device.getAuthorizedNodeCertificates(dn));
        for (String dn : device.getThisNodeCertificateRefs())
            updateCertificates(dn, loadCertificates(dn),
                    device.getThisNodeCertificates(dn));
    }

    private void updateCertificates(String ref,
            X509Certificate[] prev, X509Certificate[] certs)
            throws CertificateEncodingException, BackingStoreException {
        if (!Arrays.equals(prev, certs))
            storeCertificates(ref, certs);
    }

    private void storeChilds(Device device, Preferences deviceNode) throws ConfigurationException {
        Preferences connsNode = deviceNode.node("dcmNetworkConnection");
        int connIndex = 1;
        List<Connection> devConns = device.listConnections();
        for (Connection conn : devConns)
            storeTo(conn, connsNode.node("" + connIndex++));
        Preferences aesNode = deviceNode.node("dcmNetworkAE");
        for (ApplicationEntity ae : device.getApplicationEntities()) {
            Preferences aeNode = aesNode.node(ae.getAETitle());
            storeTo(ae, aeNode, devConns);
            storeChilds(ae, aeNode);
        }

        for (PreferencesDicomConfigurationExtension ext : extensions)
            ext.storeChilds(device, deviceNode);
    }

    private void storeChilds(ApplicationEntity ae, Preferences aeNode) {
        storeTransferCapabilities(ae, aeNode);

        for (PreferencesDicomConfigurationExtension ext : extensions)
            ext.storeChilds(ae, aeNode);
    }

    private void storeTransferCapabilities(ApplicationEntity ae,
            Preferences aeNode) {
        Preferences tcsNode = aeNode.node("dicomTransferCapability");
        storeTransferCapabilities(ae, TransferCapability.Role.SCP, tcsNode);
        storeTransferCapabilities(ae, TransferCapability.Role.SCU, tcsNode);
    }

    private void storeTransferCapabilities(ApplicationEntity ae, Role role,
            Preferences tcsNode) {
        Preferences roleNode = tcsNode.node(role.name());
        for (TransferCapability tc : ae.getTransferCapabilitiesWithRole(role))
            storeTo(tc, roleNode.node(tc.getSopClass()));
    }

    public void store(AttributeCoercions acs, Preferences parentNode) {
        Preferences acsNode = parentNode.node("dcmAttributeCoercion");
        for (AttributeCoercion ac : acs)
            storeTo(ac, acsNode.node(ac.getCommonName()));
    }

    private static void storeTo(AttributeCoercion ac, Preferences prefs) {
        PreferencesUtils.storeNotNull(prefs, "dcmDIMSE", ac.getDIMSE());
        PreferencesUtils.storeNotNull(prefs, "dicomTransferRole", ac.getRole());
        PreferencesUtils.storeNotEmpty(prefs, "dcmAETitle", ac.getAETitles());
        PreferencesUtils.storeNotEmpty(prefs, "dcmSOPClass", ac.getSOPClasses());
        PreferencesUtils.storeNotNull(prefs, "labeledURI", ac.getURI());
    }

    @Override
    public synchronized void merge(Device device) throws ConfigurationException {
        String pathName = deviceRef(device.getDeviceName());
        if (!PreferencesUtils.nodeExists(rootPrefs, pathName))
            throw new ConfigurationNotFoundException();
       
        Preferences devicePrefs = rootPrefs.node(pathName);
        Device prev = loadDevice(devicePrefs);
        try {
            storeDiffs(devicePrefs, prev, device);
            mergeChilds(prev, device, devicePrefs);
            updateCertificates(prev, device);
            devicePrefs.flush();
        } catch (BackingStoreException e) {
            throw new ConfigurationException(e);
        } catch (CertificateException e) {
            throw new ConfigurationException(e);
        }
    }

    private void updateCertificates(Device prev, Device device)
            throws CertificateException, BackingStoreException {
        for (String dn : device.getAuthorizedNodeCertificateRefs()) {
            X509Certificate[] prevCerts = prev.getAuthorizedNodeCertificates(dn);
            updateCertificates(dn,
                    prevCerts != null ? prevCerts : loadCertificates(dn),
                    device.getAuthorizedNodeCertificates(dn));
        }
        for (String dn : device.getThisNodeCertificateRefs()) {
            X509Certificate[] prevCerts = prev.getThisNodeCertificates(dn);
            updateCertificates(dn,
                    prevCerts != null ? prevCerts : loadCertificates(dn),
                    device.getThisNodeCertificates(dn));
        }
    }

    private void mergeChilds(Device prev, Device device,
            Preferences devicePrefs) throws BackingStoreException, ConfigurationException {
        mergeConnections(prev, device, devicePrefs);
        mergeAEs(prev, device, devicePrefs);
        for (PreferencesDicomConfigurationExtension ext : extensions)
            ext.mergeChilds(prev, device, devicePrefs);
    }

    @Override
    public synchronized void removeDevice(String name) throws ConfigurationException {
        String pathName = deviceRef(name);
        if (!PreferencesUtils.nodeExists(rootPrefs, pathName))
            throw new ConfigurationNotFoundException();

        try {
            Preferences node = rootPrefs.node(pathName);
            node.removeNode();
            node.flush();
        } catch (BackingStoreException e) {
            throw new ConfigurationException(e);
        }
    }

    private void storeTo(Device device, Preferences prefs) {
        PreferencesUtils.storeNotNull(prefs, "dicomDescription", device.getDescription());
        PreferencesUtils.storeNotNull(prefs, "dicomManufacturer", device.getManufacturer());
        PreferencesUtils.storeNotNull(prefs, "dicomManufacturerModelName",
                device.getManufacturerModelName());
        PreferencesUtils.storeNotEmpty(prefs, "dicomSoftwareVersion",
                device.getSoftwareVersions());
        PreferencesUtils.storeNotNull(prefs, "dicomStationName", device.getStationName());
        PreferencesUtils.storeNotNull(prefs, "dicomDeviceSerialNumber",
                device.getDeviceSerialNumber());
        PreferencesUtils.storeNotNull(prefs, "dicomIssuerOfPatientID",
                device.getIssuerOfPatientID());
        PreferencesUtils.storeNotNull(prefs, "dicomIssuerOfAccessionNumber",
                device.getIssuerOfAccessionNumber());
        PreferencesUtils.storeNotNull(prefs, "dicomOrderPlacerIdentifier",
                device.getOrderPlacerIdentifier());
        PreferencesUtils.storeNotNull(prefs, "dicomOrderFillerIdentifier",
                device.getOrderFillerIdentifier());
        PreferencesUtils.storeNotNull(prefs, "dicomIssuerOfAdmissionID",
                device.getIssuerOfAdmissionID());
        PreferencesUtils.storeNotNull(prefs, "dicomIssuerOfServiceEpisodeID",
                device.getIssuerOfServiceEpisodeID());
        PreferencesUtils.storeNotNull(prefs, "dicomIssuerOfContainerIdentifier",
                device.getIssuerOfContainerIdentifier());
        PreferencesUtils.storeNotNull(prefs, "dicomIssuerOfSpecimenIdentifier",
                device.getIssuerOfSpecimenIdentifier());
        PreferencesUtils.storeNotEmpty(prefs, "dicomInstitutionName",
                device.getInstitutionNames());
        PreferencesUtils.storeNotEmpty(prefs, "dicomInstitutionCode",
                device.getInstitutionCodes());
        PreferencesUtils.storeNotEmpty(prefs, "dicomInstitutionAddress",
                device.getInstitutionAddresses());
        PreferencesUtils.storeNotEmpty(prefs, "dicomInstitutionalDepartmentName",
                device.getInstitutionalDepartmentNames());
        PreferencesUtils.storeNotEmpty(prefs, "dicomPrimaryDeviceType",
                device.getPrimaryDeviceTypes());
        PreferencesUtils.storeNotEmpty(prefs, "dicomRelatedDeviceReference",
                device.getRelatedDeviceRefs());
        PreferencesUtils.storeNotEmpty(prefs, "dicomAuthorizedNodeCertificateReference",
                device.getAuthorizedNodeCertificateRefs());
        PreferencesUtils.storeNotEmpty(prefs, "dicomThisNodeCertificateReference",
                device.getThisNodeCertificateRefs());
        storeNotEmpty(prefs, "dicomVendorData", device.getVendorData());
        prefs.putBoolean("dicomInstalled", device.isInstalled());
       
        PreferencesUtils.storeNotDef(prefs, "dcmLimitOpenAssociations", device.getLimitOpenAssociations(), 0);
        PreferencesUtils.storeNotNull(prefs, "dcmTrustStoreURL", device.getTrustStoreURL());
        PreferencesUtils.storeNotNull(prefs, "dcmTrustStoreType", device.getTrustStoreType());
        PreferencesUtils.storeNotNull(prefs, "dcmTrustStorePin", device.getTrustStorePin());
        PreferencesUtils.storeNotNull(prefs, "dcmTrustStorePinProperty", device.getTrustStorePinProperty());
        PreferencesUtils.storeNotNull(prefs, "dcmKeyStoreURL", device.getKeyStoreURL());
        PreferencesUtils.storeNotNull(prefs, "dcmKeyStoreType", device.getKeyStoreType());
        PreferencesUtils.storeNotNull(prefs, "dcmKeyStorePin", device.getKeyStorePin());
        PreferencesUtils.storeNotNull(prefs, "dcmKeyStorePinProperty", device.getKeyStorePinProperty());
        PreferencesUtils.storeNotNull(prefs, "dcmKeyStoreKeyPin", device.getKeyStoreKeyPin());
        PreferencesUtils.storeNotNull(prefs, "dcmKeyStoreKeyPinProperty", device.getKeyStoreKeyPinProperty());
        PreferencesUtils.storeNotNull(prefs, "dcmTimeZoneOfDevice", toTimeZoneString(device.getTimeZoneOfDevice()));
        for (PreferencesDicomConfigurationExtension ext : extensions)
            ext.storeTo(device, prefs);
    }

    private void storeTo(Connection conn, Preferences prefs) {
        PreferencesUtils.storeNotNull(prefs, "cn", conn.getCommonName());
        PreferencesUtils.storeNotNull(prefs, "dicomHostname", conn.getHostname());
        PreferencesUtils.storeNotDef(prefs, "dicomPort", conn.getPort(), Connection.NOT_LISTENING);
        PreferencesUtils.storeNotEmpty(prefs, "dicomTLSCipherSuite", conn.getTlsCipherSuites());
        PreferencesUtils.storeNotNull(prefs, "dicomInstalled", conn.getInstalled());

        PreferencesUtils.storeNotNull(prefs, "dcmProtocol",
                StringUtils.nullify(conn.getProtocol(), Protocol.DICOM));
        PreferencesUtils.storeNotNull(prefs, "dcmHTTPProxy", conn.getHttpProxy());
        PreferencesUtils.storeNotEmpty(prefs, "dcmBlacklistedHostname", conn.getBlacklist());
        PreferencesUtils.storeNotDef(prefs, "dcmTCPBacklog",
                conn.getBacklog(), Connection.DEF_BACKLOG);
        PreferencesUtils.storeNotDef(prefs, "dcmTCPConnectTimeout",
                conn.getConnectTimeout(), Connection.NO_TIMEOUT);
        PreferencesUtils.storeNotDef(prefs, "dcmAARQTimeout",
                conn.getRequestTimeout(), Connection.NO_TIMEOUT);
        PreferencesUtils.storeNotDef(prefs, "dcmAAACTimeout",
                conn.getAcceptTimeout(), Connection.NO_TIMEOUT);
        PreferencesUtils.storeNotDef(prefs, "dcmARRPTimeout",
                conn.getReleaseTimeout(), Connection.NO_TIMEOUT);
        PreferencesUtils.storeNotDef(prefs, "dcmResponseTimeout",
                conn.getResponseTimeout(), Connection.NO_TIMEOUT);
        PreferencesUtils.storeNotDef(prefs, "dcmRetrieveTimeout",
                conn.getRetrieveTimeout(), Connection.NO_TIMEOUT);
        PreferencesUtils.storeNotDef(prefs, "dcmIdleTimeout",
                conn.getIdleTimeout(), Connection.NO_TIMEOUT);
        PreferencesUtils.storeNotDef(prefs, "dcmTCPCloseDelay",
                conn.getSocketCloseDelay(), Connection.DEF_SOCKETDELAY);
        PreferencesUtils.storeNotDef(prefs, "dcmTCPSendBufferSize",
                conn.getSendBufferSize(), Connection.DEF_BUFFERSIZE);
        PreferencesUtils.storeNotDef(prefs, "dcmTCPReceiveBufferSize",
                conn.getReceiveBufferSize(), Connection.DEF_BUFFERSIZE);
        PreferencesUtils.storeNotDef(prefs, "dcmTCPNoDelay", conn.isTcpNoDelay(), true);
        PreferencesUtils.storeNotNull(prefs, "dcmBindAddress", conn.getBindAddress());
        PreferencesUtils.storeNotNull(prefs, "dcmClientBindAddress", conn.getClientBindAddress());
        PreferencesUtils.storeNotDef(prefs, "dcmSendPDULength",
                conn.getSendPDULength(), Connection.DEF_MAX_PDU_LENGTH);
        PreferencesUtils.storeNotDef(prefs, "dcmReceivePDULength",
                conn.getReceivePDULength(), Connection.DEF_MAX_PDU_LENGTH);
        PreferencesUtils.storeNotDef(prefs, "dcmMaxOpsPerformed",
                conn.getMaxOpsPerformed(), Connection.SYNCHRONOUS_MODE);
        PreferencesUtils.storeNotDef(prefs, "dcmMaxOpsInvoked",
                conn.getMaxOpsInvoked(), Connection.SYNCHRONOUS_MODE);
        PreferencesUtils.storeNotDef(prefs, "dcmPackPDV", conn.isPackPDV(), true);
        if (conn.isTls()) {
            PreferencesUtils.storeNotEmpty(prefs, "dcmTLSProtocol", conn.getTlsProtocols());
            PreferencesUtils.storeNotDef(prefs, "dcmTLSNeedClientAuth", conn.isTlsNeedClientAuth(), true);
        }
    }

    private void storeTo(ApplicationEntity ae, Preferences prefs, List<Connection> devConns) {
        PreferencesUtils.storeNotNull(prefs, "dicomDescription", ae.getDescription());
        storeNotEmpty(prefs, "dicomVendorData", ae.getVendorData());
        PreferencesUtils.storeNotEmpty(prefs, "dicomApplicationCluster", ae.getApplicationClusters());
        PreferencesUtils.storeNotEmpty(prefs, "dicomPreferredCallingAETitle", ae.getPreferredCallingAETitles());
        PreferencesUtils.storeNotEmpty(prefs, "dicomPreferredCalledAETitle", ae.getPreferredCalledAETitles());
        prefs.putBoolean("dicomAssociationInitiator", ae.isAssociationInitiator());
        prefs.putBoolean("dicomAssociationAcceptor",  ae.isAssociationAcceptor());
        PreferencesUtils.storeNotEmpty(prefs, "dicomSupportedCharacterSet", ae.getSupportedCharacterSets());
        PreferencesUtils.storeNotNull(prefs, "dicomInstalled", ae.getInstalled());
        PreferencesUtils.storeConnRefs(prefs, ae.getConnections(), devConns);
        PreferencesUtils.storeNotEmpty(prefs, "dcmAcceptedCallingAETitle", ae.getAcceptedCallingAETitles());
        for (PreferencesDicomConfigurationExtension ext : extensions)
            ext.storeTo(ae, prefs);
    }

    private void storeTo(TransferCapability tc, Preferences prefs) {
        PreferencesUtils.storeNotNull(prefs, "cn", tc.getCommonName());
        PreferencesUtils.storeNotEmpty(prefs, "dicomTransferSyntax", tc.getTransferSyntaxes());
        EnumSet<QueryOption> queryOpts = tc.getQueryOptions();
        if (queryOpts != null) {
            PreferencesUtils.storeNotDef(prefs, "dcmRelationalQueries",
                    queryOpts.contains(QueryOption.RELATIONAL), false);
            PreferencesUtils.storeNotDef(prefs, "dcmCombinedDateTimeMatching",
                    queryOpts.contains(QueryOption.DATETIME), false);
            PreferencesUtils.storeNotDef(prefs, "dcmFuzzySemanticMatching",
                    queryOpts.contains(QueryOption.FUZZY), false);
            PreferencesUtils.storeNotDef(prefs, "dcmTimezoneQueryAdjustment",
                    queryOpts.contains(QueryOption.TIMEZONE), false);
        }
        StorageOptions storageOpts = tc.getStorageOptions();
        if (storageOpts != null) {
            prefs.putInt("dcmStorageConformance",
                    storageOpts.getLevelOfSupport().ordinal());
            prefs.putInt("dcmDigitalSignatureSupport",
                    storageOpts.getDigitalSignatureSupport().ordinal());
            prefs.putInt("dcmDataElementCoercion",
                    storageOpts.getElementCoercion().ordinal());
        }
    }

    private void storeDiffs(Preferences prefs, Device a, Device b) {
        PreferencesUtils.storeDiff(prefs, "dicomDescription",
                a.getDescription(),
                b.getDescription());
        PreferencesUtils.storeDiff(prefs, "dicomManufacturer",
                a.getManufacturer(),
                b.getManufacturer());
        PreferencesUtils.storeDiff(prefs, "dicomManufacturerModelName",
                a.getManufacturerModelName(),
                b.getManufacturerModelName());
        storeDiff(prefs, "dicomSoftwareVersion",
                a.getSoftwareVersions(),
                b.getSoftwareVersions());
        PreferencesUtils.storeDiff(prefs, "dicomStationName",
                a.getStationName(),
                b.getStationName());
        PreferencesUtils.storeDiff(prefs, "dicomDeviceSerialNumber",
                a.getDeviceSerialNumber(),
                b.getDeviceSerialNumber());
        PreferencesUtils.storeDiff(prefs, "dicomIssuerOfPatientID",
                a.getIssuerOfPatientID(),
                b.getIssuerOfPatientID());
        PreferencesUtils.storeDiff(prefs, "dicomIssuerOfAccessionNumber",
                a.getIssuerOfAccessionNumber(),
                b.getIssuerOfAccessionNumber());
        PreferencesUtils.storeDiff(prefs, "dicomOrderPlacerIdentifier",
                a.getOrderPlacerIdentifier(),
                b.getOrderPlacerIdentifier());
        PreferencesUtils.storeDiff(prefs, "dicomOrderFillerIdentifier",
                a.getOrderFillerIdentifier(),
                b.getOrderFillerIdentifier());
        PreferencesUtils.storeDiff(prefs, "dicomIssuerOfAdmissionID",
                a.getIssuerOfAdmissionID(),
                b.getIssuerOfAdmissionID());
        PreferencesUtils.storeDiff(prefs, "dicomIssuerOfServiceEpisodeID",
                a.getIssuerOfServiceEpisodeID(),
                b.getIssuerOfServiceEpisodeID());
        PreferencesUtils.storeDiff(prefs, "dicomIssuerOfContainerIdentifier",
                a.getIssuerOfContainerIdentifier(),
                b.getIssuerOfContainerIdentifier());
        PreferencesUtils.storeDiff(prefs, "dicomIssuerOfSpecimenIdentifier",
                a.getIssuerOfSpecimenIdentifier(),
                b.getIssuerOfSpecimenIdentifier());
        storeDiff(prefs, "dicomInstitutionName",
                a.getInstitutionNames(),
                b.getInstitutionNames());
        storeDiff(prefs, "dicomInstitutionCode",
                a.getInstitutionCodes(),
                b.getInstitutionCodes());
        storeDiff(prefs, "dicomInstitutionAddress",
                a.getInstitutionAddresses(),
                b.getInstitutionAddresses());
        storeDiff(prefs, "dicomInstitutionalDepartmentName",
                a.getInstitutionalDepartmentNames(),
                b.getInstitutionalDepartmentNames());
        storeDiff(prefs, "dicomPrimaryDeviceType",
                a.getPrimaryDeviceTypes(),
                b.getPrimaryDeviceTypes());
        storeDiff(prefs, "dicomRelatedDeviceReference",
                a.getRelatedDeviceRefs(),
                b.getRelatedDeviceRefs());
        storeDiff(prefs, "dicomAuthorizedNodeCertificateReference",
                a.getAuthorizedNodeCertificateRefs(),
                b.getAuthorizedNodeCertificateRefs());
        storeDiff(prefs, "dicomThisNodeCertificateReference",
                a.getThisNodeCertificateRefs(),
                b.getThisNodeCertificateRefs());
        storeDiff(prefs, "dicomVendorData",
                a.getVendorData(),
                b.getVendorData());
        PreferencesUtils.storeDiff(prefs, "dicomInstalled",
                a.isInstalled(),
                b.isInstalled());

        PreferencesUtils.storeDiff(prefs, "dcmLimitOpenAssociations",
                a.getLimitOpenAssociations(),
                b.getLimitOpenAssociations());
        PreferencesUtils.storeDiff(prefs, "dcmTrustStoreURL",
                a.getTrustStoreURL(),
                b.getTrustStoreURL());
        PreferencesUtils.storeDiff(prefs, "dcmTrustStoreType",
                a.getTrustStoreType(),
                b.getTrustStoreType());
        PreferencesUtils.storeDiff(prefs, "dcmTrustStorePin",
                a.getTrustStorePin(),
                b.getTrustStorePin());
        PreferencesUtils.storeDiff(prefs, "dcmTrustStorePinProperty",
                a.getTrustStorePinProperty(),
                b.getTrustStorePinProperty());
        PreferencesUtils.storeDiff(prefs, "dcmKeyStoreURL",
                a.getKeyStoreURL(),
                b.getKeyStoreURL());
        PreferencesUtils.storeDiff(prefs, "dcmKeyStoreType",
                a.getKeyStoreType(),
                b.getKeyStoreType());
        PreferencesUtils.storeDiff(prefs, "dcmKeyStorePin",
                a.getKeyStorePin(),
                b.getKeyStorePin());
        PreferencesUtils.storeDiff(prefs, "dcmKeyStorePinProperty",
                a.getKeyStorePinProperty(),
                b.getKeyStorePinProperty());
        PreferencesUtils.storeDiff(prefs, "dcmKeyStoreKeyPin",
                a.getKeyStoreKeyPin(),
                b.getKeyStoreKeyPin());
        PreferencesUtils.storeDiff(prefs, "dcmKeyStoreKeyPinProperty",
                a.getKeyStoreKeyPinProperty(),
                b.getKeyStoreKeyPinProperty());
        PreferencesUtils.storeDiff(prefs, "dcmTimeZoneOfDevice",
          a.getTimeZoneOfDevice(),
          b.getTimeZoneOfDevice());
        for (PreferencesDicomConfigurationExtension ext : extensions)
            ext.storeDiffs(a, b, prefs);
    }

    private void storeDiffs(Preferences prefs, Connection a, Connection b) {
        PreferencesUtils.storeDiff(prefs, "cn",
                a.getCommonName(),
                b.getCommonName());
        PreferencesUtils.storeDiff(prefs, "dicomHostname",
                a.getHostname(),
                b.getHostname());
        PreferencesUtils.storeDiff(prefs, "dicomPort",
                a.getPort(),
                b.getPort(),
                Connection.NOT_LISTENING);
        storeDiff(prefs, "dicomTLSCipherSuite",
                a.getTlsCipherSuites(),
                b.getTlsCipherSuites());
        PreferencesUtils.storeDiff(prefs, "dicomInstalled",
                a.getInstalled(),
                b.getInstalled());

        PreferencesUtils.storeDiff(prefs, "dcmProtocol",
                StringUtils.nullify(a.getProtocol(), Protocol.DICOM),
                StringUtils.nullify(b.getProtocol(), Protocol.DICOM));
        PreferencesUtils.storeDiff(prefs, "dcmHTTPProxy",
                a.getHttpProxy(),
                b.getHttpProxy());
        storeDiff(prefs, "dcmBlacklistedHostname",
                a.getBlacklist(),
                b.getBlacklist());
        PreferencesUtils.storeDiff(prefs, "dcmTCPBacklog",
                a.getBacklog(),
                b.getBacklog(),
                Connection.DEF_BACKLOG);
        PreferencesUtils.storeDiff(prefs, "dcmTCPConnectTimeout",
                a.getConnectTimeout(),
                b.getConnectTimeout(),
                Connection.NO_TIMEOUT);
        PreferencesUtils.storeDiff(prefs, "dcmAARQTimeout",
                a.getRequestTimeout(),
                b.getRequestTimeout(),
                Connection.NO_TIMEOUT);
        PreferencesUtils.storeDiff(prefs, "dcmAAACTimeout",
                a.getAcceptTimeout(),
                b.getAcceptTimeout(),
                Connection.NO_TIMEOUT);
        PreferencesUtils.storeDiff(prefs, "dcmARRPTimeout",
                a.getReleaseTimeout(),
                b.getReleaseTimeout(),
                Connection.NO_TIMEOUT);
        PreferencesUtils.storeDiff(prefs, "dcmResponseTimeout",
                a.getResponseTimeout(),
                b.getResponseTimeout(),
                Connection.NO_TIMEOUT);
        PreferencesUtils.storeDiff(prefs, "dcmRetrieveTimeout",
                a.getRetrieveTimeout(),
                b.getRetrieveTimeout(),
                Connection.NO_TIMEOUT);
        PreferencesUtils.storeDiff(prefs, "dcmIdleTimeout",
                a.getIdleTimeout(),
                b.getIdleTimeout(),
                Connection.NO_TIMEOUT);
        PreferencesUtils.storeDiff(prefs, "dcmTCPCloseDelay",
                a.getSocketCloseDelay(),
                b.getSocketCloseDelay(),
                Connection.DEF_SOCKETDELAY);
        PreferencesUtils.storeDiff(prefs, "dcmTCPSendBufferSize",
                a.getSendBufferSize(),
                b.getSendBufferSize(),
                Connection.DEF_BUFFERSIZE);
        PreferencesUtils.storeDiff(prefs, "dcmTCPReceiveBufferSize",
                a.getReceiveBufferSize(),
                b.getReceiveBufferSize(),
                Connection.DEF_BUFFERSIZE);
        PreferencesUtils.storeDiff(prefs, "dcmTCPNoDelay",
                a.isTcpNoDelay(),
                b.isTcpNoDelay(),
                true);
        PreferencesUtils.storeDiff(prefs, "dcmBindAddress",
                a.getBindAddress(),
                b.getBindAddress());
        PreferencesUtils.storeDiff(prefs, "dcmClientBindAddress",
                a.getClientBindAddress(),
                b.getClientBindAddress());
        storeDiff(prefs, "dcmTLSProtocol",
                a.isTls() ? a.getTlsProtocols() : StringUtils.EMPTY_STRING,
                b.isTls() ? b.getTlsProtocols() : StringUtils.EMPTY_STRING);
        PreferencesUtils.storeDiff(prefs, "dcmTLSNeedClientAuth",
                !a.isTls() || a.isTlsNeedClientAuth(),
                !a.isTls() || a.isTlsNeedClientAuth(),
                true);
        PreferencesUtils.storeDiff(prefs, "dcmSendPDULength",
                a.getSendPDULength(),
                b.getSendPDULength(),
                Connection.DEF_MAX_PDU_LENGTH);
        PreferencesUtils.storeDiff(prefs, "dcmReceivePDULength",
                a.getReceivePDULength(),
                b.getReceivePDULength(),
                Connection.DEF_MAX_PDU_LENGTH);
        PreferencesUtils.storeDiff(prefs, "dcmMaxOpsPerformed",
                a.getMaxOpsPerformed(),
                b.getMaxOpsPerformed(),
                Connection.SYNCHRONOUS_MODE);
        PreferencesUtils.storeDiff(prefs, "dcmMaxOpsInvoked",
                a.getMaxOpsInvoked(),
                b.getMaxOpsInvoked(),
                Connection.SYNCHRONOUS_MODE);
        PreferencesUtils.storeDiff(prefs, "dcmPackPDV",
                a.isPackPDV(),
                b.isPackPDV());
}

    private void storeDiffs(Preferences prefs, ApplicationEntity a, ApplicationEntity b) {
        PreferencesUtils.storeDiff(prefs, "dicomDescription",
                a.getDescription(),
                b.getDescription());
        storeDiff(prefs, "dicomVendorData",
                a.getVendorData(),
                b.getVendorData());
        storeDiff(prefs, "dicomApplicationCluster",
                a.getApplicationClusters(),
                b.getApplicationClusters());
        storeDiff(prefs, "dicomPreferredCallingAETitle",
                a.getPreferredCallingAETitles(),
                b.getPreferredCallingAETitles());
        storeDiff(prefs, "dicomPreferredCalledAETitle",
                a.getPreferredCalledAETitles(),
                b.getPreferredCalledAETitles());
        PreferencesUtils.storeDiff(prefs, "dicomAssociationInitiator",
                a.isAssociationInitiator(),
                b.isAssociationInitiator());
        PreferencesUtils.storeDiff(prefs, "dicomAssociationAcceptor",
                a.isAssociationAcceptor(),
                b.isAssociationAcceptor());
        PreferencesUtils.storeDiffConnRefs(prefs,
                a.getConnections(), a.getDevice().listConnections(),
                b.getConnections(), b.getDevice().listConnections());
        storeDiff(prefs, "dicomSupportedCharacterSet",
                a.getSupportedCharacterSets(),
                b.getSupportedCharacterSets());
        PreferencesUtils.storeDiff(prefs, "dicomInstalled",
                a.getInstalled(),
                b.getInstalled());

        storeDiff(prefs, "dcmAcceptedCallingAETitle",
                a.getAcceptedCallingAETitles(),
                b.getAcceptedCallingAETitles());

        for (PreferencesDicomConfigurationExtension ext : extensions)
            ext.storeDiffs(a, b, prefs);
    }

    private void storeDiffs(Preferences prefs,
            TransferCapability a, TransferCapability b) {
        PreferencesUtils.storeDiff(prefs, "cn",
                a.getCommonName(),
                b.getCommonName());
        storeDiff(prefs, "dicomTransferSyntax",
                a.getTransferSyntaxes(),
                b.getTransferSyntaxes());
        storeDiffs(prefs, a.getQueryOptions(), b.getQueryOptions());
        storeDiffs(prefs, a.getStorageOptions(), b.getStorageOptions());
    }

    private static void storeDiffs(Preferences prefs,
            EnumSet<QueryOption> prev, EnumSet<QueryOption> val) {
        if (prev != null ? prev.equals(val) : val == null)
            return;

        PreferencesUtils.storeDiff(prefs, "dcmRelationalQueries",
                prev != null && prev.contains(QueryOption.RELATIONAL),
                val != null && val.contains(QueryOption.RELATIONAL),
                false);
        PreferencesUtils.storeDiff(prefs, "dcmCombinedDateTimeMatching",
                prev != null && prev.contains(QueryOption.DATETIME),
                val != null && val.contains(QueryOption.DATETIME),
                false);
        PreferencesUtils.storeDiff(prefs, "dcmFuzzySemanticMatching",
                prev != null && prev.contains(QueryOption.FUZZY),
                val != null && val.contains(QueryOption.FUZZY),
                false);
        PreferencesUtils.storeDiff(prefs, "dcmTimezoneQueryAdjustment",
                prev != null && prev.contains(QueryOption.TIMEZONE),
                val != null && val.contains(QueryOption.TIMEZONE),
                false);
    }

    private static void storeDiffs(Preferences prefs,
            StorageOptions prev, StorageOptions val) {
        if (prev != null ? prev.equals(val) : val == null)
            return;

        PreferencesUtils.storeDiff(prefs, "dcmStorageConformance",
                prev != null ? prev.getLevelOfSupport().ordinal() : -1,
                val != null ? val.getLevelOfSupport().ordinal() : -1,
                -1);
        PreferencesUtils.storeDiff(prefs, "dcmDigitalSignatureSupport",
                prev != null ? prev.getDigitalSignatureSupport().ordinal() : -1,
                val != null ? val.getDigitalSignatureSupport().ordinal() : -1,
                -1);
        PreferencesUtils.storeDiff(prefs, "dcmDataElementCoercion",
                prev != null ? prev.getElementCoercion().ordinal() : -1,
                val != null ? val.getElementCoercion().ordinal() : -1,
                -1);
    }

    private void mergeConnections(Device prevDev, Device device, Preferences deviceNode)
            throws BackingStoreException {
        Preferences connsNode = deviceNode.node("dcmNetworkConnection");
        List<Connection> prevs = prevDev.listConnections();
        List<Connection> conns = device.listConnections();
        int prevsSize = prevs.size();
        int connsSize = conns.size();
        int i = 0;
        for (int n = Math.min(prevsSize, connsSize); i < n; ++i)
            storeDiffs(connsNode.node("" + (i+1)), prevs.get(i), conns.get(i));
        for (; i < prevsSize; ++i)
            connsNode.node("" + (i+1)).removeNode();
        for (; i < connsSize; ++i)
            storeTo(conns.get(i), connsNode.node("" + (i+1)));
    }

    private void mergeAEs(Device prevDev, Device dev, Preferences deviceNode)
            throws BackingStoreException {
        Preferences aesNode = deviceNode.node("dcmNetworkAE");
        Collection<String> aets = dev.getApplicationAETitles();
        for (String aet : prevDev.getApplicationAETitles()) {
            if (!aets.contains(aet))
                aesNode.node(aet).removeNode();
        }
        Collection<String> prevAETs = prevDev.getApplicationAETitles();
        List<Connection> devConns = dev.listConnections();
        for (ApplicationEntity ae : dev.getApplicationEntities()) {
            String aet = ae.getAETitle();
            Preferences aeNode = aesNode.node(aet);
            if (!prevAETs.contains(aet)) {
                storeTo(ae, aeNode, devConns);
                storeChilds(ae, aeNode);
            } else {
                ApplicationEntity prevAE = prevDev.getApplicationEntity(aet);
                storeDiffs(aeNode, prevAE, ae);
                mergeChilds(prevAE, ae, aeNode);
            }
        }
    }

    public void merge(AttributeCoercions prevs, AttributeCoercions acs,
            Preferences parentNode) throws BackingStoreException {
        Preferences acsNode = parentNode.node("dcmAttributeCoercion");
        for (AttributeCoercion prev : prevs) {
            String cn = prev.getCommonName();
            if (acs.findByCommonName(prev.getCommonName()) == null)
                acsNode.node(cn).removeNode();
        }
        for (AttributeCoercion ac : acs) {
            String cn = ac.getCommonName();
            Preferences acNode = acsNode.node(cn);
            AttributeCoercion prev = prevs.findByCommonName(cn);
            if (prev == null)
                storeTo(ac, acNode);
            else
                storeDiffs(acNode, prev, ac);
        }
    }

    private void storeDiffs(Preferences prefs, AttributeCoercion a, AttributeCoercion b) {
        PreferencesUtils.storeDiff(prefs, "dcmDIMSE", a.getDIMSE(), b.getDIMSE());
        PreferencesUtils.storeDiff(prefs, "dicomTransferRole", a.getRole(), b.getRole());
        PreferencesUtils.storeDiff(prefs, "dcmAETitle", a.getAETitles(), b.getAETitles());
        PreferencesUtils.storeDiff(prefs, "dcmSOPClass", a.getSOPClasses(), b.getSOPClasses());
        PreferencesUtils.storeDiff(prefs, "labeledURI", a.getURI(), b.getURI());
    }

    private void mergeChilds(ApplicationEntity prevAE, ApplicationEntity ae,
            Preferences aeNode) throws BackingStoreException {
        mergeTransferCapabilities(prevAE, ae, aeNode);

        for (PreferencesDicomConfigurationExtension ext : extensions)
            ext.mergeChilds(prevAE, ae, aeNode);
    }

    private void mergeTransferCapabilities(ApplicationEntity prevAE,
            ApplicationEntity ae, Preferences aeNode)
            throws BackingStoreException {
        Preferences tcsNode = aeNode.node("dicomTransferCapability");
        mergeTransferCapabilities(prevAE, ae, tcsNode, TransferCapability.Role.SCU);
        mergeTransferCapabilities(prevAE, ae, tcsNode, TransferCapability.Role.SCP);
    }

    private void mergeTransferCapabilities(ApplicationEntity prevAE,
            ApplicationEntity ae, Preferences tcsNode, Role role)
                    throws BackingStoreException {
        Preferences roleNode = tcsNode.node(role.name());
        for (TransferCapability tc : prevAE.getTransferCapabilitiesWithRole(role))
            if (ae.getTransferCapabilityFor(tc.getSopClass(), role) == null)
                roleNode.node(tc.getSopClass()).removeNode();
        for (TransferCapability tc : ae.getTransferCapabilitiesWithRole(role)) {
            Preferences tcNode = roleNode.node(tc.getSopClass());
            TransferCapability prev =
                    prevAE.getTransferCapabilityFor(tc.getSopClass(), role);
            if (prev == null)
                storeTo(tc, tcNode);
            else
                storeDiffs(tcNode, prev, tc);
        }
       
    }

    private Device loadDevice(Preferences deviceNode) throws ConfigurationException {

        // get the device cache for this loading phase
        Map<String, Device> deviceCache = currentlyLoadedDevicesLocal.get();

        // if there is none, create one for the current thread and remember that it should be cleaned up when the device is loaded
        boolean doCleanUpCache = false;
        if (deviceCache == null) {
            doCleanUpCache = true;
            deviceCache = new HashMap<String, Device>();
            currentlyLoadedDevicesLocal.set(deviceCache);
        }

        // if a requested device is already being (was) loaded, do not load it again, just return existing Device object
        if (deviceCache.containsKey(deviceNode.name()))
            return deviceCache.get(deviceNode.name());
       
        try {
            Device device = newDevice(deviceNode);

            // remember this device so it won't be loaded again in this run
            deviceCache.put(deviceNode.name(), device);
           
            loadFrom(device, deviceNode);
            loadChilds(device, deviceNode);
            return device;
        } catch (BackingStoreException e) {
            throw new ConfigurationException(e);
        } catch (CertificateException e) {
            throw new ConfigurationException(e);
        }   finally {

            // if this loadDevice call initialized the cache, then clean it up
            if (doCleanUpCache) currentlyLoadedDevicesLocal.remove();
        }
    }

    private void loadChilds(Device device, Preferences deviceNode)
            throws BackingStoreException, ConfigurationException {
        Preferences connsNode = deviceNode.node("dcmNetworkConnection");
        for (int connIndex : sort(connsNode.childrenNames())) {
            Connection conn = newConnection();
            loadFrom(conn, connsNode.node("" + connIndex));
            device.addConnection(conn);
        }
        List<Connection> devConns = device.listConnections();
        Preferences aesNode = deviceNode.node("dcmNetworkAE");
        for (String aet : aesNode.childrenNames()) {
            Preferences aeNode = aesNode.node(aet);
            ApplicationEntity ae = newApplicationEntity(aeNode);
            loadFrom(ae, aeNode);
            int n = aeNode.getInt("dicomNetworkConnectionReference.#", 0);
            for (int i = 0; i < n; i++) {
                ae.addConnection(devConns.get(
                        aeNode.getInt("dicomNetworkConnectionReference." + (i+1), 0) - 1));
            }
            loadChilds(ae, aeNode);
            device.addApplicationEntity(ae);
        }
        for (PreferencesDicomConfigurationExtension ext : extensions)
            ext.loadChilds(device, deviceNode);
    }

    private void loadChilds(ApplicationEntity ae, Preferences aeNode)
            throws BackingStoreException {
        Preferences tcsNode = aeNode.node("dicomTransferCapability");
        loadTransferCapabilities(ae, tcsNode, TransferCapability.Role.SCU);
        loadTransferCapabilities(ae, tcsNode, TransferCapability.Role.SCP);

        for (PreferencesDicomConfigurationExtension ext : extensions)
            ext.loadChilds(ae, aeNode);
    }

    private void loadTransferCapabilities(ApplicationEntity ae,
            Preferences tcsNode, Role role) throws BackingStoreException {
        Preferences roleNode = tcsNode.node(role.name());
        for (String cuid : roleNode.childrenNames())
            ae.addTransferCapability(
                    loadTransferCapability(roleNode.node(cuid), cuid, role));
    }

    public void load(AttributeCoercions acs, Preferences aeNode)
            throws BackingStoreException {
        Preferences acsNode = aeNode.node("dcmAttributeCoercion");
        for (String cn : acsNode.childrenNames()) {
            Preferences acNode = acsNode.node(cn);
            acs.add(new AttributeCoercion(
                    cn,
                    PreferencesUtils.stringArray(acNode, "dcmSOPClass"),
                    Dimse.valueOf(acNode.get("dcmDIMSE", null)),
                    TransferCapability.Role.valueOf(
                            acNode.get("dicomTransferRole", null)),
                    PreferencesUtils.stringArray(acNode, "dcmAETitle"),
                    acNode.get("labeledURI", null)));
        }
    }

    private static int[] sort(String[] ss) {
        int[] a = new int[ss.length];
        for (int i = 0; i < a.length; i++)
            a[i] = Integer.parseInt(ss[i]);
        Arrays.sort(a);
        return a;
    }

    private Device newDevice(Preferences deviceNode) {
        return new Device(deviceNode.name());
    }

    private Connection newConnection() {
        return new Connection();
    }

    private ApplicationEntity newApplicationEntity(Preferences aeNode) {
        return new ApplicationEntity(aeNode.name());
    }

    private void loadFrom(TransferCapability tc, Preferences prefs) {
        tc.setCommonName(prefs.get("cn", null));
        tc.setTransferSyntaxes(PreferencesUtils.stringArray(prefs, "dicomTransferSyntax"));
        tc.setQueryOptions(toQueryOptions(prefs));
        tc.setStorageOptions(toStorageOptions(prefs));
    }

    private static EnumSet<QueryOption> toQueryOptions(Preferences prefs) {
        String relational = prefs.get("dcmRelationalQueries", null);
        String datetime = prefs.get("dcmCombinedDateTimeMatching", null);
        String fuzzy = prefs.get("dcmFuzzySemanticMatching", null);
        String timezone = prefs.get("dcmTimezoneQueryAdjustment", null);
        if (relational == null && datetime == null && fuzzy == null && timezone == null)
            return null;
        EnumSet<QueryOption> opts = EnumSet.noneOf(QueryOption.class);
        if (Boolean.parseBoolean(relational))
            opts.add(QueryOption.RELATIONAL);
        if (Boolean.parseBoolean(datetime))
            opts.add(QueryOption.DATETIME);
        if (Boolean.parseBoolean(fuzzy))
            opts.add(QueryOption.FUZZY);
        if (Boolean.parseBoolean(timezone))
            opts.add(QueryOption.TIMEZONE);
        return opts ;
    }

    private static StorageOptions toStorageOptions(Preferences prefs) {
        int levelOfSupport = prefs.getInt("dcmStorageConformance", -1);
        int signatureSupport = prefs.getInt("dcmDigitalSignatureSupport", -1);
        int coercion = prefs.getInt("dcmDataElementCoercion", -1);
        if (levelOfSupport == -1 && signatureSupport == -1 && coercion == -1)
            return null;
        StorageOptions opts = new StorageOptions();
        if (levelOfSupport != -1)
            opts.setLevelOfSupport(StorageOptions.LevelOfSupport.valueOf(levelOfSupport));
        if (signatureSupport != -1)
            opts.setDigitalSignatureSupport(
                    StorageOptions.DigitalSignatureSupport.valueOf(signatureSupport));
        if (coercion != -1)
            opts.setElementCoercion(StorageOptions.ElementCoercion.valueOf(coercion));
        return opts;
    }

    private TransferCapability loadTransferCapability(Preferences prefs,
            String cuid, Role role) {
        TransferCapability tc = new TransferCapability(null, cuid, role,
                UID.ImplicitVRLittleEndian);
        loadFrom(tc, prefs);
        return tc;
    }

    private void loadFrom(Device device, Preferences prefs)
            throws CertificateException, BackingStoreException {
        device.setDescription(prefs.get("dicomDescription", null));
        device.setManufacturer(prefs.get("dicomManufacturer", null));
        device.setManufacturerModelName(prefs.get("dicomManufacturerModelName", null));
        device.setSoftwareVersions(PreferencesUtils.stringArray(prefs, "dicomSoftwareVersion"));
        device.setStationName(prefs.get("dicomStationName", null));
        device.setDeviceSerialNumber(prefs.get("dicomDeviceSerialNumber", null));
        device.setIssuerOfPatientID(
                issuerOf(prefs.get("dicomIssuerOfPatientID", null)));
        device.setIssuerOfAccessionNumber(
                issuerOf(prefs.get("dicomIssuerOfAccessionNumber", null)));
        device.setOrderPlacerIdentifier(
                issuerOf(prefs.get("dicomOrderPlacerIdentifier", null)));
        device.setOrderFillerIdentifier(
                issuerOf(prefs.get("dicomOrderFillerIdentifier", null)));
        device.setIssuerOfAdmissionID(
                issuerOf(prefs.get("dicomIssuerOfAdmissionID", null)));
        device.setIssuerOfServiceEpisodeID(
                issuerOf(prefs.get("dicomIssuerOfServiceEpisodeID", null)));
        device.setIssuerOfContainerIdentifier(
                issuerOf(prefs.get("dicomIssuerOfContainerIdentifier", null)));
        device.setIssuerOfSpecimenIdentifier(
                issuerOf(prefs.get("dicomIssuerOfSpecimenIdentifier", null)));
        device.setInstitutionNames(PreferencesUtils.stringArray(prefs, "dicomInstitutionName"));
        device.setInstitutionCodes(PreferencesUtils.codeArray(prefs, "dicomInstitutionCode"));
        device.setInstitutionAddresses(PreferencesUtils.stringArray(prefs, "dicomInstitutionAddress"));
        device.setInstitutionalDepartmentNames(
                PreferencesUtils.stringArray(prefs, "dicomInstitutionalDepartmentName"));
        device.setPrimaryDeviceTypes(PreferencesUtils.stringArray(prefs, "dicomPrimaryDeviceType"));
        device.setRelatedDeviceRefs(PreferencesUtils.stringArray(prefs, "dicomRelatedDeviceReference"));
        for (String ref : PreferencesUtils.stringArray(prefs, "dicomAuthorizedNodeCertificateReference"))
            device.setAuthorizedNodeCertificates(ref, loadCertificates(ref));
        for (String ref : PreferencesUtils.stringArray(prefs, "dicomThisNodeCertificateReference"))
            device.setThisNodeCertificates(ref, loadCertificates(ref));
        device.setVendorData(toVendorData(prefs, "dicomVendorData"));
        device.setInstalled(prefs.getBoolean("dicomInstalled", false));
       
        device.setLimitOpenAssociations(
                prefs.getInt("dcmLimitOpenAssociations", 0));
        device.setTrustStoreURL(prefs.get("dcmTrustStoreURL", null));
        device.setTrustStoreType(prefs.get("dcmTrustStoreType", null));
        device.setTrustStorePin(prefs.get("dcmTrustStorePin", null));
        device.setTrustStorePinProperty(
                prefs.get("dcmTrustStorePinProperty", null));
        device.setKeyStoreURL(prefs.get("dcmKeyStoreURL", null));
        device.setKeyStoreType(prefs.get("dcmKeyStoreType", null));
        device.setKeyStorePin(prefs.get("dcmKeyStorePin", null));
        device.setKeyStorePinProperty(
                prefs.get("dcmKeyStorePinProperty", null));
        device.setKeyStoreKeyPin(prefs.get("dcmKeyStoreKeyPin", null));
        device.setKeyStoreKeyPinProperty(
                prefs.get("dcmKeyStoreKeyPinProperty", null));
        device.setTimeZoneOfDevice(timeZoneOf(
          prefs.get("dcmTimeZoneOfDevice", null)));
        for (PreferencesDicomConfigurationExtension ext : extensions)
            ext.loadFrom(device, prefs);
    }

    private void loadFrom(Connection conn, Preferences prefs) {
        conn.setCommonName(prefs.get("cn", null));
        conn.setHostname(prefs.get("dicomHostname", null));
        conn.setPort(prefs.getInt("dicomPort", Connection.NOT_LISTENING));
        conn.setTlsCipherSuites(PreferencesUtils.stringArray(prefs, "dicomTLSCipherSuite"));
        conn.setInstalled(PreferencesUtils.booleanValue(prefs.get("dicomInstalled", null)));

        conn.setProtocol(Protocol.valueOf(prefs.get("dcmProtocol", "DICOM")));
        conn.setHttpProxy(prefs.get("dcmHTTPProxy", null));
        conn.setBlacklist(PreferencesUtils.stringArray(prefs, "dcmBlacklistedHostname"));
        conn.setBacklog(prefs.getInt("dcmTCPBacklog", Connection.DEF_BACKLOG));
        conn.setConnectTimeout(
                prefs.getInt("dcmTCPConnectTimeout", Connection.NO_TIMEOUT));
        conn.setRequestTimeout(
                prefs.getInt("dcmAARQTimeout", Connection.NO_TIMEOUT));
        conn.setAcceptTimeout(
                prefs.getInt("dcmAAACTimeout", Connection.NO_TIMEOUT));
        conn.setReleaseTimeout(
                prefs.getInt("dcmARRPTimeout", Connection.NO_TIMEOUT));
        conn.setResponseTimeout(prefs.getInt("dcmResponseTimeout", Connection.NO_TIMEOUT));
        conn.setRetrieveTimeout(prefs.getInt("dcmRetrieveTimeout", Connection.NO_TIMEOUT));
        conn.setIdleTimeout(
                prefs.getInt("dcmIdleTimeout", Connection.NO_TIMEOUT));
        conn.setSocketCloseDelay(
                prefs.getInt("dcmTCPCloseDelay", Connection.DEF_SOCKETDELAY));
        conn.setSendBufferSize(
                prefs.getInt("dcmTCPSendBufferSize", Connection.DEF_BUFFERSIZE));
        conn.setReceiveBufferSize(
                prefs.getInt("dcmTCPReceiveBufferSize", Connection.DEF_BUFFERSIZE));
        conn.setTcpNoDelay(prefs.getBoolean("dcmTCPNoDelay", true));
        conn.setBindAddress(prefs.get("dcmBindAddress", null));
        conn.setClientBindAddress(prefs.get("dcmClientBindAddress", null));
        conn.setTlsNeedClientAuth(prefs.getBoolean("dcmTLSNeedClientAuth", true));
        String[] tlsProtocols = PreferencesUtils.stringArray(prefs, "dcmTLSProtocol");
        if (tlsProtocols.length > 0)
            conn.setTlsProtocols(tlsProtocols);
        conn.setSendPDULength(prefs.getInt("dcmSendPDULength",
                Connection.DEF_MAX_PDU_LENGTH));
        conn.setReceivePDULength(prefs.getInt("dcmReceivePDULength",
                Connection.DEF_MAX_PDU_LENGTH));
        conn.setMaxOpsPerformed(prefs.getInt("dcmMaxOpsPerformed",
                Connection.SYNCHRONOUS_MODE));
        conn.setMaxOpsInvoked(prefs.getInt("dcmMaxOpsInvoked",
                Connection.SYNCHRONOUS_MODE));
        conn.setPackPDV(prefs.getBoolean("dcmPackPDV", true));
    }

    private void loadFrom(ApplicationEntity ae, Preferences prefs) {
        ae.setDescription(prefs.get("dicomDescription", null));
        ae.setVendorData(toVendorData(prefs, "dicomVendorData"));
        ae.setApplicationClusters(PreferencesUtils.stringArray(prefs, "dicomApplicationCluster"));
        ae.setPreferredCallingAETitles(PreferencesUtils.stringArray(prefs, "dicomPreferredCallingAETitle"));
        ae.setPreferredCalledAETitles(PreferencesUtils.stringArray(prefs, "dicomPreferredCalledAETitle"));
        ae.setAssociationInitiator(prefs.getBoolean("dicomAssociationInitiator", false));
        ae.setAssociationAcceptor(prefs.getBoolean("dicomAssociationAcceptor", false));
        ae.setSupportedCharacterSets(PreferencesUtils.stringArray(prefs, "dicomSupportedCharacterSet"));
        ae.setInstalled(PreferencesUtils.booleanValue(prefs.get("dicomInstalled", null)));

        PreferencesUtils.storeNotEmpty(prefs, "dcmAcceptedCallingAETitle", ae.getAcceptedCallingAETitles());
        ae.setAcceptedCallingAETitles(
                PreferencesUtils.stringArray(prefs, "dcmAcceptOnlyPreferredCallingAETitle"));

        for (PreferencesDicomConfigurationExtension ext : extensions)
            ext.loadFrom(ae, prefs);
}

    private static byte[][] toVendorData(Preferences prefs, String key) {
       int n = prefs.getInt(key + ".#", 0);
       byte[][] bb = new byte[n][];
       for (int i = 0; i < n; i++)
           bb[i] = prefs.getByteArray(key + '.' + (i+1), null);
       return bb;
    }

    private static Issuer issuerOf(String s) {
        return s != null ? new Issuer(s) : null;
    }

    private static void storeNotEmpty(Preferences prefs, String key, byte[][] values) {
        if (values != null && values.length != 0) {
            int count = 0;
            for (byte[] value : values)
                prefs.putByteArray(key + '.' + (++count), value);
            prefs.putInt(key + ".#", count);
        }
    }

    private static <T> void storeDiff(Preferences prefs, String key, T[] prevs, T[] vals) {
        if (!Arrays.equals(prevs, vals)) {
            PreferencesUtils.removeKeys(prefs, key, vals.length, prevs.length);
            PreferencesUtils.storeNotEmpty(prefs, key, vals);
        }
    }

    private static void storeDiff(Preferences prefs, String key,
            byte[][] prevs, byte[][] vals) {
        if (!equals(prevs, vals)) {
            PreferencesUtils.removeKeys(prefs, key, vals.length, prevs.length);
            storeNotEmpty(prefs, key, vals);
        }
    }

    private static boolean equals(byte[][] bb1, byte[][] bb2) {
        if (bb1.length != bb2.length)
            return false;
       
        for (int i = 0; i < bb1.length; i++)
            if (!Arrays.equals(bb1[i], bb2[i]))
                return false;

        return true;
    }

    @Override
    public synchronized void persistCertificates(String certRef, X509Certificate... certs)
            throws ConfigurationException {
        try {
            storeCertificates(certRef, certs);
        } catch (CertificateEncodingException e) {
            throw new ConfigurationException(e);
        } catch (BackingStoreException e) {
            throw new ConfigurationException(e);
        }
    }

    private void storeCertificates(String certRef, X509Certificate... certs)
            throws CertificateEncodingException, BackingStoreException {
        if (certs != null && certs.length != 0) {
            int count = 0;
            Preferences prefs = rootPrefs.node(certRef);
            for (X509Certificate cert : certs)
                prefs.putByteArray(USER_CERTIFICATE + '.' + (++count),
                        cert.getEncoded());
            prefs.putInt(USER_CERTIFICATE + ".#", count);
            prefs.flush();
        }
    }

    @Override
    public synchronized void removeCertificates(String certRef)
            throws ConfigurationException {
        Preferences prefs = rootPrefs.node(certRef);
        PreferencesUtils.removeKeys(prefs, USER_CERTIFICATE, 0,
                prefs.getInt(USER_CERTIFICATE + ".#", 0));
        try {
            prefs.flush();
        } catch (BackingStoreException e) {
            throw new ConfigurationException(e);
        }
    }

    @Override
    public synchronized X509Certificate[] findCertificates(String certRef)
            throws ConfigurationException {
        try {
            return loadCertificates(certRef);
        } catch (CertificateException e) {
            throw new ConfigurationException(e);
        } catch (BackingStoreException e) {
            throw new ConfigurationException(e);
        }
    }

    private X509Certificate[] loadCertificates(String certRef)
            throws CertificateException, BackingStoreException {
        if (!rootPrefs.nodeExists(certRef))
            return EMPTY_X509_CERTIFICATES;

        Preferences prefs = rootPrefs.node(certRef);
        int n = prefs.getInt(USER_CERTIFICATE + ".#", 0);
        if (n == 0)
            return EMPTY_X509_CERTIFICATES;
       
        CertificateFactory cf = CertificateFactory.getInstance("X509");
        X509Certificate[] certs = new X509Certificate[n];
        for (int i = 0; i < n; i++)
            certs[i] = (X509Certificate) cf.generateCertificate(
                    new ByteArrayInputStream(
                            prefs.getByteArray(USER_CERTIFICATE + '.' + (i+1), null)));
        return certs;
    }

    @Override
    public void close() {
        // NOOP
    }

    @Override
    public void sync() throws ConfigurationException {
        try {
            rootPrefs.sync();
        } catch (BackingStoreException e) {
            throw new ConfigurationException(e);
        }
    }

    private TimeZone timeZoneOf(String timeZoneID) {
  if(timeZoneID==null)
  return null;
  else
  return TimeZone.getTimeZone(timeZoneID);
    }
   
    private String toTimeZoneString(TimeZone tz)
    {
  if(tz==null)
      return null;
  else
      return tz.getID();
    }
}
TOP

Related Classes of org.dcm4che3.conf.prefs.PreferencesDicomConfiguration

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.