diff --git a/src/main/java/org/lsc/Hooks.java b/src/main/java/org/lsc/Hooks.java index 8d813f62..16794985 100644 --- a/src/main/java/org/lsc/Hooks.java +++ b/src/main/java/org/lsc/Hooks.java @@ -44,14 +44,15 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.lang.ProcessBuilder; +import java.io.OutputStream; import java.io.IOException; import java.io.StringWriter; import java.io.PrintWriter; +import org.lsc.utils.output.LdifLayout; import com.fasterxml.jackson.databind.ObjectMapper; // For encoding object to JSON import com.fasterxml.jackson.databind.ObjectWriter; - /** * This object is managing posthook scripts */ @@ -63,31 +64,56 @@ public class Hooks { * * return nothing */ - public final static void postSyncHook(final String hook, final LscModifications lm) { + public final static void postSyncHook(final String hook, final String outputFormat, final LscModifications lm) { if( hook != null && ! hook.equals("") ) { - // Compute json modifications - String jsonModifications = null; + + String format = ""; + if( outputFormat.equals("json") ) { + format = "json"; + } + else + { + format = "ldif"; + } + + // Compute json/ldif modifications + String modifications = null; switch (lm.getOperation()) { case CREATE_OBJECT: - jsonModifications = getJsonModifications(lm); - callHook("create", hook, lm.getMainIdentifier(), jsonModifications); + if( format.equals("json") ) { + modifications = getJsonModifications(lm); + } + else { + modifications = LdifLayout.format(lm); + } + callHook("create", hook, lm.getMainIdentifier(), format, modifications); break; case UPDATE_OBJECT: - jsonModifications = getJsonModifications(lm); - callHook("update", hook, lm.getMainIdentifier(), jsonModifications); + if( format.equals("json") ) { + modifications = getJsonModifications(lm); + } + else { + modifications = LdifLayout.format(lm); + } + callHook("update", hook, lm.getMainIdentifier(), format, modifications); break; case CHANGE_ID: - jsonModifications = getJsonModifications(lm); - callHook("changeId", hook, lm.getMainIdentifier(), jsonModifications); + if( format.equals("json") ) { + modifications = getJsonModifications(lm); + } + else { + modifications = LdifLayout.format(lm); + } + callHook("changeId", hook, lm.getMainIdentifier(), format, modifications); break; case DELETE_OBJECT: - callHook("delete", hook, lm.getMainIdentifier(), jsonModifications); + callHook("delete", hook, lm.getMainIdentifier(), format, modifications); break; default: @@ -96,20 +122,32 @@ public final static void postSyncHook(final String hook, final LscModifications } } + /** + * Method calling the hook + * + * return nothing + */ public final static void callHook( String operationType, String hook, String identifier, - String jsonModifications) { + String format, + String modifications) { - LOGGER.info("Calling {} posthook {} for {}", operationType, hook, identifier); + LOGGER.info("Calling {} posthook {} with format {} for {}", operationType, hook, format, identifier); try { - if( jsonModifications != null ) { + if( modifications != null ) { Process p = new ProcessBuilder( hook, identifier, - operationType, - jsonModifications) + operationType) .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( @@ -123,16 +161,16 @@ public final static void callHook( String operationType, StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); e.printStackTrace(pw); - LOGGER.error("Error while calling {} posthook {} for {}: {}", - operationType,hook, identifier, sw.toString()); + LOGGER.error("Error while calling {} posthook {} with format {} for {}: {}", + operationType, hook, format, identifier, sw.toString()); } } /** - * Method computing modifications as json - * - * @return modifications in a json String - */ + * Method computing modifications as json + * + * @return modifications in a json String + */ public final static String getJsonModifications(final LscModifications lm) { ObjectWriter ow = new ObjectMapper().writer().withDefaultPrettyPrinter(); String json = ""; diff --git a/src/main/java/org/lsc/beans/syncoptions/ForceSyncOptions.java b/src/main/java/org/lsc/beans/syncoptions/ForceSyncOptions.java index 8157887e..e6c7d82c 100644 --- a/src/main/java/org/lsc/beans/syncoptions/ForceSyncOptions.java +++ b/src/main/java/org/lsc/beans/syncoptions/ForceSyncOptions.java @@ -128,6 +128,10 @@ public String getCondition(LscModificationType operation) { return DEFAULT_CONDITION; } + public String getPostHookOutputFormat() { + return ""; + } + public String getCreatePostHook() { return ""; } diff --git a/src/main/java/org/lsc/beans/syncoptions/ISyncOptions.java b/src/main/java/org/lsc/beans/syncoptions/ISyncOptions.java index d60bfee5..76814359 100644 --- a/src/main/java/org/lsc/beans/syncoptions/ISyncOptions.java +++ b/src/main/java/org/lsc/beans/syncoptions/ISyncOptions.java @@ -153,6 +153,13 @@ public interface ISyncOptions { String getCondition(LscModificationType operation); + /** + * Returns the posthook output format + * + * @return the posthook output format or "" if none is specified (default) + */ + String getPostHookOutputFormat(); + /** * Returns the posthook for a creation * diff --git a/src/main/java/org/lsc/beans/syncoptions/PropertiesBasedSyncOptions.java b/src/main/java/org/lsc/beans/syncoptions/PropertiesBasedSyncOptions.java index deff1099..ba10878e 100644 --- a/src/main/java/org/lsc/beans/syncoptions/PropertiesBasedSyncOptions.java +++ b/src/main/java/org/lsc/beans/syncoptions/PropertiesBasedSyncOptions.java @@ -197,6 +197,13 @@ public String getCondition(LscModificationType operation) { return result; } + public String getPostHookOutputFormat() { + if (conf.getHooks() == null || conf.getHooks().getOutputFormat() == null) { + return ""; + } + return conf.getHooks().getOutputFormat(); + } + public String getCreatePostHook() { if (conf.getHooks() == null || conf.getHooks().getCreatePostHook() == null) { return ""; diff --git a/src/main/java/org/lsc/runnable/CleanEntryRunner.java b/src/main/java/org/lsc/runnable/CleanEntryRunner.java index 89c1190f..61563291 100644 --- a/src/main/java/org/lsc/runnable/CleanEntryRunner.java +++ b/src/main/java/org/lsc/runnable/CleanEntryRunner.java @@ -111,7 +111,8 @@ public void run() { if (task.getDestinationService().apply(lm)) { // Retrieve posthook for the current operation String hook = syncOptions.getDeletePostHook(); - Hooks.postSyncHook(hook, lm); + String outputFormat = syncOptions.getPostHookOutputFormat(); + Hooks.postSyncHook(hook, outputFormat, 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 474fdc00..a07457bb 100644 --- a/src/main/java/org/lsc/runnable/SynchronizeEntryRunner.java +++ b/src/main/java/org/lsc/runnable/SynchronizeEntryRunner.java @@ -140,7 +140,8 @@ public boolean run(IBean entry) { if (task.getDestinationService().apply(lm)) { // Retrieve posthook for the current operation String hook = task.getSyncOptions().getPostHook(modificationType); - Hooks.postSyncHook(hook, lm); + String outputFormat = task.getSyncOptions().getPostHookOutputFormat(); + Hooks.postSyncHook(hook, outputFormat, lm); counter.incrementCountCompleted(); abstractSynchronize.logAction(lm, id, syncName); return true; diff --git a/src/main/resources/schemas/lsc-core-2.2.xsd b/src/main/resources/schemas/lsc-core-2.2.xsd index 8f416e87..5aaf4e55 100644 --- a/src/main/resources/schemas/lsc-core-2.2.xsd +++ b/src/main/resources/schemas/lsc-core-2.2.xsd @@ -326,6 +326,7 @@ + diff --git a/src/test/java/org/lsc/Ldap2LdapHookSyncTest.java b/src/test/java/org/lsc/Ldap2LdapHookSyncTest.java index e782f972..c58d2099 100644 --- a/src/test/java/org/lsc/Ldap2LdapHookSyncTest.java +++ b/src/test/java/org/lsc/Ldap2LdapHookSyncTest.java @@ -45,6 +45,15 @@ */ 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; @@ -55,7 +64,6 @@ 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; @@ -81,11 +89,38 @@ public void setup() { } @Test - public final void testLdap2LdapHookSyncTest() throws Exception { + 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\" } ]"); + + 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("ldap2ldapHookTestCreate", "ldap2ldapHookTestUpdate"); - List clean_tasks = Arrays.asList("ldap2ldapHookTestDelete"); + List sync_tasks = Arrays.asList("ldap2ldapLDIFHookTestCreate", "ldap2ldapLDIFHookTestUpdate"); + List clean_tasks = Arrays.asList("ldap2ldapLDIFHookTestDelete"); // perform the sync launchSyncCleanTask(sync_tasks, false, true, false); @@ -93,17 +128,39 @@ public final void testLdap2LdapHookSyncTest() throws Exception { // check the results of the synchronization reloadJndiConnections(); - checkSyncResults(); + + 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 + * Read hook log file to check passed arguments and modification passed as input */ - private final void checkSyncResults() throws Exception { + private final void checkLDIFSyncResults(String operation, List expectedEntry) throws Exception { List hookResults = new ArrayList(); try { - File hookFile = new File("hook.log"); + File hookFile = new File("hook-ldif-" + operation + ".log"); Scanner hookReader = new Scanner(hookFile); while (hookReader.hasNextLine()) { @@ -113,16 +170,61 @@ private final void checkSyncResults() throws Exception { hookReader.close(); hookFile.delete(); // remove hook log } catch (Exception e) { - fail("Error while reading hook.log"); + 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), "create"); - assertTrue("Hook logs: ADD_VALUES not found in created entry", hookResults.get(2).matches("^.*ADD_VALUES.*$")); - assertEquals(hookResults.get(3), "cn=CN0001-hook,ou=ldap2ldap2TestTaskDst,ou=Test Data,dc=lsc-project,dc=org"); - assertEquals(hookResults.get(4), "update"); - assertTrue("Hook logs: REPLACE_VALUES not found in updated entry", hookResults.get(5).matches("^.*REPLACE_VALUES.*$")); - assertEquals(hookResults.get(6), "cn=CN0001-hook,ou=ldap2ldap2TestTaskDst,ou=Test Data,dc=lsc-project,dc=org"); - assertEquals(hookResults.get(7), "delete"); + 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()); + } + } } 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/bin/hook.sh b/src/test/resources/bin/hook.sh deleted file mode 100755 index fba65fde..00000000 --- a/src/test/resources/bin/hook.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash -for (( i=1; i <= $#; i++ )); do - eval arg='$'$i - echo $arg >> hook.log -done diff --git a/src/test/resources/etc/lsc.xml b/src/test/resources/etc/lsc.xml index 9c252908..0393a467 100644 --- a/src/test/resources/etc/lsc.xml +++ b/src/test/resources/etc/lsc.xml @@ -856,10 +856,10 @@ - ldap2ldapHookTestCreate + ldap2ldapLDIFHookTestCreate org.lsc.beans.SimpleBean - ldap2ldapHookTestCreate-src + ldap2ldapLDIFHookTestCreate-src ou=ldap2ldap2TestTaskDst,ou=Test Data,dc=lsc-project,dc=org @@ -875,7 +875,7 @@ 5 - ldap2ldapHookTestCreate-dst + ldap2ldapLDIFHookTestCreate-dst ou=ldap2ldap2TestTaskDst,ou=Test Data,dc=lsc-project,dc=org @@ -894,7 +894,8 @@ , KEEP - bin/hook.sh + ldif + bin/hook-ldif.sh objectclass @@ -922,10 +923,10 @@ - ldap2ldapHookTestUpdate + ldap2ldapLDIFHookTestUpdate org.lsc.beans.SimpleBean - ldap2ldapHookTestUpdate-src + ldap2ldapLDIFHookTestUpdate-src ou=ldap2ldap2TestTaskDst,ou=Test Data,dc=lsc-project,dc=org @@ -941,7 +942,7 @@ 5 - ldap2ldapHookTestUpdate-dst + ldap2ldapLDIFHookTestUpdate-dst ou=ldap2ldap2TestTaskDst,ou=Test Data,dc=lsc-project,dc=org @@ -958,7 +959,8 @@ , KEEP - bin/hook.sh + ldif + bin/hook-ldif.sh description @@ -970,10 +972,10 @@ - ldap2ldapHookTestDelete + ldap2ldapLDIFHookTestDelete org.lsc.beans.SimpleBean - ldap2ldapHookTestDelete-src + ldap2ldapLDIFHookTestDelete-src ou=ldap2ldap2TestTaskDst,ou=Test Data,dc=lsc-project,dc=org @@ -989,7 +991,7 @@ 5 - ldap2ldapHookTestDelete-dst + ldap2ldapLDIFHookTestDelete-dst ou=ldap2ldap2TestTaskDst,ou=Test Data,dc=lsc-project,dc=org @@ -1009,7 +1011,185 @@ , KEEP - bin/hook.sh + 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 + + (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" + + + + + + 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