/* ***** 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) 2012-2014 * 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.dcm4chee.storage.conf; import java.io.Serializable; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Map; import java.util.TreeMap; import javax.enterprise.inject.Instance; import org.dcm4che3.conf.core.api.ConfigurableClass; import org.dcm4che3.conf.core.api.ConfigurableProperty; import org.dcm4che3.conf.core.api.LDAP; import org.dcm4che3.data.Attributes; import org.dcm4che3.data.DatasetWithFMI; import org.dcm4che3.data.Tag; import org.dcm4che3.data.UID; import org.dcm4che3.net.Device; import org.dcm4chee.storage.spi.ContainerProvider; import org.dcm4chee.storage.spi.FileCacheProvider; import static org.dcm4che3.conf.core.api.ConfigurableProperty.*; /** * Container for a group of similar StorageSystem, includes several group-level * attributes. In previous versions of dcm4che it may have been referred as * Storage Pool or File System Group. * * @author Gunter Zeilinger<gunterze@gmail.com> * */ @LDAP(objectClasses = "dcmStorageSystemGroup") @ConfigurableClass public class StorageSystemGroup implements Serializable{ private static final long serialVersionUID = -8283568746257849173L; /** * ImplementationClassUID used until dcm4che 1.4.29 that produced faulty JPEG-LS images, because of a bug in * JAI-ImageIO (see [DCMEEREQ-799]). */ private static final String DCM4CHE1_UNPATCHED_JPEGLS_ImplementationClassUID = "1.2.40.0.13.1.1"; @ConfigurableProperty(name = "dcmStorageSystemGroupID", description = "Immutable identifier, should not be changed") private String groupID; @ConfigurableProperty(type = ConfigurablePropertyType.OptimisticLockingHash) private String olockHash; @ConfigurableProperty(name = "dcmStorageSystemGroupName", description = "Human-readable identifier, can be changed safely") private String storageSystemGroupName; @ConfigurableProperty(name= "dcmStorageSystemGroupType") private String storageSystemGroupType; @LDAP(distinguishingField = "dcmStorageSystemID", noContainerNode = true) @ConfigurableProperty(name = "Storage Systems") private Map<String, StorageSystem> storageSystems; @ConfigurableProperty(name = "dcmActiveStorageSystemID") private String[] activeStorageSystemIDs = {}; @ConfigurableProperty(name = "dcmNextStorageSystemID") private String nextStorageSystemID; @ConfigurableProperty(name = "dcmStorageParallelism", defaultValue = "1") private int parallelism = 1; @ConfigurableProperty(name = "dcmStorageFilePathFormat") private String storageFilePathFormat; @ConfigurableProperty(name = "dicomInstalled") private Boolean installed; @ConfigurableProperty(name = "Storage Container") private Container container; @ConfigurableProperty(name = "Storage File Cache") private FileCache fileCache; @ConfigurableProperty(name = "dcmDigestAlgorithm", defaultValue = "MD5") private String digestAlgorithm = "MD5"; @ConfigurableProperty(name = "dcmRetrieveAET") private String[] retrieveAETs = {}; @ConfigurableProperty(name = "dcmBaseStorageAccessTime", defaultValue = "0") private int baseStorageAccessTime; @ConfigurableProperty(name = "dcmSpoolStorageGroup") private String spoolStorageGroup; /** * use storageSystemGroupType instead */ @Deprecated @ConfigurableProperty(name = "dcmStorageSystemGroupLabel", description = "This field can be used to classify groups") private String storageSystemGroupLabel; @LDAP( distinguishingField = "dcmStorageAffinityGroupID", mapValueAttribute = "dcmStorageAccessTimeOffset", mapEntryObjectClass= "dcmStorageAccessTimeOffsetEntry" ) @ConfigurableProperty(name = "StorageAccessTimeOffsetMap") private final Map<String, String> storageAccessTimeOffsetMap = new TreeMap<String, String>( (String.CASE_INSENSITIVE_ORDER)); @ConfigurableProperty(name = "description") private String description; @ConfigurableProperty(name = "faultyJPEGLSImplementationClassUIDs", description = "Implementation Class UIDs used by implementations that suffered from the JAI-ImageIO " + "JPEG-LS compression bug. The archive will assure that instances stored with any of the UIDs in " + "this list will always only leave the system decompressed, so that incorrectly compressed " + "instances will not leave the system.") private String[] faultyJPEGLSImplementationClassUIDs = {DCM4CHE1_UNPATCHED_JPEGLS_ImplementationClassUID}; private StorageDeviceExtension storageDeviceExtension; private int activeStorageSystemIndex; public Boolean getInstalled() { return installed; } public void setInstalled(Boolean installed) { this.installed = installed; } public boolean installed() { Device device = storageDeviceExtension != null ? storageDeviceExtension.getDevice() : null; return device != null && device.isInstalled() && (installed == null || installed.booleanValue()); } public StorageDeviceExtension getStorageDeviceExtension() { return storageDeviceExtension; } public void setStorageDeviceExtension( StorageDeviceExtension storageDeviceExtension) { this.storageDeviceExtension = storageDeviceExtension; } public Map<String, StorageSystem> getStorageSystems() { return storageSystems; } public void setStorageSystems(Map<String, StorageSystem> storageSystems) { this.storageSystems = storageSystems; for (StorageSystem storageSystem : storageSystems.values()) { storageSystem.setStorageSystemGroup(this); } } public StorageSystem getStorageSystem(String storageSystemID) { if (storageSystems == null) return null; return storageSystems.get(storageSystemID); } public StorageSystem addStorageSystem(StorageSystem storageSystem) { if (storageSystems == null) storageSystems = new TreeMap<String,StorageSystem>(); storageSystem.setStorageSystemGroup(this); StorageSystem prev = storageSystems.put(storageSystem.getStorageSystemID(), storageSystem); if (prev != null) prev.setStorageSystemGroup(null); if (nextStorageSystemID == null) nextStorageSystemID = storageSystem.getStorageSystemID(); return prev; } public StorageSystem removeStorageSystem(String storageSystemID) { if (storageSystems == null) return null; StorageSystem system = storageSystems.remove(storageSystemID); if (system == null) return null; return system; } public Collection<String> getStorageSystemIDs() { if (storageSystems == null) return Collections.emptySet(); return storageSystems.keySet(); } public String getStorageFilePathFormat() { return storageFilePathFormat; } public void setStorageFilePathFormat(String storageFilePathFormat) { this.storageFilePathFormat = storageFilePathFormat; } public String getNextStorageSystemID() { return nextStorageSystemID; } public void setNextStorageSystemID(String nextStorageSystemID) { this.nextStorageSystemID = nextStorageSystemID; } public StorageSystem getNextStorageSystem() { if (nextStorageSystemID == null) return null; return getStorageSystem(nextStorageSystemID); } public String[] getActiveStorageSystemIDs() { return activeStorageSystemIDs; } public void setActiveStorageSystemIDs(String... activeStorageSystemIDs) { this.activeStorageSystemIDs = activeStorageSystemIDs; } public int getParallelism() { return parallelism; } public void setParallelism(int parallelism) { this.parallelism = parallelism; } public String getDigestAlgorithm() { return digestAlgorithm; } public void setDigestAlgorithm(String digestAlgorithm) { this.digestAlgorithm = digestAlgorithm; } public synchronized void activate(StorageSystem storageSystem, boolean setNextStorageSystemID) { if (!isActive(storageSystem)) { int length = activeStorageSystemIDs.length; activeStorageSystemIDs = Arrays.copyOf(activeStorageSystemIDs, length+1); activeStorageSystemIDs[length] = storageSystem.getStorageSystemID(); if (setNextStorageSystemID) setNextStorageSystemID(storageSystem.getNextStorageSystemID()); } } public synchronized void deactivate(StorageSystem storageSystem) { String systemID = storageSystem.getStorageSystemID(); for (int i = 0; i < activeStorageSystemIDs.length; i++) { if (activeStorageSystemIDs[i].equals(systemID)) { String[] dest = new String[activeStorageSystemIDs.length-1]; System.arraycopy(activeStorageSystemIDs, 0, dest, 0, i); System.arraycopy(activeStorageSystemIDs, i+1, dest, i, dest.length-i); activeStorageSystemIDs = dest; if (i < activeStorageSystemIndex) activeStorageSystemIndex--; return; } } } public synchronized StorageSystem nextActiveStorageSystem() { if (activeStorageSystemIDs.length == 0) return null; activeStorageSystemIndex %= activeStorageSystemIDs.length; String storageSystemID = activeStorageSystemIDs[activeStorageSystemIndex]; activeStorageSystemIndex++; return getStorageSystem(storageSystemID); } public boolean isActive(StorageSystem storageSystem) { return Arrays.asList(activeStorageSystemIDs).contains( storageSystem.getStorageSystemID()); } public String getGroupID() { return groupID; } public void setGroupID(String groupID) { this.groupID = groupID; } public Container getContainer() { return container; } public void setContainer(Container container) { this.container = container; } public FileCache getFileCache() { return fileCache; } public void setFileCache(FileCache fileCache) { this.fileCache = fileCache; } public ContainerProvider getContainerProvider( Instance<ContainerProvider> instances) { return container != null ? container.getContainerProvider(instances) : null; } public FileCacheProvider getFileCacheProvider( Instance<FileCacheProvider> instances) { return fileCache != null ? fileCache.getFileCacheProvider(instances) : null; } public String getStorageSystemGroupLabel() { return storageSystemGroupLabel; } public void setStorageSystemGroupLabel(String storageSystemGroupLabel) { this.storageSystemGroupLabel = storageSystemGroupLabel; } public String getStorageSystemGroupName() { return storageSystemGroupName; } public void setStorageSystemGroupName(String storageSystemGroupName) { this.storageSystemGroupName = storageSystemGroupName; } public String getStorageSystemGroupType() { return storageSystemGroupType; } public void setStorageSystemGroupType(String storageSystemGroupType) { this.storageSystemGroupType = storageSystemGroupType; } public String[] getRetrieveAETs() { return retrieveAETs; } public void setRetrieveAETs(String[] retrieveAETs) { this.retrieveAETs = retrieveAETs; } public int getBaseStorageAccessTime() { return baseStorageAccessTime; } public void setBaseStorageAccessTime(int baseStorageAccessTime) { this.baseStorageAccessTime = baseStorageAccessTime; } public Map<String, String> getStorageAccessTimeOffsetMap() { return storageAccessTimeOffsetMap; } public void setStorageAccessTimeOffsetMap( Map<String, String> storageAccessTimeOffsetMap) { this.storageAccessTimeOffsetMap.clear(); if (storageAccessTimeOffsetMap != null) this.storageAccessTimeOffsetMap.putAll(storageAccessTimeOffsetMap); } public int getStorageAccessTimeOffset() { String id = storageDeviceExtension.getAffinityGroupID(); if (id == null) return 0; String offset = storageAccessTimeOffsetMap.get(id); return offset != null ? Integer.parseInt(offset) : 0; } public int getStorageAccessTime() { return getBaseStorageAccessTime() + getStorageAccessTimeOffset(); } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public String getSpoolStorageGroup() { return spoolStorageGroup; } public void setSpoolStorageGroup(String spoolStorageGroup) { this.spoolStorageGroup = spoolStorageGroup; } public String[] getFaultyJPEGLSImplementationClassUIDs() { return faultyJPEGLSImplementationClassUIDs; } public void setFaultyJPEGLSImplementationClassUIDs(String[] faultyJPEGLSImplementationClassUIDs) { this.faultyJPEGLSImplementationClassUIDs = faultyJPEGLSImplementationClassUIDs; } /** * Check whether the instance might be compressed by an old version of dcm4chee that used unpatched JAI-ImageIO * JPEG-LS Lossless compression. * <p> * This ImplementationClassUID based check is a good indication on whether the instance is faulty JPEG-LS (created * by unpatched JAI-ImageIO), but it could also be standard-conform JPEG-LS. * The reason is that the ImplementationClassUID does not identify the DICOM implementation which compressed the * object, but the DICOM implementation of the Storage SCU from which the object was received or the File Set * Creator which stored the object into a DICOM file. * <p> * See [DCMEEREQ-799] for details. * * @param datasetWithFMI dataset with file meta information * * @return true, if the instance is likely to be faulty JPEG-LS */ public boolean isPossiblyFaultyJPEGLS(DatasetWithFMI datasetWithFMI) { Attributes fmi = datasetWithFMI.getFileMetaInformation(); Attributes dataset = datasetWithFMI.getDataset(); if (fmi != null) { if (UID.JPEGLSLossless.equals(fmi.getString(Tag.TransferSyntaxUID)) && isFaultyJPEGLSImplementationClassUID(fmi.getString(Tag.ImplementationClassUID)) && dataset.getInt(Tag.BitsAllocated, 0) == 16) { return true; } } return false; } private boolean isFaultyJPEGLSImplementationClassUID(String implementationClassUID) { if (faultyJPEGLSImplementationClassUIDs == null || implementationClassUID == null) return false; for (String faultyJPEGLSImplementationClassUID : faultyJPEGLSImplementationClassUIDs) { if (implementationClassUID.equals(faultyJPEGLSImplementationClassUID)) return true; } return false; } @Override public String toString() { return "StorageSystemGroup[id=" + groupID + ", activeStorageSystems=" + Arrays.toString(activeStorageSystemIDs) + "]"; } public String getOlockHash() { return olockHash; } public void setOlockHash(String olockHash) { this.olockHash = olockHash; } }