Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
import com.evolveum.midpoint.prism.PrismContext;
import com.evolveum.midpoint.prism.PrismObject;
import com.evolveum.midpoint.prism.delta.ItemDelta;
import com.evolveum.midpoint.prism.query.ObjectFilter;
import com.evolveum.midpoint.prism.query.ObjectQuery;
import com.evolveum.midpoint.repo.api.RepositoryService;
import com.evolveum.midpoint.repo.api.CacheRegistry;
Expand Down Expand Up @@ -261,10 +262,11 @@ private static boolean doesNeedReconfiguration(
ConfiguredConnectorInstanceEntry getOrCreateConnectorInstanceCacheEntry(ConnectorSpec connectorSpec, OperationResult result)
throws ObjectNotFoundException, SchemaException, ConfigurationException {
ConfiguredConnectorCacheKey cacheKey = connectorSpec.getCacheKey();
String connectorOid = resolveConnectorOidRequired(connectorSpec, result);
ConfiguredConnectorInstanceEntry existingCacheEntry = connectorInstanceCache.get(cacheKey);

if (existingCacheEntry != null) {
if (existingCacheEntry.matchesConnectorOid(connectorSpec.getConnectorOidRequired())) {
if (existingCacheEntry.matchesConnectorOid(connectorOid)) {
LOGGER.trace("HIT in connector cache: returning configured connector {} from cache; it may or may not be fresh",
connectorSpec);
return existingCacheEntry;
Expand All @@ -282,7 +284,7 @@ ConfiguredConnectorInstanceEntry getOrCreateConnectorInstanceCacheEntry(Connecto

// No usable connector in cache. Let's create it - unconfigured.
return new ConfiguredConnectorInstanceEntry(
connectorSpec.getConnectorOidRequired(),
connectorOid,
createConnectorInstance(connectorSpec, result));
}

Expand Down Expand Up @@ -383,12 +385,51 @@ void configureAndInitializeConnectorInstance(
// Currently, we need to throw a ConfigurationException here for the test to pass.
// E.g., IllegalStateException won't work.
return getConnectorWithSchema(
MiscUtil.configNonNull(
connectorSpec.getConnectorOid(),
"Connector OID missing in %s", connectorSpec),
resolveConnectorOidRequired(connectorSpec, result),
result);
}

/**
* Returns the connector OID for the given specification, resolving a filter-based {@code connectorRef} if needed.
*
* If the reference is resolved from a filter, the resolved OID is stored in the {@link ConnectorSpec}
* as transient state. This is intentional internal normalization: later completion/cache code reads
* the OID from the same specification, but the resource {@code connectorRef} is not modified and no
* repository delta is created for this change.
*/
private @NotNull String resolveConnectorOidRequired(ConnectorSpec connectorSpec, OperationResult result)
throws SchemaException, ConfigurationException {
String oid = connectorSpec.getConnectorOid();
if (oid != null) {
return oid;
}

ObjectReferenceType connectorRef = connectorSpec.getConnectorRef();
if (connectorRef == null || connectorRef.getFilter() == null) {
return MiscUtil.configNonNull(oid, "Connector OID missing in %s", connectorSpec);
}

ObjectFilter filter = prismContext.getQueryConverter().parseFilter(connectorRef.getFilter(), ConnectorType.class);
ObjectQuery query = prismContext.queryFactory().createQuery(filter);
SearchResultList<PrismObject<ConnectorType>> connectors =
repositoryService.searchObjects(ConnectorType.class, query, readOnly(), result);

if (connectors.isEmpty()) {
throw new ConfigurationException(
"Connector reference in " + connectorSpec + " cannot be resolved: filter matches no object");
}
if (connectors.size() > 1) {
throw new ConfigurationException(
"Connector reference in " + connectorSpec + " cannot be resolved: filter matches "
+ connectors.size() + " objects");
}

String resolvedOid = connectors.get(0).getOid();
connectorSpec.setResolvedConnectorOid(resolvedOid);
LOGGER.trace("Resolved connector reference in {} to OID {}", connectorSpec, resolvedOid);
return resolvedOid;
}

private @NotNull ConnectorWithSchema getConnectorWithSchema(String connOid, OperationResult result)
throws ObjectNotFoundException, SchemaException {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
public abstract class ConnectorSpec {

@NotNull protected final ResourceType resource;
@Nullable private String resolvedConnectorOid;

private ConnectorSpec(@NotNull ResourceType resource) {
this.resource = resource;
Expand Down Expand Up @@ -134,7 +135,18 @@ private ConnectorSpec(@NotNull ResourceType resource) {
* Note that connector OID is not required here, as the resource may be not resolved yet, or it may be
* an abstract resource with missing connectorRef.
*/
public abstract @Nullable String getConnectorOid();
public final @Nullable String getConnectorOid() {
return resolvedConnectorOid != null ? resolvedConnectorOid : getConnectorOidFromDefinition();
}

protected abstract @Nullable String getConnectorOidFromDefinition();

void setResolvedConnectorOid(@NotNull String resolvedConnectorOid) {
this.resolvedConnectorOid = resolvedConnectorOid;
}

/** Returns the backing connector reference, if present. */
abstract @Nullable ObjectReferenceType getConnectorRef();

/**
* To be used when we are sure to deal with fully expanded, non-abstract resources.
Expand Down Expand Up @@ -202,10 +214,15 @@ public ItemPath getBasePath() {
}

@Override
public @Nullable String getConnectorOid() {
protected @Nullable String getConnectorOidFromDefinition() {
return ResourceTypeUtil.getConnectorOid(resource);
}

@Override
@Nullable ObjectReferenceType getConnectorRef() {
return resource.getConnectorRef();
}

@Override
public @Nullable PrismContainer<ConnectorConfigurationType> getConnectorConfigurationContainer() {
return resource.asPrismObject().findContainer(ResourceType.F_CONNECTOR_CONFIGURATION);
Expand Down Expand Up @@ -272,10 +289,15 @@ public ItemPath getBasePath() {
}

@Override
public @Nullable String getConnectorOid() {
protected @Nullable String getConnectorOidFromDefinition() {
return getOid(definitionBean.getConnectorRef());
}

@Override
@Nullable ObjectReferenceType getConnectorRef() {
return definitionBean.getConnectorRef();
}

@Override
public @Nullable PrismContainer<ConnectorConfigurationType> getConnectorConfigurationContainer() {
//noinspection unchecked
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@

import com.evolveum.midpoint.prism.*;
import com.evolveum.midpoint.prism.delta.PropertyDelta;
import com.evolveum.midpoint.prism.query.ObjectFilter;
import com.evolveum.midpoint.prism.util.PrismAsserts;
import com.evolveum.midpoint.prism.util.PrismTestUtil;
import com.evolveum.midpoint.provisioning.impl.AbstractProvisioningIntegrationTest;
Expand All @@ -41,6 +42,7 @@
import com.evolveum.midpoint.task.api.Task;
import com.evolveum.midpoint.test.IntegrationTestTools;
import com.evolveum.midpoint.test.util.TestUtil;
import com.evolveum.midpoint.util.exception.ConfigurationException;
import com.evolveum.midpoint.xml.ns._public.common.common_3.*;
import com.evolveum.midpoint.xml.ns._public.resource.capabilities_3.ActivationCapabilityType;
import com.evolveum.midpoint.xml.ns._public.resource.capabilities_3.ScriptCapabilityHostType;
Expand Down Expand Up @@ -253,6 +255,200 @@ public void test006Capabilities() throws Exception {
dumpResourceCapabilities(resource);
}

/**
* Verifies that filter-based connector references work consistently for both the primary connector and
* an additional connector, while direct-OID additional connectors continue to work.
*/
@Test
public void test007PrimaryAndAdditionalConnectorRefsByFilter() throws Exception {
Task task = getTestTask();
OperationResult result = task.getResult();

PrismObject<ResourceType> sourceResource =
repositoryService.getObject(ResourceType.class, getResourceOid(), null, result);
String connectorOid = sourceResource.asObjectable().getConnectorRef().getOid();
ObjectReferenceType connectorByFilter = createCsvConnectorRefByFilter();
String resourceOid = getConnectorRefTestResourceOid("cf0");

PrismObject<ResourceType> resourceToAdd = cloneResourceForConnectorRefTest(
sourceResource,
resourceOid,
"Test CSV: filtered connectors");
ResourceType resourceBean = resourceToAdd.asObjectable();
resourceBean.setConnectorRef(connectorByFilter.clone());
resourceBean.getAdditionalConnector().clear();
resourceBean.getAdditionalConnector().add(
new ConnectorInstanceSpecificationType()
.name("csv-additional-filtered")
.connectorRef(connectorByFilter.clone()));
resourceBean.getAdditionalConnector().add(
new ConnectorInstanceSpecificationType()
.name("csv-additional-direct")
.connectorRef(new ObjectReferenceType()
.oid(connectorOid)
.type(ConnectorType.COMPLEX_TYPE)));

repositoryService.addObject(resourceToAdd, null, result);

when("the resource with connector refs by filter is retrieved through provisioning");
PrismObject<ResourceType> resourceAfter = provisioningService.getObject(
ResourceType.class, resourceOid, null, task, result);

then("all connector refs are usable");
result.computeStatus();
TestUtil.assertSuccess(result);
ResourceType resourceAfterBean = resourceAfter.asObjectable();
assertNull("Primary connector OID resolution should not be visible in the resource object",
resourceAfterBean.getConnectorRef().getOid());
assertEquals("Wrong filtered additional connector name", "csv-additional-filtered",
resourceAfterBean.getAdditionalConnector().get(0).getName());
assertNull("Filtered additional connector OID resolution should not be visible in the resource object",
resourceAfterBean.getAdditionalConnector().get(0).getConnectorRef().getOid());
assertEquals("Wrong direct additional connector name", "csv-additional-direct",
resourceAfterBean.getAdditionalConnector().get(1).getName());
assertEquals("Wrong direct additional connector OID", connectorOid,
resourceAfterBean.getAdditionalConnector().get(1).getConnectorRef().getOid());

PrismObject<ResourceType> resourceRepoAfter = repositoryService.getObject(ResourceType.class, resourceOid, null, result);
ResourceType resourceRepoAfterBean = resourceRepoAfter.asObjectable();
assertNull("Primary connector OID resolution should not be persisted",
resourceRepoAfterBean.getConnectorRef().getOid());
assertNull("Additional connector OID resolution should not be persisted",
resourceRepoAfterBean.getAdditionalConnector().get(0).getConnectorRef().getOid());
}

/**
* Verifies that a filter-based additional connector reference that matches no connector reports a clear
* resolution error, instead of falling through to the old "Connector OID missing" failure.
*/
@Test
public void test008AdditionalConnectorRefByFilterMatchesNone() throws Exception {
Task task = getTestTask();
OperationResult result = task.getResult();
String resourceOid = getConnectorRefTestResourceOid("cf1");

PrismObject<ResourceType> resourceToAdd = cloneResourceForConnectorRefTest(
repositoryService.getObject(ResourceType.class, getResourceOid(), null, result),
resourceOid,
"Test CSV: no connector match");
ResourceType resourceBean = resourceToAdd.asObjectable();
resourceBean.getAdditionalConnector().clear();
resourceBean.getAdditionalConnector().add(
new ConnectorInstanceSpecificationType()
.name("csv-additional")
.connectorRef(createConnectorRefByConnectorType("no.such.Connector")));
repositoryService.addObject(resourceToAdd, null, result);

when("the resource with an unresolvable additional connector filter is retrieved through provisioning");
String failureText = getResourceExpectingConnectorResolutionFailure(resourceOid, task, result);

then("the failure reason is clear");
assertTextContains(failureText, "Connector reference in ConnectorSpec.Additional");
assertTextContains(failureText, "cannot be resolved: filter matches no object");
assertTextDoesNotContain(failureText, "Connector OID missing in ConnectorSpec.Additional");
}

/**
* Verifies that an ambiguous filter-based additional connector reference reports a clear resolution
* error, instead of falling through to the old "Connector OID missing" failure.
*/
@Test
public void test009AdditionalConnectorRefByFilterMatchesMultiple() throws Exception {
Task task = getTestTask();
OperationResult result = task.getResult();
String resourceOid = getConnectorRefTestResourceOid("cf2");

assertAllConnectorsFilterIsAmbiguous(result);

PrismObject<ResourceType> resourceToAdd = cloneResourceForConnectorRefTest(
repositoryService.getObject(ResourceType.class, getResourceOid(), null, result),
resourceOid,
"Test CSV: multiple connector matches");
ResourceType resourceBean = resourceToAdd.asObjectable();
resourceBean.getAdditionalConnector().clear();
resourceBean.getAdditionalConnector().add(
new ConnectorInstanceSpecificationType()
.name("csv-additional")
.connectorRef(createAllConnectorsRef()));
repositoryService.addObject(resourceToAdd, null, result);

when("the resource with an ambiguous additional connector filter is retrieved through provisioning");
String failureText = getResourceExpectingConnectorResolutionFailure(resourceOid, task, result);

then("the failure reason is clear");
assertTextContains(failureText, "Connector reference in ConnectorSpec.Additional");
assertTextContains(failureText, "cannot be resolved: filter matches");
assertTextContains(failureText, "objects");
assertTextDoesNotContain(failureText, "Connector OID missing in ConnectorSpec.Additional");
}

private String getConnectorRefTestResourceOid(String suffix) {
String resourceOid = getResourceOid();
return resourceOid.substring(0, resourceOid.length() - suffix.length()) + suffix;
}

private PrismObject<ResourceType> cloneResourceForConnectorRefTest(
PrismObject<ResourceType> sourceResource, String oid, String name) {
PrismObject<ResourceType> resourceToAdd = sourceResource.clone();
ResourceType resourceBean = resourceToAdd.asObjectable();
resourceBean.setOid(oid);
resourceBean.setName(createPolyStringType(name));
return resourceToAdd;
}

private ObjectReferenceType createCsvConnectorRefByFilter() throws Exception {
return createConnectorRefByConnectorType(CSV_CONNECTOR_TYPE);
}

private ObjectReferenceType createConnectorRefByConnectorType(String connectorType) throws Exception {
ObjectFilter filter = prismContext.queryFor(ConnectorType.class)
.item(ConnectorType.F_CONNECTOR_TYPE)
.eq(connectorType)
.buildFilter();
return new ObjectReferenceType()
.type(ConnectorType.COMPLEX_TYPE)
.filter(prismContext.getQueryConverter().createSearchFilterType(filter));
}

private ObjectReferenceType createAllConnectorsRef() throws Exception {
ObjectFilter filter = prismContext.queryFactory().createAll();
return new ObjectReferenceType()
.type(ConnectorType.COMPLEX_TYPE)
.filter(prismContext.getQueryConverter().createSearchFilterType(filter));
}

private String getResourceExpectingConnectorResolutionFailure(String oid, Task task, OperationResult result)
throws Exception {
try {
provisioningService.getObject(ResourceType.class, oid, null, task, result);
result.computeStatus();
assertPartialError(result);
return result.debugDump();
} catch (ConfigurationException e) {
return e.getMessage();
}
}

private void assertAllConnectorsFilterIsAmbiguous(OperationResult result) throws Exception {
int connectorCount = repositoryService
.searchObjects(
ConnectorType.class,
prismContext.queryFactory().createQuery(prismContext.queryFactory().createAll()),
null,
result)
.size();
assertTrue("Expected more than one connector for the ambiguous connectorRef filter, got " + connectorCount,
connectorCount > 1);
}

private void assertTextContains(String text, String expectedText) {
assertTrue("Text does not contain '" + expectedText + "':\n" + text, text.contains(expectedText));
}

private void assertTextDoesNotContain(String text, String unexpectedText) {
assertFalse("Text contains '" + unexpectedText + "':\n" + text, text.contains(unexpectedText));
}

private void assertScriptHost(ScriptCapabilityType capScript, ProvisioningScriptHostType expectedHostType) {
for (ScriptCapabilityHostType host : capScript.getHost()) {
if (host.getType() == expectedHostType) {
Expand Down
1 change: 1 addition & 0 deletions release-notes.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ Overall, midPoint 4.10 opens up the world of identity management and governance
* Delineation suggestions: filter parsing broken after recent GUI change. See bug:MID-11175[]
* Fixed work item search by name causing repository mapping error. See bug:MID-8834[]
* Fixed translation of archetype display labels in assignment picker and summary panel. See bug:MID-11177[]
* Fixed additional connector failure when connectorRef is specified using a filter. See bug:MID-9537[]

=== Releases Of Other Components

Expand Down
Loading