From cde3d1d741afc39f51cb78447b7a505e885edf69 Mon Sep 17 00:00:00 2001 From: Radovan Semancik Date: Thu, 22 Jun 2017 14:34:55 +0200 Subject: [PATCH] Fixing add operation for semi-manual resources (MID-4002) --- .../ResourceObjectIdentification.java | 8 + .../manual/AbstractManualResourceTest.java | 19 +- .../model/intest/manual/TestSemiManual.java | 252 +++++++++++++++++- .../src/test/resources/logback-test.xml | 6 +- .../resources/manual/resource-semi-manual.xml | 35 +++ .../impl/ObjectAlreadyExistHandler.java | 13 +- .../impl/ResourceObjectConverter.java | 47 +++- 7 files changed, 362 insertions(+), 18 deletions(-) diff --git a/infra/schema/src/main/java/com/evolveum/midpoint/schema/processor/ResourceObjectIdentification.java b/infra/schema/src/main/java/com/evolveum/midpoint/schema/processor/ResourceObjectIdentification.java index 60f5f030bf9..ed2c1c5d712 100644 --- a/infra/schema/src/main/java/com/evolveum/midpoint/schema/processor/ResourceObjectIdentification.java +++ b/infra/schema/src/main/java/com/evolveum/midpoint/schema/processor/ResourceObjectIdentification.java @@ -19,8 +19,11 @@ import java.util.ArrayList; import java.util.Collection; +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.schema.util.ShadowUtil; import com.evolveum.midpoint.util.PrettyPrinter; import com.evolveum.midpoint.util.exception.SchemaException; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowType; /** * @author semancik @@ -120,6 +123,11 @@ public static ResourceObjectIdentification createFromAttributes(ObjectClassCompl return new ResourceObjectIdentification(objectClassDefinition, primaryIdentifiers, secondaryIdentifiers); } + public static ResourceObjectIdentification createFromShadow(ObjectClassComplexTypeDefinition objectClassDefinition, + ShadowType shadowType) throws SchemaException { + return createFromAttributes(objectClassDefinition, ShadowUtil.getAttributes(shadowType)); + } + public void validatePrimaryIdenfiers() { if (!hasPrimaryIdentifiers()) { throw new IllegalStateException("No primary identifiers in " + this); diff --git a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/manual/AbstractManualResourceTest.java b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/manual/AbstractManualResourceTest.java index ac0c2d43429..d89ec99fe71 100644 --- a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/manual/AbstractManualResourceTest.java +++ b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/manual/AbstractManualResourceTest.java @@ -130,6 +130,9 @@ public abstract class AbstractManualResourceTest extends AbstractConfiguredModel protected static final String USER_WILL_PASSWORD_OLD = "3lizab3th"; protected static final String USER_WILL_PASSWORD_NEW = "ELIZAbeth"; + protected static final String ACCOUNT_JACK_DESCRIPTION_MANUAL = "Manuel"; + protected static final String USER_JACK_PASSWORD_OLD = "deadM3NtellN0tales"; + private static final File TASK_SHADOW_REFRESH_FILE = new File(TEST_DIR, "task-shadow-refresh.xml"); private static final String TASK_SHADOW_REFRESH_OID = "eb8f5be6-2b51-11e7-848c-2fd84a283b03"; @@ -2104,6 +2107,8 @@ public void test340RecomputeWillAfter25min() throws Exception { // TODO: let grace period expire without updating the backing store (semi-manual-only) + // Tests 7xx are in the subclasses + /** * The 8xx tests is similar routine as 1xx,2xx,3xx, but this time * with automatic updates using refresh task. @@ -2310,7 +2315,7 @@ private void assertAccountWillAfterAssign(final String TEST_NAME) throws Excepti assertCase(willLastCaseOid, SchemaConstants.CASE_STATE_OPEN); } - private void assertAccountJackAfterAssign(final String TEST_NAME) throws Exception { + protected void assertAccountJackAfterAssign(final String TEST_NAME) throws Exception { Task task = createTask(TEST_NAME); OperationResult result = task.getResult(); @@ -2382,18 +2387,18 @@ private void assertWillAfterCreateCaseClosed(final String TEST_NAME, boolean bac } - private void assertPendingOperationDeltas(PrismObject shadow, int expectedNumber) { + protected void assertPendingOperationDeltas(PrismObject shadow, int expectedNumber) { List pendingOperations = shadow.asObjectable().getPendingOperation(); assertEquals("Wroung number of pending operations in "+shadow, expectedNumber, pendingOperations.size()); } - private PendingOperationType assertSinglePendingOperation(PrismObject shadow, + protected PendingOperationType assertSinglePendingOperation(PrismObject shadow, XMLGregorianCalendar requestStart, XMLGregorianCalendar requestEnd) { return assertSinglePendingOperation(shadow, requestStart, requestEnd, OperationResultStatusType.IN_PROGRESS, null, null); } - private PendingOperationType assertSinglePendingOperation(PrismObject shadow, + protected PendingOperationType assertSinglePendingOperation(PrismObject shadow, XMLGregorianCalendar requestStart, XMLGregorianCalendar requestEnd, OperationResultStatusType expectedStatus, XMLGregorianCalendar completionStart, XMLGregorianCalendar completionEnd) { @@ -2402,14 +2407,14 @@ private PendingOperationType assertSinglePendingOperation(PrismObject shadow, PendingOperationType pendingOperation, XMLGregorianCalendar requestStart, XMLGregorianCalendar requestEnd) { return assertPendingOperation(shadow, pendingOperation, requestStart, requestEnd, OperationResultStatusType.IN_PROGRESS, null, null); } - private PendingOperationType assertPendingOperation( + protected PendingOperationType assertPendingOperation( PrismObject shadow, PendingOperationType pendingOperation, XMLGregorianCalendar requestStart, XMLGregorianCalendar requestEnd, OperationResultStatusType expectedStatus, @@ -2432,7 +2437,7 @@ private PendingOperationType assertPendingOperation( return pendingOperation; } - private PendingOperationType findPendingOperation(PrismObject shadow, + protected PendingOperationType findPendingOperation(PrismObject shadow, OperationResultStatusType expectedResult, ItemPath itemPath) { List pendingOperations = shadow.asObjectable().getPendingOperation(); for (PendingOperationType pendingOperation: pendingOperations) { diff --git a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/manual/TestSemiManual.java b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/manual/TestSemiManual.java index 4197ad420f4..1525223ff16 100644 --- a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/manual/TestSemiManual.java +++ b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/manual/TestSemiManual.java @@ -19,7 +19,9 @@ */ package com.evolveum.midpoint.model.intest.manual; +import static org.testng.AssertJUnit.assertEquals; import static org.testng.AssertJUnit.assertNotNull; +import static org.testng.AssertJUnit.assertNull; import java.io.File; import java.io.IOException; @@ -34,14 +36,20 @@ import javax.xml.namespace.QName; import org.apache.commons.io.FileUtils; +import org.apache.commons.lang.StringUtils; 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.Listeners; +import org.testng.annotations.Test; import org.w3c.dom.Element; import com.evolveum.midpoint.prism.PrismObject; import com.evolveum.midpoint.prism.PrismProperty; +import com.evolveum.midpoint.schema.GetOperationOptions; +import com.evolveum.midpoint.schema.PointInTimeType; +import com.evolveum.midpoint.schema.SelectorOptions; import com.evolveum.midpoint.schema.constants.MidPointConstants; import com.evolveum.midpoint.schema.constants.SchemaConstants; import com.evolveum.midpoint.schema.result.OperationResult; @@ -49,7 +57,11 @@ import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; import com.evolveum.midpoint.xml.ns._public.common.common_3.ActivationStatusType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.OperationResultStatusType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.PendingOperationType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowKindType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.UserType; import com.evolveum.prism.xml.ns._public.types_3.PolyStringType; /** @@ -57,6 +69,7 @@ */ @ContextConfiguration(locations = {"classpath:ctx-model-intest-test-main.xml"}) @DirtiesContext(classMode = ClassMode.AFTER_CLASS) +@Listeners({ com.evolveum.midpoint.tools.testng.AlphabeticalMethodInterceptor.class }) public class TestSemiManual extends AbstractManualResourceTest { private static final File CSV_SOURCE_FILE = new File(TEST_DIR, "semi-manual.csv"); @@ -98,6 +111,225 @@ protected void assertResourceSchemaBeforeTest(Element resourceXsdSchemaElementBe protected int getNumberOfAccountAttributeDefinitions() { return 5; } + + /** + * MID-4002 + */ + @Test + public void test700AssignAccountJackExisting() throws Exception { + final String TEST_NAME = "test700AssignAccountJack"; + displayTestTile(TEST_NAME); + // GIVEN + Task task = createTask(TEST_NAME); + OperationResult result = task.getResult(); + + if (accountJackOid != null) { + PrismObject shadowRepoBefore = repositoryService.getObject(ShadowType.class, accountJackOid, null, result); + display("Repo shadow before", shadowRepoBefore); + assertPendingOperationDeltas(shadowRepoBefore, 0); + } + + backingStoreAddJack(); + + clock.overrideDuration("PT5M"); + + accountJackReqestTimestampStart = clock.currentTimeXMLGregorianCalendar(); + + // WHEN + displayWhen(TEST_NAME); + assignAccount(USER_JACK_OID, getResourceOid(), null, task, result); + + // THEN + displayThen(TEST_NAME); + display("result", result); + assertSuccess(result); + assertNull("Unexpected ticket in result", result.getAsynchronousOperationReference()); + + accountJackReqestTimestampEnd = clock.currentTimeXMLGregorianCalendar(); + + PrismObject userAfter = getUser(USER_JACK_OID); + display("User after", userAfter); + accountJackOid = getSingleLinkOid(userAfter); + + PrismObject shadowRepo = repositoryService.getObject(ShadowType.class, accountJackOid, null, result); + display("Repo shadow", shadowRepo); + assertPendingOperationDeltas(shadowRepo, 0); + assertShadowExists(shadowRepo, true); + assertNoShadowPassword(shadowRepo); + + PrismObject shadowModel = modelService.getObject(ShadowType.class, + accountJackOid, null, task, result); + + display("Model shadow", shadowModel); + ShadowType shadowTypeProvisioning = shadowModel.asObjectable(); + assertShadowName(shadowModel, USER_JACK_USERNAME); + assertEquals("Wrong kind (provisioning)", ShadowKindType.ACCOUNT, shadowTypeProvisioning.getKind()); + assertAttribute(shadowModel, ATTR_USERNAME_QNAME, USER_JACK_USERNAME); + assertAttribute(shadowModel, ATTR_FULLNAME_QNAME, USER_JACK_FULL_NAME); + assertShadowActivationAdministrativeStatus(shadowModel, ActivationStatusType.ENABLED); + assertShadowExists(shadowModel, true); + + assertPendingOperationDeltas(shadowModel, 0); + } + + /** + * MID-4002 + */ + @Test + public void test710UnassignAccountJack() throws Exception { + final String TEST_NAME = "test710UnassignAccountJack"; + displayTestTile(TEST_NAME); + // GIVEN + Task task = createTask(TEST_NAME); + OperationResult result = task.getResult(); + + clock.overrideDuration("PT5M"); + + accountJackReqestTimestampStart = clock.currentTimeXMLGregorianCalendar(); + + // WHEN + displayWhen(TEST_NAME); + unassignAccount(USER_JACK_OID, getResourceOid(), null, task, result); + + // THEN + displayThen(TEST_NAME); + display("result", result); + jackLastCaseOid = assertInProgress(result); + + PrismObject userAfter = getUser(USER_JACK_OID); + display("User after", userAfter); + accountJackOid = getSingleLinkOid(userAfter); + + accountJackReqestTimestampEnd = clock.currentTimeXMLGregorianCalendar(); + + PrismObject shadowRepo = repositoryService.getObject(ShadowType.class, accountJackOid, null, result); + display("Repo shadow", shadowRepo); + + assertPendingOperationDeltas(shadowRepo, 1); + PendingOperationType pendingOperation = findPendingOperation(shadowRepo, + OperationResultStatusType.IN_PROGRESS, null); + assertPendingOperation(shadowRepo, pendingOperation, accountJackReqestTimestampStart, accountJackReqestTimestampEnd); + assertNotNull("No ID in pending operation", pendingOperation.getId()); + + PrismObject shadowModel = modelService.getObject(ShadowType.class, + accountJackOid, null, task, result); + + display("Model shadow", shadowModel); + ShadowType shadowTypeProvisioning = shadowModel.asObjectable(); + assertShadowName(shadowModel, USER_JACK_USERNAME); + assertEquals("Wrong kind (provisioning)", ShadowKindType.ACCOUNT, shadowTypeProvisioning.getKind()); + assertShadowPassword(shadowModel); + + assertPendingOperationDeltas(shadowModel, 1); + pendingOperation = findPendingOperation(shadowModel, + OperationResultStatusType.IN_PROGRESS, null); + assertPendingOperation(shadowModel, pendingOperation, accountJackReqestTimestampStart, accountJackReqestTimestampEnd); + + PrismObject shadowModelFuture = modelService.getObject(ShadowType.class, + accountJackOid, + SelectorOptions.createCollection(GetOperationOptions.createPointInTimeType(PointInTimeType.FUTURE)), + task, result); + display("Model shadow (future)", shadowModelFuture); + assertShadowName(shadowModelFuture, USER_JACK_USERNAME); + assertShadowDead(shadowModelFuture); + assertShadowPassword(shadowModelFuture); + + assertCase(jackLastCaseOid, SchemaConstants.CASE_STATE_OPEN); + } + + /** + * MID-4002 + */ + @Test + public void test712CloseCaseAndRecomputeJack() throws Exception { + final String TEST_NAME = "test712CloseCaseAndRecomputeJack"; + displayTestTile(TEST_NAME); + // GIVEN + Task task = createTask(TEST_NAME); + OperationResult result = task.getResult(); + + backingStoreDeleteJack(); + + closeCase(jackLastCaseOid); + + accountJackCompletionTimestampStart = clock.currentTimeXMLGregorianCalendar(); + + // WHEN + displayWhen(TEST_NAME); + // We need reconcile and not recompute here. We need to fetch the updated case status. + reconcileUser(USER_JACK_OID, task, result); + + // THEN + displayThen(TEST_NAME); + display("result", result); + assertSuccess(result); + + accountJackCompletionTimestampEnd = clock.currentTimeXMLGregorianCalendar(); + + PrismObject shadowRepo = repositoryService.getObject(ShadowType.class, accountJackOid, null, result); + display("Repo shadow", shadowRepo); + assertSinglePendingOperation(shadowRepo, + accountJackReqestTimestampStart, accountJackReqestTimestampEnd, + OperationResultStatusType.SUCCESS, + accountJackCompletionTimestampStart, accountJackCompletionTimestampEnd); + assertShadowDead(shadowRepo); + + PrismObject shadowModel = modelService.getObject(ShadowType.class, + accountJackOid, null, task, result); + + display("Model shadow", shadowModel); + ShadowType shadowTypeModel = shadowModel.asObjectable(); + assertShadowName(shadowModel, USER_JACK_USERNAME); + assertEquals("Wrong kind (model)", ShadowKindType.ACCOUNT, shadowTypeModel.getKind()); + assertShadowDead(shadowModel); + + PendingOperationType pendingOperation = assertSinglePendingOperation(shadowModel, + accountJackReqestTimestampStart, accountJackReqestTimestampEnd, + OperationResultStatusType.SUCCESS, + accountJackCompletionTimestampStart, accountJackCompletionTimestampEnd); + + PrismObject shadowModelFuture = modelService.getObject(ShadowType.class, + accountJackOid, + SelectorOptions.createCollection(GetOperationOptions.createPointInTimeType(PointInTimeType.FUTURE)), + task, result); + display("Model shadow (future)", shadowModelFuture); + assertShadowName(shadowModelFuture, USER_JACK_USERNAME); + assertShadowDead(shadowModelFuture); + + assertCase(jackLastCaseOid, SchemaConstants.CASE_STATE_CLOSED); + } + + /** + * MID-4002 + */ + @Test + public void test719RecomputeJackAfter30min() throws Exception { + final String TEST_NAME = "test719RecomputeJackAfter30min"; + displayTestTile(TEST_NAME); + // GIVEN + Task task = createTask(TEST_NAME); + OperationResult result = task.getResult(); + + clock.overrideDuration("PT30M"); + + // WHEN + displayWhen(TEST_NAME); + // We need reconcile and not recompute here. We need to fetch the updated case status. + reconcileUser(USER_JACK_OID, task, result); + + // THEN + displayThen(TEST_NAME); + display("result", result); + assertSuccess(result); + + PrismObject userAfter = getUser(USER_JACK_OID); + display("User after", userAfter); + assertLinks(userAfter, 0); + assertNoShadow(accountJackOid); + + assertCase(jackLastCaseOid, SchemaConstants.CASE_STATE_CLOSED); + } + @Override protected void backingStoreAddWill() throws IOException { @@ -119,9 +351,17 @@ protected void backingStoreUpdateWill(String newFullName, ActivationStatusType n protected void backingStoreDeleteWill() throws IOException { deleteInCsv(USER_WILL_NAME); } + + protected void backingStoreAddJack() throws IOException { + appendToCsv(new String[]{USER_JACK_USERNAME, USER_JACK_FULL_NAME, ACCOUNT_JACK_DESCRIPTION_MANUAL, "", "false", USER_JACK_PASSWORD_OLD}); + } + + protected void backingStoreDeleteJack() throws IOException { + deleteInCsv(USER_JACK_USERNAME); + } private void appendToCsv(String[] data) throws IOException { - String line = formatCsvLine(data) + "\n"; + String line = formatCsvLine(data); Files.write(Paths.get(CSV_TARGET_FILE.getPath()), line.getBytes(), StandardOpenOption.APPEND); } @@ -134,7 +374,8 @@ private void replaceInCsv(String[] data) throws IOException { lines.set(i, formatCsvLine(data)); } } - Files.write(Paths.get(CSV_TARGET_FILE.getPath()), lines, StandardOpenOption.WRITE); + Files.write(Paths.get(CSV_TARGET_FILE.getPath()), lines, + StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING); } private void deleteInCsv(String username) throws IOException { @@ -147,12 +388,17 @@ private void deleteInCsv(String username) throws IOException { iterator.remove(); } } - Files.write(Paths.get(CSV_TARGET_FILE.getPath()), lines, StandardOpenOption.WRITE); + Files.write(Paths.get(CSV_TARGET_FILE.getPath()), lines, + StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING); } private String formatCsvLine(String[] data) { return Arrays.stream(data).map(s -> "\""+s+"\"").collect(Collectors.joining(",")); } + + protected String dumpCsv() throws IOException { + return StringUtils.join(Files.readAllLines(Paths.get(CSV_TARGET_FILE.getPath())), "\n"); + } @Override protected void assertShadowPassword(PrismObject shadow) { diff --git a/model/model-intest/src/test/resources/logback-test.xml b/model/model-intest/src/test/resources/logback-test.xml index 4ce5071bb16..2e850f2138e 100644 --- a/model/model-intest/src/test/resources/logback-test.xml +++ b/model/model-intest/src/test/resources/logback-test.xml @@ -77,9 +77,9 @@ - + - + @@ -94,7 +94,7 @@ - + diff --git a/model/model-intest/src/test/resources/manual/resource-semi-manual.xml b/model/model-intest/src/test/resources/manual/resource-semi-manual.xml index 58182ad0d5e..f5a54afcab3 100644 --- a/model/model-intest/src/test/resources/manual/resource-semi-manual.xml +++ b/model/model-intest/src/test/resources/manual/resource-semi-manual.xml @@ -134,4 +134,39 @@ PT15M + + + ri:AccountObjectClass + account + default + true + + + name + + $projection/attributes/username + + + + + linked + true + + + deleted + true + + http://midpoint.evolveum.com/xml/ns/public/model/action-3#unlink + + + + unlinked + true + + http://midpoint.evolveum.com/xml/ns/public/model/action-3#link + + + + + diff --git a/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/consistency/impl/ObjectAlreadyExistHandler.java b/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/consistency/impl/ObjectAlreadyExistHandler.java index 8a585a92d63..4a719b2fbd0 100644 --- a/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/consistency/impl/ObjectAlreadyExistHandler.java +++ b/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/consistency/impl/ObjectAlreadyExistHandler.java @@ -132,12 +132,17 @@ public T handleError(T shadow, FailedOperation op, Except private ObjectQuery createQueryByIcfName(ShadowType shadow) throws SchemaException { // TODO: error handling TODO TODO TODO set matching rule instead of null in equlas filter Collection> secondaryIdentifiers = ShadowUtil.getSecondaryIdentifiers(shadow); - S_AtomicFilterEntry q = QueryBuilder.queryFor(ShadowType.class, prismContext); - // secondary identifiers connected by 'or' clause q = q.block(); - for (ResourceAttribute secondaryIdentifier : secondaryIdentifiers) { - q = q.itemAs(secondaryIdentifier).or(); + if (secondaryIdentifiers.isEmpty()) { + for (ResourceAttribute primaryIdentifier: ShadowUtil.getPrimaryIdentifiers(shadow)) { + q = q.itemAs(primaryIdentifier).or(); + } + } else { + // secondary identifiers connected by 'or' clause + for (ResourceAttribute secondaryIdentifier : secondaryIdentifiers) { + q = q.itemAs(secondaryIdentifier).or(); + } } q = q.none().endBlock().and(); // resource + object class diff --git a/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ResourceObjectConverter.java b/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ResourceObjectConverter.java index da21d74f87f..d9e093268b9 100644 --- a/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ResourceObjectConverter.java +++ b/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ResourceObjectConverter.java @@ -261,7 +261,9 @@ public AsynchronousOperationReturnValue> addResourceObje result.recordFatalError(e); throw e; } - + + checkForAddConflicts(ctx, shadow, result); + Collection additionalOperations = new ArrayList(); addExecuteScriptOperation(additionalOperations, ProvisioningOperationTypeType.ADD, scripts, resource, result); @@ -318,6 +320,49 @@ public AsynchronousOperationReturnValue> addResourceObje return AsynchronousOperationReturnValue.wrap(shadow, result); } + /** + * Special case for multi-connectors (e.g. semi-manual connectors). There is a possibility that the object + * which we want to add is already present in the backing store. In case of manual provisioning the resource + * itself will not indicate "already exist" error. We have to explicitly check for that. + */ + private void checkForAddConflicts(ProvisioningContext ctx, PrismObject shadow, OperationResult result) throws ObjectAlreadyExistsException, SchemaException, CommunicationException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException, ObjectNotFoundException { + PrismObject existingObject = null; + ConnectorInstance readConnector = null; + try { + ConnectorInstance createConnector = ctx.getConnector(CreateCapabilityType.class, result); + readConnector = ctx.getConnector(ReadCapabilityType.class, result); + if (readConnector == createConnector) { + // Same connector for reading and creating. We assume that the connector can check uniqueness itself. + // No need to check explicitly. We will gladly skip the check, as the check may be additional overhead + // that we normally do not need or want. + return; + } + ResourceObjectIdentification identification = ResourceObjectIdentification.createFromShadow(ctx.getObjectClassDefinition(), shadow.asObjectable()); + + existingObject = readConnector.fetchObject(ShadowType.class, identification, null, ctx, result); + } catch (ObjectNotFoundException e) { + // This is OK + result.muteLastSubresultError(); + return; + } catch (CommunicationException ex) { + result.recordFatalError( + "Could not create object on the resource. Error communicating with the connector " + readConnector + ": " + ex.getMessage(), ex); + throw new CommunicationException("Error communicating with the connector " + readConnector + ": " + + ex.getMessage(), ex); + } catch (GenericFrameworkException ex) { + result.recordFatalError("Could not create object on the resource. Generic error in connector: " + ex.getMessage(), ex); + throw new GenericConnectorException("Generic error in connector: " + ex.getMessage(), ex); + } catch (Throwable e){ + result.recordFatalError(e); + throw e; + } + if (existingObject != null) { + ObjectAlreadyExistsException e = new ObjectAlreadyExistsException("Object "+shadow+" already exists in the backing store of resource "+ctx.getResource()); + result.recordFatalError(e); + throw e; + } + } + public AsynchronousOperationResult deleteResourceObject(ProvisioningContext ctx, PrismObject shadow, OperationProvisioningScriptsType scripts, OperationResult parentResult) throws ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException,