/*
* Copyright (c) 2010-2017 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.model.impl.lens;
import static com.evolveum.midpoint.test.IntegrationTestTools.display;
import static org.testng.AssertJUnit.assertEquals;
import static org.testng.AssertJUnit.assertFalse;
import static org.testng.AssertJUnit.assertNotNull;
import static org.testng.AssertJUnit.assertNull;
import static org.testng.AssertJUnit.assertTrue;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
import javax.xml.bind.JAXBException;
import javax.xml.datatype.XMLGregorianCalendar;
import javax.xml.namespace.QName;
import com.evolveum.midpoint.prism.PrismContext;
import com.evolveum.midpoint.schema.constants.SchemaConstants;
import com.evolveum.midpoint.schema.internals.InternalMonitor;
import com.evolveum.midpoint.xml.ns._public.common.common_3.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.annotation.DirtiesContext.ClassMode;
import org.springframework.test.context.ContextConfiguration;
import org.testng.AssertJUnit;
import org.testng.annotations.Test;
import com.evolveum.midpoint.model.api.context.ModelState;
import com.evolveum.midpoint.model.api.context.SynchronizationPolicyDecision;
import com.evolveum.midpoint.model.api.hooks.HookOperationMode;
import com.evolveum.midpoint.model.impl.lens.Clockwork;
import com.evolveum.midpoint.model.impl.lens.LensContext;
import com.evolveum.midpoint.model.impl.lens.LensObjectDeltaOperation;
import com.evolveum.midpoint.model.impl.lens.LensProjectionContext;
import com.evolveum.midpoint.model.impl.trigger.RecomputeTriggerHandler;
import com.evolveum.midpoint.prism.PrismContainer;
import com.evolveum.midpoint.prism.PrismObject;
import com.evolveum.midpoint.prism.PrismReference;
import com.evolveum.midpoint.prism.delta.ChangeType;
import com.evolveum.midpoint.prism.delta.ContainerDelta;
import com.evolveum.midpoint.prism.delta.ObjectDelta;
import com.evolveum.midpoint.prism.path.ItemPath;
import com.evolveum.midpoint.prism.util.PrismAsserts;
import com.evolveum.midpoint.prism.xml.XmlTypeConverter;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.schema.util.ResourceTypeUtil;
import com.evolveum.midpoint.schema.util.SchemaTestConstants;
import com.evolveum.midpoint.task.api.Task;
import com.evolveum.midpoint.task.api.TaskManager;
import com.evolveum.midpoint.test.util.TestUtil;
import com.evolveum.midpoint.util.exception.CommunicationException;
import com.evolveum.midpoint.util.exception.ConfigurationException;
import com.evolveum.midpoint.util.exception.ExpressionEvaluationException;
import com.evolveum.midpoint.util.exception.ObjectAlreadyExistsException;
import com.evolveum.midpoint.util.exception.ObjectNotFoundException;
import com.evolveum.midpoint.util.exception.PolicyViolationException;
import com.evolveum.midpoint.util.exception.SchemaException;
import com.evolveum.midpoint.util.exception.SecurityViolationException;
/**
* @author semancik
*
*/
@ContextConfiguration(locations = {"classpath:ctx-model-test-main.xml"})
@DirtiesContext(classMode = ClassMode.AFTER_CLASS)
public class TestClockwork extends AbstractLensTest {
@Autowired(required = true)
private Clockwork clockwork;
@Autowired(required = true)
private TaskManager taskManager;
public TestClockwork() throws JAXBException {
super();
}
@Override
public void initSystem(Task initTask, OperationResult initResult)
throws Exception {
super.initSystem(initTask, initResult);
assumeAssignmentPolicy(AssignmentPolicyEnforcementType.FULL);
InternalMonitor.reset();
InternalMonitor.setTraceShadowFetchOperation(true);
}
// tests specific bug dealing with preservation of null values in focus secondary deltas
@Test(enabled = true)
public void test010SerializeAddUserBarbossa() throws Exception {
final String TEST_NAME = "test010SerializeAddUserBarbossa";
TestUtil.displayTestTile(this, TEST_NAME);
// GIVEN
Task task = taskManager.createTaskInstance(TestClockwork.class.getName() + "." + TEST_NAME);
OperationResult result = task.getResult();
LensContext<UserType> context = createUserLensContext();
PrismObject<UserType> bill = prismContext.parseObject(USER_BARBOSSA_FILE);
fillContextWithAddUserDelta(context, bill);
// WHEN
clockwork.click(context, task, result); // one round - compute projections
System.out.println("Context before serialization = " + context.debugDump());
PrismContainer<LensContextType> lensContextType = context.toPrismContainer();
String xml = prismContext.xmlSerializer().serialize(lensContextType.getValue(), lensContextType.getElementName());
System.out.println("Serialized form = " + xml);
LensContextType unmarshalledContainer = prismContext.parserFor(xml).xml().parseRealValue(LensContextType.class);
LensContext context2 = LensContext.fromLensContextType(unmarshalledContainer, context.getPrismContext(), provisioningService, result);
System.out.println("Context after deserialization = " + context.debugDump());
// THEN
assertEquals("Secondary deltas are not preserved - their number differs", context.getFocusContext().getSecondaryDeltas().size(), context2.getFocusContext().getSecondaryDeltas().size());
for (int i = 0; i < context.getFocusContext().getSecondaryDeltas().size(); i++) {
assertEquals("Secondary delta #" + i + " is not preserved correctly", context.getFocusContext().getSecondaryDelta(i).debugDump(), context2.getFocusContext().getSecondaryDelta(i).debugDump());
}
}
@Test
public void test020AssignAccountToJackSync() throws Exception {
final String TEST_NAME = "test020AssignAccountToJackSync";
TestUtil.displayTestTile(this, TEST_NAME);
try {
// GIVEN
Task task = taskManager.createTaskInstance(TestClockwork.class.getName() + "." + TEST_NAME);
OperationResult result = task.getResult();
LensContext<UserType> context = createJackAssignAccountContext(result);
display("Input context", context);
assertFocusModificationSanity(context);
mockClockworkHook.reset();
mockClockworkHook.setRecord(true);
rememberShadowFetchOperationCount();
// WHEN
TestUtil.displayWhen(TEST_NAME);
clockwork.run(context, task, result);
// THEN
TestUtil.displayThen(TEST_NAME);
mockClockworkHook.setRecord(false);
display("Output context", context);
display("Hook contexts", mockClockworkHook);
assertShadowFetchOperationCountIncrement(0);
assertJackAssignAccountContext(context);
assertJackAccountShadow(context);
List<LensContext<?>> hookContexts = mockClockworkHook.getContexts();
assertFalse("No contexts recorded by the hook", hookContexts.isEmpty());
} finally {
mockClockworkHook.reset();
unassignJackAccount();
}
}
@Test
public void test030AssignAccountToJackAsyncNoserialize() throws Exception {
try {
assignAccountToJackAsync("test030AssignAccountToJackAsyncNoserialize", false);
} finally {
mockClockworkHook.reset();
unassignJackAccount();
}
}
@Test
public void test031AssignAccountToJackAsyncSerialize() throws Exception {
TestUtil.displayTestTile(this, "test031AssignAccountToJackAsyncSerialize");
try {
assignAccountToJackAsync("test031AssignAccountToJackAsyncSerialize", true);
} finally {
mockClockworkHook.reset();
unassignJackAccount();
}
}
/**
* User barbossa has a direct account assignment. This assignment has an expression for enabledisable flag.
* Let's disable user, the account should be disabled as well.
*/
@Test
public void test053ModifyUserBarbossaDisable() throws Exception {
TestUtil.displayTestTile(this, "test053ModifyUserBarbossaDisable");
// GIVEN
Task task = taskManager.createTaskInstance(TestProjector.class.getName() + ".test053ModifyUserBarbossaDisable");
OperationResult result = task.getResult();
LensContext<UserType> context = createUserLensContext();
fillContextWithUser(context, USER_BARBOSSA_OID, result);
fillContextWithAccount(context, ACCOUNT_HBARBOSSA_DUMMY_OID, result);
addModificationToContextReplaceUserProperty(context,
new ItemPath(UserType.F_ACTIVATION, ActivationType.F_ADMINISTRATIVE_STATUS),
ActivationStatusType.DISABLED);
context.recompute();
display("Input context", context);
assertFocusModificationSanity(context);
// WHEN
clockwork.run(context, task, result);
// THEN
display("Output context", context);
assertTrue(context.getFocusContext().getPrimaryDelta().getChangeType() == ChangeType.MODIFY);
assertSideEffectiveDeltasOnly(context.getFocusContext().getSecondaryDelta(), "user secondary delta",
ActivationStatusType.DISABLED);
assertFalse("No account changes", context.getProjectionContexts().isEmpty());
Collection<LensProjectionContext> accountContexts = context.getProjectionContexts();
assertEquals(1, accountContexts.size());
LensProjectionContext accContext = accountContexts.iterator().next();
assertNull(accContext.getPrimaryDelta());
assertEquals(SynchronizationPolicyDecision.KEEP,accContext.getSynchronizationPolicyDecision());
ObjectDelta<ShadowType> accountSecondaryDelta = accContext.getSecondaryDelta();
assertEquals(ChangeType.MODIFY, accountSecondaryDelta.getChangeType());
// originally here was 6 (TODO why now four?)
assertEquals("Unexpected number of account secondary changes", 4, accountSecondaryDelta.getModifications().size());
PrismAsserts.assertPropertyReplace(accountSecondaryDelta,
new ItemPath(ShadowType.F_ACTIVATION, ActivationType.F_ADMINISTRATIVE_STATUS),
ActivationStatusType.DISABLED);
PrismAsserts.assertPropertyReplace(accountSecondaryDelta, SchemaConstants.PATH_ACTIVATION_DISABLE_REASON,
SchemaConstants.MODEL_DISABLE_REASON_MAPPED);
ContainerDelta<TriggerType> triggerDelta = accountSecondaryDelta.findContainerDelta(ObjectType.F_TRIGGER);
assertNotNull("No trigger delta in account secondary delta", triggerDelta);
assertEquals("Wrong trigger delta size", 1, triggerDelta.getValuesToAdd().size());
TriggerType triggerType = triggerDelta.getValuesToAdd().iterator().next().asContainerable();
assertEquals("Wrong trigger URL", RecomputeTriggerHandler.HANDLER_URI, triggerType.getHandlerUri());
XMLGregorianCalendar start = clock.currentTimeXMLGregorianCalendar();
start.add(XmlTypeConverter.createDuration(true, 0, 0, 25, 0, 0, 0));
XMLGregorianCalendar end = clock.currentTimeXMLGregorianCalendar();
end.add(XmlTypeConverter.createDuration(true, 0, 0, 35, 0, 0, 0));
TestUtil.assertBetween("Wrong trigger timestamp", start, end, triggerType.getTimestamp());
}
private void assignAccountToJackAsync(String testName, boolean serialize) throws SchemaException, ObjectNotFoundException, JAXBException, PolicyViolationException, ExpressionEvaluationException, ObjectAlreadyExistsException, CommunicationException, ConfigurationException, SecurityViolationException, IOException, ClassNotFoundException {
TestUtil.displayTestTile(this, testName);
// GIVEN
Task task = taskManager.createTaskInstance(TestClockwork.class.getName() + "."+testName);
OperationResult result = task.getResult();
LensContext<UserType> context = createJackAssignAccountContext(result);
// display("Input context", context);
assertFocusModificationSanity(context);
mockClockworkHook.reset();
mockClockworkHook.setRecord(true);
mockClockworkHook.setAsynchronous(true);
rememberShadowFetchOperationCount();
// WHEN
while(context.getState() != ModelState.FINAL) {
System.out.println("CLICK START");
HookOperationMode mode = clockwork.click(context, task, result);
System.out.println("CLICK END");
assertTrue("Unexpected INITIAL state of the context", context.getState() != ModelState.INITIAL);
assertEquals("Wrong mode after click in "+context.getState(), HookOperationMode.BACKGROUND, mode);
assertShadowFetchOperationCountIncrement(0);
if (serialize) {
System.out.println("Context before serialization = " + context.debugDump());
PrismContainer<LensContextType> lensContextType = context.toPrismContainer();
String xml = prismContext.xmlSerializer().serialize(lensContextType.getValue(), lensContextType.getElementName());
System.out.println("Serialized form = " + xml);
LensContextType unmarshalledContainer = prismContext.parserFor(xml).xml().parseRealValue(LensContextType.class);
context = LensContext.fromLensContextType(unmarshalledContainer, context.getPrismContext(), provisioningService, result);
System.out.println("Context after deserialization = " + context.debugDump());
context.checkConsistence();
}
}
// THEN
mockClockworkHook.setRecord(false);
// display("Output context", context);
// display("Hook contexts", mockClockworkHook);
assertShadowFetchOperationCountIncrement(0);
assertJackAssignAccountContext(context);
assertJackAccountShadow(context);
}
private void assertJackAssignAccountContext(LensContext<UserType> context) {
assertTrue(context.getFocusContext().getPrimaryDelta().getChangeType() == ChangeType.MODIFY);
if (context.getFocusContext().getSecondaryDelta() != null) {
assertSideEffectiveDeltasOnly(context.getFocusContext().getSecondaryDelta(), "user secondary delta",
ActivationStatusType.ENABLED);
}
assertFalse("No account changes", context.getProjectionContexts().isEmpty());
Collection<LensProjectionContext> accountContexts = context.getProjectionContexts();
assertEquals(1, accountContexts.size());
LensProjectionContext accContext = accountContexts.iterator().next();
assertNull("Account primary delta sneaked in", accContext.getPrimaryDelta());
assertEquals(SynchronizationPolicyDecision.KEEP, accContext.getSynchronizationPolicyDecision());
ObjectDelta<?> executedDelta = getExecutedDelta(accContext);
assertNotNull("No executed delta in "+accContext, executedDelta);
assertEquals(ChangeType.ADD, executedDelta.getChangeType());
PrismAsserts.assertPropertyAdd(executedDelta, getIcfsNameAttributePath(), "jack");
PrismAsserts.assertPropertyAdd(executedDelta, getDummyResourceController().getAttributeFullnamePath(), "Jack Sparrow");
}
private ObjectDelta<?> getExecutedDelta(LensProjectionContext ctx) {
List<LensObjectDeltaOperation<ShadowType>> executedDeltas = ctx.getExecutedDeltas();
if (executedDeltas.isEmpty()) {
return null;
}
if (executedDeltas.size() > 1) {
AssertJUnit.fail("More than one executed delta in "+ctx);
}
return executedDeltas.get(0).getObjectDelta();
}
private void assertJackAccountShadow(LensContext<UserType> context) throws ObjectNotFoundException, SchemaException, SecurityViolationException, CommunicationException, ConfigurationException {
Collection<LensProjectionContext> accountContexts = context.getProjectionContexts();
assertEquals(1, accountContexts.size());
LensProjectionContext accContext = accountContexts.iterator().next();
String accountOid = accContext.getOid();
assertNotNull("No OID in account context "+accContext);
PrismObject<ShadowType> newAccount = getShadowModel(accountOid);
assertEquals(DEFAULT_INTENT, newAccount.findProperty(ShadowType.F_INTENT).getRealValue());
assertEquals(new QName(ResourceTypeUtil.getResourceNamespace(getDummyResourceType()), "AccountObjectClass"),
newAccount.findProperty(ShadowType.F_OBJECT_CLASS).getRealValue());
PrismReference resourceRef = newAccount.findReference(ShadowType.F_RESOURCE_REF);
assertEquals(getDummyResourceType().getOid(), resourceRef.getOid());
PrismContainer<?> attributes = newAccount.findContainer(ShadowType.F_ATTRIBUTES);
assertEquals("jack", attributes.findProperty(SchemaTestConstants.ICFS_NAME).getRealValue());
assertEquals("Jack Sparrow", attributes.findProperty(new QName(ResourceTypeUtil.getResourceNamespace(getDummyResourceType()), "fullname")).getRealValue());
}
private LensContext<UserType> createJackAssignAccountContext(OperationResult result) throws SchemaException, ObjectNotFoundException, IOException, JAXBException {
LensContext<UserType> context = createUserLensContext();
fillContextWithUser(context, USER_JACK_OID, result);
addFocusModificationToContext(context, REQ_USER_JACK_MODIFY_ADD_ASSIGNMENT_ACCOUNT_DUMMY);
return context;
}
private void unassignJackAccount() throws SchemaException, ObjectNotFoundException, IOException, JAXBException, PolicyViolationException, ExpressionEvaluationException, ObjectAlreadyExistsException, CommunicationException, ConfigurationException, SecurityViolationException {
Task task = taskManager.createTaskInstance(TestClockwork.class.getName() + ".unassignJackAccount");
LensContext<UserType> context = createUserLensContext();
OperationResult result = task.getResult();
fillContextWithUser(context, USER_JACK_OID, result);
addFocusModificationToContext(context, REQ_USER_JACK_MODIFY_DELETE_ASSIGNMENT_ACCOUNT_DUMMY);
clockwork.run(context, task, result);
}
}