/* * * ** 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) 2015 * 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.dicom; import junit.framework.AssertionFailedError; import org.dcm4che3.conf.core.DefaultBeanVitalizer; import org.dcm4che3.conf.core.api.ConfigurableClass; import org.dcm4che3.conf.core.api.ConfigurableProperty; import org.dcm4che3.conf.core.api.ConfigurableProperty.ConfigurablePropertyType; import org.dcm4che3.conf.core.api.Configuration; import org.dcm4che3.conf.core.api.Path; import org.dcm4che3.conf.core.api.internal.ConfigProperty; import org.dcm4che3.conf.core.api.internal.BeanVitalizer; import org.dcm4che3.conf.core.olock.OLockCopyFilter; import org.dcm4che3.conf.core.olock.OLockHashCalcFilter; import org.dcm4che3.conf.core.olock.HashBasedOptimisticLockingConfiguration; import org.dcm4che3.conf.core.util.ConfigNodeTraverser; import org.dcm4che3.conf.core.Nodes; import org.dcm4che3.net.Device; import org.junit.Assert; import org.junit.Test; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; /** * @author Roman K */ public class OptimisticLockingTest extends HashBasedOptimisticLockingConfiguration { public OptimisticLockingTest() { super(null, null); } @ConfigurableClass public static class PartyPlan { @ConfigurableProperty(type = ConfigurablePropertyType.OptimisticLockingHash) String olock; @ConfigurableProperty String name; @ConfigurableProperty int budget; @ConfigurableProperty Map<String, Party> parties; public String getOlock() { return olock; } public void setOlock(String olock) { this.olock = olock; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getBudget() { return budget; } public void setBudget(int budget) { this.budget = budget; } public Map<String, Party> getParties() { return parties; } public void setParties(Map<String, Party> parties) { this.parties = parties; } } @ConfigurableClass public static class Party { @ConfigurableProperty(type = ConfigurablePropertyType.OptimisticLockingHash) String oLock; @ConfigurableProperty(name = "prop1") int guests; @ConfigurableProperty(name = "prop2") boolean outside; @ConfigurableProperty(name = "str") String occasion; public String getoLock() { return oLock; } public void setoLock(String oLock) { this.oLock = oLock; } public int getGuests() { return guests; } public void setGuests(int guests) { this.guests = guests; } public boolean isOutside() { return outside; } public void setOutside(boolean outside) { this.outside = outside; } public String getOccasion() { return occasion; } public void setOccasion(String occasion) { this.occasion = occasion; } } @Test public void testPartyOlock() throws IOException { PartyPlan partyPlan = new PartyPlan(); partyPlan.setName("great Plan!"); partyPlan.setBudget(10000); Party party = new Party(); party.setGuests(10); party.setOccasion("gettogether"); party.setOutside(true); Party party1 = new Party(); party1.setGuests(5); party1.setOccasion("boring birthday"); party1.setOutside(false); HashMap<String, Party> parties = new HashMap<String, Party>(); parties.put("p1", party); parties.put("p2", party1); partyPlan.setParties(parties); BeanVitalizer beanVitalizer = new DefaultBeanVitalizer(); Map<String, Object> oldNode = beanVitalizer.createConfigNodeFromInstance(partyPlan); ConfigNodeTraverser.traverseNodeTypesafe(oldNode, new ConfigProperty(PartyPlan.class), new ArrayList<Class>(), new HashMarkingTypesafeNodeFilter()); ConfigNodeTraverser.traverseMapNode(oldNode, new OLockHashCalcFilter()); // consistent? String originalHash = "HRci3v11AErGV1sHTG5fEtrS5ow="; Assert.assertEquals(originalHash, oldNode.get(Configuration.OLOCK_HASH_KEY)); // remember old hash, change smth, check party1.setGuests(party1.getGuests() + 1); Map<String, Object> newNode = beanVitalizer.createConfigNodeFromInstance(partyPlan); ConfigNodeTraverser.traverseNodeTypesafe(newNode, new ConfigProperty(PartyPlan.class), new ArrayList<Class>(), new HashMarkingTypesafeNodeFilter()); ConfigNodeTraverser.traverseMapNode(newNode, new OLockHashCalcFilter()); Assert.assertNotEquals("node changed", Nodes.getNode(oldNode, "/parties/p2/" + Configuration.OLOCK_HASH_KEY), Nodes.getNode(newNode, "/parties/p2/" + Configuration.OLOCK_HASH_KEY)); Assert.assertEquals("node not changed", Nodes.getNode(oldNode, "/parties/p1/" + Configuration.OLOCK_HASH_KEY), Nodes.getNode(newNode, "/parties/p1/" + Configuration.OLOCK_HASH_KEY)); Assert.assertEquals("parent should not change", oldNode.get(Configuration.OLOCK_HASH_KEY), newNode.get(Configuration.OLOCK_HASH_KEY)); // try changing map keys parties.put("aNewOldParty", parties.remove("p1")); Map<String, Object> nodeWithMapChanged = beanVitalizer.createConfigNodeFromInstance(partyPlan); ConfigNodeTraverser.traverseNodeTypesafe(nodeWithMapChanged, new ConfigProperty(PartyPlan.class), new ArrayList<Class>(), new HashMarkingTypesafeNodeFilter()); ConfigNodeTraverser.traverseMapNode(nodeWithMapChanged, new OLockHashCalcFilter()); Assert.assertNotEquals("parent should change", newNode.get(Configuration.OLOCK_HASH_KEY), nodeWithMapChanged.get(Configuration.OLOCK_HASH_KEY)); Assert.assertEquals("map entry hash should not change", Nodes.getNode(newNode, "/parties/p1/" + Configuration.OLOCK_HASH_KEY), Nodes.getNode(nodeWithMapChanged, "/parties/aNewOldParty/" + Configuration.OLOCK_HASH_KEY)); // try recalc ConfigNodeTraverser.traverseMapNode(oldNode, new OLockHashCalcFilter()); Assert.assertEquals(originalHash, oldNode.get(Configuration.OLOCK_HASH_KEY)); // try calculating hashes when old hash is there ConfigNodeTraverser.traverseMapNode(oldNode, new OLockCopyFilter("#old_hash")); ConfigNodeTraverser.traverseMapNode(oldNode, new OLockHashCalcFilter("#old_hash")); Assert.assertEquals(originalHash, oldNode.get(Configuration.OLOCK_HASH_KEY)); } // @Test // public void testConnectionOlockHash() throws IOException { // // Configuration mockDicomConfStorage = SimpleStorageTest.getMockDicomConfStorage(); // Map<String,Object> configurationNode1 = (Map<String, Object>) mockDicomConfStorage.getConfigurationNode(DicomPath.DeviceByNameForWrite.set("deviceName", "dcmqrscp").path(), Device.class); // // new DicomNodeTraverser(new ArrayList<Class<?>>()).traverseTree(configurationNode1, Device.class, new OLockHashCalcFilter()); // // Assert.assertEquals("BA7C7621709912234F89C4D9BD96A3DD5FB29417", Nodes.getNode(configurationNode1,"/dicomConnection[cn='dicom']/oLock")); // // // extract in '_old_olock' in node being persisted // new DicomNodeTraverser(new ArrayList<Class<?>>()).traverseTree(configurationNode1, Device.class, new OLockExtractingFilter("_old_olock")); // new DicomNodeTraverser(new ArrayList<Class<?>>()).traverseTree(configurationNode1, Device.class, new OLockHashCalcFilter()); // // Assert.assertEquals("BA7C7621709912234F89C4D9BD96A3DD5FB29417", Nodes.getNode(configurationNode1,"/dicomConnection[cn='dicom']/oLock")); // // new ObjectMapper().writerWithDefaultPrettyPrinter().writeValue(System.out, configurationNode1); // // } @Test public void testDeco() throws IOException { Configuration mockDicomConfStorage = SimpleStorageTest.getMockDicomConfStorage(); Configuration lockedConfig = new HashBasedOptimisticLockingConfiguration(mockDicomConfStorage, new ArrayList<Class>()); // imitate 3 users simultaneously getting the same node Map<String, Object> configurationNode1 = (Map<String, Object>) lockedConfig.getConfigurationNode(DicomPath.devicePath("dcmqrscp"), Device.class); Map<String, Object> configurationNode2 = (Map<String, Object>) lockedConfig.getConfigurationNode(DicomPath.devicePath("dcmqrscp"), Device.class); Map<String, Object> configurationNode3 = (Map<String, Object>) lockedConfig.getConfigurationNode(DicomPath.devicePath("dcmqrscp"), Device.class); // imitate simultaneous changes Nodes.replacePrimitive(configurationNode1, false, new Path("dicomNetworkAE", "DCMQRSCP", "dicomAssociationAcceptor").getPathItems()); Nodes.replacePrimitive(configurationNode2, false, new Path("dicomNetworkAE", "DCMQRSCP", "dicomAssociationInitiator").getPathItems()); Nodes.replacePrimitive(configurationNode3, "12345", new Path("dcmKeyStorePin").getPathItems()); // persist some changes from 1st user lockedConfig.persistNode(DicomPath.devicePath("dcmqrscp"), configurationNode1, Device.class); // persist some conflicting changes from 2nd user - should fail try { lockedConfig.persistNode(DicomPath.devicePath("dcmqrscp"), configurationNode2, Device.class); throw new AssertionFailedError("Should have failed!"); } catch (Exception e) { // its ok } // persist some non-conflicting changes from 3rd user - should be fine lockedConfig.persistNode(DicomPath.devicePath("dcmqrscp"), configurationNode3, Device.class); // assert the changes that were supposed to be persisted Map<String, Object> newNode = (Map<String, Object>) lockedConfig.getConfigurationNode(DicomPath.devicePath("dcmqrscp"), Device.class); Assert.assertEquals( "12345", Nodes.getNode(newNode, "/dcmKeyStorePin")); Assert.assertEquals( false, Nodes.getNode(newNode, "/dicomNetworkAE/DCMQRSCP/dicomAssociationAcceptor")); Assert.assertEquals( true, Nodes.getNode(newNode, "/dicomNetworkAE/DCMQRSCP/dicomAssociationInitiator")); } }