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 12:43:00 UTC
[syncope] branch 2_1_X updated: [SYNCOPE-1667] Adding PropagationPolicy (#326)
This is an automated email from the ASF dual-hosted git repository.
ilgrosso pushed a commit to branch 2_1_X
in repository https://gitbox.apache.org/repos/asf/syncope.git
The following commit(s) were added to refs/heads/2_1_X by this push:
new e570484 [SYNCOPE-1667] Adding PropagationPolicy (#326)
e570484 is described below
commit e570484ae601d1a1a65ebbe1c9f8ec555309d8f7
Author: Francesco Chicchiriccò <il...@users.noreply.github.com>
AuthorDate: Tue Mar 15 13:40:41 2022 +0100
[SYNCOPE-1667] Adding PropagationPolicy (#326)
---
.github/workflows/jpajson.yml | 2 +-
.../syncope/client/console/pages/Policies.java | 11 ++
.../console/policies/PolicyModalPanelBuilder.java | 124 ++++++++++++++
.../policies/PropagationPolicyDirectoryPanel.java | 51 ++++++
.../wizards/resources/ResourceSecurityPanel.java | 25 +++
.../client/console/pages/Policies.properties | 1 +
.../client/console/pages/Policies_fr_CA.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 | 12 +-
.../policies/PolicyDirectoryPanel_it.properties | 2 +
.../policies/PolicyDirectoryPanel_ja.properties | 2 +
.../policies/PolicyDirectoryPanel_pt_BR.properties | 2 +
.../policies/PolicyDirectoryPanel_ru.properties | 2 +
.../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 +
.../common/lib/policy/PropagationPolicyTO.java | 69 ++++++++
.../apache/syncope/common/lib/to/ResourceTO.java | 12 ++
.../{PolicyType.java => BackOffStrategy.java} | 29 ++--
.../syncope/common/lib/types/PolicyType.java | 4 +
.../api/entity/policy/PropagationPolicy.java | 39 ++---
.../api/entity/resource/ExternalResource.java | 7 +-
.../src/test/resources/domains/MasterContent.xml | 5 +-
.../jpa/dao/JPAExternalResourceDAO.java | 3 +
.../core/persistence/jpa/dao/JPAPolicyDAO.java | 6 +
.../core/persistence/jpa/dao/JPARealmDAO.java | 11 +-
.../jpa/dao/JPARelationshipTypeDAO.java | 2 +-
.../core/persistence/jpa/dao/JPAUserDAO.java | 7 +-
.../persistence/jpa/entity/JPAEntityFactory.java | 4 +
.../jpa/entity/policy/JPAPolicyUtils.java | 4 +
.../jpa/entity/policy/JPAPolicyUtilsFactory.java | 6 +
.../jpa/entity/policy/JPAPropagationPolicy.java | 76 +++++++++
.../jpa/entity/resource/JPAExternalResource.java | 16 ++
.../core/persistence/jpa/inner/PolicyTest.java | 75 ++++++++-
.../core/persistence/jpa/outer/GroupTest.java | 4 +-
.../core/persistence/jpa/outer/ResourceTest.java | 17 +-
.../src/test/resources/domains/MasterContent.xml | 5 +-
.../api/propagation/PropagationTaskExecutor.java | 7 +
core/provisioning-java/pom.xml | 5 +
.../java/data/PolicyDataBinderImpl.java | 25 ++-
.../java/data/ResourceDataBinderImpl.java | 16 ++
.../AbstractPropagationTaskExecutor.java | 178 ++++++++++++++++-----
.../PriorityPropagationTaskExecutor.java | 107 +++++++------
.../apache/syncope/fit/console/PoliciesITCase.java | 4 +-
.../org/apache/syncope/fit/core/PolicyITCase.java | 29 +++-
.../syncope/fit/core/PropagationTaskITCase.java | 46 ++++++
pom.xml | 8 +-
.../reference-guide/concepts/policies.adoc | 14 ++
.../asciidoc/reference-guide/concepts/tasks.adoc | 6 +-
57 files changed, 925 insertions(+), 171 deletions(-)
diff --git a/.github/workflows/jpajson.yml b/.github/workflows/jpajson.yml
index 80a2b87..48da9c4 100644
--- a/.github/workflows/jpajson.yml
+++ b/.github/workflows/jpajson.yml
@@ -26,7 +26,7 @@ on:
- cron: '0 13 * * 4'
jobs:
- PostgreSQL:
+ JPAJSON:
runs-on: ubuntu-latest
steps:
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/pages/Policies.java b/client/console/src/main/java/org/apache/syncope/client/console/pages/Policies.java
index 294b238..5688f31 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/pages/Policies.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/pages/Policies.java
@@ -24,6 +24,7 @@ import java.util.List;
import org.apache.syncope.client.console.BookmarkablePageLinkBuilder;
import org.apache.syncope.client.console.policies.AccountPolicyDirectoryPanel;
import org.apache.syncope.client.console.policies.PasswordPolicyDirectoryPanel;
+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.extensions.markup.html.tabs.AbstractTab;
@@ -72,6 +73,16 @@ public class Policies extends BasePage {
}
});
+ 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, getPageReference());
+ }
+ });
+
tabs.add(new AbstractTab(new ResourceModel("policy.pull")) {
private static final long serialVersionUID = -6815067322125799251L;
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/policies/PolicyModalPanelBuilder.java b/client/console/src/main/java/org/apache/syncope/client/console/policies/PolicyModalPanelBuilder.java
index f4c4b43..e4497e4 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/policies/PolicyModalPanelBuilder.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/policies/PolicyModalPanelBuilder.java
@@ -30,6 +30,7 @@ import org.apache.syncope.client.console.panels.AbstractModalPanel;
import org.apache.syncope.client.console.panels.WizardModalPanel;
import org.apache.syncope.client.console.rest.PolicyRestClient;
import org.apache.syncope.client.console.rest.ResourceRestClient;
+import org.apache.syncope.client.console.wicket.ajax.form.IndicatorAjaxFormComponentUpdatingBehavior;
import org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal;
import org.apache.syncope.client.console.wicket.markup.html.form.AjaxCheckBoxPanel;
import org.apache.syncope.client.console.wicket.markup.html.form.AjaxDropDownChoicePanel;
@@ -42,8 +43,10 @@ import org.apache.syncope.client.console.wizards.AjaxWizard;
import org.apache.syncope.common.lib.policy.PolicyTO;
import org.apache.syncope.common.lib.policy.AccountPolicyTO;
import org.apache.syncope.common.lib.policy.PasswordPolicyTO;
+import org.apache.syncope.common.lib.policy.PropagationPolicyTO;
import org.apache.syncope.common.lib.policy.ProvisioningPolicyTO;
import org.apache.syncope.common.lib.to.EntityTO;
+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.Component;
@@ -51,6 +54,7 @@ import org.apache.wicket.PageReference;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.markup.html.list.ListItem;
import org.apache.wicket.markup.html.list.ListView;
+import org.apache.wicket.model.IModel;
import org.apache.wicket.model.LoadableDetachableModel;
import org.apache.wicket.model.PropertyModel;
import org.apache.wicket.model.util.ListModel;
@@ -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;
@@ -136,6 +176,61 @@ public class PolicyModalPanelBuilder<T extends PolicyTO> extends AbstractModalPa
"allowNullPassword",
new PropertyModel<>(policyTO, "allowNullPassword"),
false));
+ } else if (policyTO instanceof PropagationPolicyTO) {
+ 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(Arrays.asList((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);
+ }
+ });
} else if (policyTO instanceof ProvisioningPolicyTO) {
fields.add(new AjaxDropDownChoicePanel<>(
"field",
@@ -156,6 +251,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/console/src/main/java/org/apache/syncope/client/console/policies/PropagationPolicyDirectoryPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/policies/PropagationPolicyDirectoryPanel.java
new file mode 100644
index 0000000..4489f22
--- /dev/null
+++ b/client/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.PolicyType;
+import org.apache.syncope.common.lib.types.StandardEntitlement;
+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, StandardEntitlement.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/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel.java
index 755c11e..952fdf0 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel.java
@@ -63,6 +63,17 @@ public class ResourceSecurityPanel extends WizardStep {
}
};
+ private final IModel<Map<String, String>> propagationPolicies = new LoadableDetachableModel<Map<String, String>>() {
+
+ private static final long serialVersionUID = -2012833443695917883L;
+
+ @Override
+ protected Map<String, String> load() {
+ return policyRestClient.getPolicies(PolicyType.PROPAGATION).stream().
+ collect(Collectors.toMap(PolicyTO::getKey, PolicyTO::getDescription));
+ }
+ };
+
private final IModel<Map<String, String>> pullPolicies = new LoadableDetachableModel<Map<String, String>>() {
private static final long serialVersionUID = -2012833443695917883L;
@@ -123,6 +134,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/console/src/main/resources/org/apache/syncope/client/console/pages/Policies.properties b/client/console/src/main/resources/org/apache/syncope/client/console/pages/Policies.properties
index b8dd372..85928d6 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/pages/Policies.properties
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/pages/Policies.properties
@@ -18,3 +18,4 @@ policy.account=Account
policy.password=Password
policy.pull=Pull
policy.push=Push
+policy.propagation=Propagation
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/pages/Policies_fr_CA.properties b/client/console/src/main/resources/org/apache/syncope/client/console/pages/Policies_fr_CA.properties
index d7b18cc..db7d4b7 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/pages/Policies_fr_CA.properties
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/pages/Policies_fr_CA.properties
@@ -18,3 +18,4 @@ policy.account=Compte
policy.password=Mot de passe
policy.pull=Pull
policy.push=Push
+policy.propagation=Propagation
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/pages/Policies_it.properties b/client/console/src/main/resources/org/apache/syncope/client/console/pages/Policies_it.properties
index b8dd372..85928d6 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/pages/Policies_it.properties
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/pages/Policies_it.properties
@@ -18,3 +18,4 @@ policy.account=Account
policy.password=Password
policy.pull=Pull
policy.push=Push
+policy.propagation=Propagation
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/pages/Policies_ja.properties b/client/console/src/main/resources/org/apache/syncope/client/console/pages/Policies_ja.properties
index db61808..a602164 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/pages/Policies_ja.properties
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/pages/Policies_ja.properties
@@ -18,3 +18,4 @@ policy.account=\u30a2\u30ab\u30a6\u30f3\u30c8
policy.password=\u30d1\u30b9\u30ef\u30fc\u30c9
policy.pull=\u30d7\u30eb
policy.push=\u30d7\u30c3\u30b7\u30e5
+policy.propagation=Propagation
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/pages/Policies_pt_BR.properties b/client/console/src/main/resources/org/apache/syncope/client/console/pages/Policies_pt_BR.properties
index b8dd372..85928d6 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/pages/Policies_pt_BR.properties
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/pages/Policies_pt_BR.properties
@@ -18,3 +18,4 @@ policy.account=Account
policy.password=Password
policy.pull=Pull
policy.push=Push
+policy.propagation=Propagation
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/pages/Policies_ru.properties b/client/console/src/main/resources/org/apache/syncope/client/console/pages/Policies_ru.properties
index 5c1a92a..899dae8 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/pages/Policies_ru.properties
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/pages/Policies_ru.properties
@@ -19,3 +19,4 @@ policy.account=\u041f\u043e\u043b\u0438\u0442\u0438\u043a\u0430 \u0443\u0447\u04
policy.password=\u041f\u043e\u043b\u0438\u0442\u0438\u043a\u0430 \u043f\u0430\u0440\u043e\u043b\u0435\u0439
policy.pull=\u041f\u043e\u043b\u0438\u0442\u0438\u043a\u0430 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0434\u0430\u043d\u043d\u044b\u0445
policy.push=Push
+policy.propagation=Propagation
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel.properties b/client/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel.properties
index b9074b2..98d7a32 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel.properties
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel.properties
@@ -29,3 +29,5 @@ any.finish=Submit ${description}
any.cancel=Cancel ${description}
compose.title=rules
conflictResolutionAction=Conflict Resolution Action
+maxAttempts=Max Attempts
+backOffStrategy=BackOff Strategy
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel_fr_CA.properties b/client/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel_fr_CA.properties
index 1207fe3..f77608e 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel_fr_CA.properties
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel_fr_CA.properties
@@ -15,17 +15,19 @@
# specific language governing permissions and limitations
# under the License.
description=Description
-usedByResources=Utilis� par ressource
-usedByRealms=Utilis� par domaine
+usedByResources=Utilis\u00e9 par ressource
+usedByRealms=Utilis\u00e9 par domaine
passthroughResources=Ressources de transit
maxAuthenticationAttempts=Nombre maximal de tentatives d'authentification
propagateSuspension=Suspension de propagation
historyLength=Longueur de l'historique
allowNullPassword=Permettre mot de passe invalide
-policy.rules=R�glements ${description}
+policy.rules=R\u00e8glements ${description}
any.edit=Modifier ${description}
any.new=Nouvelle politique
any.finish=Soumettre ${description}
any.cancel=Annuler ${description}
-compose.title=r�glements
-conflictResolutionAction=Action de r�solution des conflits
+compose.title=r\u00e8glements
+conflictResolutionAction=Action de r\u00e9solution des conflits
+maxAttempts=Max Attempts
+backOffStrategy=BackOff Strategy
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel_it.properties b/client/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel_it.properties
index f93168d..67fe5d4 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel_it.properties
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel_it.properties
@@ -29,3 +29,5 @@ any.finish=Invia ${description}
any.cancel=Annulla ${description}
compose.title=regole
conflictResolutionAction=Azione di Risoluzione Conflitti
+maxAttempts=Tentativi Massimi
+backOffStrategy=Strategia di BackOff
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel_ja.properties b/client/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel_ja.properties
index 54b0d64..dd6427b 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel_ja.properties
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel_ja.properties
@@ -29,3 +29,5 @@ any.finish=${description} \u3092\u5b9f\u884c
any.cancel=${description} \u3092\u30ad\u30e3\u30f3\u30bb\u30eb
compose.title=\u30eb\u30fc\u30eb
conflictResolutionAction=\u7af6\u5408\u89e3\u6c7a\u30a2\u30af\u30b7\u30e7\u30f3
+maxAttempts=Max Attempts
+backOffStrategy=BackOff Strategy
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel_pt_BR.properties b/client/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel_pt_BR.properties
index b9074b2..98d7a32 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel_pt_BR.properties
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel_pt_BR.properties
@@ -29,3 +29,5 @@ any.finish=Submit ${description}
any.cancel=Cancel ${description}
compose.title=rules
conflictResolutionAction=Conflict Resolution Action
+maxAttempts=Max Attempts
+backOffStrategy=BackOff Strategy
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel_ru.properties b/client/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel_ru.properties
index d161fdc..780fe78 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel_ru.properties
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel_ru.properties
@@ -30,3 +30,5 @@ any.finish=\u0421\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c ${description}
any.cancel=\u041e\u0442\u043c\u0435\u043d\u0438\u0442\u044c ${description}
compose.title=rules
conflictResolutionAction=Conflict Resolution Action
+maxAttempts=Max Attempts
+backOffStrategy=BackOff Strategy
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel.html b/client/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel.html
index fea39ff..00b4882 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel.html
+++ b/client/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/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel.properties b/client/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel.properties
index e4a70ca..c2b3da7 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel.properties
+++ b/client/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/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel_fr_CA.properties b/client/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel_fr_CA.properties
index 8a2c40f..2e20b1a 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel_fr_CA.properties
+++ b/client/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/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel_it.properties b/client/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel_it.properties
index e4a70ca..c2b3da7 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel_it.properties
+++ b/client/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/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel_ja.properties b/client/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel_ja.properties
index ca6bc82..0df90b3 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel_ja.properties
+++ b/client/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/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel_pt_BR.properties b/client/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel_pt_BR.properties
index 7300d71..2b8b4b7 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel_pt_BR.properties
+++ b/client/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/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel_ru.properties b/client/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel_ru.properties
index 7d70763..70bf684 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel_ru.properties
+++ b/client/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/common/lib/src/main/java/org/apache/syncope/common/lib/policy/PropagationPolicyTO.java b/common/lib/src/main/java/org/apache/syncope/common/lib/policy/PropagationPolicyTO.java
new file mode 100644
index 0000000..970edce
--- /dev/null
+++ b/common/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 io.swagger.v3.oas.annotations.media.Schema;
+import javax.xml.bind.annotation.XmlTransient;
+import org.apache.syncope.common.lib.types.BackOffStrategy;
+
+@Schema(allOf = { PolicyTO.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;
+
+ @XmlTransient
+ @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/lib/src/main/java/org/apache/syncope/common/lib/to/ResourceTO.java b/common/lib/src/main/java/org/apache/syncope/common/lib/to/ResourceTO.java
index 20a8628..12385c1 100644
--- a/common/lib/src/main/java/org/apache/syncope/common/lib/to/ResourceTO.java
+++ b/common/lib/src/main/java/org/apache/syncope/common/lib/to/ResourceTO.java
@@ -76,6 +76,8 @@ public class ResourceTO implements EntityTO {
private String accountPolicy;
+ private String propagationPolicy;
+
private String pullPolicy;
private String pushPolicy;
@@ -181,6 +183,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;
}
@@ -292,6 +302,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(confOverride, other.confOverride).
@@ -317,6 +328,7 @@ public class ResourceTO implements EntityTO {
append(provisioningTraceLevel).
append(passwordPolicy).
append(accountPolicy).
+ append(propagationPolicy).
append(pullPolicy).
append(pushPolicy).
append(confOverride).
diff --git a/common/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java b/common/lib/src/main/java/org/apache/syncope/common/lib/types/BackOffStrategy.java
similarity index 71%
copy from common/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java
copy to common/lib/src/main/java/org/apache/syncope/common/lib/types/BackOffStrategy.java
index ea429c2..0614332 100644
--- a/common/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java
+++ b/common/lib/src/main/java/org/apache/syncope/common/lib/types/BackOffStrategy.java
@@ -21,23 +21,18 @@ package org.apache.syncope.common.lib.types;
import javax.xml.bind.annotation.XmlEnum;
@XmlEnum
-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,
- /**
- * 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/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java b/common/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java
index ea429c2..20ecf69 100644
--- a/common/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java
+++ b/common/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java
@@ -32,6 +32,10 @@ public enum PolicyType {
*/
PASSWORD,
/**
+ * For handling propagation behavior.
+ */
+ PROPAGATION,
+ /**
* For handling conflicts resolution during pull.
*/
PULL,
diff --git a/common/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 65%
copy from common/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 ea429c2..d4e8172 100644
--- a/common/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,28 +16,21 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.apache.syncope.common.lib.types;
-
-import javax.xml.bind.annotation.XmlEnum;
-
-@XmlEnum
-public enum PolicyType {
-
- /**
- * How username values should look like.
- */
- ACCOUNT,
- /**
- * How password values should look like.
- */
- PASSWORD,
- /**
- * 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 1bb44d9..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,12 +59,16 @@ public interface ExternalResource extends ProvidedKeyEntity {
void setPasswordPolicy(PasswordPolicy passwordPolicy);
+ PropagationPolicy getPropagationPolicy();
+
+ void setPropagationPolicy(PropagationPolicy propagationPolicy);
+
PullPolicy getPullPolicy();
void setPullPolicy(PullPolicy pullPolicy);
PushPolicy getPushPolicy();
-
+
Implementation getProvisionSorter();
void setProvisionSorter(Implementation provisionSorter);
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 bca97e3..591eb2c 100644
--- a/core/persistence-jpa-json/src/test/resources/domains/MasterContent.xml
+++ b/core/persistence-jpa-json/src/test/resources/domains/MasterContent.xml
@@ -103,6 +103,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" description="sample propagation policy"
+ maxAttempts="5" backOffStrategy="FIXED" backOffParams="10000"/>
<RelationshipType id="inclusion" description="Models the act that an object is included in another"/>
<RelationshipType id="neighborhood" description="Models the act that an object is near another"/>
@@ -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 4990ee0..9512483 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
@@ -44,6 +44,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;
@@ -157,6 +158,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 c1cb136..bcfee40 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
@@ -28,12 +28,14 @@ import org.apache.syncope.core.persistence.api.entity.policy.AccountPolicy;
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.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.jpa.entity.policy.AbstractPolicy;
import org.apache.syncope.core.persistence.jpa.entity.policy.JPAAccountPolicy;
import org.apache.syncope.core.persistence.jpa.entity.policy.JPAPullCorrelationRuleEntity;
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.JPAPullPolicy;
import org.apache.syncope.core.persistence.jpa.entity.policy.JPAPushCorrelationRuleEntity;
import org.apache.syncope.core.persistence.jpa.entity.policy.JPAPushPolicy;
@@ -57,6 +59,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)
@@ -155,6 +159,8 @@ public class JPAPolicyDAO extends AbstractDAO<Policy> implements PolicyDAO {
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 97f0679..5a16853 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
@@ -34,6 +34,7 @@ import org.apache.syncope.core.persistence.api.entity.policy.AccountPolicy;
import org.apache.syncope.core.persistence.api.entity.Realm;
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.ProvisioningPolicy;
import org.apache.syncope.core.persistence.api.entity.resource.ExternalResource;
import org.apache.syncope.core.persistence.jpa.entity.JPARealm;
@@ -144,8 +145,8 @@ 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 Collections.emptyList();
}
TypedQuery<Realm> query = entityManager().createQuery(
@@ -154,10 +155,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 4d7f64e..527bdc8 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
@@ -90,7 +90,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 3e5892c..d8169f1 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
@@ -289,6 +289,7 @@ public class JPAUserDAO extends AbstractAnyDAO<User> implements UserDAO {
return query.getResultList();
}
+ @Override
public List<String> findAllKeys(final int page, final int itemsPerPage) {
return findAllKeys(JPAUser.TABLE, page, itemsPerPage);
}
@@ -300,13 +301,13 @@ public class JPAUserDAO extends AbstractAnyDAO<User> implements UserDAO {
findAllResources(user).stream().
map(resource -> resource.getAccountPolicy()).
filter(policy -> policy != null).
- forEachOrdered(policy -> policies.add(policy));
+ forEach(policy -> policies.add(policy));
// add realm policies
realmDAO.findAncestors(user.getRealm()).stream().
map(realm -> realm.getAccountPolicy()).
filter(policy -> policy != null).
- forEachOrdered(policy -> policies.add(policy));
+ forEach(policy -> policies.add(policy));
return policies;
}
@@ -513,7 +514,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 048105c..9107c0e 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
@@ -134,6 +134,7 @@ import org.apache.syncope.core.persistence.api.entity.Implementation;
import org.apache.syncope.core.persistence.api.entity.Privilege;
import org.apache.syncope.core.persistence.api.entity.Remediation;
import org.apache.syncope.core.persistence.api.entity.SchemaLabel;
+import org.apache.syncope.core.persistence.api.entity.policy.PropagationPolicy;
import org.apache.syncope.core.persistence.api.entity.resource.OrgUnitItem;
import org.apache.syncope.core.persistence.jpa.entity.policy.JPAPullCorrelationRuleEntity;
import org.apache.syncope.core.persistence.jpa.entity.resource.JPAOrgUnitItem;
@@ -144,6 +145,7 @@ import org.apache.syncope.core.persistence.api.entity.user.LAPlainAttrUniqueValu
import org.apache.syncope.core.persistence.api.entity.user.LAPlainAttrValue;
import org.apache.syncope.core.persistence.api.entity.user.LinkedAccount;
import org.apache.syncope.core.persistence.jpa.dao.JPAAnySearchDAO;
+import org.apache.syncope.core.persistence.jpa.entity.policy.JPAPropagationPolicy;
import org.apache.syncope.core.persistence.jpa.entity.policy.JPAPushCorrelationRuleEntity;
import org.apache.syncope.core.persistence.jpa.entity.user.JPALAPlainAttr;
import org.apache.syncope.core.persistence.jpa.entity.user.JPALAPlainAttrUniqueValue;
@@ -174,6 +176,8 @@ public class JPAEntityFactory implements EntityFactory {
result = (E) new JPAPasswordPolicy();
} else if (reference.equals(PushPolicy.class)) {
result = (E) new JPAPushPolicy();
+ } else if (reference.equals(PropagationPolicy.class)) {
+ result = (E) new JPAPropagationPolicy();
} else if (reference.equals(PullPolicy.class)) {
result = (E) new JPAPullPolicy();
} else if (reference.equals(PullCorrelationRuleEntity.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 d87325e..926b978 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
@@ -23,6 +23,7 @@ import org.apache.syncope.core.persistence.api.entity.policy.AccountPolicy;
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.PropagationPolicy;
import org.apache.syncope.core.persistence.api.entity.policy.PullPolicy;
import org.apache.syncope.core.persistence.api.entity.policy.PushPolicy;
@@ -48,6 +49,9 @@ public class JPAPolicyUtils implements PolicyUtils {
case PASSWORD:
return PasswordPolicy.class;
+ case PROPAGATION:
+ return PropagationPolicy.class;
+
case PULL:
return PullPolicy.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 2a616c2..bde3b7d 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
@@ -21,6 +21,7 @@ package org.apache.syncope.core.persistence.jpa.entity.policy;
import org.apache.syncope.common.lib.policy.AccountPolicyTO;
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;
@@ -29,6 +30,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.springframework.stereotype.Component;
import org.apache.syncope.core.persistence.api.entity.policy.PullPolicy;
import org.apache.syncope.core.persistence.api.entity.policy.PushPolicy;
@@ -48,6 +50,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) {
@@ -66,6 +70,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 6ab0395..14edc8f 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
@@ -55,6 +55,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;
@@ -66,6 +67,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;
@@ -133,6 +135,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)
@@ -313,6 +318,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 e51329f..97f9e5f 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
@@ -29,6 +29,7 @@ import java.util.UUID;
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.ImplementationEngine;
import org.apache.syncope.common.lib.types.ImplementationType;
@@ -45,6 +46,8 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.apache.syncope.core.persistence.api.entity.policy.PullPolicy;
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.policy.PropagationPolicy;
import org.apache.syncope.core.persistence.api.entity.policy.PullCorrelationRuleEntity;
import org.apache.syncope.core.persistence.api.entity.policy.PushCorrelationRuleEntity;
import org.apache.syncope.core.persistence.api.entity.policy.PushPolicy;
@@ -70,6 +73,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);
@@ -102,7 +111,27 @@ public class PolicyTest extends AbstractTest {
}
@Test
- public void create() {
+ public void createPropagation() {
+ int beforeCount = policyDAO.findAll().size();
+
+ PropagationPolicy propagationPolicy = entityFactory.newEntity(PropagationPolicy.class);
+ propagationPolicy.setDescription("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 policy = entityFactory.newEntity(PullPolicy.class);
policy.setConflictResolutionAction(ConflictResolutionAction.IGNORE);
policy.setDescription("Pull policy");
@@ -146,6 +175,50 @@ public class PolicyTest extends AbstractTest {
}
@Test
+ public void createPush() {
+ PushPolicy policy = entityFactory.newEntity(PushPolicy.class);
+ policy.setConflictResolutionAction(ConflictResolutionAction.IGNORE);
+ policy.setDescription("Push policy");
+
+ 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(ImplementationType.PUSH_CORRELATION_RULE);
+ impl1.setBody(PushCorrelationRule.class.getName());
+ impl1 = implementationDAO.save(impl1);
+
+ PushCorrelationRuleEntity rule1 = entityFactory.newEntity(PushCorrelationRuleEntity.class);
+ rule1.setAnyType(anyTypeDAO.findUser());
+ rule1.setPushPolicy(policy);
+ rule1.setImplementation(impl1);
+ policy.add(rule1);
+
+ Implementation impl2 = entityFactory.newEntity(Implementation.class);
+ impl2.setKey(pushGRuleName);
+ impl2.setEngine(ImplementationEngine.JAVA);
+ impl2.setType(ImplementationType.PUSH_CORRELATION_RULE);
+ impl2.setBody(PushCorrelationRule.class.getName());
+ impl2 = implementationDAO.save(impl2);
+
+ PushCorrelationRuleEntity rule2 = entityFactory.newEntity(PushCorrelationRuleEntity.class);
+ rule2.setAnyType(anyTypeDAO.findGroup());
+ rule2.setPushPolicy(policy);
+ rule2.setImplementation(impl2);
+ policy.add(rule2);
+
+ policy = policyDAO.save(policy);
+
+ assertNotNull(policy);
+ assertEquals(pushURuleName,
+ policy.getCorrelationRule(anyTypeDAO.findUser()).get().getImplementation().getKey());
+ assertEquals(pushGRuleName,
+ policy.getCorrelationRule(anyTypeDAO.findGroup()).get().getImplementation().getKey());
+ }
+
+ @Test
public void update() {
PasswordPolicy policy = policyDAO.find("ce93fcda-dc3a-4369-a7b0-a6108c261c85");
assertNotNull(policy);
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 bd4bdd6..6467142 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 559a041..b08fcf8 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
@@ -39,7 +39,6 @@ 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.dao.search.OrderByClause;
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;
@@ -212,7 +211,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
@@ -231,14 +230,10 @@ 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
@@ -264,7 +259,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 c33a68f..2828915 100644
--- a/core/persistence-jpa/src/test/resources/domains/MasterContent.xml
+++ b/core/persistence-jpa/src/test/resources/domains/MasterContent.xml
@@ -160,6 +160,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" description="sample propagation policy"
+ maxAttempts="5" backOffStrategy="FIXED" backOffParams="10000"/>
<RelationshipType id="inclusion" description="Models the act that an object is included in another"/>
<RelationshipType id="neighborhood" description="Models the act that an object is near another"/>
@@ -848,7 +850,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 878df57..a9c97a0 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
diff --git a/core/provisioning-java/pom.xml b/core/provisioning-java/pom.xml
index 47b58c4..0e35b43 100644
--- a/core/provisioning-java/pom.xml
+++ b/core/provisioning-java/pom.xml
@@ -54,6 +54,11 @@ under the License.
</dependency>
<dependency>
+ <groupId>org.springframework.retry</groupId>
+ <artifactId>spring-retry</artifactId>
+ </dependency>
+
+ <dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
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 53122b5..f185f07 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
@@ -23,6 +23,7 @@ import org.apache.syncope.core.provisioning.api.data.PolicyDataBinder;
import org.apache.syncope.common.lib.policy.PolicyTO;
import org.apache.syncope.common.lib.policy.AccountPolicyTO;
import org.apache.syncope.common.lib.policy.PasswordPolicyTO;
+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.core.persistence.api.dao.AnyTypeDAO;
@@ -39,6 +40,7 @@ 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.Realm;
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;
@@ -126,6 +128,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);
@@ -157,8 +170,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);
@@ -239,6 +252,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 943172d..dd7773d 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
@@ -59,6 +59,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;
@@ -68,6 +69,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;
@@ -106,6 +108,9 @@ public class ResourceDataBinderImpl implements ResourceDataBinder {
@Autowired
private IntAttrNameParser intAttrNameParser;
+ @Autowired
+ private PropagationTaskExecutor propagationTaskExecutor;
+
@Override
public ExternalResource create(final ResourceTO resourceTO) {
return update(entityFactory.newEntity(ExternalResource.class), resourceTO);
@@ -349,6 +354,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()));
@@ -678,6 +691,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 1730ae4..f084538 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,8 +20,9 @@ package org.apache.syncope.core.provisioning.java.propagation;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collection;
+import java.util.Collections;
import java.util.Date;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@@ -62,7 +63,6 @@ import org.apache.syncope.core.persistence.api.entity.task.TaskUtilsFactory;
import org.apache.syncope.core.provisioning.api.AuditManager;
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;
@@ -81,6 +81,12 @@ import org.identityconnectors.framework.common.objects.Uid;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
+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 })
@@ -88,6 +94,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<>());
+
/**
* Connector factory.
*/
@@ -157,6 +165,11 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
@Autowired
protected OutboundMatcher outboundMatcher;
+ @Override
+ public void expireRetryTemplate(final String resource) {
+ retryTemplates.remove(resource);
+ }
+
protected List<PropagationActions> getPropagationActions(final ExternalResource resource) {
List<PropagationActions> result = new ArrayList<>();
@@ -309,10 +322,116 @@ 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, final PropagationReporter reporter) {
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);
+ 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));
+ }
+
+ protected TaskExec doExecute(
+ final PropagationTaskInfo taskInfo,
+ final PropagationTask task,
+ final PropagationReporter reporter) {
+
Connector connector = taskInfo.getConnector() == null
? connFactory.getConnector(task.getResource())
: taskInfo.getConnector();
@@ -321,8 +440,8 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
Date start = new Date();
- TaskExec execution = entityFactory.newEntity(TaskExec.class);
- execution.setStatus(ExecStatus.CREATED.name());
+ TaskExec exec = entityFactory.newEntity(TaskExec.class);
+ exec.setStatus(ExecStatus.CREATED.name());
String taskExecutionMessage = null;
String failureReason = null;
@@ -369,7 +488,7 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
default:
}
- execution.setStatus(propagationAttempted.get()
+ exec.setStatus(propagationAttempted.get()
? ExecStatus.SUCCESS.name()
: ExecStatus.NOT_ATTEMPTED.name());
@@ -396,14 +515,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) {
@@ -430,17 +549,17 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
build();
}
- execution.setStart(start);
- execution.setMessage(taskExecutionMessage);
- execution.setEnd(new Date());
+ exec.setStart(start);
+ exec.setMessage(taskExecutionMessage);
+ exec.setEnd(new Date());
- 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);
}
@@ -453,7 +572,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,
@@ -461,7 +580,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();
@@ -476,7 +595,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,
@@ -500,12 +619,9 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
taskInfo);
}
- return execution;
+ return exec;
}
- protected abstract void doExecute(
- Collection<PropagationTaskInfo> taskInfos, PropagationReporter reporter, boolean nullPriorityAsync);
-
protected TaskExec rejected(
final PropagationTaskInfo taskInfo,
final String rejectReason,
@@ -540,22 +656,6 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
return execution;
}
- @Override
- public PropagationReporter execute(
- final Collection<PropagationTaskInfo> taskInfos,
- final boolean nullPriorityAsync) {
-
- PropagationReporter reporter = new DefaultPropagationReporter();
- try {
- doExecute(taskInfos, reporter, nullPriorityAsync);
- } 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 e402638..30b6f3c 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
@@ -70,71 +70,78 @@ public class PriorityPropagationTaskExecutor extends AbstractPropagationTaskExec
}
@Override
- protected void doExecute(
+ public PropagationReporter execute(
final Collection<PropagationTaskInfo> taskInfos,
- final PropagationReporter reporter,
final boolean nullPriorityAsync) {
- 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);
+ 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);
+ 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).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(),
- execution == null ? errorMessage : execution.getMessage());
- }
- });
-
- // then process non-priority resources concurrently...
- if (!concurrentTasks.isEmpty()) {
- CompletionService<TaskExec> completionService = new ExecutorCompletionService<>(executor);
- List<Future<TaskExec>> futures = new ArrayList<>();
-
- concurrentTasks.forEach(taskInfo -> {
+ // 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 {
- futures.add(completionService.submit(newPropagationTaskCallable(taskInfo, reporter)));
-
- if (nullPriorityAsync) {
- reporter.onSuccessOrNonPriorityResourceFailures(
- taskInfo, ExecStatus.CREATED, null, null, null, null);
- }
+ execution = newPropagationTaskCallable(task, reporter).call();
+ execStatus = ExecStatus.valueOf(execution.getStatus());
} catch (Exception e) {
- LOG.error("While submitting task for async execution", taskInfo, e);
- rejected(taskInfo, e.getMessage(), reporter);
+ LOG.error("Unexpected exception", e);
+ execStatus = ExecStatus.FAILURE;
+ errorMessage = e.getMessage();
+ }
+ if (execStatus != ExecStatus.SUCCESS) {
+ throw new PropagationException(
+ task.getResource(),
+ execution == null ? errorMessage : execution.getMessage());
}
});
- // ...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<>(executor);
+ List<Future<TaskExec>> futures = new ArrayList<>();
+
+ concurrentTasks.forEach(taskInfo -> {
try {
- future.get();
+ futures.add(completionService.submit(newPropagationTaskCallable(taskInfo, reporter)));
+
+ 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);
}
});
+
+ // ...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 c4241ed..e1a28f1 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
@@ -143,7 +143,7 @@ public class PoliciesITCase extends AbstractConsoleITCase {
}
private void createPullPolicy(final String description) {
- TESTER.clickLink("body:content:tabbedPanel:tabs-container:tabs:2:link");
+ TESTER.clickLink("body:content:tabbedPanel:tabs-container:tabs:3:link");
TESTER.clickLink("body:content:tabbedPanel:panel:container:content:add");
TESTER.assertComponent("body:content:tabbedPanel:panel:outerObjectsRepeater:0:outer", Modal.class);
@@ -233,7 +233,7 @@ public class PoliciesITCase extends AbstractConsoleITCase {
}
private void deletePullPolicy(final String description) {
- TESTER.clickLink("body:content:tabbedPanel:tabs-container:tabs:2:link");
+ TESTER.clickLink("body:content:tabbedPanel:tabs-container:tabs:3:link");
Component component = findComponentByProp("description", "body:content:tabbedPanel:panel:container:content:"
+ "searchContainer:resultTable:tablePanel:groupForm:checkgroup:dataTable", description);
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 e47fcf7..1be9197 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
@@ -40,9 +40,11 @@ import org.apache.syncope.common.lib.policy.PullPolicyTO;
import org.apache.syncope.common.lib.policy.DefaultAccountRuleConf;
import org.apache.syncope.common.lib.types.AnyTypeKind;
import org.apache.syncope.common.lib.policy.DefaultPasswordRuleConf;
+import org.apache.syncope.common.lib.policy.PropagationPolicyTO;
import org.apache.syncope.common.lib.policy.PushPolicyTO;
import org.apache.syncope.common.lib.types.PolicyType;
import org.apache.syncope.common.lib.to.ImplementationTO;
+import org.apache.syncope.common.lib.types.BackOffStrategy;
import org.apache.syncope.common.lib.types.ImplementationEngine;
import org.apache.syncope.common.lib.types.ImplementationType;
import org.apache.syncope.common.rest.api.RESTHeaders;
@@ -110,10 +112,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
@@ -135,6 +140,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");
@@ -144,6 +159,14 @@ public class PolicyITCase extends AbstractITCase {
@Test
public void create() throws IOException {
+ PropagationPolicyTO propagationPolicyTO = new PropagationPolicyTO();
+ propagationPolicyTO.setDescription("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()));
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 41f0f93..f3b7e66 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;
@@ -38,8 +39,10 @@ import java.util.Optional;
import javax.ws.rs.core.Response;
import org.apache.syncope.client.lib.batch.BatchRequest;
import java.util.Set;
+import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;
+import javax.sql.DataSource;
import javax.ws.rs.core.GenericType;
import javax.xml.ws.WebServiceException;
import org.apache.commons.lang3.SerializationUtils;
@@ -92,9 +95,17 @@ 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.beans.factory.annotation.Autowired;
+import org.springframework.dao.DataAccessException;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
+@SpringJUnitConfig(locations = { "classpath:testJDBCEnv.xml" })
public class PropagationTaskITCase extends AbstractTaskITCase {
+ @Autowired
+ private DataSource testDataSource;
+
@BeforeAll
public static void testItemTransformersSetup() {
ImplementationTO dateToLong = null;
@@ -309,6 +320,41 @@ public class PropagationTaskITCase extends AbstractTaskITCase {
}
@Test
+ public void propagationPolicy() throws InterruptedException {
+ adminClient.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.getSampleTO("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 {
+ adminClient.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 ExecuteQuery.Builder().
diff --git a/pom.xml b/pom.xml
index bb2cfb3..28f8d52 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1019,7 +1019,13 @@ under the License.
<artifactId>spring-security-config</artifactId>
<version>${spring-security.version}</version>
</dependency>
-
+
+ <dependency>
+ <groupId>org.springframework.retry</groupId>
+ <artifactId>spring-retry</artifactId>
+ <version>1.3.2</version>
+ </dependency>
+
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
diff --git a/src/main/asciidoc/reference-guide/concepts/policies.adoc b/src/main/asciidoc/reference-guide/concepts/policies.adoc
index 2421f56..661443d 100644
--- a/src/main/asciidoc/reference-guide/concepts/policies.adoc
+++ b/src/main/asciidoc/reference-guide/concepts/policies.adoc
@@ -272,6 +272,20 @@ Before being able to configure the "Have I Been Pwned?" password rule as mention
a `JAVA` `PASSWORD_RULE` <<implementations,implementation>> for the
`org.apache.syncope.common.lib.policy.HaveIBeenPwnedPasswordRuleConf` class.
+[[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 18c79a4..4127cdb 100644
--- a/src/main/asciidoc/reference-guide/concepts/tasks.adoc
+++ b/src/main/asciidoc/reference-guide/concepts/tasks.adoc
@@ -60,8 +60,10 @@ ifeval::["{snapshotOrRelease}" == "snapshot"]
https://github.com/apache/syncope/blob/2_1_X/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/PriorityPropagationTaskExecutor.java[PriorityPropagationTaskExecutor^]
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>>.
+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]]