Package org.dcm4che3.conf.ldap

Source Code of org.dcm4che3.conf.ldap.LdapDicomConfiguration

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

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.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;

import javax.naming.Context;
import javax.naming.NameAlreadyBoundException;
import javax.naming.NameClassPair;
import javax.naming.NameNotFoundException;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.BasicAttribute;
import javax.naming.directory.BasicAttributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import javax.naming.directory.ModificationItem;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;

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.util.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* @author Gunter Zeilinger <gunterze@gmail.com>
*
*/
public final class LdapDicomConfiguration implements DicomConfiguration {

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

    private static final String CN_UNIQUE_AE_TITLES_REGISTRY = "cn=Unique AE Titles Registry,";
    private static final String CN_DEVICES = "cn=Devices,";
    private static final String DICOM_CONFIGURATION = "DICOM Configuration";
    private static final String DICOM_CONFIGURATION_ROOT = "dicomConfigurationRoot";
    private static final String PKI_USER = "pkiUser";
    private static final String USER_CERTIFICATE_BINARY = "userCertificate;binary";
    private static final X509Certificate[] EMPTY_X509_CERTIFICATES = {};

    private final DirContext ctx;
    private final String baseDN;
    private String configurationDN;
    private String devicesDN;
    private String aetsRegistryDN;
    private String configurationCN = DICOM_CONFIGURATION;
    private String configurationRoot = DICOM_CONFIGURATION_ROOT;
    private String pkiUser = PKI_USER;
    private String userCertificate = USER_CERTIFICATE_BINARY;
    private boolean extended = true;

    private final List<LdapDicomConfigurationExtension> extensions =
            new ArrayList<LdapDicomConfigurationExtension>();

    /**
     * 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 LdapDicomConfiguration() throws ConfigurationException {
        this(ResourceManager.getInitialEnvironment());
    }

    @SuppressWarnings("unchecked")
    public LdapDicomConfiguration(Hashtable<?,?> env)
            throws ConfigurationException {
        try {
            // split baseDN from LDAP URL
            env = (Hashtable<?,?>) env.clone();
            String s = (String) env.get(Context.PROVIDER_URL);
            int end = s.lastIndexOf('/');
            ((Hashtable<Object,Object>) env)
                .put(Context.PROVIDER_URL, s.substring(0, end));
            this.baseDN = s.substring(end+1);
            this.ctx = new InitialDirContext(env);
        } catch (Exception e) {
            throw new ConfigurationException(e);
        }
    }

    public final boolean isExtended() {
        return extended;
    }

    public final void setExtended(boolean extended) {
        this.extended = extended;
    }

    public final void setConfigurationCN(String configurationCN) {
        this.configurationCN = configurationCN;
    }

    public final String getConfigurationCN() {
        return configurationCN;
    }

    public final void setConfigurationRoot(String configurationRoot) {
        this.configurationRoot = configurationRoot;
    }

    public final String getConfigurationRoot() {
        return configurationRoot;
    }

    public void setPkiUser(String pkiUser) {
        this.pkiUser = pkiUser;
    }

    public String getPkiUser() {
        return pkiUser;
    }

    public void setUserCertificate(String userCertificate) {
        this.userCertificate = userCertificate;
    }

    public String getUserCertificate() {
        return userCertificate;
    }

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

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

        ext.setDicomConfiguration(null);
        return true;
    }


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

    @Override
    public synchronized void close() {
        safeClose(ctx);
    }

    @Override
    public synchronized boolean configurationExists() throws ConfigurationException {
        return configurationDN != null || findConfiguration();
    }

    public boolean exists(String dn) throws NamingException {
        try {
            ctx.getAttributes(dn);
            return true;
        } catch (NameNotFoundException e) {
            return false;
        }
    }

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

        try {
            destroySubcontextWithChilds(configurationDN);
            LOG.info("Purge DICOM Configuration at {}", configurationDN);
            clearConfigurationDN();
        } catch (NamingException e) {
            throw new ConfigurationException(e);
        }
        return true;
    }

    @Override
    public synchronized boolean registerAETitle(String aet) throws ConfigurationException {
        ensureConfigurationExists();
        try {
            createSubcontext(aetDN(aet, aetsRegistryDN),
                    LdapUtils.attrs("dicomUniqueAETitle", "dicomAETitle", aet));
            return true;
        } catch (NameAlreadyBoundException e) {
            return false;
        } catch (NamingException e) {
            throw new ConfigurationException(e);
       }
    }

    @Override
    public synchronized void unregisterAETitle(String aet) throws ConfigurationException {
        if (configurationExists())
            try {
                ctx.destroySubcontext(aetDN(aet, aetsRegistryDN));
            } catch (NameNotFoundException e) {
            } catch (NamingException e) {
                throw new ConfigurationException(e);
            }
    }

    @Override
    public synchronized ApplicationEntity findApplicationEntity(String aet)
            throws ConfigurationException {
        return findDevice(
                "(&(objectclass=dicomNetworkAE)(dicomAETitle=" + aet + "))", aet)
            .getApplicationEntity(aet);
    }

    public synchronized Device findDevice(String filter, String childName)
            throws ConfigurationException {
        if (!configurationExists())
            throw new ConfigurationNotFoundException();

        SearchControls ctls = new SearchControls();
        ctls.setSearchScope(SearchControls.SUBTREE_SCOPE);
        ctls.setCountLimit(1);
        ctls.setReturningAttributes(StringUtils.EMPTY_STRING);
        ctls.setReturningObjFlag(false);
        NamingEnumeration<SearchResult> ne = null;
        String childDN;
        try {
            ne = ctx.search(devicesDN, filter, ctls);
            if (!ne.hasMore())
                throw new ConfigurationNotFoundException(childName);

            childDN = ne.next().getNameInNamespace();
        } catch (NamingException e) {
            throw new ConfigurationException(e);
        } finally {
           LdapUtils.safeClose(ne);
        }
        String deviceDN = childDN.substring(childDN.indexOf(',') + 1);
        return loadDevice(deviceDN);
    }

    @Override
    public synchronized Device findDevice(String name) throws ConfigurationException {
        if (!configurationExists())
            throw new ConfigurationNotFoundException();

        return loadDevice(deviceRef(name));
    }

    @Override
    public synchronized DeviceInfo[] listDeviceInfos(DeviceInfo keys)
            throws ConfigurationException {
        if (!configurationExists())
            return new DeviceInfo[0];

        ArrayList<DeviceInfo> results = new ArrayList<DeviceInfo>();
        NamingEnumeration<SearchResult> ne = null;
        try {
            ne = search(devicesDN, toFilter(keys), "dicomDeviceName",
                "dicomDescription",
                "dicomManufacturer",
                "dicomManufacturerModelName",
                "dicomSoftwareVersion",
                "dicomStationName",
                "dicomInstitutionName",
                "dicomInstitutionDepartmentName",
                "dicomPrimaryDeviceType",
                "dicomInstalled");
            while (ne.hasMore()) {
                DeviceInfo deviceInfo = new DeviceInfo();
                loadFrom(deviceInfo, ne.next().getAttributes());
                results.add(deviceInfo);
            }
        } catch (NamingException e) {
            throw new ConfigurationException(e);
        } finally {
           LdapUtils.safeClose(ne);
        }
        return results.toArray(new DeviceInfo[results.size()]);
    }

    private String toFilter(DeviceInfo keys) {
        if (keys == null)
            return "(objectclass=dicomDevice)";

        StringBuilder sb = new StringBuilder();
        sb.append("(&(objectclass=dicomDevice)");
        appendFilter("dicomDeviceName", keys.getDeviceName(), sb);
        appendFilter("dicomDescription", keys.getDescription(), sb);
        appendFilter("dicomManufacturer", keys.getManufacturer(), sb);
        appendFilter("dicomManufacturerModelName", keys.getManufacturerModelName(), sb);
        appendFilter("dicomSoftwareVersion", keys.getSoftwareVersions(), sb);
        appendFilter("dicomStationName", keys.getStationName(), sb);
        appendFilter("dicomInstitutionName", keys.getInstitutionNames(), sb);
        appendFilter("dicomInstitutionDepartmentName", keys.getInstitutionalDepartmentNames(), sb);
        appendFilter("dicomPrimaryDeviceType", keys.getPrimaryDeviceTypes(), sb);
        appendFilter("dicomInstalled", keys.getInstalled(), sb);
        sb.append(")");
        return sb.toString();
    }

    private void appendFilter(String attrid, Boolean value, StringBuilder sb) {
        if (value != null)
            appendFilter(attrid, LdapUtils.toString(value), sb);
    }

    private void appendFilter(String attrid, String value, StringBuilder sb) {
        if (value == null)
            return;

        sb.append('(').append(attrid).append('=').append(value).append(')');
    }

    private void appendFilter(String attrid, String[] values, StringBuilder sb) {
        if (values.length == 0)
            return;

        if (values.length == 1) {
            appendFilter(attrid, values[0], sb);
            return;
        }

        sb.append("(|");
        for (String value : values)
            appendFilter(attrid, value, sb);
        sb.append(")");
    }

    private void loadFrom(DeviceInfo deviceInfo, Attributes attrs)
            throws NamingException {
        deviceInfo.setDeviceName(
                LdapUtils.stringValue(attrs.get("dicomDeviceName"), null));
        deviceInfo.setDescription(
                LdapUtils.stringValue(attrs.get("dicomDescription"), null));
        deviceInfo.setManufacturer(
                LdapUtils.stringValue(attrs.get("dicomManufacturer"), null));
        deviceInfo.setManufacturerModelName(
                LdapUtils.stringValue(attrs.get("dicomManufacturerModelName"), null));
        deviceInfo.setSoftwareVersions(
                LdapUtils.stringArray(attrs.get("dicomSoftwareVersion")));
        deviceInfo.setStationName(
                LdapUtils.stringValue(attrs.get("dicomStationName"), null));
        deviceInfo.setInstitutionNames(
                LdapUtils.stringArray(attrs.get("dicomInstitutionName")));
        deviceInfo.setInstitutionalDepartmentNames(
                LdapUtils.stringArray(attrs.get("dicomInstitutionDepartmentName")));
        deviceInfo.setPrimaryDeviceTypes(
                LdapUtils.stringArray(attrs.get("dicomPrimaryDeviceType")));
        deviceInfo.setInstalled(
                LdapUtils.booleanValue(attrs.get("dicomInstalled"), true));
    }

    @Override
    public synchronized String[] listDeviceNames() throws ConfigurationException {
        if (!configurationExists())
            return StringUtils.EMPTY_STRING;

        return list(devicesDN, "(objectclass=dicomDevice)", "dicomDeviceName");
    }

    @Override
    public synchronized String[] listRegisteredAETitles() throws ConfigurationException {
        if (!configurationExists())
            return StringUtils.EMPTY_STRING;

        return list(aetsRegistryDN, "(objectclass=dicomUniqueAETitle)", "dicomAETitle");
    }

    public synchronized String[] list(String dn, String filter, String attrID)
            throws ConfigurationException {
        ArrayList<String> values = new ArrayList<String>();
        NamingEnumeration<SearchResult> ne = null;
        try {
            ne = search(dn, filter, attrID );
            while (ne.hasMore()) {
                SearchResult sr = ne.next();
                Attributes attrs = sr.getAttributes();
                values.add(LdapUtils.stringValue(attrs.get(attrID), null));
            }
        } catch (NamingException e) {
            throw new ConfigurationException(e);
        } finally {
           LdapUtils.safeClose(ne);
        }
        return values.toArray(new String[values.size()]);
    }

    @Override
    public synchronized void persist(Device device) throws ConfigurationException {
        ensureConfigurationExists();
        String deviceName = device.getDeviceName();
        String deviceDN = deviceRef(deviceName);
        boolean rollback = false;
        try {
            createSubcontext(deviceDN, storeTo(device, new BasicAttributes(true)));
            rollback = true;
            storeChilds(deviceDN, device);
            updateCertificates(device);
            rollback = false;
        } catch (NameAlreadyBoundException e) {
            throw new ConfigurationAlreadyExistsException(deviceName);
        } catch (NamingException e) {
            throw new ConfigurationException(e);
        } catch (CertificateException e) {
            throw new ConfigurationException(e);
        } finally {
            if (rollback)
                try {
                    destroySubcontextWithChilds(deviceDN);
                } catch (NamingException e) {
                    LOG.warn("Rollback failed:", e);
                }
        }
    }

    private void updateCertificates(Device device)
            throws CertificateException, NamingException {
        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 dn,
            X509Certificate[] prev, X509Certificate[] certs)
            throws CertificateEncodingException, NamingException {
        if (!LdapUtils.equals(prev, certs))
            storeCertificates(dn, certs);
    }

    private void storeChilds(String deviceDN, Device device) throws NamingException, ConfigurationException {
        for (Connection conn : device.listConnections())
            createSubcontext(LdapUtils.dnOf(conn, deviceDN), storeTo(conn, new BasicAttributes(true)));
        for (LdapDicomConfigurationExtension ext : extensions)
            ext.storeChilds(deviceDN, device);
        for (ApplicationEntity ae : device.getApplicationEntities()) {
            String aeDN = aetDN(ae.getAETitle(), deviceDN);
            createSubcontext(aeDN, storeTo(ae, deviceDN, new BasicAttributes(true)));
            storeChilds(aeDN, ae);
            for (LdapDicomConfigurationExtension ext : extensions) {
                ext.storeChilds(aeDN, ae);
            }
        }
    }

    private void storeChilds(String aeDN, ApplicationEntity ae)
            throws NamingException {
        for (TransferCapability tc : ae.getTransferCapabilities())
            createSubcontext(dnOf(tc, aeDN), storeTo(tc, new BasicAttributes(true)));
    }

    @Override
    public synchronized void merge(Device device) throws ConfigurationException {
        if (!configurationExists())
            throw new ConfigurationNotFoundException();

        String deviceDN = deviceRef(device.getDeviceName());
        Device prev = loadDevice(deviceDN);
        try {
            modifyAttributes(deviceDN, storeDiffs(prev, device, new ArrayList<ModificationItem>()));
            mergeChilds(prev, device, deviceDN);
            updateCertificates(prev, device);
        } catch (NameNotFoundException e) {
            throw new ConfigurationNotFoundException(e);
        } catch (NamingException e) {
            throw new ConfigurationException(e);
        } catch (CertificateException e) {
            throw new ConfigurationException(e);
        }
    }

    private void updateCertificates(Device prev, Device device)
            throws CertificateException, NamingException {
        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, String deviceDN)
            throws NamingException, ConfigurationException {
        mergeConnections(prev, device, deviceDN);
        mergeAEs(prev, device, deviceDN);
        for (LdapDicomConfigurationExtension ext : extensions)
            ext.mergeChilds(prev, device, deviceDN);
    }

    @Override
    public synchronized void removeDevice(String name) throws ConfigurationException {
        if (!configurationExists())
            throw new ConfigurationNotFoundException();

        removeDeviceWithDN(deviceRef(name));
    }

    private void removeDeviceWithDN(String deviceDN) throws ConfigurationException {
        try {
            destroySubcontextWithChilds(deviceDN);
        } catch (NameNotFoundException e) {
            throw new ConfigurationNotFoundException(e);
        } catch (NamingException e) {
            throw new ConfigurationException(e);
        }
    }

    public synchronized void createSubcontext(String name, Attributes attrs)
            throws NamingException {
        safeClose(ctx.createSubcontext(name, attrs));
    }

    public synchronized void destroySubcontext(String dn) throws NamingException {
        ctx.destroySubcontext(dn);
    }

    public synchronized void destroySubcontextWithChilds(String name)
            throws NamingException {
        NamingEnumeration<NameClassPair> list = ctx.list(name);
        try {
            while (list.hasMore())
                destroySubcontextWithChilds(list.next().getNameInNamespace());
        } finally {
            LdapUtils.safeClose(list);
        }
        ctx.destroySubcontext(name);
    }

    private void setConfigurationDN(String configurationDN) {
        this.configurationDN = configurationDN;
        this.devicesDN = CN_DEVICES + configurationDN;
        this.aetsRegistryDN = CN_UNIQUE_AE_TITLES_REGISTRY + configurationDN;
    }

    public String getConfigurationDN() {
        return configurationDN;
    }

   private void clearConfigurationDN() {
        this.configurationDN = null;
        this.devicesDN = null;
        this.aetsRegistryDN = null;
    }

    public void ensureConfigurationExists() throws ConfigurationException {
        if (!configurationExists())
            initConfiguration();
    }

    private void initConfiguration() throws ConfigurationException {
        setConfigurationDN("cn=" + configurationCN + ',' + baseDN);
        try {
            createSubcontext(configurationDN,
                    LdapUtils.attrs(configurationRoot, "cn", configurationCN));
            createSubcontext(devicesDN,
                    LdapUtils.attrs("dicomDevicesRoot", "cn", "Devices"));
            createSubcontext(aetsRegistryDN,
                    LdapUtils.attrs("dicomUniqueAETitlesRegistryRoot",
                            "cn", "Unique AE Titles Registry"));
            LOG.info("Create DICOM Configuration at {}", configurationDN);
        } catch (NamingException e) {
            clearConfigurationDN();
            throw new ConfigurationException(e);
        }
    }

   private boolean findConfiguration() throws ConfigurationException {
        NamingEnumeration<SearchResult> ne = null;
        try {
            SearchControls ctls = new SearchControls();
            ctls.setSearchScope(SearchControls.SUBTREE_SCOPE);
            ctls.setCountLimit(1);
            ctls.setReturningAttributes(StringUtils.EMPTY_STRING);
            ctls.setReturningObjFlag(false);
            ne = ctx.search(
                    baseDN,
                    "(&(objectclass=" + configurationRoot
                            + ")(cn=" + configurationCN + "))",
                    ctls);
            if (!ne.hasMore())
                return false;
   
            setConfigurationDN(ne.next().getName() + "," + baseDN);
            return true;
        } catch (NamingException e) {
            throw new ConfigurationException(e);
        } finally {
            LdapUtils.safeClose(ne);
        }
    }

    private Attributes storeTo(Device device, Attributes attrs) {
        BasicAttribute objectclass = new BasicAttribute("objectclass", "dicomDevice");
        attrs.put(objectclass);
        LdapUtils.storeNotNull(attrs, "dicomDeviceName", device.getDeviceName());
        LdapUtils.storeNotNull(attrs, "dicomDescription", device.getDescription());
        LdapUtils.storeNotNull(attrs, "dicomManufacturer", device.getManufacturer());
        LdapUtils.storeNotNull(attrs, "dicomManufacturerModelName",
                device.getManufacturerModelName());
        LdapUtils.storeNotEmpty(attrs, "dicomSoftwareVersion",
                device.getSoftwareVersions());
        LdapUtils.storeNotNull(attrs, "dicomStationName", device.getStationName());
        LdapUtils.storeNotNull(attrs, "dicomDeviceSerialNumber",
                device.getDeviceSerialNumber());
        LdapUtils.storeNotNull(attrs, "dicomIssuerOfPatientID",
                device.getIssuerOfPatientID());
        LdapUtils.storeNotNull(attrs, "dicomIssuerOfAccessionNumber",
                device.getIssuerOfAccessionNumber());
        LdapUtils.storeNotNull(attrs, "dicomOrderPlacerIdentifier",
                device.getOrderPlacerIdentifier());
        LdapUtils.storeNotNull(attrs, "dicomOrderFillerIdentifier",
                device.getOrderFillerIdentifier());
        LdapUtils.storeNotNull(attrs, "dicomIssuerOfAdmissionID",
                device.getIssuerOfAdmissionID());
        LdapUtils.storeNotNull(attrs, "dicomIssuerOfServiceEpisodeID",
                device.getIssuerOfServiceEpisodeID());
        LdapUtils.storeNotNull(attrs, "dicomIssuerOfContainerIdentifier",
                device.getIssuerOfContainerIdentifier());
        LdapUtils.storeNotNull(attrs, "dicomIssuerOfSpecimenIdentifier",
                device.getIssuerOfSpecimenIdentifier());
        LdapUtils.storeNotEmpty(attrs, "dicomInstitutionName",
                device.getInstitutionNames());
        LdapUtils.storeNotEmpty(attrs, "dicomInstitutionCode",
                device.getInstitutionCodes());
        LdapUtils.storeNotEmpty(attrs, "dicomInstitutionAddress",
                device.getInstitutionAddresses());
        LdapUtils.storeNotEmpty(attrs, "dicomInstitutionDepartmentName",
                device.getInstitutionalDepartmentNames());
        LdapUtils.storeNotEmpty(attrs, "dicomPrimaryDeviceType",
                device.getPrimaryDeviceTypes());
        LdapUtils.storeNotEmpty(attrs, "dicomRelatedDeviceReference",
                device.getRelatedDeviceRefs());
        LdapUtils.storeNotEmpty(attrs, "dicomAuthorizedNodeCertificateReference",
                device.getAuthorizedNodeCertificateRefs());
        LdapUtils.storeNotEmpty(attrs, "dicomThisNodeCertificateReference",
                device.getThisNodeCertificateRefs());
        storeNotEmpty(attrs, "dicomVendorData", device.getVendorData());
        LdapUtils.storeBoolean(attrs, "dicomInstalled", device.isInstalled());
        if (!extended)
            return attrs;

        objectclass.add("dcmDevice");
        LdapUtils.storeNotDef(attrs, "dcmLimitOpenAssociations", device.getLimitOpenAssociations(), 0);
        LdapUtils.storeNotNull(attrs, "dcmTrustStoreURL", device.getTrustStoreURL());
        LdapUtils.storeNotNull(attrs, "dcmTrustStoreType", device.getTrustStoreType());
        LdapUtils.storeNotNull(attrs, "dcmTrustStorePin", device.getTrustStorePin());
        LdapUtils.storeNotNull(attrs, "dcmTrustStorePinProperty", device.getTrustStorePinProperty());
        LdapUtils.storeNotNull(attrs, "dcmKeyStoreURL", device.getKeyStoreURL());
        LdapUtils.storeNotNull(attrs, "dcmKeyStoreType", device.getKeyStoreType());
        LdapUtils.storeNotNull(attrs, "dcmKeyStorePin", device.getKeyStorePin());
        LdapUtils.storeNotNull(attrs, "dcmKeyStorePinProperty", device.getKeyStorePinProperty());
        LdapUtils.storeNotNull(attrs, "dcmKeyStoreKeyPin", device.getKeyStoreKeyPin());
        LdapUtils.storeNotNull(attrs, "dcmKeyStoreKeyPinProperty", device.getKeyStoreKeyPinProperty());
        LdapUtils.storeNotNull(attrs, "dcmTimeZoneOfDevice", toTimeZoneString(device.getTimeZoneOfDevice()));       
        for (LdapDicomConfigurationExtension ext : extensions)
            ext.storeTo(device, attrs);
        return attrs;
    }

    private Attributes storeTo(Connection conn, Attributes attrs) {
        BasicAttribute objectclass = new BasicAttribute("objectclass", "dicomNetworkConnection");
        attrs.put(objectclass);
        LdapUtils.storeNotNull(attrs, "cn", conn.getCommonName());
        LdapUtils.storeNotNull(attrs, "dicomHostname", conn.getHostname());
        LdapUtils.storeNotDef(attrs, "dicomPort", conn.getPort(), Connection.NOT_LISTENING);
        LdapUtils.storeNotEmpty(attrs, "dicomTLSCipherSuite", conn.getTlsCipherSuites());
        LdapUtils.storeNotNull(attrs, "dicomInstalled", conn.getInstalled());
        if (!extended)
            return attrs;

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

    private Attributes storeTo(ApplicationEntity ae, String deviceDN, Attributes attrs) {
        BasicAttribute objectclass = new BasicAttribute("objectclass", "dicomNetworkAE");
        attrs.put(objectclass);
        LdapUtils.storeNotNull(attrs, "dicomAETitle", ae.getAETitle());
        LdapUtils.storeNotNull(attrs, "dicomDescription", ae.getDescription());
        storeNotEmpty(attrs, "dicomVendorData", ae.getVendorData());
        LdapUtils.storeNotEmpty(attrs, "dicomApplicationCluster", ae.getApplicationClusters());
        LdapUtils.storeNotEmpty(attrs, "dicomPreferredCallingAETitle", ae.getPreferredCallingAETitles());
        LdapUtils.storeNotEmpty(attrs, "dicomPreferredCalledAETitle", ae.getPreferredCalledAETitles());
        LdapUtils.storeBoolean(attrs, "dicomAssociationInitiator", ae.isAssociationInitiator());
        LdapUtils.storeBoolean(attrs, "dicomAssociationAcceptor",  ae.isAssociationAcceptor());
        LdapUtils.storeConnRefs(attrs, ae.getConnections(), deviceDN);
        LdapUtils.storeNotEmpty(attrs, "dicomSupportedCharacterSet", ae.getSupportedCharacterSets());
        LdapUtils.storeNotNull(attrs, "dicomInstalled", ae.getInstalled());
        if (!extended)
            return attrs;

        objectclass.add("dcmNetworkAE");
        LdapUtils.storeNotEmpty(attrs, "dcmAcceptedCallingAETitle", ae.getAcceptedCallingAETitles());
        for (LdapDicomConfigurationExtension ext : extensions)
            ext.storeTo(ae, attrs);
        return attrs;
    }

    private Attributes storeTo(TransferCapability tc, Attributes attrs) {
        BasicAttribute objectclass = new BasicAttribute("objectclass", "dicomTransferCapability");
        attrs.put(objectclass);
        LdapUtils.storeNotNull(attrs, "cn", tc.getCommonName());
        LdapUtils.storeNotNull(attrs, "dicomSOPClass", tc.getSopClass());
        LdapUtils.storeNotNull(attrs, "dicomTransferRole", tc.getRole());
        LdapUtils.storeNotEmpty(attrs, "dicomTransferSyntax", tc.getTransferSyntaxes());
        if (!extended)
            return attrs;

        objectclass.add("dcmTransferCapability");
        EnumSet<QueryOption> queryOpts = tc.getQueryOptions();
        if (queryOpts != null) {
            LdapUtils.storeNotDef(attrs, "dcmRelationalQueries",
                    queryOpts.contains(QueryOption.RELATIONAL), false);
            LdapUtils.storeNotDef(attrs, "dcmCombinedDateTimeMatching",
                    queryOpts.contains(QueryOption.DATETIME), false);
            LdapUtils.storeNotDef(attrs, "dcmFuzzySemanticMatching",
                    queryOpts.contains(QueryOption.FUZZY), false);
            LdapUtils.storeNotDef(attrs, "dcmTimezoneQueryAdjustment",
                    queryOpts.contains(QueryOption.TIMEZONE), false);
        }
        StorageOptions storageOpts = tc.getStorageOptions();
        if (storageOpts != null) {
            LdapUtils.storeInt(attrs, "dcmStorageConformance",
                    storageOpts.getLevelOfSupport().ordinal());
            LdapUtils.storeInt(attrs, "dcmDigitalSignatureSupport",
                    storageOpts.getDigitalSignatureSupport().ordinal());
            LdapUtils.storeInt(attrs, "dcmDataElementCoercion",
                    storageOpts.getElementCoercion().ordinal());
        }
        return attrs;
    }

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

    private void storeCertificates(String dn, X509Certificate... certs)
            throws CertificateEncodingException, NamingException {
        byte[][] vals = new byte[certs.length][];
        for (int i = 0; i < vals.length; i++)
            vals[i] = certs[i].getEncoded();
        Attributes attrs = ctx.getAttributes(dn,
                new String[] { "objectClass" } );
        ModificationItem replaceCert = new ModificationItem(
                DirContext.REPLACE_ATTRIBUTE, attr(userCertificate, vals ));
        ctx.modifyAttributes(dn,
                LdapUtils.hasObjectClass(attrs, pkiUser)
                     ? new ModificationItem[] { replaceCert }
                     : new ModificationItem[] {
                             new ModificationItem(
                                 DirContext.ADD_ATTRIBUTE,
                                 LdapUtils.attr("objectClass", pkiUser )),
                             replaceCert });
    }

    @Override
    public synchronized void removeCertificates(String dn) throws ConfigurationException {
        try {
            ModificationItem removeCert = new ModificationItem(
                    DirContext.REMOVE_ATTRIBUTE, new BasicAttribute(userCertificate));
            ctx.modifyAttributes(dn, new ModificationItem[] { removeCert });
        } catch (NameNotFoundException e) {
            throw new ConfigurationNotFoundException(e);
        } catch (NamingException e) {
            throw new ConfigurationException(e);
        }
    }

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

    private X509Certificate[] loadCertificates(String dn)
            throws NamingException, CertificateException {
        Attributes attrs = ctx.getAttributes(dn, new String[] { userCertificate } );
        Attribute attr = attrs.get(userCertificate);
        if (attr == null)
            return EMPTY_X509_CERTIFICATES;
        CertificateFactory cf = CertificateFactory.getInstance("X509");
        X509Certificate[] certs = new X509Certificate[attr.size()];
        for (int i = 0; i < certs.length; i++)
            certs[i] = (X509Certificate) cf.generateCertificate(
                    new ByteArrayInputStream((byte[]) attr.get(i)));

        return certs;
    }

    public Device loadDevice(String deviceDN) 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(deviceDN))
            return deviceCache.get(deviceDN);
               
       
        try {
            Attributes attrs = getAttributes(deviceDN);
            Device device = new Device(LdapUtils.stringValue(attrs.get("dicomDeviceName"), null));

            // remember this device so it won't be loaded again in this run
            deviceCache.put(deviceDN, device);
                       
            loadFrom(device, attrs);
            loadChilds(device, deviceDN);
            return device;
        } catch (NameNotFoundException e) {
            throw new ConfigurationNotFoundException("Device with specified name not found",e);
        } catch (NamingException 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();
        }
       
    }

    public Attributes getAttributes(String name) throws NamingException {
        return ctx.getAttributes(name);
    }

    private void loadChilds(Device device, String deviceDN)
            throws NamingException, ConfigurationException {
        loadConnections(device, deviceDN);
        loadApplicationEntities(device, deviceDN);
        for (LdapDicomConfigurationExtension ext : extensions)
            ext.loadChilds(device, deviceDN);
    }

    private void loadFrom(TransferCapability tc, Attributes attrs) throws NamingException {
        tc.setCommonName(LdapUtils.stringValue(attrs.get("cn"), null));
        tc.setSopClass(LdapUtils.stringValue(attrs.get("dicomSOPClass"), null));
        tc.setRole(TransferCapability.Role.valueOf(
                LdapUtils.stringValue(attrs.get("dicomTransferRole"), null)));
        tc.setTransferSyntaxes(LdapUtils.stringArray(attrs.get("dicomTransferSyntax")));
        if (!LdapUtils.hasObjectClass(attrs, "dcmTransferCapability"))
            return;

        tc.setQueryOptions(toQueryOptions(attrs));
        tc.setStorageOptions(toStorageOptions(attrs));
    }

    private static EnumSet<QueryOption> toQueryOptions(Attributes attrs)
            throws NamingException {
        Attribute relational = attrs.get("dcmRelationalQueries");
        Attribute datetime = attrs.get("dcmCombinedDateTimeMatching");
        Attribute fuzzy = attrs.get("dcmFuzzySemanticMatching");
        Attribute timezone = attrs.get("dcmTimezoneQueryAdjustment");
        if (relational == null && datetime == null && fuzzy == null && timezone == null)
            return null;
        EnumSet<QueryOption> opts = EnumSet.noneOf(QueryOption.class);
        if (LdapUtils.booleanValue(relational, false))
            opts.add(QueryOption.RELATIONAL);
        if (LdapUtils.booleanValue(datetime, false))
            opts.add(QueryOption.DATETIME);
        if (LdapUtils.booleanValue(fuzzy, false))
            opts.add(QueryOption.FUZZY);
        if (LdapUtils.booleanValue(timezone, false))
            opts.add(QueryOption.TIMEZONE);
        return opts ;
     }

    private static StorageOptions toStorageOptions(Attributes attrs) throws NamingException {
        Attribute levelOfSupport = attrs.get("dcmStorageConformance");
        Attribute signatureSupport = attrs.get("dcmDigitalSignatureSupport");
        Attribute coercion = attrs.get("dcmDataElementCoercion");
        if (levelOfSupport == null && signatureSupport == null && coercion == null)
            return null;
        StorageOptions opts = new StorageOptions();
        opts.setLevelOfSupport(
                StorageOptions.LevelOfSupport.valueOf(LdapUtils.intValue(levelOfSupport, 3)));
        opts.setDigitalSignatureSupport(
                StorageOptions.DigitalSignatureSupport.valueOf(LdapUtils.intValue(signatureSupport, 0)));
        opts.setElementCoercion(
                StorageOptions.ElementCoercion.valueOf(LdapUtils.intValue(coercion, 2)));
        return opts;
    }

    private void loadFrom(Device device, Attributes attrs)
            throws NamingException, CertificateException {
        device.setDescription(LdapUtils.stringValue(attrs.get("dicomDescription"), null));
        device.setManufacturer(LdapUtils.stringValue(attrs.get("dicomManufacturer"), null));
        device.setManufacturerModelName(LdapUtils.stringValue(attrs.get("dicomManufacturerModelName"), null));
        device.setSoftwareVersions(LdapUtils.stringArray(attrs.get("dicomSoftwareVersion")));
        device.setStationName(LdapUtils.stringValue(attrs.get("dicomStationName"), null));
        device.setDeviceSerialNumber(LdapUtils.stringValue(attrs.get("dicomDeviceSerialNumber"), null));
        device.setIssuerOfPatientID(
                issuerValue(attrs.get("dicomIssuerOfPatientID")));
        device.setIssuerOfAccessionNumber(
                issuerValue(attrs.get("dicomIssuerOfAccessionNumber")));
        device.setOrderPlacerIdentifier(
                issuerValue(attrs.get("dicomOrderPlacerIdentifier")));
        device.setOrderFillerIdentifier(
                issuerValue(attrs.get("dicomOrderFillerIdentifier")));
        device.setIssuerOfAdmissionID(
                issuerValue(attrs.get("dicomIssuerOfAdmissionID")));
        device.setIssuerOfServiceEpisodeID(
                issuerValue(attrs.get("dicomIssuerOfServiceEpisodeID")));
        device.setIssuerOfContainerIdentifier(
                issuerValue(attrs.get("dicomIssuerOfContainerIdentifier")));
        device.setIssuerOfSpecimenIdentifier(
                issuerValue(attrs.get("dicomIssuerOfSpecimenIdentifier")));
        device.setInstitutionNames(LdapUtils.stringArray(attrs.get("dicomInstitutionName")));
        device.setInstitutionCodes(LdapUtils.codeArray(attrs.get("dicomInstitutionCode")));
        device.setInstitutionAddresses(LdapUtils.stringArray(attrs.get("dicomInstitutionAddress")));
        device.setInstitutionalDepartmentNames(
                LdapUtils.stringArray(attrs.get("dicomInstitutionDepartmentName")));
        device.setPrimaryDeviceTypes(LdapUtils.stringArray(attrs.get("dicomPrimaryDeviceType")));
        device.setRelatedDeviceRefs(LdapUtils.stringArray(attrs.get("dicomRelatedDeviceReference")));
        for (String dn : LdapUtils.stringArray(attrs.get("dicomAuthorizedNodeCertificateReference")))
            device.setAuthorizedNodeCertificates(dn, loadCertificates(dn));
        for (String dn : LdapUtils.stringArray(attrs.get("dicomThisNodeCertificateReference")))
            device.setThisNodeCertificates(dn, loadCertificates(dn));
        device.setVendorData(byteArrays(attrs.get("dicomVendorData")));
        device.setInstalled(LdapUtils.booleanValue(attrs.get("dicomInstalled"), true));
        if (!LdapUtils.hasObjectClass(attrs, "dcmDevice"))
            return;
       
        device.setLimitOpenAssociations(
                LdapUtils.intValue(attrs.get("dcmLimitOpenAssociations"), 0));
        device.setTrustStoreURL(LdapUtils.stringValue(attrs.get("dcmTrustStoreURL"), null));
        device.setTrustStoreType(LdapUtils.stringValue(attrs.get("dcmTrustStoreType"), null));
        device.setTrustStorePin(LdapUtils.stringValue(attrs.get("dcmTrustStorePin"), null));
        device.setTrustStorePinProperty(
                LdapUtils.stringValue(attrs.get("dcmTrustStorePinProperty"), null));
        device.setKeyStoreURL(LdapUtils.stringValue(attrs.get("dcmKeyStoreURL"), null));
        device.setKeyStoreType(LdapUtils.stringValue(attrs.get("dcmKeyStoreType"), null));
        device.setKeyStorePin(LdapUtils.stringValue(attrs.get("dcmKeyStorePin"), null));
        device.setKeyStorePinProperty(
                LdapUtils.stringValue(attrs.get("dcmKeyStorePinProperty"), null));
        device.setKeyStoreKeyPin(LdapUtils.stringValue(attrs.get("dcmKeyStoreKeyPin"), null));
        device.setKeyStoreKeyPinProperty(
                LdapUtils.stringValue(attrs.get("dcmKeyStoreKeyPinProperty"), null));
        device.setTimeZoneOfDevice(timeZoneOf(
          LdapUtils.stringValue(attrs.get("dcmTimeZoneOfDevice"), null)));
        for (LdapDicomConfigurationExtension ext : extensions)
            ext.loadFrom(device, attrs);
    }

    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();
    }
    private void loadConnections(Device device, String deviceDN) throws NamingException {
        NamingEnumeration<SearchResult> ne =
                search(deviceDN, "(objectclass=dicomNetworkConnection)");
        try {
            while (ne.hasMore()) {
                SearchResult sr = ne.next();
                Attributes attrs = sr.getAttributes();
                Connection conn = new Connection();
                loadFrom(conn, attrs);
                device.addConnection(conn);
            }
        } finally {
           LdapUtils.safeClose(ne);
        }
    }

    public NamingEnumeration<SearchResult> search(String dn, String filter)
            throws NamingException {
        return  search(dn, filter, (String[]) null);
    }

    private NamingEnumeration<SearchResult> search(String dn, String filter,
            String... attrs) throws NamingException {
        SearchControls ctls = new SearchControls();
        ctls.setSearchScope(SearchControls.ONELEVEL_SCOPE);
        ctls.setReturningObjFlag(false);
        ctls.setReturningAttributes(attrs);
        return ctx.search(dn, filter, ctls);
    }

    private void loadFrom(Connection conn, Attributes attrs) throws NamingException {
        conn.setCommonName(LdapUtils.stringValue(attrs.get("cn"), null));
        conn.setHostname(LdapUtils.stringValue(attrs.get("dicomHostname"), null));
        conn.setPort(LdapUtils.intValue(attrs.get("dicomPort"), Connection.NOT_LISTENING));
        conn.setTlsCipherSuites(LdapUtils.stringArray(attrs.get("dicomTLSCipherSuite")));
        conn.setInstalled(LdapUtils.booleanValue(attrs.get("dicomInstalled"), null));
        if (!LdapUtils.hasObjectClass(attrs, "dcmNetworkConnection"))
            return;

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

    private void loadApplicationEntities(Device device, String deviceDN)
            throws NamingException {
        NamingEnumeration<SearchResult> ne =
                search(deviceDN, "(objectclass=dicomNetworkAE)");
        try {
            while (ne.hasMore()) {
                device.addApplicationEntity(
                        loadApplicationEntity(ne.next(), deviceDN, device));
            }
        } finally {
           LdapUtils.safeClose(ne);
        }
    }

    private ApplicationEntity loadApplicationEntity(SearchResult sr,
            String deviceDN, Device device) throws NamingException {
        Attributes attrs = sr.getAttributes();
        ApplicationEntity ae = new ApplicationEntity(LdapUtils.stringValue(attrs.get("dicomAETitle"), null));
        loadFrom(ae, attrs);
        for (String connDN : LdapUtils.stringArray(attrs.get("dicomNetworkConnectionReference")))
            ae.addConnection(LdapUtils.findConnection(connDN, deviceDN, device));
        loadChilds(ae, sr.getNameInNamespace());
        return ae ;
    }

    private void loadFrom(ApplicationEntity ae, Attributes attrs) throws NamingException {
        ae.setDescription(LdapUtils.stringValue(attrs.get("dicomDescription"), null));
        ae.setVendorData(byteArrays(attrs.get("dicomVendorData")));
        ae.setApplicationClusters(LdapUtils.stringArray(attrs.get("dicomApplicationCluster")));
        ae.setPreferredCallingAETitles(LdapUtils.stringArray(attrs.get("dicomPreferredCallingAETitle")));
        ae.setPreferredCalledAETitles(LdapUtils.stringArray(attrs.get("dicomPreferredCalledAETitle")));
        ae.setAssociationInitiator(LdapUtils.booleanValue(attrs.get("dicomAssociationInitiator"), false));
        ae.setAssociationAcceptor(LdapUtils.booleanValue(attrs.get("dicomAssociationAcceptor"), false));
        ae.setSupportedCharacterSets(LdapUtils.stringArray(attrs.get("dicomSupportedCharacterSet")));
        ae.setInstalled(LdapUtils.booleanValue(attrs.get("dicomInstalled"), null));
        if (!LdapUtils.hasObjectClass(attrs, "dcmNetworkAE"))
            return;

        ae.setAcceptedCallingAETitles(LdapUtils.stringArray(attrs.get("dcmAcceptedCallingAETitle")));
        for (LdapDicomConfigurationExtension ext : extensions)
            ext.loadFrom(ae, attrs);
    }

    private void loadChilds(ApplicationEntity ae, String aeDN) throws NamingException {
        loadTransferCapabilities(ae, aeDN);
        for (LdapDicomConfigurationExtension ext : extensions)
            ext.loadChilds(ae, aeDN);
    }

    private void loadTransferCapabilities(ApplicationEntity ae, String aeDN)
            throws NamingException {
        NamingEnumeration<SearchResult> ne =
                search(aeDN, "(objectclass=dicomTransferCapability)");
        try {
            while (ne.hasMore())
                ae.addTransferCapability(loadTransferCapability(ne.next()));
        } finally {
           LdapUtils.safeClose(ne);
        }
    }

    private TransferCapability loadTransferCapability(SearchResult sr)
            throws NamingException {
        Attributes attrs = sr.getAttributes();
        TransferCapability tc = new TransferCapability();
        loadFrom(tc, attrs);
        return tc;
    }

    private List<ModificationItem> storeDiffs(Device a, Device b,
            List<ModificationItem> mods) {
        LdapUtils.storeDiff(mods, "dicomDescription",
                a.getDescription(),
                b.getDescription());
        LdapUtils.storeDiff(mods, "dicomManufacturer",
                a.getManufacturer(),
                b.getManufacturer());
        LdapUtils.storeDiff(mods, "dicomManufacturerModelName",
                a.getManufacturerModelName(),
                b.getManufacturerModelName());
        LdapUtils.storeDiff(mods, "dicomSoftwareVersion",
                a.getSoftwareVersions(),
                b.getSoftwareVersions());
        LdapUtils.storeDiff(mods, "dicomStationName",
                a.getStationName(),
                b.getStationName());
        LdapUtils.storeDiff(mods, "dicomDeviceSerialNumber",
                a.getDeviceSerialNumber(),
                b.getDeviceSerialNumber());
        LdapUtils.storeDiff(mods, "dicomIssuerOfPatientID",
                a.getIssuerOfPatientID(),
                b.getIssuerOfPatientID());
        LdapUtils.storeDiff(mods, "dicomIssuerOfAccessionNumber",
                a.getIssuerOfAccessionNumber(),
                b.getIssuerOfAccessionNumber());
        LdapUtils.storeDiff(mods, "dicomOrderPlacerIdentifier",
                a.getOrderPlacerIdentifier(),
                b.getOrderPlacerIdentifier());
        LdapUtils.storeDiff(mods, "dicomOrderFillerIdentifier",
                a.getOrderFillerIdentifier(),
                b.getOrderFillerIdentifier());
        LdapUtils.storeDiff(mods, "dicomIssuerOfAdmissionID",
                a.getIssuerOfAdmissionID(),
                b.getIssuerOfAdmissionID());
        LdapUtils.storeDiff(mods, "dicomIssuerOfServiceEpisodeID",
                a.getIssuerOfServiceEpisodeID(),
                b.getIssuerOfServiceEpisodeID());
        LdapUtils.storeDiff(mods, "dicomIssuerOfContainerIdentifier",
                a.getIssuerOfContainerIdentifier(),
                b.getIssuerOfContainerIdentifier());
        LdapUtils.storeDiff(mods, "dicomIssuerOfSpecimenIdentifier",
                a.getIssuerOfSpecimenIdentifier(),
                b.getIssuerOfSpecimenIdentifier());
        LdapUtils.storeDiff(mods, "dicomInstitutionName",
                a.getInstitutionNames(),
                b.getInstitutionNames());
        LdapUtils.storeDiff(mods, "dicomInstitutionCode",
                a.getInstitutionCodes(),
                b.getInstitutionCodes());
        LdapUtils.storeDiff(mods, "dicomInstitutionAddress",
                a.getInstitutionAddresses(),
                b.getInstitutionAddresses());
        LdapUtils.storeDiff(mods, "dicomInstitutionDepartmentName",
                a.getInstitutionalDepartmentNames(),
                b.getInstitutionalDepartmentNames());
        LdapUtils.storeDiff(mods, "dicomPrimaryDeviceType",
                a.getPrimaryDeviceTypes(),
                b.getPrimaryDeviceTypes());
        LdapUtils.storeDiff(mods, "dicomRelatedDeviceReference",
                a.getRelatedDeviceRefs(),
                b.getRelatedDeviceRefs());
        LdapUtils.storeDiff(mods, "dicomAuthorizedNodeCertificateReference",
                a.getAuthorizedNodeCertificateRefs(),
                b.getAuthorizedNodeCertificateRefs());
        LdapUtils.storeDiff(mods, "dicomThisNodeCertificateReference",
                a.getThisNodeCertificateRefs(),
                b.getThisNodeCertificateRefs());
        storeDiff(mods, "dicomVendorData",
                a.getVendorData(),
                b.getVendorData());
        LdapUtils.storeDiff(mods, "dicomInstalled",
                a.isInstalled(),
                b.isInstalled());
        if (!extended)
            return mods;

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

    private List<ModificationItem> storeDiffs(Connection a, Connection b,
            List<ModificationItem> mods) {
        LdapUtils.storeDiff(mods, "dicomHostname",
                a.getHostname(),
                b.getHostname());
        LdapUtils.storeDiff(mods, "dicomPort",
                a.getPort(),
                b.getPort(),
                Connection.NOT_LISTENING);
        LdapUtils.storeDiff(mods, "dicomTLSCipherSuite",
                a.getTlsCipherSuites(),
                b.getTlsCipherSuites());
        LdapUtils.storeDiff(mods, "dicomInstalled",
                a.getInstalled(),
                b.getInstalled());
        if (!extended)
            return mods;

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

    private List<ModificationItem> storeDiffs(ApplicationEntity a,
            ApplicationEntity b, String deviceDN, List<ModificationItem> mods) {
        LdapUtils.storeDiff(mods, "dicomDescription",
                a.getDescription(),
                b.getDescription());
        storeDiff(mods, "dicomVendorData",
                a.getVendorData(),
                b.getVendorData());
        LdapUtils.storeDiff(mods, "dicomApplicationCluster",
                a.getApplicationClusters(),
                b.getApplicationClusters());
        LdapUtils.storeDiff(mods, "dicomPreferredCallingAETitle",
                a.getPreferredCallingAETitles(),
                b.getPreferredCallingAETitles());
        LdapUtils.storeDiff(mods, "dicomPreferredCalledAETitle",
                a.getPreferredCalledAETitles(),
                b.getPreferredCalledAETitles());
        LdapUtils.storeDiff(mods, "dicomAssociationInitiator",
                a.isAssociationInitiator(),
                b.isAssociationInitiator());
        LdapUtils.storeDiff(mods, "dicomAssociationAcceptor",
                a.isAssociationAcceptor(),
                b.isAssociationAcceptor());
        LdapUtils.storeDiff(mods, "dicomNetworkConnectionReference",
                a.getConnections(),
                b.getConnections(),
                deviceDN);
        LdapUtils.storeDiff(mods, "dicomSupportedCharacterSet",
                a.getSupportedCharacterSets(),
                b.getSupportedCharacterSets());
        LdapUtils.storeDiff(mods, "dicomInstalled",
                a.getInstalled(),
                b.getInstalled());
        if (!extended)
            return mods;

        LdapUtils.storeDiff(mods, "dcmAcceptedCallingAETitle",
                a.getAcceptedCallingAETitles(),
                b.getAcceptedCallingAETitles());
        for (LdapDicomConfigurationExtension ext : extensions)
            ext.storeDiffs(a, b, mods);
        return mods;
    }

    private List<ModificationItem> storeDiffs(TransferCapability a,
            TransferCapability b, List<ModificationItem> mods) {
        LdapUtils.storeDiff(mods, "dicomSOPClass",
                a.getSopClass(),
                b.getSopClass());
        LdapUtils.storeDiff(mods, "dicomTransferRole",
                a.getRole(),
                b.getRole());
        LdapUtils.storeDiff(mods, "dicomTransferSyntax",
                a.getTransferSyntaxes(),
                b.getTransferSyntaxes());
        if (!extended)
            return mods;

        storeDiffs(a.getQueryOptions(), b.getQueryOptions(), mods);
        storeDiffs(a.getStorageOptions(), b.getStorageOptions(), mods);
        return mods;
    }

    private void storeDiffs(EnumSet<QueryOption> prev,
            EnumSet<QueryOption> val, List<ModificationItem> mods) {
        if (prev != null ? prev.equals(val) : val == null)
            return;

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

    private void storeDiffs(StorageOptions prev,
            StorageOptions val, List<ModificationItem> mods) {
        if (prev != null ? prev.equals(val) : val == null)
            return;

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

    private static byte[][] byteArrays(Attribute attr) throws NamingException {
        if (attr == null)
            return new byte[0][];

        byte[][] bb = new byte[attr.size()][];
        for (int i = 0; i < bb.length; i++)
            bb[i] = (byte[]) attr.get(i);

        return bb;
    }

    private static Issuer issuerValue(Attribute attr) throws NamingException {
        return attr != null ? new Issuer((String) attr.get()) : null;
    }

    private void mergeAEs(Device prevDev, Device dev, String deviceDN)
            throws NamingException {
        Collection<String> aets = dev.getApplicationAETitles();
        for (String aet : prevDev.getApplicationAETitles()) {
            if (!aets.contains(aet))
                destroySubcontextWithChilds(aetDN(aet, deviceDN));
        }
        Collection<String> prevAETs = prevDev.getApplicationAETitles();
        for (ApplicationEntity ae : dev.getApplicationEntities()) {
            String aet = ae.getAETitle();
            if (!prevAETs.contains(aet)) {
                String aeDN = aetDN(ae.getAETitle(), deviceDN);
                createSubcontext(aeDN,
                        storeTo(ae, deviceDN, new BasicAttributes(true)));
                storeChilds(aeDN, ae);
            } else
                merge(prevDev.getApplicationEntity(aet), ae, deviceDN);
        }
    }

    private void merge(ApplicationEntity prev, ApplicationEntity ae,
            String deviceDN) throws NamingException {
        String aeDN = aetDN(ae.getAETitle(), deviceDN);
        modifyAttributes(aeDN, storeDiffs(prev, ae, deviceDN, new ArrayList<ModificationItem>()));
        mergeChilds(prev, ae, aeDN);
    }

    private void mergeChilds(ApplicationEntity prev, ApplicationEntity ae,
            String aeDN) throws NamingException {
        merge(prev.getTransferCapabilities(), ae.getTransferCapabilities(), aeDN);
        for (LdapDicomConfigurationExtension ext : extensions)
            ext.mergeChilds(prev, ae, aeDN);
    }

    public void modifyAttributes(String dn, List<ModificationItem> mods)
            throws NamingException {
        if (!mods.isEmpty())
            ctx.modifyAttributes(dn, mods.toArray(new ModificationItem[mods.size()]));
    }

    public void replaceAttributes(String dn, Attributes attrs)
            throws NamingException {
        ctx.modifyAttributes(dn, DirContext.REPLACE_ATTRIBUTE, attrs);
    }

   
    private void merge(Collection<TransferCapability> prevs,
            Collection<TransferCapability> tcs, String aeDN) throws NamingException {
        for (TransferCapability tc : prevs) {
            String dn = dnOf(tc, aeDN);
            if (findByDN(aeDN, tcs, dn) == null)
                destroySubcontext(dn);
        }
        for (TransferCapability tc : tcs) {
            String dn = dnOf(tc, aeDN);
            TransferCapability prev = findByDN(aeDN, prevs, dn);
            if (prev == null)
                createSubcontext(dn, storeTo(tc, new BasicAttributes(true)));
            else
                modifyAttributes(dn, storeDiffs(prev, tc, new ArrayList<ModificationItem>()));
        }
    }

    private void mergeConnections(Device prevDev, Device device, String deviceDN)
            throws NamingException {
        List<Connection> prevs = prevDev.listConnections();
        List<Connection> conns = device.listConnections();
        for (Connection prev : prevs) {
            String dn = LdapUtils.dnOf(prev, deviceDN);
            if (LdapUtils.findByDN(deviceDN, conns, dn) == null)
                destroySubcontext(dn);
        }
        for (Connection conn : conns) {
            String dn = LdapUtils.dnOf(conn, deviceDN);
            Connection prev = LdapUtils.findByDN(deviceDN, prevs, dn);
            if (prev == null)
                createSubcontext(dn, storeTo(conn, new BasicAttributes(true)));
            else
                modifyAttributes(dn, storeDiffs(prev, conn, new ArrayList<ModificationItem>()));
        }
    }

    private static TransferCapability findByDN(String aeDN,
            Collection<TransferCapability> tcs, String dn) {
        for (TransferCapability tc : tcs)
            if (dn.equals(dnOf(tc, aeDN)))
                return tc;
        return null;
    }

    private static void storeDiff(List<ModificationItem> mods, String attrId,
            byte[][] prevs, byte[][] vals) {
        if (!equals(prevs, vals))
            mods.add((vals.length == 0)
                    ? new ModificationItem(DirContext.REMOVE_ATTRIBUTE,
                            new BasicAttribute(attrId))
                    : new ModificationItem(DirContext.REPLACE_ATTRIBUTE,
                            attr(attrId, vals)));
    }

    private static boolean equals(byte[][] a, byte[][] a2) {
        int length = a.length;
        if (a2.length != length)
            return false;

        outer:
        for (byte[] o1 : a) {
            for (byte[] o2 : a2)
                if (Arrays.equals(o1, o2))
                    continue outer;
            return false;
        }
        return true;
    }

    private static String aetDN(String aet, String parentDN) {
        return LdapUtils.dnOf("dicomAETitle" ,aet, parentDN);
    }

    @Override
    public String deviceRef(String name) {
        return LdapUtils.dnOf("dicomDeviceName" ,name, devicesDN);
    }

    private static String dnOf(TransferCapability tc, String aeDN) {
        String cn = tc.getCommonName();
        return (cn != null)
            ? LdapUtils.dnOf("cn", cn , aeDN)
            : LdapUtils.dnOf("dicomSOPClass", tc.getSopClass(),
                   "dicomTransferRole", tc.getRole().toString(), aeDN);
    }

    private static void storeNotEmpty(Attributes attrs, String attrID, byte[]... vals) {
        if (vals != null && vals.length > 0)
            attrs.put(attr(attrID, vals));
    }

    private static <T> Attribute attr(String attrID, byte[]... vals) {
        Attribute attr = new BasicAttribute(attrID);
        for (byte[] val : vals)
            attr.add(val);
        return attr;
    }

    private static void safeClose(Context ctx) {
        if (ctx != null)
            try {
                ctx.close();
            } catch (NamingException e) {
            }
    }

    public void store(AttributeCoercions coercions, String parentDN)
            throws NamingException {
            for (AttributeCoercion ac : coercions)
                createSubcontext(
                        LdapUtils.dnOf("cn", ac.getCommonName(), parentDN),
                        storeTo(ac, new BasicAttributes(true)));
        }

    private static Attributes storeTo(AttributeCoercion ac, BasicAttributes attrs) {
        attrs.put("objectclass", "dcmAttributeCoercion");
        attrs.put("cn", ac.getCommonName());
        LdapUtils.storeNotNull(attrs, "dcmDIMSE", ac.getDIMSE());
        LdapUtils.storeNotNull(attrs, "dicomTransferRole", ac.getRole());
        LdapUtils.storeNotEmpty(attrs, "dcmAETitle", ac.getAETitles());
        LdapUtils.storeNotEmpty(attrs, "dcmSOPClass", ac.getSOPClasses());
        LdapUtils.storeNotNull(attrs, "labeledURI", ac.getURI());
        return attrs;
    }

    public void load(AttributeCoercions acs, String dn) throws NamingException {
        NamingEnumeration<SearchResult> ne =
                search(dn, "(objectclass=dcmAttributeCoercion)");
        try {
            while (ne.hasMore()) {
                SearchResult sr = ne.next();
                Attributes attrs = sr.getAttributes();
                acs.add(new AttributeCoercion(
                        LdapUtils.stringValue(attrs.get("cn"), null),
                        LdapUtils.stringArray(attrs.get("dcmSOPClass")),
                        Dimse.valueOf(LdapUtils.stringValue(attrs.get("dcmDIMSE"), null)),
                        TransferCapability.Role.valueOf(
                                LdapUtils.stringValue(attrs.get("dicomTransferRole"), null)),
                        LdapUtils.stringArray(attrs.get("dcmAETitle")),
                        LdapUtils.stringValue(attrs.get("labeledURI"), null)));
            }
        } finally {
           LdapUtils.safeClose(ne);
        }
    }

    public void merge(AttributeCoercions prevs, AttributeCoercions acs,
            String parentDN) throws NamingException {
        for (AttributeCoercion prev : prevs) {
            String cn = prev.getCommonName();
            if (acs.findByCommonName(cn) == null)
                destroySubcontext(LdapUtils.dnOf("cn", cn, parentDN));
        }
        for (AttributeCoercion ac : acs) {
            String cn = ac.getCommonName();
            String dn = LdapUtils.dnOf("cn", cn, parentDN);
            AttributeCoercion prev = prevs.findByCommonName(cn);
            if (prev == null)
                createSubcontext(dn, storeTo(ac, new BasicAttributes(true)));
            else
                modifyAttributes(dn, storeDiffs(prev, ac,
                        new ArrayList<ModificationItem>()));
        }
    }

    private List<ModificationItem> storeDiffs(AttributeCoercion prev,
            AttributeCoercion ac, ArrayList<ModificationItem> mods) {
        LdapUtils.storeDiff(mods, "dcmDIMSE", prev.getDIMSE(), ac.getDIMSE());
        LdapUtils.storeDiff(mods, "dicomTransferRole",
                prev.getRole(),
                ac.getRole());
        LdapUtils.storeDiff(mods, "dcmAETitle",
                prev.getAETitles(),
                ac.getAETitles());
        LdapUtils.storeDiff(mods, "dcmSOPClass",
                prev.getSOPClasses(),
                ac.getSOPClasses());
        LdapUtils.storeDiff(mods, "labeledURI", prev.getURI(), ac.getURI());
        return mods;
    }

    @Override
    public void sync() throws ConfigurationException {
        // NOOP
    }
   

}
TOP

Related Classes of org.dcm4che3.conf.ldap.LdapDicomConfiguration

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.