From 886a64b8c1ddc854958bbac92cc9b76d91d3fe26 Mon Sep 17 00:00:00 2001
From: Matthew Swift <matthew.swift@forgerock.com>
Date: Mon, 05 Dec 2011 17:54:22 +0000
Subject: [PATCH] OPENDJ-370: Implement an example Sun DSEE -> OpenDJ change log based synchronization daemon.

---
 opendj3/opendj-ldap-sync/src/main/assembly/libbat/_script-util.bat                                |  201 +++
 opendj3/opendj-ldap-sync/src/main/java/org/forgerock/opendj/sync/filters/Matchers.java            |  910 +++++++++++++++++
 opendj3/opendj-ldap-sync/src/main/java/org/forgerock/opendj/sync/filters/FilterResult.java        |  267 +++++
 opendj3/opendj-ldap-sync/src/main/java/org/forgerock/opendj/sync/Main.java                        |   54 +
 opendj3/opendj-ldap-sync/src/main/java/org/forgerock/opendj/sync/filters/package-info.java        |   34 
 opendj3/opendj-ldap-sync/pom.xml                                                                  |  127 ++
 opendj3/opendj-ldap-sync/src/main/java/org/forgerock/opendj/sync/filters/Transforms.java          |  657 ++++++++++++
 opendj3/opendj-ldap-sync/src/main/assembly/libbat/_client-script.bat                              |   53 +
 opendj3/pom.xml                                                                                   |    1 
 opendj3/opendj-ldap-sync/src/main/java/org/forgerock/opendj/sync/filters/Filter.java              |  107 ++
 opendj3/opendj-ldap-sync/src/main/assembly/libbat/setcp.bat                                       |   40 
 opendj3/opendj-ldap-sync/src/main/assembly/descriptor.xml                                         |   99 +
 opendj3/opendj-ldap-sync/README                                                                   |   14 
 opendj3/opendj-ldap-sync/src/main/assembly/bat/ldapsync.bat                                       |   33 
 opendj3/opendj-ldap-sync/src/main/assembly/bin/ldapsync                                           |   37 
 opendj3/opendj-ldap-sync/src/main/assembly/libbin/_client-script.sh                               |   65 +
 opendj3/opendj-ldap-sync/src/main/assembly/libbin/_script-util.sh                                 |  130 ++
 opendj3/opendj-ldap-sync/src/main/java/org/forgerock/opendj/sync/filters/ChangeRecordContext.java |  138 ++
 opendj3/opendj-ldap-sync/src/main/java/org/forgerock/opendj/sync/package-info.java                |   37 
 19 files changed, 3,004 insertions(+), 0 deletions(-)

diff --git a/opendj3/opendj-ldap-sync/README b/opendj3/opendj-ldap-sync/README
new file mode 100644
index 0000000..c3a63a1
--- /dev/null
+++ b/opendj3/opendj-ldap-sync/README
@@ -0,0 +1,14 @@
+OpenDJ LDAP synchronization tool.
+
+This Maven project contains the OpenDJ LDAP synchronization daemon. It is 100%
+Java based and requires Java 1.6. 
+
+OpenDJ is a downstream build of the OpenDS project, with a different name
+to avoid trademark issues.
+
+Complete documentation for this product may be found online
+at http://www.forgerock.com/opendj.html.
+
+This product is made available under the Common Development and Distribution
+License (CDDL).  The complete text for this license, and for alternate licenses
+of included components, may be found in the legal-notices directory.
diff --git a/opendj3/opendj-ldap-sync/pom.xml b/opendj3/opendj-ldap-sync/pom.xml
new file mode 100644
index 0000000..2065144
--- /dev/null
+++ b/opendj3/opendj-ldap-sync/pom.xml
@@ -0,0 +1,127 @@
+<?xml version="1.0"?>
+<!--
+ ! CDDL HEADER START
+ !
+ ! The contents of this file are subject to the terms of the
+ ! Common Development and Distribution License, Version 1.0 only
+ ! (the "License").  You may not use this file except in compliance
+ ! with the License.
+ !
+ ! You can obtain a copy of the license at
+ ! trunk/opendj3/legal-notices/CDDLv1_0.txt
+ ! or http://forgerock.org/license/CDDLv1.0.html.
+ ! See the License for the specific language governing permissions
+ ! and limitations under the License.
+ !
+ ! When distributing Covered Code, include this CDDL HEADER in each
+ ! file and include the License file at
+ ! trunk/opendj3/legal-notices/CDDLv1_0.txt.  If applicable,
+ ! add the following below this CDDL HEADER, with the fields enclosed
+ ! by brackets "[]" replaced with your own identifying information:
+ !      Portions Copyright [yyyy] [name of copyright owner]
+ !
+ ! CDDL HEADER END
+ !
+ !      Copyright 2011 ForgeRock AS
+ !    
+ -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <artifactId>opendj-project</artifactId>
+    <groupId>org.forgerock.opendj</groupId>
+    <version>3.0.0-SNAPSHOT</version>
+  </parent>
+  <artifactId>opendj-ldap-sync</artifactId>
+  <name>OpenDJ LDAP Synchronization Daemon</name>
+  <description>
+    This module includes an LDAP synchronization daemon based on the OpenDJ
+    LDAP SDK.
+  </description>
+  <packaging>jar</packaging>
+  <dependencies>
+    <dependency>
+      <groupId>org.forgerock.opendj</groupId>
+      <artifactId>opendj-ldap-sdk</artifactId>
+      <version>${project.version}</version>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.forgerock.opendj</groupId>
+      <artifactId>opendj-build-tools</artifactId>
+      <version>${project.version}</version>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+  <build>
+    <plugins>
+      <!--
+      <plugin>
+        <groupId>org.forgerock.commons</groupId>
+        <artifactId>i18n-maven-plugin</artifactId>
+        <executions>
+          <execution>
+            <phase>generate-sources</phase>
+            <goals>
+              <goal>generate-messages</goal>
+            </goals>
+            <configuration>
+              <messageFiles>
+                <messageFile>com/forgerock/opendj/sync/sync.properties</messageFile>
+              </messageFiles>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+      -->
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-surefire-plugin</artifactId>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-checkstyle-plugin</artifactId>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-assembly-plugin</artifactId>
+        <executions>
+          <execution>
+            <id>make-assembly</id>
+            <phase>package</phase>
+            <goals>
+              <goal>single</goal>
+            </goals>
+            <configuration>
+              <appendAssemblyId>false</appendAssemblyId>
+              <descriptors>
+                <descriptor>src/main/assembly/descriptor.xml</descriptor>
+              </descriptors>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+  </build>
+  <reporting>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-project-info-reports-plugin</artifactId>
+        <reportSets>
+          <reportSet>
+            <reports>
+              <report>mailing-list</report>
+              <report>issue-tracking</report>
+              <report>license</report>
+              <report>scm</report>
+              <report>cim</report>
+              <report>distribution-management</report>
+            </reports>
+          </reportSet>
+        </reportSets>
+      </plugin>
+    </plugins>
+  </reporting>
+</project>
diff --git a/opendj3/opendj-ldap-sync/src/main/assembly/bat/ldapsync.bat b/opendj3/opendj-ldap-sync/src/main/assembly/bat/ldapsync.bat
new file mode 100755
index 0000000..0a00d8c
--- /dev/null
+++ b/opendj3/opendj-ldap-sync/src/main/assembly/bat/ldapsync.bat
@@ -0,0 +1,33 @@
+
+@echo off
+rem CDDL HEADER START
+rem
+rem The contents of this file are subject to the terms of the
+rem Common Development and Distribution License, Version 1.0 only
+rem (the "License").  You may not use this file except in compliance
+rem with the License.
+rem
+rem You can obtain a copy of the license at
+rem trunk/opendj3/legal-notices/CDDLv1_0.txt
+rem or http://forgerock.org/license/CDDLv1.0.html.
+rem See the License for the specific language governing permissions
+rem and limitations under the License.
+rem
+rem When distributing Covered Code, include this CDDL HEADER in each
+rem file and include the License file at
+rem trunk/opendj3/legal-notices/CDDLv1_0.txt.  If applicable,
+rem add the following below this CDDL HEADER, with the fields enclosed
+rem by brackets "[]" replaced with your own identifying information:
+rem      Portions Copyright [yyyy] [name of copyright owner]
+rem
+rem CDDL HEADER END
+rem
+rem
+rem      Copyright 2011 ForgeRock AS.
+
+setlocal
+
+set OPENDJ_INVOKE_CLASS="com.forgerock.opendj.sync.Main"
+set SCRIPT_NAME=ldapsync
+for %%i in (%~sf0) do call "%%~dPsi\..\lib\_client-script.bat" %*
+
diff --git a/opendj3/opendj-ldap-sync/src/main/assembly/bin/ldapsync b/opendj3/opendj-ldap-sync/src/main/assembly/bin/ldapsync
new file mode 100644
index 0000000..86decf3
--- /dev/null
+++ b/opendj3/opendj-ldap-sync/src/main/assembly/bin/ldapsync
@@ -0,0 +1,37 @@
+#!/bin/sh
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License, Version 1.0 only
+# (the "License").  You may not use this file except in compliance
+# with the License.
+#
+# You can obtain a copy of the license at
+# trunk/opendj3/legal-notices/CDDLv1_0.txt
+# or http://forgerock.org/license/CDDLv1.0.html.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at
+# trunk/opendj3/legal-notices/CDDLv1_0.txt.  If applicable,
+# add the following below this CDDL HEADER, with the fields enclosed
+# by brackets "[]" replaced with your own identifying information:
+#      Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+#
+#      Copyright 2011 ForgeRock AS.
+
+
+# This script may be used to perform LDAP search operations.
+OPENDJ_INVOKE_CLASS="com.forgerock.opendj.sync.Main"
+export OPENDJ_INVOKE_CLASS
+
+SCRIPT_NAME="ldapsync"
+export SCRIPT_NAME
+
+SCRIPT_DIR=`dirname "${0}"`
+"${SCRIPT_DIR}/../lib/_client-script.sh" "${@}"
diff --git a/opendj3/opendj-ldap-sync/src/main/assembly/descriptor.xml b/opendj3/opendj-ldap-sync/src/main/assembly/descriptor.xml
new file mode 100644
index 0000000..638c4eb
--- /dev/null
+++ b/opendj3/opendj-ldap-sync/src/main/assembly/descriptor.xml
@@ -0,0 +1,99 @@
+<?xml version="1.0"?>
+<!--
+ ! CDDL HEADER START
+ !
+ ! The contents of this file are subject to the terms of the
+ ! Common Development and Distribution License, Version 1.0 only
+ ! (the "License").  You may not use this file except in compliance
+ ! with the License.
+ !
+ ! You can obtain a copy of the license at
+ ! trunk/opendj3/legal-notices/CDDLv1_0.txt
+ ! or http://forgerock.org/license/CDDLv1.0.html.
+ ! See the License for the specific language governing permissions
+ ! and limitations under the License.
+ !
+ ! When distributing Covered Code, include this CDDL HEADER in each
+ ! file and include the License file at
+ ! trunk/opendj3/legal-notices/CDDLv1_0.txt.  If applicable,
+ ! add the following below this CDDL HEADER, with the fields enclosed
+ ! by brackets "[]" replaced with your own identifying information:
+ !      Portions Copyright [yyyy] [name of copyright owner]
+ !
+ ! CDDL HEADER END
+ !
+ !      Copyright 2011 ForgeRock AS
+ !    
+ -->
+<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2"
+  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2
+                      http://maven.apache.org/xsd/assembly-1.1.2.xsd">
+  <id>opendj-ldap-sync</id>
+  <formats>
+    <format>zip</format>
+  </formats>
+  <fileSets>
+    <fileSet>
+      <directory>${project.basedir}</directory>
+      <outputDirectory>/</outputDirectory>
+      <directoryMode>755</directoryMode>
+      <fileMode>644</fileMode>
+      <includes>
+        <include>README</include>
+        <include>LICENSE</include>
+        <include>NOTICE</include>
+      </includes>
+    </fileSet>
+    <fileSet>
+      <directory>${project.parent.parent.basedir}/legal-notices</directory>
+      <outputDirectory>legal-notices</outputDirectory>
+      <directoryMode>0755</directoryMode>
+      <fileMode>0644</fileMode>
+    </fileSet>
+  <fileSet>
+    <directory>${project.parent.parent.basedir}</directory>
+    <outputDirectory>/</outputDirectory>
+    <directoryMode>755</directoryMode>
+    <fileMode>644</fileMode>
+    <includes>
+      <include>*.png</include>
+    </includes>
+  </fileSet>
+    <fileSet>
+      <directory>src/main/assembly/bin</directory>
+      <outputDirectory>bin</outputDirectory>
+      <directoryMode>0755</directoryMode>
+      <fileMode>0755</fileMode>
+      <lineEnding>unix</lineEnding>
+    </fileSet>
+    <fileSet>
+      <directory>src/main/assembly/bat</directory>
+      <outputDirectory>bat</outputDirectory>
+      <directoryMode>0755</directoryMode>
+      <fileMode>0644</fileMode>
+      <lineEnding>dos</lineEnding>
+    </fileSet>
+    <fileSet>
+      <directory>src/main/assembly/libbin</directory>
+      <outputDirectory>lib</outputDirectory>
+      <directoryMode>0755</directoryMode>
+      <fileMode>0755</fileMode>
+      <lineEnding>unix</lineEnding>
+    </fileSet>
+    <fileSet>
+      <directory>src/main/assembly/libbat</directory>
+      <outputDirectory>lib</outputDirectory>
+      <directoryMode>0755</directoryMode>
+      <fileMode>0644</fileMode>
+      <lineEnding>dos</lineEnding>
+    </fileSet>
+  </fileSets>
+  <dependencySets>
+    <dependencySet>
+      <outputDirectory>lib</outputDirectory>
+      <directoryMode>0755</directoryMode>
+      <fileMode>0644</fileMode>
+    </dependencySet>
+  </dependencySets>
+</assembly>
diff --git a/opendj3/opendj-ldap-sync/src/main/assembly/libbat/_client-script.bat b/opendj3/opendj-ldap-sync/src/main/assembly/libbat/_client-script.bat
new file mode 100755
index 0000000..8cc2431
--- /dev/null
+++ b/opendj3/opendj-ldap-sync/src/main/assembly/libbat/_client-script.bat
@@ -0,0 +1,53 @@
+
+@echo off
+rem CDDL HEADER START
+rem
+rem The contents of this file are subject to the terms of the
+rem Common Development and Distribution License, Version 1.0 only
+rem (the "License").  You may not use this file except in compliance
+rem with the License.
+rem
+rem You can obtain a copy of the license at
+rem trunk/opendj3/legal-notices/CDDLv1_0.txt
+rem or http://forgerock.org/license/CDDLv1.0.html.
+rem See the License for the specific language governing permissions
+rem and limitations under the License.
+rem
+rem When distributing Covered Code, include this CDDL HEADER in each
+rem file and include the License file at
+rem trunk/opendj3/legal-notices/CDDLv1_0.txt.  If applicable,
+rem add the following below this CDDL HEADER, with the fields enclosed
+rem by brackets "[]" replaced with your own identifying information:
+rem      Portions Copyright [yyyy] [name of copyright owner]
+rem
+rem CDDL HEADER END
+rem
+rem
+rem      Copyright 2006-2009 Sun Microsystems, Inc.
+
+rem This script is used to invoke various client-side processes.  It should not
+rem be invoked directly by end users.
+
+setlocal
+for %%i in (%~sf0) do set DIR_HOME=%%~dPsi..
+set INSTALL_ROOT=%DIR_HOME%
+
+if "%NO_CHECK%" == "" set NO_CHECK=true
+
+if "%OPENDJ_INVOKE_CLASS%" == "" goto noInvokeClass
+goto launchCommand
+
+:noInvokeClass
+echo Error:  OPENDJ_INVOKE_CLASS environment variable is not set.
+pause
+goto end
+
+:launchCommand
+set SCRIPT_UTIL_CMD=set-full-environment
+call "%INSTALL_ROOT%\lib\_script-util.bat" $*
+if NOT %errorlevel% == 0 exit /B %errorlevel%
+
+"%OPENDJ_JAVA_BIN%" %OPENDJ_JAVA_ARGS% %SCRIPT_NAME_ARG% %OPENDJ_INVOKE_CLASS% %*
+
+:end
+
diff --git a/opendj3/opendj-ldap-sync/src/main/assembly/libbat/_script-util.bat b/opendj3/opendj-ldap-sync/src/main/assembly/libbat/_script-util.bat
new file mode 100755
index 0000000..739a7b0
--- /dev/null
+++ b/opendj3/opendj-ldap-sync/src/main/assembly/libbat/_script-util.bat
@@ -0,0 +1,201 @@
+@echo off
+rem CDDL HEADER START
+rem
+rem The contents of this file are subject to the terms of the
+rem Common Development and Distribution License, Version 1.0 only
+rem (the "License").  You may not use this file except in compliance
+rem with the License.
+rem
+rem You can obtain a copy of the license at
+rem trunk/opendj3/legal-notices/CDDLv1_0.txt
+rem or http://forgerock.org/license/CDDLv1.0.html.
+rem See the License for the specific language governing permissions
+rem and limitations under the License.
+rem
+rem When distributing Covered Code, include this CDDL HEADER in each
+rem file and include the License file at
+rem trunk/opendj3/legal-notices/CDDLv1_0.txt.  If applicable,
+rem add the following below this CDDL HEADER, with the fields enclosed
+rem by brackets "[]" replaced with your own identifying information:
+rem      Portions Copyright [yyyy] [name of copyright owner]
+rem
+rem CDDL HEADER END
+rem
+rem
+rem      Copyright 2008-2009 Sun Microsystems, Inc.
+
+set SET_JAVA_HOME_AND_ARGS_DONE=false
+set SET_ENVIRONMENT_VARS_DONE=false
+set SET_CLASSPATH_DONE=false
+
+if "%INSTALL_ROOT%" == "" goto setInstanceRoot
+
+:scriptBegin
+if "%SCRIPT_UTIL_CMD%" == "set-full-environment-and-test-java" goto setFullEnvironmentAndTestJava
+if "%SCRIPT_UTIL_CMD%" == "set-full-environment" goto setFullEnvironment
+if "%SCRIPT_UTIL_CMD%" == "set-java-home-and-args" goto setJavaHomeAndArgs
+if "%SCRIPT_UTIL_CMD%" == "set_environment_vars" goto setEnvironmentVars
+if "%SCRIPT_UTIL_CMD%" == "test-java" goto testJava
+if "%SCRIPT_UTIL_CMD%" == "set-classpath" goto setClassPath
+goto prepareCheck
+
+:setInstanceRoot
+setlocal
+for %%i in (%~sf0) do set DIR_HOME=%%~dPsi..
+set INSTALL_ROOT=%DIR_HOME%
+set CUR_DIR=%~dp0
+cd /d %INSTALL_ROOT%
+cd /d %INSTANCE_DIR%
+set INSTANCE_ROOT=%CD%
+cd /d %CUR_DIR%
+goto scriptBegin
+
+
+:setClassPath
+if "%SET_CLASSPATH_DONE%" == "true" goto prepareCheck
+FOR %%x in ("%INSTALL_ROOT%\lib\*.jar") DO call "%INSTALL_ROOT%\lib\setcp.bat" %%x
+if "%INSTALL_ROOT%" == "%INSTANCE_ROOT%"goto setClassPathDone
+FOR %%x in ("%INSTANCE_ROOT%\lib\*.jar") DO call "%INSTANCE_ROOT%\lib\setcp.bat" %%x
+FOR %%x in ("%INSTALL_ROOT%\resources\*.jar") DO call "%INSTALL_ROOT%\lib\setcp.bat" %%x
+set CLASSPATH=%INSTANCE_ROOT%\classes;%CLASSPATH%
+:setClassPathDone
+set SET_CLASSPATH_DONE=true
+goto scriptBegin
+
+:setFullEnvironment
+if "%SET_JAVA_HOME_AND_ARGS_DONE%" == "false" goto setJavaHomeAndArgs
+if "%SET_CLASSPATH_DONE%" == "false" goto setClassPath
+if "%SET_ENVIRONMENT_VARS_DONE%" == "false" goto setEnvironmentVars
+goto prepareCheck
+
+:setFullEnvironmentAndTestJava
+if "%SET_JAVA_HOME_AND_ARGS_DONE%" == "false" goto setJavaHomeAndArgs
+if "%SET_CLASSPATH_DONE%" == "false" goto setClassPath
+if "%SET_ENVIRONMENT_VARS_DONE%" == "false" goto setEnvironmentVars
+goto testJava
+
+
+:setJavaHomeAndArgs
+if "%SET_JAVA_HOME_AND_ARGS_DONE%" == "true" goto prepareCheck
+if not exist "%INSTANCE_ROOT%\lib\set-java-home.bat" goto checkEnvJavaArgs
+call "%INSTANCE_ROOT%\lib\set-java-home.bat"
+if "%OPENDJ_JAVA_BIN%" == "" goto checkEnvJavaArgs
+:endJavaHomeAndArgs
+set SET_JAVA_HOME_AND_ARGS_DONE=true
+goto scriptBegin
+
+:checkEnvJavaArgs
+if "%OPENDJ_JAVA_BIN%" == "" goto checkOpenDJJavaHome
+if not exist "%OPENDJ_JAVA_BIN%" goto checkOpenDJJavaHome
+goto endJavaHomeAndArgs
+
+:checkOpenDJJavaHome
+if "%OPENDJ_JAVA_HOME%" == "" goto checkJavaBin
+if not exist "%OPENDJ_JAVA_HOME%\bin\java.exe" goto checkJavaBin
+set OPENDJ_JAVA_BIN=%OPENDJ_JAVA_HOME%\bin\java.exe
+goto endJavaHomeAndArgs
+
+:checkJavaBin
+if "%JAVA_BIN%" == "" goto checkJavaHome
+if not exist "%JAVA_BIN%" goto checkJavaHome
+set OPENDJ_JAVA_BIN=%JAVA_BIN%
+goto endJavaHomeAndArgs
+
+:checkJavaHome
+if "%JAVA_HOME%" == "" goto checkJavaPath
+if not exist "%JAVA_HOME%\bin\java.exe" goto noJavaFound
+set OPENDJ_JAVA_BIN=%JAVA_HOME%\bin\java.exe
+goto endJavaHomeAndArgs
+
+:checkJavaPath
+java.exe -version > NUL 2>&1
+if not %errorlevel% == 0 goto noJavaFound
+set OPENDJ_JAVA_BIN=java.exe
+goto endJavaHomeAndArgs
+
+:noJavaFound
+echo ERROR:  Could not find a valid Java binary to be used.
+echo You must specify the path to a valid Java 5.0 or higher version.
+echo The procedure to follow is:
+echo 1. Delete the file %INSTANCE_ROOT%\lib\set-java-home.bat if it exists.
+echo 2. Set the environment variable OPENDJ_JAVA_HOME to the root of a valid
+echo Java 5.0 installation.
+echo If you want to have specific Java settings for each command line you must
+echo follow the steps 3 and 4.
+echo 3. Edit the properties file specifying the Java binary and the Java arguments
+echo for each command line.  The Java properties file is located in:
+echo %INSTANCE_ROOT%\config\java.properties.
+echo 4. Run the command-line %INSTALL_ROOT%\bat\dsjavaproperties.bat
+pause
+exit /B 1
+
+:setEnvironmentVars
+if %SET_ENVIRONMENT_VARS_DONE% == "true" goto prepareCheck
+set PATH=%SystemRoot%;%PATH%
+set SCRIPT_NAME_ARG=-Dcom.forgerock.opendj.ldap.tools.scriptName=%SCRIPT_NAME%
+set SET_ENVIRONMENT_VARS_DONE=true
+goto scriptBegin
+
+:noValidJavaHome
+if NOT "%OPENDJ_JAVA_ARGS%" == "" goto noValidHomeWithArgs
+echo ERROR:  The detected Java version could not be used.  The detected
+echo Java binary is:
+echo %OPENDJ_JAVA_BIN%
+echo You must specify the path to a valid Java 5.0 or higher version.
+echo The procedure to follow is:
+echo 1. Delete the file %INSTANCE_ROOT%\lib\set-java-home.bat if it exists.
+echo 2. Set the environment variable OPENDJ_JAVA_HOME to the root of a valid
+echo Java 5.0 installation.
+echo If you want to have specific Java settings for each command line you must
+echo follow the steps 3 and 4.
+echo 3. Edit the properties file specifying the Java binary and the Java arguments
+echo for each command line.  The Java properties file is located in:
+echo %INSTANCE_ROOT%\config\java.properties.
+echo 4. Run the command-line %INSTALL_ROOT%\bat\dsjavaproperties.bat
+pause
+exit /B 1
+
+:notSupportedJavaHome
+rem We get here when the java version is 5 (or up) but not supported.  We run
+rem InstallDS again to see a localized message.
+"%OPENDJ_JAVA_BIN%" %OPENDJ_JAVA_ARGS% org.opendj.server.tools.InstallDS -t
+pause
+exit /B 1
+
+:noValidHomeWithArgs
+echo ERROR:  The detected Java version could not be used with the set of Java
+echo arguments %OPENDJ_JAVA_ARGS%.
+echo The detected Java binary is:
+echo %OPENDJ_JAVA_BIN%
+echo You must specify the path to a valid Java 5.0 or higher version.
+echo The procedure to follow is:
+echo 1. Delete the file %INSTANCE_ROOT%\lib\set-java-home.bat if it exists.
+echo 2. Set the environment variable OPENDJ_JAVA_HOME to the root of a valid
+echo Java 5.0 installation.
+echo If you want to have specific Java settings for each command line you must
+echo follow the steps 3 and 4.
+echo 3. Edit the properties file specifying the Java binary and the Java arguments
+echo for each command line.  The Java properties file is located in:
+echo %INSTANCE_ROOT%\config\java.properties.
+echo 4. Run the command-line %INSTALL_ROOT%\bat\dsjavaproperties.bat
+pause
+exit /B 1
+
+:isVersionOrHelp
+if [%1] == [] goto check
+if [%1] == [--help] goto end
+if [%1] == [-H] goto end
+if [%1] == [--version] goto end
+if [%1] == [-V] goto end
+if [%1] == [--fullversion] goto end
+if [%1] == [-F] goto end
+shift
+goto isVersionOrHelp
+
+:prepareCheck
+rem Perform check unless it is specified not to do it
+if "%NO_CHECK%" == ""  set NO_CHECK=false
+goto isVersionOrHelp
+
+:end
+exit /B 0
diff --git a/opendj3/opendj-ldap-sync/src/main/assembly/libbat/setcp.bat b/opendj3/opendj-ldap-sync/src/main/assembly/libbat/setcp.bat
new file mode 100755
index 0000000..4a3fd8f
--- /dev/null
+++ b/opendj3/opendj-ldap-sync/src/main/assembly/libbat/setcp.bat
@@ -0,0 +1,40 @@
+
+rem CDDL HEADER START
+rem
+rem The contents of this file are subject to the terms of the
+rem Common Development and Distribution License, Version 1.0 only
+rem (the "License").  You may not use this file except in compliance
+rem with the License.
+rem
+rem You can obtain a copy of the license at
+rem trunk/opendj3/legal-notices/CDDLv1_0.txt
+rem or http://forgerock.org/license/CDDLv1.0.html.
+rem See the License for the specific language governing permissions
+rem and limitations under the License.
+rem
+rem When distributing Covered Code, include this CDDL HEADER in each
+rem file and include the License file at
+rem trunk/opendj3/legal-notices/CDDLv1_0.txt.  If applicable,
+rem add the following below this CDDL HEADER, with the fields enclosed
+rem by brackets "[]" replaced with your own identifying information:
+rem      Portions Copyright [yyyy] [name of copyright owner]
+rem
+rem CDDL HEADER END
+rem
+rem
+rem      Copyright 2006-2008 Sun Microsystems, Inc.
+
+set CLASSPATHCOMPONENT=%1
+if ""%1""=="""" goto gotAllArgs
+shift
+
+:argCheck
+if ""%1""=="""" goto gotAllArgs
+set CLASSPATHCOMPONENT=%CLASSPATHCOMPONENT% %1
+shift
+goto argCheck
+
+:gotAllArgs
+set CLASSPATH=%CLASSPATHCOMPONENT%;%CLASSPATH%
+
+
diff --git a/opendj3/opendj-ldap-sync/src/main/assembly/libbin/_client-script.sh b/opendj3/opendj-ldap-sync/src/main/assembly/libbin/_client-script.sh
new file mode 100755
index 0000000..c010f3a
--- /dev/null
+++ b/opendj3/opendj-ldap-sync/src/main/assembly/libbin/_client-script.sh
@@ -0,0 +1,65 @@
+#!/bin/sh
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License, Version 1.0 only
+# (the "License").  You may not use this file except in compliance
+# with the License.
+#
+# You can obtain a copy of the license at
+# trunk/opendj3/legal-notices/CDDLv1_0.txt
+# or http://forgerock.org/license/CDDLv1.0.html.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at
+# trunk/opendj3/legal-notices/CDDLv1_0.txt.  If applicable,
+# add the following below this CDDL HEADER, with the fields enclosed
+# by brackets "[]" replaced with your own identifying information:
+#      Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+#
+#      Copyright 2006-2008 Sun Microsystems, Inc.
+
+
+# This script is used to invoke various client-side processes.  It should not
+# be invoked directly by end users.
+if test -z "${OPENDJ_INVOKE_CLASS}"
+then
+  echo "ERROR:  OPENDJ_INVOKE_CLASS environment variable is not set."
+  exit 1
+fi
+
+
+# Capture the current working directory so that we can change to it later.
+# Then capture the location of this script and the Directory Server install
+# root so that we can use them to create appropriate paths.
+WORKING_DIR=`pwd`
+
+cd "`dirname "${0}"`"
+SCRIPT_DIR=`pwd`
+
+cd ..
+INSTALL_ROOT=`pwd`
+export INSTALL_ROOT
+
+cd "${WORKING_DIR}"
+
+
+# Set environment variables
+SCRIPT_UTIL_CMD=set-full-environment
+export SCRIPT_UTIL_CMD
+
+.  "${INSTALL_ROOT}/lib/_script-util.sh"
+RETURN_CODE=$?
+if test ${RETURN_CODE} -ne 0
+then
+  exit ${RETURN_CODE}
+fi
+
+# Launch the appropriate client utility.
+"${OPENDJ_JAVA_BIN}" ${OPENDJ_JAVA_ARGS} ${SCRIPT_NAME_ARG} "${OPENDJ_INVOKE_CLASS}" "${@}"
diff --git a/opendj3/opendj-ldap-sync/src/main/assembly/libbin/_script-util.sh b/opendj3/opendj-ldap-sync/src/main/assembly/libbin/_script-util.sh
new file mode 100755
index 0000000..18e27bd
--- /dev/null
+++ b/opendj3/opendj-ldap-sync/src/main/assembly/libbin/_script-util.sh
@@ -0,0 +1,130 @@
+#!/bin/sh
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License, Version 1.0 only
+# (the "License").  You may not use this file except in compliance
+# with the License.
+#
+# You can obtain a copy of the license at
+# trunk/opendj3/legal-notices/CDDLv1_0.txt
+# or http://forgerock.org/license/CDDLv1.0.html.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at
+# trunk/opendj3/legal-notices/CDDLv1_0.txt.  If applicable,
+# add the following below this CDDL HEADER, with the fields enclosed
+# by brackets "[]" replaced with your own identifying information:
+#      Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+#
+#      Copyright 2008-2009 Sun Microsystems, Inc.
+
+#
+# function that sets the java home
+#
+set_java_home_and_args() {
+  if test -f "${INSTANCE_ROOT}/lib/set-java-home"
+  then
+    . "${INSTANCE_ROOT}/lib/set-java-home"
+  fi
+  if test -z "${OPENDJ_JAVA_BIN}"
+  then
+    if test -z "${OPENDJ_JAVA_HOME}"
+    then
+      if test -z "${JAVA_BIN}"
+      then
+        if test -z "${JAVA_HOME}"
+        then
+          OPENDJ_JAVA_BIN=`which java 2> /dev/null`
+          if test ${?} -eq 0
+          then
+            export OPENDJ_JAVA_BIN
+          else
+            echo "Please set OPENDJ_JAVA_HOME to the root of a Java 5 (or later) installation"
+            echo "or edit the java.properties file and then run the dsjavaproperties script to"
+            echo "specify the Java version to be used"
+            exit 1
+          fi
+        else
+          OPENDJ_JAVA_BIN="${JAVA_HOME}/bin/java"
+          export OPENDJ_JAVA_BIN
+        fi
+      else
+        OPENDJ_JAVA_BIN="${JAVA_BIN}"
+        export OPENDJ_JAVA_BIN
+      fi
+    else
+      OPENDJ_JAVA_BIN="${OPENDJ_JAVA_HOME}/bin/java"
+      export OPENDJ_JAVA_BIN
+    fi
+  fi
+}
+
+# Explicitly set the PATH, LD_LIBRARY_PATH, LD_PRELOAD, and other important
+# system environment variables for security and compatibility reasons.
+set_environment_vars() {
+  PATH=/bin:/usr/bin
+  LD_LIBRARY_PATH=
+  LD_LIBRARY_PATH_32=
+  LD_LIBRARY_PATH_64=
+  LD_PRELOAD=
+  LD_PRELOAD_32=
+  LD_PRELOAD_64=
+  export PATH LD_LIBRARY_PATH LD_LIBRARY_PATH_32 LD_LIBRARY_PATH_64 \
+       LD_PRELOAD LD_PRELOAD_32 LD_PRELOAD_64
+  SCRIPT_NAME_ARG=-Dcom.forgerock.opendj.ldap.tools.scriptName=${SCRIPT_NAME}
+	export SCRIPT_NAME_ARG
+}
+
+# Configure the appropriate CLASSPATH.
+set_classpath() {
+  CLASSPATH=${INSTANCE_ROOT}/classes
+  for JAR in ${INSTALL_ROOT}/lib/*.jar
+  do
+    CLASSPATH=${CLASSPATH}:${JAR}
+  done
+  if [ "${INSTALL_ROOT}" != "${INSTANCE_ROOT}" ]
+  then
+    for JAR in ${INSTANCE_ROOT}/lib/*.jar
+    do
+      CLASSPATH=${CLASSPATH}:${JAR}
+    done
+  fi
+  export CLASSPATH
+}
+
+if test "${INSTALL_ROOT}" = ""
+then
+  # Capture the current working directory so that we can change to it later.
+  # Then capture the location of this script and the Directory Server instance
+  # root so that we can use them to create appropriate paths.
+  WORKING_DIR=`pwd`
+
+  cd "`dirname "${0}"`"
+  cd ..
+  INSTALL_ROOT=`pwd`
+  cd "${WORKING_DIR}"
+fi
+
+if test "${SCRIPT_UTIL_CMD}" = "set-full-environment"
+then
+  set_java_home_and_args
+  set_environment_vars
+  set_classpath
+elif test "${SCRIPT_UTIL_CMD}" = "set-java-home-and-args"
+then
+  set_java_home_and_args
+elif test "${SCRIPT_UTIL_CMD}" = "set-environment-vars"
+then
+  set_environment_vars
+elif test "${SCRIPT_UTIL_CMD}" = "set-classpath"
+then
+  set_classpath
+fi
+
diff --git a/opendj3/opendj-ldap-sync/src/main/java/org/forgerock/opendj/sync/Main.java b/opendj3/opendj-ldap-sync/src/main/java/org/forgerock/opendj/sync/Main.java
new file mode 100644
index 0000000..be8363c
--- /dev/null
+++ b/opendj3/opendj-ldap-sync/src/main/java/org/forgerock/opendj/sync/Main.java
@@ -0,0 +1,54 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opendj3/legal-notices/CDDLv1_0.txt
+ * or http://forgerock.org/license/CDDLv1.0.html.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opendj3/legal-notices/CDDLv1_0.txt.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *      Copyright 2011 ForgeRock AS
+ */
+
+package org.forgerock.opendj.sync;
+
+
+
+/**
+ * LDAP synchronization application.
+ */
+public final class Main
+{
+  /**
+   * Application entry point.
+   *
+   * @param args
+   *          Command line arguments.
+   */
+  public static void main(final String[] args)
+  {
+    // TODO:
+  }
+
+
+
+  private Main()
+  {
+    // Prevent instantiation.
+  }
+
+}
diff --git a/opendj3/opendj-ldap-sync/src/main/java/org/forgerock/opendj/sync/filters/ChangeRecordContext.java b/opendj3/opendj-ldap-sync/src/main/java/org/forgerock/opendj/sync/filters/ChangeRecordContext.java
new file mode 100644
index 0000000..cef0cdd
--- /dev/null
+++ b/opendj3/opendj-ldap-sync/src/main/java/org/forgerock/opendj/sync/filters/ChangeRecordContext.java
@@ -0,0 +1,138 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opendj3/legal-notices/CDDLv1_0.txt
+ * or http://forgerock.org/license/CDDLv1.0.html.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opendj3/legal-notices/CDDLv1_0.txt.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *      Copyright 2011 ForgeRock AS
+ */
+
+package org.forgerock.opendj.sync.filters;
+
+
+
+import java.util.Date;
+
+import org.forgerock.opendj.ldap.DN;
+import org.forgerock.opendj.ldap.Entries;
+import org.forgerock.opendj.ldap.Entry;
+
+
+
+/**
+ * The context associated with a change record to be filtered. The context
+ * provides access to meta data associated with changes as they are processed,
+ * including but not limited to:
+ * <ul>
+ * <li>The change number
+ * <li>The CSN, if available
+ * <li>The entry's unique ID, if available
+ * <li>The name of the user who performed the change, if available
+ * </ul>
+ */
+public final class ChangeRecordContext
+{
+  private final Entry entry;
+
+
+
+  /**
+   * Creates a new change record context using the provided change log entry.
+   *
+   * @param entry
+   *          The change log entry.
+   */
+  ChangeRecordContext(final Entry entry)
+  {
+    this.entry = Entries.unmodifiableEntry(entry);
+  }
+
+
+
+  /**
+   * Returns the change cookie contained in the change log entry, or
+   * {@code null} if it is not present.
+   *
+   * @return The change cookie contained in the change log entry, or
+   *         {@code null} if it is not present.
+   */
+  public String getChangeCookie()
+  {
+    // TODO
+    return null;
+  }
+
+
+
+  /**
+   * Returns an unmodifiable view of the underlying change log entry.
+   *
+   * @return An unmodifiable view of the underlying change log entry.
+   */
+  public Entry getChangeEntry()
+  {
+    return entry;
+  }
+
+
+
+  /**
+   * Returns the change initiators name contained in the change log entry, or
+   * {@code null} if it is not present.
+   *
+   * @return The change initiators name contained in the change log entry, or
+   *         {@code null} if it is not present.
+   */
+  public DN getChangeInitiatorsName()
+  {
+    // TODO
+    return null;
+  }
+
+
+
+  /**
+   * Returns the change number contained in the change log entry, or {@code -1}
+   * if it is not present.
+   *
+   * @return The change number contained in the change log entry, or {@code -1}
+   *         if it is not present.
+   */
+  public long getChangeNumber()
+  {
+    // TODO
+    return 0L;
+  }
+
+
+
+  /**
+   * Returns the change time contained in the change log entry, or {@code null}
+   * if it is not present.
+   *
+   * @return The change time contained in the change log entry, or {@code null}
+   *         if it is not present.
+   */
+  public Date getChangeTime()
+  {
+    // TODO
+    return null;
+  }
+}
diff --git a/opendj3/opendj-ldap-sync/src/main/java/org/forgerock/opendj/sync/filters/Filter.java b/opendj3/opendj-ldap-sync/src/main/java/org/forgerock/opendj/sync/filters/Filter.java
new file mode 100644
index 0000000..7d4f4ac
--- /dev/null
+++ b/opendj3/opendj-ldap-sync/src/main/java/org/forgerock/opendj/sync/filters/Filter.java
@@ -0,0 +1,107 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opendj3/legal-notices/CDDLv1_0.txt
+ * or http://forgerock.org/license/CDDLv1.0.html.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opendj3/legal-notices/CDDLv1_0.txt.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *      Copyright 2011 ForgeRock AS
+ */
+
+package org.forgerock.opendj.sync.filters;
+
+
+
+import java.io.Closeable;
+
+import org.forgerock.opendj.ldap.requests.AddRequest;
+import org.forgerock.opendj.ldap.requests.DeleteRequest;
+import org.forgerock.opendj.ldap.requests.ModifyDNRequest;
+import org.forgerock.opendj.ldap.requests.ModifyRequest;
+
+
+
+/**
+ * A change record filter. Transforms may modify the content of the provided
+ * change record.
+ */
+public interface Filter extends Closeable
+{
+  /**
+   * Release any resources associated with this filter. This method is called
+   * during application termination.
+   */
+  @Override
+  void close();
+
+
+
+  /**
+   * Transforms the provided add request.
+   *
+   * @param context
+   *          Additional information associated with the change record.
+   * @param change
+   *          The add request to be filtered or modified.
+   * @return The result of the filter.
+   */
+  FilterResult filterChangeRecord(ChangeRecordContext context, AddRequest change);
+
+
+
+  /**
+   * Transforms the provided delete request.
+   *
+   * @param context
+   *          Additional information associated with the change record.
+   * @param change
+   *          The delete request to be filtered or modified.
+   * @return The result of the filter.
+   */
+  FilterResult filterChangeRecord(ChangeRecordContext context,
+      DeleteRequest change);
+
+
+
+  /**
+   * Transforms the provided modify DN request.
+   *
+   * @param context
+   *          Additional information associated with the change record.
+   * @param change
+   *          The modify DN request to be filtered or modified.
+   * @return The result of the filter.
+   */
+  FilterResult filterChangeRecord(ChangeRecordContext context,
+      ModifyDNRequest change);
+
+
+
+  /**
+   * Transforms the provided modify request.
+   *
+   * @param context
+   *          Additional information associated with the change record.
+   * @param change
+   *          The modify request to be filtered or modified.
+   * @return The result of the filter.
+   */
+  FilterResult filterChangeRecord(ChangeRecordContext context,
+      ModifyRequest change);
+}
diff --git a/opendj3/opendj-ldap-sync/src/main/java/org/forgerock/opendj/sync/filters/FilterResult.java b/opendj3/opendj-ldap-sync/src/main/java/org/forgerock/opendj/sync/filters/FilterResult.java
new file mode 100644
index 0000000..4ae54cb
--- /dev/null
+++ b/opendj3/opendj-ldap-sync/src/main/java/org/forgerock/opendj/sync/filters/FilterResult.java
@@ -0,0 +1,267 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opendj3/legal-notices/CDDLv1_0.txt
+ * or http://forgerock.org/license/CDDLv1.0.html.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opendj3/legal-notices/CDDLv1_0.txt.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *      Copyright 2011 ForgeRock AS
+ */
+
+package org.forgerock.opendj.sync.filters;
+
+
+
+import org.forgerock.i18n.LocalizableException;
+import org.forgerock.i18n.LocalizableMessage;
+
+
+
+/**
+ * The result of processing a change record in a {@code Filter}.
+ */
+public final class FilterResult
+{
+
+  /**
+   * The set of possible actions that a {@code Filter} may return in order to
+   * indicate how change record processing should continue.
+   */
+  public enum Action
+  {
+
+    /**
+     * Indicates that the change record was processed successfully by the
+     * filter, and it should now be processed by any remaining filters.
+     */
+    NEXT,
+
+    /**
+     * Indicates that the change record was processed successfully by the
+     * filter, but it should not be processed by any remaining filters.
+     */
+    STOP,
+
+    /**
+     * Indicates that the change record could not be processed by the filter due
+     * to a non-fatal error, and it should not be processed by any remaining
+     * filters. However, subsequent change records can still be processed.
+     */
+    FAIL,
+
+    /**
+     * Indicates that a fatal error occurred and it will not be possible to
+     * process any more change records.
+     */
+    FATAL;
+  }
+
+
+
+  /**
+   * Returns a filter result whose action is {@link Action#FAIL} and which has
+   * the specified cause.
+   *
+   * @param cause
+   *          The exception which caused processing of the change record to
+   *          stop.
+   * @return A filter result indicating that processing of the change record
+   *         should stop due to an error.
+   */
+  public static FilterResult fail(final Throwable cause)
+  {
+    return new FilterResult(Action.FAIL, null, cause);
+  }
+
+
+
+  /**
+   * Returns a filter result whose action is {@link Action#FATAL} and which has
+   * the specified cause.
+   *
+   * @param cause
+   *          The exception which caused the filter chain to fail.
+   * @return A filter result indicating that the filter chain has failed and no
+   *         more change records can be processed.
+   */
+  public static FilterResult fatal(final Throwable cause)
+  {
+    return new FilterResult(Action.FATAL, null, cause);
+  }
+
+
+
+  /**
+   * Returns a filter result whose action is {@link Action#STOP} and which has
+   * no cause or message.
+   *
+   * @return A filter result indicating that processing of the change record
+   *         should stop.
+   */
+  public static FilterResult stop()
+  {
+    return stop(null);
+  }
+
+
+
+  /**
+   * Returns a filter result whose action is {@link Action#STOP} and which has
+   * the specified message, but no cause.
+   *
+   * @param message
+   *          A message explaining why processing of the change record should
+   *          stop.
+   * @return A filter result indicating that processing of the change record
+   *         should stop.
+   */
+  public static FilterResult stop(final LocalizableMessage message)
+  {
+    if (message == null)
+    {
+      return STOP;
+    }
+    else
+    {
+      return new FilterResult(Action.STOP, message, null);
+    }
+  }
+
+
+
+  private final Action action;
+
+  private final LocalizableMessage message;
+
+  private final Throwable cause;
+
+  private static final FilterResult NEXT = new FilterResult(Action.NEXT, null,
+      null);
+
+  private static final FilterResult STOP = new FilterResult(Action.STOP, null,
+      null);
+
+
+
+  /**
+   * Returns a filter result whose action is {@link Action#NEXT} and which has
+   * no cause or message.
+   *
+   * @return A filter result indicating that processing of the change record
+   *         should continue.
+   */
+  public static FilterResult next()
+  {
+    return NEXT;
+  }
+
+
+
+  private FilterResult(final Action action, final LocalizableMessage message,
+      final Throwable cause)
+  {
+    this.action = action;
+    this.cause = cause;
+
+    if (message != null)
+    {
+      this.message = message;
+    }
+    else if (cause != null)
+    {
+      if (cause instanceof LocalizableException)
+      {
+        this.message = ((LocalizableException) cause).getMessageObject();
+      }
+      else
+      {
+        this.message = LocalizableMessage.raw("%s", cause.getMessage());
+      }
+    }
+    else
+    {
+      this.message = null;
+    }
+  }
+
+
+
+  /**
+   * Returns the action associated with this filter result.
+   *
+   * @return The action associated with this filter result.
+   */
+  public Action getAction()
+  {
+    return action;
+  }
+
+
+
+  /**
+   * Returns the exception which caused processing to be stopped.
+   *
+   * @return The exception which caused processing to be stopped, may be
+   *         {@code null}.
+   */
+  public Throwable getCause()
+  {
+    return cause;
+  }
+
+
+
+  /**
+   * Returns the optional localizable message describing why processing has
+   * stopped.
+   *
+   * @return The localizable message describing why processing has stopped, may
+   *         be {@code null}.
+   */
+  public LocalizableMessage getMessage()
+  {
+    return message;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public String toString()
+  {
+    final StringBuilder builder = new StringBuilder();
+    builder.append("FilterResult(");
+    builder.append(action);
+    if (message != null)
+    {
+      builder.append(',');
+      builder.append(message);
+    }
+    if (cause != null)
+    {
+      builder.append(',');
+      builder.append(cause);
+    }
+    builder.append(')');
+    return builder.toString();
+  }
+
+}
diff --git a/opendj3/opendj-ldap-sync/src/main/java/org/forgerock/opendj/sync/filters/Matchers.java b/opendj3/opendj-ldap-sync/src/main/java/org/forgerock/opendj/sync/filters/Matchers.java
new file mode 100644
index 0000000..c577365
--- /dev/null
+++ b/opendj3/opendj-ldap-sync/src/main/java/org/forgerock/opendj/sync/filters/Matchers.java
@@ -0,0 +1,910 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opendj3/legal-notices/CDDLv1_0.txt
+ * or http://forgerock.org/license/CDDLv1.0.html.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opendj3/legal-notices/CDDLv1_0.txt.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *      Copyright 2011 ForgeRock AS
+ */
+
+package org.forgerock.opendj.sync.filters;
+
+
+
+import org.forgerock.opendj.ldap.DN;
+import org.forgerock.opendj.ldap.SearchScope;
+import org.forgerock.opendj.ldap.requests.AddRequest;
+import org.forgerock.opendj.ldap.requests.DeleteRequest;
+import org.forgerock.opendj.ldap.requests.ModifyDNRequest;
+import org.forgerock.opendj.ldap.requests.ModifyRequest;
+import org.forgerock.opendj.ldif.ChangeRecord;
+import org.forgerock.opendj.sync.filters.FilterResult.Action;
+
+
+
+/**
+ * Factory methods for constructing common types of matching filters. Matchers
+ * are logical filters which can be used to construct conditional
+ * transformations. Furthermore, matchers implement boolean logic where
+ * {@code true} is indicated by the filter action {@link Action#NEXT} and
+ * {@code false} is indicated by the filter action {@link Action#STOP}.
+ */
+public final class Matchers
+{
+  private static final class AndMatcher implements Filter
+  {
+    private final Filter[] subFilters;
+
+
+
+    private AndMatcher(final Filter[] subFilters)
+    {
+      this.subFilters = subFilters;
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void close()
+    {
+      for (final Filter filter : subFilters)
+      {
+        filter.close();
+      }
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public FilterResult filterChangeRecord(final ChangeRecordContext context,
+        final AddRequest change)
+    {
+      for (final Filter subFilter : subFilters)
+      {
+        final FilterResult result = subFilter.filterChangeRecord(context,
+            change);
+        if (result.getAction() != Action.NEXT)
+        {
+          return result;
+        }
+      }
+      return FilterResult.next();
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public FilterResult filterChangeRecord(final ChangeRecordContext context,
+        final DeleteRequest change)
+    {
+      for (final Filter subFilter : subFilters)
+      {
+        final FilterResult result = subFilter.filterChangeRecord(context,
+            change);
+        if (result.getAction() != Action.NEXT)
+        {
+          return result;
+        }
+      }
+      return FilterResult.next();
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public FilterResult filterChangeRecord(final ChangeRecordContext context,
+        final ModifyDNRequest change)
+    {
+      for (final Filter subFilter : subFilters)
+      {
+        final FilterResult result = subFilter.filterChangeRecord(context,
+            change);
+        if (result.getAction() != Action.NEXT)
+        {
+          return result;
+        }
+      }
+      return FilterResult.next();
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public FilterResult filterChangeRecord(final ChangeRecordContext context,
+        final ModifyRequest change)
+    {
+      for (final Filter subFilter : subFilters)
+      {
+        final FilterResult result = subFilter.filterChangeRecord(context,
+            change);
+        if (result.getAction() != Action.NEXT)
+        {
+          return result;
+        }
+      }
+      return FilterResult.next();
+    }
+  }
+
+
+
+  private static final class ChangeInitiatorInScopeMatcher implements Filter
+  {
+    private final DN dn;
+    private final SearchScope scope;
+
+
+
+    private ChangeInitiatorInScopeMatcher(final DN dn, final SearchScope scope)
+    {
+      this.dn = dn;
+      this.scope = scope;
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void close()
+    {
+      // Nothing to do.
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public FilterResult filterChangeRecord(final ChangeRecordContext context,
+        final AddRequest change)
+    {
+      return matchesScope(change);
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public FilterResult filterChangeRecord(final ChangeRecordContext context,
+        final DeleteRequest change)
+    {
+      return matchesScope(change);
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public FilterResult filterChangeRecord(final ChangeRecordContext context,
+        final ModifyDNRequest change)
+    {
+      return matchesScope(change);
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public FilterResult filterChangeRecord(final ChangeRecordContext context,
+        final ModifyRequest change)
+    {
+      return matchesScope(change);
+    }
+
+
+
+    private FilterResult matchesScope(final ChangeRecord change)
+    {
+      if (change.getName().isInScopeOf(dn, scope))
+      {
+        return FilterResult.next();
+      }
+      else
+      {
+        return FilterResult.stop();
+      }
+    }
+  }
+
+
+
+  private static final class ImpliesMatcher implements Filter
+  {
+    private final Filter condition;
+    private final Filter transformation;
+
+
+
+    private ImpliesMatcher(final Filter condition, final Filter transformation)
+    {
+      this.condition = condition;
+      this.transformation = transformation;
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void close()
+    {
+      condition.close();
+      transformation.close();
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public FilterResult filterChangeRecord(final ChangeRecordContext context,
+        final AddRequest change)
+    {
+      final FilterResult result = condition.filterChangeRecord(context, change);
+      if (result.getAction() == Action.NEXT)
+      {
+        return transformation.filterChangeRecord(context, change);
+      }
+      else
+      {
+        return result;
+      }
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public FilterResult filterChangeRecord(final ChangeRecordContext context,
+        final DeleteRequest change)
+    {
+      final FilterResult result = condition.filterChangeRecord(context, change);
+      if (result.getAction() == Action.NEXT)
+      {
+        return transformation.filterChangeRecord(context, change);
+      }
+      else
+      {
+        return result;
+      }
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public FilterResult filterChangeRecord(final ChangeRecordContext context,
+        final ModifyDNRequest change)
+    {
+      final FilterResult result = condition.filterChangeRecord(context, change);
+      if (result.getAction() == Action.NEXT)
+      {
+        return transformation.filterChangeRecord(context, change);
+      }
+      else
+      {
+        return result;
+      }
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public FilterResult filterChangeRecord(final ChangeRecordContext context,
+        final ModifyRequest change)
+    {
+      final FilterResult result = condition.filterChangeRecord(context, change);
+      if (result.getAction() == Action.NEXT)
+      {
+        return transformation.filterChangeRecord(context, change);
+      }
+      else
+      {
+        return result;
+      }
+    }
+  }
+
+
+
+  private static final class NotMatcher implements Filter
+  {
+    private final Filter subFilter;
+
+
+
+    private NotMatcher(final Filter subFilter)
+    {
+      this.subFilter = subFilter;
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void close()
+    {
+      subFilter.close();
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public FilterResult filterChangeRecord(final ChangeRecordContext context,
+        final AddRequest change)
+    {
+      return transformResult(subFilter.filterChangeRecord(context, change));
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public FilterResult filterChangeRecord(final ChangeRecordContext context,
+        final DeleteRequest change)
+    {
+      return transformResult(subFilter.filterChangeRecord(context, change));
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public FilterResult filterChangeRecord(final ChangeRecordContext context,
+        final ModifyDNRequest change)
+    {
+      return transformResult(subFilter.filterChangeRecord(context, change));
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public FilterResult filterChangeRecord(final ChangeRecordContext context,
+        final ModifyRequest change)
+    {
+      return transformResult(subFilter.filterChangeRecord(context, change));
+    }
+
+
+
+    private FilterResult transformResult(final FilterResult result)
+    {
+      switch (result.getAction())
+      {
+      case NEXT:
+        return FilterResult.stop();
+      case STOP:
+        return FilterResult.next();
+      default:
+        return result;
+      }
+    }
+  }
+
+
+
+  private static final class OrMatcher implements Filter
+  {
+    private final Filter[] subFilters;
+
+
+
+    private OrMatcher(final Filter[] subFilters)
+    {
+      this.subFilters = subFilters;
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void close()
+    {
+      for (final Filter filter : subFilters)
+      {
+        filter.close();
+      }
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public FilterResult filterChangeRecord(final ChangeRecordContext context,
+        final AddRequest change)
+    {
+      for (final Filter subFilter : subFilters)
+      {
+        final FilterResult result = subFilter.filterChangeRecord(context,
+            change);
+        if (result.getAction() != Action.STOP)
+        {
+          return result;
+        }
+      }
+      return FilterResult.stop();
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public FilterResult filterChangeRecord(final ChangeRecordContext context,
+        final DeleteRequest change)
+    {
+      for (final Filter subFilter : subFilters)
+      {
+        final FilterResult result = subFilter.filterChangeRecord(context,
+            change);
+        if (result.getAction() != Action.STOP)
+        {
+          return result;
+        }
+      }
+      return FilterResult.stop();
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public FilterResult filterChangeRecord(final ChangeRecordContext context,
+        final ModifyDNRequest change)
+    {
+      for (final Filter subFilter : subFilters)
+      {
+        final FilterResult result = subFilter.filterChangeRecord(context,
+            change);
+        if (result.getAction() != Action.STOP)
+        {
+          return result;
+        }
+      }
+      return FilterResult.stop();
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public FilterResult filterChangeRecord(final ChangeRecordContext context,
+        final ModifyRequest change)
+    {
+      for (final Filter subFilter : subFilters)
+      {
+        final FilterResult result = subFilter.filterChangeRecord(context,
+            change);
+        if (result.getAction() != Action.STOP)
+        {
+          return result;
+        }
+      }
+      return FilterResult.stop();
+    }
+  }
+
+
+
+  private static final class TargetInScopeMatcher implements Filter
+  {
+    private final DN dn;
+    private final SearchScope scope;
+
+
+
+    private TargetInScopeMatcher(final DN dn, final SearchScope scope)
+    {
+      this.dn = dn;
+      this.scope = scope;
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void close()
+    {
+      // Nothing to do.
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public FilterResult filterChangeRecord(final ChangeRecordContext context,
+        final AddRequest change)
+    {
+      return matchesScope(change);
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public FilterResult filterChangeRecord(final ChangeRecordContext context,
+        final DeleteRequest change)
+    {
+      return matchesScope(change);
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public FilterResult filterChangeRecord(final ChangeRecordContext context,
+        final ModifyDNRequest change)
+    {
+      return matchesScope(change);
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public FilterResult filterChangeRecord(final ChangeRecordContext context,
+        final ModifyRequest change)
+    {
+      return matchesScope(change);
+    }
+
+
+
+    private FilterResult matchesScope(final ChangeRecord change)
+    {
+      if (change.getName().isInScopeOf(dn, scope))
+      {
+        return FilterResult.next();
+      }
+      else
+      {
+        return FilterResult.stop();
+      }
+    }
+  }
+
+
+
+  private static final Filter TRUE = new Filter()
+  {
+
+    @Override
+    public void close()
+    {
+      // Nothing to do.
+    }
+
+
+
+    @Override
+    public FilterResult filterChangeRecord(final ChangeRecordContext context,
+        final AddRequest change)
+    {
+      return FilterResult.next();
+    }
+
+
+
+    @Override
+    public FilterResult filterChangeRecord(final ChangeRecordContext context,
+        final DeleteRequest change)
+    {
+      return FilterResult.next();
+    }
+
+
+
+    @Override
+    public FilterResult filterChangeRecord(final ChangeRecordContext context,
+        final ModifyDNRequest change)
+    {
+      return FilterResult.next();
+    }
+
+
+
+    @Override
+    public FilterResult filterChangeRecord(final ChangeRecordContext context,
+        final ModifyRequest change)
+    {
+      return FilterResult.next();
+    }
+
+  };
+
+  private static final Filter FALSE = new Filter()
+  {
+
+    @Override
+    public void close()
+    {
+      // Nothing to do.
+    }
+
+
+
+    @Override
+    public FilterResult filterChangeRecord(final ChangeRecordContext context,
+        final AddRequest change)
+    {
+      return FilterResult.stop();
+    }
+
+
+
+    @Override
+    public FilterResult filterChangeRecord(final ChangeRecordContext context,
+        final DeleteRequest change)
+    {
+      return FilterResult.stop();
+    }
+
+
+
+    @Override
+    public FilterResult filterChangeRecord(final ChangeRecordContext context,
+        final ModifyDNRequest change)
+    {
+      return FilterResult.stop();
+    }
+
+
+
+    @Override
+    public FilterResult filterChangeRecord(final ChangeRecordContext context,
+        final ModifyRequest change)
+    {
+      return FilterResult.stop();
+    }
+
+  };
+
+
+
+  /**
+   * Returns a matcher which always evaluates to {@code false}.
+   *
+   * @return A matcher which always evaluates to {@code false}.
+   */
+  public static Filter alwaysFalse()
+  {
+    return FALSE;
+  }
+
+
+
+  /**
+   * Returns a matcher which always evaluates to {@code true}.
+   *
+   * @return A matcher which always evaluates to {@code true}.
+   */
+  public static Filter alwaysTrue()
+  {
+    return TRUE;
+  }
+
+
+
+  /**
+   * Returns a matcher which evaluates to {@code true} if and only if all of the
+   * provided matchers evaluate to {@code true}. This method evaluates the
+   * matchers in the order provided and stops as soon as one evaluates to
+   * {@code false}.
+   *
+   * @param matchers
+   *          The matchers whose results must all evaluate to {@code true} in
+   *          order for the returned matcher to evaluate to {@code true}.
+   * @return A matcher which evaluates to {@code true} if and only if all of the
+   *         provided matchers evaluate to {@code true}.
+   */
+  public static Filter and(final Filter... matchers)
+  {
+    return new AndMatcher(matchers);
+  }
+
+
+
+  /**
+   * Returns a matcher which will only evaluate a transformation if a condition
+   * evaluates to {@code true}. This method can be used to construct conditional
+   * if...then... style logic. Note that the returned matcher will evaluate to
+   * {@code true} if the condition evaluated to {@code false}.
+   *
+   * @param condition
+   *          The condition matcher which must evaluate to {@code true} in order
+   *          for the transformation to be evaluated.
+   * @param transformation
+   *          The transformation which will only be evaluated if the condition
+   *          evaluates to {@code true}.
+   * @return A matcher which will only evaluate a transformation if a condition
+   *         evaluates to {@code true}.
+   */
+  public static Filter implies(final Filter condition,
+      final Filter transformation)
+  {
+    return new ImpliesMatcher(condition, transformation);
+  }
+
+
+
+  /**
+   * Returns a matcher which evaluates to {@code true} if and only if the name
+   * of the entity which performed the change exactly matches the provided DN.
+   *
+   * @param dn
+   *          The name of the change initiator.
+   * @return A matcher which evaluates to {@code true} if and only if the name
+   *         of the entity which performed the change exactly matches the
+   *         provided DN.
+   */
+  public static Filter initiatorIsEqualTo(final DN dn)
+  {
+    return new ChangeInitiatorInScopeMatcher(dn, SearchScope.BASE_OBJECT);
+  }
+
+
+
+  /**
+   * Returns a matcher which evaluates to {@code true} if and only if the name
+   * of the entity which performed the change matches the provided base DN and
+   * search scope.
+   *
+   * @param baseDN
+   *          The base DN.
+   * @param scope
+   *          The search scope.
+   * @return A matcher which evaluates to {@code true} if and only if the name
+   *         of the entity which performed the change matches the provided base
+   *         DN and search scope.
+   */
+  public static Filter initiatorMatchesBaseAndScope(final DN baseDN,
+      final SearchScope scope)
+  {
+    return new ChangeInitiatorInScopeMatcher(baseDN, scope);
+  }
+
+
+
+  /**
+   * Returns a matcher which evaluates to {@code true} if the provided matcher
+   * evaluates to {@code false}, and vice versa.
+   *
+   * @param matcher
+   *          The matcher whose result is to be inverted.
+   * @return A matcher which evaluates to {@code true} if the provided matcher
+   *         evaluates to {@code false}, and vice versa.
+   */
+  public static Filter not(final Filter matcher)
+  {
+    return new NotMatcher(matcher);
+  }
+
+
+
+  /**
+   * Returns a matcher which evaluates to {@code false} if and only if all of
+   * the provided matchers evaluate to {@code false}. This method evaluates the
+   * matchers in the order provided and stops as soon as one evaluates to
+   * {@code true}.
+   *
+   * @param matchers
+   *          The matchers whose results must all evaluate to {@code false} in
+   *          order for the returned matcher to evaluate to {@code false}.
+   * @return A matcher which evaluates to {@code false} if and only if all of
+   *         the provided matchers evaluate to {@code false}.
+   */
+  public static Filter or(final Filter... matchers)
+  {
+    return new OrMatcher(matchers);
+  }
+
+
+
+  /**
+   * Returns a matcher which evaluates to {@code true} if and only if the name
+   * of the entry targeted by the change record matches the provided base DN and
+   * search scope.
+   *
+   * @param baseDN
+   *          The base DN.
+   * @param scope
+   *          The search scope.
+   * @return A matcher which evaluates to {@code true} if and only if the name
+   *         of the entry targeted by the change record matches the provided
+   *         base DN and search scope.
+   */
+  public static Filter targetMatchesBaseAndScope(final DN baseDN,
+      final SearchScope scope)
+  {
+    return new TargetInScopeMatcher(baseDN, scope);
+  }
+
+
+
+  private Matchers()
+  {
+    // Prevent instantiation.
+  }
+
+}
diff --git a/opendj3/opendj-ldap-sync/src/main/java/org/forgerock/opendj/sync/filters/Transforms.java b/opendj3/opendj-ldap-sync/src/main/java/org/forgerock/opendj/sync/filters/Transforms.java
new file mode 100644
index 0000000..ad10087
--- /dev/null
+++ b/opendj3/opendj-ldap-sync/src/main/java/org/forgerock/opendj/sync/filters/Transforms.java
@@ -0,0 +1,657 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opendj3/legal-notices/CDDLv1_0.txt
+ * or http://forgerock.org/license/CDDLv1.0.html.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opendj3/legal-notices/CDDLv1_0.txt.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *      Copyright 2011 ForgeRock AS
+ */
+
+package org.forgerock.opendj.sync.filters;
+
+
+
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.ListIterator;
+
+import org.forgerock.i18n.LocalizableMessage;
+import org.forgerock.opendj.ldap.*;
+import org.forgerock.opendj.ldap.requests.AddRequest;
+import org.forgerock.opendj.ldap.requests.DeleteRequest;
+import org.forgerock.opendj.ldap.requests.ModifyDNRequest;
+import org.forgerock.opendj.ldap.requests.ModifyRequest;
+import org.forgerock.opendj.ldif.ChangeRecordWriter;
+
+
+
+/**
+ * Factory methods for constructing common types of transformation filters.
+ */
+public final class Transforms
+{
+  private static final class AlwaysStopTransform implements Filter
+  {
+
+    private final FilterResult result;
+
+
+
+    private AlwaysStopTransform(final LocalizableMessage message)
+    {
+      this.result = FilterResult.stop(message);
+    }
+
+
+
+    @Override
+    public void close()
+    {
+      // Nothing to do.
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public FilterResult filterChangeRecord(final ChangeRecordContext context,
+        final AddRequest change)
+    {
+      return result;
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public FilterResult filterChangeRecord(final ChangeRecordContext context,
+        final DeleteRequest change)
+    {
+      return result;
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public FilterResult filterChangeRecord(final ChangeRecordContext context,
+        final ModifyDNRequest change)
+    {
+      return result;
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public FilterResult filterChangeRecord(final ChangeRecordContext context,
+        final ModifyRequest change)
+    {
+      return result;
+    }
+  }
+
+
+
+  private static final class ChangeRecordWriterTransform implements Filter
+  {
+    private final ChangeRecordWriter writer;
+
+
+
+    private ChangeRecordWriterTransform(final ChangeRecordWriter writer)
+    {
+      this.writer = writer;
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void close()
+    {
+      try
+      {
+        writer.close();
+      }
+      catch (final IOException e)
+      {
+        // FIXME: this could occur while flushing. Log the error?
+      }
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public FilterResult filterChangeRecord(final ChangeRecordContext context,
+        final AddRequest change)
+    {
+      try
+      {
+        writer.writeChangeRecord(change);
+        return FilterResult.next();
+      }
+      catch (final IOException e)
+      {
+        return FilterResult.fatal(e);
+      }
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public FilterResult filterChangeRecord(final ChangeRecordContext context,
+        final DeleteRequest change)
+    {
+      try
+      {
+        writer.writeChangeRecord(change);
+        return FilterResult.next();
+      }
+      catch (final IOException e)
+      {
+        return FilterResult.fatal(e);
+      }
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public FilterResult filterChangeRecord(final ChangeRecordContext context,
+        final ModifyDNRequest change)
+    {
+      try
+      {
+        writer.writeChangeRecord(change);
+        return FilterResult.next();
+      }
+      catch (final IOException e)
+      {
+        return FilterResult.fatal(e);
+      }
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public FilterResult filterChangeRecord(final ChangeRecordContext context,
+        final ModifyRequest change)
+    {
+      try
+      {
+        writer.writeChangeRecord(change);
+        return FilterResult.next();
+      }
+      catch (final IOException e)
+      {
+        return FilterResult.fatal(e);
+      }
+    }
+
+  }
+
+
+
+  private static final class RemoveAttributeTransform implements Filter
+  {
+    private final AttributeDescription name;
+    private final boolean includeSubtypes;
+
+
+
+    private RemoveAttributeTransform(final AttributeDescription name,
+        final boolean includeSubtypes)
+    {
+      this.name = name;
+      this.includeSubtypes = includeSubtypes;
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void close()
+    {
+      // Nothing to do.
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public FilterResult filterChangeRecord(final ChangeRecordContext context,
+        final AddRequest change)
+    {
+      if (includeSubtypes)
+      {
+        final Iterator<Attribute> i = change.getAllAttributes(name).iterator();
+        while (i.hasNext())
+        {
+          i.next();
+          i.remove();
+        }
+      }
+      else
+      {
+        change.removeAttribute(name);
+      }
+      return FilterResult.next();
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public FilterResult filterChangeRecord(final ChangeRecordContext context,
+        final DeleteRequest change)
+    {
+      return FilterResult.next();
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public FilterResult filterChangeRecord(final ChangeRecordContext context,
+        final ModifyDNRequest change)
+    {
+      return FilterResult.next();
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public FilterResult filterChangeRecord(final ChangeRecordContext context,
+        final ModifyRequest change)
+    {
+      final Iterator<Modification> i = change.getModifications().iterator();
+      while (i.hasNext())
+      {
+        final Modification modification = i.next();
+        final Attribute attribute = modification.getAttribute();
+        if (includeSubtypes)
+        {
+          if (attribute.getAttributeDescription().isSubTypeOf(name))
+          {
+            i.remove();
+          }
+        }
+        else
+        {
+          if (attribute.getAttributeDescription().equals(name))
+          {
+            i.remove();
+          }
+        }
+      }
+      return FilterResult.next();
+    }
+
+  }
+
+
+
+  private static final class RenameAttributeTransform implements Filter
+  {
+    // TODO: do we want to rename attributes in the DN?
+
+    private final AttributeDescription from;
+    private final AttributeDescription to;
+
+
+
+    private RenameAttributeTransform(final AttributeDescription from,
+        final AttributeDescription to)
+    {
+      this.from = from;
+      this.to = to;
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void close()
+    {
+      // Nothing to do.
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public FilterResult filterChangeRecord(final ChangeRecordContext context,
+        final AddRequest change)
+    {
+      final Attribute attribute = change.getAttribute(from);
+      if (attribute != null)
+      {
+        change.removeAttribute(from);
+        change.addAttribute(Attributes.renameAttribute(attribute, to));
+      }
+      return FilterResult.next();
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public FilterResult filterChangeRecord(final ChangeRecordContext context,
+        final DeleteRequest change)
+    {
+      return FilterResult.next();
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public FilterResult filterChangeRecord(final ChangeRecordContext context,
+        final ModifyDNRequest change)
+    {
+      return FilterResult.next();
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public FilterResult filterChangeRecord(final ChangeRecordContext context,
+        final ModifyRequest change)
+    {
+      final ListIterator<Modification> i = change.getModifications()
+          .listIterator();
+      while (i.hasNext())
+      {
+        final Modification modification = i.next();
+        final Attribute attribute = modification.getAttribute();
+        if (attribute.getAttributeDescription().equals(from))
+        {
+          i.set(new Modification(modification.getModificationType(), Attributes
+              .renameAttribute(attribute, to)));
+        }
+      }
+      return FilterResult.next();
+    }
+
+  }
+
+
+
+  private static final class RenameEntryTransform implements Filter
+  {
+    private final DN from;
+    private final DN to;
+
+
+
+    private RenameEntryTransform(final DN from, final DN to)
+    {
+      this.from = from;
+      this.to = to;
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void close()
+    {
+      // Nothing to do.
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public FilterResult filterChangeRecord(final ChangeRecordContext context,
+        final AddRequest change)
+    {
+      change.setName(change.getName().rename(from, to));
+      return FilterResult.next();
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public FilterResult filterChangeRecord(final ChangeRecordContext context,
+        final DeleteRequest change)
+    {
+      change.setName(change.getName().rename(from, to));
+      return FilterResult.next();
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public FilterResult filterChangeRecord(final ChangeRecordContext context,
+        final ModifyDNRequest change)
+    {
+      change.setName(change.getName().rename(from, to));
+      return FilterResult.next();
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public FilterResult filterChangeRecord(final ChangeRecordContext context,
+        final ModifyRequest change)
+    {
+      change.setName(change.getName().rename(from, to));
+      return FilterResult.next();
+    }
+
+  }
+
+
+
+  private static final Filter ALWAYS_STOP = new AlwaysStopTransform(null);
+
+
+
+  /**
+   * Returns a transformation which always drops changes. This transformation
+   * can be combined with matches in order to selectively drop certain changes.
+   *
+   * @return A transformation which always drops changes.
+   */
+  public static Filter alwaysStop()
+  {
+    return ALWAYS_STOP;
+  }
+
+
+
+  /**
+   * Returns a transformation which always drops changes. This transformation
+   * can be combined with matches in order to selectively drop certain changes.
+   *
+   * @param reason
+   *          The reason why the change is being dropped.
+   * @return A transformation which always drops changes.
+   */
+  public static Filter alwaysStop(final LocalizableMessage reason)
+  {
+    return new AlwaysStopTransform(reason);
+  }
+
+
+
+  /**
+   * Returns a transformation which writers change records to the provided
+   * writer.
+   *
+   * @param writer
+   *          The change record writer.
+   * @return A transformation which writers change records to the provided
+   *         writer.
+   */
+  public static Filter changeRecordWriter(final ChangeRecordWriter writer)
+  {
+    return new ChangeRecordWriterTransform(writer);
+  }
+
+
+
+  /**
+   * Returns a transformation which will remove the named attribute from Add and
+   * Modify operations if it is present. Note that only the named attribute will
+   * be removed and not its sub-types.
+   *
+   * @param name
+   *          The name of the attribute to be removed.
+   * @return A transformation which will remove the named attribute from Add and
+   *         Modify operations if it is present.
+   */
+  public static Filter removeAttribute(final AttributeDescription name)
+  {
+    return new RemoveAttributeTransform(name, false);
+  }
+
+
+
+  /**
+   * Returns a transformation which will remove the named attribute and its
+   * sub-types from Add and Modify operations if it is present.
+   *
+   * @param name
+   *          The name of the attribute and sub-types to be removed.
+   * @return A transformation which will remove the named attribute and its
+   *         sub-types from Add and Modify operations if it is present.
+   */
+  public static Filter removeAttributeAndSubtypes(
+      final AttributeDescription name)
+  {
+    return new RemoveAttributeTransform(name, true);
+  }
+
+
+
+  /**
+   * Returns a transformation which will rename attributes contained in Add and
+   * Modify operations.
+   *
+   * @param from
+   *          The name of the attribute to be renamed.
+   * @param to
+   *          The new name of the renamed attribute.
+   * @return A transformation which will rename attributes contained in Add and
+   *         Modify operations.
+   */
+  public static Filter renameAttribute(final AttributeDescription from,
+      final AttributeDescription to)
+  {
+    return new RenameAttributeTransform(from, to);
+  }
+
+
+
+  /**
+   * Returns a transformation which will rename the operation target DN.
+   *
+   * @param from
+   *          The source base DN.
+   * @param to
+   *          The target base DN.
+   * @return A transformation which will rename the operation target DN.
+   */
+  public static Filter renameEntry(final DN from, final DN to)
+  {
+    return new RenameEntryTransform(from, to);
+  }
+
+
+
+  /**
+   * Returns a transformation which will delegate transformation to the provided
+   * Groovy script.
+   *
+   * @return A transformation which will delegate transformation to the provided
+   *         Groovy script.
+   */
+  public static Filter transformUsingGroovy()
+  {
+    // TODO:
+    return null;
+  }
+
+
+
+  private Transforms()
+  {
+    // Prevent instantiation.
+  }
+}
diff --git a/opendj3/opendj-ldap-sync/src/main/java/org/forgerock/opendj/sync/filters/package-info.java b/opendj3/opendj-ldap-sync/src/main/java/org/forgerock/opendj/sync/filters/package-info.java
new file mode 100755
index 0000000..0808cb5
--- /dev/null
+++ b/opendj3/opendj-ldap-sync/src/main/java/org/forgerock/opendj/sync/filters/package-info.java
@@ -0,0 +1,34 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opendj3/legal-notices/CDDLv1_0.txt
+ * or http://forgerock.org/license/CDDLv1.0.html.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opendj3/legal-notices/CDDLv1_0.txt.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2011 ForgeRock AS.
+ */
+
+/**
+ * LDAP synchronization daemon core implementation classes.
+ */
+package org.forgerock.opendj.sync.filters;
+
+
+
diff --git a/opendj3/opendj-ldap-sync/src/main/java/org/forgerock/opendj/sync/package-info.java b/opendj3/opendj-ldap-sync/src/main/java/org/forgerock/opendj/sync/package-info.java
new file mode 100755
index 0000000..363cfc1
--- /dev/null
+++ b/opendj3/opendj-ldap-sync/src/main/java/org/forgerock/opendj/sync/package-info.java
@@ -0,0 +1,37 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opendj3/legal-notices/CDDLv1_0.txt
+ * or http://forgerock.org/license/CDDLv1.0.html.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opendj3/legal-notices/CDDLv1_0.txt.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2011 ForgeRock AS.
+ */
+
+/**
+ * An LDAP synchronization daemon which can be used synchronize changes from
+ * a source directory to a destination directory. The daemon reads the source
+ * directory's change log and forwards changes to the destination directory,
+ * while possibly applying filters and transformations.
+ */
+package org.forgerock.opendj.sync;
+
+
+
diff --git a/opendj3/pom.xml b/opendj3/pom.xml
index 1424c85..bfe24ac 100644
--- a/opendj3/pom.xml
+++ b/opendj3/pom.xml
@@ -95,6 +95,7 @@
     <module>opendj-build-tools</module>
     <module>opendj-ldap-sdk</module>
     <module>opendj-ldap-toolkit</module>
+    <module>opendj-ldap-sync</module>
     <module>opendj-ldap-sdk-examples</module>
   </modules>
   <properties>

--
Gitblit v1.10.0