001/* 002 * **** BEGIN LICENSE BLOCK ***** 003 * Version: MPL 1.1/GPL 2.0/LGPL 2.1 004 * 005 * The contents of this file are subject to the Mozilla Public License Version 006 * 1.1 (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * http://www.mozilla.org/MPL/ 009 * 010 * Software distributed under the License is distributed on an "AS IS" basis, 011 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 012 * for the specific language governing rights and limitations under the 013 * License. 014 * 015 * The Original Code is part of dcm4che, an implementation of DICOM(TM) in 016 * Java(TM), hosted at https://github.com/gunterze/dcm4che. 017 * 018 * The Initial Developer of the Original Code is 019 * Agfa Healthcare. 020 * Portions created by the Initial Developer are Copyright (C) 2014 021 * the Initial Developer. All Rights Reserved. 022 * 023 * Contributor(s): 024 * See @authors listed below 025 * 026 * Alternatively, the contents of this file may be used under the terms of 027 * either the GNU General Public License Version 2 or later (the "GPL"), or 028 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), 029 * in which case the provisions of the GPL or the LGPL are applicable instead 030 * of those above. If you wish to allow use of your version of this file only 031 * under the terms of either the GPL or the LGPL, and not to allow others to 032 * use your version of this file under the terms of the MPL, indicate your 033 * decision by deleting the provisions above and replace them with the notice 034 * and other provisions required by the GPL or the LGPL. If you do not delete 035 * the provisions above, a recipient may use your version of this file under 036 * the terms of any one of the MPL, the GPL or the LGPL. 037 * 038 * ***** END LICENSE BLOCK ***** 039 */ 040package org.dcm4che3.conf.dicom; 041 042import org.dcm4che3.audit.EventID; 043import org.dcm4che3.audit.EventTypeCode; 044import org.dcm4che3.audit.ObjectFactory; 045import org.dcm4che3.audit.RoleIDCode; 046import org.dcm4che3.conf.api.*; 047import org.dcm4che3.conf.api.internal.DicomConfigurationManager; 048import org.dcm4che3.conf.core.DefaultBeanVitalizer; 049import org.dcm4che3.conf.core.DefaultTypeSafeConfiguration; 050import org.dcm4che3.conf.core.Nodes; 051import org.dcm4che3.conf.core.adapters.NullToNullDecorator; 052import org.dcm4che3.conf.core.api.BatchRunner.Batch; 053import org.dcm4che3.conf.core.api.Configuration; 054import org.dcm4che3.conf.core.api.ConfigurationException; 055import org.dcm4che3.conf.core.api.TypeSafeConfiguration; 056import org.dcm4che3.conf.core.api.internal.BeanVitalizer; 057import org.dcm4che3.conf.core.context.LoadingContext; 058import org.dcm4che3.conf.dicom.adapters.*; 059import org.dcm4che3.data.Code; 060import org.dcm4che3.data.Issuer; 061import org.dcm4che3.data.ValueSelector; 062import org.dcm4che3.net.ApplicationEntity; 063import org.dcm4che3.net.Device; 064import org.dcm4che3.net.DeviceInfo; 065import org.dcm4che3.util.AttributesFormat; 066import org.dcm4che3.util.Property; 067import org.slf4j.Logger; 068import org.slf4j.LoggerFactory; 069 070import java.util.*; 071 072/** 073 * @author Roman K 074 */ 075@SuppressWarnings("unchecked") 076public class CommonDicomConfiguration implements DicomConfigurationManager, TransferCapabilityConfigExtension { 077 078 private static final Logger log = LoggerFactory.getLogger(CommonDicomConfiguration.class); 079 080 081 /** 082 * see preventDeviceModifications(org.dcm4che3.net.Device) 083 */ 084 private final Map<Device, Object> readOnlyDevices = Collections.synchronizedMap(new WeakHashMap<Device, Object>()); 085 086 protected final Configuration lowLevelConfig; 087 private final BeanVitalizer vitalizer; 088 089 private final Map<Class, List<Class>> extensionsByClass; 090 private final TypeSafeConfiguration<DicomConfigurationRoot> config; 091 private AlternativeTCLoader alternativeTCLoader; 092 093 public CommonDicomConfiguration(Configuration configurationStorage, Map<Class, List<Class>> extensionsByClass, boolean doCacheTCGroups) { 094 this(configurationStorage, extensionsByClass); 095 alternativeTCLoader = new AlternativeTCLoader(this, doCacheTCGroups); 096 } 097 098 public CommonDicomConfiguration(Configuration configStorage, Map<Class, List<Class>> extensionsByClass) { 099 100 config = new DefaultTypeSafeConfiguration<DicomConfigurationRoot>( 101 configStorage, 102 DicomConfigurationRoot.class, 103 extensionsByClass 104 ); 105 106 this.lowLevelConfig = configStorage; 107 this.extensionsByClass = extensionsByClass; 108 109 vitalizer = config.getVitalizer(); 110 111 addCustomAdapters(vitalizer); 112 113 // quick init 114 try { 115 if (!configurationExists()) { 116 lowLevelConfig.persistNode(DicomPath.CONFIG_ROOT_PATH, createInitialConfigRootNode(), null); 117 118 } 119 } catch (ConfigurationException e) { 120 throw new RuntimeException("Dicom configuration cannot be initialized", e); 121 } 122 123 alternativeTCLoader = new AlternativeTCLoader(this, false); 124 } 125 126 /** 127 * Returns a list of registered extensions for a specified base extension class 128 * 129 * @param clazz 130 * @param <T> 131 * @return 132 */ 133 @Override 134 public <T> List<Class<? extends T>> getExtensionClassesByBaseClass(Class<T> clazz) { 135 List<Class> classes = extensionsByClass.get(clazz); 136 137 List<Class<? extends T>> list = new ArrayList<Class<? extends T>>(); 138 139 if (classes != null) 140 for (Class<?> aClass : classes) list.add((Class<? extends T>) aClass); 141 142 return list; 143 } 144 145 public static void addCustomAdapters(BeanVitalizer defaultBeanVitalizer) { 146 147 // register DICOM type adapters 148 defaultBeanVitalizer.registerCustomConfigTypeAdapter(AttributesFormat.class, new NullToNullDecorator(new AttributeFormatTypeAdapter())); 149 defaultBeanVitalizer.registerCustomConfigTypeAdapter(Code.class, new NullToNullDecorator(new CodeTypeAdapter())); 150 defaultBeanVitalizer.registerCustomConfigTypeAdapter(Issuer.class, new NullToNullDecorator(new IssuerTypeAdapter())); 151 defaultBeanVitalizer.registerCustomConfigTypeAdapter(ValueSelector.class, new NullToNullDecorator(new ValueSelectorTypeAdapter())); 152 defaultBeanVitalizer.registerCustomConfigTypeAdapter(Property.class, new NullToNullDecorator(new PropertyTypeAdapter())); 153 154 // register audit log type adapters 155 defaultBeanVitalizer.registerCustomConfigTypeAdapter(EventTypeCode.class, new NullToNullDecorator(new AuditSimpleTypeAdapters.EventTypeCodeAdapter())); 156 defaultBeanVitalizer.registerCustomConfigTypeAdapter(EventID.class, new NullToNullDecorator(new AuditSimpleTypeAdapters.EventIDTypeAdapter())); 157 defaultBeanVitalizer.registerCustomConfigTypeAdapter(RoleIDCode.class, new NullToNullDecorator(new AuditSimpleTypeAdapters.RoleIDCodeTypeAdapter())); 158 } 159 160 protected HashMap<String, Object> createInitialConfigRootNode() { 161 HashMap<String, Object> rootNode = new HashMap<String, Object>(); 162 rootNode.put("dicomDevicesRoot", new HashMap<String, Object>()); 163 164 List<Object> pathItems = new ArrayList<Object>(METADATA_ROOT_PATH.getPathItems()); 165 pathItems.remove(0); 166 167 Nodes.replaceNode(rootNode, new HashMap(), pathItems); 168 return rootNode; 169 } 170 171 @Override 172 public TypeSafeConfiguration<DicomConfigurationRoot> getTypeSafeConfiguration() { 173 return config; 174 } 175 176 @Override 177 public boolean configurationExists() throws ConfigurationException { 178 return lowLevelConfig.nodeExists(DicomPath.CONFIG_ROOT_PATH); 179 } 180 181 @Override 182 public boolean purgeConfiguration() throws ConfigurationException { 183 if (!configurationExists()) return false; 184 lowLevelConfig.persistNode(DicomPath.CONFIG_ROOT_PATH, new HashMap<String, Object>(), null); 185 return true; 186 } 187 188 @Override 189 public void preventDeviceModifications(Device d) { 190 readOnlyDevices.put(d, true); 191 } 192 193 @Override 194 public void refreshTCGroups() { 195 alternativeTCLoader.refreshTCGroups(); 196 } 197 198 @Override 199 public boolean registerAETitle(String aet) throws ConfigurationException { 200 return true; 201 } 202 203 @Override 204 public void unregisterAETitle(String aet) throws ConfigurationException { 205 } 206 207 208 @Override 209 public ApplicationEntity findApplicationEntity(String aet) throws ConfigurationException { 210 211 if (aet == null) throw new IllegalArgumentException("Requested AE's title cannot be null"); 212 213 Iterator<?> search = lowLevelConfig.search(DicomPath.DeviceNameByAEName.set("aeName", aet).path()); 214 215 if (!search.hasNext()) { 216 search = lowLevelConfig.search(DicomPath.DeviceNameByAENameAlias.set("aeNameAlias", aet).path()); 217 218 if (!search.hasNext()) 219 throw new ConfigurationNotFoundException("AE '" + aet + "' not found"); 220 } 221 222 String deviceNameNode = (String) search.next(); 223 if (search.hasNext()) 224 log.warn("Application entity title '{}' is not unique. Check the configuration!", aet); 225 Device device = findDevice(deviceNameNode); 226 227 ApplicationEntity ae = device.getApplicationEntity(aet); 228 if (ae == null) 229 throw new NoSuchElementException("Unexpected error"); 230 return ae; 231 232 } 233 234 @Override 235 public ApplicationEntity findApplicationEntityByUUID(String uuid) throws ConfigurationException { 236 237 if (uuid == null) throw new IllegalArgumentException("Requested AE's uuid cannot be null"); 238 239 Iterator search = lowLevelConfig.search(DicomPath.DeviceNameByAEUUID.set("aeUUID", uuid).path()); 240 241 try { 242 String deviceNameNode = (String) search.next(); 243 Device device = findDevice(deviceNameNode); 244 245 for (ApplicationEntity applicationEntity : device.getApplicationEntities()) { 246 if (uuid.equals(applicationEntity.getUuid())) { 247 return applicationEntity; 248 } 249 } 250 251 throw new NoSuchElementException("Unexpected error"); 252 253 } catch (NoSuchElementException e) { 254 throw new ConfigurationNotFoundException("AE with UUID '" + uuid + "' not found", e); 255 } 256 } 257 258 @Override 259 public Device findDeviceByUUID(String uuid) throws ConfigurationException { 260 if (uuid == null) throw new IllegalArgumentException("Requested Device's uuid cannot be null"); 261 262 Iterator search = lowLevelConfig.search(DicomPath.DeviceNameByUUID.set("deviceUUID", uuid).path()); 263 264 try { 265 String deviceNameNode = (String) search.next(); 266 return findDevice(deviceNameNode); 267 } catch (NoSuchElementException e) { 268 throw new ConfigurationNotFoundException("Device with UUID '" + uuid + "' not found", e); 269 } 270 } 271 272 @Override 273 public Device findDevice(String name, DicomConfigOptions options) throws ConfigurationException { 274 275 options = options == null ? new DicomConfigOptions() : options; 276 277 if (name == null) throw new IllegalArgumentException("Requested device name cannot be null"); 278 279 try { 280 Object deviceConfigurationNode = lowLevelConfig.getConfigurationNode(DicomPath.devicePath(name), Device.class); 281 if (deviceConfigurationNode == null) 282 throw new ConfigurationNotFoundException("Device " + name + " not found"); 283 284 LoadingContext ctx = config.getContextFactory().newLoadingContext(); 285 if (options.getIgnoreUnresolvedReferences() == Boolean.TRUE) { 286 ctx.setIgnoreUnresolvedReferences(true); 287 } 288 289 Device device = vitalizer.newConfiguredInstance((Map<String, Object>) deviceConfigurationNode, Device.class, ctx); 290 291 // perform alternative TC init in case an extension is present 292 alternativeTCLoader.initGroupBasedTCs(device); 293 294 return device; 295 } catch (ConfigurationNotFoundException e) { 296 throw e; 297 } catch (RuntimeException e) { 298 throw new ConfigurationException("Configuration for device " + name + " cannot be loaded", e); 299 } 300 301 } 302 303 @Override 304 public Device findDevice(String name) throws ConfigurationException { 305 return findDevice(name, null); 306 } 307 308 @Override 309 public DeviceInfo[] listDeviceInfos(DeviceInfo keys) throws ConfigurationException { 310 throw new RuntimeException("Not yet implemented"); 311 } 312 313 @Override 314 public String[] listDeviceNames() throws ConfigurationException { 315 Iterator search = lowLevelConfig.search(DicomPath.AllDeviceNames.path()); 316 List<String> deviceNames = null; 317 try { 318 deviceNames = new ArrayList<String>(); 319 while (search.hasNext()) 320 deviceNames.add((String) search.next()); 321 } catch (Exception e) { 322 throw new ConfigurationException("Error while getting list of device names", e); 323 } 324 return deviceNames.toArray(new String[deviceNames.size()]); 325 } 326 327 @Override 328 public List<String> listAllAETitles() throws ConfigurationException { 329 List<String> aeNames = new ArrayList<String>(); 330 try { 331 Iterator search = lowLevelConfig.search(DicomPath.AllAETitles.path()); 332 while (search.hasNext()) 333 aeNames.add((String) search.next()); 334 } catch (Exception e) { 335 throw new ConfigurationException("Error while getting the list of registered AE titles", e); 336 } 337 return aeNames; 338 } 339 340 @Override 341 public void persist(Device device) throws ConfigurationException { 342 343 if (readOnlyDevices.containsKey(device)) handleReadOnlyDeviceModification(); 344 345 if (device.getDeviceName() == null) throw new ConfigurationException("The name of the device must not be null"); 346 if (lowLevelConfig.nodeExists(DicomPath.devicePath(device.getDeviceName()))) 347 throw new ConfigurationAlreadyExistsException("Device " + device.getDeviceName() + " already exists"); 348 // otherwise it is the same as merge 349 merge(device); 350 } 351 352 private void handleReadOnlyDeviceModification() { 353 354 String message = "Persisting the config for a Device object that is marked as read-only. " + 355 "This warning is not affecting the behavior for now, but soon it will be replaced with throwing an exception!" + 356 "If you want to make config modifications, use a separate instance of Device! See CSP configuration docs for details."; 357 358 // create exception to log the stacktrace 359 ConfigurationException exception = new ConfigurationException(); 360 361 log.warn(message, exception); 362 } 363 364 @Override 365 public void merge(Device device) throws ConfigurationException { 366 367 if (readOnlyDevices.containsKey(device)) handleReadOnlyDeviceModification(); 368 369 if (device.getDeviceName() == null) throw new ConfigurationException("The name of the device must not be null"); 370 Map<String, Object> configNode = createDeviceConfigNode(device); 371 lowLevelConfig.persistNode(DicomPath.devicePath(device.getDeviceName()), configNode, Device.class); 372 } 373 374 protected Map<String, Object> createDeviceConfigNode(Device device) throws ConfigurationException { 375 376 final Map<String, Object> deviceConfigNode = vitalizer.createConfigNodeFromInstance(device, Device.class); 377 378 // wipe out TCs in case an extension is present 379 alternativeTCLoader.cleanUpTransferCapabilitiesInDeviceNode(device, deviceConfigNode); 380 381 382 return deviceConfigNode; 383 } 384 385 @Override 386 public void removeDevice(String name) throws ConfigurationException { 387 lowLevelConfig.removeNode(DicomPath.devicePath(name)); 388 } 389 390 391 @Override 392 public void close() { 393 394 } 395 396 @Override 397 public void sync() throws ConfigurationException { 398 lowLevelConfig.refreshNode(DicomPath.CONFIG_ROOT_PATH); 399 } 400 401 @Override 402 public <T> T getDicomConfigurationExtension(Class<T> clazz) { 403 404 // trick CDI 405 if (TransferCapabilityConfigExtension.class.equals(clazz)) { 406 return (T) new TransferCapabilityConfigExtension() { 407 @Override 408 public void persistTransferCapabilityConfig(TCConfiguration tcConfig) throws ConfigurationException { 409 CommonDicomConfiguration.this.persistTransferCapabilityConfig(tcConfig); 410 } 411 412 @Override 413 public TCConfiguration getTransferCapabilityConfig() throws ConfigurationException { 414 return CommonDicomConfiguration.this.getTransferCapabilityConfig(); 415 } 416 }; 417 } 418 419 if (!clazz.isAssignableFrom(this.getClass())) { 420 throw new IllegalArgumentException("Cannot find a configuration extension for class " + clazz.getName()); 421 } 422 423 return (T) this; 424 } 425 426 @Override 427 public BeanVitalizer getVitalizer() { 428 return vitalizer; 429 } 430 431 432 @Override 433 public Configuration getConfigurationStorage() { 434 return lowLevelConfig; 435 } 436 437 @Override 438 public void persistTransferCapabilityConfig(TCConfiguration tcConfig) throws ConfigurationException { 439 Map<String, Object> configNode = vitalizer.createConfigNodeFromInstance(tcConfig); 440 lowLevelConfig.persistNode(DicomPath.TC_GROUPS_PATH, configNode, TCConfiguration.class); 441 } 442 443 @Override 444 public TCConfiguration getTransferCapabilityConfig() throws ConfigurationException { 445 Map<String, Object> configurationNode = (Map<String, Object>) lowLevelConfig.getConfigurationNode(DicomPath.TC_GROUPS_PATH, TCConfiguration.class); 446 447 if (configurationNode == null) 448 return new TCConfiguration(); 449 450 return vitalizer.newConfiguredInstance( 451 configurationNode, 452 TCConfiguration.class); 453 } 454 455 @Override 456 public void runBatch(final DicomConfigBatch dicomConfigBatch) { 457 /* 458 * Use the batch support of underlying configuration storage to execute batch 459 */ 460 lowLevelConfig.runBatch(new Batch() { 461 462 @Override 463 public void run() { 464 dicomConfigBatch.run(); 465 } 466 467 }); 468 } 469 470 public static BeanVitalizer createDefaultDicomVitalizer() { 471 DefaultBeanVitalizer defaultBeanVitalizer = new DefaultBeanVitalizer(); 472 addCustomAdapters(defaultBeanVitalizer); 473 return defaultBeanVitalizer; 474 } 475} 476