use of com.evolveum.midpoint.provisioning.ucf.api.UcfExecutionContext in project midpoint by Evolveum.
the class ConnectorInstanceConnIdImpl method fetchChanges.
@Override
public UcfFetchChangesResult fetchChanges(ResourceObjectDefinition objectDefinition, UcfSyncToken initialTokenValue, AttributesToReturn attrsToReturn, Integer maxChanges, UcfExecutionContext ctx, @NotNull UcfLiveSyncChangeListener changeListener, OperationResult parentResult) throws CommunicationException, GenericFrameworkException, SchemaException {
OperationResult result = parentResult.subresult(OP_FETCH_CHANGES).addArbitraryObjectAsContext("objectClass", objectDefinition).addArbitraryObjectAsParam("initialToken", initialTokenValue).build();
try {
SyncToken initialToken = TokenUtil.toConnId(initialTokenValue);
LOGGER.trace("Initial token: {}", initialToken == null ? null : initialToken.getValue());
ResourceObjectClassDefinition objectClassDefinition = objectDefinition != null ? objectDefinition.getObjectClassDefinition() : null;
// get icf object class
ObjectClass requestConnIdObjectClass;
if (objectClassDefinition == null) {
requestConnIdObjectClass = ObjectClass.ALL;
} else {
requestConnIdObjectClass = objectClassToConnId(objectClassDefinition);
}
OperationOptionsBuilder optionsBuilder = new OperationOptionsBuilder();
if (objectDefinition != null) {
convertToIcfAttrsToGet(objectDefinition, attrsToReturn, optionsBuilder);
}
OperationOptions options = optionsBuilder.build();
AtomicInteger deltasProcessed = new AtomicInteger(0);
Thread callerThread = Thread.currentThread();
SyncDeltaConverter changeConverter = new SyncDeltaConverter(this, objectDefinition);
AtomicBoolean allChangesFetched = new AtomicBoolean(true);
UcfFetchChangesResult fetchChangesResult;
OperationResult connIdResult = result.subresult(ConnectorFacade.class.getName() + ".sync").addContext("connector", connIdConnectorFacade.getClass()).addArbitraryObjectAsParam("objectClass", requestConnIdObjectClass).addArbitraryObjectAsParam("initialToken", initialToken).build();
try {
InternalMonitor.recordConnectorOperation("sync");
ConnIdOperation operation = recordIcfOperationStart(ctx, ProvisioningOperation.ICF_SYNC, objectDefinition);
/*
* We assume that the only way how changes are _not_ fetched is that we explicitly tell ConnId to stop
* fetching them by returning 'false' from the handler.handle() method. (Or an exception occurs in the sync()
* method.)
*
* In other words, we assume that if we tell ConnId to continue feeding changes to us, we are sure that on
* successful exit from sync() method all changes were processed.
*/
SyncResultsHandler syncHandler = syncDelta -> {
Thread handlingThread = Thread.currentThread();
if (!handlingThread.equals(callerThread)) {
LOGGER.warn("Live Sync changes are being processed in a thread {} that is different from the invoking one ({}). " + "This can cause issues e.g. with operational statistics reporting.", handlingThread, callerThread);
}
recordIcfOperationSuspend(ctx, operation);
LOGGER.trace("Received sync delta: {}", syncDelta);
OperationResult handleResult;
// But - just for sure - let us create subresults in a safe way.
synchronized (connIdResult) {
handleResult = connIdResult.subresult(OP_FETCH_CHANGES + ".handle").addArbitraryObjectAsParam("uid", syncDelta.getUid()).setMinor().build();
}
UcfLiveSyncChange change = null;
try {
// Here we again assume we are called in a single thread, and that changes received here are in
// the correct order - i.e. in the order in which they are to be processed.
int sequentialNumber = deltasProcessed.incrementAndGet();
change = changeConverter.createChange(sequentialNumber, syncDelta, handleResult);
// The following should not throw any exceptions
boolean canContinue = changeListener.onChange(change, handleResult);
boolean doContinue = canContinue && canRun(ctx) && (maxChanges == null || maxChanges == 0 || sequentialNumber < maxChanges);
if (!doContinue) {
allChangesFetched.set(false);
}
return doContinue;
} catch (RuntimeException e) {
handleResult.recordFatalError(e);
// any exception here is not expected
LoggingUtils.logUnexpectedException(LOGGER, "Got unexpected exception while handling live sync " + "change, stopping the processing. Sync delta: {}, UCF change: {}", e, syncDelta, change);
return false;
} finally {
// Asynchronously processed changes (if used) have their own, separate, operation results
// that are tied to the lightweight asynchronous task handlers in ChangeProcessingCoordinator.
//
// So we can safely compute/cleanup/summarize results here.
handleResult.computeStatusIfUnknown();
handleResult.cleanupResult();
connIdResult.summarize(true);
recordIcfOperationResume(ctx, operation);
}
};
LOGGER.trace("Invoking ConnId sync operation: {}", operation);
SyncToken finalToken;
try {
finalToken = connIdConnectorFacade.sync(requestConnIdObjectClass, initialToken, syncHandler, options);
// Note that finalToken value is not quite reliable. The SyncApiOp documentation is not clear on its semantics;
// it is only from SyncTokenResultsHandler (SPI) documentation and SyncImpl class that we know this value is
// non-null when all changes were fetched. And some of the connectors return null even then.
LOGGER.trace("connector sync method returned: {}", finalToken);
connIdResult.computeStatus();
connIdResult.cleanupResult();
connIdResult.addReturn(OperationResult.RETURN_COUNT, deltasProcessed.get());
recordIcfOperationEnd(ctx, operation, null);
} catch (Throwable ex) {
recordIcfOperationEnd(ctx, operation, ex);
Throwable midpointEx = processConnIdException(ex, this, connIdResult);
connIdResult.computeStatusIfUnknown();
connIdResult.cleanupResult();
result.computeStatus();
// Do some kind of acrobatics to do proper throwing of checked exception
if (midpointEx instanceof CommunicationException) {
throw (CommunicationException) midpointEx;
} else if (midpointEx instanceof GenericFrameworkException) {
throw (GenericFrameworkException) midpointEx;
} else if (midpointEx instanceof SchemaException) {
throw (SchemaException) midpointEx;
} else if (midpointEx instanceof RuntimeException) {
throw (RuntimeException) midpointEx;
} else if (midpointEx instanceof Error) {
throw (Error) midpointEx;
} else {
throw new SystemException("Got unexpected exception: " + ex.getClass().getName() + ": " + ex.getMessage(), ex);
}
}
if (!canRun(ctx)) {
result.recordStatus(OperationResultStatus.SUCCESS, "Interrupted by task suspension");
}
if (allChangesFetched.get()) {
// We might consider finalToken value here. I.e. it it's non null, we could declare all changes to be fetched.
// But as mentioned above, this is not supported explicitly in SyncApiOp. So let's be a bit conservative.
LOGGER.trace("All changes were fetched; with finalToken = {}", finalToken);
fetchChangesResult = new UcfFetchChangesResult(true, TokenUtil.toUcf(finalToken));
} else {
fetchChangesResult = new UcfFetchChangesResult(false, null);
}
} catch (Throwable t) {
connIdResult.recordFatalError(t);
throw t;
} finally {
connIdResult.computeStatusIfUnknown();
}
result.recordSuccess();
result.addReturn(OperationResult.RETURN_COUNT, deltasProcessed.get());
return fetchChangesResult;
} catch (Throwable t) {
result.recordFatalError(t);
throw t;
} finally {
result.computeStatusIfUnknown();
}
}
use of com.evolveum.midpoint.provisioning.ucf.api.UcfExecutionContext in project midpoint by Evolveum.
the class TestUcfDummyMulti method test210TwoBlockingSearches.
@Test
public void test210TwoBlockingSearches() throws Exception {
// GIVEN
UcfExecutionContext ctx = createExecutionContext();
final ResourceObjectClassDefinition accountDefinition = resourceSchema.findObjectClassDefinitionRequired(ACCOUNT_OBJECT_CLASS_NAME);
// Determine object class from the schema
OperationResult result1 = createOperationResult();
final List<PrismObject<ShadowType>> searchResults1 = new ArrayList<>();
final ObjectHandler handler1 = (ucfObject, result) -> {
checkUcfShadow(ucfObject.getResourceObject(), accountDefinition);
searchResults1.add(ucfObject.getResourceObject());
return true;
};
OperationResult result2 = createOperationResult();
final List<PrismObject<ShadowType>> searchResults2 = new ArrayList<>();
final ObjectHandler handler2 = (ucfObject, result) -> {
checkUcfShadow(ucfObject.getResourceObject(), accountDefinition);
searchResults2.add(ucfObject.getResourceObject());
return true;
};
dummyResource.setBlockOperations(true);
// WHEN
Thread t1 = new Thread(() -> {
try {
cc.search(accountDefinition, null, handler1, null, null, null, null, ctx, result1);
} catch (CommunicationException | GenericFrameworkException | SchemaException | SecurityViolationException | ObjectNotFoundException e) {
logger.error("Error in the search: {}", e.getMessage(), e);
}
});
t1.setName("search1");
t1.start();
// Give the new thread a chance to get blocked
Thread.sleep(500);
ConnectorOperationalStatus opStat = cc.getOperationalStatus();
displayDumpable("stats (blocked 1)", opStat);
assertEquals("Wrong pool active", (Integer) 1, opStat.getPoolStatusNumActive());
assertEquals("Wrong pool active", (Integer) 0, opStat.getPoolStatusNumIdle());
assertEquals("Unexpected number of search results", 0, searchResults1.size());
Thread t2 = new Thread(() -> {
try {
cc.search(accountDefinition, null, handler2, null, null, null, null, ctx, result2);
} catch (CommunicationException | GenericFrameworkException | SchemaException | SecurityViolationException | ObjectNotFoundException e) {
logger.error("Error in the search: {}", e.getMessage(), e);
}
});
t2.setName("search2");
t2.start();
// Give the new thread a chance to get blocked
Thread.sleep(500);
opStat = cc.getOperationalStatus();
displayDumpable("stats (blocked 2)", opStat);
assertEquals("Wrong pool active", (Integer) 2, opStat.getPoolStatusNumActive());
assertEquals("Wrong pool active", (Integer) 0, opStat.getPoolStatusNumIdle());
assertEquals("Unexpected number of search results", 0, searchResults1.size());
dummyResource.unblockAll();
t1.join();
t2.join();
dummyResource.setBlockOperations(false);
// THEN
assertEquals("Unexpected number of search results 1", 1, searchResults1.size());
assertEquals("Unexpected number of search results 2", 1, searchResults2.size());
opStat = cc.getOperationalStatus();
displayDumpable("stats (final)", opStat);
assertEquals("Wrong pool active", (Integer) 0, opStat.getPoolStatusNumActive());
assertEquals("Wrong pool active", (Integer) 2, opStat.getPoolStatusNumIdle());
PrismObject<ShadowType> searchResult1 = searchResults1.get(0);
displayDumpable("Search result 1", searchResult1);
PrismObject<ShadowType> searchResult2 = searchResults2.get(0);
displayDumpable("Search result 2", searchResult2);
}
use of com.evolveum.midpoint.provisioning.ucf.api.UcfExecutionContext in project midpoint by Evolveum.
the class TestUcfDummyMulti method test110SearchNonBlocking.
@Test
public void test110SearchNonBlocking() throws Exception {
// GIVEN
UcfExecutionContext ctx = createExecutionContext();
final ResourceObjectClassDefinition accountDefinition = resourceSchema.findObjectClassDefinitionRequired(ACCOUNT_OBJECT_CLASS_NAME);
// Determine object class from the schema
final List<PrismObject<ShadowType>> searchResults = new ArrayList<>();
ObjectHandler handler = (ucfObject, result) -> {
displayDumpable("Search: found", ucfObject);
checkUcfShadow(ucfObject.getResourceObject(), accountDefinition);
searchResults.add(ucfObject.getResourceObject());
return true;
};
OperationResult result = createOperationResult();
// WHEN
cc.search(accountDefinition, null, handler, null, null, null, null, ctx, result);
// THEN
assertEquals("Unexpected number of search results", 1, searchResults.size());
ConnectorOperationalStatus opStat = cc.getOperationalStatus();
displayDumpable("stats", opStat);
assertEquals("Wrong pool active", (Integer) 0, opStat.getPoolStatusNumActive());
assertEquals("Wrong pool active", (Integer) 1, opStat.getPoolStatusNumIdle());
}
use of com.evolveum.midpoint.provisioning.ucf.api.UcfExecutionContext in project midpoint by Evolveum.
the class ResourceObjectConverter method executeProvisioningScripts.
private void executeProvisioningScripts(ProvisioningContext ctx, ProvisioningOperationTypeType provisioningOperationType, BeforeAfterType beforeAfter, OperationProvisioningScriptsType scripts, OperationResult result) throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, ExpressionEvaluationException, GenericConnectorException {
Collection<ExecuteProvisioningScriptOperation> operations = determineExecuteScriptOperations(provisioningOperationType, beforeAfter, scripts, ctx.getResource(), result);
if (operations == null) {
return;
}
ConnectorInstance connector = ctx.getConnector(ScriptCapabilityType.class, result);
for (ExecuteProvisioningScriptOperation operation : operations) {
UcfExecutionContext ucfCtx = new UcfExecutionContext(lightweightIdentifierGenerator, ctx.getResource(), ctx.getTask());
try {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("PROVISIONING SCRIPT EXECUTION {} {} operation on resource {}", beforeAfter.value(), provisioningOperationType.value(), ctx.getResource());
}
Object returnedValue = connector.executeScript(operation, ucfCtx, result);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("PROVISIONING SCRIPT EXECUTION {} {} successful, returned value: {}", beforeAfter.value(), provisioningOperationType.value(), returnedValue);
}
} catch (CommunicationException ex) {
String message = "Could not execute provisioning script. Error communicating with the connector " + connector + ": " + ex.getMessage();
if (ExceptionUtil.isFatalCriticality(operation.getCriticality(), CriticalityType.FATAL)) {
result.recordFatalError(message, ex);
throw new CommunicationException(message, ex);
} else {
LOGGER.warn("{}", message);
}
} catch (GenericFrameworkException ex) {
String message = "Could not execute provisioning script. Generic error in connector: " + ex.getMessage();
if (ExceptionUtil.isFatalCriticality(operation.getCriticality(), CriticalityType.FATAL)) {
result.recordFatalError(message, ex);
throw new GenericConnectorException(message, ex);
} else {
LOGGER.warn("{}", message);
}
} catch (Throwable t) {
String message = "Could not execute provisioning script. Unexpected error in connector: " + t.getClass().getSimpleName() + ": " + t.getMessage();
if (ExceptionUtil.isFatalCriticality(operation.getCriticality(), CriticalityType.FATAL)) {
result.recordFatalError(message, t);
throw t;
} else {
LOGGER.warn("{}", message);
}
}
}
}
use of com.evolveum.midpoint.provisioning.ucf.api.UcfExecutionContext in project midpoint by Evolveum.
the class ResourceManager method executeScript.
public Object executeScript(String resourceOid, ProvisioningScriptType script, Task task, OperationResult result) throws ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, ExpressionEvaluationException {
PrismObject<ResourceType> resource = getResource(resourceOid, null, task, result);
ConnectorSpec connectorSpec = selectConnectorSpec(resource, ScriptCapabilityType.class);
if (connectorSpec == null) {
throw new UnsupportedOperationException("No connector supports script capability");
}
ConnectorInstance connectorInstance = connectorManager.getConfiguredConnectorInstance(connectorSpec, false, result);
ExecuteProvisioningScriptOperation scriptOperation = ProvisioningUtil.convertToScriptOperation(script, "script on " + resource, prismContext);
try {
UcfExecutionContext ucfCtx = new UcfExecutionContext(lightweightIdentifierGenerator, resource.asObjectable(), task);
return connectorInstance.executeScript(scriptOperation, ucfCtx, result);
} catch (GenericFrameworkException e) {
// Not expected. Transform to system exception
result.recordFatalError("Generic provisioning framework error", e);
throw new SystemException("Generic provisioning framework error: " + e.getMessage(), e);
}
}
Aggregations