/* * Copyright (c) 2010-2013 Evolveum * * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.evolveum.midpoint.repo.sql; import com.evolveum.midpoint.prism.ItemDefinition; import com.evolveum.midpoint.prism.PrismContainerDefinition; import com.evolveum.midpoint.prism.PrismObject; import com.evolveum.midpoint.prism.PrismObjectDefinition; import com.evolveum.midpoint.prism.PrismPropertyDefinition; import com.evolveum.midpoint.prism.PrismPropertyValue; import com.evolveum.midpoint.prism.delta.ContainerDelta; import com.evolveum.midpoint.prism.delta.ItemDelta; import com.evolveum.midpoint.prism.delta.ObjectDelta; import com.evolveum.midpoint.prism.delta.PropertyDelta; import com.evolveum.midpoint.prism.match.PolyStringOrigMatchingRule; import com.evolveum.midpoint.prism.path.IdItemPathSegment; import com.evolveum.midpoint.prism.path.ItemPath; import com.evolveum.midpoint.prism.path.ItemPathSegment; import com.evolveum.midpoint.prism.path.NameItemPathSegment; import com.evolveum.midpoint.prism.polystring.PolyString; import com.evolveum.midpoint.prism.query.EqualFilter; import com.evolveum.midpoint.prism.query.ObjectQuery; import com.evolveum.midpoint.prism.query.builder.QueryBuilder; import com.evolveum.midpoint.repo.sql.testing.SqlRepoTestUtil; import com.evolveum.midpoint.schema.ResultHandler; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.util.exception.SchemaException; import com.evolveum.midpoint.util.logging.LoggingUtils; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; import com.evolveum.midpoint.xml.ns._public.common.common_3.AssignmentType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ConstructionType; import com.evolveum.midpoint.xml.ns._public.common.common_3.UserType; import com.evolveum.prism.xml.ns._public.types_3.PolyStringType; import org.hibernate.Session; import org.hibernate.jdbc.Work; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.ContextConfiguration; import org.testng.AssertJUnit; import org.testng.annotations.Test; import java.io.File; import java.sql.Connection; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; /** * @author Pavol Mederly */ @ContextConfiguration(locations = {"../../../../../ctx-test.xml"}) @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) public class ConcurrencyTest extends BaseSQLRepoTest { private static final Trace LOGGER = TraceManager.getTrace(ConcurrencyTest.class); //private static final long WAIT_TIME = 60000; //private static final long WAIT_STEP = 500; @Test(enabled = true) public void concurrency001_TwoWriters_OneAttributeEach__NoReader() throws Exception { ModifierThread[] mts = new ModifierThread[]{ new ModifierThread(1, new ItemPath(UserType.F_GIVEN_NAME), true, null, true), new ModifierThread(2, new ItemPath(UserType.F_FAMILY_NAME), true, null, true), // new ModifierThread(3, oid, UserType.F_DESCRIPTION, false), // new ModifierThread(4, oid, UserType.F_EMAIL_ADDRESS, false), // new ModifierThread(5, oid, UserType.F_TELEPHONE_NUMBER, false), // new ModifierThread(6, oid, UserType.F_EMPLOYEE_NUMBER, false), // new ModifierThread(8, oid, UserType.F_EMAIL_ADDRESS), // new ModifierThread(9, oid, UserType.F_EMPLOYEE_NUMBER) }; concurrencyUniversal("Test1", 30000L, 500L, mts, null); } @Test(enabled = true) public void concurrency002_FourWriters_OneAttributeEach__NoReader() throws Exception { ModifierThread[] mts = new ModifierThread[]{ new ModifierThread(1, new ItemPath(UserType.F_GIVEN_NAME), true, null, true), new ModifierThread(2, new ItemPath(UserType.F_FAMILY_NAME), true, null, true), new ModifierThread(3, new ItemPath(UserType.F_DESCRIPTION), false, null, true), new ModifierThread(4, new ItemPath(UserType.F_EMAIL_ADDRESS), false, null, true) }; concurrencyUniversal("Test2", 60000L, 500L, mts, null); } @Test(enabled = true) public void concurrency003_OneWriter_TwoAttributes__OneReader() throws Exception { ModifierThread[] mts = new ModifierThread[]{ new ModifierThread(1, new ItemPath(UserType.F_GIVEN_NAME), true, new ItemPath( new NameItemPathSegment(UserType.F_ASSIGNMENT), new IdItemPathSegment(1L), new NameItemPathSegment(AssignmentType.F_DESCRIPTION)), true) }; Checker checker = new Checker() { @Override public void check(int iteration, String oid) throws Exception { PrismObject<UserType> userRetrieved = repositoryService.getObject(UserType.class, oid, null, new OperationResult("dummy")); String givenName = userRetrieved.asObjectable().getGivenName().getOrig(); String assignmentDescription = userRetrieved.asObjectable().getAssignment().get(0).getDescription(); LOGGER.info("[" + iteration + "] givenName = " + givenName + ", assignment description = " + assignmentDescription); if (!givenName.equals(assignmentDescription)) { String msg = "Inconsistent object state: GivenName = " + givenName + ", assignment description = " + assignmentDescription; LOGGER.error(msg); throw new AssertionError(msg); } } }; concurrencyUniversal("Test3", 60000L, 0L, mts, checker); } @Test(enabled = true) public void concurrency004_TwoWriters_TwoAttributesEach__OneReader() throws Exception { ModifierThread[] mts = new ModifierThread[]{ new ModifierThread(1, new ItemPath(UserType.F_GIVEN_NAME), true, new ItemPath( new NameItemPathSegment(UserType.F_ASSIGNMENT), new IdItemPathSegment(1L), new NameItemPathSegment(AssignmentType.F_DESCRIPTION)), true), new ModifierThread(2, new ItemPath(UserType.F_FAMILY_NAME), true, new ItemPath( new NameItemPathSegment(UserType.F_ASSIGNMENT), new IdItemPathSegment(1L), new NameItemPathSegment(AssignmentType.F_CONSTRUCTION)), true), }; Checker checker = new Checker() { @Override public void check(int iteration, String oid) throws Exception { PrismObject<UserType> userRetrieved = repositoryService.getObject(UserType.class, oid, null, new OperationResult("dummy")); String givenName = userRetrieved.asObjectable().getGivenName().getOrig(); String familyName = userRetrieved.asObjectable().getFamilyName().getOrig(); String assignmentDescription = userRetrieved.asObjectable().getAssignment().get(0).getDescription(); String referenceDescription = userRetrieved.asObjectable().getAssignment().get(0).getConstruction().getDescription(); LOGGER.info("[" + iteration + "] givenName = " + givenName + ", assignment description = " + assignmentDescription + ", familyName = " + familyName + ", referenceDescription = " + referenceDescription); if (!givenName.equals(assignmentDescription)) { String msg = "Inconsistent object state: GivenName = " + givenName + ", assignment description = " + assignmentDescription; LOGGER.error(msg); throw new AssertionError(msg); } if (!familyName.equals(referenceDescription)) { String msg = "Inconsistent object state: FamilyName = " + familyName + ", account construction description = " + referenceDescription; LOGGER.error(msg); throw new AssertionError(msg); } } }; concurrencyUniversal("Test4", 60000L, 0L, mts, checker); } private interface Checker { void check(int iteration, String oid) throws Exception; } private void concurrencyUniversal(String name, long duration, long waitStep, ModifierThread[] modifierThreads, Checker checker) throws Exception { Session session = getFactory().openSession(); session.doWork(new Work() { @Override public void execute(Connection connection) throws SQLException { System.out.println(">>>>" + connection.getTransactionIsolation()); } }); session.close(); final File file = new File("src/test/resources/concurrency/user.xml"); PrismObject<UserType> user = prismContext.parseObject(file); user.asObjectable().setName(new PolyStringType(name)); OperationResult result = new OperationResult("Concurrency Test"); String oid = repositoryService.addObject(user, null, result); LOGGER.info("*** Object added: " + oid + " ***"); LOGGER.info("*** Starting modifier threads ***"); // modifierThreads[1].setOid(oid); // modifierThreads[1].runOnce(); // if(true) return; for (ModifierThread mt : modifierThreads) { mt.setOid(oid); mt.start(); } LOGGER.info("*** Waiting " + duration + " ms ***"); long startTime = System.currentTimeMillis(); int readIteration = 1; main: while (System.currentTimeMillis() - startTime < duration) { if (checker != null) { checker.check(readIteration, oid); } if (waitStep > 0L) { Thread.sleep(waitStep); } for (ModifierThread mt : modifierThreads) { if (!mt.isAlive()) { LOGGER.error("At least one of threads died prematurely, finishing waiting."); break main; } } readIteration++; } for (ModifierThread mt : modifierThreads) { mt.stop = true; // stop the threads System.out.println("Thread " + mt.id + " has done " + (mt.counter - 1) + " iterations"); LOGGER.info("Thread " + mt.id + " has done " + (mt.counter - 1) + " iterations"); } // we do not have to wait for the threads to be stopped, just examine their results Thread.sleep(1000); // give the threads a chance to finish (before repo will be shut down) for (ModifierThread mt : modifierThreads) { LOGGER.info("Modifier thread " + mt.id + " finished with an exception: ", mt.threadResult); } for (ModifierThread mt : modifierThreads) { if (mt.threadResult != null) { throw new AssertionError("Modifier thread " + mt.id + " finished with an exception: " + mt.threadResult, mt.threadResult); } } } class ModifierThread extends Thread { int id; String oid; // object to modify ItemPath attribute1; // attribute to modify ItemPath attribute2; // attribute to modify boolean poly; boolean checkValue; String lastVersion = null; volatile Throwable threadResult; volatile int counter = 1; ModifierThread(int id, ItemPath attribute1, boolean poly, ItemPath attribute2, boolean checkValue) { this.id = id; this.attribute1 = attribute1; this.attribute2 = attribute2; this.poly = poly; this.setName("Modifier for " + attributeNames()); this.checkValue = checkValue; } private String attributeNames() { return lastName(attribute1) + (attribute2 != null ? "/" + lastName(attribute2) : ""); } private String lastName(ItemPath path) { List<ItemPathSegment> segments = path.getSegments(); for (int i = segments.size()-1; i >= 0; i++) { if (segments.get(i) instanceof NameItemPathSegment) { return ((NameItemPathSegment) segments.get(i)).getName().getLocalPart(); } } return "?"; } public volatile boolean stop = false; @Override public void run() { try { while (!stop) { runOnce(); } } catch (Throwable t) { LoggingUtils.logException(LOGGER, "Unexpected exception: " + t, t); threadResult = t; } } public void runOnce() throws SchemaException { OperationResult result = new OperationResult("run"); LOGGER.info(" --- Iteration number " + counter + " for " + attributeNames() + " ---"); PrismObjectDefinition<?> userPrismDefinition = prismContext.getSchemaRegistry().findObjectDefinitionByCompileTimeClass(UserType.class); String prefix = lastName(attribute1); String dataWritten = "[" + prefix + ":" + Integer.toString(counter++) + "]"; PrismPropertyDefinition propertyDefinition1 = userPrismDefinition.findPropertyDefinition(attribute1); if (propertyDefinition1 == null) { throw new IllegalArgumentException("No definition for " + attribute1 + " in " + userPrismDefinition); } PropertyDelta delta1 = new PropertyDelta(attribute1, propertyDefinition1, prismContext); delta1.setValueToReplace(new PrismPropertyValue(poly ? new PolyString(dataWritten) : dataWritten)); List<ItemDelta> deltas = new ArrayList<ItemDelta>(); deltas.add(delta1); ItemDefinition propertyDefinition2 = null; if (attribute2 != null) { propertyDefinition2 = userPrismDefinition.findItemDefinition(attribute2); if (propertyDefinition2 == null) { throw new IllegalArgumentException("No definition for " + attribute2 + " in " + userPrismDefinition); } ItemDelta delta2 = null; if (propertyDefinition2 instanceof PrismContainerDefinition) { delta2 = new ContainerDelta(attribute2, (PrismContainerDefinition) propertyDefinition2, prismContext); } else { delta2 = new PropertyDelta(attribute2, (PrismPropertyDefinition) propertyDefinition2, prismContext); } if (ConstructionType.COMPLEX_TYPE.equals(propertyDefinition2.getTypeName())) { ConstructionType act = new ConstructionType(); act.setDescription(dataWritten); delta2.setValueToReplace(act.asPrismContainerValue()); } else { delta2.setValueToReplace(new PrismPropertyValue(dataWritten)); } deltas.add(delta2); } try { repositoryService.modifyObject(UserType.class, oid, deltas, result); result.computeStatus(); if (result.isError()) { LOGGER.error("Error found in operation result:\n{}", result.debugDump()); throw new IllegalStateException("Error found in operation result"); } } catch (Exception e) { String msg = "modifyObject failed while modifying attribute(s) " + attributeNames() + " to value " + dataWritten; threadResult = new RuntimeException(msg, e); LOGGER.error(msg, e); threadResult = e; stop = true; return; // finish processing } if (checkValue) { try { Thread.sleep(100); } catch (InterruptedException e) { } PrismObject<UserType> user; try { user = repositoryService.getObject(UserType.class, oid, null, result); } catch (Exception e) { String msg = "getObject failed while getting attribute(s) " + attributeNames(); threadResult = new RuntimeException(msg, e); LOGGER.error(msg, e); threadResult = e; stop = true; return; // finish processing } // check the attribute String dataRead; if (poly) { dataRead = user.findProperty(attribute1).getRealValue(PolyString.class).getOrig(); } else { dataRead = user.findProperty(attribute1).getRealValue(String.class); } if (!dataWritten.equals(dataRead)) { threadResult = new RuntimeException("Data read back (" + dataRead + ") does not match the data written (" + dataWritten + ") on attribute " + attribute1); LOGGER.error("compare failed", threadResult); stop = true; return; } if (attribute2 != null) { if (ConstructionType.COMPLEX_TYPE.equals(propertyDefinition2.getTypeName())) { dataRead = ((ConstructionType)user.findContainer(attribute2).getValue().getValue()).getDescription(); } else { dataRead = user.findProperty(attribute2).getRealValue(String.class); } if (!dataWritten.equals(dataRead)) { threadResult = new RuntimeException("Data read back (" + dataRead + ") does not match the data written (" + dataWritten + ") on attribute " + attribute2); LOGGER.error("compare failed", threadResult); stop = true; return; } } String currentVersion = user.getVersion(); String versionError = SqlRepoTestUtil.checkVersionProgress(lastVersion, currentVersion); if (versionError != null) { threadResult = new RuntimeException(versionError); LOGGER.error(versionError); stop = true; return; } lastVersion = currentVersion; } } public void setOid(String oid) { this.oid = oid; } } @Test(enabled = true) public void concurrency010_SearchIterative() throws Exception { String name = "Test10"; final String newFullName = "new-full-name"; final File file = new File("src/test/resources/concurrency/user.xml"); PrismObject<UserType> user = prismContext.parseObject(file); user.asObjectable().setName(new PolyStringType(name)); final OperationResult result = new OperationResult("Concurrency Test10"); String oid = repositoryService.addObject(user, null, result); repositoryService.searchObjectsIterative(UserType.class, QueryBuilder.queryFor(UserType.class, prismContext) .item(UserType.F_NAME).eqPoly(name).matchingOrig().build(), new ResultHandler<UserType>() { @Override public boolean handle(PrismObject<UserType> object, OperationResult parentResult) { LOGGER.info("Handling " + object + "..."); ObjectDelta delta = ObjectDelta.createModificationReplaceProperty(UserType.class, object.getOid(), UserType.F_FULL_NAME, prismContext, new PolyString(newFullName)); try { repositoryService.modifyObject(UserType.class, object.getOid(), delta.getModifications(), parentResult); } catch (Exception e) { throw new RuntimeException("Exception in handle method", e); } return true; } }, null, false, result); PrismObject<UserType> reloaded = repositoryService.getObject(UserType.class, oid, null, result); AssertJUnit.assertEquals("Full name was not changed", newFullName, reloaded.asObjectable().getFullName().getOrig()); } }