diff --git a/pom.xml b/pom.xml
index a7b330f6..02b3b0f5 100644
--- a/pom.xml
+++ b/pom.xml
@@ -72,6 +72,7 @@
${project.build.directory}/staging
1.5
22.3.1
+ 2.9.6
${project.build.directory}/compiler
@@ -872,6 +873,11 @@
xml-apis
1.4.01
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ ${jackson.version}
+
diff --git a/src/main/java/org/lsc/Hooks.java b/src/main/java/org/lsc/Hooks.java
new file mode 100644
index 00000000..69a557fc
--- /dev/null
+++ b/src/main/java/org/lsc/Hooks.java
@@ -0,0 +1,148 @@
+/*
+ ****************************************************************************
+ * Ldap Synchronization Connector provides tools to synchronize
+ * electronic identities from a list of data sources including
+ * any database with a JDBC connector, another LDAP directory,
+ * flat files...
+ *
+ * ==LICENSE NOTICE==
+ *
+ * Copyright (c) 2008 - 2011 LSC Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the LSC Project nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
+ * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * ==LICENSE NOTICE==
+ *
+ * (c) 2008 - 2011 LSC Project
+ ****************************************************************************
+ */
+package org.lsc;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import java.lang.ProcessBuilder;
+import java.io.OutputStream;
+import java.io.IOException;
+import java.util.Optional;
+import org.lsc.utils.output.LdifLayout;
+import org.lsc.beans.syncoptions.ISyncOptions.OutputFormat;
+import com.fasterxml.jackson.databind.ObjectMapper; // For encoding object to JSON
+import com.fasterxml.jackson.databind.ObjectWriter;
+
+
+/**
+ * This object is managing posthook scripts
+ */
+public class Hooks {
+
+ static final Logger LOGGER = LoggerFactory.getLogger(Hooks.class);
+
+ private static final ObjectMapper Mapper = new ObjectMapper();
+ /**
+ * Method calling a postSyncHook if necessary
+ *
+ * return nothing
+ */
+ public final void postSyncHook(final Optional hook, final OutputFormat outputFormat, final LscModifications lm) {
+
+ hook.ifPresent( h -> callHook(lm.getOperation(), h, lm.getMainIdentifier(), outputFormat, lm));
+ }
+
+ public final static void callHook( LscModificationType operationType,
+ String hook,
+ String identifier,
+ OutputFormat outputFormat,
+ LscModifications lm) {
+
+ LOGGER.info("Calling {} posthook {} with format {} for {}",
+ operationType.getDescription(),
+ hook,
+ outputFormat.toString(),
+ identifier);
+
+ String modifications = null;
+ // Compute modifications only in a create / update / changeid operation
+ if( operationType != LscModificationType.DELETE_OBJECT)
+ {
+ if( outputFormat == OutputFormat.JSON ) {
+ modifications = getJsonModifications(lm);
+ }
+ else {
+ modifications = LdifLayout.format(lm);
+ }
+ }
+
+ try {
+ if( modifications != null ) {
+ Process p = new ProcessBuilder(
+ hook,
+ identifier,
+ operationType.getDescription())
+ .start();
+
+ // sends ldif modifications to stdin of hook script
+ OutputStream stdin = p.getOutputStream();
+ stdin.write(modifications.getBytes());
+ stdin.write("\n".getBytes());
+ stdin.flush();
+ stdin.close();
+ }
+ else {
+ Process p = new ProcessBuilder(
+ hook,
+ identifier,
+ operationType.getDescription())
+ .start();
+ }
+ }
+ catch(IOException e) {
+ LOGGER.error("Error while calling {} posthook {} with format {} for {}",
+ operationType.getDescription(),
+ hook,
+ outputFormat.toString(),
+ identifier,
+ e);
+ }
+ }
+
+ /**
+ * Method computing modifications as json
+ *
+ * @return modifications in a json String
+ */
+ public final static String getJsonModifications(final LscModifications lm) {
+ ObjectWriter ow = Mapper.writer().withDefaultPrettyPrinter();
+ String json = "";
+ try {
+ json = ow.writeValueAsString(lm.getLscAttributeModifications());
+ }
+ catch(Exception e) {
+ LOGGER.error("Error while encoding LSC modifications to json", e);
+ }
+ return json;
+ }
+
+}
diff --git a/src/main/java/org/lsc/beans/syncoptions/ForceSyncOptions.java b/src/main/java/org/lsc/beans/syncoptions/ForceSyncOptions.java
index f5f6c1ba..1bb474e0 100644
--- a/src/main/java/org/lsc/beans/syncoptions/ForceSyncOptions.java
+++ b/src/main/java/org/lsc/beans/syncoptions/ForceSyncOptions.java
@@ -47,6 +47,7 @@
import java.util.List;
import java.util.Set;
+import java.util.Optional;
import org.lsc.LscModificationType;
import org.lsc.configuration.PolicyType;
@@ -128,6 +129,30 @@ public String getCondition(LscModificationType operation) {
return DEFAULT_CONDITION;
}
+ public OutputFormat getPostHookOutputFormat() {
+ return OutputFormat.LDIF;
+ }
+
+ public Optional getCreatePostHook() {
+ return Optional.empty();
+ }
+
+ public Optional getDeletePostHook() {
+ return Optional.empty();
+ }
+
+ public Optional getUpdatePostHook() {
+ return Optional.empty();
+ }
+
+ public Optional getChangeIdPostHook() {
+ return Optional.empty();
+ }
+
+ public Optional getPostHook(LscModificationType operation) {
+ return Optional.empty();
+ }
+
public String getDn() {
return null;//((Ldap)task.getDestinationService()).getDn();
}
diff --git a/src/main/java/org/lsc/beans/syncoptions/ISyncOptions.java b/src/main/java/org/lsc/beans/syncoptions/ISyncOptions.java
index 9df18858..69616755 100644
--- a/src/main/java/org/lsc/beans/syncoptions/ISyncOptions.java
+++ b/src/main/java/org/lsc/beans/syncoptions/ISyncOptions.java
@@ -47,6 +47,7 @@
import java.util.List;
import java.util.Set;
+import java.util.Optional;
import org.lsc.LscModificationType;
import org.lsc.configuration.PolicyType;
@@ -60,6 +61,9 @@ public interface ISyncOptions {
/** default condition if none is given */
public static final String DEFAULT_CONDITION = "true";
+
+ /** list of output formats */
+ public enum OutputFormat { LDIF, JSON };
/**
* Initialize the synchronization options policy.
@@ -153,6 +157,44 @@ public interface ISyncOptions {
String getCondition(LscModificationType operation);
+ /**
+ * Returns the posthook output format
+ *
+ * @return the posthook output format (default = OutputFormat.LDIF)
+ */
+ OutputFormat getPostHookOutputFormat();
+
+ /**
+ * Returns the posthook for a creation
+ *
+ * @return the posthook or "" if none is specified (default)
+ */
+ Optional getCreatePostHook();
+
+ /**
+ * Returns the posthook for an update
+ *
+ * @return the posthook or "" if none is specified (default)
+ */
+ Optional getUpdatePostHook();
+
+ /**
+ * Returns the posthook for a delete
+ *
+ * @return the posthook or "" if none is specified (default)
+ */
+ Optional getDeletePostHook();
+
+ /**
+ * Returns the posthook for a id change
+ *
+ * @return the posthook or "" if none is specified (default)
+ */
+ Optional getChangeIdPostHook();
+
+
+ Optional getPostHook(LscModificationType operation);
+
/**
* Return the expression used to infer the new object DN
* @return the new object dn pattern
diff --git a/src/main/java/org/lsc/beans/syncoptions/PropertiesBasedSyncOptions.java b/src/main/java/org/lsc/beans/syncoptions/PropertiesBasedSyncOptions.java
index aa04944d..42ff763f 100644
--- a/src/main/java/org/lsc/beans/syncoptions/PropertiesBasedSyncOptions.java
+++ b/src/main/java/org/lsc/beans/syncoptions/PropertiesBasedSyncOptions.java
@@ -49,6 +49,7 @@
import java.util.HashSet;
import java.util.List;
import java.util.Set;
+import java.util.Optional;
import org.lsc.LscModificationType;
import org.lsc.configuration.DatasetType;
@@ -196,6 +197,53 @@ public String getCondition(LscModificationType operation) {
}
return result;
}
+
+ public OutputFormat getPostHookOutputFormat() {
+ // default: returns LDIF format
+ if (conf.getHooks() == null || conf.getHooks().getOutputFormat() == null) {
+ return OutputFormat.LDIF;
+ }
+ switch(conf.getHooks().getOutputFormat()){
+ case "json":
+ return OutputFormat.JSON;
+ default:
+ return OutputFormat.LDIF;
+ }
+ }
+
+ public Optional getCreatePostHook() {
+ Optional hook = Optional.ofNullable(conf.getHooks().getCreatePostHook()).filter(s -> !s.isEmpty());
+ return hook;
+ }
+
+ public Optional getDeletePostHook() {
+ Optional hook = Optional.ofNullable(conf.getHooks().getDeletePostHook()).filter(s -> !s.isEmpty());
+ return hook;
+ }
+
+ public Optional getUpdatePostHook() {
+ Optional hook = Optional.ofNullable(conf.getHooks().getUpdatePostHook()).filter(s -> !s.isEmpty());
+ return hook;
+ }
+
+ public Optional getChangeIdPostHook() {
+ Optional hook = Optional.ofNullable(conf.getHooks().getChangeIdPostHook()).filter(s -> !s.isEmpty());
+ return hook;
+ }
+
+ public Optional getPostHook(LscModificationType operation) {
+ switch (operation) {
+ case CREATE_OBJECT:
+ return this.getCreatePostHook();
+ case UPDATE_OBJECT:
+ return this.getUpdatePostHook();
+ case DELETE_OBJECT:
+ return this.getDeletePostHook();
+ case CHANGE_ID:
+ return this.getChangeIdPostHook();
+ }
+ return Optional.empty();
+ }
public String getDelimiter(String name) {
DatasetType dataset = LscConfiguration.getDataset(conf, name);
diff --git a/src/main/java/org/lsc/configuration/.gitignore b/src/main/java/org/lsc/configuration/.gitignore
index faf60484..e83453ba 100644
--- a/src/main/java/org/lsc/configuration/.gitignore
+++ b/src/main/java/org/lsc/configuration/.gitignore
@@ -4,6 +4,7 @@
/AuditType.java
/AuditsType.java
/ConditionsType.java
+/HooksType.java
/ConnectionType.java
/ConnectionsType.java
/CsvAuditType.java
diff --git a/src/main/java/org/lsc/runnable/CleanEntryRunner.java b/src/main/java/org/lsc/runnable/CleanEntryRunner.java
index 3d481a16..51d753d3 100644
--- a/src/main/java/org/lsc/runnable/CleanEntryRunner.java
+++ b/src/main/java/org/lsc/runnable/CleanEntryRunner.java
@@ -6,6 +6,7 @@
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
+import java.util.Optional;
import javax.naming.CommunicationException;
@@ -23,6 +24,8 @@
import org.lsc.utils.ScriptingEvaluator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.lsc.Hooks;
+import org.lsc.beans.syncoptions.ISyncOptions.OutputFormat;
/**
* @author sfroger
@@ -31,11 +34,13 @@ public class CleanEntryRunner extends AbstractEntryRunner {
static final Logger LOGGER = LoggerFactory.getLogger(CleanEntryRunner.class);
+ private Hooks hooks;
public CleanEntryRunner(final Task task, InfoCounter counter,
AbstractSynchronize abstractSynchronize,
Entry id) {
super(task, counter, abstractSynchronize, id);
+ this.hooks = new Hooks();
}
@Override
@@ -108,6 +113,10 @@ public void run() {
// if we got here, we have a modification to apply - let's
// do it!
if (task.getDestinationService().apply(lm)) {
+ // Retrieve posthook for the current operation
+ hooks.postSyncHook( syncOptions.getDeletePostHook(),
+ syncOptions.getPostHookOutputFormat(),
+ lm);
counter.incrementCountCompleted();
abstractSynchronize.logAction(lm, id, task.getName());
} else {
diff --git a/src/main/java/org/lsc/runnable/SynchronizeEntryRunner.java b/src/main/java/org/lsc/runnable/SynchronizeEntryRunner.java
index dec1fb56..139a0f09 100644
--- a/src/main/java/org/lsc/runnable/SynchronizeEntryRunner.java
+++ b/src/main/java/org/lsc/runnable/SynchronizeEntryRunner.java
@@ -3,6 +3,7 @@
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
+import java.util.Optional;
import org.lsc.AbstractSynchronize;
import org.lsc.beans.InfoCounter;
@@ -16,6 +17,8 @@
import org.lsc.utils.ScriptingEvaluator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.lsc.Hooks;
+import org.lsc.beans.syncoptions.ISyncOptions.OutputFormat;
/**
* @author sbahloul
@@ -24,6 +27,7 @@ public class SynchronizeEntryRunner extends AbstractEntryRunner {
static final Logger LOGGER = LoggerFactory.getLogger(SynchronizeEntryRunner.class);
private boolean fromSource;
+ private Hooks hooks;
public SynchronizeEntryRunner(final Task task, InfoCounter counter,
AbstractSynchronize abstractSynchronize,
@@ -31,6 +35,7 @@ public SynchronizeEntryRunner(final Task task, InfoCounter counter,
boolean fromSource) {
super(task, counter, abstractSynchronize, id);
this.fromSource = fromSource;
+ this.hooks = new Hooks();
}
@Override
@@ -137,6 +142,10 @@ public boolean run(IBean entry) {
// if we got here, we have a modification to apply - let's do it!
if (task.getDestinationService().apply(lm)) {
+ // Retrieve posthook for the current operation
+ hooks.postSyncHook( task.getSyncOptions().getPostHook(modificationType),
+ task.getSyncOptions().getPostHookOutputFormat(),
+ lm);
counter.incrementCountCompleted();
abstractSynchronize.logAction(lm, id, syncName);
return true;
@@ -160,4 +169,5 @@ public boolean run(IBean entry) {
}
}
-}
\ No newline at end of file
+}
+
diff --git a/src/main/resources/schemas/lsc-core-2.2.xsd b/src/main/resources/schemas/lsc-core-2.2.xsd
index 968c0227..5aaf4e55 100644
--- a/src/main/resources/schemas/lsc-core-2.2.xsd
+++ b/src/main/resources/schemas/lsc-core-2.2.xsd
@@ -323,7 +323,17 @@
-
+
+
+
+
+
+
+
+
+
+
+
@@ -334,6 +344,7 @@
default="FORCE" />
+
diff --git a/src/test/java/org/lsc/Ldap2LdapHookSyncTest.java b/src/test/java/org/lsc/Ldap2LdapHookSyncTest.java
new file mode 100644
index 00000000..85cbce91
--- /dev/null
+++ b/src/test/java/org/lsc/Ldap2LdapHookSyncTest.java
@@ -0,0 +1,261 @@
+/*
+ ****************************************************************************
+ * Ldap Synchronization Connector provides tools to synchronize
+ * electronic identities from a list of data sources including
+ * any database with a JDBC connector, another LDAP directory,
+ * flat files...
+ *
+ * ==LICENSE NOTICE==
+ *
+ * Copyright (c) 2008 - 2011 LSC Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the LSC Project nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
+ * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * ==LICENSE NOTICE==
+ *
+ * (c) 2008 - 2011 LSC Project
+ * Sebastien Bahloul <seb@lsc-project.org>
+ * Thomas Chemineau <thomas@lsc-project.org>
+ * Jonathan Clarke <jon@lsc-project.org>
+ * Remy-Christophe Schermesser <rcs@lsc-project.org>
+ ****************************************************************************
+ */
+package org.lsc;
+
+import com.fasterxml.jackson.databind.ObjectMapper; // For encoding object to JSON
+import com.fasterxml.jackson.databind.ObjectWriter;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.core.JsonFactory;
+import com.fasterxml.jackson.core.JsonParseException;
+import com.fasterxml.jackson.core.JsonParser;
+import java.io.StringWriter;
+import java.io.PrintWriter;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.lsc.configuration.LscConfiguration;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.io.File;
+import java.util.Scanner;
+
+
+/**
+ * This test case attempts to reproduce a ldap2ldap setup via SimpleSynchronize.
+ * It attempts to launch the tasks defined in src/test/resources/etc/lsc.xml:
+ * ldap2ldapHookTestCreate ldap2ldapHookTestUpdate ldap2ldapHookTestDelete
+ */
+public class Ldap2LdapHookSyncTest extends CommonLdapSyncTest {
+
+
+ @Before
+ public void setup() {
+ LscConfiguration.reset();
+ LscConfiguration.getInstance();
+ Assert.assertNotNull(LscConfiguration.getConnection("src-ldap"));
+ Assert.assertNotNull(LscConfiguration.getConnection("dst-ldap"));
+ reloadJndiConnections();
+ }
+
+ @Test
+ public final void testLdap2LdapJSONHookSyncTest() throws Exception {
+
+ // Declare the tasks to launch in the correct order
+ List sync_tasks = Arrays.asList("ldap2ldapJSONHookTestCreate", "ldap2ldapJSONHookTestUpdate");
+ List clean_tasks = Arrays.asList("ldap2ldapJSONHookTestDelete");
+
+ // perform the sync
+ launchSyncCleanTask(sync_tasks, false, true, false);
+ launchSyncCleanTask(clean_tasks, false, false, true);
+
+ // check the results of the synchronization
+ reloadJndiConnections();
+
+ ObjectMapper mapperCreatedEntry = new ObjectMapper();
+ JsonNode expectedCreatedEntry = mapperCreatedEntry.readTree("[ { \"attributeName\" : \"objectClass\", \"values\" : [ \"inetOrgPerson\", \"person\", \"top\" ], \"operation\" : \"ADD_VALUES\" }, { \"attributeName\" : \"cn\", \"values\" : [ \"CN0001-hook\" ], \"operation\" : \"ADD_VALUES\" }, { \"attributeName\" : \"sn\", \"values\" : [ \"CN0001-hook\" ], \"operation\" : \"ADD_VALUES\" } ]");
+
+ checkJSONSyncResults("create", expectedCreatedEntry);
+
+ ObjectMapper mapperUpdatedEntry = new ObjectMapper();
+ JsonNode expectedUpdatedEntry = mapperUpdatedEntry.readTree("[ { \"attributeName\" : \"description\", \"values\" : [ \"CN0001-hook\" ], \"operation\" : \"REPLACE_VALUES\" }, {\"attributeName\":\"userCertificate;binary\",\"values\":[\"MIIDkTCCAnmgAwIBAgIUDhx/9qofTrT+yNFFvihdDn7rjOQwDQYJKoZIhvcNAQELBQAwWDELMAkGA1UEBhMCRlIxDTALBgNVBAgMBHRlc3QxDTALBgNVBAcMBHRlc3QxDTALBgNVBAoMBHRlc3QxDTALBgNVBAsMBHRlc3QxDTALBgNVBAMMBHRlc3QwHhcNMjMxMDI3MTQzMDQxWhcNMzMxMDI0MTQzMDQxWjBYMQswCQYDVQQGEwJGUjENMAsGA1UECAwEdGVzdDENMAsGA1UEBwwEdGVzdDENMAsGA1UECgwEdGVzdDENMAsGA1UECwwEdGVzdDENMAsGA1UEAwwEdGVzdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKdWt0QgFnEi7a1hIJQv4ZdOM5y0GGLHQYNrUNSReArvpkYUY5zasFNVzVHCApRuj0t1NMDrn1gNzKkxTIbYGaWRSn+21J0ow+Nxh2TAQW8dkJnWTksCfyGGGItI5q3ST3EUKnepaAzUYYENcSHRyx7UY/3XuzcW0aGhy4PrVTIHBpyLq0Uzv8nH5nbWM+LYt6YbQMmlAz/psTXIC2dfEZhUb4plLGSo7rZxM5geC6Z+os+I8+uw+mGjps1VP7eGq0jCGHNs2rUHMqBNgLvwMH2WlMXo/iNarAb8fUEPdp59FwiTygBlWAn6GoKHJ1HWPpqMxdtjL2Y5+ZMcp70eJqcCAwEAAaNTMFEwHQYDVR0OBBYEFLwffjUBL/Rp4a6MgeCJiFnCZFu8MB8GA1UdIwQYMBaAFLwffjUBL/Rp4a6MgeCJiFnCZFu8MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBACjwsg1Z9PyauoKAhkIfyPTEnlEOCo1nN37c2vnH4fyY6fuBdV6GWtk/u9FCuDmYT/4KDRxe33ZUChwSUX0INgamOarWRES3UoPC1GeOvuMf7uustEMLcHAYZVKXSZUrsOjw+VIZ5XrD6GDE64QtvW5Ve3jf43aGgLf27NF0vhF9+gHOZjjBT33S977HUutMUKfRu9PdHAn8Yb1FmSbAvqqK+SAjn6cJC8l5yS5t0BSNQGbKSA8bPzvWI9HXYVvb+ym6GDrsr+Zad3NrqUSZGzS2JFEDVD9aAikldXu6g02fA5A7nufVePmaG7iTyylO/ZU2lTiJ0SHc2DnO0pg2i+0=\"],\"operation\":\"REPLACE_VALUES\"} ]");
+
+ checkJSONSyncResults("update", expectedUpdatedEntry);
+
+ checkJSONSyncResults("delete", null);
+ }
+
+ @Test
+ public final void testLdap2LdapLDIFHookSyncTest() throws Exception {
+
+ // Declare the tasks to launch in the correct order
+ List sync_tasks = Arrays.asList("ldap2ldapLDIFHookTestCreate", "ldap2ldapLDIFHookTestUpdate");
+ List clean_tasks = Arrays.asList("ldap2ldapLDIFHookTestDelete");
+
+ // perform the sync
+ launchSyncCleanTask(sync_tasks, false, true, false);
+ launchSyncCleanTask(clean_tasks, false, false, true);
+
+ // check the results of the synchronization
+ reloadJndiConnections();
+
+ List expectedCreatedEntry = Arrays.asList(
+ "dn: cn=CN0001-hook,ou=ldap2ldap2TestTaskDst,ou=Test Data,dc=lsc-project,dc=org",
+ "changetype: add",
+ "objectClass: inetOrgPerson",
+ "objectClass: person",
+ "objectClass: top",
+ "cn: CN0001-hook",
+ "sn: CN0001-hook"
+ );
+
+ checkLDIFSyncResults("create", expectedCreatedEntry);
+
+ List expectedUpdatedEntry = Arrays.asList(
+ "dn: cn=CN0001-hook,ou=ldap2ldap2TestTaskDst,ou=Test Data,dc=lsc-project,dc=org",
+ "changetype: modify",
+ "replace: description",
+ "description: CN0001-hook"
+ );
+
+ checkLDIFSyncResults("update", expectedUpdatedEntry);
+
+ checkLDIFSyncResults("delete", new ArrayList());
+ }
+
+ /*
+ * Read hook log file to check passed arguments and modification passed as input
+ */
+ private final void checkLDIFSyncResults(String operation, List expectedEntry) throws Exception {
+
+ List hookResults = new ArrayList();
+ try {
+ File hookFile = new File("hook-ldif-" + operation + ".log");
+ Scanner hookReader = new Scanner(hookFile);
+
+ while (hookReader.hasNextLine()) {
+ String data = hookReader.nextLine();
+ hookResults.add(data);
+ }
+ hookReader.close();
+ hookFile.delete(); // remove hook log
+ } catch (Exception e) {
+ fail("Error while reading hook-ldif-" + operation + ".log");
+ }
+ assertEquals(hookResults.get(0), "cn=CN0001-hook,ou=ldap2ldap2TestTaskDst,ou=Test Data,dc=lsc-project,dc=org");
+ assertEquals(hookResults.get(1), operation);
+
+ if(operation != "delete") {
+ // Make sure all attributes in expectedEntry are present in the hook file
+ List entry = new ArrayList(hookResults.subList(3, (hookResults.size()-1)));
+ for (String attr : expectedEntry) {
+ assertTrue("Attribute " + attr + " not found in " + operation + " entry " + entry.toString(), entry.contains(attr));
+ }
+ }
+
+ }
+
+ /*
+ * Read hook log file to check passed arguments and modification passed as input
+ */
+ private final void checkJSONSyncResults(String operation, JsonNode expectedEntry) throws Exception {
+
+ List hookResults = new ArrayList();
+ try {
+ File hookFile = new File("hook-json-" + operation + ".log");
+ Scanner hookReader = new Scanner(hookFile);
+
+ while (hookReader.hasNextLine()) {
+ String data = hookReader.nextLine();
+ hookResults.add(data);
+ }
+ hookReader.close();
+ hookFile.delete(); // remove hook log
+ } catch (Exception e) {
+ fail("Error while reading hook-json-" + operation + ".log");
+ }
+ assertEquals(hookResults.get(0), "cn=CN0001-hook,ou=ldap2ldap2TestTaskDst,ou=Test Data,dc=lsc-project,dc=org");
+ assertEquals(hookResults.get(1), operation);
+
+ if(operation != "delete") {
+ // Make sure all attributes in expectedEntry are present in the hook file
+ String entry = String.join("", new ArrayList(hookResults.subList(2, hookResults.size())));
+
+ ObjectMapper mapper = new ObjectMapper();
+ JsonFactory factory = mapper.getJsonFactory();
+ JsonParser jp = factory.createJsonParser(entry);
+ try {
+ JsonNode hookOperation = mapper.readTree(jp);
+ assertEquals(hookOperation, expectedEntry);
+ }
+ catch (Exception e) {
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+ e.printStackTrace(pw);
+ fail("Error while decoding operation in hook-json-" + operation + ".log as JSON:" + sw.toString());
+ }
+ }
+
+ }
+
+ public static void launchSyncCleanTask(List tasks, boolean doAsync, boolean doSync,
+ boolean doClean) throws Exception {
+ // initialize required stuff
+ SimpleSynchronize sync = new SimpleSynchronize();
+ List asyncType = new ArrayList();
+ List syncType = new ArrayList();
+ List cleanType = new ArrayList();
+
+
+ if (doAsync) {
+ for(String taskName: tasks) {
+ asyncType.add(taskName);
+ }
+ }
+
+ if (doSync) {
+ for(String taskName: tasks) {
+ syncType.add(taskName);
+ }
+ }
+
+ if (doClean) {
+ for(String taskName: tasks) {
+ cleanType.add(taskName);
+ }
+ }
+
+ boolean ret = sync.launch(asyncType, syncType, cleanType);
+ assertTrue(ret);
+ }
+}
diff --git a/src/test/resources/bin/hook-json.sh b/src/test/resources/bin/hook-json.sh
new file mode 100755
index 00000000..42f73ee8
--- /dev/null
+++ b/src/test/resources/bin/hook-json.sh
@@ -0,0 +1,13 @@
+#!/bin/bash
+
+# Parse arguments
+for (( i=1; i <= $#; i++ )); do
+ eval arg='$'$i
+ echo $arg >> hook-json-$2.log
+done
+
+# Parse input
+while IFS= read line; do
+echo "$line" >> hook-json-$2.log
+done
+
diff --git a/src/test/resources/bin/hook-ldif.sh b/src/test/resources/bin/hook-ldif.sh
new file mode 100755
index 00000000..b57873d5
--- /dev/null
+++ b/src/test/resources/bin/hook-ldif.sh
@@ -0,0 +1,13 @@
+#!/bin/bash
+
+# Parse arguments
+for (( i=1; i <= $#; i++ )); do
+ eval arg='$'$i
+ echo $arg >> hook-ldif-$2.log
+done
+
+# Parse input
+while IFS= read line; do
+echo "$line" >> hook-ldif-$2.log
+done
+
diff --git a/src/test/resources/etc/lsc.xml b/src/test/resources/etc/lsc.xml
index 43230b9e..8073fd36 100644
--- a/src/test/resources/etc/lsc.xml
+++ b/src/test/resources/etc/lsc.xml
@@ -855,6 +855,375 @@
+
+ ldap2ldapLDIFHookTestCreate
+ org.lsc.beans.SimpleBean
+
+ ldap2ldapLDIFHookTestCreate-src
+
+ ou=ldap2ldap2TestTaskDst,ou=Test Data,dc=lsc-project,dc=org
+
+ cn
+
+
+ cn
+
+ (cn=CN0001)
+ (cn=CN0001)
+ (cn=CN0001)
+ yyyyMMddHHmmss'Z'
+ 5
+
+
+ ldap2ldapLDIFHookTestCreate-dst
+
+ ou=ldap2ldap2TestTaskDst,ou=Test Data,dc=lsc-project,dc=org
+
+ cn
+
+
+ cn
+ sn
+ objectClass
+
+ (cn=CN0001-hook)
+ (cn=CN0001-hook)
+
+
+ "cn=CN0001-hook,ou=ldap2ldap2TestTaskDst,ou=Test Data,dc=lsc-project,dc=org"
+ ,
+ KEEP
+
+ ldif
+ bin/hook-ldif.sh
+
+
+ objectclass
+ FORCE
+
+ "inetOrgPerson"
+ "person"
+ "top"
+
+
+
+ cn
+ FORCE
+
+ "CN0001-hook"
+
+
+
+ sn
+ FORCE
+
+ "CN0001-hook"
+
+
+
+
+
+ ldap2ldapLDIFHookTestUpdate
+ org.lsc.beans.SimpleBean
+
+ ldap2ldapLDIFHookTestUpdate-src
+
+ ou=ldap2ldap2TestTaskDst,ou=Test Data,dc=lsc-project,dc=org
+
+ cn
+
+
+ cn
+
+ (cn=CN0001)
+ (cn=CN0001)
+ (cn=CN0001)
+ yyyyMMddHHmmss'Z'
+ 5
+
+
+ ldap2ldapLDIFHookTestUpdate-dst
+
+ ou=ldap2ldap2TestTaskDst,ou=Test Data,dc=lsc-project,dc=org
+
+ cn
+
+
+ description
+
+ (cn=CN0001-hook)
+ (cn=CN0001-hook)
+
+
+ "cn=CN0001-hook,ou=ldap2ldap2TestTaskDst,ou=Test Data,dc=lsc-project,dc=org"
+ ,
+ KEEP
+
+ ldif
+ bin/hook-ldif.sh
+
+
+ description
+ FORCE
+
+ "CN0001-hook"
+
+
+
+
+
+ ldap2ldapLDIFHookTestDelete
+ org.lsc.beans.SimpleBean
+
+ ldap2ldapLDIFHookTestDelete-src
+
+ ou=ldap2ldap2TestTaskDst,ou=Test Data,dc=lsc-project,dc=org
+
+ cn
+
+
+ cn
+
+ (cn=7SlBBCtxp0ILsbBEp_DeUjS69rpgpWIbOfhXLGvP2HvGY_g_XecLW9J8ymR5b7qN)
+ (cn=7SlBBCtxp0ILsbBEp_DeUjS69rpgpWIbOfhXLGvP2HvGY_g_XecLW9J8ymR5b7qN)
+ (cn=7SlBBCtxp0ILsbBEp_DeUjS69rpgpWIbOfhXLGvP2HvGY_g_XecLW9J8ymR5b7qN)
+ yyyyMMddHHmmss'Z'
+ 5
+
+
+ ldap2ldapLDIFHookTestDelete-dst
+
+ ou=ldap2ldap2TestTaskDst,ou=Test Data,dc=lsc-project,dc=org
+
+ cn
+
+
+ cn
+ sn
+ description
+ objectClass
+
+ (cn=CN0001-hook)
+ (cn=CN0001-hook)
+
+
+ "cn=CN0001-hook,ou=ldap2ldap2TestTaskDst,ou=Test Data,dc=lsc-project,dc=org"
+ ,
+ KEEP
+
+ ldif
+ bin/hook-ldif.sh
+
+
+ objectclass
+ FORCE
+
+ "inetOrgPerson"
+ "person"
+ "top"
+
+
+
+ cn
+ FORCE
+
+ "CN0001-hook"
+
+
+
+
+
+ ldap2ldapJSONHookTestCreate
+ org.lsc.beans.SimpleBean
+
+ ldap2ldapJSONHookTestCreate-src
+
+ ou=ldap2ldap2TestTaskDst,ou=Test Data,dc=lsc-project,dc=org
+
+ cn
+
+
+ cn
+
+ (cn=CN0001)
+ (cn=CN0001)
+ (cn=CN0001)
+ yyyyMMddHHmmss'Z'
+ 5
+
+
+ ldap2ldapJSONHookTestCreate-dst
+
+ ou=ldap2ldap2TestTaskDst,ou=Test Data,dc=lsc-project,dc=org
+
+ cn
+
+
+ cn
+ sn
+ objectClass
+
+ (cn=CN0001-hook)
+ (cn=CN0001-hook)
+
+
+ "cn=CN0001-hook,ou=ldap2ldap2TestTaskDst,ou=Test Data,dc=lsc-project,dc=org"
+ ,
+ KEEP
+
+ json
+ bin/hook-json.sh
+
+
+ objectclass
+ FORCE
+
+ "inetOrgPerson"
+ "person"
+ "top"
+
+
+
+ cn
+ FORCE
+
+ "CN0001-hook"
+
+
+
+ sn
+ FORCE
+
+ "CN0001-hook"
+
+
+
+
+
+ ldap2ldapJSONHookTestUpdate
+ org.lsc.beans.SimpleBean
+
+ ldap2ldapJSONHookTestUpdate-src
+
+ ou=ldap2ldap2TestTaskDst,ou=Test Data,dc=lsc-project,dc=org
+
+ cn
+
+
+ cn
+
+ (cn=CN0001)
+ (cn=CN0001)
+ (cn=CN0001)
+ yyyyMMddHHmmss'Z'
+ 5
+
+
+ ldap2ldapJSONHookTestUpdate-dst
+
+ ou=ldap2ldap2TestTaskDst,ou=Test Data,dc=lsc-project,dc=org
+
+ cn
+
+
+ description
+ userCertificate;binary
+
+ (cn=CN0001-hook)
+ (cn=CN0001-hook)
+
+
+ "cn=CN0001-hook,ou=ldap2ldap2TestTaskDst,ou=Test Data,dc=lsc-project,dc=org"
+ ,
+ KEEP
+
+ json
+ bin/hook-json.sh
+
+
+ description
+ FORCE
+
+ "CN0001-hook"
+
+
+
+ userCertificate;binary
+ FORCE
+
+
+
+
+
+
+
+
+
+ ldap2ldapJSONHookTestDelete
+ org.lsc.beans.SimpleBean
+
+ ldap2ldapJSONHookTestDelete-src
+
+ ou=ldap2ldap2TestTaskDst,ou=Test Data,dc=lsc-project,dc=org
+
+ cn
+
+
+ cn
+
+ (cn=7SlBBCtxp0ILsbBEp_DeUjS69rpgpWIbOfhXLGvP2HvGY_g_XecLW9J8ymR5b7qN)
+ (cn=7SlBBCtxp0ILsbBEp_DeUjS69rpgpWIbOfhXLGvP2HvGY_g_XecLW9J8ymR5b7qN)
+ (cn=7SlBBCtxp0ILsbBEp_DeUjS69rpgpWIbOfhXLGvP2HvGY_g_XecLW9J8ymR5b7qN)
+ yyyyMMddHHmmss'Z'
+ 5
+
+
+ ldap2ldapJSONHookTestDelete-dst
+
+ ou=ldap2ldap2TestTaskDst,ou=Test Data,dc=lsc-project,dc=org
+
+ cn
+
+
+ cn
+ sn
+ description
+ objectClass
+
+ (cn=CN0001-hook)
+ (cn=CN0001-hook)
+
+
+ "cn=CN0001-hook,ou=ldap2ldap2TestTaskDst,ou=Test Data,dc=lsc-project,dc=org"
+ ,
+ KEEP
+
+ json
+ bin/hook-json.sh
+
+
+ objectclass
+ FORCE
+
+ "inetOrgPerson"
+ "person"
+ "top"
+
+
+
+ cn
+ FORCE
+
+ "CN0001-hook"
+
+
+
+