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 2022/03/15 08:38:06 UTC
[syncope] branch master updated: [SYNCOPE-1667] Adding PropagationPolicy (#325)
This is an automated email from the ASF dual-hosted git repository.
ilgrosso pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/syncope.git
The following commit(s) were added to refs/heads/master by this push:
new a039899 [SYNCOPE-1667] Adding PropagationPolicy (#325)
a039899 is described below
commit a03989916e1d86e6a8412de6f4b9ecdb2269e4c5
Author: Francesco Chicchiriccò <il...@users.noreply.github.com>
AuthorDate: Tue Mar 15 09:36:13 2022 +0100
[SYNCOPE-1667] Adding PropagationPolicy (#325)
---
.../console/commons/IdMPolicyTabProvider.java | 11 ++
.../policies/PropagationPolicyDirectoryPanel.java | 51 ++++++
.../wizards/resources/ResourceSecurityPanel.java | 33 +++-
.../wizards/resources/ResourceSecurityPanel.html | 3 +
.../resources/ResourceSecurityPanel.properties | 1 +
.../ResourceSecurityPanel_fr_CA.properties | 1 +
.../resources/ResourceSecurityPanel_it.properties | 1 +
.../resources/ResourceSecurityPanel_ja.properties | 1 +
.../ResourceSecurityPanel_pt_BR.properties | 1 +
.../resources/ResourceSecurityPanel_ru.properties | 1 +
.../console/policies/PolicyModalPanelBuilder.java | 126 ++++++++++++++
.../client/console/pages/Policies.properties | 1 +
.../client/console/pages/Policies_it.properties | 1 +
.../client/console/pages/Policies_ja.properties | 1 +
.../client/console/pages/Policies_pt_BR.properties | 1 +
.../client/console/pages/Policies_ru.properties | 1 +
.../policies/PolicyDirectoryPanel.properties | 2 +
.../policies/PolicyDirectoryPanel_fr_CA.properties | 2 +
.../policies/PolicyDirectoryPanel_it.properties | 2 +
.../policies/PolicyDirectoryPanel_ja.properties | 2 +
.../policies/PolicyDirectoryPanel_pt_BR.properties | 2 +
.../policies/PolicyDirectoryPanel_ru.properties | 2 +
.../common/lib/policy/PropagationPolicyTO.java | 69 ++++++++
.../apache/syncope/common/lib/to/ResourceTO.java | 12 ++
.../{PolicyType.java => BackOffStrategy.java} | 41 ++---
.../syncope/common/lib/types/PolicyType.java | 4 +
.../api/entity/policy/PropagationPolicy.java | 48 ++----
.../api/entity/resource/ExternalResource.java | 5 +
.../src/test/resources/domains/MasterContent.xml | 5 +-
.../jpa/dao/JPAExternalResourceDAO.java | 3 +
.../core/persistence/jpa/dao/JPAPolicyDAO.java | 7 +
.../core/persistence/jpa/dao/JPARealmDAO.java | 13 +-
.../jpa/dao/JPARelationshipTypeDAO.java | 2 +-
.../core/persistence/jpa/dao/JPAUserDAO.java | 6 +-
.../persistence/jpa/entity/JPAEntityFactory.java | 24 +--
.../jpa/entity/policy/JPAPolicyUtils.java | 10 +-
.../jpa/entity/policy/JPAPolicyUtilsFactory.java | 6 +
.../jpa/entity/policy/JPAPropagationPolicy.java | 76 +++++++++
.../jpa/entity/resource/JPAExternalResource.java | 16 ++
.../core/persistence/jpa/inner/PolicyTest.java | 121 ++++++++++++--
.../core/persistence/jpa/outer/GroupTest.java | 4 +-
.../core/persistence/jpa/outer/ResourceTest.java | 17 +-
.../src/test/resources/domains/MasterContent.xml | 7 +-
.../api/propagation/PropagationTaskExecutor.java | 10 +-
core/provisioning-java/pom.xml | 4 +
.../provisioning/java/ProvisioningContext.java | 70 +++++---
.../java/data/PolicyDataBinderImpl.java | 25 ++-
.../java/data/ResourceDataBinderImpl.java | 19 ++-
.../AbstractPropagationTaskExecutor.java | 183 ++++++++++++++++-----
.../PriorityPropagationTaskExecutor.java | 111 +++++++------
.../apache/syncope/fit/console/PoliciesITCase.java | 4 +-
.../org/apache/syncope/fit/core/PolicyITCase.java | 38 ++++-
.../syncope/fit/core/PropagationTaskITCase.java | 40 +++++
.../concepts/authenticationmodules.adoc | 24 +--
.../concepts/clientapplications.adoc | 2 +-
.../reference-guide/concepts/policies.adoc | 20 ++-
.../asciidoc/reference-guide/concepts/tasks.adoc | 3 +
57 files changed, 1027 insertions(+), 269 deletions(-)
diff --git a/client/idm/console/src/main/java/org/apache/syncope/client/console/commons/IdMPolicyTabProvider.java b/client/idm/console/src/main/java/org/apache/syncope/client/console/commons/IdMPolicyTabProvider.java
index cc63b83..0f2b920 100644
--- a/client/idm/console/src/main/java/org/apache/syncope/client/console/commons/IdMPolicyTabProvider.java
+++ b/client/idm/console/src/main/java/org/apache/syncope/client/console/commons/IdMPolicyTabProvider.java
@@ -20,6 +20,7 @@ package org.apache.syncope.client.console.commons;
import java.util.ArrayList;
import java.util.List;
+import org.apache.syncope.client.console.policies.PropagationPolicyDirectoryPanel;
import org.apache.syncope.client.console.policies.PullPolicyDirectoryPanel;
import org.apache.syncope.client.console.policies.PushPolicyDirectoryPanel;
import org.apache.wicket.PageReference;
@@ -41,6 +42,16 @@ public class IdMPolicyTabProvider implements PolicyTabProvider {
public List<ITab> buildTabList(final PageReference pageRef) {
List<ITab> tabs = new ArrayList<>();
+ tabs.add(new AbstractTab(new ResourceModel("policy.propagation")) {
+
+ private static final long serialVersionUID = -6815067322125799251L;
+
+ @Override
+ public Panel getPanel(final String panelId) {
+ return new PropagationPolicyDirectoryPanel(panelId, pageRef);
+ }
+ });
+
tabs.add(new AbstractTab(new ResourceModel("policy.pull")) {
private static final long serialVersionUID = -6815067322125799251L;
diff --git a/client/idm/console/src/main/java/org/apache/syncope/client/console/policies/PropagationPolicyDirectoryPanel.java b/client/idm/console/src/main/java/org/apache/syncope/client/console/policies/PropagationPolicyDirectoryPanel.java
new file mode 100644
index 0000000..8a9a793
--- /dev/null
+++ b/client/idm/console/src/main/java/org/apache/syncope/client/console/policies/PropagationPolicyDirectoryPanel.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.client.console.policies;
+
+import java.util.List;
+import org.apache.syncope.common.lib.policy.PropagationPolicyTO;
+import org.apache.syncope.common.lib.types.IdRepoEntitlement;
+import org.apache.syncope.common.lib.types.PolicyType;
+import org.apache.wicket.PageReference;
+import org.apache.wicket.authroles.authorization.strategies.role.metadata.MetaDataRoleAuthorizationStrategy;
+import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn;
+import org.apache.wicket.extensions.markup.html.repeater.data.table.PropertyColumn;
+import org.apache.wicket.model.StringResourceModel;
+
+public class PropagationPolicyDirectoryPanel extends PolicyDirectoryPanel<PropagationPolicyTO> {
+
+ private static final long serialVersionUID = 25188602686577L;
+
+ public PropagationPolicyDirectoryPanel(final String id, final PageReference pageRef) {
+ super(id, PolicyType.PROPAGATION, pageRef);
+
+ PropagationPolicyTO defaultItem = new PropagationPolicyTO();
+
+ this.addNewItemPanelBuilder(
+ new PolicyModalPanelBuilder<>(PolicyType.PROPAGATION, defaultItem, modal, pageRef), true);
+ MetaDataRoleAuthorizationStrategy.authorize(addAjaxLink, RENDER, IdRepoEntitlement.POLICY_CREATE);
+
+ initResultTable();
+ }
+
+ @Override
+ protected void addCustomColumnFields(final List<IColumn<PropagationPolicyTO, String>> columns) {
+ columns.add(new PropertyColumn<>(new StringResourceModel("maxAttempts", this), "maxAttempts", "maxAttempts"));
+ }
+}
diff --git a/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel.java b/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel.java
index e339bba..f23862e 100644
--- a/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel.java
+++ b/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel.java
@@ -46,7 +46,7 @@ public class ResourceSecurityPanel extends WizardStep {
@Override
protected Map<String, String> load() {
return PolicyRestClient.list(PolicyType.PASSWORD).stream().
- collect(Collectors.toMap(PolicyTO::getKey, PolicyTO::getName));
+ collect(Collectors.toMap(PolicyTO::getKey, PolicyTO::getName));
}
};
@@ -57,7 +57,18 @@ public class ResourceSecurityPanel extends WizardStep {
@Override
protected Map<String, String> load() {
return PolicyRestClient.list(PolicyType.ACCOUNT).stream().
- collect(Collectors.toMap(PolicyTO::getKey, PolicyTO::getName));
+ collect(Collectors.toMap(PolicyTO::getKey, PolicyTO::getName));
+ }
+ };
+
+ private final IModel<Map<String, String>> propagationPolicies = new LoadableDetachableModel<>() {
+
+ private static final long serialVersionUID = -2012833443695917883L;
+
+ @Override
+ protected Map<String, String> load() {
+ return PolicyRestClient.list(PolicyType.PROPAGATION).stream().
+ collect(Collectors.toMap(PolicyTO::getKey, PolicyTO::getName));
}
};
@@ -68,7 +79,7 @@ public class ResourceSecurityPanel extends WizardStep {
@Override
protected Map<String, String> load() {
return PolicyRestClient.list(PolicyType.PULL).stream().
- collect(Collectors.toMap(PolicyTO::getKey, PolicyTO::getName));
+ collect(Collectors.toMap(PolicyTO::getKey, PolicyTO::getName));
}
};
@@ -79,7 +90,7 @@ public class ResourceSecurityPanel extends WizardStep {
@Override
protected Map<String, String> load() {
return PolicyRestClient.list(PolicyType.PUSH).stream().
- collect(Collectors.toMap(PolicyTO::getKey, PolicyTO::getName));
+ collect(Collectors.toMap(PolicyTO::getKey, PolicyTO::getName));
}
};
@@ -121,6 +132,20 @@ public class ResourceSecurityPanel extends WizardStep {
// -------------------------------
// -------------------------------
+ // Propagation policy selection
+ // -------------------------------
+ AjaxDropDownChoicePanel<String> propagationPolicy = new AjaxDropDownChoicePanel<>(
+ "propagationPolicy",
+ new ResourceModel("propagationPolicy", "propagationPolicy").getObject(),
+ new PropertyModel<>(resourceTO, "propagationPolicy"),
+ false);
+ propagationPolicy.setChoiceRenderer(new PolicyRenderer(propagationPolicies));
+ propagationPolicy.setChoices(new ArrayList<>(propagationPolicies.getObject().keySet()));
+ ((DropDownChoice<?>) propagationPolicy.getField()).setNullValid(true);
+ container.add(propagationPolicy);
+ // -------------------------------
+
+ // -------------------------------
// Pull policy selection
// -------------------------------
AjaxDropDownChoicePanel<String> pullPolicy = new AjaxDropDownChoicePanel<>(
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel.html b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel.html
index fea39ff..00b4882 100644
--- a/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel.html
+++ b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel.html
@@ -25,6 +25,9 @@ under the License.
<span wicket:id="passwordPolicy">
[panel for dynamic input type markup]
</span>
+ <span wicket:id="propagationPolicy">
+ [panel for dynamic input type markup]
+ </span>
<span wicket:id="pullPolicy">
[panel for dynamic input type markup]
</span>
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel.properties b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel.properties
index e4a70ca..c2b3da7 100644
--- a/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel.properties
+++ b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel.properties
@@ -18,3 +18,4 @@ passwordPolicy=Password Policy
accountPolicy=Account Policy
pullPolicy=Pull Policy
pushPolicy=Push Policy
+propagationPolicy=Propagation Policy
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel_fr_CA.properties b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel_fr_CA.properties
index 8a2c40f..2e20b1a 100644
--- a/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel_fr_CA.properties
+++ b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel_fr_CA.properties
@@ -18,3 +18,4 @@ passwordPolicy=Politique sur les mots de passe
accountPolicy=Politique sur les comptes
pullPolicy=Politique Pull
pushPolicy=Politique Push
+propagationPolicy=Propagation Policy
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel_it.properties b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel_it.properties
index e4a70ca..c2b3da7 100644
--- a/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel_it.properties
+++ b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel_it.properties
@@ -18,3 +18,4 @@ passwordPolicy=Password Policy
accountPolicy=Account Policy
pullPolicy=Pull Policy
pushPolicy=Push Policy
+propagationPolicy=Propagation Policy
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel_ja.properties b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel_ja.properties
index ca6bc82..0df90b3 100644
--- a/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel_ja.properties
+++ b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel_ja.properties
@@ -18,3 +18,4 @@ passwordPolicy=\u30d1\u30b9\u30ef\u30fc\u30c9\u30dd\u30ea\u30b7\u30fc
accountPolicy=\u30a2\u30ab\u30a6\u30f3\u30c8\u30dd\u30ea\u30b7\u30fc
pullPolicy=\u30d7\u30eb\u30dd\u30ea\u30b7\u30fc
pushPolicy=\u30d7\u30c3\u30b7\u30e5\u30dd\u30ea\u30b7\u30fc
+propagationPolicy=Propagation Policy
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel_pt_BR.properties b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel_pt_BR.properties
index 7300d71..2b8b4b7 100644
--- a/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel_pt_BR.properties
+++ b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel_pt_BR.properties
@@ -18,3 +18,4 @@ passwordPolicy=Pol\u00edtica de Senha
accountPolicy=Pol\u00edtica de Conta
pullPolicy=Pol\u00edtica de Pull
pushPolicy=Push Policy
+propagationPolicy=Propagation Policy
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel_ru.properties b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel_ru.properties
index 7d70763..70bf684 100644
--- a/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel_ru.properties
+++ b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel_ru.properties
@@ -19,3 +19,4 @@ passwordPolicy = \u041f\u043e\u043b\u0438\u0442\u0438\u043a\u0430 \u043f\u0430\u
accountPolicy = \u041f\u043e\u043b\u0438\u0442\u0438\u043a\u0430 \u0443\u0447\u0435\u0442\u043d\u044b\u0445 \u0437\u0430\u043f\u0438\u0441\u0435\u0439
pullPolicy=\u041f\u043e\u043b\u0438\u0442\u0438\u043a\u0430 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0434\u0430\u043d\u043d\u044b\u0445
pushPolicy=Push Policy
+propagationPolicy=Propagation Policy
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/policies/PolicyModalPanelBuilder.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/policies/PolicyModalPanelBuilder.java
index 82fdaa2..7778556 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/policies/PolicyModalPanelBuilder.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/policies/PolicyModalPanelBuilder.java
@@ -21,14 +21,17 @@ package org.apache.syncope.client.console.policies;
import java.io.Serializable;
import java.net.URI;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import java.util.Optional;
+import java.util.stream.Collectors;
import org.apache.syncope.client.console.SyncopeWebApplication;
import org.apache.syncope.client.console.SyncopeConsoleSession;
import org.apache.syncope.client.ui.commons.Constants;
import org.apache.syncope.client.console.panels.AbstractModalPanel;
import org.apache.syncope.client.console.rest.PolicyRestClient;
import org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal;
+import org.apache.syncope.client.ui.commons.ajax.form.IndicatorAjaxFormComponentUpdatingBehavior;
import org.apache.syncope.client.ui.commons.markup.html.form.AjaxCheckBoxPanel;
import org.apache.syncope.client.ui.commons.markup.html.form.AjaxDropDownChoicePanel;
import org.apache.syncope.client.ui.commons.markup.html.form.AjaxPalettePanel;
@@ -39,6 +42,7 @@ import org.apache.syncope.client.ui.commons.wizards.AbstractModalPanelBuilder;
import org.apache.syncope.client.ui.commons.wizards.AjaxWizard;
import org.apache.syncope.client.ui.commons.panels.WizardModalPanel;
import org.apache.syncope.common.lib.policy.PolicyTO;
+import org.apache.syncope.common.lib.types.BackOffStrategy;
import org.apache.syncope.common.lib.types.ConflictResolutionAction;
import org.apache.syncope.common.lib.types.PolicyType;
import org.apache.wicket.Application;
@@ -59,6 +63,42 @@ public class PolicyModalPanelBuilder<T extends PolicyTO> extends AbstractModalPa
private static final long serialVersionUID = 5945391813567245081L;
+ private static class BackOffParamsModel<N extends Number> implements IModel<N> {
+
+ private static final long serialVersionUID = 28839546672164L;
+
+ private final PropertyModel<String> backOffParamsModel;
+
+ private final int index;
+
+ BackOffParamsModel(final PropertyModel<String> backOffParamsModel, final int index) {
+ this.backOffParamsModel = backOffParamsModel;
+ this.index = index;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public N getObject() {
+ String[] split = backOffParamsModel.getObject().split(";");
+ if (index >= split.length) {
+ return null;
+ }
+
+ return index == 2
+ ? (N) Double.valueOf(backOffParamsModel.getObject().split(";")[index])
+ : (N) Long.valueOf(backOffParamsModel.getObject().split(";")[index]);
+ }
+
+ @Override
+ public void setObject(final N object) {
+ String[] split = backOffParamsModel.getObject().split(";");
+ if (index < split.length) {
+ split[index] = object.toString();
+ backOffParamsModel.setObject(Arrays.stream(split).collect(Collectors.joining(";")));
+ }
+ }
+ }
+
private final BaseModal<T> modal;
private final PolicyType type;
@@ -137,6 +177,63 @@ public class PolicyModalPanelBuilder<T extends PolicyTO> extends AbstractModalPa
false));
break;
+ case PROPAGATION:
+ fields.add(new AjaxSpinnerFieldPanel.Builder<Integer>().build(
+ "field",
+ "maxAttempts",
+ Integer.class,
+ new PropertyModel<>(policyTO, "maxAttempts")));
+ AjaxDropDownChoicePanel<Serializable> backOffStrategy = new AjaxDropDownChoicePanel<>(
+ "field",
+ "backOffStrategy",
+ new PropertyModel<>(policyTO, "backOffStrategy")).
+ setChoices(List.of((Serializable[]) BackOffStrategy.values()));
+ fields.add(backOffStrategy);
+
+ PropertyModel<String> backOffParamsModel = new PropertyModel<>(policyTO, "backOffParams");
+
+ AjaxSpinnerFieldPanel<Long> initialInterval = new AjaxSpinnerFieldPanel.Builder<Long>().
+ min(1L).build(
+ "field",
+ "initialInterval",
+ Long.class,
+ new BackOffParamsModel<>(backOffParamsModel, 0));
+ fields.add(initialInterval.setOutputMarkupPlaceholderTag(true));
+ AjaxSpinnerFieldPanel<Long> maxInterval = new AjaxSpinnerFieldPanel.Builder<Long>().
+ min(1L).build(
+ "field",
+ "maxInterval",
+ Long.class,
+ new BackOffParamsModel<>(backOffParamsModel, 1));
+ fields.add(maxInterval.setOutputMarkupPlaceholderTag(true).setVisible(false));
+ AjaxSpinnerFieldPanel<Double> multiplier = new AjaxSpinnerFieldPanel.Builder<Double>().
+ min(1D).build(
+ "field",
+ "multiplier",
+ Double.class,
+ new BackOffParamsModel<>(backOffParamsModel, 2));
+ fields.add(multiplier.setOutputMarkupPlaceholderTag(true).setVisible(false));
+
+ showHide(backOffStrategy, initialInterval, maxInterval, multiplier);
+
+ backOffStrategy.getField().add(new IndicatorAjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) {
+
+ private static final long serialVersionUID = -1107858522700306810L;
+
+ @Override
+ protected void onUpdate(final AjaxRequestTarget target) {
+ BackOffStrategy strategy = (BackOffStrategy) backOffStrategy.getField().getModelObject();
+ backOffParamsModel.setObject(strategy.getDefaultBackOffParams());
+
+ showHide(backOffStrategy, initialInterval, maxInterval, multiplier);
+
+ target.add(initialInterval);
+ target.add(maxInterval);
+ target.add(multiplier);
+ }
+ });
+ break;
+
case PULL:
case PUSH:
fields.add(new AjaxDropDownChoicePanel<>(
@@ -228,6 +325,35 @@ public class PolicyModalPanelBuilder<T extends PolicyTO> extends AbstractModalPa
});
}
+ private void showHide(
+ final AjaxDropDownChoicePanel<Serializable> backOffStrategy,
+ final AjaxSpinnerFieldPanel<Long> initialInterval,
+ final AjaxSpinnerFieldPanel<Long> maxInterval,
+ final AjaxSpinnerFieldPanel<Double> multiplier) {
+
+ BackOffStrategy strategy = (BackOffStrategy) backOffStrategy.getField().getModelObject();
+
+ switch (strategy) {
+ case EXPONENTIAL:
+ initialInterval.addLabel("initialInterval");
+ maxInterval.setVisible(true);
+ multiplier.setVisible(true);
+ break;
+
+ case RANDOM:
+ initialInterval.addLabel("initialInterval");
+ maxInterval.setVisible(true);
+ multiplier.setVisible(true);
+ break;
+
+ case FIXED:
+ default:
+ initialInterval.addLabel("period");
+ maxInterval.setVisible(false);
+ multiplier.setVisible(false);
+ }
+ }
+
@Override
public void onSubmit(final AjaxRequestTarget target) {
try {
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/Policies.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/Policies.properties
index 8f9914e..b44aeda 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/Policies.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/Policies.properties
@@ -21,3 +21,4 @@ policy.push=Push
policy.access=Access
policy.attrRelease=Attribute Release
policy.auth=Authentication
+policy.propagation=Propagation
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/Policies_it.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/Policies_it.properties
index 1d36635..693aa35 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/Policies_it.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/Policies_it.properties
@@ -21,3 +21,4 @@ policy.push=Push
policy.access=Accesso
policy.attrRelease=Rilascio Attributi
policy.auth=Autenticazione
+policy.propagation=Propagazione
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/Policies_ja.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/Policies_ja.properties
index c998c1a..5ed564c 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/Policies_ja.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/Policies_ja.properties
@@ -21,3 +21,4 @@ policy.push=\u30d7\u30c3\u30b7\u30e5
policy.access=Access
policy.attrRelease=Attribute Release
policy.auth=Authentication
+policy.propagation=Propagation
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/Policies_pt_BR.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/Policies_pt_BR.properties
index 8f9914e..b44aeda 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/Policies_pt_BR.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/Policies_pt_BR.properties
@@ -21,3 +21,4 @@ policy.push=Push
policy.access=Access
policy.attrRelease=Attribute Release
policy.auth=Authentication
+policy.propagation=Propagation
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/Policies_ru.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/Policies_ru.properties
index 6d42c42..992307b 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/Policies_ru.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/Policies_ru.properties
@@ -22,3 +22,4 @@ policy.push=Push
policy.access=Access
policy.attrRelease=Attribute Release
policy.auth=Authentication
+policy.propagation=Propagation
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel.properties
index 4345946..1f1d3dc 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel.properties
@@ -51,3 +51,5 @@ caseInsensitive=Case Insensitive
order=Order
rejectedAttrs.title=Rejected Attributes
unauthorizedRedirectUrl=Unauthorized Redirect URL
+maxAttempts=Max Attempts
+backOffStrategy=BackOff Strategy
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel_fr_CA.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel_fr_CA.properties
index 1be912b..a7858b0 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel_fr_CA.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel_fr_CA.properties
@@ -51,3 +51,5 @@ caseInsensitive=Case Insensitive
order=Order
rejectedAttrs.title=Rejected Attributes
unauthorizedRedirectUrl=Unauthorized Redirect URL
+maxAttempts=Max Attempts
+backOffStrategy=BackOff Strategy
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel_it.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel_it.properties
index 1c041ba..690c704 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel_it.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel_it.properties
@@ -51,3 +51,5 @@ caseInsensitive=Case Insensitive
order=Ordinamento
rejectedAttrs.title=Attributi Rifiutati
unauthorizedRedirectUrl=URL di Ridirezione Per Mancata Autorizzazione
+maxAttempts=Tentativi Massimi
+backOffStrategy=Strategia di BackOff
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel_ja.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel_ja.properties
index 14c4ef4..ebefed5 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel_ja.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel_ja.properties
@@ -51,3 +51,5 @@ caseInsensitive=Case Insensitive
order=Order
rejectedAttrs.title=Rejected Attributes
unauthorizedRedirectUrl=Unauthorized Redirect URL
+maxAttempts=Max Attempts
+backOffStrategy=BackOff Strategy
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel_pt_BR.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel_pt_BR.properties
index 0635b3a..29e278e 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel_pt_BR.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel_pt_BR.properties
@@ -51,3 +51,5 @@ caseInsensitive=Case Insensitive
order=Order
rejectedAttrs.title=Rejected Attributes
unauthorizedRedirectUrl=Unauthorized Redirect URL
+maxAttempts=Max Attempts
+backOffStrategy=BackOff Strategy
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel_ru.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel_ru.properties
index c4091fc..4212773 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel_ru.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel_ru.properties
@@ -52,3 +52,5 @@ caseInsensitive=Case Insensitive
order=Order
rejectedAttrs.title=Rejected Attributes
unauthorizedRedirectUrl=Unauthorized Redirect URL
+maxAttempts=Max Attempts
+backOffStrategy=BackOff Strategy
diff --git a/common/idm/lib/src/main/java/org/apache/syncope/common/lib/policy/PropagationPolicyTO.java b/common/idm/lib/src/main/java/org/apache/syncope/common/lib/policy/PropagationPolicyTO.java
new file mode 100644
index 0000000..90bc7cd
--- /dev/null
+++ b/common/idm/lib/src/main/java/org/apache/syncope/common/lib/policy/PropagationPolicyTO.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.common.lib.policy;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import org.apache.syncope.common.lib.types.BackOffStrategy;
+
+@Schema(allOf = { PolicyTO.class }, discriminatorProperty = "_class")
+public class PropagationPolicyTO extends PolicyTO {
+
+ private static final long serialVersionUID = 10604950933449L;
+
+ private BackOffStrategy backOffStrategy = BackOffStrategy.FIXED;
+
+ private String backOffParams = BackOffStrategy.FIXED.getDefaultBackOffParams();
+
+ private int maxAttempts = 3;
+
+ @JacksonXmlProperty(localName = "_class", isAttribute = true)
+ @JsonProperty("_class")
+ @Schema(name = "_class", required = true, example = "org.apache.syncope.common.lib.policy.PropagationPolicyTO")
+ @Override
+ public String getDiscriminator() {
+ return getClass().getName();
+ }
+
+ public BackOffStrategy getBackOffStrategy() {
+ return backOffStrategy;
+ }
+
+ public void setBackOffStrategy(final BackOffStrategy backOffStrategy) {
+ this.backOffStrategy = backOffStrategy;
+ this.backOffParams = backOffStrategy.getDefaultBackOffParams();
+ }
+
+ public String getBackOffParams() {
+ return backOffParams;
+ }
+
+ public void setBackOffParams(final String backOffParams) {
+ this.backOffParams = backOffParams;
+ }
+
+ public int getMaxAttempts() {
+ return maxAttempts;
+ }
+
+ public void setMaxAttempts(final int maxAttempts) {
+ this.maxAttempts = maxAttempts;
+ }
+}
diff --git a/common/idm/lib/src/main/java/org/apache/syncope/common/lib/to/ResourceTO.java b/common/idm/lib/src/main/java/org/apache/syncope/common/lib/to/ResourceTO.java
index 344b0a6..59233a3 100644
--- a/common/idm/lib/src/main/java/org/apache/syncope/common/lib/to/ResourceTO.java
+++ b/common/idm/lib/src/main/java/org/apache/syncope/common/lib/to/ResourceTO.java
@@ -69,6 +69,8 @@ public class ResourceTO implements EntityTO {
private String accountPolicy;
+ private String propagationPolicy;
+
private String pullPolicy;
private String pushPolicy;
@@ -178,6 +180,14 @@ public class ResourceTO implements EntityTO {
this.accountPolicy = accountPolicy;
}
+ public String getPropagationPolicy() {
+ return propagationPolicy;
+ }
+
+ public void setPropagationPolicy(final String propagationPolicy) {
+ this.propagationPolicy = propagationPolicy;
+ }
+
public String getPullPolicy() {
return pullPolicy;
}
@@ -293,6 +303,7 @@ public class ResourceTO implements EntityTO {
append(provisioningTraceLevel, other.provisioningTraceLevel).
append(passwordPolicy, other.passwordPolicy).
append(accountPolicy, other.accountPolicy).
+ append(propagationPolicy, other.propagationPolicy).
append(pullPolicy, other.pullPolicy).
append(pushPolicy, other.pushPolicy).
append(authPolicy, other.authPolicy).
@@ -321,6 +332,7 @@ public class ResourceTO implements EntityTO {
append(provisioningTraceLevel).
append(passwordPolicy).
append(accountPolicy).
+ append(propagationPolicy).
append(pullPolicy).
append(pushPolicy).
append(authPolicy).
diff --git a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/BackOffStrategy.java
similarity index 59%
copy from common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java
copy to common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/BackOffStrategy.java
index 3272e79..314a8f7 100644
--- a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java
+++ b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/BackOffStrategy.java
@@ -18,35 +18,18 @@
*/
package org.apache.syncope.common.lib.types;
-public enum PolicyType {
+public enum BackOffStrategy {
+ FIXED("1000"),
+ EXPONENTIAL("100;30000;2"),
+ RANDOM("100;30000;2");
- /**
- * How username values should look like.
- */
- ACCOUNT,
- /**
- * How password values should look like.
- */
- PASSWORD,
- /**
- * How authentication policies should look like.
- */
- AUTH,
- /**
- * How attribute release policies should look like.
- */
- ATTR_RELEASE,
- /**
- * How access policies should be defined.
- */
- ACCESS,
- /**
- * For handling conflicts resolution during pull.
- */
- PULL,
- /**
- * For handling conflicts resolution during push.
- */
- PUSH;
+ private final String defaultBackOffParams;
+ BackOffStrategy(final String defaultBackOffParams) {
+ this.defaultBackOffParams = defaultBackOffParams;
+ }
+
+ public String getDefaultBackOffParams() {
+ return defaultBackOffParams;
+ }
}
diff --git a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java
index 3272e79..e9a71bd 100644
--- a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java
+++ b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java
@@ -41,6 +41,10 @@ public enum PolicyType {
*/
ACCESS,
/**
+ * For handling propagation behavior.
+ */
+ PROPAGATION,
+ /**
* For handling conflicts resolution during pull.
*/
PULL,
diff --git a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/policy/PropagationPolicy.java
similarity index 56%
copy from common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java
copy to core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/policy/PropagationPolicy.java
index 3272e79..d4e8172 100644
--- a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/policy/PropagationPolicy.java
@@ -16,37 +16,21 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.apache.syncope.common.lib.types;
-
-public enum PolicyType {
-
- /**
- * How username values should look like.
- */
- ACCOUNT,
- /**
- * How password values should look like.
- */
- PASSWORD,
- /**
- * How authentication policies should look like.
- */
- AUTH,
- /**
- * How attribute release policies should look like.
- */
- ATTR_RELEASE,
- /**
- * How access policies should be defined.
- */
- ACCESS,
- /**
- * For handling conflicts resolution during pull.
- */
- PULL,
- /**
- * For handling conflicts resolution during push.
- */
- PUSH;
+package org.apache.syncope.core.persistence.api.entity.policy;
+import org.apache.syncope.common.lib.types.BackOffStrategy;
+
+public interface PropagationPolicy extends Policy {
+
+ BackOffStrategy getBackOffStrategy();
+
+ void setBackOffStrategy(BackOffStrategy backOffStrategy);
+
+ String getBackOffParams();
+
+ void setBackOffParams(String backOffParams);
+
+ int getMaxAttempts();
+
+ void setMaxAttempts(int maxAttempts);
}
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/resource/ExternalResource.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/resource/ExternalResource.java
index 8ad9c40..86fb038 100644
--- a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/resource/ExternalResource.java
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/resource/ExternalResource.java
@@ -30,6 +30,7 @@ import org.apache.syncope.core.persistence.api.entity.ConnInstance;
import org.apache.syncope.core.persistence.api.entity.Implementation;
import org.apache.syncope.core.persistence.api.entity.ProvidedKeyEntity;
import org.apache.syncope.core.persistence.api.entity.policy.PasswordPolicy;
+import org.apache.syncope.core.persistence.api.entity.policy.PropagationPolicy;
import org.apache.syncope.core.persistence.api.entity.policy.PullPolicy;
import org.apache.syncope.core.persistence.api.entity.policy.PushPolicy;
import org.identityconnectors.framework.common.objects.ObjectClass;
@@ -58,6 +59,10 @@ public interface ExternalResource extends ProvidedKeyEntity {
void setPasswordPolicy(PasswordPolicy passwordPolicy);
+ PropagationPolicy getPropagationPolicy();
+
+ void setPropagationPolicy(PropagationPolicy propagationPolicy);
+
PullPolicy getPullPolicy();
void setPullPolicy(PullPolicy pullPolicy);
diff --git a/core/persistence-jpa-json/src/test/resources/domains/MasterContent.xml b/core/persistence-jpa-json/src/test/resources/domains/MasterContent.xml
index 54ecf96..c3c079e 100644
--- a/core/persistence-jpa-json/src/test/resources/domains/MasterContent.xml
+++ b/core/persistence-jpa-json/src/test/resources/domains/MasterContent.xml
@@ -43,6 +43,8 @@ under the License.
<Implementation id="DefaultPasswordRuleConf3" type="PASSWORD_RULE" engine="JAVA"
body='{"_class":"org.apache.syncope.common.lib.policy.DefaultPasswordRuleConf","maxLength":0,"minLength":10,"nonAlphanumericRequired":true,"alphanumericRequired":false,"digitRequired":true,"lowercaseRequired":true,"uppercaseRequired":true,"mustStartWithDigit":true,"mustntStartWithDigit":false,"mustEndWithDigit":true,"mustntEndWithDigit":false,"mustStartWithNonAlpha":false,"mustStartWithAlpha":false,"mustntStartWithNonAlpha":false,"mustntStartWithAlpha":false,"mustEndWit [...]
<PasswordPolicyRule policy_id="55e5de0b-c79c-4e66-adda-251b6fb8579a" implementation_id="DefaultPasswordRuleConf3"/>
+ <PropagationPolicy id="89d322db-9878-420c-b49c-67be13df9a12" name="sample propagation policy"
+ maxAttempts="5" backOffStrategy="FIXED" backOffParams="10000"/>
<!-- Authentication policies -->
<AuthPolicy id="659b9906-4b6e-4bc0-aca0-6809dff346d4" name="MyDefaultAuthPolicyConf"
@@ -704,7 +706,8 @@ under the License.
<ExternalResource id="resource-db-scripted" connector_id="a6d017fd-a705-4507-bb7c-6ab6a6745997"
randomPwdIfNotProvided="0" createTraceLevel="ALL" deleteTraceLevel="ALL" provisioningTraceLevel="ALL" updateTraceLevel="ALL"
- enforceMandatoryCondition="0" overrideCapabilities="0"/>
+ enforceMandatoryCondition="0" overrideCapabilities="0"
+ propagationPolicy_id="89d322db-9878-420c-b49c-67be13df9a12"/>
<ExternalResource id="rest-target-resource" connector_id="44c02549-19c3-483c-8025-4919c3283c37"
createTraceLevel="ALL" updateTraceLevel="ALL" deleteTraceLevel="ALL" provisioningTraceLevel="ALL"
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAExternalResourceDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAExternalResourceDAO.java
index b304b8b..769ca55 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAExternalResourceDAO.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAExternalResourceDAO.java
@@ -42,6 +42,7 @@ import org.apache.syncope.core.persistence.api.entity.resource.ExternalResource;
import org.apache.syncope.core.persistence.api.entity.resource.MappingItem;
import org.apache.syncope.core.persistence.api.entity.policy.PasswordPolicy;
import org.apache.syncope.core.persistence.api.entity.policy.Policy;
+import org.apache.syncope.core.persistence.api.entity.policy.PropagationPolicy;
import org.apache.syncope.core.persistence.api.entity.policy.PullPolicy;
import org.apache.syncope.core.persistence.api.entity.policy.PushPolicy;
import org.apache.syncope.core.persistence.api.entity.resource.Provision;
@@ -64,6 +65,8 @@ public class JPAExternalResourceDAO extends AbstractDAO<ExternalResource> implem
query.append("accountPolicy");
} else if (PasswordPolicy.class.isAssignableFrom(policyClass)) {
query.append("passwordPolicy");
+ } else if (PropagationPolicy.class.isAssignableFrom(policyClass)) {
+ query.append("propagationPolicy");
} else if (PullPolicy.class.isAssignableFrom(policyClass)) {
query.append("pullPolicy");
} else if (PushPolicy.class.isAssignableFrom(policyClass)) {
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAPolicyDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAPolicyDAO.java
index fe65201..2f349fe 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAPolicyDAO.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAPolicyDAO.java
@@ -30,6 +30,7 @@ import org.apache.syncope.core.persistence.api.entity.policy.AttrReleasePolicy;
import org.apache.syncope.core.persistence.api.entity.policy.AuthPolicy;
import org.apache.syncope.core.persistence.api.entity.policy.PasswordPolicy;
import org.apache.syncope.core.persistence.api.entity.policy.Policy;
+import org.apache.syncope.core.persistence.api.entity.policy.PropagationPolicy;
import org.apache.syncope.core.persistence.api.entity.policy.PullPolicy;
import org.apache.syncope.core.persistence.api.entity.policy.PushPolicy;
import org.apache.syncope.core.persistence.api.entity.resource.ExternalResource;
@@ -39,6 +40,7 @@ import org.apache.syncope.core.persistence.jpa.entity.policy.JPAAccountPolicy;
import org.apache.syncope.core.persistence.jpa.entity.policy.JPAAttrReleasePolicy;
import org.apache.syncope.core.persistence.jpa.entity.policy.JPAAuthPolicy;
import org.apache.syncope.core.persistence.jpa.entity.policy.JPAPasswordPolicy;
+import org.apache.syncope.core.persistence.jpa.entity.policy.JPAPropagationPolicy;
import org.apache.syncope.core.persistence.jpa.entity.policy.JPAPullCorrelationRuleEntity;
import org.apache.syncope.core.persistence.jpa.entity.policy.JPAPullPolicy;
import org.apache.syncope.core.persistence.jpa.entity.policy.JPAPushCorrelationRuleEntity;
@@ -51,6 +53,8 @@ public class JPAPolicyDAO extends AbstractDAO<Policy> implements PolicyDAO {
? JPAAccountPolicy.class
: PasswordPolicy.class.isAssignableFrom(reference)
? JPAPasswordPolicy.class
+ : PropagationPolicy.class.isAssignableFrom(reference)
+ ? JPAPropagationPolicy.class
: PullPolicy.class.isAssignableFrom(reference)
? JPAPullPolicy.class
: PushPolicy.class.isAssignableFrom(reference)
@@ -168,11 +172,14 @@ public class JPAPolicyDAO extends AbstractDAO<Policy> implements PolicyDAO {
if (!(policy instanceof AuthPolicy)
&& !(policy instanceof AttrReleasePolicy)
&& !(policy instanceof AccessPolicy)) {
+
resourceDAO.findByPolicy(policy).forEach(resource -> {
if (policy instanceof AccountPolicy) {
resource.setAccountPolicy(null);
} else if (policy instanceof PasswordPolicy) {
resource.setPasswordPolicy(null);
+ } else if (policy instanceof PropagationPolicy) {
+ resource.setPropagationPolicy(null);
} else if (policy instanceof PullPolicy) {
resource.setPullPolicy(null);
} else if (policy instanceof PushPolicy) {
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPARealmDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPARealmDAO.java
index b36b473..9fb6c1d 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPARealmDAO.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPARealmDAO.java
@@ -19,7 +19,6 @@
package org.apache.syncope.core.persistence.jpa.dao;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.List;
import java.util.Optional;
import javax.persistence.NoResultException;
@@ -41,6 +40,7 @@ import org.apache.syncope.core.persistence.api.entity.resource.ExternalResource;
import org.apache.syncope.core.persistence.jpa.entity.JPARealm;
import org.springframework.transaction.annotation.Transactional;
import org.apache.syncope.core.persistence.api.entity.policy.AuthPolicy;
+import org.apache.syncope.core.persistence.api.entity.policy.PropagationPolicy;
public class JPARealmDAO extends AbstractDAO<Realm> implements RealmDAO {
@@ -145,9 +145,10 @@ public class JPARealmDAO extends AbstractDAO<Realm> implements RealmDAO {
@Override
public <T extends Policy> List<Realm> findByPolicy(final T policy) {
- if (ProvisioningPolicy.class.isAssignableFrom(policy.getClass())) {
- return Collections.<Realm>emptyList();
+ if (policy instanceof PropagationPolicy || policy instanceof ProvisioningPolicy) {
+ return List.of();
}
+
String policyColumn = null;
if (policy instanceof AccountPolicy) {
policyColumn = "accountPolicy";
@@ -167,10 +168,10 @@ public class JPARealmDAO extends AbstractDAO<Realm> implements RealmDAO {
query.setParameter("policy", policy);
List<Realm> result = new ArrayList<>();
- query.getResultList().stream().map(realm -> {
+ query.getResultList().forEach(realm -> {
result.add(realm);
- return realm;
- }).forEachOrdered(realm -> result.addAll(findSamePolicyChildren(realm, policy)));
+ result.addAll(findSamePolicyChildren(realm, policy));
+ });
return result;
}
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPARelationshipTypeDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPARelationshipTypeDAO.java
index 2a29d9b..f4595c9 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPARelationshipTypeDAO.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPARelationshipTypeDAO.java
@@ -88,7 +88,7 @@ public class JPARelationshipTypeDAO extends AbstractDAO<RelationshipType> implem
}
relationship.setLeftEnd(null);
return relationship;
- }).forEachOrdered(relationship -> entityManager().remove(relationship));
+ }).forEach(entityManager()::remove);
entityManager().remove(type);
}
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAUserDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAUserDAO.java
index 7ecb27a..84be74f 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAUserDAO.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAUserDAO.java
@@ -317,13 +317,13 @@ public class JPAUserDAO extends AbstractAnyDAO<User> implements UserDAO {
findAllResources(user).stream().
map(ExternalResource::getAccountPolicy).
filter(Objects::nonNull).
- forEachOrdered(policies::add);
+ forEach(policies::add);
// add realm policies
realmDAO.findAncestors(user.getRealm()).stream().
map(Realm::getAccountPolicy).
filter(Objects::nonNull).
- forEachOrdered(policies::add);
+ forEach(policies::add);
return policies;
}
@@ -533,7 +533,7 @@ public class JPAUserDAO extends AbstractAnyDAO<User> implements UserDAO {
query.getResultList().stream().map(resultKey -> resultKey instanceof Object[]
? (String) ((Object[]) resultKey)[0]
: ((String) resultKey)).
- forEachOrdered(roleKey -> {
+ forEach(roleKey -> {
Role role = roleDAO.find(roleKey.toString());
if (role == null) {
LOG.error("Could not find role {}, even though returned by the native query", roleKey);
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAEntityFactory.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAEntityFactory.java
index 0b1b369..7b04ca6 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAEntityFactory.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAEntityFactory.java
@@ -25,6 +25,7 @@ import org.apache.syncope.core.persistence.api.entity.AnyTemplateRealm;
import org.apache.syncope.core.persistence.api.entity.AnyType;
import org.apache.syncope.core.persistence.api.entity.AnyTypeClass;
import org.apache.syncope.core.persistence.api.entity.Application;
+import org.apache.syncope.core.persistence.api.entity.AuditConf;
import org.apache.syncope.core.persistence.api.entity.Batch;
import org.apache.syncope.core.persistence.api.entity.ConnInstance;
import org.apache.syncope.core.persistence.api.entity.ConnPoolConf;
@@ -46,6 +47,7 @@ import org.apache.syncope.core.persistence.api.entity.Report;
import org.apache.syncope.core.persistence.api.entity.ReportExec;
import org.apache.syncope.core.persistence.api.entity.ReportTemplate;
import org.apache.syncope.core.persistence.api.entity.Role;
+import org.apache.syncope.core.persistence.api.entity.SRARoute;
import org.apache.syncope.core.persistence.api.entity.SchemaLabel;
import org.apache.syncope.core.persistence.api.entity.VirSchema;
import org.apache.syncope.core.persistence.api.entity.anyobject.ADynGroupMembership;
@@ -58,7 +60,12 @@ import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject;
import org.apache.syncope.core.persistence.api.entity.auth.AuthModule;
import org.apache.syncope.core.persistence.api.entity.auth.AuthModuleItem;
import org.apache.syncope.core.persistence.api.entity.auth.AuthProfile;
+import org.apache.syncope.core.persistence.api.entity.auth.CASSPClientApp;
import org.apache.syncope.core.persistence.api.entity.auth.OIDCJWKS;
+import org.apache.syncope.core.persistence.api.entity.auth.OIDCRPClientApp;
+import org.apache.syncope.core.persistence.api.entity.auth.SAML2IdPEntity;
+import org.apache.syncope.core.persistence.api.entity.auth.SAML2SPClientApp;
+import org.apache.syncope.core.persistence.api.entity.auth.SAML2SPEntity;
import org.apache.syncope.core.persistence.api.entity.auth.WAConfigEntry;
import org.apache.syncope.core.persistence.api.entity.group.GPlainAttr;
import org.apache.syncope.core.persistence.api.entity.group.GPlainAttrUniqueValue;
@@ -70,6 +77,7 @@ import org.apache.syncope.core.persistence.api.entity.policy.AccountPolicy;
import org.apache.syncope.core.persistence.api.entity.policy.AttrReleasePolicy;
import org.apache.syncope.core.persistence.api.entity.policy.AuthPolicy;
import org.apache.syncope.core.persistence.api.entity.policy.PasswordPolicy;
+import org.apache.syncope.core.persistence.api.entity.policy.PropagationPolicy;
import org.apache.syncope.core.persistence.api.entity.policy.PullCorrelationRuleEntity;
import org.apache.syncope.core.persistence.api.entity.policy.PullPolicy;
import org.apache.syncope.core.persistence.api.entity.policy.PushCorrelationRuleEntity;
@@ -109,10 +117,13 @@ import org.apache.syncope.core.persistence.jpa.entity.anyobject.JPAAPlainAttrUni
import org.apache.syncope.core.persistence.jpa.entity.anyobject.JPAAPlainAttrValue;
import org.apache.syncope.core.persistence.jpa.entity.anyobject.JPAARelationship;
import org.apache.syncope.core.persistence.jpa.entity.anyobject.JPAAnyObject;
+import org.apache.syncope.core.persistence.jpa.entity.auth.JPAAuthModule;
+import org.apache.syncope.core.persistence.jpa.entity.auth.JPAAuthModuleItem;
import org.apache.syncope.core.persistence.jpa.entity.auth.JPAAuthProfile;
import org.apache.syncope.core.persistence.jpa.entity.auth.JPACASSPClientApp;
import org.apache.syncope.core.persistence.jpa.entity.auth.JPAOIDCJWKS;
import org.apache.syncope.core.persistence.jpa.entity.auth.JPAOIDCRPClientApp;
+import org.apache.syncope.core.persistence.jpa.entity.auth.JPASAML2IdPEntity;
import org.apache.syncope.core.persistence.jpa.entity.auth.JPASAML2SPClientApp;
import org.apache.syncope.core.persistence.jpa.entity.auth.JPASAML2SPEntity;
import org.apache.syncope.core.persistence.jpa.entity.auth.JPAWAConfigEntry;
@@ -126,6 +137,7 @@ import org.apache.syncope.core.persistence.jpa.entity.policy.JPAAccountPolicy;
import org.apache.syncope.core.persistence.jpa.entity.policy.JPAAttrReleasePolicy;
import org.apache.syncope.core.persistence.jpa.entity.policy.JPAAuthPolicy;
import org.apache.syncope.core.persistence.jpa.entity.policy.JPAPasswordPolicy;
+import org.apache.syncope.core.persistence.jpa.entity.policy.JPAPropagationPolicy;
import org.apache.syncope.core.persistence.jpa.entity.policy.JPAPullCorrelationRuleEntity;
import org.apache.syncope.core.persistence.jpa.entity.policy.JPAPullPolicy;
import org.apache.syncope.core.persistence.jpa.entity.policy.JPAPushCorrelationRuleEntity;
@@ -158,16 +170,6 @@ import org.apache.syncope.core.persistence.jpa.entity.user.JPAUPlainAttrValue;
import org.apache.syncope.core.persistence.jpa.entity.user.JPAURelationship;
import org.apache.syncope.core.persistence.jpa.entity.user.JPAUser;
import org.apache.syncope.core.spring.security.SecureRandomUtils;
-import org.apache.syncope.core.persistence.jpa.entity.auth.JPAAuthModule;
-import org.apache.syncope.core.persistence.jpa.entity.auth.JPAAuthModuleItem;
-import org.apache.syncope.core.persistence.jpa.entity.auth.JPASAML2IdPEntity;
-import org.apache.syncope.core.persistence.api.entity.SRARoute;
-import org.apache.syncope.core.persistence.api.entity.auth.SAML2IdPEntity;
-import org.apache.syncope.core.persistence.api.entity.auth.SAML2SPEntity;
-import org.apache.syncope.core.persistence.api.entity.auth.SAML2SPClientApp;
-import org.apache.syncope.core.persistence.api.entity.auth.CASSPClientApp;
-import org.apache.syncope.core.persistence.api.entity.auth.OIDCRPClientApp;
-import org.apache.syncope.core.persistence.api.entity.AuditConf;
public class JPAEntityFactory implements EntityFactory {
@@ -188,6 +190,8 @@ public class JPAEntityFactory implements EntityFactory {
result = (E) new JPAAccountPolicy();
} else if (reference.equals(PasswordPolicy.class)) {
result = (E) new JPAPasswordPolicy();
+ } else if (reference.equals(PropagationPolicy.class)) {
+ result = (E) new JPAPropagationPolicy();
} else if (reference.equals(PushPolicy.class)) {
result = (E) new JPAPushPolicy();
} else if (reference.equals(PullPolicy.class)) {
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/policy/JPAPolicyUtils.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/policy/JPAPolicyUtils.java
index c694eca..c30361b 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/policy/JPAPolicyUtils.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/policy/JPAPolicyUtils.java
@@ -28,6 +28,7 @@ import org.apache.syncope.core.persistence.api.entity.policy.PolicyUtils;
import org.apache.syncope.core.persistence.api.entity.policy.PullPolicy;
import org.apache.syncope.core.persistence.api.entity.policy.PushPolicy;
import org.apache.syncope.core.persistence.api.entity.policy.AuthPolicy;
+import org.apache.syncope.core.persistence.api.entity.policy.PropagationPolicy;
public class JPAPolicyUtils implements PolicyUtils {
@@ -51,9 +52,6 @@ public class JPAPolicyUtils implements PolicyUtils {
case PASSWORD:
return PasswordPolicy.class;
- case PULL:
- return PullPolicy.class;
-
case AUTH:
return AuthPolicy.class;
@@ -63,6 +61,12 @@ public class JPAPolicyUtils implements PolicyUtils {
case ACCESS:
return AccessPolicy.class;
+ case PROPAGATION:
+ return PropagationPolicy.class;
+
+ case PULL:
+ return PullPolicy.class;
+
case PUSH:
default:
return PushPolicy.class;
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/policy/JPAPolicyUtilsFactory.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/policy/JPAPolicyUtilsFactory.java
index ec0378e..f13302a 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/policy/JPAPolicyUtilsFactory.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/policy/JPAPolicyUtilsFactory.java
@@ -24,6 +24,7 @@ import org.apache.syncope.common.lib.policy.AttrReleasePolicyTO;
import org.apache.syncope.common.lib.policy.AuthPolicyTO;
import org.apache.syncope.common.lib.policy.PasswordPolicyTO;
import org.apache.syncope.common.lib.policy.PolicyTO;
+import org.apache.syncope.common.lib.policy.PropagationPolicyTO;
import org.apache.syncope.common.lib.policy.PullPolicyTO;
import org.apache.syncope.common.lib.policy.PushPolicyTO;
import org.apache.syncope.common.lib.types.PolicyType;
@@ -35,6 +36,7 @@ import org.apache.syncope.core.persistence.api.entity.policy.PasswordPolicy;
import org.apache.syncope.core.persistence.api.entity.policy.Policy;
import org.apache.syncope.core.persistence.api.entity.policy.PolicyUtils;
import org.apache.syncope.core.persistence.api.entity.policy.PolicyUtilsFactory;
+import org.apache.syncope.core.persistence.api.entity.policy.PropagationPolicy;
import org.apache.syncope.core.persistence.api.entity.policy.PullPolicy;
import org.apache.syncope.core.persistence.api.entity.policy.PushPolicy;
@@ -52,6 +54,8 @@ public class JPAPolicyUtilsFactory implements PolicyUtilsFactory {
type = PolicyType.ACCOUNT;
} else if (policy instanceof PasswordPolicy) {
type = PolicyType.PASSWORD;
+ } else if (policy instanceof PropagationPolicy) {
+ type = PolicyType.PROPAGATION;
} else if (policy instanceof PullPolicy) {
type = PolicyType.PULL;
} else if (policy instanceof PushPolicy) {
@@ -76,6 +80,8 @@ public class JPAPolicyUtilsFactory implements PolicyUtilsFactory {
type = PolicyType.ACCOUNT;
} else if (policyClass == PasswordPolicyTO.class) {
type = PolicyType.PASSWORD;
+ } else if (policyClass == PropagationPolicyTO.class) {
+ type = PolicyType.PROPAGATION;
} else if (policyClass == PullPolicyTO.class) {
type = PolicyType.PULL;
} else if (policyClass == PushPolicyTO.class) {
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/policy/JPAPropagationPolicy.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/policy/JPAPropagationPolicy.java
new file mode 100644
index 0000000..207886f
--- /dev/null
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/policy/JPAPropagationPolicy.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.persistence.jpa.entity.policy;
+
+import java.util.Optional;
+import javax.persistence.Entity;
+import javax.persistence.EnumType;
+import javax.persistence.Enumerated;
+import javax.persistence.Table;
+import javax.validation.constraints.NotNull;
+import org.apache.syncope.common.lib.types.BackOffStrategy;
+import org.apache.syncope.core.persistence.api.entity.policy.PropagationPolicy;
+
+@Entity
+@Table(name = JPAPropagationPolicy.TABLE)
+public class JPAPropagationPolicy extends AbstractPolicy implements PropagationPolicy {
+
+ private static final long serialVersionUID = 17400846199535L;
+
+ public static final String TABLE = "PropagationPolicy";
+
+ @Enumerated(EnumType.STRING)
+ @NotNull
+ private BackOffStrategy backOffStrategy;
+
+ private String backOffParams;
+
+ @NotNull
+ private Integer maxAttempts = 3;
+
+ @Override
+ public BackOffStrategy getBackOffStrategy() {
+ return backOffStrategy;
+ }
+
+ @Override
+ public void setBackOffStrategy(final BackOffStrategy backOffStrategy) {
+ this.backOffStrategy = backOffStrategy;
+ }
+
+ @Override
+ public String getBackOffParams() {
+ return backOffParams;
+ }
+
+ @Override
+ public void setBackOffParams(final String backOffParams) {
+ this.backOffParams = backOffParams;
+ }
+
+ @Override
+ public int getMaxAttempts() {
+ return Optional.ofNullable(maxAttempts).orElse(3);
+ }
+
+ @Override
+ public void setMaxAttempts(final int maxAttempts) {
+ this.maxAttempts = maxAttempts;
+ }
+}
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/resource/JPAExternalResource.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/resource/JPAExternalResource.java
index ef97aa6..d9efbda 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/resource/JPAExternalResource.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/resource/JPAExternalResource.java
@@ -53,6 +53,7 @@ import org.apache.syncope.core.persistence.jpa.validation.entity.ExternalResourc
import org.apache.syncope.core.provisioning.api.serialization.POJOHelper;
import org.apache.syncope.core.persistence.api.entity.AnyType;
import org.apache.syncope.core.persistence.api.entity.Implementation;
+import org.apache.syncope.core.persistence.api.entity.policy.PropagationPolicy;
import org.apache.syncope.core.persistence.api.entity.resource.ExternalResource;
import org.apache.syncope.core.persistence.api.entity.resource.Provision;
import org.apache.syncope.core.persistence.jpa.entity.policy.JPAAccountPolicy;
@@ -64,6 +65,7 @@ import org.apache.syncope.core.persistence.api.entity.policy.PushPolicy;
import org.apache.syncope.core.persistence.api.entity.resource.OrgUnit;
import org.apache.syncope.core.persistence.jpa.entity.AbstractProvidedKeyEntity;
import org.apache.syncope.core.persistence.jpa.entity.JPAImplementation;
+import org.apache.syncope.core.persistence.jpa.entity.policy.JPAPropagationPolicy;
import org.apache.syncope.core.persistence.jpa.entity.policy.JPAPushPolicy;
import org.identityconnectors.framework.common.objects.ObjectClass;
@@ -131,6 +133,9 @@ public class JPAExternalResource extends AbstractProvidedKeyEntity implements Ex
private JPAAccountPolicy accountPolicy;
@ManyToOne(fetch = FetchType.EAGER)
+ private JPAPropagationPolicy propagationPolicy;
+
+ @ManyToOne(fetch = FetchType.EAGER)
private JPAPullPolicy pullPolicy;
@ManyToOne(fetch = FetchType.EAGER)
@@ -311,6 +316,17 @@ public class JPAExternalResource extends AbstractProvidedKeyEntity implements Ex
}
@Override
+ public PropagationPolicy getPropagationPolicy() {
+ return propagationPolicy;
+ }
+
+ @Override
+ public void setPropagationPolicy(final PropagationPolicy propagationPolicy) {
+ checkType(propagationPolicy, JPAPropagationPolicy.class);
+ this.propagationPolicy = (JPAPropagationPolicy) propagationPolicy;
+ }
+
+ @Override
public PullPolicy getPullPolicy() {
return pullPolicy;
}
diff --git a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/PolicyTest.java b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/PolicyTest.java
index 785b79f..1607f0d 100644
--- a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/PolicyTest.java
+++ b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/PolicyTest.java
@@ -33,6 +33,7 @@ import org.apache.syncope.common.lib.policy.DefaultAuthPolicyConf;
import org.apache.syncope.common.lib.policy.DefaultPasswordRuleConf;
import org.apache.syncope.common.lib.policy.DefaultPullCorrelationRuleConf;
import org.apache.syncope.common.lib.policy.DefaultPushCorrelationRuleConf;
+import org.apache.syncope.common.lib.types.BackOffStrategy;
import org.apache.syncope.common.lib.types.ConflictResolutionAction;
import org.apache.syncope.common.lib.types.IdMImplementationType;
import org.apache.syncope.common.lib.types.IdRepoImplementationType;
@@ -41,12 +42,14 @@ import org.apache.syncope.core.persistence.api.dao.AnyTypeDAO;
import org.apache.syncope.core.persistence.api.dao.ImplementationDAO;
import org.apache.syncope.core.persistence.api.dao.PolicyDAO;
import org.apache.syncope.core.persistence.api.dao.PullCorrelationRule;
+import org.apache.syncope.core.persistence.api.dao.PushCorrelationRule;
import org.apache.syncope.core.persistence.api.entity.Implementation;
import org.apache.syncope.core.persistence.api.entity.policy.AccessPolicy;
import org.apache.syncope.core.persistence.api.entity.policy.AttrReleasePolicy;
import org.apache.syncope.core.persistence.api.entity.policy.AuthPolicy;
import org.apache.syncope.core.persistence.api.entity.policy.PasswordPolicy;
import org.apache.syncope.core.persistence.api.entity.policy.Policy;
+import org.apache.syncope.core.persistence.api.entity.policy.PropagationPolicy;
import org.apache.syncope.core.persistence.api.entity.policy.PullCorrelationRuleEntity;
import org.apache.syncope.core.persistence.api.entity.policy.PullPolicy;
import org.apache.syncope.core.persistence.api.entity.policy.PushCorrelationRuleEntity;
@@ -78,6 +81,12 @@ public class PolicyTest extends AbstractTest {
@Test
public void findByKey() {
+ PropagationPolicy propagationPolicy = policyDAO.find("89d322db-9878-420c-b49c-67be13df9a12");
+ assertNotNull(propagationPolicy);
+ assertEquals(BackOffStrategy.FIXED, propagationPolicy.getBackOffStrategy());
+ assertEquals("10000", propagationPolicy.getBackOffParams());
+ assertEquals(5, propagationPolicy.getMaxAttempts());
+
PullPolicy pullPolicy = policyDAO.find("880f8553-069b-4aed-9930-2cd53873f544");
assertNotNull(pullPolicy);
@@ -125,6 +134,10 @@ public class PolicyTest extends AbstractTest {
@Test
public void findByType() {
+ List<PropagationPolicy> propagationPolicies = policyDAO.find(PropagationPolicy.class);
+ assertNotNull(propagationPolicies);
+ assertFalse(propagationPolicies.isEmpty());
+
List<PullPolicy> pullPolicies = policyDAO.find(PullPolicy.class);
assertNotNull(pullPolicies);
assertFalse(pullPolicies.isEmpty());
@@ -143,10 +156,30 @@ public class PolicyTest extends AbstractTest {
}
@Test
- public void create() {
- PullPolicy policy = entityFactory.newEntity(PullPolicy.class);
- policy.setConflictResolutionAction(ConflictResolutionAction.IGNORE);
- policy.setName("Pull policy");
+ public void createPropagation() {
+ int beforeCount = policyDAO.findAll().size();
+
+ PropagationPolicy propagationPolicy = entityFactory.newEntity(PropagationPolicy.class);
+ propagationPolicy.setName("Propagation policy");
+ propagationPolicy.setMaxAttempts(5);
+ propagationPolicy.setBackOffStrategy(BackOffStrategy.EXPONENTIAL);
+ propagationPolicy.setBackOffParams(propagationPolicy.getBackOffStrategy().getDefaultBackOffParams());
+
+ propagationPolicy = policyDAO.save(propagationPolicy);
+ assertNotNull(propagationPolicy);
+ assertEquals(5, propagationPolicy.getMaxAttempts());
+ assertEquals(BackOffStrategy.EXPONENTIAL, propagationPolicy.getBackOffStrategy());
+ assertEquals(BackOffStrategy.EXPONENTIAL.getDefaultBackOffParams(), propagationPolicy.getBackOffParams());
+
+ int afterCount = policyDAO.findAll().size();
+ assertEquals(afterCount, beforeCount + 1);
+ }
+
+ @Test
+ public void createPull() {
+ PullPolicy pullPolicy = entityFactory.newEntity(PullPolicy.class);
+ pullPolicy.setConflictResolutionAction(ConflictResolutionAction.IGNORE);
+ pullPolicy.setName("Pull policy");
final String pullURuleName = "net.tirasa.pull.correlation.TirasaURule";
final String pullGRuleName = "net.tirasa.pull.correlation.TirasaGRule";
@@ -160,9 +193,9 @@ public class PolicyTest extends AbstractTest {
PullCorrelationRuleEntity rule1 = entityFactory.newEntity(PullCorrelationRuleEntity.class);
rule1.setAnyType(anyTypeDAO.findUser());
- rule1.setPullPolicy(policy);
+ rule1.setPullPolicy(pullPolicy);
rule1.setImplementation(impl1);
- policy.add(rule1);
+ pullPolicy.add(rule1);
Implementation impl2 = entityFactory.newEntity(Implementation.class);
impl2.setKey(pullGRuleName);
@@ -173,19 +206,67 @@ public class PolicyTest extends AbstractTest {
PullCorrelationRuleEntity rule2 = entityFactory.newEntity(PullCorrelationRuleEntity.class);
rule2.setAnyType(anyTypeDAO.findGroup());
- rule2.setPullPolicy(policy);
+ rule2.setPullPolicy(pullPolicy);
rule2.setImplementation(impl2);
- policy.add(rule2);
+ pullPolicy.add(rule2);
- policy = policyDAO.save(policy);
+ pullPolicy = policyDAO.save(pullPolicy);
- assertNotNull(policy);
+ assertNotNull(pullPolicy);
assertEquals(pullURuleName,
- policy.getCorrelationRule(anyTypeDAO.findUser()).get().getImplementation().getKey());
+ pullPolicy.getCorrelationRule(anyTypeDAO.findUser()).get().getImplementation().getKey());
assertEquals(pullGRuleName,
- policy.getCorrelationRule(anyTypeDAO.findGroup()).get().getImplementation().getKey());
+ pullPolicy.getCorrelationRule(anyTypeDAO.findGroup()).get().getImplementation().getKey());
+ }
+
+ @Test
+ public void createPush() {
+ PushPolicy pushPolicy = entityFactory.newEntity(PushPolicy.class);
+ pushPolicy.setName("Push policy");
+ pushPolicy.setConflictResolutionAction(ConflictResolutionAction.IGNORE);
+
+ final String pushURuleName = "net.tirasa.push.correlation.TirasaURule";
+ final String pushGRuleName = "net.tirasa.push.correlation.TirasaGRule";
+
+ Implementation impl1 = entityFactory.newEntity(Implementation.class);
+ impl1.setKey(pushURuleName);
+ impl1.setEngine(ImplementationEngine.JAVA);
+ impl1.setType(IdMImplementationType.PUSH_CORRELATION_RULE);
+ impl1.setBody(PushCorrelationRule.class.getName());
+ impl1 = implementationDAO.save(impl1);
+
+ PushCorrelationRuleEntity rule1 = entityFactory.newEntity(PushCorrelationRuleEntity.class);
+ rule1.setAnyType(anyTypeDAO.findUser());
+ rule1.setPushPolicy(pushPolicy);
+ rule1.setImplementation(impl1);
+ pushPolicy.add(rule1);
+
+ Implementation impl2 = entityFactory.newEntity(Implementation.class);
+ impl2.setKey(pushGRuleName);
+ impl2.setEngine(ImplementationEngine.JAVA);
+ impl2.setType(IdMImplementationType.PUSH_CORRELATION_RULE);
+ impl2.setBody(PushCorrelationRule.class.getName());
+ impl2 = implementationDAO.save(impl2);
+
+ PushCorrelationRuleEntity rule2 = entityFactory.newEntity(PushCorrelationRuleEntity.class);
+ rule2.setAnyType(anyTypeDAO.findGroup());
+ rule2.setPushPolicy(pushPolicy);
+ rule2.setImplementation(impl2);
+ pushPolicy.add(rule2);
+
+ pushPolicy = policyDAO.save(pushPolicy);
+
+ assertNotNull(pushPolicy);
+ assertEquals(pushURuleName,
+ pushPolicy.getCorrelationRule(anyTypeDAO.findUser()).get().getImplementation().getKey());
+ assertEquals(pushGRuleName,
+ pushPolicy.getCorrelationRule(anyTypeDAO.findGroup()).get().getImplementation().getKey());
+ }
+ @Test
+ public void createAccess() {
int beforeCount = policyDAO.findAll().size();
+
AccessPolicy accessPolicy = entityFactory.newEntity(AccessPolicy.class);
accessPolicy.setName("AttrReleasePolicyAllowEverything");
@@ -200,8 +281,12 @@ public class PolicyTest extends AbstractTest {
int afterCount = policyDAO.findAll().size();
assertEquals(afterCount, beforeCount + 1);
+ }
+
+ @Test
+ public void createAuth() {
+ int beforeCount = policyDAO.findAll().size();
- beforeCount = policyDAO.findAll().size();
AuthPolicy authPolicy = entityFactory.newEntity(AuthPolicy.class);
authPolicy.setName("AuthPolicyTest");
@@ -215,10 +300,14 @@ public class PolicyTest extends AbstractTest {
assertNotNull(authPolicy);
assertNotNull(authPolicy.getKey());
- afterCount = policyDAO.findAll().size();
+ int afterCount = policyDAO.findAll().size();
assertEquals(afterCount, beforeCount + 1);
+ }
+
+ @Test
+ public void createAttrRelease() {
+ int beforeCount = policyDAO.findAll().size();
- beforeCount = policyDAO.findAll().size();
AttrReleasePolicy attrReleasePolicy = entityFactory.newEntity(AttrReleasePolicy.class);
attrReleasePolicy.setName("AttrReleasePolicyAllowEverything");
attrReleasePolicy.setStatus(Boolean.TRUE);
@@ -235,7 +324,7 @@ public class PolicyTest extends AbstractTest {
assertNotNull(attrReleasePolicy.getStatus());
assertNotNull(((DefaultAttrReleasePolicyConf) attrReleasePolicy.getConf()).getAllowedAttrs());
- afterCount = policyDAO.findAll().size();
+ int afterCount = policyDAO.findAll().size();
assertEquals(afterCount, beforeCount + 1);
}
diff --git a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/GroupTest.java b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/GroupTest.java
index c857676..05ab3f0 100644
--- a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/GroupTest.java
+++ b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/GroupTest.java
@@ -181,7 +181,7 @@ public class GroupTest extends AbstractTest {
query.getResultList().stream().map(resultKey -> resultKey instanceof Object[]
? (String) ((Object[]) resultKey)[0]
: ((String) resultKey)).
- forEachOrdered(actualKey -> {
+ forEach(actualKey -> {
Group group = groupDAO.find(actualKey.toString());
if (group == null) {
} else if (!result.contains(group)) {
@@ -282,7 +282,7 @@ public class GroupTest extends AbstractTest {
query.getResultList().stream().map(resultKey -> resultKey instanceof Object[]
? (String) ((Object[]) resultKey)[0]
: ((String) resultKey)).
- forEachOrdered(actualKey -> {
+ forEach(actualKey -> {
Group group = groupDAO.find(actualKey.toString());
if (group == null) {
} else if (!result.contains(group)) {
diff --git a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/ResourceTest.java b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/ResourceTest.java
index 32bcd63..cde0f36 100644
--- a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/ResourceTest.java
+++ b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/ResourceTest.java
@@ -37,7 +37,6 @@ import org.apache.syncope.core.persistence.api.dao.TaskDAO;
import org.apache.syncope.core.persistence.api.dao.UserDAO;
import org.apache.syncope.core.persistence.api.dao.VirSchemaDAO;
import org.apache.syncope.core.persistence.api.entity.ConnInstance;
-import org.apache.syncope.core.persistence.api.entity.Entity;
import org.apache.syncope.core.persistence.api.entity.resource.ExternalResource;
import org.apache.syncope.core.persistence.api.entity.policy.PasswordPolicy;
import org.apache.syncope.core.persistence.api.entity.resource.Mapping;
@@ -210,7 +209,7 @@ public class ResourceTest extends AbstractTest {
List<User> users = userDAO.findByResource(resource);
assertNotNull(users);
- Set<String> userKeys = users.stream().map(Entity::getKey).collect(Collectors.toSet());
+ Set<String> userKeys = users.stream().map(User::getKey).collect(Collectors.toSet());
// -------------------------------------
// Get tasks
@@ -229,13 +228,11 @@ public class ResourceTest extends AbstractTest {
assertNull(actual);
// resource must be not referenced any more from users
- userKeys.stream().
- map(key -> userDAO.find(key)).
- map(actualUser -> {
- assertNotNull(actualUser);
- return actualUser;
- }).forEachOrdered((actualUser) -> userDAO.findAllResources(actualUser).
- forEach(res -> assertFalse(res.getKey().equalsIgnoreCase(resource.getKey()))));
+ userKeys.stream().map(userDAO::find).forEach(user -> {
+ assertNotNull(user);
+ userDAO.findAllResources(user).
+ forEach(r -> assertFalse(r.getKey().equalsIgnoreCase(resource.getKey())));
+ });
// resource must be not referenced any more from the connector
ConnInstance actualConnector = connInstanceDAO.find(connector.getKey());
@@ -260,7 +257,7 @@ public class ResourceTest extends AbstractTest {
List<? extends MappingItem> items = ldap.getProvision(anyTypeDAO.findGroup()).get().getMapping().getItems();
assertNotNull(items);
assertFalse(items.isEmpty());
- List<String> itemKeys = items.stream().map(Entity::getKey).collect(Collectors.toList());
+ List<String> itemKeys = items.stream().map(MappingItem::getKey).collect(Collectors.toList());
Provision groupProvision = ldap.getProvision(anyTypeDAO.findGroup()).get();
virSchemaDAO.findByProvision(groupProvision).
diff --git a/core/persistence-jpa/src/test/resources/domains/MasterContent.xml b/core/persistence-jpa/src/test/resources/domains/MasterContent.xml
index d4684d3..817ce37 100644
--- a/core/persistence-jpa/src/test/resources/domains/MasterContent.xml
+++ b/core/persistence-jpa/src/test/resources/domains/MasterContent.xml
@@ -43,7 +43,9 @@ under the License.
<Implementation id="DefaultPasswordRuleConf3" type="PASSWORD_RULE" engine="JAVA"
body='{"_class":"org.apache.syncope.common.lib.policy.DefaultPasswordRuleConf","maxLength":0,"minLength":10,"nonAlphanumericRequired":true,"alphanumericRequired":false,"digitRequired":true,"lowercaseRequired":true,"uppercaseRequired":true,"mustStartWithDigit":true,"mustntStartWithDigit":false,"mustEndWithDigit":true,"mustntEndWithDigit":false,"mustStartWithNonAlpha":false,"mustStartWithAlpha":false,"mustntStartWithNonAlpha":false,"mustntStartWithAlpha":false,"mustEndWit [...]
<PasswordPolicyRule policy_id="55e5de0b-c79c-4e66-adda-251b6fb8579a" implementation_id="DefaultPasswordRuleConf3"/>
-
+ <PropagationPolicy id="89d322db-9878-420c-b49c-67be13df9a12" name="sample propagation policy"
+ maxAttempts="5" backOffStrategy="FIXED" backOffParams="10000"/>
+
<!-- Authentication policies -->
<AuthPolicy id="659b9906-4b6e-4bc0-aca0-6809dff346d4" name="MyDefaultAuthPolicyConf"
jsonConf='{"_class":"org.apache.syncope.common.lib.policy.DefaultAuthPolicyConf","authModules":["LdapAuthenticationTest"]}'/>
@@ -791,7 +793,8 @@ under the License.
<ExternalResource id="resource-db-scripted" connector_id="a6d017fd-a705-4507-bb7c-6ab6a6745997"
randomPwdIfNotProvided="0" createTraceLevel="ALL" deleteTraceLevel="ALL" provisioningTraceLevel="ALL" updateTraceLevel="ALL"
- enforceMandatoryCondition="0" overrideCapabilities="0"/>
+ enforceMandatoryCondition="0" overrideCapabilities="0"
+ propagationPolicy_id="89d322db-9878-420c-b49c-67be13df9a12"/>
<ExternalResource id="rest-target-resource" connector_id="44c02549-19c3-483c-8025-4919c3283c37"
createTraceLevel="ALL" updateTraceLevel="ALL" deleteTraceLevel="ALL" provisioningTraceLevel="ALL"
diff --git a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/propagation/PropagationTaskExecutor.java b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/propagation/PropagationTaskExecutor.java
index cd20967..470ba42 100644
--- a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/propagation/PropagationTaskExecutor.java
+++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/propagation/PropagationTaskExecutor.java
@@ -43,6 +43,13 @@ public interface PropagationTaskExecutor {
String MANDATORY_NULL_OR_EMPTY_ATTR_NAME = "__MANDATORY_NULL_OR_EMPTY__";
/**
+ * Remove any RetryTemplate defined for the given External Resource from local cache.
+ *
+ * @param resource External Resource name
+ */
+ void expireRetryTemplate(String resource);
+
+ /**
* Execute the given task and returns the generated {@link TaskExec}.
*
* @param taskInfo to be executed
@@ -62,6 +69,5 @@ public interface PropagationTaskExecutor {
* @param executor the executor of this task
* @return reporter to report propagation execution status
*/
- PropagationReporter execute(
- Collection<PropagationTaskInfo> taskInfos, boolean nullPriorityAsync, String executor);
+ PropagationReporter execute(Collection<PropagationTaskInfo> taskInfos, boolean nullPriorityAsync, String executor);
}
diff --git a/core/provisioning-java/pom.xml b/core/provisioning-java/pom.xml
index d850ded..aaff252 100644
--- a/core/provisioning-java/pom.xml
+++ b/core/provisioning-java/pom.xml
@@ -49,6 +49,10 @@ under the License.
<artifactId>spring-context-support</artifactId>
</dependency>
<dependency>
+ <groupId>org.springframework.retry</groupId>
+ <artifactId>spring-retry</artifactId>
+ </dependency>
+ <dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
</dependency>
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/ProvisioningContext.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/ProvisioningContext.java
index 7581dbb..03613fb 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/ProvisioningContext.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/ProvisioningContext.java
@@ -219,8 +219,9 @@ public class ProvisioningContext {
/**
* Annotated as {@code @Primary} because it will be used by {@code @Async} in {@link AsyncConnectorFacade}.
+ *
* @param provisioningProperties configuration properties
- *
+ *
* @return executor
*/
@Bean
@@ -238,8 +239,10 @@ public class ProvisioningContext {
@Bean
public AsyncConfigurer asyncConfigurer(@Qualifier("asyncConnectorFacadeExecutor")
- final ThreadPoolTaskExecutor asyncConnectorFacadeExecutor) {
+ final ThreadPoolTaskExecutor asyncConnectorFacadeExecutor) {
+
return new AsyncConfigurer() {
+
@Override
public Executor getAsyncExecutor() {
return asyncConnectorFacadeExecutor;
@@ -255,7 +258,7 @@ public class ProvisioningContext {
*/
@Bean
public ThreadPoolTaskExecutor propagationTaskExecutorAsyncExecutor(
- final ProvisioningProperties provisioningProperties) {
+ final ProvisioningProperties provisioningProperties) {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(provisioningProperties.getPropagationTaskExecutorAsyncExecutor().getCorePoolSize());
executor.setMaxPoolSize(provisioningProperties.getPropagationTaskExecutorAsyncExecutor().getMaxPoolSize());
@@ -285,7 +288,7 @@ public class ProvisioningContext {
@Lazy(false)
@Bean
public SchedulerFactoryBean scheduler(final ApplicationContext ctx,
- final ProvisioningProperties provisioningProperties) {
+ final ProvisioningProperties provisioningProperties) {
SchedulerFactoryBean scheduler = new SchedulerFactoryBean();
scheduler.setAutoStartup(true);
scheduler.setApplicationContext(ctx);
@@ -347,7 +350,7 @@ public class ProvisioningContext {
@ConditionalOnMissingBean
@Bean
public JavaMailSender mailSender(final ProvisioningProperties provisioningProperties)
- throws IllegalArgumentException, IOException {
+ throws IllegalArgumentException, IOException {
JavaMailSenderImpl mailSender = new JavaMailSenderImpl() {
@Override
@@ -582,7 +585,7 @@ public class ProvisioningContext {
@ConditionalOnMissingBean
@Bean
- public IntAttrNameParser intAttrNameParser(
+ public IntAttrNameParser intAttrNameParser(
final AnyUtilsFactory anyUtilsFactory,
final PlainSchemaDAO plainSchemaDAO,
final DerSchemaDAO derSchemaDAO,
@@ -764,16 +767,21 @@ public class ProvisioningContext {
@ConditionalOnMissingBean
@Bean
- public NotificationJob notificationJob(final NotificationJobDelegate delegate,
- final DomainHolder domainHolder,
- final SecurityProperties securityProperties) {
+ public NotificationJob notificationJob(
+ final NotificationJobDelegate delegate,
+ final DomainHolder domainHolder,
+ final SecurityProperties securityProperties) {
+
return new NotificationJob(securityProperties, domainHolder, delegate);
}
@ConditionalOnMissingBean
@Bean
- public ReportJobDelegate reportJobDelegate(final ReportDAO reportDAO, final ReportExecDAO reportExecDAO,
- final EntityFactory entityFactory) {
+ public ReportJobDelegate reportJobDelegate(
+ final ReportDAO reportDAO,
+ final ReportExecDAO reportExecDAO,
+ final EntityFactory entityFactory) {
+
return new DefaultReportJobDelegate(reportDAO, reportExecDAO, entityFactory);
}
@@ -870,8 +878,10 @@ public class ProvisioningContext {
@ConditionalOnMissingBean
@Bean
- public ApplicationDataBinder applicationDataBinder(final ApplicationDAO applicationDAO,
- final EntityFactory entityFactory) {
+ public ApplicationDataBinder applicationDataBinder(
+ final ApplicationDAO applicationDAO,
+ final EntityFactory entityFactory) {
+
return new ApplicationDataBinderImpl(applicationDAO, entityFactory);
}
@@ -895,8 +905,10 @@ public class ProvisioningContext {
@ConditionalOnMissingBean
@Bean
- public ClientAppDataBinder clientAppDataBinder(final PolicyDAO policyDAO,
- final EntityFactory entityFactory) {
+ public ClientAppDataBinder clientAppDataBinder(
+ final PolicyDAO policyDAO,
+ final EntityFactory entityFactory) {
+
return new ClientAppDataBinderImpl(policyDAO, entityFactory);
}
@@ -913,16 +925,22 @@ public class ProvisioningContext {
@ConditionalOnMissingBean
@Bean
- public DelegationDataBinder delegationDataBinder(final UserDAO userDAO, final RoleDAO roleDAO,
- final EntityFactory entityFactory) {
+ public DelegationDataBinder delegationDataBinder(
+ final UserDAO userDAO,
+ final RoleDAO roleDAO,
+ final EntityFactory entityFactory) {
+
return new DelegationDataBinderImpl(userDAO, roleDAO, entityFactory);
}
@ConditionalOnMissingBean
@Bean
- public DynRealmDataBinder dynRealmDataBinder(final AnyTypeDAO anyTypeDAO, final DynRealmDAO dynRealmDAO,
- final SearchCondVisitor searchCondVisitor,
- final EntityFactory entityFactory) {
+ public DynRealmDataBinder dynRealmDataBinder(
+ final AnyTypeDAO anyTypeDAO,
+ final DynRealmDAO dynRealmDAO,
+ final SearchCondVisitor searchCondVisitor,
+ final EntityFactory entityFactory) {
+
return new DynRealmDataBinderImpl(anyTypeDAO, dynRealmDAO, entityFactory, searchCondVisitor);
}
@@ -1066,7 +1084,8 @@ public class ProvisioningContext {
final AnyTypeClassDAO anyTypeClassDAO,
final ImplementationDAO implementationDAO,
final PlainSchemaDAO plainSchemaDAO,
- final IntAttrNameParser intAttrNameParser) {
+ final IntAttrNameParser intAttrNameParser,
+ final PropagationTaskExecutor propagationTaskExecutor) {
return new ResourceDataBinderImpl(
anyTypeDAO,
@@ -1077,7 +1096,8 @@ public class ProvisioningContext {
implementationDAO,
plainSchemaDAO,
entityFactory,
- intAttrNameParser);
+ intAttrNameParser,
+ propagationTaskExecutor);
}
@ConditionalOnMissingBean
@@ -1224,8 +1244,10 @@ public class ProvisioningContext {
@ConditionalOnMissingBean
@Bean
- public WAConfigDataBinder waConfigDataBinder(final WAConfigDAO waConfigDAO,
- final EntityFactory entityFactory) {
+ public WAConfigDataBinder waConfigDataBinder(
+ final WAConfigDAO waConfigDAO,
+ final EntityFactory entityFactory) {
+
return new WAConfigDataBinderImpl(waConfigDAO, entityFactory);
}
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/PolicyDataBinderImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/PolicyDataBinderImpl.java
index b42ac2d..afbe9ca 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/PolicyDataBinderImpl.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/PolicyDataBinderImpl.java
@@ -27,6 +27,7 @@ import org.apache.syncope.common.lib.policy.PushPolicyTO;
import org.apache.syncope.common.lib.policy.AccessPolicyTO;
import org.apache.syncope.common.lib.policy.AttrReleasePolicyTO;
import org.apache.syncope.common.lib.policy.AuthPolicyTO;
+import org.apache.syncope.common.lib.policy.PropagationPolicyTO;
import org.apache.syncope.core.persistence.api.dao.AnyTypeDAO;
import org.apache.syncope.core.persistence.api.dao.ExternalResourceDAO;
import org.apache.syncope.core.persistence.api.dao.ImplementationDAO;
@@ -43,6 +44,7 @@ import org.apache.syncope.core.persistence.api.entity.policy.AttrReleasePolicy;
import org.apache.syncope.core.persistence.api.entity.policy.AuthPolicy;
import org.apache.syncope.core.persistence.api.entity.policy.PasswordPolicy;
import org.apache.syncope.core.persistence.api.entity.policy.Policy;
+import org.apache.syncope.core.persistence.api.entity.policy.PropagationPolicy;
import org.apache.syncope.core.persistence.api.entity.policy.PullCorrelationRuleEntity;
import org.apache.syncope.core.persistence.api.entity.policy.PullPolicy;
import org.apache.syncope.core.persistence.api.entity.policy.PushCorrelationRuleEntity;
@@ -138,6 +140,17 @@ public class PolicyDataBinderImpl implements PolicyDataBinder {
accountPolicy.add(resource);
}
});
+ } else if (policyTO instanceof PropagationPolicyTO) {
+ if (result == null) {
+ result = (T) entityFactory.newEntity(PropagationPolicy.class);
+ }
+
+ PropagationPolicy propagationPolicy = PropagationPolicy.class.cast(result);
+ PropagationPolicyTO propagationPolicyTO = PropagationPolicyTO.class.cast(policyTO);
+
+ propagationPolicy.setBackOffStrategy(propagationPolicyTO.getBackOffStrategy());
+ propagationPolicy.setBackOffParams(propagationPolicyTO.getBackOffParams());
+ propagationPolicy.setMaxAttempts(propagationPolicyTO.getMaxAttempts());
} else if (policyTO instanceof PullPolicyTO) {
if (result == null) {
result = (T) entityFactory.newEntity(PullPolicy.class);
@@ -169,8 +182,8 @@ public class PolicyDataBinderImpl implements PolicyDataBinder {
}
});
// remove all rules not contained in the TO
- pullPolicy.getCorrelationRules().removeIf(anyFilter
- -> !pullPolicyTO.getCorrelationRules().containsKey(anyFilter.getAnyType().getKey()));
+ pullPolicy.getCorrelationRules().removeIf(anyFilter -> !pullPolicyTO.getCorrelationRules().
+ containsKey(anyFilter.getAnyType().getKey()));
} else if (policyTO instanceof PushPolicyTO) {
if (result == null) {
result = (T) entityFactory.newEntity(PushPolicy.class);
@@ -289,6 +302,14 @@ public class PolicyDataBinderImpl implements PolicyDataBinder {
accountPolicyTO.getPassthroughResources().addAll(
accountPolicy.getResources().stream().map(Entity::getKey).collect(Collectors.toList()));
+ } else if (policy instanceof PropagationPolicy) {
+ PropagationPolicy propagationPolicy = PropagationPolicy.class.cast(policy);
+ PropagationPolicyTO propagationPolicyTO = new PropagationPolicyTO();
+ policyTO = (T) propagationPolicyTO;
+
+ propagationPolicyTO.setBackOffStrategy(propagationPolicy.getBackOffStrategy());
+ propagationPolicyTO.setBackOffParams(propagationPolicy.getBackOffParams());
+ propagationPolicyTO.setMaxAttempts(propagationPolicy.getMaxAttempts());
} else if (policy instanceof PullPolicy) {
PullPolicy pullPolicy = PullPolicy.class.cast(policy);
PullPolicyTO pullPolicyTO = new PullPolicyTO();
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ResourceDataBinderImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ResourceDataBinderImpl.java
index 5cf60a9..6cbf8cd 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ResourceDataBinderImpl.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ResourceDataBinderImpl.java
@@ -60,6 +60,7 @@ import org.apache.syncope.core.persistence.api.entity.Entity;
import org.apache.syncope.core.persistence.api.entity.Implementation;
import org.apache.syncope.core.persistence.api.entity.PlainSchema;
import org.apache.syncope.core.persistence.api.entity.VirSchema;
+import org.apache.syncope.core.persistence.api.entity.policy.PropagationPolicy;
import org.apache.syncope.core.persistence.api.entity.policy.PullPolicy;
import org.apache.syncope.core.persistence.api.entity.policy.PushPolicy;
import org.apache.syncope.core.persistence.api.entity.resource.Item;
@@ -69,6 +70,7 @@ import org.apache.syncope.core.persistence.api.entity.resource.Provision;
import org.apache.syncope.core.provisioning.api.IntAttrNameParser;
import org.apache.syncope.core.provisioning.api.IntAttrName;
import org.apache.syncope.core.provisioning.api.data.ResourceDataBinder;
+import org.apache.syncope.core.provisioning.api.propagation.PropagationTaskExecutor;
import org.identityconnectors.framework.common.objects.ObjectClass;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -95,6 +97,8 @@ public class ResourceDataBinderImpl implements ResourceDataBinder {
protected final IntAttrNameParser intAttrNameParser;
+ protected final PropagationTaskExecutor propagationTaskExecutor;
+
public ResourceDataBinderImpl(
final AnyTypeDAO anyTypeDAO,
final ConnInstanceDAO connInstanceDAO,
@@ -104,7 +108,8 @@ public class ResourceDataBinderImpl implements ResourceDataBinder {
final ImplementationDAO implementationDAO,
final PlainSchemaDAO plainSchemaDAO,
final EntityFactory entityFactory,
- final IntAttrNameParser intAttrNameParser) {
+ final IntAttrNameParser intAttrNameParser,
+ final PropagationTaskExecutor propagationTaskExecutor) {
this.anyTypeDAO = anyTypeDAO;
this.connInstanceDAO = connInstanceDAO;
@@ -115,6 +120,7 @@ public class ResourceDataBinderImpl implements ResourceDataBinder {
this.plainSchemaDAO = plainSchemaDAO;
this.entityFactory = entityFactory;
this.intAttrNameParser = intAttrNameParser;
+ this.propagationTaskExecutor = propagationTaskExecutor;
}
@Override
@@ -358,6 +364,14 @@ public class ResourceDataBinderImpl implements ResourceDataBinder {
resource.setAccountPolicy(resourceTO.getAccountPolicy() == null
? null : (AccountPolicy) policyDAO.find(resourceTO.getAccountPolicy()));
+ if (resource.getPropagationPolicy() != null
+ && !resource.getPropagationPolicy().getKey().equals(resourceTO.getPropagationPolicy())) {
+
+ propagationTaskExecutor.expireRetryTemplate(resource.getKey());
+ }
+ resource.setPropagationPolicy(resourceTO.getPropagationPolicy() == null
+ ? null : (PropagationPolicy) policyDAO.find(resourceTO.getPropagationPolicy()));
+
resource.setPullPolicy(resourceTO.getPullPolicy() == null
? null : (PullPolicy) policyDAO.find(resourceTO.getPullPolicy()));
@@ -688,6 +702,9 @@ public class ResourceDataBinderImpl implements ResourceDataBinder {
resourceTO.setAccountPolicy(resource.getAccountPolicy() == null
? null : resource.getAccountPolicy().getKey());
+ resourceTO.setPropagationPolicy(resource.getPropagationPolicy() == null
+ ? null : resource.getPropagationPolicy().getKey());
+
resourceTO.setPullPolicy(resource.getPullPolicy() == null
? null : resource.getPullPolicy().getKey());
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 682070d..e6daff8 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
@@ -20,7 +20,8 @@ package org.apache.syncope.core.provisioning.java.propagation;
import java.time.OffsetDateTime;
import java.util.ArrayList;
-import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@@ -61,7 +62,6 @@ import org.apache.syncope.core.provisioning.api.AuditManager;
import org.apache.syncope.core.provisioning.api.ConnectorManager;
import org.apache.syncope.core.provisioning.api.data.TaskDataBinder;
import org.apache.syncope.core.provisioning.api.notification.NotificationManager;
-import org.apache.syncope.core.provisioning.api.propagation.PropagationException;
import org.apache.syncope.core.provisioning.api.propagation.PropagationTaskInfo;
import org.apache.syncope.core.provisioning.api.serialization.POJOHelper;
import org.apache.syncope.core.provisioning.java.pushpull.OutboundMatcher;
@@ -79,6 +79,12 @@ import org.identityconnectors.framework.common.objects.ObjectClass;
import org.identityconnectors.framework.common.objects.Uid;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.springframework.retry.RetryException;
+import org.springframework.retry.backoff.ExponentialBackOffPolicy;
+import org.springframework.retry.backoff.ExponentialRandomBackOffPolicy;
+import org.springframework.retry.backoff.FixedBackOffPolicy;
+import org.springframework.retry.policy.SimpleRetryPolicy;
+import org.springframework.retry.support.RetryTemplate;
import org.springframework.transaction.annotation.Transactional;
@Transactional(rollbackFor = { Throwable.class })
@@ -86,6 +92,8 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
protected static final Logger LOG = LoggerFactory.getLogger(PropagationTaskExecutor.class);
+ protected final Map<String, RetryTemplate> retryTemplates = Collections.synchronizedMap(new HashMap<>());
+
protected final ConnectorManager connectorManager;
protected final ConnObjectUtils connObjectUtils;
@@ -146,6 +154,11 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
this.outboundMatcher = outboundMatcher;
}
+ @Override
+ public void expireRetryTemplate(final String resource) {
+ retryTemplates.remove(resource);
+ }
+
protected List<PropagationActions> getPropagationActions(final ExternalResource resource) {
List<PropagationActions> result = new ArrayList<>();
@@ -298,6 +311,94 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
return task;
}
+ protected Optional<RetryTemplate> retryTemplate(final ExternalResource resource) {
+ RetryTemplate retryTemplate = null;
+
+ if (resource.getPropagationPolicy() != null) {
+ retryTemplate = retryTemplates.get(resource.getKey());
+ if (retryTemplate == null) {
+ retryTemplate = new RetryTemplate();
+
+ SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy();
+ retryPolicy.setMaxAttempts(resource.getPropagationPolicy().getMaxAttempts());
+ retryTemplate.setRetryPolicy(retryPolicy);
+
+ String[] params = resource.getPropagationPolicy().getBackOffParams().split(";");
+
+ switch (resource.getPropagationPolicy().getBackOffStrategy()) {
+ case EXPONENTIAL:
+ ExponentialBackOffPolicy eBackOffPolicy = new ExponentialBackOffPolicy();
+ if (params.length > 0) {
+ try {
+ eBackOffPolicy.setInitialInterval(Long.valueOf(params[0]));
+ } catch (NumberFormatException e) {
+ LOG.error("Could not convert to long: {}", params[0], e);
+ }
+ }
+ if (params.length > 1) {
+ try {
+ eBackOffPolicy.setMaxInterval(Long.valueOf(params[1]));
+ } catch (NumberFormatException e) {
+ LOG.error("Could not convert to long: {}", params[1], e);
+ }
+ }
+ if (params.length > 2) {
+ try {
+ eBackOffPolicy.setMultiplier(Double.valueOf(params[2]));
+ } catch (NumberFormatException e) {
+ LOG.error("Could not convert to double: {}", params[2], e);
+ }
+ }
+ retryTemplate.setBackOffPolicy(eBackOffPolicy);
+ break;
+
+ case RANDOM:
+ ExponentialRandomBackOffPolicy erBackOffPolicy = new ExponentialRandomBackOffPolicy();
+ if (params.length > 0) {
+ try {
+ erBackOffPolicy.setInitialInterval(Long.valueOf(params[0]));
+ } catch (NumberFormatException e) {
+ LOG.error("Could not convert to long: {}", params[0], e);
+ }
+ }
+ if (params.length > 1) {
+ try {
+ erBackOffPolicy.setMaxInterval(Long.valueOf(params[1]));
+ } catch (NumberFormatException e) {
+ LOG.error("Could not convert to long: {}", params[1], e);
+ }
+ }
+ if (params.length > 2) {
+ try {
+ erBackOffPolicy.setMultiplier(Double.valueOf(params[2]));
+ } catch (NumberFormatException e) {
+ LOG.error("Could not convert to double: {}", params[2], e);
+ }
+ }
+ retryTemplate.setBackOffPolicy(erBackOffPolicy);
+ break;
+
+ case FIXED:
+ default:
+ FixedBackOffPolicy fBackOffPolicy = new FixedBackOffPolicy();
+ if (params.length > 0) {
+ try {
+ fBackOffPolicy.setBackOffPeriod(Long.valueOf(params[0]));
+ } catch (NumberFormatException e) {
+ LOG.error("Could not convert to long: {}", params[0], e);
+ }
+
+ }
+ retryTemplate.setBackOffPolicy(fBackOffPolicy);
+ }
+
+ retryTemplates.put(resource.getKey(), retryTemplate);
+ }
+ }
+
+ return Optional.ofNullable(retryTemplate);
+ }
+
@Override
public TaskExec execute(
final PropagationTaskInfo taskInfo,
@@ -306,6 +407,25 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
PropagationTask task = buildTask(taskInfo);
+ return retryTemplate(task.getResource()).map(rt -> rt.execute(context -> {
+ LOG.debug("#{} Propagation attempt", context.getRetryCount());
+
+ TaskExec exec = doExecute(taskInfo, task, reporter, executor);
+ if (context.getRetryCount() < task.getResource().getPropagationPolicy().getMaxAttempts() - 1
+ && !ExecStatus.SUCCESS.name().equals(exec.getStatus())) {
+
+ throw new RetryException("Attempt #" + context.getRetryCount() + " failed");
+ }
+ return exec;
+ })).orElseGet(() -> doExecute(taskInfo, task, reporter, executor));
+ }
+
+ protected TaskExec doExecute(
+ final PropagationTaskInfo taskInfo,
+ final PropagationTask task,
+ final PropagationReporter reporter,
+ final String executor) {
+
Connector connector = taskInfo.getConnector() == null
? connectorManager.getConnector(task.getResource())
: taskInfo.getConnector();
@@ -314,9 +434,9 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
OffsetDateTime start = OffsetDateTime.now();
- TaskExec execution = entityFactory.newEntity(TaskExec.class);
- execution.setStatus(ExecStatus.CREATED.name());
- execution.setExecutor(executor);
+ TaskExec exec = entityFactory.newEntity(TaskExec.class);
+ exec.setStatus(ExecStatus.CREATED.name());
+ exec.setExecutor(executor);
String taskExecutionMessage = null;
String failureReason = null;
@@ -363,7 +483,7 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
default:
}
- execution.setStatus(propagationAttempted.get()
+ exec.setStatus(propagationAttempted.get()
? ExecStatus.SUCCESS.name()
: ExecStatus.NOT_ATTEMPTED.name());
@@ -390,14 +510,14 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
}
try {
- execution.setStatus(ExecStatus.FAILURE.name());
+ exec.setStatus(ExecStatus.FAILURE.name());
} catch (Exception wft) {
- LOG.error("While executing KO action on {}", execution, wft);
+ LOG.error("While executing KO action on {}", exec, wft);
}
propagationAttempted.set(true);
- actions.forEach(action -> action.onError(task, execution, e));
+ actions.forEach(action -> action.onError(task, exec, e));
} finally {
// Try to read remote object AFTER any actual operation
if (connector != null) {
@@ -424,17 +544,17 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
build();
}
- execution.setStart(start);
- execution.setMessage(taskExecutionMessage);
- execution.setEnd(OffsetDateTime.now());
+ exec.setStart(start);
+ exec.setMessage(taskExecutionMessage);
+ exec.setEnd(OffsetDateTime.now());
- LOG.debug("Execution finished: {}", execution);
+ LOG.debug("Execution finished: {}", exec);
- if (hasToBeregistered(task, execution)) {
- LOG.debug("Execution to be stored: {}", execution);
+ if (hasToBeregistered(task, exec)) {
+ LOG.debug("Execution to be stored: {}", exec);
- execution.setTask(task);
- task.add(execution);
+ exec.setTask(task);
+ task.add(exec);
taskDAO.save(task);
}
@@ -447,7 +567,7 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
? outboundMatcher.getFIQL(beforeObj, provision)
: null;
reporter.onSuccessOrNonPriorityResourceFailures(taskInfo,
- ExecStatus.valueOf(execution.getStatus()),
+ ExecStatus.valueOf(exec.getStatus()),
failureReason,
fiql,
beforeObj,
@@ -455,7 +575,7 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
}
for (PropagationActions action : actions) {
- action.after(task, execution, afterObj);
+ action.after(task, exec, afterObj);
}
// SYNCOPE-1136
String anyTypeKind = task.getAnyTypeKind() == null ? "realm" : task.getAnyTypeKind().name().toLowerCase();
@@ -470,7 +590,7 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
operation);
if (notificationsAvailable || auditRequested) {
- ExecTO execTO = taskDataBinder.getExecTO(execution);
+ ExecTO execTO = taskDataBinder.getExecTO(exec);
notificationManager.createTasks(
AuthContextUtils.getWho(),
AuditElements.EventCategoryType.PROPAGATION,
@@ -494,13 +614,9 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
taskInfo);
}
- return execution;
+ return exec;
}
- protected abstract void doExecute(
- Collection<PropagationTaskInfo> taskInfos, PropagationReporter reporter, boolean nullPriorityAsync,
- String executor);
-
protected TaskExec rejected(
final PropagationTaskInfo taskInfo,
final String rejectReason,
@@ -536,23 +652,6 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
return execution;
}
- @Override
- public PropagationReporter execute(
- final Collection<PropagationTaskInfo> taskInfos,
- final boolean nullPriorityAsync,
- final String executor) {
-
- PropagationReporter reporter = new DefaultPropagationReporter();
- try {
- doExecute(taskInfos, reporter, nullPriorityAsync, executor);
- } catch (PropagationException e) {
- LOG.error("Error propagation priority resource", e);
- reporter.onPriorityResourceFailure(e.getResourceName(), taskInfos);
- }
-
- return reporter;
- }
-
/**
* Check whether an execution has to be stored, for a given task.
*
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 c5a1454..f1d1218 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
@@ -120,72 +120,79 @@ public class PriorityPropagationTaskExecutor extends AbstractPropagationTaskExec
}
@Override
- protected void doExecute(
+ public PropagationReporter execute(
final Collection<PropagationTaskInfo> taskInfos,
- final PropagationReporter reporter,
final boolean nullPriorityAsync,
final String executor) {
- List<PropagationTaskInfo> prioritizedTasks = taskInfos.stream().
- filter(task -> task.getExternalResource().getPropagationPriority() != null).
- sorted(Comparator.comparing(task -> task.getExternalResource().getPropagationPriority())).
- collect(Collectors.toList());
- LOG.debug("Propagation tasks sorted by priority, for serial execution: {}", prioritizedTasks);
-
- List<PropagationTaskInfo> concurrentTasks = taskInfos.stream().
- filter(task -> !prioritizedTasks.contains(task)).
- collect(Collectors.toList());
- LOG.debug("Propagation tasks for concurrent execution: {}", concurrentTasks);
-
- // first process priority resources sequentially and fail as soon as any propagation failure is reported
- prioritizedTasks.forEach(task -> {
- TaskExec execution = null;
- ExecStatus execStatus;
- String errorMessage = null;
- try {
- execution = newPropagationTaskCallable(task, reporter, executor).call();
- execStatus = ExecStatus.valueOf(execution.getStatus());
- } catch (Exception e) {
- LOG.error("Unexpected exception", e);
- execStatus = ExecStatus.FAILURE;
- errorMessage = e.getMessage();
- }
- if (execStatus != ExecStatus.SUCCESS) {
- throw new PropagationException(
- task.getResource(),
- Optional.ofNullable(execution).map(Exec::getMessage).orElse(errorMessage));
- }
- });
-
- // then process non-priority resources concurrently...
- if (!concurrentTasks.isEmpty()) {
- CompletionService<TaskExec> completionService = new ExecutorCompletionService<>(taskExecutor);
- List<Future<TaskExec>> futures = new ArrayList<>();
-
- concurrentTasks.forEach(taskInfo -> {
+ PropagationReporter reporter = new DefaultPropagationReporter();
+ try {
+ List<PropagationTaskInfo> prioritizedTasks = taskInfos.stream().
+ filter(task -> task.getExternalResource().getPropagationPriority() != null).
+ sorted(Comparator.comparing(task -> task.getExternalResource().getPropagationPriority())).
+ collect(Collectors.toList());
+ LOG.debug("Propagation tasks sorted by priority, for serial execution: {}", prioritizedTasks);
+
+ List<PropagationTaskInfo> concurrentTasks = taskInfos.stream().
+ filter(task -> !prioritizedTasks.contains(task)).
+ collect(Collectors.toList());
+ LOG.debug("Propagation tasks for concurrent execution: {}", concurrentTasks);
+
+ // first process priority resources sequentially and fail as soon as any propagation failure is reported
+ prioritizedTasks.forEach(task -> {
+ TaskExec exec = null;
+ ExecStatus execStatus;
+ String errorMessage = null;
try {
- futures.add(completionService.submit(newPropagationTaskCallable(taskInfo, reporter, executor)));
-
- if (nullPriorityAsync) {
- reporter.onSuccessOrNonPriorityResourceFailures(
- taskInfo, ExecStatus.CREATED, null, null, null, null);
- }
+ exec = newPropagationTaskCallable(task, reporter, executor).call();
+ execStatus = ExecStatus.valueOf(exec.getStatus());
} catch (Exception e) {
- LOG.error("While submitting task for async execution: {}", taskInfo, e);
- rejected(taskInfo, e.getMessage(), reporter, executor);
+ LOG.error("Unexpected exception", e);
+ execStatus = ExecStatus.FAILURE;
+ errorMessage = e.getMessage();
+ }
+ if (execStatus != ExecStatus.SUCCESS) {
+ throw new PropagationException(
+ task.getResource(),
+ Optional.ofNullable(exec).map(Exec::getMessage).orElse(errorMessage));
}
});
- // ...waiting for all callables to complete, if async processing was not required
- if (!nullPriorityAsync) {
- futures.forEach(future -> {
+ // then process non-priority resources concurrently...
+ if (!concurrentTasks.isEmpty()) {
+ CompletionService<TaskExec> completionService = new ExecutorCompletionService<>(taskExecutor);
+ List<Future<TaskExec>> futures = new ArrayList<>();
+
+ concurrentTasks.forEach(taskInfo -> {
try {
- future.get();
+ futures.add(completionService.submit(newPropagationTaskCallable(taskInfo, reporter, executor)));
+
+ if (nullPriorityAsync) {
+ reporter.onSuccessOrNonPriorityResourceFailures(
+ taskInfo, ExecStatus.CREATED, null, null, null, null);
+ }
} catch (Exception e) {
- LOG.error("Unexpected exception", e);
+ LOG.error("While submitting task for async execution: {}", taskInfo, e);
+ rejected(taskInfo, e.getMessage(), reporter, executor);
}
});
+
+ // ...waiting for all callables to complete, if async processing was not required
+ if (!nullPriorityAsync) {
+ futures.forEach(future -> {
+ try {
+ future.get();
+ } catch (Exception e) {
+ LOG.error("Unexpected exception", e);
+ }
+ });
+ }
}
+ } catch (PropagationException e) {
+ LOG.error("Error propagation priority resource", e);
+ reporter.onPriorityResourceFailure(e.getResourceName(), taskInfos);
}
+
+ return reporter;
}
}
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/console/PoliciesITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/console/PoliciesITCase.java
index 7e37ba2..9cbe4bf 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/console/PoliciesITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/console/PoliciesITCase.java
@@ -149,7 +149,7 @@ public class PoliciesITCase extends AbstractConsoleITCase {
}
private static void createPullPolicy(String name) {
- TESTER.clickLink("body:content:tabbedPanel:tabs-container:tabs:5:link");
+ TESTER.clickLink("body:content:tabbedPanel:tabs-container:tabs:6:link");
TESTER.clickLink("body:content:tabbedPanel:panel:container:content:add");
TESTER.assertComponent("body:content:tabbedPanel:panel:outerObjectsRepeater:0:outer", Modal.class);
@@ -244,7 +244,7 @@ public class PoliciesITCase extends AbstractConsoleITCase {
}
private static void deletePullPolicy(String name) {
- TESTER.clickLink("body:content:tabbedPanel:tabs-container:tabs:5:link");
+ TESTER.clickLink("body:content:tabbedPanel:tabs-container:tabs:6:link");
Component component = findComponentByProp("name",
"body:content:tabbedPanel:panel:container:content:"
+ "searchContainer:resultTable:tablePanel:groupForm:checkgroup:dataTable", name);
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PolicyITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PolicyITCase.java
index 8693080..7f250a1 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PolicyITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PolicyITCase.java
@@ -58,6 +58,8 @@ import java.util.Set;
import org.apache.syncope.common.lib.Attr;
import org.apache.syncope.common.lib.policy.AuthPolicyTO;
import org.apache.syncope.common.lib.policy.DefaultAuthPolicyConf;
+import org.apache.syncope.common.lib.policy.PropagationPolicyTO;
+import org.apache.syncope.common.lib.types.BackOffStrategy;
public class PolicyITCase extends AbstractITCase {
@@ -117,10 +119,13 @@ public class PolicyITCase extends AbstractITCase {
@Test
public void listByType() {
- List<PullPolicyTO> policyTOs = policyService.list(PolicyType.PULL);
+ List<PropagationPolicyTO> propagationPolicies = policyService.list(PolicyType.PROPAGATION);
+ assertNotNull(propagationPolicies);
+ assertFalse(propagationPolicies.isEmpty());
- assertNotNull(policyTOs);
- assertFalse(policyTOs.isEmpty());
+ List<PullPolicyTO> pullPolicies = policyService.list(PolicyType.PULL);
+ assertNotNull(pullPolicies);
+ assertFalse(pullPolicies.isEmpty());
}
@Test
@@ -142,6 +147,16 @@ public class PolicyITCase extends AbstractITCase {
}
@Test
+ public void getPropagationPolicy() {
+ PropagationPolicyTO policyTO =
+ policyService.read(PolicyType.PROPAGATION, "89d322db-9878-420c-b49c-67be13df9a12");
+
+ assertNotNull(policyTO);
+ assertTrue(policyTO.getUsedByResources().contains(RESOURCE_NAME_DBSCRIPTED));
+ assertTrue(policyTO.getUsedByRealms().isEmpty());
+ }
+
+ @Test
public void getPullPolicy() {
PullPolicyTO policyTO = policyService.read(PolicyType.PULL, "66691e96-285f-4464-bc19-e68384ea4c85");
@@ -151,8 +166,7 @@ public class PolicyITCase extends AbstractITCase {
@Test
public void getAuthPolicy() {
- AuthPolicyTO policyTO =
- policyService.read(PolicyType.AUTH, "659b9906-4b6e-4bc0-aca0-6809dff346d4");
+ AuthPolicyTO policyTO = policyService.read(PolicyType.AUTH, "659b9906-4b6e-4bc0-aca0-6809dff346d4");
assertNotNull(policyTO);
assertTrue(policyTO.getUsedByRealms().isEmpty());
@@ -160,8 +174,7 @@ public class PolicyITCase extends AbstractITCase {
@Test
public void getAccessPolicy() {
- AccessPolicyTO policyTO =
- policyService.read(PolicyType.ACCESS, "419935c7-deb3-40b3-8a9a-683037e523a2");
+ AccessPolicyTO policyTO = policyService.read(PolicyType.ACCESS, "419935c7-deb3-40b3-8a9a-683037e523a2");
assertNotNull(policyTO);
assertTrue(policyTO.getUsedByRealms().isEmpty());
@@ -178,6 +191,14 @@ public class PolicyITCase extends AbstractITCase {
@Test
public void create() throws IOException {
+ PropagationPolicyTO propagationPolicyTO = new PropagationPolicyTO();
+ propagationPolicyTO.setName("propagation policy name");
+ propagationPolicyTO.setBackOffStrategy(BackOffStrategy.EXPONENTIAL);
+ propagationPolicyTO = createPolicy(PolicyType.PROPAGATION, propagationPolicyTO);
+ assertNotNull(propagationPolicyTO);
+ assertEquals(3, propagationPolicyTO.getMaxAttempts());
+ assertEquals(BackOffStrategy.EXPONENTIAL.getDefaultBackOffParams(), propagationPolicyTO.getBackOffParams());
+
PullPolicyTO pullPolicyTO = createPolicy(PolicyType.PULL, buildPullPolicyTO());
assertNotNull(pullPolicyTO);
assertEquals("TestPullRule", pullPolicyTO.getCorrelationRules().get(AnyTypeKind.USER.name()));
@@ -186,8 +207,7 @@ public class PolicyITCase extends AbstractITCase {
assertNotNull(pushPolicyTO);
assertEquals("TestPushRule", pushPolicyTO.getCorrelationRules().get(AnyTypeKind.USER.name()));
- AuthPolicyTO authPolicyTO = createPolicy(PolicyType.AUTH,
- buildAuthPolicyTO("LdapAuthentication1"));
+ AuthPolicyTO authPolicyTO = createPolicy(PolicyType.AUTH, buildAuthPolicyTO("LdapAuthentication1"));
assertNotNull(authPolicyTO);
assertEquals("Test Authentication policy", authPolicyTO.getName());
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 3b36ed7..6c46c8d 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
@@ -18,6 +18,7 @@
*/
package org.apache.syncope.fit.core;
+import static org.awaitility.Awaitility.await;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
@@ -42,12 +43,14 @@ import org.apache.syncope.common.lib.request.AttrPatch;
import org.apache.syncope.common.lib.request.UserCR;
import org.apache.syncope.common.lib.request.UserUR;
import java.util.Set;
+import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.ws.rs.core.GenericType;
import javax.xml.ws.WebServiceException;
import org.apache.commons.lang3.SerializationUtils;
import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.client.lib.SyncopeClient;
import org.apache.syncope.common.lib.SyncopeClientException;
import org.apache.syncope.common.lib.SyncopeConstants;
import org.apache.syncope.common.lib.to.TaskTO;
@@ -96,6 +99,8 @@ import org.identityconnectors.framework.common.objects.AttributeUtil;
import org.identityconnectors.framework.common.objects.Name;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.BeforeAll;
+import org.springframework.dao.DataAccessException;
+import org.springframework.jdbc.core.JdbcTemplate;
public class PropagationTaskITCase extends AbstractTaskITCase {
@@ -311,6 +316,41 @@ public class PropagationTaskITCase extends AbstractTaskITCase {
}
@Test
+ public void propagationPolicy() throws InterruptedException {
+ SyncopeClient.nullPriorityAsync(anyObjectService, true);
+
+ JdbcTemplate jdbcTemplate = new JdbcTemplate(testDataSource);
+ jdbcTemplate.execute("ALTER TABLE TESTPRINTER ADD COLUMN MAND_VALUE VARCHAR(1)");
+ jdbcTemplate.execute("UPDATE TESTPRINTER SET MAND_VALUE='C'");
+ jdbcTemplate.execute("ALTER TABLE TESTPRINTER ALTER COLUMN MAND_VALUE VARCHAR(1) NOT NULL");
+ try {
+ String entityKey = createAnyObject(AnyObjectITCase.getSample("propagationPolicy")).getEntity().getKey();
+
+ Thread.sleep(1000);
+ jdbcTemplate.execute("ALTER TABLE TESTPRINTER DROP COLUMN MAND_VALUE");
+
+ PagedResult<PropagationTaskTO> propagations = await().atMost(MAX_WAIT_SECONDS, TimeUnit.SECONDS).until(
+ () -> taskService.search(
+ new TaskQuery.Builder(TaskType.PROPAGATION).resource(RESOURCE_NAME_DBSCRIPTED).
+ anyTypeKind(AnyTypeKind.ANY_OBJECT).entityKey(entityKey).build()),
+ p -> p.getTotalCount() > 0);
+
+ propagations.getResult().get(0).getExecutions().stream().
+ anyMatch(e -> ExecStatus.FAILURE.name().equals(e.getStatus()));
+ propagations.getResult().get(0).getExecutions().stream().
+ anyMatch(e -> ExecStatus.SUCCESS.name().equals(e.getStatus()));
+ } finally {
+ SyncopeClient.nullPriorityAsync(anyObjectService, false);
+
+ try {
+ jdbcTemplate.execute("ALTER TABLE TESTPRINTER DROP COLUMN MAND_VALUE");
+ } catch (DataAccessException e) {
+ // ignore
+ }
+ }
+ }
+
+ @Test
public void issueSYNCOPE741() {
for (int i = 0; i < 3; i++) {
taskService.execute(new ExecSpecs.Builder().
diff --git a/src/main/asciidoc/reference-guide/concepts/authenticationmodules.adoc b/src/main/asciidoc/reference-guide/concepts/authenticationmodules.adoc
index cda13af..951dcc9 100644
--- a/src/main/asciidoc/reference-guide/concepts/authenticationmodules.adoc
+++ b/src/main/asciidoc/reference-guide/concepts/authenticationmodules.adoc
@@ -24,18 +24,18 @@ technology or repository, in the context of a certain <<policies-authentication,
Several authentication modules are provided:
* Principal Authentication:
- ** https://apereo.github.io/cas/6.4.x/authentication/Database-Authentication.html[Database^]
- ** https://apereo.github.io/cas/6.4.x/authentication/JAAS-Authentication.html[JAAS^]
- ** https://apereo.github.io/cas/6.4.x/authentication/LDAP-Authentication.html[LDAP^]
- ** https://apereo.github.io/cas/6.4.x/integration/Delegate-Authentication.html[OpenID Connect^]
- ** https://apereo.github.io/cas/6.4.x/mfa/RADIUS-Authentication.html[Radius^]
- ** https://apereo.github.io/cas/6.4.x/authentication/Syncope-Authentication.html[Static^]
- ** https://apereo.github.io/cas/6.4.x/authentication/Syncope-Authentication.html[Syncope^]
- ** https://apereo.github.io/cas/6.4.x/integration/Delegate-Authentication.html[SAML^]
+ ** https://apereo.github.io/cas/6.5.x/authentication/Database-Authentication.html[Database^]
+ ** https://apereo.github.io/cas/6.5.x/authentication/JAAS-Authentication.html[JAAS^]
+ ** https://apereo.github.io/cas/6.5.x/authentication/LDAP-Authentication.html[LDAP^]
+ ** https://apereo.github.io/cas/6.5.x/integration/Delegate-Authentication.html[OpenID Connect^]
+ ** https://apereo.github.io/cas/6.5.x/mfa/RADIUS-Authentication.html[Radius^]
+ ** https://apereo.github.io/cas/6.5.x/authentication/Syncope-Authentication.html[Static^]
+ ** https://apereo.github.io/cas/6.5.x/authentication/Syncope-Authentication.html[Syncope^]
+ ** https://apereo.github.io/cas/6.5.x/integration/Delegate-Authentication.html[SAML^]
* MFA:
- ** https://apereo.github.io/cas/6.4.x/mfa/DuoSecurity-Authentication.html[Duo Security^]
- ** https://apereo.github.io/cas/6.4.x/mfa/FIDO-U2F-Authentication.html[Fido U2F^]
- ** https://apereo.github.io/cas/6.4.x/mfa/GoogleAuthenticator-Authentication.html[Google Authenticator^]
+ ** https://apereo.github.io/cas/6.5.x/mfa/DuoSecurity-Authentication.html[Duo Security^]
+ ** https://apereo.github.io/cas/6.5.x/mfa/FIDO-U2F-Authentication.html[Fido U2F^]
+ ** https://apereo.github.io/cas/6.5.x/mfa/GoogleAuthenticator-Authentication.html[Google Authenticator^]
[TIP]
====
@@ -58,4 +58,4 @@ class.
[NOTE]
Authentication Modules are dynamically translated into
-https://apereo.github.io/cas/6.4.x/authentication/Configuring-Authentication-Components.html#authentication-handlers[CAS Authentication Handlers^].
+https://apereo.github.io/cas/6.5.x/authentication/Configuring-Authentication-Components.html#authentication-handlers[CAS Authentication Handlers^].
diff --git a/src/main/asciidoc/reference-guide/concepts/clientapplications.adoc b/src/main/asciidoc/reference-guide/concepts/clientapplications.adoc
index 4148456..97d8437 100644
--- a/src/main/asciidoc/reference-guide/concepts/clientapplications.adoc
+++ b/src/main/asciidoc/reference-guide/concepts/clientapplications.adoc
@@ -41,4 +41,4 @@ More parameters are required to be specified depending on the actual client appl
[NOTE]
Client Applications are dynamically translated into
-https://apereo.github.io/cas/6.4.x/services/Service-Management.html[CAS Services^].
+https://apereo.github.io/cas/6.5.x/services/Service-Management.html[CAS Services^].
diff --git a/src/main/asciidoc/reference-guide/concepts/policies.adoc b/src/main/asciidoc/reference-guide/concepts/policies.adoc
index 5b4dd19..576d838 100644
--- a/src/main/asciidoc/reference-guide/concepts/policies.adoc
+++ b/src/main/asciidoc/reference-guide/concepts/policies.adoc
@@ -286,7 +286,7 @@ authentication request from the application arrives.
[NOTE]
Access Policy instances are dynamically translated into
-https://apereo.github.io/cas/6.4.x/services/Configuring-Service-Access-Strategy.html#configure-service-access-strategy[CAS Service Access Strategy^].
+https://apereo.github.io/cas/6.5.x/services/Configuring-Service-Access-Strategy.html#configure-service-access-strategy[CAS Service Access Strategy^].
[[policies-attribute-release]]
==== Attribute Release
@@ -298,7 +298,7 @@ values.
[NOTE]
Attribute Release Policy instances are dynamically translated into
-https://apereo.github.io/cas/6.4.x/integration/Attribute-Release-Policies.html#attribute-release-policies[CAS Attribute Release Policy^].
+https://apereo.github.io/cas/6.5.x/integration/Attribute-Release-Policies.html#attribute-release-policies[CAS Attribute Release Policy^].
[[policies-authentication]]
==== Authentication
@@ -325,7 +325,21 @@ be considered successful.
[NOTE]
Authentication Policy instances are dynamically translated into
-https://apereo.github.io/cas/6.4.x/authentication/Configuring-Authentication-Policy.html#authentication-policy[CAS Authentication Policy^].
+https://apereo.github.io/cas/6.5.x/authentication/Configuring-Authentication-Policy.html#authentication-policy[CAS Authentication Policy^].
+
+[[policies-propagation]]
+==== Propagation
+
+Propagation policies are evaluated during the execution of <<tasks-propagation,propagation tasks>> and are meant to
+retry the configured operations in case of propagation failures.
+
+When defining a propagation policy, the following information must be provided:
+
+* max number of attempts
+* back-off strategy
+** `FIXED` - pauses for a fixed period of time before continuing
+** `EXPONENTIAL` - increases the back off period for each retry attempt in a given set up to a limit
+** `RANDOM` - chooses a random multiple of the interval that would come from a simple deterministic exponential
[[policies-pull]]
==== Pull
diff --git a/src/main/asciidoc/reference-guide/concepts/tasks.adoc b/src/main/asciidoc/reference-guide/concepts/tasks.adoc
index 21e01ac..26c29cf 100644
--- a/src/main/asciidoc/reference-guide/concepts/tasks.adoc
+++ b/src/main/asciidoc/reference-guide/concepts/tasks.adoc
@@ -62,6 +62,9 @@ endif::[]
during the <<propagation,propagation>> process, and are permanently saved - for later re-execution or for examining
the execution details - depending on the trace levels set on the related
<<external-resource-details,external resource>>.
+
+Automatic retry in case of failure can be configured by mean of a <<policies-propagation,propagation policy>>, for the
+related external resource.
====
[[tasks-pull]]