You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@syncope.apache.org by il...@apache.org on 2021/01/25 16:12:26 UTC
[syncope] branch 2_1_X updated: [SYNCOPE-1610] Refactoring
ReconQuery to work with FIQL (#234)
This is an automated email from the ASF dual-hosted git repository.
ilgrosso pushed a commit to branch 2_1_X
in repository https://gitbox.apache.org/repos/asf/syncope.git
The following commit(s) were added to refs/heads/2_1_X by this push:
new 3bc2f51 [SYNCOPE-1610] Refactoring ReconQuery to work with FIQL (#234)
3bc2f51 is described below
commit 3bc2f5170397f02cc5d1aaa860b624bd983525a4
Author: Francesco Chicchiriccò <il...@users.noreply.github.com>
AuthorDate: Mon Jan 25 17:12:17 2021 +0100
[SYNCOPE-1610] Refactoring ReconQuery to work with FIQL (#234)
---
.../client/console/SyncopeConsoleSession.java | 37 ++-
.../client/console/commons/status/StatusUtils.java | 4 +-
.../console/panels/ConnObjectListViewPanel.java | 59 ++--
.../syncope/client/console/panels/ConnObjects.java | 6 +-
.../panels/DisplayAttributesModalPanel.java | 2 +-
.../console/panels/LinkedAccountModalPanel.java | 5 +-
.../client/console/status/ReconTaskPanel.java | 7 +-
.../any/MergeLinkedAccountsWizardBuilder.java | 112 ++++---
.../apache/syncope/common/lib/to/ConnObjectTO.java | 12 +
.../syncope/common/rest/api/beans/ReconQuery.java | 16 +-
.../rest/api/service/ReconciliationService.java | 6 +-
.../syncope/core/logic/ReconciliationLogic.java | 354 +++++++++++++--------
.../apache/syncope/core/logic/ResourceLogic.java | 25 +-
.../persistence/api/dao/PushCorrelationRule.java | 26 +-
.../jpa/dao/DefaultPushCorrelationRule.java | 61 +++-
.../api/propagation/PropagationReporter.java | 2 +
...ecutor.java => ConstantReconFilterBuilder.java} | 25 +-
...ecutor.java => KeyValueReconFilterBuilder.java} | 30 +-
.../api/pushpull/SyncopeSinglePullExecutor.java | 3 +-
.../java/data/AbstractAnyDataBinder.java | 26 +-
.../AbstractPropagationTaskExecutor.java | 9 +
.../propagation/DefaultPropagationReporter.java | 5 +-
.../PriorityPropagationTaskExecutor.java | 2 +-
.../java/pushpull/OutboundMatcher.java | 6 +
.../java/pushpull/SinglePullJobDelegate.java | 25 +-
.../provisioning/java/utils/ConnObjectUtils.java | 20 +-
.../cxf/service/ReconciliationServiceImpl.java | 97 ++++--
.../syncope/fit/core/PropagationTaskITCase.java | 9 +-
.../syncope/fit/core/ReconciliationITCase.java | 10 +-
29 files changed, 647 insertions(+), 354 deletions(-)
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/SyncopeConsoleSession.java b/client/console/src/main/java/org/apache/syncope/client/console/SyncopeConsoleSession.java
index dd3bafe..43cc661 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/SyncopeConsoleSession.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/SyncopeConsoleSession.java
@@ -91,29 +91,29 @@ public class SyncopeConsoleSession extends AuthenticatedWebSession {
}
}
- private static final Logger LOG = LoggerFactory.getLogger(SyncopeConsoleSession.class);
+ protected static final Logger LOG = LoggerFactory.getLogger(SyncopeConsoleSession.class);
- private final SyncopeClientFactoryBean clientFactory;
+ protected final SyncopeClientFactoryBean clientFactory;
- private final SyncopeClient anonymousClient;
+ protected final SyncopeClient anonymousClient;
- private final PlatformInfo platformInfo;
+ protected final PlatformInfo platformInfo;
- private final SystemInfo systemInfo;
+ protected final SystemInfo systemInfo;
- private final Map<Class<?>, Object> services = Collections.synchronizedMap(new HashMap<>());
+ protected final Map<Class<?>, Object> services = Collections.synchronizedMap(new HashMap<>());
- private final ThreadPoolTaskExecutor executor;
+ protected final ThreadPoolTaskExecutor executor;
- private String domain;
+ protected String domain;
- private SyncopeClient client;
+ protected SyncopeClient client;
- private UserTO selfTO;
+ protected UserTO selfTO;
- private Map<String, Set<String>> auth;
+ protected Map<String, Set<String>> auth;
- private Roles roles;
+ protected Roles roles;
public static SyncopeConsoleSession get() {
return (SyncopeConsoleSession) Session.get();
@@ -139,6 +139,10 @@ public class SyncopeConsoleSession extends AuthenticatedWebSession {
executor.initialize();
}
+ protected String message(final SyncopeClientException sce) {
+ return sce.getType().name() + ": " + sce.getElements().stream().collect(Collectors.joining(", "));
+ }
+
/**
* Extract and localize (if translation available) the actual message from the given exception; then, report it
* via {@link Session#error(java.io.Serializable)}.
@@ -153,9 +157,10 @@ public class SyncopeConsoleSession extends AuthenticatedWebSession {
if (root instanceof SyncopeClientException) {
SyncopeClientException sce = (SyncopeClientException) root;
- if (!sce.isComposite()) {
- message = sce.getElements().stream().collect(Collectors.joining(", "));
- }
+ message = sce.isComposite()
+ ? sce.asComposite().getExceptions().stream().
+ map(c -> message(c)).collect(Collectors.joining("; "))
+ : message(sce);
} else if (root instanceof AccessControlException || root instanceof ForbiddenException) {
Error error = StringUtils.containsIgnoreCase(message, "expired")
? Error.SESSION_EXPIRED
@@ -358,7 +363,7 @@ public class SyncopeConsoleSession extends AuthenticatedWebSession {
}
@SuppressWarnings("unchecked")
- private <T> T getCachedService(final Class<T> serviceClass) {
+ protected <T> T getCachedService(final Class<T> serviceClass) {
T service;
if (services.containsKey(serviceClass)) {
service = (T) services.get(serviceClass);
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/commons/status/StatusUtils.java b/client/console/src/main/java/org/apache/syncope/client/console/commons/status/StatusUtils.java
index 5db8e22..d1ac417 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/commons/status/StatusUtils.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/commons/status/StatusUtils.java
@@ -57,8 +57,8 @@ public final class StatusUtils implements Serializable {
ReconStatus result = null;
try {
- result = RECONCILIATION_REST_CLIENT.status(
- new ReconQuery.Builder(anyTypeKey, resource).connObjectKeyValue(connObjectKeyValue).build());
+ result = RECONCILIATION_REST_CLIENT.status(new ReconQuery.Builder(anyTypeKey, resource).
+ fiql(ConnIdSpecialName.UID + "==" + connObjectKeyValue).build());
} catch (Exception e) {
LOG.warn("Unexpected error for {} {} on {}", anyTypeKey, connObjectKeyValue, resource, e);
}
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/ConnObjectListViewPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/ConnObjectListViewPanel.java
index 5f5806c..7df6bd5 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/panels/ConnObjectListViewPanel.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/ConnObjectListViewPanel.java
@@ -28,9 +28,11 @@ import java.util.List;
import java.util.Optional;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
+import org.apache.syncope.client.console.SyncopeConsoleSession;
import org.apache.syncope.client.console.commons.ConnIdSpecialName;
import org.apache.syncope.client.console.commons.Constants;
import org.apache.syncope.client.console.commons.status.StatusUtils;
+import org.apache.syncope.client.console.pages.BasePage;
import org.apache.syncope.client.console.panels.ListViewPanel.ListViewReload;
import org.apache.syncope.client.console.panels.search.AbstractSearchPanel;
import org.apache.syncope.client.console.panels.search.ConnObjectSearchPanel;
@@ -70,11 +72,15 @@ import org.apache.wicket.markup.html.panel.Panel;
import org.apache.wicket.model.Model;
import org.apache.wicket.model.StringResourceModel;
import org.apache.wicket.model.util.ListModel;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
public abstract class ConnObjectListViewPanel extends Panel {
private static final long serialVersionUID = 4986172040062752781L;
+ private static final Logger LOG = LoggerFactory.getLogger(ConnObjectListViewPanel.class);
+
private final AnyTypeRestClient anyTypeRestClient = new AnyTypeRestClient();
private final ReconciliationRestClient reconRestClient = new ReconciliationRestClient();
@@ -144,9 +150,9 @@ public abstract class ConnObjectListViewPanel extends Panel {
accordion.setOutputMarkupId(true);
add(accordion.setEnabled(true).setVisible(true));
- final List<ConnObjectTO> listOfItems = reloadItems(resource.getKey(), anyType, null, null);
+ List<ConnObjectTO> listOfItems = reloadItems(resource.getKey(), anyType, null, null);
- final ListViewPanel.Builder<ConnObjectTO> builder = new ListViewPanel.Builder<ConnObjectTO>(
+ ListViewPanel.Builder<ConnObjectTO> builder = new ListViewPanel.Builder<ConnObjectTO>(
ConnObjectTO.class, pageRef) {
private static final long serialVersionUID = -8251750413385566738L;
@@ -154,13 +160,21 @@ public abstract class ConnObjectListViewPanel extends Panel {
@Override
protected Component getValueComponent(final String key, final ConnObjectTO bean) {
if (StringUtils.equals(key, STATUS)) {
- ReconStatus status = reconRestClient.status(new ReconQuery.Builder(anyType, resource.getKey()).
- connObjectKeyValue(bean.getAttr(ConnIdSpecialName.UID).get().getValues().get(0)).build());
+ ReconStatus status;
+ try {
+ status = reconRestClient.status(
+ new ReconQuery.Builder(anyType, resource.getKey()).fiql(bean.getFiql()).build());
+ } catch (Exception e) {
+ LOG.error("While requesting for reconciliation status of {} {} with FIQL '{}'",
+ anyType, resource.getKey(), bean.getFiql(), e);
+
+ status = new ReconStatus();
+ }
return status.getOnSyncope() == null
? StatusUtils.getLabel("field", "notfound icon", "Not found", Constants.NOT_FOUND_ICON)
: new Label("field", Model.of()).add(new PopoverBehavior(
- Model.<String>of(),
+ Model.of(),
Model.of(status.getAnyKey()),
new PopoverConfig().
withTitle(status.getMatchType() == MatchType.LINKED_ACCOUNT
@@ -217,20 +231,25 @@ public abstract class ConnObjectListViewPanel extends Panel {
@Override
public void onClick(final AjaxRequestTarget target, final ConnObjectTO modelObject) {
- String connObjectKeyValue = modelObject.getAttr(ConnIdSpecialName.UID).get().getValues().get(0);
- ReconStatus status = reconRestClient.status(
- new ReconQuery.Builder(anyType, resource.getKey())
- .connObjectKeyValue(connObjectKeyValue)
- .build());
-
- pullConnObject(
- connObjectKeyValue,
- target,
- resource.getKey(),
- anyType,
- status.getRealm(),
- StringUtils.isNotBlank(status.getAnyKey()),
- pageRef);
+ try {
+ ReconStatus status = reconRestClient.status(
+ new ReconQuery.Builder(anyType, resource.getKey()).fiql(modelObject.getFiql()).build());
+
+ pullConnObject(
+ modelObject.getFiql(),
+ target,
+ resource.getKey(),
+ anyType,
+ status.getRealm(),
+ StringUtils.isNotBlank(status.getAnyKey()),
+ pageRef);
+ } catch (Exception e) {
+ LOG.error("While puling single object {} {} with FIQL '{}'",
+ anyType, resource.getKey(), modelObject.getFiql(), e);
+
+ SyncopeConsoleSession.get().onException(e);
+ ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target);
+ }
}
}, ActionLink.ActionType.RECONCILIATION_PULL, StandardEntitlement.TASK_EXECUTE);
@@ -276,7 +295,7 @@ public abstract class ConnObjectListViewPanel extends Panel {
protected abstract void viewConnObject(ConnObjectTO connObjectTO, AjaxRequestTarget target);
protected abstract void pullConnObject(
- String connObjectTO,
+ String fiql,
AjaxRequestTarget target,
String resource,
String anyType,
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/ConnObjects.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/ConnObjects.java
index 0eef8e1..e84ca06 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/panels/ConnObjects.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/ConnObjects.java
@@ -127,13 +127,14 @@ public class ConnObjects extends Panel implements ModalPanel {
@Override
protected void pullConnObject(
- final String connObjectKeyValue,
+ final String fiql,
final AjaxRequestTarget target,
final String resource,
final String anyType,
final String realm,
final boolean isOnSyncope,
final PageReference pageRef) {
+
anyTypes.setEnabled(false);
target.add(anyTypes);
@@ -145,13 +146,12 @@ public class ConnObjects extends Panel implements ModalPanel {
pullTaskTO,
anyType,
null,
- connObjectKeyValue,
+ fiql,
isOnSyncope,
connObjects,
pageRef),
target
);
}
-
}
}
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/DisplayAttributesModalPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/DisplayAttributesModalPanel.java
index b1c6c7a..4c5d40f 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/panels/DisplayAttributesModalPanel.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/DisplayAttributesModalPanel.java
@@ -164,7 +164,7 @@ public abstract class DisplayAttributesModalPanel<T extends Serializable> extend
SyncopeConsoleSession.get().error(getString("tooManySelections"));
onError(target);
} else {
- final Map<String, List<String>> prefs = new HashMap<>();
+ Map<String, List<String>> prefs = new HashMap<>();
prefs.put(DisplayAttributesModalPanel.getPrefDetailView(type), selectedDetails);
prefs.put(DisplayAttributesModalPanel.getPrefPlainAttributeView(type), selectedPlainSchemas);
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/LinkedAccountModalPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/LinkedAccountModalPanel.java
index 36e2bc2..13ef887 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/panels/LinkedAccountModalPanel.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/LinkedAccountModalPanel.java
@@ -24,6 +24,7 @@ import java.util.List;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.apache.syncope.client.console.SyncopeConsoleSession;
+import org.apache.syncope.client.console.commons.ConnIdSpecialName;
import org.apache.syncope.client.console.commons.Constants;
import org.apache.syncope.client.console.layout.AnyLayout;
import org.apache.syncope.client.console.layout.AnyLayoutUtils;
@@ -266,7 +267,7 @@ public class LinkedAccountModalPanel extends Panel implements ModalPanel {
new PushTaskTO(),
model.getObject().getType(),
null,
- linkedAccountTO.getConnObjectKeyValue(),
+ ConnIdSpecialName.UID + "==" + linkedAccountTO.getConnObjectKeyValue(),
true,
mlp,
pageRef),
@@ -292,7 +293,7 @@ public class LinkedAccountModalPanel extends Panel implements ModalPanel {
new PullTaskTO(),
model.getObject().getType(),
null,
- linkedAccountTO.getConnObjectKeyValue(),
+ ConnIdSpecialName.UID + "==" + linkedAccountTO.getConnObjectKeyValue(),
true,
mlp,
pageRef),
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/status/ReconTaskPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/status/ReconTaskPanel.java
index 980315b..245993c 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/status/ReconTaskPanel.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/status/ReconTaskPanel.java
@@ -103,6 +103,7 @@ public class ReconTaskPanel extends MultilevelPanel.SecondLevel {
final boolean isOnSyncope,
final MultilevelPanel multiLevelPanelRef,
final PageReference pageRef) {
+
this(resource, taskTO, anyType, anyKey, null, isOnSyncope, multiLevelPanelRef, pageRef);
}
@@ -111,7 +112,7 @@ public class ReconTaskPanel extends MultilevelPanel.SecondLevel {
final ProvisioningTaskTO taskTO,
final String anyType,
final String anyKey,
- final String connObjectKeyValue,
+ final String fiql,
final boolean isOnSyncope,
final MultilevelPanel multiLevelPanelRef,
final PageReference pageRef) {
@@ -202,9 +203,7 @@ public class ReconTaskPanel extends MultilevelPanel.SecondLevel {
@Override
protected void onSubmit(final AjaxRequestTarget target) {
- ReconQuery reconQuery = new ReconQuery.Builder(anyType, resource).
- anyKey(anyKey).
- connObjectKeyValue(connObjectKeyValue).build();
+ ReconQuery reconQuery = new ReconQuery.Builder(anyType, resource).anyKey(anyKey).fiql(fiql).build();
try {
if (taskTO instanceof PushTaskTO) {
reconRestClient.push(reconQuery, (PushTaskTO) form.getModelObject());
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/MergeLinkedAccountsWizardBuilder.java b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/MergeLinkedAccountsWizardBuilder.java
index ed9a119..b17ba5f 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/MergeLinkedAccountsWizardBuilder.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/MergeLinkedAccountsWizardBuilder.java
@@ -19,6 +19,13 @@
package org.apache.syncope.client.console.wizards.any;
import com.fasterxml.jackson.databind.ObjectMapper;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+import javax.ws.rs.HttpMethod;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
import org.apache.syncope.client.console.SyncopeConsoleSession;
import org.apache.syncope.client.console.commons.Constants;
import org.apache.syncope.client.console.pages.BasePage;
@@ -47,16 +54,8 @@ import org.apache.wicket.event.IEventSink;
import org.apache.wicket.extensions.wizard.WizardModel;
import org.apache.wicket.model.IModel;
-import javax.ws.rs.HttpMethod;
-import javax.ws.rs.core.HttpHeaders;
-import javax.ws.rs.core.MediaType;
-
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Optional;
-
public class MergeLinkedAccountsWizardBuilder extends AjaxWizardBuilder<UserTO> implements IEventSink {
+
private static final long serialVersionUID = -9142332740863374891L;
private static final ObjectMapper MAPPER = new ObjectMapper();
@@ -68,7 +67,7 @@ public class MergeLinkedAccountsWizardBuilder extends AjaxWizardBuilder<UserTO>
private MergeLinkedAccountsWizardModel model;
public MergeLinkedAccountsWizardBuilder(final IModel<UserTO> model, final PageReference pageRef,
- final UserDirectoryPanel parentPanel, final BaseModal<?> modal) {
+ final UserDirectoryPanel parentPanel, final BaseModal<?> modal) {
super(model.getObject(), pageRef);
this.parentPanel = parentPanel;
this.modal = modal;
@@ -76,7 +75,7 @@ public class MergeLinkedAccountsWizardBuilder extends AjaxWizardBuilder<UserTO>
@Override
protected WizardModel buildModelSteps(final UserTO modelObject, final WizardModel wizardModel) {
- this.model = new MergeLinkedAccountsWizardModel(modelObject);
+ model = new MergeLinkedAccountsWizardModel(modelObject);
wizardModel.add(new MergeLinkedAccountsSearchPanel(model, getPageReference()));
wizardModel.add(new MergeLinkedAccountsResourcesPanel(model, getPageReference()));
wizardModel.add(new MergeLinkedAccountsReviewPanel(model, getPageReference()));
@@ -89,78 +88,76 @@ public class MergeLinkedAccountsWizardBuilder extends AjaxWizardBuilder<UserTO>
((AjaxWizard.NewItemCancelEvent<?>) event.getPayload()).getTarget().ifPresent(modal::close);
}
if (event.getPayload() instanceof AjaxWizard.NewItemFinishEvent) {
- Optional<AjaxRequestTarget> targetResult = ((AjaxWizard.NewItemFinishEvent<?>)
- event.getPayload()).getTarget();
+ Optional<AjaxRequestTarget> target =
+ ((AjaxWizard.NewItemFinishEvent<?>) event.getPayload()).getTarget();
try {
mergeAccounts();
- this.parentPanel.info(this.parentPanel.getString(Constants.OPERATION_SUCCEEDED));
- targetResult.ifPresent(target -> {
- ((BasePage) this.parentPanel.getPage()).getNotificationPanel().refresh(target);
- parentPanel.updateResultTable(target);
- modal.close(target);
+
+ parentPanel.info(parentPanel.getString(Constants.OPERATION_SUCCEEDED));
+ target.ifPresent(t -> {
+ ((BasePage) parentPanel.getPage()).getNotificationPanel().refresh(t);
+ parentPanel.updateResultTable(t);
+ modal.close(t);
});
- } catch (Exception e) {
- this.parentPanel.error(this.parentPanel.getString(Constants.ERROR) + ": " + e.getMessage());
- targetResult.ifPresent(target -> ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target));
+ } catch (Exception e) {
+ parentPanel.error(parentPanel.getString(Constants.ERROR) + ": " + e.getMessage());
+ target.ifPresent(t -> ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(t));
}
}
}
private void mergeAccounts() throws Exception {
- final UserTO mergingUserTO = this.model.getMergingUser();
- final ResourceRestClient resourceRestClient = new ResourceRestClient();
+ UserTO mergingUserTO = model.getMergingUser();
+ ResourceRestClient resourceRestClient = new ResourceRestClient();
UserPatch userPatch = new UserPatch();
- userPatch.setKey(this.model.getBaseUser().getUsername());
+ userPatch.setKey(model.getBaseUser().getUsername());
// Move linked accounts into the target/base user as linked accounts
mergingUserTO.getLinkedAccounts().forEach(acct -> {
LinkedAccountTO linkedAccount =
- new LinkedAccountTO.Builder(acct.getResource(), acct.getConnObjectKeyValue())
- .password(acct.getPassword())
- .suspended(acct.isSuspended())
- .username(acct.getUsername())
- .build();
+ new LinkedAccountTO.Builder(acct.getResource(), acct.getConnObjectKeyValue()).
+ password(acct.getPassword()).
+ suspended(acct.isSuspended()).
+ username(acct.getUsername()).
+ build();
linkedAccount.getPlainAttrs().addAll(acct.getPlainAttrs());
linkedAccount.getPrivileges().addAll(acct.getPrivileges());
- LinkedAccountPatch patch = new LinkedAccountPatch.Builder()
- .linkedAccountTO(linkedAccount)
- .operation(PatchOperation.ADD_REPLACE)
- .build();
+ LinkedAccountPatch patch = new LinkedAccountPatch.Builder().
+ linkedAccountTO(linkedAccount).
+ operation(PatchOperation.ADD_REPLACE).
+ build();
userPatch.getLinkedAccounts().add(patch);
});
// Move merging user's resources into the target/base user as a linked account
mergingUserTO.getResources().forEach(resource -> {
String connObjectKeyValue = resourceRestClient.getConnObjectKeyValue(resource,
- mergingUserTO.getType(), mergingUserTO.getKey());
- LinkedAccountTO linkedAccount =
- new LinkedAccountTO.Builder(resource, connObjectKeyValue)
- .build();
+ mergingUserTO.getType(), mergingUserTO.getKey());
+ LinkedAccountTO linkedAccount = new LinkedAccountTO.Builder(resource, connObjectKeyValue).build();
linkedAccount.getPlainAttrs().addAll(mergingUserTO.getPlainAttrs());
linkedAccount.getPrivileges().addAll(mergingUserTO.getPrivileges());
- LinkedAccountPatch patch = new LinkedAccountPatch.Builder()
- .linkedAccountTO(linkedAccount)
- .operation(PatchOperation.ADD_REPLACE)
- .build();
+ LinkedAccountPatch patch = new LinkedAccountPatch.Builder().
+ linkedAccountTO(linkedAccount).
+ operation(PatchOperation.ADD_REPLACE).
+ build();
userPatch.getLinkedAccounts().add(patch);
});
// Move merging user into target/base user as a linked account
String connObjectKeyValue = resourceRestClient.getConnObjectKeyValue(
- this.model.getResource().getKey(),
- mergingUserTO.getType(), mergingUserTO.getKey());
- LinkedAccountTO linkedAccount =
- new LinkedAccountTO.Builder(this.model.getResource().getKey(), connObjectKeyValue)
- .password(mergingUserTO.getPassword())
- .suspended(mergingUserTO.isSuspended())
- .username(mergingUserTO.getUsername())
- .build();
+ model.getResource().getKey(),
+ mergingUserTO.getType(), mergingUserTO.getKey());
+ LinkedAccountTO linkedAccount = new LinkedAccountTO.Builder(model.getResource().getKey(), connObjectKeyValue).
+ password(mergingUserTO.getPassword()).
+ suspended(mergingUserTO.isSuspended()).
+ username(mergingUserTO.getUsername()).
+ build();
linkedAccount.getPlainAttrs().addAll(mergingUserTO.getPlainAttrs());
linkedAccount.getPrivileges().addAll(mergingUserTO.getPrivileges());
- LinkedAccountPatch patch = new LinkedAccountPatch.Builder().linkedAccountTO(linkedAccount)
- .operation(PatchOperation.ADD_REPLACE)
- .build();
+ LinkedAccountPatch patch = new LinkedAccountPatch.Builder().linkedAccountTO(linkedAccount).
+ operation(PatchOperation.ADD_REPLACE).
+ build();
userPatch.getLinkedAccounts().add(patch);
BatchRequest batchRequest = SyncopeConsoleSession.get().batch();
@@ -176,16 +173,16 @@ public class MergeLinkedAccountsWizardBuilder extends AjaxWizardBuilder<UserTO>
String updateUserPayload = MAPPER.writeValueAsString(userPatch);
BatchRequestItem updateUser = new BatchRequestItem();
updateUser.setMethod(HttpMethod.PATCH);
- updateUser.setRequestURI("/users/" + this.model.getBaseUser().getUsername());
+ updateUser.setRequestURI("/users/" + model.getBaseUser().getUsername());
updateUser.setHeaders(new HashMap<>());
updateUser.getHeaders().put(RESTHeaders.PREFER,
- Collections.singletonList(Preference.RETURN_NO_CONTENT.toString()));
+ Collections.singletonList(Preference.RETURN_NO_CONTENT.toString()));
updateUser.getHeaders().put(HttpHeaders.ACCEPT,
- Collections.singletonList(MediaType.APPLICATION_JSON));
+ Collections.singletonList(MediaType.APPLICATION_JSON));
updateUser.getHeaders().put(HttpHeaders.CONTENT_TYPE,
- Collections.singletonList(MediaType.APPLICATION_JSON));
+ Collections.singletonList(MediaType.APPLICATION_JSON));
updateUser.getHeaders().put(HttpHeaders.CONTENT_LENGTH,
- Collections.singletonList(updateUserPayload.length()));
+ Collections.singletonList(updateUserPayload.length()));
updateUser.setContent(updateUserPayload);
batchRequest.getItems().add(updateUser);
@@ -196,5 +193,4 @@ public class MergeLinkedAccountsWizardBuilder extends AjaxWizardBuilder<UserTO>
}
});
}
-
}
diff --git a/common/lib/src/main/java/org/apache/syncope/common/lib/to/ConnObjectTO.java b/common/lib/src/main/java/org/apache/syncope/common/lib/to/ConnObjectTO.java
index e6301b5..fc0df8c 100644
--- a/common/lib/src/main/java/org/apache/syncope/common/lib/to/ConnObjectTO.java
+++ b/common/lib/src/main/java/org/apache/syncope/common/lib/to/ConnObjectTO.java
@@ -37,8 +37,18 @@ public class ConnObjectTO implements Serializable {
private static final long serialVersionUID = 5139554911265442497L;
+ private String fiql;
+
private final Set<AttrTO> attrs = new LinkedHashSet<>();
+ public String getFiql() {
+ return fiql;
+ }
+
+ public void setFiql(final String fiql) {
+ this.fiql = fiql;
+ }
+
@XmlElementWrapper(name = "attrs")
@XmlElement(name = "attribute")
@JsonProperty("attrs")
@@ -54,6 +64,7 @@ public class ConnObjectTO implements Serializable {
@Override
public int hashCode() {
return new HashCodeBuilder().
+ append(fiql).
append(attrs).
build();
}
@@ -71,6 +82,7 @@ public class ConnObjectTO implements Serializable {
}
final ConnObjectTO other = (ConnObjectTO) obj;
return new EqualsBuilder().
+ append(fiql, other.fiql).
append(attrs, other.attrs).
build();
}
diff --git a/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/ReconQuery.java b/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/ReconQuery.java
index bc44695..4e671a1 100644
--- a/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/ReconQuery.java
+++ b/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/ReconQuery.java
@@ -48,8 +48,8 @@ public class ReconQuery implements Serializable {
return this;
}
- public Builder connObjectKeyValue(final String connObjectKeyValue) {
- instance.setConnObjectKeyValue(connObjectKeyValue);
+ public Builder fiql(final String fiql) {
+ instance.setFiql(fiql);
return this;
}
@@ -82,7 +82,7 @@ public class ReconQuery implements Serializable {
private String resourceKey;
- private String connObjectKeyValue;
+ private String fiql;
private Set<String> moreAttrsToGet;
@@ -115,13 +115,13 @@ public class ReconQuery implements Serializable {
this.resourceKey = resourceKey;
}
- public String getConnObjectKeyValue() {
- return connObjectKeyValue;
+ public String getFiql() {
+ return fiql;
}
- @QueryParam("connObjectKeyValue")
- public void setConnObjectKeyValue(final String connObjectKeyValue) {
- this.connObjectKeyValue = connObjectKeyValue;
+ @QueryParam(JAXRSService.PARAM_FIQL)
+ public void setFiql(final String fiql) {
+ this.fiql = fiql;
}
public Set<String> getMoreAttrsToGet() {
diff --git a/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/ReconciliationService.java b/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/ReconciliationService.java
index a2c8193..6e1e480 100644
--- a/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/ReconciliationService.java
+++ b/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/ReconciliationService.java
@@ -69,6 +69,7 @@ public interface ReconciliationService extends JAXRSService {
*
* @param query query conditions
* @param pushTask push specification
+ * @return push report
*/
@ApiResponses(
@ApiResponse(responseCode = "204", description = "Operation was successful"))
@@ -76,13 +77,14 @@ public interface ReconciliationService extends JAXRSService {
@Path("push")
@Consumes({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
@Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
- void push(@BeanParam ReconQuery query, @NotNull PushTaskTO pushTask);
+ List<ProvisioningReport> push(@BeanParam ReconQuery query, @NotNull PushTaskTO pushTask);
/**
* Pulls the matching user, group, any object or linked account from the External Resource into Syncope.
*
* @param query query conditions
* @param pullTask pull specification
+ * @return pull report
*/
@ApiResponses(
@ApiResponse(responseCode = "204", description = "Operation was successful"))
@@ -90,7 +92,7 @@ public interface ReconciliationService extends JAXRSService {
@Path("pull")
@Consumes({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
@Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
- void pull(@BeanParam ReconQuery query, @NotNull PullTaskTO pullTask);
+ List<ProvisioningReport> pull(@BeanParam ReconQuery query, @NotNull PullTaskTO pullTask);
/**
* Export a list of any objects matching the given query as CSV according to the provided specification.
diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/ReconciliationLogic.java b/core/logic/src/main/java/org/apache/syncope/core/logic/ReconciliationLogic.java
index 50daf7f..d1b650b 100644
--- a/core/logic/src/main/java/org/apache/syncope/core/logic/ReconciliationLogic.java
+++ b/core/logic/src/main/java/org/apache/syncope/core/logic/ReconciliationLogic.java
@@ -23,9 +23,11 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Method;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.Set;
+import java.util.stream.Stream;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.syncope.common.lib.SyncopeClientException;
@@ -41,7 +43,6 @@ import org.apache.syncope.common.lib.types.ClientExceptionType;
import org.apache.syncope.common.lib.types.MatchType;
import org.apache.syncope.common.lib.types.StandardEntitlement;
import org.apache.syncope.common.rest.api.beans.CSVPullSpec;
-import org.apache.syncope.common.rest.api.beans.ReconQuery;
import org.apache.syncope.core.persistence.api.dao.AnyDAO;
import org.apache.syncope.core.persistence.api.dao.AnyTypeDAO;
import org.apache.syncope.core.persistence.api.dao.ExternalResourceDAO;
@@ -68,6 +69,10 @@ import org.apache.syncope.core.persistence.api.dao.VirSchemaDAO;
import org.apache.syncope.core.persistence.api.dao.search.OrderByClause;
import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
import org.apache.syncope.core.persistence.api.entity.AnyUtils;
+import org.apache.syncope.core.persistence.api.entity.VirSchema;
+import org.apache.syncope.core.provisioning.api.pushpull.ConstantReconFilterBuilder;
+import org.apache.syncope.core.provisioning.api.pushpull.KeyValueReconFilterBuilder;
+import org.apache.syncope.core.provisioning.api.pushpull.ReconFilterBuilder;
import org.apache.syncope.core.provisioning.java.pushpull.stream.CSVStreamConnector;
import org.apache.syncope.core.provisioning.api.pushpull.SyncopeSinglePullExecutor;
import org.apache.syncope.core.provisioning.api.pushpull.SyncopeSinglePushExecutor;
@@ -82,11 +87,17 @@ import org.quartz.JobExecutionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Component;
-import org.springframework.util.CollectionUtils;
import org.apache.syncope.core.provisioning.api.pushpull.stream.SyncopeStreamPullExecutor;
import org.apache.syncope.core.provisioning.api.pushpull.stream.SyncopeStreamPushExecutor;
import org.apache.syncope.core.provisioning.api.utils.RealmUtils;
import org.apache.syncope.core.spring.security.AuthContextUtils;
+import org.identityconnectors.framework.common.objects.OperationOptions;
+import org.identityconnectors.framework.common.objects.SearchResult;
+import org.identityconnectors.framework.common.objects.SyncDeltaBuilder;
+import org.identityconnectors.framework.common.objects.SyncDeltaType;
+import org.identityconnectors.framework.common.objects.SyncToken;
+import org.identityconnectors.framework.common.objects.filter.Filter;
+import org.identityconnectors.framework.spi.SearchResultsHandler;
@Component
public class ReconciliationLogic extends AbstractTransactionalLogic<EntityTO> {
@@ -167,7 +178,7 @@ public class ReconciliationLogic extends AbstractTransactionalLogic<EntityTO> {
final String connObjectKeyValue,
final Set<Attribute> attrs) {
- ConnObjectTO connObjectTO = ConnObjectUtils.getConnObjectTO(attrs);
+ ConnObjectTO connObjectTO = ConnObjectUtils.getConnObjectTO(null, attrs);
connObjectTO.getAttrs().add(new AttrTO.Builder().
schema(connObjectKeyItem.getExtAttrName()).value(connObjectKeyValue).build());
connObjectTO.getAttrs().add(new AttrTO.Builder().
@@ -208,71 +219,111 @@ public class ReconciliationLogic extends AbstractTransactionalLogic<EntityTO> {
}
@PreAuthorize("hasRole('" + StandardEntitlement.RESOURCE_GET_CONNOBJECT + "')")
- public ReconStatus status(final ReconQuery query) {
- Provision provision = getProvision(query.getAnyTypeKey(), query.getResourceKey());
+ public ReconStatus status(final String anyTypeKey, final String resourceKey, final String anyKey) {
+ Provision provision = getProvision(anyTypeKey, resourceKey);
MappingItem connObjectKeyItem = MappingUtils.getConnObjectKeyItem(provision).
orElseThrow(() -> new NotFoundException(
"ConnObjectKey for " + provision.getAnyType().getKey()
+ " on resource '" + provision.getResource().getKey() + "'"));
+ Any<?> any = getAny(provision, anyKey);
+
ReconStatus status = new ReconStatus();
+ status.setMatchType(MatchType.ANY);
+ status.setAnyTypeKind(any.getType().getKind());
+ status.setAnyKey(any.getKey());
+ status.setRealm(any.getRealm().getFullPath());
+ status.setOnSyncope(getOnSyncope(any, connObjectKeyItem, provision));
+
+ List<ConnectorObject> connObjs = outboundMatcher.match(
+ connFactory.getConnector(provision.getResource()), any, provision, Optional.empty());
+ if (!connObjs.isEmpty()) {
+ status.setOnResource(ConnObjectUtils.getConnObjectTO(
+ outboundMatcher.getFIQL(connObjs.get(0), provision), connObjs.get(0).getAttributes()));
+
+ if (connObjs.size() > 1) {
+ LOG.warn("Expected single match, found {}", connObjs);
+ } else {
+ virAttrHandler.setValues(any, connObjs.get(0));
+ }
+ }
- if (query.getConnObjectKeyValue() != null) {
- inboundMatcher.matchByConnObjectKeyValue(connObjectKeyItem, query.getConnObjectKeyValue(), provision).
- stream().findFirst().ifPresent(match -> {
- if (match.getAny() != null) {
- status.setMatchType(MatchType.ANY);
- status.setAnyTypeKind(match.getAny().getType().getKind());
- status.setAnyKey(match.getAny().getKey());
- status.setRealm(match.getAny().getRealm().getFullPath());
- status.setOnSyncope(getOnSyncope(match.getAny(), connObjectKeyItem, provision));
- } else if (match.getLinkedAccount() != null) {
- status.setMatchType(MatchType.LINKED_ACCOUNT);
- status.setAnyTypeKind(AnyTypeKind.USER);
- status.setAnyKey(match.getLinkedAccount().getOwner().getKey());
- status.setRealm(match.getLinkedAccount().getOwner().getRealm().getFullPath());
- status.setOnSyncope(getOnSyncope(match.getLinkedAccount(), connObjectKeyItem, provision));
- }
- });
+ return status;
+ }
- Optional<String[]> moreAttrsToGet = CollectionUtils.isEmpty(query.getMoreAttrsToGet())
- ? Optional.empty()
- : Optional.of(query.getMoreAttrsToGet().toArray(new String[0]));
+ private SyncDeltaBuilder syncDeltaBuilder(
+ final Provision provision,
+ final Filter filter,
+ final Set<String> moreAttrsToGet) {
+
+ Stream<MappingItem> mapItems = Stream.concat(
+ provision.getMapping().getItems().stream(),
+ virSchemaDAO.findByProvision(provision).stream().map(VirSchema::asLinkingMappingItem));
+ OperationOptions options = MappingUtils.buildOperationOptions(mapItems, moreAttrsToGet.toArray(new String[0]));
+
+ SyncDeltaBuilder syncDeltaBuilder = new SyncDeltaBuilder().
+ setToken(new SyncToken("")).
+ setDeltaType(SyncDeltaType.CREATE_OR_UPDATE).
+ setObjectClass(provision.getObjectClass());
+ connFactory.getConnector(provision.getResource()).
+ search(provision.getObjectClass(), filter, new SearchResultsHandler() {
+
+ @Override
+ public boolean handle(final ConnectorObject connObj) {
+ syncDeltaBuilder.setObject(connObj);
+ return false;
+ }
+
+ @Override
+ public void handleResult(final SearchResult sr) {
+ // do nothing
+ }
+ }, 1, null, Collections.emptyList(), options);
+
+ return syncDeltaBuilder;
+ }
- outboundMatcher.matchByConnObjectKeyValue(
- connFactory.getConnector(provision.getResource()),
- connObjectKeyItem,
- query.getConnObjectKeyValue(),
- provision,
- moreAttrsToGet,
- Optional.empty()).
- ifPresent(connObj -> {
- status.setOnResource(ConnObjectUtils.getConnObjectTO(connObj.getAttributes()));
+ @PreAuthorize("hasRole('" + StandardEntitlement.RESOURCE_GET_CONNOBJECT + "')")
+ public ReconStatus status(
+ final String anyTypeKey,
+ final String resourceKey,
+ final Filter filter,
+ final Set<String> moreAttrsToGet) {
- if (status.getMatchType() == MatchType.ANY && StringUtils.isNotBlank(status.getAnyKey())) {
- virAttrHandler.setValues(getAny(provision, status.getAnyKey()), connObj);
- }
- });
- }
- if (query.getAnyKey() != null) {
- Any<?> any = getAny(provision, query.getAnyKey());
- status.setMatchType(MatchType.ANY);
- status.setAnyTypeKind(any.getType().getKind());
- status.setAnyKey(any.getKey());
- status.setRealm(any.getRealm().getFullPath());
- status.setOnSyncope(getOnSyncope(any, connObjectKeyItem, provision));
-
- List<ConnectorObject> connObjs = outboundMatcher.match(
- connFactory.getConnector(provision.getResource()), any, provision, Optional.empty());
- if (!connObjs.isEmpty()) {
- status.setOnResource(ConnObjectUtils.getConnObjectTO(connObjs.get(0).getAttributes()));
-
- if (connObjs.size() > 1) {
- LOG.warn("Expected single match, found {}", connObjs);
- } else {
- virAttrHandler.setValues(any, connObjs.get(0));
+ Provision provision = getProvision(anyTypeKey, resourceKey);
+
+ SyncDeltaBuilder syncDeltaBuilder = syncDeltaBuilder(provision, filter, moreAttrsToGet);
+
+ ReconStatus status = new ReconStatus();
+ if (syncDeltaBuilder.getObject() != null) {
+ MappingItem connObjectKeyItem = MappingUtils.getConnObjectKeyItem(provision).
+ orElseThrow(() -> new NotFoundException(
+ "ConnObjectKey for " + provision.getAnyType().getKey()
+ + " on resource '" + provision.getResource().getKey() + "'"));
+
+ inboundMatcher.match(syncDeltaBuilder.build(), provision).stream().findFirst().ifPresent(match -> {
+ if (match.getAny() != null) {
+ status.setMatchType(MatchType.ANY);
+ status.setAnyTypeKind(match.getAny().getType().getKind());
+ status.setAnyKey(match.getAny().getKey());
+ status.setRealm(match.getAny().getRealm().getFullPath());
+ status.setOnSyncope(getOnSyncope(match.getAny(), connObjectKeyItem, provision));
+ } else if (match.getLinkedAccount() != null) {
+ status.setMatchType(MatchType.LINKED_ACCOUNT);
+ status.setAnyTypeKind(AnyTypeKind.USER);
+ status.setAnyKey(match.getLinkedAccount().getOwner().getKey());
+ status.setRealm(match.getLinkedAccount().getOwner().getRealm().getFullPath());
+ status.setOnSyncope(getOnSyncope(match.getLinkedAccount(), connObjectKeyItem, provision));
}
+ });
+
+ status.setOnResource(ConnObjectUtils.getConnObjectTO(
+ outboundMatcher.getFIQL(syncDeltaBuilder.getObject(), provision),
+ syncDeltaBuilder.getObject().getAttributes()));
+
+ if (status.getMatchType() == MatchType.ANY && StringUtils.isNotBlank(status.getAnyKey())) {
+ virAttrHandler.setValues(getAny(provision, status.getAnyKey()), syncDeltaBuilder.getObject());
}
}
@@ -280,102 +331,105 @@ public class ReconciliationLogic extends AbstractTransactionalLogic<EntityTO> {
}
@PreAuthorize("hasRole('" + StandardEntitlement.TASK_EXECUTE + "')")
- public void push(final ReconQuery query, final PushTaskTO pushTask) {
- Provision provision = getProvision(query.getAnyTypeKey(), query.getResourceKey());
+ public List<ProvisioningReport> push(
+ final String anyTypeKey,
+ final String resourceKey,
+ final String anyKey,
+ final PushTaskTO pushTask) {
- MappingItem connObjectKeyItem = MappingUtils.getConnObjectKeyItem(provision).
- orElseThrow(() -> new NotFoundException(
- "ConnObjectKey for " + provision.getAnyType().getKey()
- + " on resource '" + provision.getResource().getKey() + "'"));
+ Provision provision = getProvision(anyTypeKey, resourceKey);
SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.Reconciliation);
List<ProvisioningReport> results = new ArrayList<>();
-
- if (query.getConnObjectKeyValue() != null) {
- inboundMatcher.matchByConnObjectKeyValue(connObjectKeyItem, query.getConnObjectKeyValue(), provision).
- stream().findFirst().ifPresent(match -> {
- try {
- if (match.getMatchTarget() == MatchType.ANY) {
- results.addAll(singlePushExecutor.push(
- provision,
- connFactory.getConnector(provision.getResource()),
- match.getAny(),
- pushTask));
- if (!results.isEmpty()
- && results.get(0).getStatus() == ProvisioningReport.Status.FAILURE) {
-
- sce.getElements().add(results.get(0).getMessage());
- }
- } else {
- ProvisioningReport result = singlePushExecutor.push(
- provision,
- connFactory.getConnector(provision.getResource()),
- match.getLinkedAccount(),
- pushTask);
- if (result.getStatus() == ProvisioningReport.Status.FAILURE) {
- sce.getElements().add(result.getMessage());
- } else {
- results.add(result);
- }
- }
- } catch (JobExecutionException e) {
- sce.getElements().add(e.getMessage());
- }
- });
- }
-
- if (sce.isEmpty() && results.isEmpty() && query.getAnyKey() != null) {
- try {
- results.addAll(singlePushExecutor.push(
- provision,
- connFactory.getConnector(provision.getResource()),
- getAny(provision, query.getAnyKey()),
- pushTask));
- if (!results.isEmpty() && results.get(0).getStatus() == ProvisioningReport.Status.FAILURE) {
- sce.getElements().add(results.get(0).getMessage());
- }
- } catch (JobExecutionException e) {
- sce.getElements().add(e.getMessage());
+ try {
+ results.addAll(singlePushExecutor.push(
+ provision,
+ connFactory.getConnector(provision.getResource()),
+ getAny(provision, anyKey),
+ pushTask));
+ if (!results.isEmpty() && results.get(0).getStatus() == ProvisioningReport.Status.FAILURE) {
+ sce.getElements().add(results.get(0).getMessage());
}
+ } catch (JobExecutionException e) {
+ sce.getElements().add(e.getMessage());
}
if (!sce.isEmpty()) {
throw sce;
}
+
+ return results;
}
@PreAuthorize("hasRole('" + StandardEntitlement.TASK_EXECUTE + "')")
- public void pull(final ReconQuery query, final PullTaskTO pullTask) {
- Provision provision = getProvision(query.getAnyTypeKey(), query.getResourceKey());
+ public List<ProvisioningReport> push(
+ final String anyTypeKey,
+ final String resourceKey,
+ final Filter filter,
+ final Set<String> moreAttrsToGet,
+ final PushTaskTO pushTask) {
+
+ Provision provision = getProvision(anyTypeKey, resourceKey);
- Optional<String> connObjectKeyValue = Optional.ofNullable(query.getConnObjectKeyValue());
- if (query.getAnyKey() != null) {
- Any<?> any = getAny(provision, query.getAnyKey());
- connObjectKeyValue = mappingManager.getConnObjectKeyValue(any, provision);
+ SyncDeltaBuilder syncDeltaBuilder = syncDeltaBuilder(provision, filter, moreAttrsToGet);
+
+ SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.Reconciliation);
+ List<ProvisioningReport> results = new ArrayList<>();
+
+ if (syncDeltaBuilder.getObject() != null) {
+ inboundMatcher.match(syncDeltaBuilder.build(), provision).stream().findFirst().ifPresent(match -> {
+ try {
+ if (match.getMatchTarget() == MatchType.ANY) {
+ results.addAll(singlePushExecutor.push(
+ provision,
+ connFactory.getConnector(provision.getResource()),
+ match.getAny(),
+ pushTask));
+ if (!results.isEmpty() && results.get(0).getStatus() == ProvisioningReport.Status.FAILURE) {
+ sce.getElements().add(results.get(0).getMessage());
+ }
+ } else {
+ ProvisioningReport result = singlePushExecutor.push(
+ provision,
+ connFactory.getConnector(provision.getResource()),
+ match.getLinkedAccount(),
+ pushTask);
+ if (result.getStatus() == ProvisioningReport.Status.FAILURE) {
+ sce.getElements().add(result.getMessage());
+ } else {
+ results.add(result);
+ }
+ }
+ } catch (JobExecutionException e) {
+ sce.getElements().add(e.getMessage());
+ }
+ });
}
- if (!connObjectKeyValue.isPresent()) {
- throw new NotFoundException(
- "ConnObjectKey for " + provision.getAnyType().getKey()
- + " on resource '" + provision.getResource().getKey() + "'");
+
+ if (!sce.isEmpty()) {
+ throw sce;
}
+ return results;
+ }
+
+ private List<ProvisioningReport> pull(
+ final Provision provision,
+ final ReconFilterBuilder reconFilterBuilder,
+ final PullTaskTO pullTask) {
+
if (pullTask.getDestinationRealm() == null || realmDAO.findByFullPath(pullTask.getDestinationRealm()) == null) {
throw new NotFoundException("Realm " + pullTask.getDestinationRealm());
}
- if (!provision.getMapping().getConnObjectKeyItem().isPresent()) {
- throw new NotFoundException(
- "ConnObjectKey cannot be determines for mapping " + provision.getMapping().getKey());
- }
-
SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.Reconciliation);
+ List<ProvisioningReport> results = new ArrayList<>();
try {
- List<ProvisioningReport> results = singlePullExecutor.pull(
+ results.addAll(singlePullExecutor.pull(
provision,
connFactory.getConnector(provision.getResource()),
- provision.getMapping().getConnObjectKeyItem().get().getExtAttrName(),
- connObjectKeyValue.get(),
- pullTask);
+ reconFilterBuilder,
+ pullTask));
if (!results.isEmpty() && results.get(0).getStatus() == ProvisioningReport.Status.FAILURE) {
sce.getElements().add(results.get(0).getMessage());
}
@@ -386,6 +440,52 @@ public class ReconciliationLogic extends AbstractTransactionalLogic<EntityTO> {
if (!sce.isEmpty()) {
throw sce;
}
+
+ return results;
+ }
+
+ @PreAuthorize("hasRole('" + StandardEntitlement.TASK_EXECUTE + "')")
+ public List<ProvisioningReport> pull(
+ final String anyTypeKey,
+ final String resourceKey,
+ final String anyKey,
+ final PullTaskTO pullTask) {
+
+ Provision provision = getProvision(anyTypeKey, resourceKey);
+
+ Any<?> any = getAny(provision, anyKey);
+
+ if (!provision.getMapping().getConnObjectKeyItem().isPresent()) {
+ throw new NotFoundException(
+ "ConnObjectKey cannot be determined for mapping " + provision.getMapping().getKey());
+ }
+
+ String connObjectKeyValue = mappingManager.getConnObjectKeyValue(any, provision).
+ orElseThrow(() -> new NotFoundException(
+ "ConnObjectKey for " + provision.getAnyType().getKey()
+ + " on resource '" + provision.getResource().getKey() + "'"));
+
+ return pull(
+ provision,
+ new KeyValueReconFilterBuilder(
+ provision.getMapping().getConnObjectKeyItem().get().getExtAttrName(), connObjectKeyValue),
+ pullTask);
+ }
+
+ @PreAuthorize("hasRole('" + StandardEntitlement.TASK_EXECUTE + "')")
+ public List<ProvisioningReport> pull(
+ final String anyTypeKey,
+ final String resourceKey,
+ final Filter filter,
+ final Set<String> moreAttrsToGet,
+ final PullTaskTO pullTask) {
+
+ Provision provision = getProvision(anyTypeKey, resourceKey);
+
+ return pull(
+ provision,
+ new ConstantReconFilterBuilder(filter),
+ pullTask);
}
private CsvSchema.Builder csvSchema(final AbstractCSVSpec spec) {
diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/ResourceLogic.java b/core/logic/src/main/java/org/apache/syncope/core/logic/ResourceLogic.java
index d059ede..217df7b 100644
--- a/core/logic/src/main/java/org/apache/syncope/core/logic/ResourceLogic.java
+++ b/core/logic/src/main/java/org/apache/syncope/core/logic/ResourceLogic.java
@@ -335,7 +335,8 @@ public class ResourceLogic extends AbstractTransactionalLogic<ResourceTO> {
virAttrHandler.setValues(any, connObjs.get(0));
}
- return ConnObjectUtils.getConnObjectTO(connObjs.get(0).getAttributes());
+ return ConnObjectUtils.getConnObjectTO(
+ outboundMatcher.getFIQL(connObjs.get(0), provision), connObjs.get(0).getAttributes());
}
@PreAuthorize("hasRole('" + StandardEntitlement.RESOURCE_GET_CONNOBJECT + "')")
@@ -352,20 +353,18 @@ public class ResourceLogic extends AbstractTransactionalLogic<ResourceTO> {
"ConnObjectKey mapping for " + provision.getAnyType().getKey()
+ " on resource '" + provision.getResource().getKey() + "'"));
- Optional<ConnectorObject> connObj = outboundMatcher.matchByConnObjectKeyValue(
+ return outboundMatcher.matchByConnObjectKeyValue(
connFactory.getConnector(provision.getResource()),
connObjectKeyItem,
connObjectKeyValue,
provision,
Optional.empty(),
- Optional.empty());
- if (connObj.isPresent()) {
- return ConnObjectUtils.getConnObjectTO(connObj.get().getAttributes());
- }
-
- throw new NotFoundException(
+ Optional.empty()).
+ map(connectorObject -> ConnObjectUtils.getConnObjectTO(
+ outboundMatcher.getFIQL(connectorObject, provision), connectorObject.getAttributes())).
+ orElseThrow(() -> new NotFoundException(
"Object " + connObjectKeyValue + " with class " + provision.getObjectClass()
- + " not found on resource " + provision.getResource().getKey());
+ + " not found on resource " + provision.getResource().getKey()));
}
@PreAuthorize("hasRole('" + StandardEntitlement.RESOURCE_LIST_CONNOBJECT + "')")
@@ -380,6 +379,7 @@ public class ResourceLogic extends AbstractTransactionalLogic<ResourceTO> {
final List<OrderByClause> orderBy) {
ExternalResource resource;
+ Provision provision;
ObjectClass objectClass;
OperationOptions options;
if (SyncopeConstants.REALM_ANYTYPE.equals(anyTypeKey)) {
@@ -391,11 +391,12 @@ public class ResourceLogic extends AbstractTransactionalLogic<ResourceTO> {
throw new NotFoundException("Realm provisioning for resource '" + key + "'");
}
+ provision = null;
objectClass = resource.getOrgUnit().getObjectClass();
options = MappingUtils.buildOperationOptions(
resource.getOrgUnit().getItems().stream(), moreAttrsToGet.toArray(new String[0]));
} else {
- Provision provision = getProvision(key, anyTypeKey);
+ provision = getProvision(key, anyTypeKey);
resource = provision.getResource();
objectClass = provision.getObjectClass();
@@ -413,7 +414,9 @@ public class ResourceLogic extends AbstractTransactionalLogic<ResourceTO> {
@Override
public boolean handle(final ConnectorObject connectorObject) {
- connObjects.add(ConnObjectUtils.getConnObjectTO(connectorObject.getAttributes()));
+ connObjects.add(ConnObjectUtils.getConnObjectTO(
+ provision == null ? null : outboundMatcher.getFIQL(connectorObject, provision),
+ connectorObject.getAttributes()));
// safety protection against uncontrolled result size
count++;
return count < size;
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/PushCorrelationRule.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/PushCorrelationRule.java
index 0b042a5..542f0c8 100644
--- a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/PushCorrelationRule.java
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/PushCorrelationRule.java
@@ -18,9 +18,12 @@
*/
package org.apache.syncope.core.persistence.api.dao;
+import java.util.function.BiFunction;
import org.apache.syncope.common.lib.policy.PushCorrelationRuleConf;
import org.apache.syncope.core.persistence.api.entity.Any;
import org.apache.syncope.core.persistence.api.entity.resource.Provision;
+import org.identityconnectors.framework.common.objects.ConnectorObject;
+import org.identityconnectors.framework.common.objects.Uid;
import org.identityconnectors.framework.common.objects.filter.Filter;
/**
@@ -28,15 +31,34 @@ import org.identityconnectors.framework.common.objects.filter.Filter;
*/
public interface PushCorrelationRule {
+ /**
+ * Default FIQL builder using __UID__.
+ */
+ BiFunction<ConnectorObject, Provision, String> DEFAULT_FIQL_BUILDER =
+ (connectorObject, provision) -> Uid.NAME + "==" + connectorObject.getUid().getUidValue();
+
default void setConf(PushCorrelationRuleConf conf) {
}
/**
- * Return a search condition.
+ * Returns a filter to match the given any with a connector object on the external resource identified by
+ * the given provision.
*
* @param any user, group or any object
* @param provision resource provision
- * @return search condition.
+ * @return filter.
*/
Filter getFilter(Any<?> any, Provision provision);
+
+ /**
+ * Returns a FIQL string to match the given connector object when searching into the external resource identified by
+ * the given provision.
+ *
+ * @param connectorObject connector object
+ * @param provision resource provision
+ * @return fiql
+ */
+ default String getFiql(ConnectorObject connectorObject, Provision provision) {
+ return DEFAULT_FIQL_BUILDER.apply(connectorObject, provision);
+ }
}
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/DefaultPushCorrelationRule.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/DefaultPushCorrelationRule.java
index 03d1e3d..19ac31c 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/DefaultPushCorrelationRule.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/DefaultPushCorrelationRule.java
@@ -19,10 +19,15 @@
package org.apache.syncope.core.persistence.jpa.dao;
import java.util.ArrayList;
+import java.util.Base64;
import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
import org.apache.commons.lang3.tuple.Pair;
+import org.apache.cxf.jaxrs.ext.search.client.CompleteCondition;
import org.apache.syncope.common.lib.policy.DefaultPushCorrelationRuleConf;
import org.apache.syncope.common.lib.policy.PushCorrelationRuleConf;
+import org.apache.syncope.common.lib.search.ConnObjectTOFiqlSearchConditionBuilder;
import org.apache.syncope.common.lib.types.MappingPurpose;
import org.apache.syncope.core.persistence.api.entity.resource.Provision;
import org.identityconnectors.framework.common.objects.Attribute;
@@ -32,18 +37,26 @@ import org.apache.syncope.core.persistence.api.entity.Any;
import org.apache.syncope.core.provisioning.api.AccountGetter;
import org.apache.syncope.core.provisioning.api.MappingManager;
import org.apache.syncope.core.provisioning.api.PlainAttrGetter;
+import org.identityconnectors.common.security.GuardedByteArray;
+import org.identityconnectors.common.security.GuardedString;
+import org.identityconnectors.common.security.SecurityUtil;
import org.identityconnectors.framework.common.objects.AttributeBuilder;
+import org.identityconnectors.framework.common.objects.ConnectorObject;
import org.identityconnectors.framework.common.objects.filter.Filter;
import org.identityconnectors.framework.common.objects.filter.FilterBuilder;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.util.CollectionUtils;
@PushCorrelationRuleConfClass(DefaultPushCorrelationRuleConf.class)
public class DefaultPushCorrelationRule implements PushCorrelationRule {
+ protected static final ConnObjectTOFiqlSearchConditionBuilder FIQL_BUILDER =
+ new ConnObjectTOFiqlSearchConditionBuilder();
+
@Autowired
- private MappingManager mappingManager;
+ protected MappingManager mappingManager;
- private DefaultPushCorrelationRuleConf conf;
+ protected DefaultPushCorrelationRuleConf conf;
@Override
public void setConf(final PushCorrelationRuleConf conf) {
@@ -60,8 +73,7 @@ public class DefaultPushCorrelationRule implements PushCorrelationRule {
List<Filter> filters = new ArrayList<>();
provision.getMapping().getItems().stream().filter(
- item -> conf.getSchemas().contains(item.getIntAttrName())
- && (item.getPurpose() == MappingPurpose.PROPAGATION || item.getPurpose() == MappingPurpose.BOTH)).
+ item -> conf.getSchemas().contains(item.getIntAttrName()) && item.getPurpose() != MappingPurpose.NONE).
forEach(item -> {
Pair<String, Attribute> attr = mappingManager.prepareAttr(
provision,
@@ -90,4 +102,45 @@ public class DefaultPushCorrelationRule implements PushCorrelationRule {
? FilterBuilder.or(filters)
: FilterBuilder.and(filters);
}
+
+ @Override
+ public String getFiql(final ConnectorObject connectorObject, final Provision provision) {
+ List<CompleteCondition> conditions = new ArrayList<>();
+
+ provision.getMapping().getItems().stream().filter(
+ item -> conf.getSchemas().contains(item.getIntAttrName()) && item.getPurpose() != MappingPurpose.NONE).
+ forEach(item -> Optional.ofNullable(connectorObject.getAttributeByName(item.getExtAttrName())).
+ ifPresent(attr -> {
+ if (CollectionUtils.isEmpty(attr.getValue())) {
+ conditions.add(FIQL_BUILDER.isNull(attr.getName()));
+ } else {
+ List<CompleteCondition> valueConditions = new ArrayList<>();
+
+ attr.getValue().stream().filter(Objects::nonNull).forEach(value -> {
+ if (value instanceof GuardedString) {
+ valueConditions.add(FIQL_BUILDER.is(attr.getName()).
+ equalTo(SecurityUtil.decrypt((GuardedString) value)));
+ } else if (value instanceof GuardedByteArray) {
+ valueConditions.add(FIQL_BUILDER.is(attr.getName()).
+ equalTo(new String(SecurityUtil.decrypt((GuardedByteArray) value))));
+ } else if (value instanceof byte[]) {
+ valueConditions.add(FIQL_BUILDER.is(attr.getName()).
+ equalTo(Base64.getEncoder().encodeToString((byte[]) value)));
+ } else {
+ valueConditions.add(FIQL_BUILDER.is(attr.getName()).equalTo(value.toString()));
+ }
+ });
+
+ if (!valueConditions.isEmpty()) {
+ conditions.add(valueConditions.size() == 1
+ ? valueConditions.get(0)
+ : FIQL_BUILDER.and(valueConditions));
+ }
+ }
+ }));
+
+ return conf.isOrSchemas()
+ ? FIQL_BUILDER.or(conditions).query()
+ : FIQL_BUILDER.and(conditions).query();
+ }
}
diff --git a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/propagation/PropagationReporter.java b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/propagation/PropagationReporter.java
index 814f75b..fd7ccbe 100644
--- a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/propagation/PropagationReporter.java
+++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/propagation/PropagationReporter.java
@@ -44,6 +44,7 @@ public interface PropagationReporter {
* @param taskInfo propagation task
* @param execStatus propagation execution status
* @param failureReason propagation execution failure message
+ * @param fiql FIQL string to match the connector objects into the external resource
* @param beforeObj retrieved connector object before operation execution
* @param afterObj retrieved connector object after operation execution
*/
@@ -51,6 +52,7 @@ public interface PropagationReporter {
PropagationTaskInfo taskInfo,
ExecStatus execStatus,
String failureReason,
+ String fiql,
ConnectorObject beforeObj,
ConnectorObject afterObj);
diff --git a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/SyncopeSinglePullExecutor.java b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/ConstantReconFilterBuilder.java
similarity index 59%
copy from core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/SyncopeSinglePullExecutor.java
copy to core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/ConstantReconFilterBuilder.java
index ec9cd8b..d980602 100644
--- a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/SyncopeSinglePullExecutor.java
+++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/ConstantReconFilterBuilder.java
@@ -18,19 +18,18 @@
*/
package org.apache.syncope.core.provisioning.api.pushpull;
-import org.apache.syncope.common.lib.to.ProvisioningReport;
-import java.util.List;
-import org.apache.syncope.common.lib.to.PullTaskTO;
-import org.apache.syncope.core.persistence.api.entity.resource.Provision;
-import org.apache.syncope.core.provisioning.api.Connector;
-import org.quartz.JobExecutionException;
+import org.identityconnectors.framework.common.objects.filter.Filter;
-public interface SyncopeSinglePullExecutor {
+public class ConstantReconFilterBuilder implements ReconFilterBuilder {
- List<ProvisioningReport> pull(
- Provision provision,
- Connector connector,
- String connObjectKey,
- String connObjectValue,
- PullTaskTO pullTaskTO) throws JobExecutionException;
+ private final Filter filter;
+
+ public ConstantReconFilterBuilder(final Filter filter) {
+ this.filter = filter;
+ }
+
+ @Override
+ public Filter build() {
+ return filter;
+ }
}
diff --git a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/SyncopeSinglePullExecutor.java b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/KeyValueReconFilterBuilder.java
similarity index 58%
copy from core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/SyncopeSinglePullExecutor.java
copy to core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/KeyValueReconFilterBuilder.java
index ec9cd8b..1b5d349 100644
--- a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/SyncopeSinglePullExecutor.java
+++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/KeyValueReconFilterBuilder.java
@@ -18,19 +18,23 @@
*/
package org.apache.syncope.core.provisioning.api.pushpull;
-import org.apache.syncope.common.lib.to.ProvisioningReport;
-import java.util.List;
-import org.apache.syncope.common.lib.to.PullTaskTO;
-import org.apache.syncope.core.persistence.api.entity.resource.Provision;
-import org.apache.syncope.core.provisioning.api.Connector;
-import org.quartz.JobExecutionException;
+import org.identityconnectors.framework.common.objects.AttributeBuilder;
+import org.identityconnectors.framework.common.objects.filter.Filter;
+import org.identityconnectors.framework.common.objects.filter.FilterBuilder;
-public interface SyncopeSinglePullExecutor {
+public class KeyValueReconFilterBuilder implements ReconFilterBuilder {
- List<ProvisioningReport> pull(
- Provision provision,
- Connector connector,
- String connObjectKey,
- String connObjectValue,
- PullTaskTO pullTaskTO) throws JobExecutionException;
+ private final String key;
+
+ private final String value;
+
+ public KeyValueReconFilterBuilder(final String key, final String value) {
+ this.key = key;
+ this.value = value;
+ }
+
+ @Override
+ public Filter build() {
+ return FilterBuilder.equalTo(AttributeBuilder.build(key, value));
+ }
}
diff --git a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/SyncopeSinglePullExecutor.java b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/SyncopeSinglePullExecutor.java
index ec9cd8b..57db2ff 100644
--- a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/SyncopeSinglePullExecutor.java
+++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/SyncopeSinglePullExecutor.java
@@ -30,7 +30,6 @@ public interface SyncopeSinglePullExecutor {
List<ProvisioningReport> pull(
Provision provision,
Connector connector,
- String connObjectKey,
- String connObjectValue,
+ ReconFilterBuilder reconFilterBuilder,
PullTaskTO pullTaskTO) throws JobExecutionException;
}
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AbstractAnyDataBinder.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AbstractAnyDataBinder.java
index ea027c3..07bb31f 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AbstractAnyDataBinder.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AbstractAnyDataBinder.java
@@ -83,9 +83,13 @@ import org.apache.syncope.core.provisioning.api.VirAttrHandler;
import org.apache.syncope.core.provisioning.api.IntAttrNameParser;
import org.apache.syncope.core.provisioning.api.PropagationByResource;
import org.apache.syncope.core.provisioning.api.jexl.JexlUtils;
+import org.apache.syncope.core.provisioning.java.pushpull.OutboundMatcher;
import org.apache.syncope.core.provisioning.java.utils.ConnObjectUtils;
import org.apache.syncope.core.provisioning.java.utils.MappingUtils;
import org.identityconnectors.framework.common.objects.Attribute;
+import org.identityconnectors.framework.common.objects.AttributeBuilder;
+import org.identityconnectors.framework.common.objects.ConnectorObject;
+import org.identityconnectors.framework.common.objects.ConnectorObjectBuilder;
import org.identityconnectors.framework.common.objects.Uid;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -143,6 +147,9 @@ abstract class AbstractAnyDataBinder {
@Autowired
protected IntAttrNameParser intAttrNameParser;
+ @Autowired
+ protected OutboundMatcher outboundMatcher;
+
protected void setRealm(final Any<?> any, final AnyPatch anyPatch) {
if (anyPatch.getRealm() != null && StringUtils.isNotBlank(anyPatch.getRealm().getValue())) {
Realm newRealm = realmDAO.findByFullPath(anyPatch.getRealm().getValue());
@@ -169,11 +176,20 @@ abstract class AbstractAnyDataBinder {
Pair<String, Set<Attribute>> prepared = mappingManager.prepareAttrsFromAny(
any, password, changePwd, true, provision);
- ConnObjectTO connObjectTO = ConnObjectUtils.getConnObjectTO(prepared.getRight());
- connObjectTO.getAttrs().add(new AttrTO.Builder().
- schema(connObjectKeyItem.getExtAttrName()).value(prepared.getLeft()).build());
- connObjectTO.getAttrs().add(new AttrTO.Builder().
- schema(Uid.NAME).value(prepared.getLeft()).build());
+ ConnObjectTO connObjectTO;
+ if (StringUtils.isBlank(prepared.getLeft())) {
+ connObjectTO = ConnObjectUtils.getConnObjectTO(null, prepared.getRight());
+ } else {
+ ConnectorObject connectorObject = new ConnectorObjectBuilder().
+ addAttributes(prepared.getRight()).
+ addAttribute(new Uid(prepared.getLeft())).
+ addAttribute(AttributeBuilder.build(connObjectKeyItem.getExtAttrName(), prepared.getLeft())).
+ build();
+
+ connObjectTO = ConnObjectUtils.getConnObjectTO(
+ outboundMatcher.getFIQL(connectorObject, provision),
+ connectorObject.getAttributes());
+ }
onResources.put(provision.getResource().getKey(), connObjectTO);
}));
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/AbstractPropagationTaskExecutor.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/AbstractPropagationTaskExecutor.java
index aaf3871..16aea1d 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/AbstractPropagationTaskExecutor.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/AbstractPropagationTaskExecutor.java
@@ -445,9 +445,17 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
taskDAO.save(task);
}
+ String fiql = provision == null
+ ? null
+ : afterObj != null
+ ? outboundMatcher.getFIQL(afterObj, provision)
+ : beforeObj != null
+ ? outboundMatcher.getFIQL(beforeObj, provision)
+ : null;
reporter.onSuccessOrNonPriorityResourceFailures(taskInfo,
ExecStatus.valueOf(execution.getStatus()),
failureReason,
+ fiql,
beforeObj,
afterObj);
}
@@ -526,6 +534,7 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
ExecStatus.valueOf(execution.getStatus()),
rejectReason,
null,
+ null,
null);
return execution;
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/DefaultPropagationReporter.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/DefaultPropagationReporter.java
index 5fbb4b0..010daab 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/DefaultPropagationReporter.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/DefaultPropagationReporter.java
@@ -50,6 +50,7 @@ public class DefaultPropagationReporter implements PropagationReporter {
final PropagationTaskInfo taskInfo,
final ExecStatus executionStatus,
final String failureReason,
+ final String fiql,
final ConnectorObject beforeObj,
final ConnectorObject afterObj) {
@@ -59,11 +60,11 @@ public class DefaultPropagationReporter implements PropagationReporter {
status.setFailureReason(failureReason);
if (beforeObj != null) {
- status.setBeforeObj(ConnObjectUtils.getConnObjectTO(beforeObj.getAttributes()));
+ status.setBeforeObj(ConnObjectUtils.getConnObjectTO(fiql, beforeObj.getAttributes()));
}
if (afterObj != null) {
- status.setAfterObj(ConnObjectUtils.getConnObjectTO(afterObj.getAttributes()));
+ status.setAfterObj(ConnObjectUtils.getConnObjectTO(fiql, afterObj.getAttributes()));
}
add(status);
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/PriorityPropagationTaskExecutor.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/PriorityPropagationTaskExecutor.java
index ecd4764..e402638 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/PriorityPropagationTaskExecutor.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/PriorityPropagationTaskExecutor.java
@@ -117,7 +117,7 @@ public class PriorityPropagationTaskExecutor extends AbstractPropagationTaskExec
if (nullPriorityAsync) {
reporter.onSuccessOrNonPriorityResourceFailures(
- taskInfo, ExecStatus.CREATED, null, null, null);
+ taskInfo, ExecStatus.CREATED, null, null, null, null);
}
} catch (Exception e) {
LOG.error("While submitting task for async execution", taskInfo, e);
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/OutboundMatcher.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/OutboundMatcher.java
index c4c2e2e..1add311 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/OutboundMatcher.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/OutboundMatcher.java
@@ -94,6 +94,12 @@ public class OutboundMatcher {
return rule;
}
+ public String getFIQL(final ConnectorObject connectorObject, final Provision provision) {
+ return rule(provision).
+ map(rule -> rule.getFiql(connectorObject, provision)).
+ orElseGet(() -> PushCorrelationRule.DEFAULT_FIQL_BUILDER.apply(connectorObject, provision));
+ }
+
public List<ConnectorObject> match(
final PropagationTask task,
final Connector connector,
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/SinglePullJobDelegate.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/SinglePullJobDelegate.java
index 16beb2d..eb906a6 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/SinglePullJobDelegate.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/SinglePullJobDelegate.java
@@ -50,9 +50,6 @@ import org.apache.syncope.core.provisioning.api.pushpull.SyncopeSinglePullExecut
import org.apache.syncope.core.provisioning.java.utils.MappingUtils;
import org.apache.syncope.core.provisioning.java.utils.TemplateUtils;
import org.apache.syncope.core.spring.ImplementationManager;
-import org.identityconnectors.framework.common.objects.AttributeBuilder;
-import org.identityconnectors.framework.common.objects.filter.Filter;
-import org.identityconnectors.framework.common.objects.filter.FilterBuilder;
import org.quartz.JobExecutionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@@ -73,8 +70,7 @@ public class SinglePullJobDelegate extends PullJobDelegate implements SyncopeSin
public List<ProvisioningReport> pull(
final Provision provision,
final Connector connector,
- final String connObjectKey,
- final String connObjectValue,
+ final ReconFilterBuilder reconFilterBuilder,
final PullTaskTO pullTaskTO) throws JobExecutionException {
LOG.debug("Executing pull on {}", provision.getResource());
@@ -163,7 +159,7 @@ public class SinglePullJobDelegate extends PullJobDelegate implements SyncopeSin
connector.filteredReconciliation(
provision.getObjectClass(),
- new SingleReconciliationFilterBuilder(connObjectKey, connObjectValue),
+ reconFilterBuilder,
handler,
MappingUtils.buildOperationOptions(mapItems, moreAttrsToGet.toArray(new String[0])));
@@ -184,21 +180,4 @@ public class SinglePullJobDelegate extends PullJobDelegate implements SyncopeSin
: new JobExecutionException("While pulling from connector", e);
}
}
-
- class SingleReconciliationFilterBuilder implements ReconFilterBuilder {
-
- private final String key;
-
- private final String value;
-
- SingleReconciliationFilterBuilder(final String key, final String value) {
- this.key = key;
- this.value = value;
- }
-
- @Override
- public Filter build() {
- return FilterBuilder.equalTo(AttributeBuilder.build(key, value));
- }
- }
}
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/utils/ConnObjectUtils.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/utils/ConnObjectUtils.java
index 17b4db6..8de86f5 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/utils/ConnObjectUtils.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/utils/ConnObjectUtils.java
@@ -21,6 +21,7 @@ package org.apache.syncope.core.provisioning.java.utils;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
+import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
@@ -59,6 +60,7 @@ import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.CollectionUtils;
@Component
public class ConnObjectUtils {
@@ -95,7 +97,7 @@ public class ConnObjectUtils {
* @return password value
*/
public static String getPassword(final Object pwd) {
- final StringBuilder result = new StringBuilder();
+ StringBuilder result = new StringBuilder();
if (pwd instanceof GuardedString) {
result.append(SecurityUtil.decrypt((GuardedString) pwd));
@@ -113,27 +115,31 @@ public class ConnObjectUtils {
/**
* Builds {@link ConnObjectTO} out of a collection of {@link Attribute} instances.
*
+ * @param fiql FIQL expression to uniquely identify the given Connector Object
* @param attrs attributes
* @return transfer object
*/
- public static ConnObjectTO getConnObjectTO(final Set<Attribute> attrs) {
- final ConnObjectTO connObjectTO = new ConnObjectTO();
+ public static ConnObjectTO getConnObjectTO(final String fiql, final Set<Attribute> attrs) {
+ ConnObjectTO connObjectTO = new ConnObjectTO();
+ connObjectTO.setFiql(fiql);
- if (attrs != null) {
+ if (!CollectionUtils.isEmpty(attrs)) {
connObjectTO.getAttrs().addAll(attrs.stream().map(attr -> {
AttrTO attrTO = new AttrTO();
attrTO.setSchema(attr.getName());
- if (attr.getValue() != null) {
- attr.getValue().stream().filter(value -> value != null).forEachOrdered(value -> {
+
+ if (!CollectionUtils.isEmpty(attr.getValue())) {
+ attr.getValue().stream().filter(Objects::nonNull).forEach(value -> {
if (value instanceof GuardedString || value instanceof GuardedByteArray) {
attrTO.getValues().add(getPassword(value));
} else if (value instanceof byte[]) {
attrTO.getValues().add(Base64.getEncoder().encodeToString((byte[]) value));
- } else if (value != null) {
+ } else {
attrTO.getValues().add(value.toString());
}
});
}
+
return attrTO;
}).collect(Collectors.toList()));
}
diff --git a/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/ReconciliationServiceImpl.java b/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/ReconciliationServiceImpl.java
index 6f3b1a1..881544b 100644
--- a/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/ReconciliationServiceImpl.java
+++ b/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/ReconciliationServiceImpl.java
@@ -19,17 +19,25 @@
package org.apache.syncope.core.rest.cxf.service;
import java.io.InputStream;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
import javax.validation.ValidationException;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.StreamingOutput;
import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.exception.ExceptionUtils;
+import org.apache.commons.lang3.tuple.Pair;
+import org.apache.cxf.jaxrs.ext.search.SearchBean;
+import org.apache.cxf.jaxrs.ext.search.SearchCondition;
+import org.apache.syncope.common.lib.SyncopeClientException;
import org.apache.syncope.common.lib.SyncopeConstants;
import org.apache.syncope.common.lib.to.ProvisioningReport;
import org.apache.syncope.common.lib.to.PullTaskTO;
import org.apache.syncope.common.lib.to.PushTaskTO;
import org.apache.syncope.common.lib.to.ReconStatus;
+import org.apache.syncope.common.lib.types.ClientExceptionType;
import org.apache.syncope.common.rest.api.RESTHeaders;
import org.apache.syncope.common.rest.api.beans.AnyQuery;
import org.apache.syncope.common.rest.api.beans.CSVPullSpec;
@@ -38,7 +46,9 @@ import org.apache.syncope.common.rest.api.beans.ReconQuery;
import org.apache.syncope.common.rest.api.service.ReconciliationService;
import org.apache.syncope.core.logic.ReconciliationLogic;
import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
+import org.apache.syncope.core.persistence.api.search.FilterVisitor;
import org.apache.syncope.core.spring.security.AuthContextUtils;
+import org.identityconnectors.framework.common.objects.filter.Filter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@@ -49,44 +59,91 @@ public class ReconciliationServiceImpl extends AbstractServiceImpl implements Re
private ReconciliationLogic logic;
private void validate(final ReconQuery reconQuery) {
- if ((reconQuery.getAnyKey() == null && reconQuery.getConnObjectKeyValue() == null)
- || (reconQuery.getAnyKey() != null && reconQuery.getConnObjectKeyValue() != null)) {
+ if ((reconQuery.getAnyKey() == null && reconQuery.getFiql() == null)
+ || (reconQuery.getAnyKey() != null && reconQuery.getFiql() != null)) {
- throw new ValidationException("Either provide anyKey or connObjectKeyValue, not both");
+ throw new ValidationException("Either provide anyKey or fiql, not both");
}
}
+ private Pair<Filter, Set<String>> buildFromFIQL(final ReconQuery reconQuery) {
+ Filter filter = null;
+ Set<String> moreAttrsToGet = new HashSet<>();
+ if (reconQuery.getMoreAttrsToGet() != null) {
+ moreAttrsToGet.addAll(reconQuery.getMoreAttrsToGet());
+ }
+ if (StringUtils.isNotBlank(reconQuery.getFiql())) {
+ try {
+ FilterVisitor visitor = new FilterVisitor();
+ SearchCondition<SearchBean> sc = searchContext.getCondition(reconQuery.getFiql(), SearchBean.class);
+ sc.accept(visitor);
+
+ filter = visitor.getQuery();
+ moreAttrsToGet.addAll(visitor.getAttrs());
+ } catch (Exception e) {
+ LOG.error("Invalid FIQL expression: {}", reconQuery.getFiql(), e);
+
+ SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.InvalidSearchExpression);
+ sce.getElements().add(reconQuery.getFiql());
+ sce.getElements().add(ExceptionUtils.getRootCauseMessage(e));
+ throw sce;
+ }
+ }
+
+ return Pair.of(filter, moreAttrsToGet);
+ }
+
@Override
- public ReconStatus status(final ReconQuery reconQuery) {
- validate(reconQuery);
- return logic.status(reconQuery);
+ public ReconStatus status(final ReconQuery query) {
+ validate(query);
+
+ if (query.getAnyKey() != null) {
+ return logic.status(query.getAnyTypeKey(), query.getResourceKey(), query.getAnyKey());
+ }
+
+ Pair<Filter, Set<String>> fromFIQL = buildFromFIQL(query);
+ return logic.status(query.getAnyTypeKey(), query.getResourceKey(), fromFIQL.getLeft(), fromFIQL.getRight());
}
@Override
- public void push(final ReconQuery reconQuery, final PushTaskTO pushTask) {
- validate(reconQuery);
- logic.push(reconQuery, pushTask);
+ public List<ProvisioningReport> push(final ReconQuery query, final PushTaskTO pushTask) {
+ validate(query);
+
+ if (query.getAnyKey() != null) {
+ return logic.push(query.getAnyTypeKey(), query.getResourceKey(), query.getAnyKey(), pushTask);
+ }
+
+ Pair<Filter, Set<String>> fromFIQL = buildFromFIQL(query);
+ return logic.push(
+ query.getAnyTypeKey(), query.getResourceKey(), fromFIQL.getLeft(), fromFIQL.getRight(), pushTask);
}
@Override
- public void pull(final ReconQuery reconQuery, final PullTaskTO pullTask) {
- validate(reconQuery);
- logic.pull(reconQuery, pullTask);
+ public List<ProvisioningReport> pull(final ReconQuery query, final PullTaskTO pullTask) {
+ validate(query);
+
+ if (query.getAnyKey() != null) {
+ return logic.pull(query.getAnyTypeKey(), query.getResourceKey(), query.getAnyKey(), pullTask);
+ }
+
+ Pair<Filter, Set<String>> fromFIQL = buildFromFIQL(query);
+ return logic.pull(
+ query.getAnyTypeKey(), query.getResourceKey(), fromFIQL.getLeft(), fromFIQL.getRight(), pullTask);
}
@Override
- public Response push(final AnyQuery anyQuery, final CSVPushSpec spec) {
- String realm = StringUtils.prependIfMissing(anyQuery.getRealm(), SyncopeConstants.ROOT_REALM);
+ public Response push(final AnyQuery query, final CSVPushSpec spec) {
+ String realm = StringUtils.prependIfMissing(query.getRealm(), SyncopeConstants.ROOT_REALM);
- SearchCond searchCond = StringUtils.isBlank(anyQuery.getFiql())
+ SearchCond searchCond = StringUtils.isBlank(query.getFiql())
? null
- : getSearchCond(anyQuery.getFiql(), realm);
+ : getSearchCond(query.getFiql(), realm);
- StreamingOutput sout = (os) -> logic.push(
+ StreamingOutput sout = os -> logic.push(
searchCond,
- anyQuery.getPage(),
- anyQuery.getSize(),
- getOrderByClauses(anyQuery.getOrderBy()),
+ query.getPage(),
+ query.getSize(),
+ getOrderByClauses(query.getOrderBy()),
realm,
spec,
os);
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PropagationTaskITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PropagationTaskITCase.java
index f2edcfb..260be17 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PropagationTaskITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PropagationTaskITCase.java
@@ -665,7 +665,7 @@ public class PropagationTaskITCase extends AbstractTaskITCase {
GroupTO groupTO = new GroupTO();
groupTO.setName("SYNCOPEGROUP1605-" + getUUIDString());
- groupTO.setRealm("/");
+ groupTO.setRealm(SyncopeConstants.ROOT_REALM);
groupTO.getResources().add(ldap.getKey());
groupTO.getPlainAttrs().add(new AttrTO.Builder().schema("originalName").value(originalName).build());
@@ -674,8 +674,7 @@ public class PropagationTaskITCase extends AbstractTaskITCase {
// 3. check attributes prepared for propagation
PagedResult<PropagationTaskTO> tasks = taskService.search(new TaskQuery.Builder(TaskType.PROPAGATION).
- resource(groupTO.getResources().iterator().next()).
- anyTypeKind(AnyTypeKind.GROUP).entityKey(groupTO.getKey()).build());
+ resource(ldap.getKey()).anyTypeKind(AnyTypeKind.GROUP).entityKey(groupTO.getKey()).build());
assertEquals(1, tasks.getSize());
assertEquals(ResourceOperation.CREATE, tasks.getResult().get(0).getOperation());
assertEquals(ExecStatus.SUCCESS.name(), tasks.getResult().get(0).getLatestExecStatus());
@@ -690,8 +689,8 @@ public class PropagationTaskITCase extends AbstractTaskITCase {
groupTO = updateGroup(groupPatch).getEntity();
tasks = taskService.search(new TaskQuery.Builder(TaskType.PROPAGATION).
- resource(groupTO.getResources().iterator().next()).
- anyTypeKind(AnyTypeKind.GROUP).entityKey(groupTO.getKey()).orderBy("start DESC").build());
+ resource(ldap.getKey()).anyTypeKind(AnyTypeKind.GROUP).entityKey(groupTO.getKey()).
+ orderBy("start DESC").build());
assertEquals(2, tasks.getSize());
assertEquals(ResourceOperation.UPDATE, tasks.getResult().get(0).getOperation());
assertEquals(ExecStatus.SUCCESS.name(), tasks.getResult().get(0).getLatestExecStatus());
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/ReconciliationITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/ReconciliationITCase.java
index 10e2b38..36f8209 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/ReconciliationITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/ReconciliationITCase.java
@@ -123,6 +123,10 @@ public class ReconciliationITCase extends AbstractITCase {
if (enable != null) {
status.getOnSyncope().getAttrs().remove(enable);
}
+ // FIQL is always null for Syncope
+ assertNull(status.getOnSyncope().getFiql());
+ assertNotNull(status.getOnResource().getFiql());
+ status.getOnResource().setFiql(null);
assertEquals(status.getOnSyncope(), status.getOnResource());
}
@@ -175,7 +179,7 @@ public class ReconciliationITCase extends AbstractITCase {
// 2. verify reconciliation status
ReconStatus status = reconciliationService.status(
- new ReconQuery.Builder(PRINTER, RESOURCE_NAME_DBSCRIPTED).connObjectKeyValue(externalKey).build());
+ new ReconQuery.Builder(PRINTER, RESOURCE_NAME_DBSCRIPTED).fiql("ID==" + externalKey).build());
assertNotNull(status);
assertNull(status.getAnyTypeKind());
assertNull(status.getAnyKey());
@@ -189,8 +193,8 @@ public class ReconciliationITCase extends AbstractITCase {
PullTaskTO pullTask = new PullTaskTO();
pullTask.setDestinationRealm(SyncopeConstants.ROOT_REALM);
pullTask.setPerformCreate(true);
- reconciliationService.pull(new ReconQuery.Builder(PRINTER, RESOURCE_NAME_DBSCRIPTED).
- connObjectKeyValue(externalKey).build(), pullTask);
+ reconciliationService.pull(
+ new ReconQuery.Builder(PRINTER, RESOURCE_NAME_DBSCRIPTED).fiql("ID==" + externalKey).build(), pullTask);
// 4. verify reconciliation result
AnyObjectTO printer = anyObjectService.read(externalName);