001package org.dcm4che3.conf.core.olock; 002 003import org.dcm4che3.conf.core.DelegatingConfiguration; 004import org.dcm4che3.conf.core.api.Configuration; 005import org.dcm4che3.conf.core.api.ConfigurationException; 006import org.dcm4che3.conf.core.api.Path; 007import org.dcm4che3.conf.core.api.internal.ConfigProperty; 008import org.dcm4che3.conf.core.api.internal.ConfigReflection; 009import org.dcm4che3.conf.core.util.ConfigNodeTraverser; 010import org.dcm4che3.conf.core.util.ConfigNodeTraverser.ConfigNodeTypesafeFilter; 011import org.dcm4che3.conf.core.Nodes; 012import org.slf4j.Logger; 013import org.slf4j.LoggerFactory; 014 015import java.util.List; 016import java.util.Map; 017 018/** 019 * 020 * 021 * Caution: persistNode should be called in a transaction to ensure consistent comparison between data from backend/data being persisted, 022 * otherwise it's possible that another writer modifies something before this class obtains a lock and the changes by that writer will be lost 023 * 024 * Edge cases: 025 * <ul> 026 * <li> 027 * If one deletes a node that is a root of olock, and another changes that node, it is NOT considered a conflict, regardless of operation order the node will be just deleted. 028 * this holds for case of a subnode, a map entry, or a collection element that has a uuid. 029 * </li><li> 030 * For any collections whose elements don't have uuids, a collection is always fully overwritten without trying 031 * to merge in any modified contents from the backend. 032 * </li> 033 * </ul> 034 * 035 * 036 * @author Roman K 037 */ 038@SuppressWarnings("unchecked") 039public class HashBasedOptimisticLockingConfiguration extends DelegatingConfiguration { 040 041 private static final Logger log = LoggerFactory.getLogger(HashBasedOptimisticLockingConfiguration.class); 042 043 public static final String NOT_CALCULATED_YET = "not-calculated-yet"; 044 public static final String OLD_OLOCK_HASH_KEY = "#old_hash"; 045 046 private List<Class> allExtensionClasses; 047 048 /** 049 * @param delegate 050 * @param allExtensionClasses 051 */ 052 public HashBasedOptimisticLockingConfiguration(Configuration delegate, List<Class> allExtensionClasses) { 053 super(delegate); 054 055 this.allExtensionClasses = allExtensionClasses; 056 } 057 058 @Override 059 public void persistNode(final Path path, final Map<String, Object> configNode, final Class configurableClass) throws ConfigurationException { 060 Map<String, Object> nodeBeingPersisted = (Map<String, Object>) Nodes.deepCloneNode(configNode); 061 062 // get existing node from storage 063 Map<String, Object> nodeInStorage = (Map<String, Object>) getConfigurationNode(path, configurableClass); 064 065 // if there is nothing in storage - just persist and leave 066 if (nodeInStorage == null) { 067 if (nodeBeingPersisted != null) { 068 ConfigNodeTraverser.traverseMapNode(nodeBeingPersisted, new CleanupFilter(Configuration.OLOCK_HASH_KEY)); 069 } 070 delegate.persistNode(path, nodeBeingPersisted, configurableClass); 071 return; 072 } 073 074 // save old hashes in node being persisted 075 ConfigNodeTraverser.traverseMapNode(nodeBeingPersisted, new OLockCopyFilter(OLD_OLOCK_HASH_KEY)); 076 077 // calculate current hashes in node being persisted 078 ConfigNodeTraverser.traverseMapNode(nodeBeingPersisted, new OLockHashCalcFilter(OLD_OLOCK_HASH_KEY)); 079 080 ////// merge the object ///// 081 ConfigNodeTraverser.dualTraverseMapNodes(nodeInStorage, nodeBeingPersisted, new OLockMergeDualFilter()); 082 083 // filter the #hash clutter out 084 ConfigNodeTraverser.traverseMapNode(nodeBeingPersisted, new CleanupFilter(OLD_OLOCK_HASH_KEY, Configuration.OLOCK_HASH_KEY)); 085 086 delegate.persistNode(path, nodeBeingPersisted, configurableClass); 087 } 088 089 090 @Override 091 public Object getConfigurationNode(Path path, Class configurableClass) throws ConfigurationException { 092 093 Object configurationNode = super.getConfigurationNode(path, configurableClass); 094 095 // calculate olock hashes if called with configurableClass 096 if (configurableClass != null && configurationNode != null) { 097 098 ConfigNodeTraverser.traverseNodeTypesafe( 099 configurationNode, 100 ConfigReflection.getDummyPropertyForClass(configurableClass), 101 allExtensionClasses, 102 new HashMarkingTypesafeNodeFilter() 103 ); 104 105 ConfigNodeTraverser.traverseMapNode(configurationNode, new OLockHashCalcFilter()); 106 } 107 108 return configurationNode; 109 } 110 111 /** 112 * Inserts #hash props into node according to @ConfigurableProperty annotations 113 */ 114 public static class HashMarkingTypesafeNodeFilter implements ConfigNodeTypesafeFilter { 115 116 @Override 117 public boolean beforeNode(Map<String, Object> containerNode, Class containerNodeClass, ConfigProperty property) throws ConfigurationException { 118 119 if (property.isOlockHash()) { 120 containerNode.put(OLOCK_HASH_KEY, NOT_CALCULATED_YET); 121 return true; 122 } 123 124 return false; 125 } 126 } 127 128}