mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

abobrov
13.03.2009 27ede8298aee9ed2b6f83863173ba2415189b4f6
- land NDB Backend implementation.
28 files added
11 files modified
13954 ■■■■■ changed files
opends/build.xml 117 ●●●● patch | view | raw | blame | history
opends/resource/config/ndbconfig.ldif 30 ●●●●● patch | view | raw | blame | history
opends/resource/schema/02-config.ldif 75 ●●●●● patch | view | raw | blame | history
opends/src/admin/defn/org/opends/server/admin/std/NdbBackendConfiguration.xml 400 ●●●●● patch | view | raw | blame | history
opends/src/admin/defn/org/opends/server/admin/std/NdbIndexConfiguration.xml 187 ●●●●● patch | view | raw | blame | history
opends/src/messages/messages/ndb.properties 247 ●●●●● patch | view | raw | blame | history
opends/src/messages/src/org/opends/messages/Category.java 7 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/ndb/AbstractTransaction.java 188 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/ndb/BackendImpl.java 2101 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/ndb/DatabaseContainer.java 83 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/ndb/EntryContainer.java 2205 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/ndb/ExportJob.java 293 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/ndb/IndexFilter.java 244 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/ndb/NDBException.java 76 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/ndb/OperationContainer.java 1708 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/ndb/RootContainer.java 499 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/ndb/importLDIF/DNContext.java 363 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/ndb/importLDIF/Importer.java 643 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/ndb/importLDIF/WorkElement.java 104 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/ndb/importLDIF/WorkThread.java 240 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/ndb/importLDIF/package-info.java 36 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/ndb/package-info.java 37 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendAddOperation.java 60 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendBindOperation.java 66 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendCompareOperation.java 30 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendDeleteOperation.java 55 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendModifyDNOperation.java 71 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendModifyOperation.java 119 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendSearchOperation.java 44 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendWorkflowElement.java 7 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/workflowelement/ndb/NDBAddOperation.java 661 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/workflowelement/ndb/NDBBindOperation.java 251 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/workflowelement/ndb/NDBCompareOperation.java 309 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/workflowelement/ndb/NDBDeleteOperation.java 428 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/workflowelement/ndb/NDBModifyDNOperation.java 532 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/workflowelement/ndb/NDBModifyOperation.java 529 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/workflowelement/ndb/NDBSearchOperation.java 437 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/workflowelement/ndb/NDBWorkflowElement.java 436 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/workflowelement/ndb/package-info.java 36 ●●●●● patch | view | raw | blame | history
opends/build.xml
@@ -22,7 +22,7 @@
 ! CDDL HEADER END
 !
 !
 !      Copyright 2006-2008 Sun Microsystems, Inc.
 !      Copyright 2006-2009 Sun Microsystems, Inc.
 ! -->
<project name="Directory Server" basedir="." default="package">
@@ -41,6 +41,7 @@
  <property name="src.dir"          location="src/server"              />
  <property name="build.dir"        location="build"                   />
  <property name="classes.dir"      location="${build.dir}/classes"    />
  <property name="build.lib.dir"    location="${build.dir}/lib"        />
  <property name="lib.dir"          location="lib"                     />
  <property name="ext.dir"          location="ext"                     />
  <property name="package.dir"      location="${build.dir}/package"    />
@@ -161,6 +162,23 @@
  <property name="snmp.classes.dir"
            location="${classes.dir}/org/opends/server/snmp" />
  <!-- Condition properties for NDB Backend build. -->
  <condition property="ismysqldirpresent">
    <available file="${mysql.lib.dir}" type="dir" />
  </condition>
  <condition property="exclude.ndb.xml" value=""
             else="**/Ndb*">
             <available file="${mysql.lib.dir}" type="dir" />
  </condition>
  <condition property="exclude.ndb.src" value=""
             else="org/opends/server/backends/ndb/**,
                   org/opends/server/workflowelement/ndb/**">
             <available file="${mysql.lib.dir}" type="dir" />
  </condition>
  <!-- Property for excluding NDB Backend config. -->
  <property name="exclude.ndb.config" value="ndbconfig.ldif" />
  <!-- Create a package bundle containing the DSML library. -->
  <target name="dsml" depends="predsml,package"
       description="Build a Directory Server package bundle with DSML.">
@@ -288,8 +306,25 @@
    <genmsg sourceProps="${msg.prop.dir}/servicetag.properties"
            destJava="${msg.javagen.dir}/org/opends/messages/ServiceTagMessages.java">
    </genmsg>
    <antcall target="generatendbmessages" />
  </target>
  <!-- Generate NDB Backend messages if needed -->
  <target name="generatendbmessages" if="ismysqldirpresent">
    <typedef name="genmsg"
             classname="org.opends.build.tools.GenerateMessageFile" >
      <classpath>
        <fileset dir="${build.dir}/build-tools">
          <include name="*.jar" />
        </fileset>
      </classpath>
    </typedef>
    <genmsg sourceProps="${msg.prop.dir}/ndb.properties"
            destJava="${msg.javagen.dir}/org/opends/messages/NdbMessages.java">
    </genmsg>
  </target>
  <!-- Remove all dynamically-generated build files. -->
  <target name="clean" depends="init,cleanadmin,cleanmessages,cleansnmp"
@@ -575,9 +610,13 @@
       depends="init,checkjavaversion,dynamicconstants,generatemessages,compileadmin"
       description="Compile the Directory Server source files.">
    <mkdir dir="${classes.dir}" />
    <mkdir dir="${build.lib.dir}" />
    <!-- Copy NDB Backend dependencies if necessary -->
    <antcall target="copyndbdeps" />
    <javac srcdir="${src.dir}:${admin.src.dir}:${msg.src.dir}:${msg.javagen.dir}:${ads.src.dir}:${quicksetup.src.dir}:${guitools.src.dir}"
         destdir="${classes.dir}" debug="on" debuglevel="${build.debuglevel}"
         destdir="${classes.dir}" excludes="${exclude.ndb.src}" debug="on" debuglevel="${build.debuglevel}"
         source="1.5" target="1.5" deprecation="true" fork="true"
         memoryInitialSize="${MEM}" memoryMaximumSize="${MEM}">
      <compilerarg value="-Xlint:all" />
@@ -589,6 +628,9 @@
        <fileset dir="${build.dir}/build-tools">
          <include name="build-tools.jar" />
        </fileset>
        <fileset dir="${build.lib.dir}">
          <include name="*.jar" />
        </fileset>
      </classpath>
    </javac>
@@ -743,10 +785,13 @@
    <!-- Regenerate configuration files if necessary -->
    <antcall target="compileadmin" />
    <!-- Copy NDB Backend dependencies if necessary -->
    <antcall target="copyndbdeps" />
    <!-- Recreate the classes directory and recompile into it. -->
    <mkdir dir="${classes.dir}" />
    <javac srcdir="${src.dir}:${msg.src.dir}:${msg.javagen.dir}:${admin.src.dir}:${ads.src.dir}:${quicksetup.src.dir}:${guitools.src.dir}"
         destdir="${classes.dir}"
         destdir="${classes.dir}" excludes="${exclude.ndb.src}"
         debug="on" debuglevel="${build.debuglevel}" source="1.5" target="1.5"
         deprecation="true" fork="true" memoryInitialSize="${MEM}"
         memoryMaximumSize="${MEM}">
@@ -759,6 +804,9 @@
        <fileset dir="${build.dir}/build-tools">
          <include name="build-tools.jar" />
        </fileset>
        <fileset dir="${build.lib.dir}">
          <include name="*.jar" />
        </fileset>
      </classpath>
    </javac>
@@ -830,6 +878,11 @@
    <jar jarfile="${pdir}/lib/quicksetup.jar"
         basedir="${quicksetup.classes.dir}" compress="true" index="true" />
    <!-- Copy over external dependencies. -->
    <copy todir="${pdir}/lib">
      <fileset file="${build.lib.dir}/*.jar" />
    </copy>
    <!-- Regenerate example plugin. -->
    <antcall target="example-plugin" />
  </target>
@@ -965,11 +1018,13 @@
    <fixcrlf srcDir="${scripts.dir}" destDir="${pdir}/lib" includes="_client-script.bat,_server-script.bat,_mixed-script.bat,_script-util.bat,setcp.bat" eol="crlf" />
    <copy todir="${pdir}/config">
      <fileset file="${config.dir}/*" />
      <fileset file="${config.dir}/*" excludes="${exclude.ndb.config}" />
    </copy>
    <antcall target="package-snmp" />
    <antcall target="packagendb" />
    <copy file="${pdir}/config/config.ldif"
         tofile="${pdir}/config/upgrade/config.ldif.${REVISION_NUMBER}" />
@@ -1272,7 +1327,7 @@
        <dirset dir="${quicksetup.classes.dir}" />
      </classpath>
      <packageset dir="${src.dir}" />
      <packageset dir="${src.dir}" excludes="${exclude.ndb.src}" />
      <packageset dir="${admin.src.dir}" />
      <packageset dir="${ads.src.dir}" />
      <packageset dir="${dsml.src.dir}" />
@@ -1433,8 +1488,11 @@
    <echo message="Performing partial rebuild (OpenDS zip package found)"/>
    <mkdir dir="${classes.dir}" />
    <!-- Copy NDB Backend dependencies if necessary -->
    <antcall target="copyndbdeps" />
    <javac srcdir="${src.dir}:${admin.src.dir}:${msg.src.dir}:${msg.javagen.dir}:${ads.src.dir}:${quicksetup.src.dir}:${guitools.src.dir}"
        destdir="${classes.dir}" debug="on" debuglevel="${build.debuglevel}"
        destdir="${classes.dir}" excludes="${exclude.ndb.src}" debug="on" debuglevel="${build.debuglevel}"
        source="1.5" target="1.5" deprecation="true" fork="true"
        memoryInitialSize="${MEM}" memoryMaximumSize="${MEM}">
      <compilerarg value="-Xlint:all" />
@@ -1446,6 +1504,9 @@
        <fileset dir="${build.dir}/build-tools">
          <include name="build-tools.jar" />
        </fileset>
        <fileset dir="${build.lib.dir}">
          <include name="*.jar" />
        </fileset>
      </classpath>
    </javac>
@@ -2146,6 +2207,7 @@
      <arg value="-buildfile" />
      <arg value="${ant.file}" />
      <arg value="-quiet" />
      <arg value="-Dexclude.ndb.xml=${exclude.ndb.xml}" />
      <arg value="compileadminsubtask" />
      <env key="ANT_OPTS" value="-Xmx${MEM}" />
    </exec>
@@ -2155,7 +2217,8 @@
  <target name="compileadminsubtask">
    <!-- Generate introspection API for core administration components. -->
    <xslt basedir="${admin.defn.dir}" destdir="${admin.src.dir}" includes="**/*Configuration.xml" style="${admin.rules.dir}/metaMO.xsl">
    <xslt basedir="${admin.defn.dir}" destdir="${admin.src.dir}" includes="**/*Configuration.xml"
          excludes="${exclude.ndb.xml}" style="${admin.rules.dir}/metaMO.xsl">
      <regexpmapper handledirsep="true" from="^(.*)/([^/]+)Configuration\.xml$$" to="\1/meta/\2CfgDefn.java" />
      <param name="base-dir" expression="${admin.defn.dir}" />
    </xslt>
@@ -2166,7 +2229,8 @@
    </xslt>
    <!-- Generate client API for core administration components. -->
    <xslt basedir="${admin.defn.dir}" destdir="${admin.src.dir}" includes="**/*Configuration.xml" style="${admin.rules.dir}/clientMO.xsl">
    <xslt basedir="${admin.defn.dir}" destdir="${admin.src.dir}" includes="**/*Configuration.xml"
          excludes="${exclude.ndb.xml}" style="${admin.rules.dir}/clientMO.xsl">
      <regexpmapper handledirsep="true" from="^(.*)/([^/]+)Configuration\.xml$$" to="\1/client/\2CfgClient.java" />
      <param name="base-dir" expression="${admin.defn.dir}" />
    </xslt>
@@ -2177,7 +2241,8 @@
    </xslt>
    <!-- Generate server API for core administration components. -->
    <xslt basedir="${admin.defn.dir}" destdir="${admin.src.dir}" includes="**/*Configuration.xml" style="${admin.rules.dir}/serverMO.xsl">
    <xslt basedir="${admin.defn.dir}" destdir="${admin.src.dir}" includes="**/*Configuration.xml"
          excludes="${exclude.ndb.xml}" style="${admin.rules.dir}/serverMO.xsl">
      <regexpmapper handledirsep="true" from="^(.*)/([^/]+)Configuration\.xml$$" to="\1/server/\2Cfg.java" />
      <param name="base-dir" expression="${admin.defn.dir}" />
    </xslt>
@@ -2189,19 +2254,22 @@
    <!-- Generate LDAP profile for core administration components. -->
    <mkdir dir="${classes.dir}" />
    <xslt basedir="${admin.defn.dir}" destdir="${classes.dir}/admin/profiles/ldap" includes="**/*Configuration.xml" style="${admin.rules.dir}/ldapMOProfile.xsl">
    <xslt basedir="${admin.defn.dir}" destdir="${classes.dir}/admin/profiles/ldap" includes="**/*Configuration.xml"
          excludes="${exclude.ndb.xml}" style="${admin.rules.dir}/ldapMOProfile.xsl">
      <regexpmapper handledirsep="true" from="^(.*)/([^/]+)Configuration\.xml$$" to="\1/meta/\2CfgDefn.properties" />
      <param name="base-dir" expression="${admin.defn.dir}" />
    </xslt>
    <!-- Generate CLI profile for core administration components. -->
    <xslt basedir="${admin.defn.dir}" destdir="${classes.dir}/admin/profiles/cli" includes="**/*Configuration.xml" style="${admin.rules.dir}/cliMOProfile.xsl">
    <xslt basedir="${admin.defn.dir}" destdir="${classes.dir}/admin/profiles/cli" includes="**/*Configuration.xml"
          excludes="${exclude.ndb.xml}" style="${admin.rules.dir}/cliMOProfile.xsl">
      <regexpmapper handledirsep="true" from="^(.*)/([^/]+)Configuration\.xml$$" to="\1/meta/\2CfgDefn.properties" />
      <param name="base-dir" expression="${admin.defn.dir}" />
    </xslt>
    <!-- Generate I18N messages for core administration components. -->
    <xslt basedir="${admin.defn.dir}" destdir="${classes.dir}/admin/messages" includes="**/*Configuration.xml" style="${admin.rules.dir}/messagesMO.xsl">
    <xslt basedir="${admin.defn.dir}" destdir="${classes.dir}/admin/messages" includes="**/*Configuration.xml"
          excludes="${exclude.ndb.xml}" style="${admin.rules.dir}/messagesMO.xsl">
      <regexpmapper handledirsep="true" from="^(.*)/([^/]+)Configuration\.xml$$" to="\1/meta/\2CfgDefn.properties" />
      <param name="base-dir" expression="${admin.defn.dir}" />
    </xslt>
@@ -2209,7 +2277,8 @@
    <!-- Generate manifest file for core administration components. -->
    <tempfile property="admin.temp.dir" destDir="${build.dir}" prefix="tmp" />
    <mkdir dir="${admin.temp.dir}" />
    <xslt basedir="${admin.defn.dir}" destdir="${admin.temp.dir}" extension=".manifest" includes="**/*Configuration.xml" style="${admin.rules.dir}/manifestMO.xsl"/>
    <xslt basedir="${admin.defn.dir}" destdir="${admin.temp.dir}" extension=".manifest" includes="**/*Configuration.xml"
          excludes="${exclude.ndb.xml}" style="${admin.rules.dir}/manifestMO.xsl"/>
    <concat destfile="${classes.dir}/admin/core.manifest">
      <fileset dir="${admin.temp.dir}" includes="**/*.manifest" />
    </concat>
@@ -2437,4 +2506,26 @@
  <import file="build-svr4.xml"/>
  <!-- Copy NDB Backend dependencies to build lib directory -->
  <target name="copyndbdeps" if="ismysqldirpresent"
    description="Internal target to copy NDB Backend dependencies">
    <!-- Blanket copy of all jars found at mysql.lib location -->
    <copy todir="${build.lib.dir}">
      <fileset file="${mysql.lib.dir}/*.jar" />
    </copy>
  </target>
  <!-- Package NDB Backend with Directory Server distribution -->
  <target name="packagendb" if="ismysqldirpresent"
    description="Internal target to package NDB Backend dependencies">
    <echo message="Packaging with NDB Backend dependencies"/>
    <copy todir="${pdir}/lib">
      <fileset file="${mysql.lib.dir}/*.jar" />
    </copy>
    <!-- Concat NDB Backend config entry to default config -->
    <concat destfile="${pdir}/config/config.ldif" append="true">
        <filelist dir="${config.dir}" files="ndbconfig.ldif"/>
    </concat>
  </target>
</project>
opends/resource/config/ndbconfig.ldif
New file
@@ -0,0 +1,30 @@
dn: ds-cfg-backend-id=ndbRoot,cn=Backends,cn=config
objectClass: top
objectClass: ds-cfg-backend
objectClass: ds-cfg-ndb-backend
ds-cfg-enabled: false
ds-cfg-java-class: org.opends.server.backends.ndb.BackendImpl
ds-cfg-backend-id: ndbRoot
ds-cfg-writability-mode: enabled
ds-cfg-base-dn: dc=example,dc=com
ds-cfg-ndb-connect-string: localhost
ds-cfg-sql-connect-string: localhost
ds-cfg-ndb-dbname: ldap
ds-cfg-sql-user: root
ds-cfg-ndb-attr-len: 128
ds-cfg-ndb-attr-blob: audio
ds-cfg-ndb-attr-blob: photo
ds-cfg-ndb-attr-blob: jpegPhoto
ds-cfg-ndb-attr-blob: personalSignature
ds-cfg-ndb-attr-blob: userCertificate
ds-cfg-ndb-attr-blob: caCertificate
ds-cfg-ndb-attr-blob: authorityRevocationList
ds-cfg-ndb-attr-blob: certificateRevocationList
ds-cfg-ndb-attr-blob: crossCertificatePair
ds-cfg-ndb-attr-blob: userSMIMECertificate
ds-cfg-ndb-attr-blob: userPKCS12
ds-cfg-ndb-thread-count: 24
ds-cfg-ndb-num-connections: 4
ds-cfg-deadlock-retry-limit: 10
opends/resource/schema/02-config.ldif
@@ -1095,6 +1095,11 @@
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.7
  SINGLE-VALUE
  X-ORIGIN 'OpenDS Directory Server' )
attributeTypes: ( 1.3.6.1.4.1.26027.1.1.225
  NAME 'ds-cfg-deadlock-retry-limit'
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
  SINGLE-VALUE
  X-ORIGIN 'OpenDS Directory Server' )
attributeTypes: ( 1.3.6.1.4.1.26027.1.1.226
  NAME 'ds-cfg-db-evictor-lru-only'
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.7
@@ -2323,6 +2328,50 @@
  NAME 'ds-cfg-collation'
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
  X-ORIGIN 'OpenDS Directory Server' )
attributeTypes: ( 1.3.6.1.4.1.26027.1.1.501
  NAME 'ds-cfg-ndb-connect-string'
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
  SINGLE-VALUE
  X-ORIGIN 'OpenDS Directory Server' )
attributeTypes: ( 1.3.6.1.4.1.26027.1.1.502
  NAME 'ds-cfg-ndb-thread-count'
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
  SINGLE-VALUE
  X-ORIGIN 'OpenDS Directory Server' )
attributeTypes: ( 1.3.6.1.4.1.26027.1.1.503
  NAME 'ds-cfg-ndb-num-connections'
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
  SINGLE-VALUE
  X-ORIGIN 'OpenDS Directory Server' )
attributeTypes: ( 1.3.6.1.4.1.26027.1.1.504
  NAME 'ds-cfg-sql-user'
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
  SINGLE-VALUE
  X-ORIGIN 'OpenDS Directory Server' )
attributeTypes: ( 1.3.6.1.4.1.26027.1.1.505
  NAME 'ds-cfg-sql-passwd'
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
  SINGLE-VALUE
  X-ORIGIN 'OpenDS Directory Server' )
attributeTypes: ( 1.3.6.1.4.1.26027.1.1.506
  NAME 'ds-cfg-ndb-dbname'
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
  SINGLE-VALUE
  X-ORIGIN 'OpenDS Directory Server' )
attributeTypes: ( 1.3.6.1.4.1.26027.1.1.507
  NAME 'ds-cfg-ndb-attr-len'
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
  SINGLE-VALUE
  X-ORIGIN 'OpenDS Directory Server' )
attributeTypes: ( 1.3.6.1.4.1.26027.1.1.508
  NAME 'ds-cfg-ndb-attr-blob'
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
  X-ORIGIN 'OpenDS Directory Server' )
attributeTypes: ( 1.3.6.1.4.1.26027.1.1.509
  NAME 'ds-cfg-sql-connect-string'
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
  SINGLE-VALUE
  X-ORIGIN 'OpenDS Directory Server' )
attributeTypes: ( 1.3.6.1.4.1.26027.1.1.511
  NAME 'ds-cfg-quality-of-protection'
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
@@ -3943,6 +3992,32 @@
  MUST ( ds-cfg-matching-rule-type $
        ds-cfg-collation )
  X-ORIGIN 'OpenDS Directory Server' )
objectClasses: ( 1.3.6.1.4.1.26027.1.2.196
  NAME 'ds-cfg-ndb-backend'
  SUP ds-cfg-backend
  STRUCTURAL
  MUST ( ds-cfg-ndb-connect-string $
         ds-cfg-sql-connect-string $
         ds-cfg-ndb-dbname )
  MAY ( ds-cfg-sql-user $
        ds-cfg-sql-passwd $
        ds-cfg-ndb-attr-len $
        ds-cfg-ndb-attr-blob $
        ds-cfg-ndb-thread-count $
        ds-cfg-ndb-num-connections $
        ds-cfg-deadlock-retry-limit $
        ds-cfg-import-queue-size $
        ds-cfg-import-thread-count )
  X-ORIGIN 'OpenDS Directory Server' )
objectClasses: ( 1.3.6.1.4.1.26027.1.2.197
  NAME 'ds-cfg-ndb-index'
  SUP top
  STRUCTURAL
  MUST ( ds-cfg-attribute $
         ds-cfg-index-type )
  MAY ( ds-cfg-index-entry-limit $
        ds-cfg-substring-length )
  X-ORIGIN 'OpenDS Directory Server' )
objectClasses: ( 1.3.6.1.4.1.26027.1.2.950
  NAME 'ds-mon-branch'
  SUP top
opends/src/admin/defn/org/opends/server/admin/std/NdbBackendConfiguration.xml
New file
@@ -0,0 +1,400 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
  ! 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/opends/resource/legal-notices/OpenDS.LICENSE
  ! or https://OpenDS.dev.java.net/OpenDS.LICENSE.
  ! 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/opends/resource/legal-notices/OpenDS.LICENSE.  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.
  ! -->
<adm:managed-object name="ndb-backend"
  plural-name="ndb-backends" package="org.opends.server.admin.std"
  extends="backend" xmlns:adm="http://www.opends.org/admin"
  xmlns:ldap="http://www.opends.org/admin-ldap"
  xmlns:cli="http://www.opends.org/admin-cli">
  <adm:synopsis>
    The
    <adm:user-friendly-name />
    uses the NDB to store user-provided data.
  </adm:synopsis>
  <adm:description>
    The
    <adm:user-friendly-name />
    stores the entries in NDB Cluster using shared data model
    which allows for simultanious LDAP/SQL datastore access.
  </adm:description>
  <adm:profile name="ldap">
    <ldap:object-class>
      <ldap:name>ds-cfg-ndb-backend</ldap:name>
      <ldap:superior>ds-cfg-backend</ldap:superior>
    </ldap:object-class>
  </adm:profile>
  <adm:relation name="ndb-index">
    <adm:one-to-many naming-property="attribute">
      <adm:default-managed-object name="aci">
        <adm:property name="index-type">
          <adm:value>presence</adm:value>
        </adm:property>
        <adm:property name="attribute">
          <adm:value>aci</adm:value>
        </adm:property>
      </adm:default-managed-object>
      <adm:default-managed-object name="entryUUID">
        <adm:property name="index-type">
          <adm:value>equality</adm:value>
        </adm:property>
        <adm:property name="attribute">
          <adm:value>entryUUID</adm:value>
        </adm:property>
      </adm:default-managed-object>
      <adm:default-managed-object name="objectClass">
        <adm:property name="index-type">
          <adm:value>equality</adm:value>
        </adm:property>
        <adm:property name="attribute">
          <adm:value>objectClass</adm:value>
        </adm:property>
      </adm:default-managed-object>
      <adm:default-managed-object name="ds-sync-hist">
        <adm:property name="index-type">
          <adm:value>ordering</adm:value>
        </adm:property>
        <adm:property name="attribute">
          <adm:value>ds-sync-hist</adm:value>
        </adm:property>
      </adm:default-managed-object>
    </adm:one-to-many>
    <adm:profile name="ldap">
      <ldap:rdn-sequence>cn=Index</ldap:rdn-sequence>
    </adm:profile>
    <adm:profile name="cli">
      <cli:relation>
        <cli:default-property name="index-type" />
      </cli:relation>
    </adm:profile>
  </adm:relation>
  <adm:property-override name="java-class" advanced="true">
    <adm:default-behavior>
      <adm:defined>
        <adm:value>
          org.opends.server.backends.ndb.BackendImpl
        </adm:value>
      </adm:defined>
    </adm:default-behavior>
  </adm:property-override>
  <adm:property name="ndb-connect-string" mandatory="true">
    <adm:synopsis>
      Specifies the NDB connect string.
    </adm:synopsis>
    <adm:description>
      IP addresses or hostnames with portnumbers may be provided.
    </adm:description>
    <adm:requires-admin-action>
      <adm:component-restart />
    </adm:requires-admin-action>
    <adm:default-behavior>
      <adm:defined>
        <adm:value>localhost</adm:value>
      </adm:defined>
    </adm:default-behavior>
    <adm:syntax>
      <adm:string />
    </adm:syntax>
    <adm:profile name="ldap">
      <ldap:attribute>
        <ldap:name>ds-cfg-ndb-connect-string</ldap:name>
      </ldap:attribute>
    </adm:profile>
  </adm:property>
  <adm:property name="sql-connect-string" mandatory="true">
    <adm:synopsis>
      Specifies the SQL connect string.
    </adm:synopsis>
    <adm:description>
      IP addresses or hostnames with portnumbers may be provided.
    </adm:description>
    <adm:requires-admin-action>
      <adm:component-restart />
    </adm:requires-admin-action>
    <adm:default-behavior>
      <adm:defined>
        <adm:value>localhost</adm:value>
      </adm:defined>
    </adm:default-behavior>
    <adm:syntax>
      <adm:string />
    </adm:syntax>
    <adm:profile name="ldap">
      <ldap:attribute>
        <ldap:name>ds-cfg-sql-connect-string</ldap:name>
      </ldap:attribute>
    </adm:profile>
  </adm:property>
  <adm:property name="sql-user">
    <adm:synopsis>
      Specifies the SQL database user on whose behalf
      the connection is being made.
    </adm:synopsis>
    <adm:description>
      SQL user name may be provided.
    </adm:description>
    <adm:requires-admin-action>
      <adm:component-restart />
    </adm:requires-admin-action>
    <adm:default-behavior>
      <adm:defined>
        <adm:value>root</adm:value>
      </adm:defined>
    </adm:default-behavior>
    <adm:syntax>
      <adm:string />
    </adm:syntax>
    <adm:profile name="ldap">
      <ldap:attribute>
        <ldap:name>ds-cfg-sql-user</ldap:name>
      </ldap:attribute>
    </adm:profile>
  </adm:property>
  <adm:property name="sql-passwd">
    <adm:synopsis>
      Specifies the SQL database user password.
    </adm:synopsis>
    <adm:description>
      SQL user password may be provided.
    </adm:description>
    <adm:requires-admin-action>
      <adm:component-restart />
    </adm:requires-admin-action>
    <adm:default-behavior>
      <adm:undefined />
    </adm:default-behavior>
    <adm:syntax>
      <adm:string />
    </adm:syntax>
    <adm:profile name="ldap">
      <ldap:attribute>
        <ldap:name>ds-cfg-sql-passwd</ldap:name>
      </ldap:attribute>
    </adm:profile>
  </adm:property>
  <adm:property name="ndb-dbname" mandatory="true">
    <adm:synopsis>
      Specifies the SQL/NDB database name.
    </adm:synopsis>
    <adm:description>
      SQL/NDB database name may be provided.
    </adm:description>
    <adm:requires-admin-action>
      <adm:component-restart />
    </adm:requires-admin-action>
    <adm:default-behavior>
      <adm:defined>
        <adm:value>ldap</adm:value>
      </adm:defined>
    </adm:default-behavior>
    <adm:syntax>
      <adm:string />
    </adm:syntax>
    <adm:profile name="ldap">
      <ldap:attribute>
        <ldap:name>ds-cfg-ndb-dbname</ldap:name>
      </ldap:attribute>
    </adm:profile>
  </adm:property>
  <adm:property name="ndb-num-connections" advanced="true">
    <adm:synopsis>
      Specifies the number of NDB connections.
    </adm:synopsis>
    <adm:description>
      Logical connections made to NDB Cluster.
    </adm:description>
    <adm:requires-admin-action>
      <adm:component-restart />
    </adm:requires-admin-action>
    <adm:default-behavior>
      <adm:defined>
        <adm:value>4</adm:value>
      </adm:defined>
    </adm:default-behavior>
    <adm:syntax>
      <adm:integer lower-limit="1" />
    </adm:syntax>
    <adm:profile name="ldap">
      <ldap:attribute>
        <ldap:name>ds-cfg-ndb-num-connections</ldap:name>
      </ldap:attribute>
    </adm:profile>
  </adm:property>
  <adm:property name="ndb-thread-count" advanced="true">
    <adm:synopsis>
      Specifies the number of threads that is used for concurrent
      NDB processing.
    </adm:synopsis>
    <adm:description>
      This should generally be equal to the number of worker threads.
    </adm:description>
    <adm:requires-admin-action>
      <adm:component-restart/>
    </adm:requires-admin-action>
    <adm:default-behavior>
      <adm:defined>
        <adm:value>24</adm:value>
      </adm:defined>
    </adm:default-behavior>
    <adm:syntax>
      <adm:integer lower-limit="1" upper-limit="128" />
    </adm:syntax>
    <adm:profile name="ldap">
      <ldap:attribute>
        <ldap:name>ds-cfg-ndb-thread-count</ldap:name>
      </ldap:attribute>
    </adm:profile>
  </adm:property>
  <adm:property name="ndb-attr-len" advanced="true">
    <adm:synopsis>
      Specifies the attribute length.
    </adm:synopsis>
    <adm:description>
      This should reflect SQL/NDB attribute column length.
    </adm:description>
    <adm:requires-admin-action>
      <adm:component-restart/>
    </adm:requires-admin-action>
    <adm:default-behavior>
      <adm:defined>
        <adm:value>128</adm:value>
      </adm:defined>
    </adm:default-behavior>
    <adm:syntax>
      <adm:integer lower-limit="1" />
    </adm:syntax>
    <adm:profile name="ldap">
      <ldap:attribute>
        <ldap:name>ds-cfg-ndb-attr-len</ldap:name>
      </ldap:attribute>
    </adm:profile>
  </adm:property>
  <adm:property name="ndb-attr-blob" multi-valued="true" advanced="true">
    <adm:synopsis>
      Specifies the blob attribute.
    </adm:synopsis>
    <adm:description>
      This should specify which attribute to treat as a blob.
    </adm:description>
    <adm:requires-admin-action>
      <adm:component-restart />
    </adm:requires-admin-action>
    <adm:default-behavior>
      <adm:undefined />
    </adm:default-behavior>
    <adm:syntax>
      <adm:string />
    </adm:syntax>
    <adm:profile name="ldap">
      <ldap:attribute>
        <ldap:name>ds-cfg-ndb-attr-blob</ldap:name>
      </ldap:attribute>
    </adm:profile>
  </adm:property>
  <adm:property name="deadlock-retry-limit" advanced="true">
    <adm:synopsis>
      Specifies the number of times that the server should retry an
      attempted operation in the backend if a deadlock results from
      two concurrent requests that interfere with each other in a
      conflicting manner.
    </adm:synopsis>
    <adm:description>
      A value of "0" indicates no limit.
    </adm:description>
    <adm:default-behavior>
      <adm:defined>
        <adm:value>10</adm:value>
      </adm:defined>
    </adm:default-behavior>
    <adm:syntax>
      <adm:integer lower-limit="0" upper-limit="2147483647" />
    </adm:syntax>
    <adm:profile name="ldap">
      <ldap:attribute>
        <ldap:name>ds-cfg-deadlock-retry-limit</ldap:name>
      </ldap:attribute>
    </adm:profile>
  </adm:property>
  <adm:property name="import-queue-size" advanced="true">
    <adm:synopsis>
      Specifies the size (in number of entries) of the queue that is
      used to hold the entries read during an LDIF import.
    </adm:synopsis>
    <adm:requires-admin-action>
      <adm:none>
        <adm:synopsis>
          Changes do not take effect for any import that may already
          be in progress.
        </adm:synopsis>
      </adm:none>
    </adm:requires-admin-action>
    <adm:default-behavior>
      <adm:defined>
        <adm:value>100</adm:value>
      </adm:defined>
    </adm:default-behavior>
    <adm:syntax>
      <adm:integer lower-limit="1" upper-limit="2147483647" />
    </adm:syntax>
    <adm:profile name="ldap">
      <ldap:attribute>
        <ldap:name>ds-cfg-import-queue-size</ldap:name>
      </ldap:attribute>
    </adm:profile>
  </adm:property>
  <adm:property name="import-thread-count" advanced="true">
    <adm:synopsis>
      Specifies the number of threads that is used for concurrent
      processing during an LDIF import.
    </adm:synopsis>
    <adm:description>
      This should generally be a small multiple (for example, 2x) of the number
      of CPUs in the system for a traditional system, or equal to the
      number of CPU strands for a CMT system.
    </adm:description>
    <adm:requires-admin-action>
      <adm:none>
        <adm:synopsis>
          Changes do not take effect for any import that may already
          be in progress.
        </adm:synopsis>
      </adm:none>
    </adm:requires-admin-action>
    <adm:default-behavior>
      <adm:defined>
        <adm:value>8</adm:value>
      </adm:defined>
    </adm:default-behavior>
    <adm:syntax>
      <adm:integer lower-limit="1" upper-limit="2147483647" />
    </adm:syntax>
    <adm:profile name="ldap">
      <ldap:attribute>
        <ldap:name>ds-cfg-import-thread-count</ldap:name>
      </ldap:attribute>
    </adm:profile>
  </adm:property>
</adm:managed-object>
opends/src/admin/defn/org/opends/server/admin/std/NdbIndexConfiguration.xml
New file
@@ -0,0 +1,187 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
  ! 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/opends/resource/legal-notices/OpenDS.LICENSE
  ! or https://OpenDS.dev.java.net/OpenDS.LICENSE.
  ! 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/opends/resource/legal-notices/OpenDS.LICENSE.  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.
  ! -->
<adm:managed-object name="ndb-index" plural-name="ndb-indexes"
  package="org.opends.server.admin.std"
  xmlns:adm="http://www.opends.org/admin"
  xmlns:ldap="http://www.opends.org/admin-ldap">
  <adm:synopsis>
    <adm:user-friendly-plural-name />
    are used to store information that makes it possible to locate
    entries very quickly when processing search operations.
  </adm:synopsis>
  <adm:description>
    Indexing is performed on a per-attribute level and different types
    of indexing may be performed for different kinds of attributes, based
    on how they are expected to be accessed during search operations.
  </adm:description>
  <adm:tag name="database" />
  <adm:profile name="ldap">
    <ldap:object-class>
      <ldap:name>ds-cfg-ndb-index</ldap:name>
      <ldap:superior>top</ldap:superior>
    </ldap:object-class>
  </adm:profile>
  <adm:property name="attribute" mandatory="true" read-only="true">
    <adm:synopsis>
      Specifies the name of the attribute for which the index is to
      be maintained.
    </adm:synopsis>
    <adm:syntax>
      <adm:attribute-type />
    </adm:syntax>
    <adm:profile name="ldap">
      <ldap:attribute>
        <ldap:name>ds-cfg-attribute</ldap:name>
      </ldap:attribute>
    </adm:profile>
  </adm:property>
  <adm:property name="index-entry-limit">
    <adm:synopsis>
      Specifies the maximum number of entries that are allowed
      to match a given index key before that particular index key is no
      longer maintained.
    </adm:synopsis>
    <adm:description>
      This is analogous to the ALL IDs threshold in the Sun Java System
      Directory Server. If this is specified, its value overrides any
      backend-wide configuration. For no limit, use 0 for the value.
    </adm:description>
    <adm:requires-admin-action>
      <adm:other>
        <adm:synopsis>
          If any index keys have already reached this limit, indexes
          must be rebuilt before they will be allowed to use the
          new limit.
        </adm:synopsis>
      </adm:other>
    </adm:requires-admin-action>
    <adm:default-behavior>
      <adm:inherited>
        <adm:relative property-name="index-entry-limit" offset="1"
          managed-object-name="ndb-backend" />
      </adm:inherited>
    </adm:default-behavior>
    <adm:syntax>
      <adm:integer lower-limit="0" upper-limit="2147483647">
        <adm:unit-synopsis>Number of entries</adm:unit-synopsis>
      </adm:integer>
    </adm:syntax>
    <adm:profile name="ldap">
      <ldap:attribute>
        <ldap:name>ds-cfg-index-entry-limit</ldap:name>
      </ldap:attribute>
    </adm:profile>
  </adm:property>
  <adm:property name="index-type" mandatory="true"
    multi-valued="true">
    <adm:synopsis>
      Specifies the type(s) of indexing that should be performed
      for the associated attribute.
    </adm:synopsis>
    <adm:description>
      For equality, presence, and substring index types, the associated
      attribute type must have a corresponding matching rule.
    </adm:description>
    <adm:requires-admin-action>
      <adm:other>
        <adm:synopsis>
          If any new index types are added for an attribute, and
          values for that attribute already exist in the
          database, the index must be rebuilt before it
          will be accurate.
        </adm:synopsis>
      </adm:other>
    </adm:requires-admin-action>
    <adm:syntax>
      <adm:enumeration>
        <adm:value name="equality">
          <adm:synopsis>
            This index type is used to improve the efficiency
            of searches using equality search filters.
          </adm:synopsis>
        </adm:value>
        <adm:value name="ordering">
          <adm:synopsis>
            This index type is used to improve the efficiency
            of searches using "greater than or equal to" or "less then
            or equal to" search filters.
          </adm:synopsis>
        </adm:value>
        <adm:value name="presence">
          <adm:synopsis>
            This index type is used to improve the efficiency
            of searches using the presence search filters.
          </adm:synopsis>
        </adm:value>
        <adm:value name="substring">
          <adm:synopsis>
            This index type is used to improve the efficiency
            of searches using substring search filters.
          </adm:synopsis>
        </adm:value>
        <adm:value name="approximate">
          <adm:synopsis>
            This index type is used to improve the efficiency
            of searches using approximate matching search filters.
          </adm:synopsis>
        </adm:value>
      </adm:enumeration>
    </adm:syntax>
    <adm:profile name="ldap">
      <ldap:attribute>
        <ldap:name>ds-cfg-index-type</ldap:name>
      </ldap:attribute>
    </adm:profile>
  </adm:property>
  <adm:property name="substring-length" advanced="true">
    <adm:synopsis>
      The length of substrings in a substring index.
    </adm:synopsis>
    <adm:requires-admin-action>
      <adm:other>
        <adm:synopsis>
          The index must be rebuilt before it will reflect the
          new value.
        </adm:synopsis>
      </adm:other>
    </adm:requires-admin-action>
    <adm:default-behavior>
      <adm:defined>
        <adm:value>6</adm:value>
      </adm:defined>
    </adm:default-behavior>
    <adm:syntax>
      <adm:integer lower-limit="3" />
    </adm:syntax>
    <adm:profile name="ldap">
      <ldap:attribute>
        <ldap:name>ds-cfg-substring-length</ldap:name>
      </ldap:attribute>
    </adm:profile>
  </adm:property>
</adm:managed-object>
opends/src/messages/messages/ndb.properties
New file
@@ -0,0 +1,247 @@
# 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/opends/resource/legal-notices/OpenDS.LICENSE
# or https://OpenDS.dev.java.net/OpenDS.LICENSE.
# 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/opends/resource/legal-notices/OpenDS.LICENSE.  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.
#
# Global directives
#
global.category=NDB
#
# Format string definitions
#
# Keys must be formatted as follows:
#
# [SEVERITY]_[DESCRIPTION]_[ORDINAL]
#
# where:
#
# SEVERITY is one of:
# [INFO, MILD_WARN, SEVERE_WARN, MILD_ERR, SEVERE_ERR, FATAL_ERR, DEBUG, NOTICE]
#
# DESCRIPTION is an upper case string providing a hint as to the context of
# the message in upper case with the underscore ('_') character serving as
# word separator
#
# ORDINAL is an integer unique among other ordinals in this file
#
MILD_ERR_NDB_INCORRECT_ROUTING_1=The backend does not contain that part of \
 the Directory Information Tree pertaining to the entry '%s'
SEVERE_ERR_NDB_OPEN_DATABASE_FAIL_2=The database could not be opened: %s
SEVERE_ERR_NDB_OPEN_ENV_FAIL_3=The database environment could not be opened: \
 %s
SEVERE_ERR_NDB_HIGHEST_ID_FAIL_5=The database highest entry identifier could \
 not be determined
SEVERE_WARN_NDB_FUNCTION_NOT_SUPPORTED_6=The requested operation is not \
 supported by this backend
SEVERE_ERR_NDB_MISSING_DN2ID_RECORD_10=The DN database does not contain a \
 record for '%s'
SEVERE_ERR_NDB_MISSING_ID2ENTRY_RECORD_11=The entry database does not contain \
 a record for ID %s
SEVERE_ERR_NDB_ENTRY_DATABASE_CORRUPT_12=The entry database does not contain \
 a valid record for ID %s
SEVERE_ERR_NDB_DATABASE_EXCEPTION_14=Database exception: %s
SEVERE_ERR_CONFIG_INDEX_TYPE_NEEDS_MATCHING_RULE_26=The attribute '%s' cannot \
 have indexing of type '%s' because it does not have a corresponding matching \
 rule
MILD_ERR_NDB_UNCHECKED_EXCEPTION_28=Unchecked exception during database \
 transaction
NOTICE_NDB_SUBTREE_DELETE_SIZE_LIMIT_EXCEEDED_32=Exceeded the administrative \
 limit on the number of entries that may be deleted in a subtree delete \
 operation. The number of entries actually deleted was %d. The operation may \
 be retried until all entries in the subtree have been deleted
NOTICE_NDB_DELETED_ENTRY_COUNT_33=The number of entries deleted was %d
MILD_ERR_NDB_DUPLICATE_CONFIG_ENTRY_36=The configuration entry '%s' will be \
 ignored. Only one configuration entry with object class '%s' is allowed
MILD_ERR_NDB_CONFIG_ENTRY_NOT_RECOGNIZED_37=The configuration entry '%s' will \
 be ignored because it is not recognized
MILD_ERR_NDB_INDEX_ATTRIBUTE_TYPE_NOT_FOUND_38=The index configuration entry \
 '%s' will be ignored because it specifies an unknown attribute type '%s'
MILD_ERR_NDB_DUPLICATE_INDEX_CONFIG_39=The index configuration entry '%s' \
 will be ignored because it specifies the attribute type '%s', which has \
 already been defined in another index configuration entry
SEVERE_ERR_NDB_IO_ERROR_40=I/O error during backend operation: %s
NOTICE_NDB_BACKEND_STARTED_42=The database backend %s containing %d entries \
 has started
MILD_ERR_NDB_IMPORT_PARENT_NOT_FOUND_43=The parent entry '%s' does not exist
SEVERE_WARN_NDB_IMPORT_ENTRY_EXISTS_44=The entry exists and the import \
 options do not allow it to be replaced
MILD_ERR_NDB_ATTRIBUTE_INDEX_NOT_CONFIGURED_45=There is no index configured \
 for attribute type '%s'
MILD_ERR_NDB_SEARCH_NO_SUCH_OBJECT_46=The search base entry '%s' does not \
 exist
MILD_ERR_NDB_ADD_NO_SUCH_OBJECT_47=The entry '%s' cannot be added because its \
 parent entry does not exist
MILD_ERR_NDB_DELETE_NO_SUCH_OBJECT_48=The entry '%s' cannot be removed \
 because it does not exist
MILD_ERR_NDB_MODIFY_NO_SUCH_OBJECT_49=The entry '%s' cannot be modified \
 because it does not exist
MILD_ERR_NDB_MODIFYDN_NO_SUCH_OBJECT_50=The entry '%s' cannot be renamed \
 because it does not exist
MILD_ERR_NDB_ADD_ENTRY_ALREADY_EXISTS_51=The entry '%s' cannot be added \
 because an entry with that name already exists
MILD_ERR_NDB_DELETE_NOT_ALLOWED_ON_NONLEAF_52=The entry '%s' cannot be \
 removed because it has subordinate entries
MILD_ERR_NDB_MODIFYDN_ALREADY_EXISTS_53=The entry cannot be renamed to '%s' \
 because an entry with that name already exists
MILD_ERR_NDB_NEW_SUPERIOR_NO_SUCH_OBJECT_54=The entry cannot be moved because \
 the new parent entry '%s' does not exist
NOTICE_NDB_EXPORT_FINAL_STATUS_87=Exported %d entries and skipped %d in %d \
 seconds (average rate %.1f/sec)
NOTICE_NDB_EXPORT_PROGRESS_REPORT_88=Exported %d records and skipped %d (recent \
 rate %.1f/sec)
NOTICE_NDB_IMPORT_THREAD_COUNT_89=Import Thread Count: %d threads
INFO_NDB_IMPORT_BUFFER_SIZE_90=Buffer size per thread = %,d
INFO_NDB_IMPORT_LDIF_PROCESSING_TIME_91=LDIF processing took %d seconds
INFO_NDB_IMPORT_INDEX_PROCESSING_TIME_92=Index processing took %d seconds
NOTICE_NDB_WAITING_FOR_CLUSTER_93=Waiting for NDB Cluster to become \
 available
NOTICE_NDB_IMPORT_FINAL_STATUS_94=Processed %d entries, imported %d, skipped \
 %d, rejected %d and migrated %d in %d seconds (average rate %.1f/sec)
NOTICE_NDB_IMPORT_ENTRY_LIMIT_EXCEEDED_COUNT_95=Number of index values that \
 exceeded the entry limit: %d
NOTICE_NDB_IMPORT_PROGRESS_REPORT_96=Processed %d entries, skipped %d, rejected \
 %d, and migrated %d (recent rate %.1f/sec)
NOTICE_NDB_VERIFY_CLEAN_FINAL_STATUS_101=Checked %d records and found %d \
 error(s) in %d seconds (average rate %.1f/sec)
INFO_NDB_VERIFY_MULTIPLE_REFERENCE_COUNT_102=Number of records referencing \
 more than one entry: %d
INFO_NDB_VERIFY_ENTRY_LIMIT_EXCEEDED_COUNT_103=Number of records that exceed \
 the entry limit: %d
INFO_NDB_VERIFY_AVERAGE_REFERENCE_COUNT_104=Average number of entries \
 referenced is %.2f/record
INFO_NDB_VERIFY_MAX_REFERENCE_COUNT_105=Maximum number of entries referenced \
 by any record is %d
NOTICE_NDB_BOOTSTRAP_SCHEMA_106=Processing LDAP schema and NDB tables, this \
 may take awhile
INFO_NDB_VERIFY_ENTRY_LIMIT_STATS_HEADER_107=Statistics for records that have \
 exceeded the entry limit:
NOTICE_NDB_VERIFY_PROGRESS_REPORT_109=Processed %d out of %d records and found \
 %d error(s) (recent rate %.1f/sec)
MILD_ERR_NDB_INVALID_PAGED_RESULTS_COOKIE_111=The following paged results \
 control cookie value was not recognized: %s
NOTICE_NDB_REFERRAL_RESULT_MESSAGE_112=A referral entry %s indicates that the \
 operation must be processed at a different server
SEVERE_ERR_NDB_INCOMPATIBLE_ENTRY_VERSION_126=Entry record with ID %s is not \
 compatible with this version of the backend database. Entry version: %x
NOTICE_NDB_LOOKTHROUGH_LIMIT_EXCEEDED_127=This search operation has checked the \
 maximum of %d entries for matches
SEVERE_WARN_NDB_GET_ENTRY_COUNT_FAILED_129=Unable to determine the total \
 number of entries in the container: %s
NOTICE_NDB_CONFIG_ATTR_REQUIRES_RESTART_130=The change to the %s attribute will \
 not take effect until the backend is restarted
NOTICE_NDB_REBUILD_PROGRESS_REPORT_131=%.1f%% Completed. Processed %d/%d \
 records. (recent rate %.1f/sec)
NOTICE_NDB_REBUILD_FINAL_STATUS_133=Rebuild complete. Processed %d records in \
 %d seconds (average rate %.1f/sec)
SEVERE_ERR_NDB_REBUILD_INDEX_FAILED_134=An error occurred while rebuilding \
 index %s: %s
MILD_ERR_NDB_REBUILD_INSERT_ENTRY_FAILED_135=An error occurred while \
 inserting entry into the %s database/index: %s
SEVERE_ERR_NDB_REBUILD_INDEX_CONFLICT_136=Another rebuild of index %s is \
 already in progress
NOTICE_NDB_REBUILD_START_137=Rebuild of index(es) %s started with %d total \
 records to process
SEVERE_ERR_NDB_REBUILD_BACKEND_ONLINE_138=Rebuilding system index(es) must be \
 done with the backend containing the base DN disabled
SEVERE_ERR_ENTRYIDSORTER_CANNOT_EXAMINE_ENTRY_139=Unable to examine the entry \
 with ID %s for sorting purposes:  %s
MILD_ERR_NDB_SEARCH_CANNOT_SORT_UNINDEXED_140=The search results cannot be \
 sorted because the given search request is not indexed
MILD_ERR_ENTRYIDSORTER_NEGATIVE_START_POS_141=Unable to process the virtual \
 list view request because the target start position was before the beginning \
 of the result set
MILD_ERR_ENTRYIDSORTER_OFFSET_TOO_LARGE_142=Unable to process the virtual \
 list view request because the target offset %d was greater than the total \
 number of results in the list (%d)
MILD_ERR_ENTRYIDSORTER_TARGET_VALUE_NOT_FOUND_143=Unable to process the \
 virtual list view request because no entry was found in the result set with a \
 sort value greater than or equal to the provided assertion value
MILD_ERR_NDB_SEARCH_CANNOT_MIX_PAGEDRESULTS_AND_VLV_144=The requested search \
 operation included both the simple paged results control and the virtual list \
 view control.  These controls are mutually exclusive and cannot be used \
 together
MILD_ERR_NDB_SEARCH_UNINDEXED_INSUFFICIENT_PRIVILEGES_145=You do not have \
 sufficient privileges to perform an unindexed search
NOTICE_NDB_CONFIG_INDEX_ENTRY_LIMIT_REQUIRES_REBUILD_148=Some index keys have \
 already exceeded the previous index entry limit in index %s. This index must \
 be rebuilt before it can use the new limit
NOTICE_NDB_INDEX_ADD_REQUIRES_REBUILD_150=Due to changes in the \
 configuration, index %s is currently operating in a degraded state and must \
 be rebuilt before it can used
SEVERE_ERR_NDB_INDEX_CORRUPT_REQUIRES_REBUILD_151=An error occurred while \
 reading from index %s. The index seems to be corrupt and is now operating in \
 a degraded state. The index must be rebuilt before it can return to normal \
 operation
SEVERE_ERR_NDB_IMPORT_BACKEND_ONLINE_152=The backend must be disabled before \
 the import process can start
SEVERE_ERR_NDB_IMPORT_THREAD_EXCEPTION_153=An error occurred in import thread \
 %s: %s. The thread can not continue
SEVERE_ERR_NDB_IMPORT_NO_WORKER_THREADS_154=There are no more import worker \
 threads to process the imported entries
SEVERE_ERR_NDB_IMPORT_CREATE_TMPDIR_ERROR_155=Unable to create the temporary \
 directory %s
NOTICE_NDB_IMPORT_MIGRATION_START_157=Migrating %s entries for base DN %s
NOTICE_NDB_IMPORT_LDIF_START_158=Processing LDIF
NOTICE_NDB_IMPORT_LDIF_END_159=End of LDIF reached
SEVERE_ERR_NDB_CONFIG_VLV_INDEX_UNDEFINED_ATTR_160=Sort attribute %s for VLV \
 index %s is not defined in the server schema
SEVERE_ERR_NDB_CONFIG_VLV_INDEX_BAD_FILTER_161=An error occurred while parsing \
 the search filter %s defined for VLV index %s: %s
MILD_ERR_NDB_VLV_INDEX_NOT_CONFIGURED_162=There is no VLV index configured \
 with name '%s'
MILD_ERR_NDB_MODIFYDN_ABORTED_BY_SUBORDINATE_PLUGIN_163=A plugin caused the \
 modify DN operation to be aborted while moving and/or renaming an entry from \
 %s to %s
MILD_ERR_NDB_MODIFYDN_ABORTED_BY_SUBORDINATE_SCHEMA_ERROR_164=A plugin caused \
 the modify DN operation to be aborted while moving and/or renaming an entry \
 from %s to %s because the change to that entry violated the server schema \
 configuration:  %s
SEVERE_ERR_NDB_COMPSCHEMA_CANNOT_STORE_STATUS_167=An error occurred while \
 attempting to store compressed schema information in the database.  The \
 result returned was:  %s
SEVERE_ERR_NDB_COMPSCHEMA_CANNOT_STORE_EX_168=An error occurred while \
 attempting to store compressed schema information in the database:  %s
SEVERE_ERR_NDB_COMPSCHEMA_CANNOT_STORE_MULTIPLE_FAILURES_169=The server was \
 unable to store compressed schema information in the database after multiple \
 attempts
SEVERE_ERR_NDB_COMPSCHEMA_UNKNOWN_OC_TOKEN_170=Unable to decode the provided \
 object class set because it used an undefined token %s
SEVERE_ERR_NDB_COMPSCHEMA_UNRECOGNIZED_AD_TOKEN_171=Unable to decode the \
 provided attribute because it used an undefined attribute description token \
 %s
NOTICE_NDB_IMPORT_STARTING_173=%s starting import (build %s, R%d)
SEVERE_ERR_NDB_IMPORT_LDIF_ABORT_175=The import was aborted because an \
  uncaught exception was thrown during processing
NOTICE_NDB_IMPORT_LDIF_BUFFER_TOT_AVAILMEM_184=Available buffer memory %d bytes is \
  below the minimum value of %d bytes. Setting available buffer memory to \
  the minimum
NOTICE_NDB_IMPORT_LDIF_BUFFER_CONTEXT_AVAILMEM_186=Available buffer memory %d \
  bytes is below the minimum value of %d bytes allowed for a single import \
  context. Setting context available buffer memory to the minimum
SEVERE_ERR_NDB_IMPORT_OFFLINE_NOT_SUPPORTED_191=Offline import is currently \
 not supported by this backend
SEVERE_ERR_NDB_EXPORT_OFFLINE_NOT_SUPPORTED_192=Offline export is currently \
 not supported by this backend
opends/src/messages/src/org/opends/messages/Category.java
@@ -22,7 +22,7 @@
 * CDDL HEADER END
 *
 *
 *      Copyright 2007-2008 Sun Microsystems, Inc.
 *      Copyright 2007-2009 Sun Microsystems, Inc.
 */
package org.opends.messages;
@@ -162,6 +162,11 @@
  SERVICETAG(0x01400000),
  /**
   * The category used for messages associated with the NDB backend.
   */
  NDB(0x01500000),
  /**
   * The category that will be used for messages associated with
   * third-party (including user-defined) modules.
   */
opends/src/server/org/opends/server/backends/ndb/AbstractTransaction.java
New file
@@ -0,0 +1,188 @@
/*
 * 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/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * 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/opends/resource/legal-notices/OpenDS.LICENSE.  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.
 */
package org.opends.server.backends.ndb;
import com.mysql.cluster.ndbj.Ndb;
import com.mysql.cluster.ndbj.NdbApiException;
import com.mysql.cluster.ndbj.NdbOperation.AbortOption;
import com.mysql.cluster.ndbj.NdbTransaction;
import com.mysql.cluster.ndbj.NdbTransaction.ExecType;
/**
 * This class represents abstract transaction.
 */
public class AbstractTransaction {
  private Ndb ndb;
  private NdbTransaction ndbTxn;
  private NdbTransaction ndbDATxn;
  private RootContainer rootContainer;
  /**
   * Default constructor.
   * @param rootContainer root container to associate transaction with.
   */
  public AbstractTransaction(RootContainer rootContainer) {
    this.ndb = null;
    this.ndbTxn = null;
    this.ndbDATxn = null;
    this.rootContainer = rootContainer;
  }
  /**
   * Get Ndb handle associated with this abstract transaction.
   * @return Ndb handle.
   */
  public Ndb getNdb()
  {
    if (ndb == null) {
      ndb = rootContainer.getNDB();
    }
    return ndb;
  }
  /**
   * Get transaction.
   * @return A transaction handle.
   * @throws NdbApiException If an error occurs while attempting to begin
   * a new transaction.
   */
  public NdbTransaction getNdbTransaction()
      throws NdbApiException
  {
    if (ndb == null) {
      ndb = rootContainer.getNDB();
    }
    if (ndbTxn == null) {
      ndbTxn = ndb.startTransaction();
    }
    return ndbTxn;
  }
  /**
   * Get DA transaction.
   * @param tableName table name for DA.
   * @param partitionKey partition key for DA.
   * @return A transaction handle.
   * @throws NdbApiException If an error occurs while attempting to begin
   * a new transaction.
   */
  public NdbTransaction
  getNdbDATransaction(String tableName, long partitionKey)
    throws NdbApiException
  {
    if (ndb == null) {
      ndb = rootContainer.getNDB();
    }
    if (ndbDATxn == null) {
      ndbDATxn = ndb.startTransactionBig(tableName, partitionKey);
    }
    return ndbDATxn;
  }
  /**
   * Commit transaction.
   * @throws NdbApiException If an error occurs while attempting to commit
   * the transaction.
   */
  public void commit()
    throws NdbApiException {
    try {
      if (ndbDATxn != null) {
        try {
          ndbDATxn.execute(ExecType.Commit, AbortOption.AbortOnError, true);
        } finally {
          if (ndbDATxn != null) {
            ndbDATxn.close();
          }
        }
      }
      if (ndbTxn != null) {
        try {
          ndbTxn.execute(ExecType.Commit, AbortOption.AbortOnError, true);
        } finally {
          if (ndbTxn != null) {
            ndbTxn.close();
          }
        }
      }
    } finally {
      if (ndb != null) {
        rootContainer.releaseNDB(ndb);
      }
      ndbDATxn = null;
      ndbTxn = null;
      ndb = null;
    }
  }
  /**
   * Execute transaction.
   * @throws NdbApiException If an error occurs while attempting to execute
   * the transaction.
   */
  public void execute()
    throws NdbApiException {
    if (ndbDATxn != null) {
      ndbDATxn.execute(ExecType.NoCommit, AbortOption.AbortOnError, true);
    }
    if (ndbTxn != null) {
      ndbTxn.execute(ExecType.NoCommit, AbortOption.AbortOnError, true);
    }
  }
  /**
   * Close transaction.
   * @throws NdbApiException If an error occurs while attempting to close the
   * transaction.
   */
  public void close()
    throws NdbApiException {
    try {
      if (ndbDATxn != null) {
        ndbDATxn.close();
      }
      if (ndbTxn != null) {
        ndbTxn.close();
      }
    } finally {
      if (ndb != null) {
        rootContainer.releaseNDB(ndb);
      }
      ndbDATxn = null;
      ndbTxn = null;
      ndb = null;
    }
  }
}
opends/src/server/org/opends/server/backends/ndb/BackendImpl.java
New file
@@ -0,0 +1,2101 @@
/*
 * 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/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * 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/opends/resource/legal-notices/OpenDS.LICENSE.  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.
 */
package org.opends.server.backends.ndb;
import com.mysql.cluster.ndbj.NdbApiException;
import com.mysql.cluster.ndbj.NdbOperation;
import java.io.IOException;
import org.opends.messages.Message;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.*;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.concurrent.ConcurrentHashMap;
import org.opends.server.api.Backend;
import org.opends.server.api.MonitorProvider;
import org.opends.server.api.AlertGenerator;
import org.opends.server.config.ConfigException;
import org.opends.server.core.AddOperation;
import org.opends.server.core.DeleteOperation;
import org.opends.server.core.DirectoryServer;
import org.opends.server.core.ModifyOperation;
import org.opends.server.core.ModifyDNOperation;
import org.opends.server.core.SearchOperation;
import org.opends.server.util.Validator;
import static org.opends.server.util.StaticUtils.*;
import static org.opends.messages.BackendMessages.*;
import static org.opends.messages.NdbMessages.*;
import static org.opends.server.loggers.ErrorLogger.logError;
import static org.opends.server.loggers.debug.DebugLogger.*;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.types.*;
import static org.opends.server.util.ServerConstants.*;
import org.opends.server.admin.Configuration;
import org.opends.server.admin.server.ConfigurationChangeListener;
import org.opends.server.admin.std.meta.GlobalCfgDefn.WorkflowConfigurationMode;
import org.opends.server.admin.std.server.NdbBackendCfg;
import org.opends.server.admin.std.server.NdbIndexCfg;
import org.opends.server.backends.SchemaBackend;
import org.opends.server.backends.ndb.importLDIF.Importer;
import org.opends.server.core.Workflow;
import org.opends.server.core.WorkflowImpl;
import org.opends.server.core.networkgroups.NetworkGroup;
import org.opends.server.types.DN;
import org.opends.server.util.LDIFException;
import org.opends.server.workflowelement.WorkflowElement;
import org.opends.server.workflowelement.ndb.NDBWorkflowElement;
/**
 * This is an implementation of a Directory Server Backend which stores
 * entries in MySQL Cluster NDB database engine.
 */
public class BackendImpl
    extends Backend
    implements ConfigurationChangeListener<NdbBackendCfg>, AlertGenerator
{
  /**
   * The tracer object for the debug logger.
   */
  private static final DebugTracer TRACER = getTracer();
  /**
    * The fully-qualified name of this class.
    */
  private static final String CLASS_NAME =
        "org.opends.server.backends.ndb.BackendImpl";
  /**
   * The configuration of this NDB backend.
   */
  private NdbBackendCfg cfg;
  /**
   * The root container to use for this backend.
   */
  private RootContainer rootContainer;
  /**
   * A count of the total operation threads currently in the backend.
   */
  private AtomicInteger threadTotalCount = new AtomicInteger(0);
  /**
   * A count of the write operation threads currently in the backend.
   */
  private AtomicInteger threadWriteCount = new AtomicInteger(0);
  /**
   * A list of monitor providers created for this backend instance.
   */
  private ArrayList<MonitorProvider<?>> monitorProviders =
      new ArrayList<MonitorProvider<?>>();
  /**
   * The base DNs defined for this backend instance.
   */
  private DN[] baseDNs;
  /**
   * The mysqld connection object.
   */
  private Connection sqlConn;
  /**
   * The controls supported by this backend.
   */
  private static HashSet<String> supportedControls;
  /**
   * Database name.
   */
  protected static String DATABASE_NAME;
  /**
   * Attribute column length.
   */
  private static int ATTRLEN;
  /**
   * Attribute column length string.
   */
  private static String ATTRLEN_STRING;
  /**
   * NDB Max Row Size known.
   */
  private static final int NDB_MAXROWSIZE = 8052;
  /**
   * Number of rDN components supported.
   */
  protected static final int DN2ID_DN_NC = 16;
  /**
   * Number of times to retry NDB transaction.
   */
  protected static int TXN_RETRY_LIMIT = 0;
  /**
   * DN2ID table.
   */
  protected static final String DN2ID_TABLE = "DS_dn2id";
  /**
   * NEXTID autoincrement table.
   */
  protected static final String NEXTID_TABLE = "DS_nextid";
  /**
   * Operational Attributes table.
   */
  protected static final String OPATTRS_TABLE = "DS_opattrs";
  /**
   * Operational Attributes table.
   */
  protected static final String TAGS_TABLE = "DS_tags";
  /**
   * Index table prefix.
   */
  protected static final String IDX_TABLE_PREFIX = "DS_idx_";
  /**
   * Referrals table.
   */
  protected static final String REFERRALS_TABLE = "referral";
  /**
   * Name prefix for server specific objectclasses.
   */
  private static final String DSOBJ_NAME_PREFIX = "ds-";
  /**
   * EID column name.
   */
  protected static final String EID = "eid";
  /**
   * MID column name.
   */
  protected static final String MID = "mid";
  /**
   * DN column name prefix.
   */
  protected static final String DN2ID_DN = "a";
  /**
   * OC column name.
   */
  protected static final String DN2ID_OC = "object_classes";
  /**
   * Extensible OC column name.
   */
  protected static final String DN2ID_XOC = "x_object_classes";
  /**
   * Attribute column name.
   */
  protected static final String TAG_ATTR = "attr";
  /**
   * Tags column name.
   */
  protected static final String TAG_TAGS = "tags";
  /**
   * Value column name.
   */
  protected static final String IDX_VAL = "value";
  /**
   * Attribute name to lowercase name map.
   */
  protected static Map<String, String> attrName2LC;
  /**
   * Set of blob attribute names.
   */
  protected static Set<String> blobAttributes;
  /**
   * List of operational attribute names.
   */
  protected static List<String> operationalAttributes;
  /**
   * List of index names.
   */
  protected static List<String> indexes;
  /**
   * Attribute name to ObjectClass name/s map.
   */
  protected static Map<String, String> attr2Oc;
  /**
   * Entry DN to Transaction Map to track entry locking.
   */
  protected static Map<DN, AbstractTransaction> lockMap;
  /**
   * The features supported by this backend.
   */
  private static HashSet<String> supportedFeatures;
  static
  {
    // Set our supported controls.
    supportedControls = new HashSet<String>();
    supportedControls.add(OID_MANAGE_DSAIT_CONTROL);
    supportedControls.add(OID_SUBTREE_DELETE_CONTROL);
    attrName2LC = new HashMap<String, String>();
    operationalAttributes = new ArrayList<String>();
    blobAttributes = new HashSet<String>();
    indexes = new ArrayList<String>();
    attr2Oc = new HashMap<String, String>();
    lockMap = new ConcurrentHashMap<DN, AbstractTransaction>();
    // Set supported features.
    supportedFeatures = new HashSet<String>();
  }
  /**
   * Begin a Backend API method that reads the database.
   */
  private void readerBegin()
  {
    threadTotalCount.getAndIncrement();
  }
  /**
   * End a Backend API method that reads the database.
   */
  private void readerEnd()
  {
    threadTotalCount.getAndDecrement();
  }
  /**
   * Begin a Backend API method that writes the database.
   */
  private void writerBegin()
  {
    threadTotalCount.getAndIncrement();
    threadWriteCount.getAndIncrement();
  }
  /**
   * End a Backend API method that writes the database.
   */
  private void writerEnd()
  {
    threadWriteCount.getAndDecrement();
    threadTotalCount.getAndDecrement();
  }
  /**
   * Wait until there are no more threads accessing the database. It is assumed
   * that new threads have been prevented from entering the database at the time
   * this method is called.
   */
  private void waitUntilQuiescent()
  {
    while (threadTotalCount.get() > 0)
    {
      // Still have threads in the database so sleep a little
      try
      {
        Thread.sleep(500);
      }
      catch (InterruptedException e)
      {
        if (debugEnabled())
        {
          TRACER.debugCaught(DebugLogLevel.ERROR, e);
        }
      }
    }
  }
  /**
   * Get suggested minimum upper bound for given attribute type.
   * @param attrType attribute type
   * @return suggested upper bound
   *         or 0 if none suggested.
   */
  private int getAttributeBound(AttributeType attrType) {
    // HACK: This should be done by Directory Server
    // Schema parser and available in AttributeSyntax.
    String attrDefinition = attrType.getDefinition();
    try {
      int boundOpenIndex = attrDefinition.indexOf("{");
      if (boundOpenIndex == -1) {
        return 0;
      }
      int boundCloseIndex = attrDefinition.indexOf("}");
      if (boundCloseIndex == -1) {
        return 0;
      }
      String boundString = attrDefinition.substring(
        boundOpenIndex + 1, boundCloseIndex);
      return Integer.parseInt(boundString);
    } catch (Exception ex) {
      return 0;
    }
  }
  /**
   * {@inheritDoc}
   */
  public void configureBackend(Configuration cfg)
      throws ConfigException
  {
    Validator.ensureNotNull(cfg);
    Validator.ensureTrue(cfg instanceof NdbBackendCfg);
    this.cfg = (NdbBackendCfg)cfg;
    Set<DN> dnSet = this.cfg.getBaseDN();
    baseDNs = new DN[dnSet.size()];
    dnSet.toArray(baseDNs);
    this.DATABASE_NAME = this.cfg.getNdbDbname();
    this.ATTRLEN = this.cfg.getNdbAttrLen();
    this.ATTRLEN_STRING = Integer.toString(ATTRLEN);
    this.TXN_RETRY_LIMIT = this.cfg.getDeadlockRetryLimit();
    for (String attrName : this.cfg.getNdbAttrBlob()) {
      this.blobAttributes.add(attrName);
    }
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public void initializeBackend()
      throws ConfigException, InitializationException
  {
    // Checksum this db environment and register its offline state id/checksum.
    DirectoryServer.registerOfflineBackendStateID(this.getBackendID(), 0);
    // Load MySQL JDBC driver.
    try {
      // The newInstance() call is a work around for some
      // broken Java implementations
      Class.forName("com.mysql.jdbc.Driver").newInstance();
    } catch (Exception ex) {
      throw new InitializationException(
        ERR_NDB_DATABASE_EXCEPTION.get(ex.getMessage()));
    }
    // Get MySQL connection.
    try {
      sqlConn =
        DriverManager.getConnection("jdbc:mysql://" +
        cfg.getSqlConnectString(),
        cfg.getSqlUser(), cfg.getSqlPasswd());
    } catch (SQLException ex) {
      throw new InitializationException(
        ERR_NDB_DATABASE_EXCEPTION.get(ex.getMessage()));
    }
    // Initialize the database.
    Statement stmt = null;
    try {
      stmt = sqlConn.createStatement();
      stmt.execute("CREATE DATABASE IF NOT EXISTS " + DATABASE_NAME);
    } catch (SQLException ex) {
      throw new InitializationException(
        ERR_NDB_DATABASE_EXCEPTION.get("CREATE DATABASE failed: " +
        ex.getMessage()));
    } finally {
      // release resources.
      if (stmt != null) {
        try {
          stmt.close();
        } catch (SQLException ex) {}
        stmt = null;
      }
    }
    try {
      stmt = sqlConn.createStatement();
      stmt.execute("USE " + DATABASE_NAME);
    } catch (SQLException ex) {
      throw new InitializationException(
        ERR_NDB_DATABASE_EXCEPTION.get("USE failed: " +
        ex.getMessage()));
    } finally {
      // release resources.
      if (stmt != null) {
        try {
          stmt.close();
        } catch (SQLException ex) {}
        stmt = null;
      }
    }
    // Log a message indicating that database init
    // and schema bootstraping are about to start.
    logError(NOTE_NDB_BOOTSTRAP_SCHEMA.get());
    // Initialize dn2id table.
    try {
      stmt = sqlConn.createStatement();
      stmt.execute("CREATE TABLE IF NOT EXISTS " +
        DN2ID_TABLE + "(" +
        "eid bigint unsigned NOT NULL, " +
    "object_classes VARCHAR(1024) NOT NULL, " +
        "x_object_classes VARCHAR(1024) NOT NULL, " +
    "a0 VARCHAR(" + ATTRLEN_STRING + ") NOT NULL DEFAULT '', " +
    "a1 VARCHAR(" + ATTRLEN_STRING + ") NOT NULL DEFAULT '', " +
    "a2 VARCHAR(" + ATTRLEN_STRING + ") NOT NULL DEFAULT '', " +
    "a3 VARCHAR(" + ATTRLEN_STRING + ") NOT NULL DEFAULT '', " +
    "a4 VARCHAR(" + ATTRLEN_STRING + ") NOT NULL DEFAULT '', " +
    "a5 VARCHAR(" + ATTRLEN_STRING + ") NOT NULL DEFAULT '', " +
    "a6 VARCHAR(" + ATTRLEN_STRING + ") NOT NULL DEFAULT '', " +
    "a7 VARCHAR(" + ATTRLEN_STRING + ") NOT NULL DEFAULT '', " +
    "a8 VARCHAR(" + ATTRLEN_STRING + ") NOT NULL DEFAULT '', " +
    "a9 VARCHAR(" + ATTRLEN_STRING + ") NOT NULL DEFAULT '', " +
    "a10 VARCHAR(" + ATTRLEN_STRING + ") NOT NULL DEFAULT '', " +
    "a11 VARCHAR(" + ATTRLEN_STRING + ") NOT NULL DEFAULT '', " +
    "a12 VARCHAR(" + ATTRLEN_STRING + ") NOT NULL DEFAULT '', " +
    "a13 VARCHAR(" + ATTRLEN_STRING + ") NOT NULL DEFAULT '', " +
    "a14 VARCHAR(" + ATTRLEN_STRING + ") NOT NULL DEFAULT '', " +
    "a15 VARCHAR(" + ATTRLEN_STRING + ") NOT NULL DEFAULT '', " +
    "PRIMARY KEY (a0, a1, a2, a3, a4, a5, a6, " +
        "a7, a8, a9, a10, a11, a12, a13, a14, a15), " +
        "UNIQUE KEY eid (eid)" +
        ") ENGINE=ndb");
    } catch (SQLException ex) {
      throw new InitializationException(
        ERR_NDB_DATABASE_EXCEPTION.get("CREATE TABLE " +
          DN2ID_TABLE + " failed: " + ex.getMessage()));
    } finally {
      // release resources.
      if (stmt != null) {
        try {
          stmt.close();
        } catch (SQLException ex) {}
        stmt = null;
      }
    }
    // Initialize nextid autoincrement table.
    try {
      stmt = sqlConn.createStatement();
      stmt.execute("CREATE TABLE IF NOT EXISTS " +
        NEXTID_TABLE + "(" +
        "a bigint unsigned AUTO_INCREMENT PRIMARY KEY" +
        ") ENGINE=ndb");
    } catch (SQLException ex) {
      throw new InitializationException(
        ERR_NDB_DATABASE_EXCEPTION.get("CREATE TABLE " +
          NEXTID_TABLE + " failed: " + ex.getMessage()));
    } finally {
      // release resources.
      if (stmt != null) {
        try {
          stmt.close();
        } catch (SQLException ex) {}
        stmt = null;
      }
    }
    // Set schema read only.
    SchemaBackend schemaBackend =
      (SchemaBackend) DirectoryServer.getBackend("schema");
    if (schemaBackend != null) {
      schemaBackend.setWritabilityMode(WritabilityMode.DISABLED);
    }
    // Set defined attributes.
    Map<String, AttributeType> attrTypesMap =
      DirectoryServer.getSchema().getAttributeTypes();
    for (AttributeType attrType : attrTypesMap.values()) {
      String attrName = attrType.getNameOrOID();
      attrName2LC.put(attrName, attrName.toLowerCase());
      // Skip over server specific object classes.
      // FIXME: this is not clean.
      if (attrName.startsWith(DSOBJ_NAME_PREFIX)) {
        continue;
      }
      if (attrType.getUsage() == AttributeUsage.DIRECTORY_OPERATION) {
        if (operationalAttributes.contains(attrName)) {
          continue;
        }
        operationalAttributes.add(attrName);
      }
    }
    // Strip virtual attributes.
    for (VirtualAttributeRule rule :
      DirectoryServer.getVirtualAttributes())
    {
      String attrName = rule.getAttributeType().getNameOrOID();
      if (operationalAttributes.contains(attrName)) {
        operationalAttributes.remove(attrName);
      }
    }
    // Initialize objectClass tables.
    // TODO: dynamic schema validation and adjustement.
    Map<String,ObjectClass> objectClasses =
      DirectoryServer.getSchema().getObjectClasses();
    Set<Map.Entry<String, ObjectClass>> ocKeySet =
      objectClasses.entrySet();
    for (Map.Entry<String, ObjectClass> ocEntry : ocKeySet) {
      ObjectClass oc = ocEntry.getValue();
      String ocName = oc.getNameOrOID();
      if (oc.getObjectClassType() == ObjectClassType.ABSTRACT) {
        continue;
      }
      // Skip over server specific object classes.
      // FIXME: this is not clean.
      if (ocName.startsWith(DSOBJ_NAME_PREFIX)) {
        continue;
      }
      int nColumns = 0;
      StringBuilder attrsBuffer = new StringBuilder();
      Set<AttributeType> reqAttrs = oc.getRequiredAttributes();
      for (AttributeType attrType : reqAttrs) {
        String attrName = attrType.getNameOrOID();
        if (nColumns > 0) {
          attrsBuffer.append(", ");
        }
        attrsBuffer.append("`");
        attrsBuffer.append(attrName);
        attrsBuffer.append("`");
        if (blobAttributes.contains(attrName)) {
          attrsBuffer.append(" BLOB");
        } else {
          attrsBuffer.append(" VARCHAR(");
          int attrBound = getAttributeBound(attrType);
          if ((attrBound > 0) && (attrBound < NDB_MAXROWSIZE)) {
            attrsBuffer.append(Integer.toString(attrBound));
          } else {
            attrsBuffer.append(ATTRLEN_STRING);
          }
          attrsBuffer.append(")");
        }
        if (!attr2Oc.containsKey(attrName)) {
          attr2Oc.put(attrName, ocName);
        }
        nColumns++;
      }
      Set<AttributeType> optAttrs = oc.getOptionalAttributes();
      for (AttributeType attrType : optAttrs) {
        String attrName = attrType.getNameOrOID();
        if (nColumns > 0) {
          attrsBuffer.append(", ");
        }
        attrsBuffer.append("`");
        attrsBuffer.append(attrName);
        attrsBuffer.append("`");
        if (blobAttributes.contains(attrName)) {
          attrsBuffer.append(" BLOB");
        } else {
          attrsBuffer.append(" VARCHAR(");
          int attrBound = getAttributeBound(attrType);
          if ((attrBound > 0) && (attrBound < NDB_MAXROWSIZE)) {
            attrsBuffer.append(Integer.toString(attrBound));
          } else {
            attrsBuffer.append(ATTRLEN_STRING);
          }
          attrsBuffer.append(")");
        }
        if (!attr2Oc.containsKey(attrName)) {
          attr2Oc.put(attrName, ocName);
        }
        nColumns++;
      }
      if (attrsBuffer.toString().length() != 0) {
        attrsBuffer.append(", PRIMARY KEY(eid, mid))");
      }
      String attrsString = attrsBuffer.toString();
      try {
        stmt = sqlConn.createStatement();
        stmt.execute("CREATE TABLE IF NOT EXISTS " +
          "`" + ocName + "`" + " (" +
          "eid bigint unsigned NOT NULL, " +
          "mid int unsigned NOT NULL" +
          (attrsString.length() != 0 ? ", " +
           attrsString : ", PRIMARY KEY(eid, mid))") +
          " ENGINE=ndb PARTITION BY KEY(eid)");
      } catch (SQLException ex) {
        throw new InitializationException(
          ERR_NDB_DATABASE_EXCEPTION.get("CREATE TABLE " +
            ocName + " failed: " + ex.getMessage()));
      } finally {
        // release resources.
        if (stmt != null) {
          try {
            stmt.close();
          } catch (SQLException ex) {
          }
          stmt = null;
        }
      }
    }
    // Initialize operational attributes table.
    int nColumns = 0;
    StringBuilder attrsBuffer = new StringBuilder();
    for (String attrName : operationalAttributes) {
      if (nColumns > 0) {
        attrsBuffer.append(", ");
      }
      attrsBuffer.append("`");
      attrsBuffer.append(attrName);
      attrsBuffer.append("`");
      attrsBuffer.append(" VARCHAR(");
      attrsBuffer.append(ATTRLEN_STRING);
      attrsBuffer.append(")");
      nColumns++;
    }
    if (attrsBuffer.toString().length() != 0) {
      attrsBuffer.append(", PRIMARY KEY(eid))");
    }
    String attrsString = attrsBuffer.toString();
    try {
      stmt = sqlConn.createStatement();
      stmt.execute("CREATE TABLE IF NOT EXISTS " +
        "`" + OPATTRS_TABLE + "`" + " (" +
        "eid bigint unsigned NOT NULL" +
        (attrsString.length() != 0 ? ", " +
        attrsString : ", PRIMARY KEY(eid))") +
        " ENGINE=ndb PARTITION BY KEY(eid)");
    } catch (SQLException ex) {
      throw new InitializationException(
        ERR_NDB_DATABASE_EXCEPTION.get("CREATE TABLE " +
        OPATTRS_TABLE + " failed: " + ex.getMessage()));
    } finally {
      // release resources.
      if (stmt != null) {
        try {
          stmt.close();
        } catch (SQLException ex) {
        }
        stmt = null;
      }
    }
    // Initialize attribute options table.
    try {
      stmt = sqlConn.createStatement();
      stmt.execute("CREATE TABLE IF NOT EXISTS " +
        TAGS_TABLE + "(" +
        "eid bigint unsigned NOT NULL, " +
        "attr VARCHAR(" + ATTRLEN_STRING + "), " +
        "mid int unsigned NOT NULL, " +
        "tags VARCHAR(" + ATTRLEN_STRING + "), " +
        "PRIMARY KEY (eid, attr, mid))" +
        " ENGINE=ndb PARTITION BY KEY(eid)");
    } catch (SQLException ex) {
      throw new InitializationException(
        ERR_NDB_DATABASE_EXCEPTION.get("CREATE TABLE " +
          TAGS_TABLE + " failed: " + ex.getMessage()));
    } finally {
      // release resources.
      if (stmt != null) {
        try {
          stmt.close();
        } catch (SQLException ex) {}
        stmt = null;
      }
    }
    // Initialize configured indexes.
    for (String idx : cfg.listNdbIndexes()) {
      NdbIndexCfg indexCfg = cfg.getNdbIndex(idx);
      // TODO: Substring indexes.
      AttributeType attrType = indexCfg.getAttribute();
      String attrName = attrType.getNameOrOID();
      indexes.add(attrName);
      try {
        stmt = sqlConn.createStatement();
        stmt.execute("CREATE TABLE IF NOT EXISTS " +
          IDX_TABLE_PREFIX + attrName + "(" +
          "eid bigint unsigned NOT NULL, " +
          "mid int unsigned NOT NULL, " +
          "value VARCHAR(" + ATTRLEN_STRING + "), " +
          "PRIMARY KEY (eid, mid), " +
          "KEY value (value)" +
          ") ENGINE=ndb PARTITION BY KEY(eid)");
      } catch (SQLException ex) {
        throw new InitializationException(
          ERR_NDB_DATABASE_EXCEPTION.get("CREATE TABLE " +
          IDX_TABLE_PREFIX + attrName + " failed: " +
          ex.getMessage()));
      } finally {
        // release resources.
        if (stmt != null) {
          try {
            stmt.close();
          } catch (SQLException ex) {
          }
          stmt = null;
        }
      }
    }
    // Open Root Container.
    if (rootContainer == null) {
      rootContainer = initializeRootContainer();
    }
    try
    {
      // Log an informational message about the number of entries.
      Message message = NOTE_NDB_BACKEND_STARTED.get(
          cfg.getBackendId(), rootContainer.getEntryCount());
      logError(message);
    }
    catch(NdbApiException ex)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, ex);
      }
      Message message =
          WARN_NDB_GET_ENTRY_COUNT_FAILED.get(ex.getMessage());
      throw new InitializationException(message, ex);
    }
    WorkflowConfigurationMode workflowConfigMode =
      DirectoryServer.getWorkflowConfigurationMode();
    DirectoryServer.setWorkflowConfigurationMode(
      WorkflowConfigurationMode.MANUAL);
    for (DN dn : cfg.getBaseDN())
    {
      try
      {
        DirectoryServer.registerBaseDN(dn, this, false);
        WorkflowImpl workflowImpl = createWorkflow(dn);
        registerWorkflowWithDefaultNetworkGroup(workflowImpl);
      }
      catch (Exception e)
      {
        if (debugEnabled())
        {
          TRACER.debugCaught(DebugLogLevel.ERROR, e);
        }
        Message message = ERR_BACKEND_CANNOT_REGISTER_BASEDN.get(
            String.valueOf(dn), String.valueOf(e));
        throw new InitializationException(message, e);
      }
    }
    DirectoryServer.setWorkflowConfigurationMode(workflowConfigMode);
    // Register as an AlertGenerator.
    DirectoryServer.registerAlertGenerator(this);
    // Register this backend as a change listener.
    cfg.addNdbChangeListener(this);
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public void finalizeBackend()
  {
    // Deregister as a change listener.
    cfg.removeNdbChangeListener(this);
    WorkflowConfigurationMode workflowConfigMode =
      DirectoryServer.getWorkflowConfigurationMode();
    DirectoryServer.setWorkflowConfigurationMode(
      WorkflowConfigurationMode.MANUAL);
    // Deregister our base DNs.
    for (DN dn : rootContainer.getBaseDNs())
    {
      try
      {
        DirectoryServer.deregisterBaseDN(dn);
        deregisterWorkflowWithDefaultNetworkGroup(dn);
      }
      catch (Exception e)
      {
        if (debugEnabled())
        {
          TRACER.debugCaught(DebugLogLevel.ERROR, e);
        }
      }
    }
    DirectoryServer.setWorkflowConfigurationMode(workflowConfigMode);
    // Deregister our monitor providers.
    for (MonitorProvider<?> monitor : monitorProviders)
    {
      DirectoryServer.deregisterMonitorProvider(
           monitor.getMonitorInstanceName().toLowerCase());
    }
    monitorProviders = new ArrayList<MonitorProvider<?>>();
    // We presume the server will prevent more operations coming into this
    // backend, but there may be existing operations already in the
    // backend. We need to wait for them to finish.
    waitUntilQuiescent();
    // Close the database.
    try
    {
      rootContainer.close();
      rootContainer = null;
    }
    catch (NdbApiException e)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
      Message message = ERR_NDB_DATABASE_EXCEPTION.get(e.getMessage());
      logError(message);
    }
    try {
      if (sqlConn != null) {
        sqlConn.close();
      }
    } catch (SQLException e) {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
      Message message = ERR_NDB_DATABASE_EXCEPTION.get(e.getMessage());
      logError(message);
    }
    // Checksum this db environment and register its offline state id/checksum.
    DirectoryServer.registerOfflineBackendStateID(this.getBackendID(), 0);
    //Deregister the alert generator.
    DirectoryServer.deregisterAlertGenerator(this);
    // Make sure the thread counts are zero for next initialization.
    threadTotalCount.set(0);
    threadWriteCount.set(0);
    // Log an informational message.
    Message message = NOTE_BACKEND_OFFLINE.get(cfg.getBackendId());
    logError(message);
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public boolean isLocal()
  {
    return true;
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public boolean isIndexed(AttributeType attributeType, IndexType indexType)
  {
    // Substring indexing NYI.
    if (indexType == IndexType.SUBSTRING) {
      return false;
    }
    String attrName = attributeType.getNameOrOID();
    if (indexes.contains(attrName)) {
      return true;
    }
    return false;
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public boolean supportsLDIFExport()
  {
    return true;
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public boolean supportsLDIFImport()
  {
    return true;
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public boolean supportsBackup()
  {
    return false;
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public boolean supportsBackup(BackupConfig backupConfig,
                                StringBuilder unsupportedReason)
  {
    return false;
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public boolean supportsRestore()
  {
    return false;
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public HashSet<String> getSupportedFeatures()
  {
    return supportedFeatures;
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public HashSet<String> getSupportedControls()
  {
    return supportedControls;
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public DN[] getBaseDNs()
  {
    return baseDNs;
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public long getEntryCount()
  {
    if (rootContainer != null)
    {
      try
      {
        return rootContainer.getEntryCount();
      }
      catch (Exception e)
      {
        if (debugEnabled())
        {
          TRACER.debugCaught(DebugLogLevel.ERROR, e);
        }
      }
    }
    return -1;
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public ConditionResult hasSubordinates(DN entryDN)
         throws DirectoryException
  {
    long ret = numSubordinates(entryDN, false);
    if(ret < 0)
    {
      return ConditionResult.UNDEFINED;
    }
    else if(ret == 0)
    {
      return ConditionResult.FALSE;
    }
    else
    {
      return ConditionResult.TRUE;
    }
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public long numSubordinates(DN entryDN, boolean subtree)
      throws DirectoryException
  {
    // NYI.
    return -1;
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public Entry getEntry(DN entryDN) throws DirectoryException
  {
    readerBegin();
    EntryContainer ec;
    if (rootContainer != null)
    {
      ec = rootContainer.getEntryContainer(entryDN);
    }
    else
    {
      Message message = ERR_ROOT_CONTAINER_NOT_INITIALIZED.get(getBackendID());
      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
              message);
    }
    ec.sharedLock.lock();
    Entry entry;
    try
    {
      entry = ec.getEntry(entryDN);
    }
    catch (NdbApiException e)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
      throw createDirectoryException(e);
    }
    catch (NDBException e)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
                                   e.getMessageObject());
    }
    finally
    {
      ec.sharedLock.unlock();
      readerEnd();
    }
    return entry;
  }
  /**
   * Retrieves the requested entry from this backend.  Note that the
   * caller must hold a read or write lock on the specified DN. Note
   * that the lock is held after this method has completed execution.
   *
   * @param  entryDN  The distinguished name of the entry to retrieve.
   * @param  txn      Abstarct transaction for this operation.
   * @param  lockMode Lock mode for this operation.
   *
   * @return  The requested entry, or {@code null} if the entry does
   *          not exist.
   *
   * @throws  DirectoryException  If a problem occurs while trying to
   *                              retrieve the entry.
   */
  public Entry getEntryNoCommit(DN entryDN, AbstractTransaction txn,
    NdbOperation.LockMode lockMode) throws DirectoryException
  {
    readerBegin();
    EntryContainer ec;
    if (rootContainer != null)
    {
      ec = rootContainer.getEntryContainer(entryDN);
    }
    else
    {
      Message message = ERR_ROOT_CONTAINER_NOT_INITIALIZED.get(getBackendID());
      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
              message);
    }
    ec.sharedLock.lock();
    Entry entry;
    try
    {
      entry = ec.getEntryNoCommit(entryDN, txn, lockMode);
    }
    catch (NdbApiException e)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
      throw createDirectoryException(e);
    }
    catch (NDBException e)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
                                   e.getMessageObject());
    }
    finally
    {
      ec.sharedLock.unlock();
      readerEnd();
    }
    return entry;
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public void addEntry(Entry entry, AddOperation addOperation)
      throws DirectoryException, CanceledOperationException
  {
    throw createDirectoryException(new UnsupportedOperationException());
  }
  /**
   * Adds the provided entry to this backend.  This method must ensure
   * that the entry is appropriate for the backend and that no entry
   * already exists with the same DN.  The caller must hold a write
   * lock on the DN of the provided entry.
   *
   * @param  entry         The entry to add to this backend.
   * @param  addOperation  The add operation with which the new entry
   *                       is associated.  This may be {@code null}
   *                       for adds performed internally.
   * @param  txn           Abstract transaction for this operation.
   *
   * @throws DirectoryException  If a problem occurs while trying to
   *                             add the entry.
   *
   * @throws CanceledOperationException  If this backend noticed and
   *                                       reacted to a request to
   *                                       cancel or abandon the add
   *                                       operation.
   */
  public void addEntry(Entry entry, AddOperation addOperation,
    AbstractTransaction txn)
      throws DirectoryException, CanceledOperationException
  {
    writerBegin();
    DN entryDN = entry.getDN();
    EntryContainer ec;
    if (rootContainer != null)
    {
      ec = rootContainer.getEntryContainer(entryDN);
    }
    else
    {
      Message message = ERR_ROOT_CONTAINER_NOT_INITIALIZED.get(getBackendID());
      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
              message);
    }
    ec.sharedLock.lock();
    try
    {
      ec.addEntry(entry, addOperation, txn);
    }
    catch (NdbApiException e)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
      throw createDirectoryException(e);
    }
    catch (NDBException e)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
                                   e.getMessageObject());
    }
    finally
    {
      ec.sharedLock.unlock();
      writerEnd();
    }
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public void deleteEntry(DN entryDN, DeleteOperation deleteOperation)
      throws DirectoryException, CanceledOperationException
  {
    throw createDirectoryException(new UnsupportedOperationException());
  }
  /**
   * Removes the specified entry from this backend.  This method must
   * ensure that the entry exists and that it does not have any
   * subordinate entries (unless the backend supports a subtree delete
   * operation and the client included the appropriate information in
   * the request).  The caller must hold a write lock on the provided
   * entry DN.
   *
   * @param  entryDN          The DN of the entry to remove from this
   *                          backend.
   * @param  entry            The entry to delete.
   * @param  deleteOperation  The delete operation with which this
   *                          action is associated.  This may be
   *                          {@code null} for deletes performed
   *                          internally.
   * @param  txn              Abstract transaction for this operation.
   *
   * @throws DirectoryException  If a problem occurs while trying to
   *                             remove the entry.
   *
   * @throws CanceledOperationException  If this backend noticed and
   *                                       reacted to a request to
   *                                       cancel or abandon the
   *                                       delete operation.
   */
  public void deleteEntry(DN entryDN, Entry entry,
    DeleteOperation deleteOperation, AbstractTransaction txn)
    throws DirectoryException, CanceledOperationException
  {
    writerBegin();
    EntryContainer ec;
    if (rootContainer != null)
    {
      ec = rootContainer.getEntryContainer(entryDN);
    }
    else
    {
      Message message = ERR_ROOT_CONTAINER_NOT_INITIALIZED.get(getBackendID());
      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
              message);
    }
    ec.sharedLock.lock();
    try
    {
      ec.deleteEntry(entryDN, entry, deleteOperation, txn);
    }
    catch (NdbApiException e)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
      throw createDirectoryException(e);
    }
    catch (NDBException e)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
                                   e.getMessageObject());
    }
    finally
    {
      ec.sharedLock.unlock();
      writerEnd();
    }
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public void replaceEntry(Entry oldEntry, Entry newEntry,
    ModifyOperation modifyOperation)
      throws DirectoryException, CanceledOperationException
  {
    throw createDirectoryException(new UnsupportedOperationException());
  }
  /**
   * Replaces the specified entry with the provided entry in this
   * backend. The backend must ensure that an entry already exists
   * with the same DN as the provided entry. The caller must hold a
   * write lock on the DN of the provided entry.
   *
   * @param oldEntry
   *          The original entry that is being replaced.
   * @param newEntry
   *          The new entry to use in place of the existing entry with
   *          the same DN.
   * @param modifyOperation
   *          The modify operation with which this action is
   *          associated. This may be {@code null} for modifications
   *          performed internally.
   * @param txn
   *          Abstract transaction for this operation.
   * @throws DirectoryException
   *           If a problem occurs while trying to replace the entry.
   * @throws CanceledOperationException
   *           If this backend noticed and reacted to a request to
   *           cancel or abandon the modify operation.
   */
  public void replaceEntry(Entry oldEntry, Entry newEntry,
    ModifyOperation modifyOperation, AbstractTransaction txn)
      throws DirectoryException, CanceledOperationException
  {
    writerBegin();
    DN entryDN = newEntry.getDN();
    EntryContainer ec;
    if (rootContainer != null)
    {
      ec = rootContainer.getEntryContainer(entryDN);
    }
    else
    {
      Message message = ERR_ROOT_CONTAINER_NOT_INITIALIZED.get(getBackendID());
      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
              message);
    }
    ec.sharedLock.lock();
    try
    {
      ec.replaceEntry(oldEntry, newEntry, modifyOperation, txn);
    }
    catch (NdbApiException e)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
      throw createDirectoryException(e);
    }
    catch (NDBException e)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
                                   e.getMessageObject());
    }
    finally
    {
      ec.sharedLock.unlock();
      writerEnd();
    }
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public void renameEntry(DN currentDN, Entry entry,
                          ModifyDNOperation modifyDNOperation)
      throws DirectoryException, CanceledOperationException
  {
    throw createDirectoryException(new UnsupportedOperationException());
  }
  /**
   * Moves and/or renames the provided entry in this backend, altering
   * any subordinate entries as necessary. This must ensure that an
   * entry already exists with the provided current DN, and that no
   * entry exists with the target DN of the provided entry. The caller
   * must hold write locks on both the current DN and the new DN for
   * the entry.
   *
   * @param currentDN
   *          The current DN of the entry to be replaced.
   * @param entry
   *          The new content to use for the entry.
   * @param modifyDNOperation
   *          The modify DN operation with which this action is
   *          associated. This may be {@code null} for modify DN
   *          operations performed internally.
   * @param txn
   *          Abstract transaction for this operation.
   * @throws DirectoryException
   *           If a problem occurs while trying to perform the rename.
   * @throws CanceledOperationException
   *           If this backend noticed and reacted to a request to
   *           cancel or abandon the modify DN operation.
   */
  public void renameEntry(DN currentDN, Entry entry,
                          ModifyDNOperation modifyDNOperation,
                          AbstractTransaction txn)
      throws DirectoryException, CanceledOperationException
  {
    writerBegin();
    EntryContainer currentContainer;
    if (rootContainer != null)
    {
      currentContainer = rootContainer.getEntryContainer(currentDN);
    }
    else
    {
      Message message = ERR_ROOT_CONTAINER_NOT_INITIALIZED.get(getBackendID());
      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
              message);
    }
    EntryContainer container = rootContainer.getEntryContainer(entry.getDN());
    if (currentContainer != container)
    {
      // FIXME: No reason why we cannot implement a move between containers
      // since the containers share the same database environment.
      Message msg = WARN_NDB_FUNCTION_NOT_SUPPORTED.get();
      throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
                                   msg);
    }
    try
    {
      currentContainer.sharedLock.lock();
      currentContainer.renameEntry(currentDN, entry, modifyDNOperation, txn);
    }
    catch (NdbApiException e)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
      throw createDirectoryException(e);
    }
    catch (NDBException e)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
                                   e.getMessageObject());
    }
    finally
    {
      currentContainer.sharedLock.unlock();
      writerEnd();
    }
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public void search(SearchOperation searchOperation)
      throws DirectoryException, CanceledOperationException
  {
    // Abort any Group or ACI bulk search operations.
    List<Control> requestControls = searchOperation.getRequestControls();
    if (requestControls != null) {
      for (Control c : requestControls) {
        if (c.getOID().equals(OID_INTERNAL_GROUP_MEMBERSHIP_UPDATE)) {
          return;
        }
      }
    }
    EntryContainer ec;
    if (rootContainer != null)
    {
      ec = rootContainer.getEntryContainer(searchOperation.getBaseDN());
    }
    else
    {
      Message message = ERR_ROOT_CONTAINER_NOT_INITIALIZED.get(getBackendID());
      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
              message);
    }
    ec.sharedLock.lock();
    readerBegin();
    try
    {
      ec.search(searchOperation);
    }
    catch (NdbApiException e)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
      throw createDirectoryException(e);
    }
    catch (NDBException e)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
      Message message = ERR_NDB_DATABASE_EXCEPTION.get(e.getMessage());
      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
                                   message);
    }
    finally
    {
      ec.sharedLock.unlock();
      readerEnd();
    }
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public void exportLDIF(LDIFExportConfig exportConfig)
      throws DirectoryException
  {
    if (!DirectoryServer.isRunning()) {
      // No offline export for now.
      Message message = ERR_NDB_EXPORT_OFFLINE_NOT_SUPPORTED.get();
      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
                                   message);
    }
    try
    {
      if (rootContainer == null) {
        rootContainer = initializeRootContainer();
      }
      ExportJob exportJob = new ExportJob(exportConfig);
      exportJob.exportLDIF(rootContainer);
    }
    catch (IOException ioe)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, ioe);
      }
      Message message = ERR_NDB_IO_ERROR.get(ioe.getMessage());
      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
                                   message);
    }
    catch (NdbApiException nae)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, nae);
      }
      throw createDirectoryException(nae);
    }
    catch (LDIFException e)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
                                   e.getMessageObject());
    }
    catch (InitializationException ie)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, ie);
      }
      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
                                   ie.getMessageObject());
    }
    catch (ConfigException ce)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, ce);
      }
      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
                                   ce.getMessageObject());
    }
    finally
    {
      // leave the backend in the same state.
      if (rootContainer != null)
      {
        try
        {
          rootContainer.close();
          rootContainer = null;
        }
        catch (NdbApiException e)
        {
          if (debugEnabled())
          {
            TRACER.debugCaught(DebugLogLevel.ERROR, e);
          }
        }
      }
    }
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public LDIFImportResult importLDIF(LDIFImportConfig importConfig)
      throws DirectoryException
  {
    if (!DirectoryServer.isRunning()) {
      // No offline import for now.
      Message message = ERR_NDB_IMPORT_OFFLINE_NOT_SUPPORTED.get();
      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
                                   message);
    }
    try
    {
      Importer importer = new Importer(importConfig);
      if (rootContainer == null) {
        rootContainer = initializeRootContainer();
      }
      return importer.processImport(rootContainer);
    }
    catch (IOException ioe)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, ioe);
      }
      Message message = ERR_NDB_IO_ERROR.get(ioe.getMessage());
      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
                                   message);
    }
    catch (NDBException ne)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, ne);
      }
      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
                                   ne.getMessageObject());
    }
    catch (InitializationException ie)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, ie);
      }
      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
                                   ie.getMessageObject());
    }
    catch (ConfigException ce)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, ce);
      }
      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
                                   ce.getMessageObject());
    }
    finally
    {
      // leave the backend in the same state.
      try
      {
        if (rootContainer != null)
        {
          rootContainer.close();
          rootContainer = null;
        }
      }
      catch (NdbApiException nae)
      {
        if (debugEnabled())
        {
          TRACER.debugCaught(DebugLogLevel.ERROR, nae);
        }
      }
    }
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public void createBackup(BackupConfig backupConfig)
      throws DirectoryException
  {
    // Not supported, do nothing.
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public void removeBackup(BackupDirectory backupDirectory, String backupID)
      throws DirectoryException
  {
    // Not supported, do nothing.
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public void restoreBackup(RestoreConfig restoreConfig)
      throws DirectoryException
  {
    // Not supported, do nothing.
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public boolean isConfigurationAcceptable(Configuration configuration,
                                           List<Message> unacceptableReasons)
  {
    NdbBackendCfg config = (NdbBackendCfg) configuration;
    return isConfigurationChangeAcceptable(config, unacceptableReasons);
  }
  /**
   * {@inheritDoc}
   */
  public boolean isConfigurationChangeAcceptable(
      NdbBackendCfg cfg,
      List<Message> unacceptableReasons)
  {
    return true;
  }
  /**
   * {@inheritDoc}
   */
  public ConfigChangeResult applyConfigurationChange(NdbBackendCfg newCfg)
  {
    ConfigChangeResult ccr;
    ResultCode resultCode = ResultCode.SUCCESS;
    ArrayList<Message> messages = new ArrayList<Message>();
    ccr = new ConfigChangeResult(resultCode, false, messages);
    return ccr;
  }
  /**
   * Returns a handle to the root container currently used by this backend.
   * The rootContainer could be NULL if the backend is not initialized.
   *
   * @return The RootContainer object currently used by this backend.
   */
  public RootContainer getRootContainer()
  {
    return rootContainer;
  }
  /**
   * Creates a customized DirectoryException from the NdbApiException
   * thrown by NDB backend.
   *
   * @param  e The NdbApiException to be converted.
   * @return  DirectoryException created from exception.
   */
  DirectoryException createDirectoryException(Exception e)
  {
    ResultCode resultCode = DirectoryServer.getServerErrorResultCode();
    Message message = null;
    String jeMessage = e.getMessage();
    if (jeMessage == null)
    {
      jeMessage = stackTraceToSingleLineString(e);
    }
    message = ERR_NDB_DATABASE_EXCEPTION.get(jeMessage);
    return new DirectoryException(resultCode, message, e);
  }
  /**
   * {@inheritDoc}
   */
  public String getClassName()
  {
    return CLASS_NAME;
  }
  /**
   * {@inheritDoc}
   */
  public LinkedHashMap<String,String> getAlerts()
  {
    LinkedHashMap<String,String> alerts = new LinkedHashMap<String,String>();
    alerts.put(ALERT_TYPE_BACKEND_ENVIRONMENT_UNUSABLE,
               ALERT_DESCRIPTION_BACKEND_ENVIRONMENT_UNUSABLE);
    return alerts;
  }
  /**
   * {@inheritDoc}
   */
  public DN getComponentEntryDN()
  {
    return cfg.dn();
  }
  private RootContainer initializeRootContainer()
      throws ConfigException, InitializationException
  {
    // Open the database environment
    try
    {
      RootContainer rc = new RootContainer(this, cfg);
      rc.open();
      return rc;
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
      Message message = ERR_NDB_OPEN_ENV_FAIL.get(e.getMessage());
      throw new InitializationException(message, e);
    }
  }
  /**
   * {@inheritDoc}
   */
  public void preloadEntryCache() throws
    UnsupportedOperationException
  {
    throw new UnsupportedOperationException("Operation not supported");
  }
  /**
   * Creates one workflow for a given base DN in a backend.
   *
   * @param baseDN   the base DN of the workflow to create
   * @param backend  the backend handled by the workflow
   *
   * @return the newly created workflow
   *
   * @throws DirectoryException  If the workflow ID for the provided
   *                             workflow conflicts with the workflow
   *                             ID of an existing workflow.
   */
  private WorkflowImpl createWorkflow(DN baseDN) throws DirectoryException
  {
    String backendID = this.getBackendID();
    // Create a root workflow element to encapsulate the backend
    NDBWorkflowElement rootWE =
        NDBWorkflowElement.createAndRegister(backendID, this);
    // The workflow ID is "backendID + baseDN".
    // We cannot use backendID as workflow identifier because a backend
    // may handle several base DNs. We cannot use baseDN either because
    // we might want to configure several workflows handling the same
    // baseDN through different network groups. So a mix of both
    // backendID and baseDN should be ok.
    String workflowID = backendID + "#" + baseDN.toString();
    // Create the worklfow for the base DN and register the workflow with
    // the server.
    WorkflowImpl workflowImpl = new WorkflowImpl(workflowID, baseDN,
      rootWE.getWorkflowElementID(), (WorkflowElement) rootWE);
    workflowImpl.register();
    return workflowImpl;
  }
  /**
   * Registers a workflow with the default network group.
   *
   * @param workflowImpl  The workflow to register with the
   *                      default network group
   *
   * @throws  DirectoryException  If the workflow is already registered with
   *                              the default network group
   */
  private void registerWorkflowWithDefaultNetworkGroup(
      WorkflowImpl workflowImpl
      ) throws DirectoryException
  {
    NetworkGroup defaultNetworkGroup = NetworkGroup.getDefaultNetworkGroup();
    defaultNetworkGroup.registerWorkflow(workflowImpl);
  }
  /**
   * Deregisters a workflow with the default network group and
   * deregisters the workflow with the server. This method is
   * intended to be called when workflow configuration mode is
   * auto.
   *
   * @param baseDN  the DN of the workflow to deregister
   */
  private void deregisterWorkflowWithDefaultNetworkGroup(
      DN baseDN
      )
  {
    String backendID = this.getBackendID();
    // Get the default network group and deregister all the workflows
    // being configured for the backend (there is one worklfow per
    // backend base DN).
    NetworkGroup defaultNetworkGroup = NetworkGroup.getDefaultNetworkGroup();
    Workflow workflow = defaultNetworkGroup.deregisterWorkflow(baseDN);
    WorkflowImpl workflowImpl = (WorkflowImpl) workflow;
    // The workflow ID is "backendID + baseDN".
    // We cannot use backendID as workflow identifier because a backend
    // may handle several base DNs. We cannot use baseDN either because
    // we might want to configure several workflows handling the same
    // baseDN through different network groups. So a mix of both
    // backendID and baseDN should be ok.
    String workflowID = backendID + "#" + baseDN.toString();
    NDBWorkflowElement.remove(backendID);
    workflowImpl.deregister(workflowID);
  }
}
opends/src/server/org/opends/server/backends/ndb/DatabaseContainer.java
New file
@@ -0,0 +1,83 @@
/*
 * 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/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * 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/opends/resource/legal-notices/OpenDS.LICENSE.  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.
 */
package org.opends.server.backends.ndb;
import static org.opends.server.loggers.debug.DebugLogger.*;
import org.opends.server.loggers.debug.DebugTracer;
/**
 * This class is a wrapper around the NDB database object.
 */
public abstract class DatabaseContainer
{
  /**
   * The tracer object for the debug logger.
   */
  private static final DebugTracer TRACER = getTracer();
  /**
   * The database entryContainer.
   */
  protected EntryContainer entryContainer;
  /**
   * The name of the database within the entryContainer.
   */
  protected String name;
  /**
   * Create a new DatabaseContainer object.
   *
   * @param name The name of the entry database.
   * @param entryContainer The entryContainer of the entry database.
   */
  protected DatabaseContainer(String name, EntryContainer entryContainer)
  {
    this.entryContainer = entryContainer;
    this.name = name;
  }
  /**
   * Get a string representation of this object.
   * @return return A string representation of this object.
   */
  @Override
  public String toString()
  {
    return name;
  }
  /**
   * Get the NDB database name for this database container.
   *
   * @return NDB database name for this database container.
   */
  public String getName()
  {
    return name;
  }
}
opends/src/server/org/opends/server/backends/ndb/EntryContainer.java
New file
@@ -0,0 +1,2205 @@
/*
 * 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/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * 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/opends/resource/legal-notices/OpenDS.LICENSE.  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.
 */
package org.opends.server.backends.ndb;
import com.mysql.cluster.ndbj.NdbApiException;
import com.mysql.cluster.ndbj.NdbApiPermanentException;
import com.mysql.cluster.ndbj.NdbApiTemporaryException;
import com.mysql.cluster.ndbj.NdbError;
import com.mysql.cluster.ndbj.NdbOperation;
import org.opends.messages.Message;
import org.opends.server.api.Backend;
import org.opends.server.core.AddOperation;
import org.opends.server.core.DeleteOperation;
import org.opends.server.core.ModifyOperation;
import org.opends.server.core.ModifyDNOperation;
import org.opends.server.core.SearchOperation;
import org.opends.server.types.*;
import org.opends.server.util.ServerConstants;
import java.util.*;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import static org.opends.messages.NdbMessages.*;
import org.opends.messages.MessageBuilder;
import static org.opends.server.loggers.debug.DebugLogger.*;
import org.opends.server.loggers.debug.DebugTracer;
import static org.opends.server.util.ServerConstants.*;
import org.opends.server.admin.server.ConfigurationChangeListener;
import org.opends.server.admin.std.server.NdbBackendCfg;
import org.opends.server.backends.ndb.OperationContainer.DN2IDSearchCursor;
import org.opends.server.backends.ndb.OperationContainer.SearchCursorResult;
import org.opends.server.config.ConfigException;
/**
 * Storage container for LDAP entries.  Each base DN of a NDB backend is given
 * its own entry container.  The entry container is the object that implements
 * the guts of the backend API methods for LDAP operations.
 */
public class EntryContainer
    implements ConfigurationChangeListener<NdbBackendCfg>
{
  /**
   * The tracer object for the debug logger.
   */
  private static final DebugTracer TRACER = getTracer();
  /**
   * The backend to which this entry entryContainer belongs.
   */
  private Backend backend;
  /**
   * The root container in which this entryContainer belongs.
   */
  private RootContainer rootContainer;
  /**
   * The baseDN this entry container is responsible for.
   */
  private DN baseDN;
  /**
   * The backend configuration.
   */
  private NdbBackendCfg config;
  /**
   * The operation container.
   */
  private OperationContainer dn2id;
  /**
   * Cached values from config so they don't have to be retrieved per operation.
   */
  private int deadlockRetryLimit;
  private int subtreeDeleteSizeLimit;
  private int subtreeDeleteBatchSize;
  private String databasePrefix;
  /**
   * A read write lock to handle schema changes and bulk changes.
   */
  private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
  final Lock sharedLock = lock.readLock();
  final Lock exclusiveLock = lock.writeLock();
  /**
   * Create a new entry entryContainer object.
   *
   * @param baseDN  The baseDN this entry container will be responsible for
   *                storing on disk.
   * @param databasePrefix The prefix to use in the database names used by
   *                       this entry container.
   * @param backend A reference to the NDB backend that is creating this entry
   *                container.
   * @param config The configuration of the NDB backend.
   * @param rootContainer The root container this entry container is in.
   * @throws ConfigException if a configuration related error occurs.
   */
  public EntryContainer(DN baseDN, String databasePrefix, Backend backend,
                        NdbBackendCfg config, RootContainer rootContainer)
      throws ConfigException
  {
    this.backend = backend;
    this.baseDN = baseDN;
    this.config = config;
    this.rootContainer = rootContainer;
    StringBuilder builder = new StringBuilder(databasePrefix.length());
    for (int i = 0; i < databasePrefix.length(); i++)
    {
      char ch = databasePrefix.charAt(i);
      if (Character.isLetterOrDigit(ch))
      {
        builder.append(ch);
      }
      else
      {
        builder.append('_');
      }
    }
    this.databasePrefix = builder.toString();
    this.deadlockRetryLimit = config.getDeadlockRetryLimit();
    config.addNdbChangeListener(this);
  }
  /**
   * Opens the entryContainer for reading and writing.
   *
   * @throws NdbApiException If an error occurs in the NDB database.
   * @throws ConfigException if a configuration related error occurs.
   */
  public void open()
      throws NdbApiException, ConfigException
  {
    try
    {
      dn2id = new OperationContainer(BackendImpl.DN2ID_TABLE, this);
    }
    catch (NdbApiException de)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, de);
      }
      close();
      throw de;
    }
  }
  /**
   * Closes the entry entryContainer.
   *
   * @throws NdbApiException If an error occurs in the NDB database.
   */
  public void close()
      throws NdbApiException
  {
    config.removeNdbChangeListener(this);
    rootContainer = null;
    backend = null;
    config = null;
    dn2id = null;
  }
  /**
   * Retrieves a reference to the root container in which this entry container
   * exists.
   *
   * @return  A reference to the root container in which this entry container
   *          exists.
   */
  public RootContainer getRootContainer()
  {
    return rootContainer;
  }
  /**
   * Get the DN database used by this entry entryContainer. The entryContainer
   * must have been opened.
   *
   * @return The DN database.
   */
  public OperationContainer getDN2ID()
  {
    return dn2id;
  }
  /**
   * Processes the specified search in this entryContainer.
   * Matching entries should be provided back to the core server using the
   * <CODE>SearchOperation.returnEntry</CODE> method.
   *
   * @param searchOperation The search operation to be processed.
   * @throws DirectoryException If a problem occurs while processing
   *         the search.
   * @throws CanceledOperationException If operation is canceled
   *         while in progress.
   * @throws NdbApiException If an error occurs in the NDB database.
   * @throws NDBException If an error occurs in the NDB backend.
   */
  public void search(SearchOperation searchOperation)
       throws CanceledOperationException, DirectoryException,
       NdbApiException, NDBException
  {
    DN baseDN = searchOperation.getBaseDN();
    SearchScope searchScope = searchOperation.getScope();
    AbstractTransaction txn = new AbstractTransaction(rootContainer);
    int txnRetries = 0;
    boolean completed = false;
    while (!completed) {
      try {
        // Handle base-object search first.
        if (searchScope == SearchScope.BASE_OBJECT) {
          // Fetch the base entry.
          Entry baseEntry = dn2id.get(txn, baseDN,
            NdbOperation.LockMode.LM_CommittedRead);
          // The base entry must exist for a successful result.
          if (baseEntry == null) {
            // Check for referral entries above the base entry.
            targetEntryReferrals(txn, baseDN, searchScope);
            Message message =
              ERR_NDB_SEARCH_NO_SUCH_OBJECT.get(baseDN.toString());
            DN matchedDN = getMatchedDN(txn, baseDN);
            throw new DirectoryException(ResultCode.NO_SUCH_OBJECT,
              message, matchedDN, null);
          }
          if (!isManageDsaITOperation(searchOperation)) {
            checkTargetForReferral(baseEntry, searchOperation.getScope());
          }
          if (searchOperation.getFilter().matchesEntry(baseEntry)) {
            searchOperation.returnEntry(baseEntry, null);
          }
          completed = true;
          return;
        }
        IndexFilter indexFilter = new IndexFilter(txn, this, searchOperation);
        if (indexFilter.evaluate()) {
          searchIndexed(searchOperation, indexFilter);
          completed = true;
        } else {
          DN2IDSearchCursor cursor = dn2id.getSearchCursor(txn, baseDN);
          searchNotIndexed(searchOperation, cursor);
          completed = true;
        }
      } catch (NdbApiTemporaryException databaseException) {
        if (txnRetries < BackendImpl.TXN_RETRY_LIMIT) {
          txnRetries++;
          continue;
        }
        throw databaseException;
      } finally {
        if (txn != null) {
          txn.close();
        }
      }
    }
  }
  /**
   * We were able to obtain a set of candidate entries for the
   * search from the indexes.
   */
  private void searchIndexed(SearchOperation searchOperation,
    IndexFilter indexFilter)
       throws CanceledOperationException, NdbApiException,
       DirectoryException, NDBException
  {
    DN baseDN = searchOperation.getBaseDN();
    SearchScope searchScope = searchOperation.getScope();
    boolean manageDsaIT = isManageDsaITOperation(searchOperation);
    AbstractTransaction txn = new AbstractTransaction(rootContainer);
    try {
      // Fetch the base entry.
      Entry baseEntry = null;
      baseEntry = dn2id.get(txn, baseDN, NdbOperation.LockMode.LM_Read);
      // The base entry must exist for a successful result.
      if (baseEntry == null) {
        // Check for referral entries above the base entry.
        targetEntryReferrals(txn, baseDN, searchScope);
        Message message = ERR_NDB_SEARCH_NO_SUCH_OBJECT.get(baseDN.toString());
        DN matchedDN = getMatchedDN(txn, baseDN);
        throw new DirectoryException(ResultCode.NO_SUCH_OBJECT,
          message, matchedDN, null);
      }
      if (!manageDsaIT) {
        checkTargetForReferral(baseEntry, searchScope);
      }
      /*
       * The base entry is only included for whole subtree search.
       */
      if (searchScope == SearchScope.WHOLE_SUBTREE) {
        if (searchOperation.getFilter().matchesEntry(baseEntry)) {
          searchOperation.returnEntry(baseEntry, null);
        }
      }
      int lookthroughCount = 0;
      int lookthroughLimit =
        searchOperation.getClientConnection().getLookthroughLimit();
      indexFilter.scan();
      try {
        long eid = indexFilter.getNext();
        while (eid != 0) {
          if (lookthroughLimit > 0 && lookthroughCount > lookthroughLimit) {
            //Lookthrough limit exceeded
            searchOperation.setResultCode(ResultCode.ADMIN_LIMIT_EXCEEDED);
            searchOperation.appendErrorMessage(
              NOTE_NDB_LOOKTHROUGH_LIMIT_EXCEEDED.get(lookthroughLimit));
            return;
          }
          // Fetch the candidate entry from the database.
          Entry entry = dn2id.get(txn, eid, NdbOperation.LockMode.LM_Read);
          // We have found a subordinate entry.
          DN dn = entry.getDN();
          boolean isInScope = true;
          if (searchScope == SearchScope.SINGLE_LEVEL) {
            // Check if this entry is an immediate child.
            if ((dn.getNumComponents() !=
              baseDN.getNumComponents() + 1)) {
              isInScope = false;
            }
          }
          if (isInScope) {
            // Process the candidate entry.
            if (entry != null) {
              lookthroughCount++;
              if (manageDsaIT || !entry.isReferral()) {
                // Filter the entry.
                if (searchOperation.getFilter().matchesEntry(entry)) {
                  if (!searchOperation.returnEntry(entry, null)) {
                    // We have been told to discontinue processing of the
                    // search. This could be due to size limit exceeded or
                    // operation cancelled.
                    return;
                  }
                }
              } else {
                if (entry.isReferral()) {
                  try {
                    checkTargetForReferral(entry, searchScope);
                  } catch (DirectoryException refException) {
                    if (refException.getResultCode() == ResultCode.REFERRAL) {
                      SearchResultReference reference =
                        new SearchResultReference(
                        refException.getReferralURLs());
                      if (!searchOperation.returnReference(dn, reference)) {
                        // We have been told to discontinue processing of the
                        // search. This could be due to size limit exceeded or
                        // operation cancelled.
                        return;
                      }
                    } else {
                      throw refException;
                    }
                  }
                }
              }
            }
          }
          searchOperation.checkIfCanceled(false);
          // Move to the next record.
          eid = indexFilter.getNext();
        }
      } finally {
        indexFilter.close();
      }
    } finally {
      if (txn != null) {
        txn.close();
      }
    }
  }
  /**
   * We were not able to obtain a set of candidate entries for the
   * search from the indexes.
   */
  private void searchNotIndexed(SearchOperation searchOperation,
    DN2IDSearchCursor cursor)
       throws CanceledOperationException, NdbApiException,
       DirectoryException, NDBException
  {
    DN baseDN = searchOperation.getBaseDN();
    SearchScope searchScope = searchOperation.getScope();
    boolean manageDsaIT = isManageDsaITOperation(searchOperation);
    AbstractTransaction txn = new AbstractTransaction(rootContainer);
    try {
      // Fetch the base entry.
      Entry baseEntry = null;
      baseEntry = dn2id.get(txn, baseDN, NdbOperation.LockMode.LM_Read);
      // The base entry must exist for a successful result.
      if (baseEntry == null) {
        // Check for referral entries above the base entry.
        targetEntryReferrals(txn, baseDN, searchScope);
        Message message = ERR_NDB_SEARCH_NO_SUCH_OBJECT.get(baseDN.toString());
        DN matchedDN = getMatchedDN(txn, baseDN);
        throw new DirectoryException(ResultCode.NO_SUCH_OBJECT,
          message, matchedDN, null);
      }
      if (!manageDsaIT) {
        checkTargetForReferral(baseEntry, searchScope);
      }
      /*
       * The base entry is only included for whole subtree search.
       */
      if (searchScope == SearchScope.WHOLE_SUBTREE) {
        if (searchOperation.getFilter().matchesEntry(baseEntry)) {
          searchOperation.returnEntry(baseEntry, null);
        }
      }
      int lookthroughCount = 0;
      int lookthroughLimit =
        searchOperation.getClientConnection().getLookthroughLimit();
      cursor.open();
      try {
        SearchCursorResult result = cursor.getNext();
        while (result != null) {
          if (lookthroughLimit > 0 && lookthroughCount > lookthroughLimit) {
            //Lookthrough limit exceeded
            searchOperation.setResultCode(ResultCode.ADMIN_LIMIT_EXCEEDED);
            searchOperation.appendErrorMessage(
              NOTE_NDB_LOOKTHROUGH_LIMIT_EXCEEDED.get(lookthroughLimit));
            return;
          }
          // We have found a subordinate entry.
          DN dn = DN.decode(result.dn);
          boolean isInScope = true;
          if (searchScope == SearchScope.SINGLE_LEVEL) {
            // Check if this entry is an immediate child.
            if ((dn.getNumComponents() !=
              baseDN.getNumComponents() + 1)) {
              isInScope = false;
            }
          }
          if (isInScope) {
            // Fetch the candidate entry from the database.
            Entry entry = dn2id.get(txn, dn, NdbOperation.LockMode.LM_Read);
            // Process the candidate entry.
            if (entry != null) {
              lookthroughCount++;
              if (manageDsaIT || !entry.isReferral()) {
                // Filter the entry.
                if (searchOperation.getFilter().matchesEntry(entry)) {
                  if (!searchOperation.returnEntry(entry, null)) {
                    // We have been told to discontinue processing of the
                    // search. This could be due to size limit exceeded or
                    // operation cancelled.
                    return;
                  }
                }
              } else {
                if (entry.isReferral()) {
                  try {
                    checkTargetForReferral(entry, searchScope);
                  } catch (DirectoryException refException) {
                    if (refException.getResultCode() == ResultCode.REFERRAL) {
                      SearchResultReference reference =
                        new SearchResultReference(
                        refException.getReferralURLs());
                      if (!searchOperation.returnReference(dn, reference)) {
                        // We have been told to discontinue processing of the
                        // search. This could be due to size limit exceeded or
                        // operation cancelled.
                        return;
                      }
                    } else {
                      throw refException;
                    }
                  }
                }
              }
            }
          }
          searchOperation.checkIfCanceled(false);
          // Move to the next record.
          result = cursor.getNext();
        }
      } finally {
        cursor.close();
      }
    } finally {
      if (txn != null) {
        txn.close();
      }
    }
  }
  /**
   * Adds the provided entry to this database.  This method must ensure that the
   * entry is appropriate for the database and that no entry already exists with
   * the same DN.  The caller must hold a write lock on the DN of the provided
   * entry.
   *
   * @param entry        The entry to add to this database.
   * @param addOperation The add operation with which the new entry is
   *                     associated.  This may be <CODE>null</CODE> for adds
   *                     performed internally.
   * @param txn          Abstract transaction for this operation.
   * @throws DirectoryException If a problem occurs while trying to add the
   *                            entry.
   * @throws CanceledOperationException If operation is canceled
   *         while in progress.
   * @throws NdbApiException If an error occurs in the NDB database.
   * @throws NDBException If an error occurs in the NDB backend.
   */
  public void addEntry(Entry entry, AddOperation addOperation,
    AbstractTransaction txn)
      throws CanceledOperationException, NdbApiException,
      DirectoryException, NDBException
  {
    TransactedOperation operation = new AddEntryTransaction(entry);
    invokeTransactedOperation(txn, operation, addOperation, true, false);
  }
  /**
   * Adds the provided entry to this database.  This method must ensure that the
   * entry is appropriate for the database and that no entry already exists with
   * the same DN.  The caller must hold a write lock on the DN of the provided
   * entry.
   *
   * @param entry        The entry to add to this database.
   * @param addOperation The add operation with which the new entry is
   *                     associated.  This may be <CODE>null</CODE> for adds
   *                     performed internally.
   * @param txn          Abstract transaction for this operation.
   * @throws DirectoryException If a problem occurs while trying to add the
   *                            entry.
   * @throws CanceledOperationException If operation is canceled
   *         while in progress.
   * @throws NdbApiException If an error occurs in the NDB database.
   * @throws NDBException If an error occurs in the NDB backend.
   */
  public void addEntryNoCommit(Entry entry, AddOperation addOperation,
    AbstractTransaction txn)
      throws CanceledOperationException, NdbApiException,
      DirectoryException, NDBException
  {
    TransactedOperation operation = new AddEntryTransaction(entry);
    invokeTransactedOperation(txn, operation, addOperation, false, false);
  }
  /**
   * This method is common to all operations invoked under a database
   * transaction. It retries the operation if the transaction is
   * aborted due to a deadlock condition, up to a configured maximum
   * number of retries.
   *
   * @param operation An object implementing the TransactedOperation interface.
   * @throws NdbApiException If an error occurs in the NDB database.
   * @throws DirectoryException If a Directory Server error occurs.
   * @throws NDBException If an error occurs in the NDB backend.
   */
  private void invokeTransactedOperation(AbstractTransaction txn,
    TransactedOperation operation, Operation ldapOperation,
    boolean commit, boolean locked)
      throws CanceledOperationException, NdbApiException,
      DirectoryException, NDBException
  {
    // Attempt the operation under a transaction until it fails or completes.
    int txnRetries = 0;
    boolean completed = false;
    while (!completed)
    {
      try
      {
        // Invoke the operation.
        operation.invokeOperation(txn);
        // One last check before committing.
        if (ldapOperation != null) {
          ldapOperation.checkIfCanceled(true);
        }
        // Commit the transaction.
        if (commit) {
          txn.commit();
        }
        completed = true;
      }
      catch (NdbApiTemporaryException databaseException)
      {
        if (!locked) {
          if (txnRetries < BackendImpl.TXN_RETRY_LIMIT) {
            if (txn != null) {
              txn.close();
            }
            txnRetries++;
            continue;
          }
        }
        if (debugEnabled()) {
          TRACER.debugCaught(DebugLogLevel.ERROR,
            databaseException);
        }
        throw databaseException;
      }
      catch (NdbApiPermanentException databaseException)
      {
        throw databaseException;
      }
      catch (DirectoryException directoryException)
      {
        throw directoryException;
      }
      catch (NDBException NDBException)
      {
        throw NDBException;
      }
      catch (Exception e)
      {
        Message message = ERR_NDB_UNCHECKED_EXCEPTION.get();
        throw new NDBException(message, e);
      }
      finally {
        if (commit) {
          if (txn != null) {
            txn.close();
          }
        }
      }
    }
    // Do any actions necessary after successful commit,
    // usually to update the entry cache.
    operation.postCommitAction();
  }
  /**
   * This interface represents any kind of operation on the database
   * that must be performed under a transaction. A class which implements
   * this interface does not need to be concerned with creating the
   * transaction nor retrying the transaction after deadlock.
   */
  private interface TransactedOperation
  {
    /**
     * Invoke the operation under the given transaction.
     *
     * @param txn The transaction to be used to perform the operation.
     * @throws NdbApiException If an error occurs in the NDB database.
     * @throws DirectoryException If a Directory Server error occurs.
     * @throws NDBException If an error occurs in the NDB backend.
     */
    public abstract void invokeOperation(AbstractTransaction txn)
        throws NdbApiException, DirectoryException,
        CanceledOperationException, NDBException;
    /**
     * This method is called after the transaction has successfully
     * committed.
     */
    public abstract void postCommitAction();
  }
  /**
   * This inner class implements the Add Entry operation through
   * the TransactedOperation interface.
   */
  private class AddEntryTransaction implements TransactedOperation
  {
    /**
     * The entry to be added.
     */
    private Entry entry;
    /**
     * The DN of the superior entry of the entry to be added.  This can be
     * null if the entry to be added is a base entry.
     */
    DN parentDN;
    /**
     * The ID of the entry once it has been assigned.
     */
    long entryID;
    /**
     * Create a new Add Entry NdbTransaction.
     * @param entry The entry to be added.
     */
    public AddEntryTransaction(Entry entry)
    {
      this.entry = entry;
      this.parentDN = getParentWithinBase(entry.getDN());
    }
    /**
     * Invoke the operation under the given transaction.
     *
     * @param txn The transaction to be used to perform the operation.
     * @throws NdbApiException If an error occurs in the NDB database.
     * @throws DirectoryException If a Directory Server error occurs.
     * @throws NDBException If an error occurs in the NDB backend.
     */
    public void invokeOperation(AbstractTransaction txn)
        throws NdbApiException, DirectoryException, NDBException
    {
      // Check that the parent entry exists.
      if (parentDN != null) {
        // Check for referral entries above the target.
        targetEntryReferrals(txn, entry.getDN(), null);
        long parentID = dn2id.getID(txn, parentDN,
          NdbOperation.LockMode.LM_Read);
        if (parentID == 0) {
          Message message = ERR_NDB_ADD_NO_SUCH_OBJECT.get(
                  entry.getDN().toString());
          DN matchedDN = getMatchedDN(txn, baseDN);
          throw new DirectoryException(ResultCode.NO_SUCH_OBJECT,
              message, matchedDN, null);
        }
      }
      // First time through, assign the next entryID.
      if (entryID == 0)
      {
        entryID = rootContainer.getNextEntryID(txn.getNdb());
      }
      // Insert.
      try {
        dn2id.insert(txn, entry.getDN(), entryID, entry);
        txn.execute();
      } catch (NdbApiException ne) {
        if (ne.getErrorObj().getClassification() ==
          NdbError.Classification.ConstraintViolation)
        {
          Message message =
            ERR_NDB_ADD_ENTRY_ALREADY_EXISTS.get(entry.getDN().toString());
          throw new DirectoryException(ResultCode.ENTRY_ALREADY_EXISTS,
            message);
        } else {
          throw ne;
        }
      }
    }
    /**
     * This method is called after the transaction has successfully
     * committed.
     */
    public void postCommitAction()
    {
    }
  }
  /**
   * Removes the specified entry from this database.  This method must ensure
   * that the entry exists and that it does not have any subordinate entries
   * (unless the database supports a subtree delete operation and the client
   * included the appropriate information in the request).
   * The caller must hold a write lock on the provided entry DN.
   *
   * @param entryDN         The DN of the entry to remove from this database.
   * @param entry           The entry to delete.
   * @param deleteOperation The delete operation with which this action is
   *                        associated.  This may be <CODE>null</CODE> for
   *                        deletes performed internally.
   * @param txn             Abstract transaction for this operation.
   * @throws DirectoryException If a problem occurs while trying to remove the
   *                            entry.
   * @throws CanceledOperationException If operation is canceled
   *         while in progress.
   * @throws NdbApiException If an error occurs in the NDB database.
   * @throws NDBException If an error occurs in the NDB backend.
   */
  public void deleteEntry(DN entryDN, Entry entry,
    DeleteOperation deleteOperation, AbstractTransaction txn)
    throws CanceledOperationException, DirectoryException,
    NdbApiException, NDBException
  {
    DeleteEntryTransaction operation =
        new DeleteEntryTransaction(entryDN, entry, deleteOperation);
    boolean isComplete = false;
    while(!isComplete)
    {
      invokeTransactedOperation(txn, operation, deleteOperation, true, true);
      if (operation.adminSizeLimitExceeded())
      {
        Message message = NOTE_NDB_SUBTREE_DELETE_SIZE_LIMIT_EXCEEDED.get(
                operation.getDeletedEntryCount());
        throw new DirectoryException(
          ResultCode.ADMIN_LIMIT_EXCEEDED,
          message);
      }
      if(operation.batchSizeExceeded())
      {
        operation.resetBatchSize();
        continue;
      }
      isComplete = true;
      Message message =
          NOTE_NDB_DELETED_ENTRY_COUNT.get(operation.getDeletedEntryCount());
      MessageBuilder errorMessage = new MessageBuilder();
      errorMessage.append(message);
      deleteOperation.setErrorMessage(errorMessage);
    }
  }
  /**
   * Removes the specified entry from this database.  This method must ensure
   * that the entry exists and that it does not have any subordinate entries
   * (unless the database supports a subtree delete operation and the client
   * included the appropriate information in the request).
   * The caller must hold a write lock on the provided entry DN.
   *
   * @param entryDN         The DN of the entry to remove from this database.
   * @param entry           The entry to delete.
   * @param deleteOperation The delete operation with which this action is
   *                        associated.  This may be <CODE>null</CODE> for
   *                        deletes performed internally.
   * @param txn             Abstract transaction for this operation.
   * @throws DirectoryException If a problem occurs while trying to remove the
   *                            entry.
   * @throws CanceledOperationException If operation is canceled
   *         while in progress.
   * @throws NdbApiException If an error occurs in the NDB database.
   * @throws NDBException If an error occurs in the NDB backend.
   */
  public void deleteEntryNoCommit(DN entryDN, Entry entry,
    DeleteOperation deleteOperation, AbstractTransaction txn)
    throws CanceledOperationException, DirectoryException,
    NdbApiException, NDBException
  {
    DeleteEntryTransaction operation =
        new DeleteEntryTransaction(entryDN, entry, deleteOperation);
    boolean isComplete = false;
    while(!isComplete)
    {
      invokeTransactedOperation(txn, operation, deleteOperation, false, true);
      if (operation.adminSizeLimitExceeded())
      {
        Message message = NOTE_NDB_SUBTREE_DELETE_SIZE_LIMIT_EXCEEDED.get(
                operation.getDeletedEntryCount());
        throw new DirectoryException(
          ResultCode.ADMIN_LIMIT_EXCEEDED,
          message);
      }
      if(operation.batchSizeExceeded())
      {
        operation.resetBatchSize();
        continue;
      }
      isComplete = true;
      Message message =
          NOTE_NDB_DELETED_ENTRY_COUNT.get(operation.getDeletedEntryCount());
      MessageBuilder errorMessage = new MessageBuilder();
      errorMessage.append(message);
      deleteOperation.setErrorMessage(errorMessage);
    }
  }
  /**
   * Delete a leaf entry.
   * The caller must be sure that the entry is indeed a leaf.
   *
   * @param txn    The abstract transaction.
   * @param leafDN The DN of the leaf entry to be deleted.
   * @param leafID The ID of the leaf entry.
   * @throws NdbApiException If an error occurs in the NDB database.
   * @throws DirectoryException If a Directory Server error occurs.
   * @throws NDBException If an error occurs in the NDB backend.
   */
  private void deleteLeaf(AbstractTransaction txn,
                          DN leafDN,
                          long leafID,
                          DeleteOperation operation)
      throws NdbApiException, DirectoryException, NDBException
  {
    Entry entry = dn2id.get(txn, leafDN, NdbOperation.LockMode.LM_Exclusive);
    // Check that the entry exists.
    if (entry == null)
    {
      Message msg = ERR_NDB_MISSING_ID2ENTRY_RECORD.get(Long.toString(leafID));
      throw new NDBException(msg);
    }
    if (!isManageDsaITOperation(operation))
    {
      checkTargetForReferral(entry, null);
    }
    // Remove from dn2id.
    if (!dn2id.remove(txn, entry))
    {
      Message msg = ERR_NDB_MISSING_ID2ENTRY_RECORD.get(Long.toString(leafID));
      throw new NDBException(msg);
    }
  }
  /**
   * Delete the target entry of a delete operation, with appropriate handling
   * of referral entries. The caller must be sure that the entry is indeed a
   * leaf.
   *
   * @param txn    The abstract transaction.
   * @param leafDN The DN of the target entry to be deleted.
   * @throws NdbApiException If an error occurs in the NDB database.
   * @throws DirectoryException If a Directory Server error occurs.
   * @throws NDBException If an error occurs in the NDB backend.
   */
  private void deleteTarget(AbstractTransaction txn,
                            DN leafDN, Entry entry,
                            DeleteOperation operation)
      throws NdbApiException, DirectoryException, NDBException
  {
    // Check that the entry exists.
    if (entry == null)
    {
      Message message = ERR_NDB_DELETE_NO_SUCH_OBJECT.get(leafDN.toString());
      DN matchedDN = getMatchedDN(txn, baseDN);
      throw new DirectoryException(ResultCode.NO_SUCH_OBJECT,
          message, matchedDN, null);
    }
    if (!isManageDsaITOperation(operation))
    {
      checkTargetForReferral(entry, null);
    }
    // Remove from dn2id.
    if (!dn2id.remove(txn, entry))
    {
      Message msg = ERR_NDB_MISSING_DN2ID_RECORD.get(leafDN.toString());
      throw new NDBException(msg);
    }
  }
  /**
   * This inner class implements the Delete Entry operation through
   * the TransactedOperation interface.
   */
  private class DeleteEntryTransaction implements TransactedOperation
  {
    /**
     * The DN of the entry or subtree to be deleted.
     */
    private DN entryDN;
    /**
     * The entry itself.
     */
    private Entry entry;
    /**
     * The Delete operation.
     */
    private DeleteOperation deleteOperation;
    /**
     * A list of the DNs of all entries deleted by this operation in a batch.
     * The subtree delete control can cause multiple entries to be deleted.
     */
    private ArrayList<DN> deletedDNList;
    /**
     * Indicates whether the subtree delete size limit has been exceeded.
     */
    private boolean adminSizeLimitExceeded = false;
    /**
     * Indicates whether the subtree delete batch size has been exceeded.
     */
    private boolean batchSizeExceeded = false;
    /**
     * Indicates the count of deleted DNs in the Delete Operation.
     */
    private int countDeletedDN;
    /**
     * Create a new Delete Entry NdbTransaction.
     * @param entryDN The entry or subtree to be deleted.
     * @param deleteOperation The Delete operation.
     */
    public DeleteEntryTransaction(DN entryDN, Entry entry,
      DeleteOperation deleteOperation)
    {
      this.entryDN = entryDN;
      this.entry = entry;
      this.deleteOperation = deleteOperation;
      deletedDNList = new ArrayList<DN>();
    }
    /**
     * Determine whether the subtree delete size limit has been exceeded.
     * @return true if the size limit has been exceeded.
     */
    public boolean adminSizeLimitExceeded()
    {
      return adminSizeLimitExceeded;
    }
    /**
     * Determine whether the subtree delete batch size has been exceeded.
     * @return true if the batch size has been exceeded.
     */
    public boolean batchSizeExceeded()
    {
      return batchSizeExceeded;
    }
    /**
     * Resets the batchSizeExceeded parameter to reuse the object
     * for multiple batches.
     */
    public void resetBatchSize()
    {
      batchSizeExceeded=false;
      deletedDNList.clear();
    }
    /**
     * Get the number of entries deleted during the operation.
     * @return The number of entries deleted.
     */
    public int getDeletedEntryCount()
    {
      return countDeletedDN;
    }
    /**
     * Invoke the operation under the given transaction.
     *
     * @param txn The transaction to be used to perform the operation.
     * @throws NdbApiException If an error occurs in the NDB database.
     * @throws DirectoryException If a Directory Server error occurs.
     * @throws NDBException If an error occurs in the NDB backend.
     */
    public void invokeOperation(AbstractTransaction txn)
        throws CanceledOperationException, NdbApiException,
        DirectoryException, NDBException
    {
      // Check for referral entries above the target entry.
      targetEntryReferrals(txn, entryDN, null);
      // Determine whether this is a subtree delete.
      int adminSizeLimit = subtreeDeleteSizeLimit;
      int deleteBatchSize = subtreeDeleteBatchSize;
      boolean isSubtreeDelete = false;
      List<Control> controls = deleteOperation.getRequestControls();
      if (controls != null)
      {
        for (Control control : controls)
        {
          if (control.getOID().equals(OID_SUBTREE_DELETE_CONTROL))
          {
            isSubtreeDelete = true;
          }
        }
      }
      if (dn2id.hasSubordinates(txn, entryDN) && !isSubtreeDelete) {
        // The subtree delete control was not specified and
        // the target entry is not a leaf.
        Message message =
          ERR_NDB_DELETE_NOT_ALLOWED_ON_NONLEAF.get(entryDN.toString());
        throw new DirectoryException(ResultCode.NOT_ALLOWED_ON_NONLEAF,
          message);
      }
      if (isSubtreeDelete) {
        AbstractTransaction cursorTxn =
          new AbstractTransaction(rootContainer);
        DN2IDSearchCursor cursor = dn2id.getSearchCursor(cursorTxn, entryDN);
        cursor.open();
        try {
          SearchCursorResult result = cursor.getNext();
          while (result != null) {
            // We have found a subordinate entry.
            if (!isSubtreeDelete) {
              // The subtree delete control was not specified and
              // the target entry is not a leaf.
              Message message =
                ERR_NDB_DELETE_NOT_ALLOWED_ON_NONLEAF.get(entryDN.toString());
              throw new DirectoryException(ResultCode.NOT_ALLOWED_ON_NONLEAF,
                message);
            }
            // Enforce any subtree delete size limit.
            if (adminSizeLimit > 0 && countDeletedDN >= adminSizeLimit) {
              adminSizeLimitExceeded = true;
              break;
            }
            // Enforce any subtree delete batch size.
            if (deleteBatchSize > 0 &&
              deletedDNList.size() >= deleteBatchSize) {
              batchSizeExceeded = true;
              break;
            }
            /*
             * Delete this entry which by now must be a leaf because
             * we have been deleting from the bottom of the tree upwards.
             */
            long entryID = result.id;
            DN subordinateDN = DN.decode(result.dn);
            deleteLeaf(txn, subordinateDN, entryID, deleteOperation);
            deletedDNList.add(subordinateDN);
            countDeletedDN++;
            if (deleteOperation != null) {
              deleteOperation.checkIfCanceled(false);
            }
            result = cursor.getNext();
          }
        } finally {
          cursor.close();
          cursorTxn.close();
        }
      }
      // Finally delete the target entry as it was not included
      // in the dn2id iteration.
      if (!adminSizeLimitExceeded && !batchSizeExceeded)
      {
        // Enforce any subtree delete size limit.
        if (adminSizeLimit > 0 && countDeletedDN >= adminSizeLimit)
        {
          adminSizeLimitExceeded = true;
        }
        else if (deleteBatchSize > 0 &&
                                      deletedDNList.size() >= deleteBatchSize)
        {
          batchSizeExceeded = true;
        }
        else
        {
          deleteTarget(txn, entryDN, entry, deleteOperation);
          deletedDNList.add(entryDN);
          countDeletedDN++;
        }
      }
    }
    /**
     * This method is called after the transaction has successfully
     * committed.
     */
    public void postCommitAction()
    {
    }
  }
  /**
   * Indicates whether an entry with the specified DN exists.
   *
   * @param  txn      Abstract transaction for this operation.
   * @param  entryDN  The DN of the entry for which to determine existence.
   *
   * @return  <CODE>true</CODE> if the specified entry exists,
   *          or <CODE>false</CODE> if it does not.
   *
   * @throws  DirectoryException  If a problem occurs while trying to make the
   *                              determination.
   * @throws  NdbApiException     An error occurred during a database operation.
   */
  public boolean entryExists(AbstractTransaction txn, DN entryDN)
      throws DirectoryException, NdbApiException
  {
    // Read the ID from dn2id.
    long id = 0;
    try
    {
      id = dn2id.getID(txn, entryDN, NdbOperation.LockMode.LM_CommittedRead);
    }
    catch (NdbApiException e)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
    }
    return id != 0;
  }
  /**
   * Fetch an entry by DN retrieves the requested entry.
   * Note that the caller must hold a read or write lock
   * on the specified DN.
   *
   * @param entryDN  The distinguished name of the entry to retrieve.
   * @param txn      Abstract transaction for this operation.
   * @param lockMode Operation lock mode.
   * @return The requested entry, or <CODE>null</CODE> if the entry does not
   *         exist.
   * @throws DirectoryException If a problem occurs while trying to retrieve
   *                            the entry.
   * @throws NDBException If an error occurs in the NDB backend.
   * @throws NdbApiException An error occurred during a database operation.
   */
  public Entry getEntryNoCommit(DN entryDN, AbstractTransaction txn,
    NdbOperation.LockMode lockMode)
      throws NDBException, NdbApiException, DirectoryException
  {
    Entry entry = null;
    GetEntryByDNOperation operation =
      new GetEntryByDNOperation(entryDN, lockMode);
    try {
      // Fetch the entry from the database.
      invokeTransactedOperation(txn, operation, null, false, false);
    } catch (CanceledOperationException ex) {
      // No LDAP operation, ignore.
    }
    entry = operation.getEntry();
    return entry;
  }
  /**
   * Fetch an entry by DN retrieves the requested entry.
   * Note that the caller must hold a read or write lock
   * on the specified DN.
   *
   * @param entryDN The distinguished name of the entry to retrieve.
   * @return The requested entry, or <CODE>null</CODE> if the entry does not
   *         exist.
   * @throws DirectoryException If a problem occurs while trying to retrieve
   *                            the entry.
   * @throws NDBException If an error occurs in the NDB backend.
   * @throws NdbApiException An error occurred during a database operation.
   */
  public Entry getEntry(DN entryDN)
      throws NDBException, NdbApiException, DirectoryException
  {
    Entry entry = null;
    GetEntryByDNOperation operation = new GetEntryByDNOperation(entryDN,
      NdbOperation.LockMode.LM_CommittedRead);
    AbstractTransaction txn = new AbstractTransaction(rootContainer);
    try {
      // Fetch the entry from the database.
      invokeTransactedOperation(txn, operation, null, true, false);
    } catch (CanceledOperationException ex) {
      // No LDAP operation, ignore.
    }
    entry = operation.getEntry();
    return entry;
  }
  /**
   * This inner class gets an entry by DN through
   * the TransactedOperation interface.
   */
  private class GetEntryByDNOperation implements TransactedOperation
  {
    /**
     * The retrieved entry.
     */
    private Entry entry = null;
    /**
     * The ID of the retrieved entry.
     */
    private long entryID = 0;
    /**
     * Operation lock mode.
     */
    private NdbOperation.LockMode lockMode;
    /**
     * The DN of the entry to be retrieved.
     */
    DN entryDN;
    /**
     * Create a new transacted operation to retrieve an entry by DN.
     * @param entryDN The DN of the entry to be retrieved.
     */
    public GetEntryByDNOperation(DN entryDN, NdbOperation.LockMode lockMode)
    {
      this.entryDN = entryDN;
      this.lockMode = lockMode;
    }
    /**
     * Get the retrieved entry.
     * @return The retrieved entry.
     */
    public Entry getEntry()
    {
      return entry;
    }
    /**
     * Get the ID of the retrieved entry.
     * @return The ID of the retrieved entry.
     */
    public long getEntryID()
    {
      return entryID;
    }
    /**
     * Invoke the operation under the given transaction.
     *
     * @param txn The transaction to be used to perform the operation
     * @throws NdbApiException If an error occurs in the NDB database.
     * @throws DirectoryException If a Directory Server error occurs.
     * @throws NDBException If an error occurs in the NDB backend.
     */
    public void invokeOperation(AbstractTransaction txn)
      throws NdbApiException, DirectoryException, NDBException
    {
      entry = dn2id.get(txn, entryDN, lockMode);
      if (entry == null) {
        // Check for referral entries above the target entry.
        targetEntryReferrals(txn, entryDN, null);
      }
    }
    /**
     * This method is called after the transaction has successfully
     * committed.
     */
    public void postCommitAction()
    {
      // No implementation required.
    }
  }
  /**
   * The simplest case of replacing an entry in which the entry DN has
   * not changed.
   *
   * @param oldEntry           The old contents of the entry.
   * @param newEntry           The new contents of the entry
   * @param modifyOperation The modify operation with which this action is
   *                        associated.  This may be <CODE>null</CODE> for
   *                        modifications performed internally.
   * @param txn             Abstract transaction for this operation.
   * @throws NdbApiException If an error occurs in the NDB database.
   * @throws DirectoryException If a Directory Server error occurs.
   * @throws CanceledOperationException If operation is canceled
   *         while in progress.
   * @throws NDBException If an error occurs in the NDB backend.
   */
  public void replaceEntry(Entry oldEntry, Entry newEntry,
    ModifyOperation modifyOperation, AbstractTransaction txn)
       throws CanceledOperationException, NdbApiException,
       DirectoryException, NDBException
  {
    TransactedOperation operation =
         new ReplaceEntryTransaction(oldEntry, newEntry, modifyOperation);
    invokeTransactedOperation(txn, operation, modifyOperation, true, true);
  }
  /**
   * This inner class implements the Replace Entry operation through
   * the TransactedOperation interface.
   */
  private class ReplaceEntryTransaction implements TransactedOperation
  {
    /**
     * The new contents of the entry.
     */
    private Entry newEntry;
    /**
     * The old contents of the entry.
     */
    private Entry oldEntry;
    /**
     * The Modify operation, or null if the replace is not due to a Modify
     * operation.
     */
    private ModifyOperation modifyOperation;
    /**
     * The ID of the entry that was replaced.
     */
    private Long entryID;
    /**
     * Create a new transacted operation to replace an entry.
     * @param entry The new contents of the entry.
     * @param modifyOperation The Modify operation, or null if the replace is
     * not due to a Modify operation.
     */
    public ReplaceEntryTransaction(Entry oldEntry, Entry newEntry,
                                   ModifyOperation modifyOperation)
    {
      this.oldEntry = oldEntry;
      this.newEntry = newEntry;
      this.modifyOperation = modifyOperation;
    }
    /**
     * Invoke the operation under the given transaction.
     *
     * @param txn The transaction to be used to perform the operation.
     * @throws NdbApiException If an error occurs in the NDB database.
     * @throws DirectoryException If a Directory Server error occurs.
     * @throws NDBException If an error occurs in the NDB backend.
     */
    public void invokeOperation(AbstractTransaction txn) throws NdbApiException,
                                                        DirectoryException,
                                                        NDBException
    {
      DN entryDN = newEntry.getDN();
      entryID = (Long) oldEntry.getAttachment();
      if (entryID == 0)
      {
        // The entry does not exist.
        Message message =
                ERR_NDB_MODIFY_NO_SUCH_OBJECT.get(entryDN.toString());
        DN matchedDN = getMatchedDN(txn, baseDN);
        throw new DirectoryException(ResultCode.NO_SUCH_OBJECT,
            message, matchedDN, null);
      }
      if (!isManageDsaITOperation(modifyOperation))
      {
        // Check if the entry is a referral entry.
        checkTargetForReferral(oldEntry, null);
      }
      // List<Modification> modsList = modifyOperation.getModifications();
      // Replace.
      if (!dn2id.put(txn, entryDN, entryID, newEntry, oldEntry))
      {
        // The entry does not exist.
        Message msg = ERR_NDB_MISSING_ID2ENTRY_RECORD.get(
          Long.toString(entryID));
        throw new NDBException(msg);
      }
    }
    /**
     * This method is called after the transaction has successfully
     * committed.
     */
    public void postCommitAction()
    {
    }
  }
  /**
   * Moves and/or renames the provided entry in this backend, altering any
   * subordinate entries as necessary.  This must ensure that an entry already
   * exists with the provided current DN, and that no entry exists with the
   * target DN of the provided entry.  The caller must hold write locks on both
   * the current DN and the new DN for the entry.
   *
   * @param currentDN         The current DN of the entry to be replaced.
   * @param entry             The new content to use for the entry.
   * @param modifyDNOperation The modify DN operation with which this action
   *                          is associated.  This may be <CODE>null</CODE>
   *                          for modify DN operations performed internally.
   * @param txn               Abstract transaction for this operation.
   * @throws org.opends.server.types.DirectoryException
   *          If a problem occurs while trying to perform
   *          the rename.
   * @throws org.opends.server.types.CanceledOperationException
   *          If this backend noticed and reacted
   *          to a request to cancel or abandon the
   *          modify DN operation.
   * @throws NdbApiException If an error occurs in the NDB database.
   * @throws NDBException If an error occurs in the NDB backend.
   */
  public void renameEntry(DN currentDN, Entry entry,
                          ModifyDNOperation modifyDNOperation,
                          AbstractTransaction txn)
      throws NdbApiException, NDBException, DirectoryException,
      CanceledOperationException {
    TransactedOperation operation =
        new RenameEntryTransaction(currentDN, entry, modifyDNOperation);
    invokeTransactedOperation(txn, operation, modifyDNOperation, true, true);
  }
  /**
   * This inner class implements the Modify DN operation through
   * the TransactedOperation interface.
   */
  private class RenameEntryTransaction implements TransactedOperation
  {
    /**
     * The DN of the entry to be renamed.
     */
    private DN oldApexDN;
    /**
     * The DN of the superior entry of the entry to be renamed.
     * This is null if the entry to be renamed is a base entry.
     */
    private DN oldSuperiorDN;
    /**
     * The DN of the new superior entry, which can be the same
     * as the current superior entry.
     */
    private DN newSuperiorDN;
    /**
     * The new contents of the entry to be renamed.
     */
    private Entry newApexEntry;
    /**
     * The Modify DN operation.
     */
    private ModifyDNOperation modifyDNOperation;
    /**
     * Create a new transacted operation for a Modify DN operation.
     * @param currentDN The DN of the entry to be renamed.
     * @param entry The new contents of the entry.
     * @param modifyDNOperation The Modify DN operation to be performed.
     */
    public RenameEntryTransaction(DN currentDN, Entry entry,
                                  ModifyDNOperation modifyDNOperation)
    {
      this.oldApexDN = currentDN;
      this.oldSuperiorDN = getParentWithinBase(currentDN);
      this.newSuperiorDN = getParentWithinBase(entry.getDN());
      this.newApexEntry = entry;
      this.modifyDNOperation = modifyDNOperation;
    }
    /**
     * Invoke the operation under the given transaction.
     *
     * @param txn The transaction to be used to perform the operation.
     * @throws NdbApiException If an error occurs in the NDB database.
     * @throws DirectoryException If a Directory Server error occurs.
     * @throws NDBException If an error occurs in the NDB backend.
     */
    public void invokeOperation(AbstractTransaction txn)
      throws NdbApiException, DirectoryException,
      CanceledOperationException, NDBException
    {
      DN requestedNewSuperiorDN = null;
      if (modifyDNOperation != null)
      {
        requestedNewSuperiorDN = modifyDNOperation.getNewSuperior();
      }
      // Check whether the renamed entry already exists.
      if (dn2id.getID(txn, newApexEntry.getDN(),
        NdbOperation.LockMode.LM_Exclusive) != 0)
      {
        Message message = ERR_NDB_MODIFYDN_ALREADY_EXISTS.get(
            newApexEntry.getDN().toString());
        throw new DirectoryException(ResultCode.ENTRY_ALREADY_EXISTS,
                                     message);
      }
      Entry oldApexEntry = dn2id.get(txn, oldApexDN,
        NdbOperation.LockMode.LM_Exclusive);
      if (oldApexEntry == null)
      {
        // Check for referral entries above the target entry.
        targetEntryReferrals(txn, oldApexDN, null);
        Message message =
                ERR_NDB_MODIFYDN_NO_SUCH_OBJECT.get(oldApexDN.toString());
        DN matchedDN = getMatchedDN(txn, baseDN);
        throw new DirectoryException(ResultCode.NO_SUCH_OBJECT,
            message, matchedDN, null);
      }
      if (!isManageDsaITOperation(modifyDNOperation))
      {
        checkTargetForReferral(oldApexEntry, null);
      }
      long oldApexID = (Long) oldApexEntry.getAttachment();
      long newApexID = oldApexID;
      if (newSuperiorDN != null)
      {
        long newSuperiorID = dn2id.getID(txn, newSuperiorDN,
          NdbOperation.LockMode.LM_Exclusive);
        if (newSuperiorID == 0)
        {
          Message msg =
                  ERR_NDB_NEW_SUPERIOR_NO_SUCH_OBJECT.get(
                          newSuperiorDN.toString());
          DN matchedDN = getMatchedDN(txn, baseDN);
          throw new DirectoryException(ResultCode.NO_SUCH_OBJECT,
              msg, matchedDN, null);
        }
        newApexID = rootContainer.getNextEntryID(txn.getNdb());
      }
      // Move or rename the apex entry.
      if (requestedNewSuperiorDN != null)
      {
        moveApexEntry(txn, newApexID, oldApexEntry, newApexEntry);
      }
      else
      {
        long newID = rootContainer.getNextEntryID(txn.getNdb());
        renameApexEntry(txn, newID, oldApexEntry, newApexEntry);
      }
      AbstractTransaction cursorTxn =
          new AbstractTransaction(rootContainer);
      DN2IDSearchCursor cursor = dn2id.getSearchCursor(cursorTxn, oldApexDN);
      cursor.open();
      try {
        SearchCursorResult result = cursor.getNext();
        // Step forward until we pass the ending value.
        while (result != null) {
          // We have found a subordinate entry.
          long oldID = result.id;
          String oldDN = result.dn;
          Entry oldEntry = dn2id.get(txn, DN.decode(oldDN),
            NdbOperation.LockMode.LM_Exclusive);
          if (!isManageDsaITOperation(modifyDNOperation)) {
            checkTargetForReferral(oldEntry, null);
          }
          // Construct the new DN of the entry.
          DN newDN = modDN(oldEntry.getDN(),
            oldApexDN.getNumComponents(),
            newApexEntry.getDN());
          if (requestedNewSuperiorDN != null) {
            // Assign a new entry ID if we are renumbering.
            long newID = oldID;
            if (newApexID != oldApexID) {
              newID = rootContainer.getNextEntryID(txn.getNdb());
            }
            // Move this entry.
            moveSubordinateEntry(txn, newID, oldEntry, newDN);
          } else {
            // Rename this entry.
            renameSubordinateEntry(txn, oldID, oldEntry, newDN);
          }
          if (modifyDNOperation != null) {
            modifyDNOperation.checkIfCanceled(false);
          }
          result = cursor.getNext();
        }
      } finally {
        cursor.close();
        cursorTxn.close();
      }
    }
    /**
     * Update the database for the target entry of a ModDN operation
     * specifying a new superior.
     *
     * @param txn The abstract transaction to be used for the updates.
     * @param newID The new ID of the target entry, or the original ID if
     *              the ID has not changed.
     * @param oldEntry The original contents of the target entry.
     * @param newEntry The new contents of the target entry.
     * @throws NDBException If an error occurs in the NDB backend.
     * @throws DirectoryException If a Directory Server error occurs.
     * @throws NdbApiException If an error occurs in the NDB database.
     */
    private void moveApexEntry(AbstractTransaction txn,
                               long newID, Entry oldEntry, Entry newEntry)
        throws NDBException, DirectoryException, NdbApiException
    {
      // DN oldDN = oldEntry.getDN();
      DN newDN = newEntry.getDN();
      // Remove the old DN from dn2id.
      dn2id.remove(txn, oldEntry);
      // Insert the new DN in dn2id.
      if (!dn2id.insert(txn, newDN, newID, newEntry))
      {
        Message message = ERR_NDB_MODIFYDN_ALREADY_EXISTS.get(newDN.toString());
        throw new DirectoryException(ResultCode.ENTRY_ALREADY_EXISTS,
                                     message);
      }
    }
    /**
     * Update the database for the target entry of a Modify DN operation
     * not specifying a new superior.
     *
     * @param txn The abstract transaction to be used for the updates.
     * @param newID The new ID of the target entry.
     * @param oldEntry The original contents of the target entry.
     * @param newEntry The new contents of the target entry.
     * @throws DirectoryException If a Directory Server error occurs.
     * @throws NdbApiException If an error occurs in the NDB database.
     * @throws NDBException if an error occurs in the NDB Backend.
     */
    private void renameApexEntry(AbstractTransaction txn, long newID,
                                 Entry oldEntry, Entry newEntry)
        throws DirectoryException, NdbApiException, NDBException
    {
      // DN oldDN = oldEntry.getDN();
      DN newDN = newEntry.getDN();
      // Remove the old DN from dn2id.
      dn2id.remove(txn, oldEntry);
      // Insert the new DN in dn2id.
      if (!dn2id.insert(txn, newDN, newID, newEntry))
      {
        Message message = ERR_NDB_MODIFYDN_ALREADY_EXISTS.get(newDN.toString());
        throw new DirectoryException(ResultCode.ENTRY_ALREADY_EXISTS,
                                     message);
      }
    }
    /**
     * Update the database for a subordinate entry of the target entry
     * of a Modify DN operation specifying a new superior.
     *
     * @param txn The abstract transaction to be used for the updates.
     * @param newID The new ID of the subordinate entry, or the original ID if
     *              the ID has not changed.
     * @param oldEntry The original contents of the subordinate entry.
     * @param newDN The new DN of the subordinate entry.
     * @throws NDBException If an error occurs in the NDB backend.
     * @throws DirectoryException If a Directory Server error occurs.
     * @throws NdbApiException If an error occurs in the NDB database.
     */
    private void moveSubordinateEntry(AbstractTransaction txn,
                                      long newID,
                                      Entry oldEntry, DN newDN)
        throws NDBException, DirectoryException, NdbApiException
    {
      // Remove the old DN from dn2id.
      dn2id.remove(txn, oldEntry);
      // Create a new entry that is a copy of the old entry but with the new DN.
      Entry newEntry = oldEntry.duplicate(false);
      newEntry.setDN(newDN);
      // Put the new DN in dn2id.
      if (!dn2id.insert(txn, newDN, newID, newEntry))
      {
        Message message = ERR_NDB_MODIFYDN_ALREADY_EXISTS.get(newDN.toString());
        throw new DirectoryException(ResultCode.ENTRY_ALREADY_EXISTS,
                                     message);
      }
    }
    /**
     * Update the database for a subordinate entry of the target entry
     * of a Modify DN operation not specifying a new superior.
     *
     * @param txn The abstract transaction to be used for the updates.
     * @param entryID The ID of the subordinate entry.
     * @param oldEntry The original contents of the subordinate entry.
     * @param newDN The new DN of the subordinate entry.
     * @throws DirectoryException If a Directory Server error occurs.
     * @throws NdbApiException If an error occurs in the NDB database.
     */
    private void renameSubordinateEntry(AbstractTransaction txn, long entryID,
                                        Entry oldEntry, DN newDN)
        throws DirectoryException, NDBException, NdbApiException
    {
      // Remove the old DN from dn2id.
      dn2id.remove(txn, oldEntry);
      // Create a new entry that is a copy of the old entry but with the new DN.
      Entry newEntry = oldEntry.duplicate(false);
      newEntry.setDN(newDN);
      // Insert the new DN in dn2id.
      if (!dn2id.insert(txn, newDN, entryID, newEntry))
      {
        Message message = ERR_NDB_MODIFYDN_ALREADY_EXISTS.get(newDN.toString());
        throw new DirectoryException(ResultCode.ENTRY_ALREADY_EXISTS,
                                     message);
      }
    }
    /**
     * This method is called after the transaction has successfully
     * committed.
     */
    public void postCommitAction()
    {
      // No implementation needed.
    }
  }
  /**
   * Make a new DN for a subordinate entry of a renamed or moved entry.
   *
   * @param oldDN The current DN of the subordinate entry.
   * @param oldSuffixLen The current DN length of the renamed or moved entry.
   * @param newSuffixDN The new DN of the renamed or moved entry.
   * @return The new DN of the subordinate entry.
   */
  public static DN modDN(DN oldDN, int oldSuffixLen, DN newSuffixDN)
  {
    int oldDNNumComponents    = oldDN.getNumComponents();
    int oldDNKeepComponents   = oldDNNumComponents - oldSuffixLen;
    int newSuffixDNComponents = newSuffixDN.getNumComponents();
    RDN[] newDNComponents = new RDN[oldDNKeepComponents+newSuffixDNComponents];
    for (int i=0; i < oldDNKeepComponents; i++)
    {
      newDNComponents[i] = oldDN.getRDN(i);
    }
    for (int i=oldDNKeepComponents, j=0; j < newSuffixDNComponents; i++,j++)
    {
      newDNComponents[i] = newSuffixDN.getRDN(j);
    }
    return new DN(newDNComponents);
  }
  /**
   * Get a count of the number of entries stored in this entry entryContainer.
   *
   * @return The number of entries stored in this entry entryContainer.
   * @throws NdbApiException If an error occurs in the NDB database.
   */
  public long getEntryCount() throws NdbApiException
  {
    return dn2id.getRecordCount();
  }
  /**
   * Get the number of values for which the entry limit has been exceeded
   * since the entry entryContainer was opened.
   * @return The number of values for which the entry limit has been exceeded.
   */
  public int getEntryLimitExceededCount()
  {
    int count = 0;
    return count;
  }
  /**
   * Get a list of the databases opened by this entryContainer.
   * @param dbList A list of database containers.
   */
  public void listDatabases(List<DatabaseContainer> dbList)
  {
    dbList.add(dn2id);
  }
  /**
   * Determine whether the provided operation has the ManageDsaIT request
   * control.
   * @param operation The operation for which the determination is to be made.
   * @return true if the operation has the ManageDsaIT request control, or false
   * if not.
   */
  public static boolean isManageDsaITOperation(Operation operation)
  {
    if(operation != null)
    {
      List<Control> controls = operation.getRequestControls();
      if (controls != null)
      {
        for (Control control : controls)
        {
          if (control.getOID().equals(ServerConstants.OID_MANAGE_DSAIT_CONTROL))
          {
            return true;
          }
        }
      }
    }
    return false;
  }
  /**
   * This method constructs a container name from a base DN. Only alphanumeric
   * characters are preserved, all other characters are replaced with an
   * underscore.
   *
   * @return The container name for the base DN.
   */
  public String getDatabasePrefix()
  {
    return databasePrefix;
  }
  /**
   * Get the baseDN this entry container is responsible for.
   *
   * @return The Base DN for this entry container.
   */
  public DN getBaseDN()
  {
    return baseDN;
  }
  /**
   * Get the parent of a DN in the scope of the base DN.
   *
   * @param dn A DN which is in the scope of the base DN.
   * @return The parent DN, or null if the given DN is the base DN.
   */
  public DN getParentWithinBase(DN dn)
  {
    if (dn.equals(baseDN))
    {
      return null;
    }
    return dn.getParent();
  }
  /**
   * {@inheritDoc}
   */
  public synchronized boolean isConfigurationChangeAcceptable(
      NdbBackendCfg cfg, List<Message> unacceptableReasons)
  {
    // This is always true because only all config attributes used
    // by the entry container should be validated by the admin framework.
    return true;
  }
  /**
   * {@inheritDoc}
   */
  public synchronized ConfigChangeResult applyConfigurationChange(
      NdbBackendCfg cfg)
  {
    boolean adminActionRequired = false;
    ArrayList<Message> messages = new ArrayList<Message>();
    this.config = cfg;
    this.deadlockRetryLimit = config.getDeadlockRetryLimit();
    return new ConfigChangeResult(ResultCode.SUCCESS,
                                  adminActionRequired, messages);
  }
  /**
   * Checks whether the target of an operation is a referral entry and throws
   * a Directory referral exception if it is.
   * @param entry The target entry of the operation, or the base entry of a
   * search operation.
   * @param searchScope The scope of the search operation, or null if the
   * operation is not a search operation.
   * @throws DirectoryException If a referral is found at or above the target
   * DN.  The referral URLs will be set appropriately for the references found
   * in the referral entry.
   */
  public void checkTargetForReferral(Entry entry, SearchScope searchScope)
       throws DirectoryException
  {
    Set<String> referralURLs = entry.getReferralURLs();
    if (referralURLs != null)
    {
      throwReferralException(entry.getDN(), entry.getDN(), referralURLs,
                             searchScope);
    }
  }
  /**
   * Throws a Directory referral exception for the case where a referral entry
   * exists at or above the target DN of an operation.
   * @param targetDN The target DN of the operation, or the base object of a
   * search operation.
   * @param referralDN The DN of the referral entry.
   * @param labeledURIs The set of labeled URIs in the referral entry.
   * @param searchScope The scope of the search operation, or null if the
   * operation is not a search operation.
   * @throws DirectoryException If a referral is found at or above the target
   * DN.  The referral URLs will be set appropriately for the references found
   * in the referral entry.
   */
  public void throwReferralException(DN targetDN, DN referralDN,
                                     Set<String> labeledURIs,
                                     SearchScope searchScope)
       throws DirectoryException
  {
    ArrayList<String> URIList = new ArrayList<String>(labeledURIs.size());
    for (String labeledURI : labeledURIs)
    {
      // Remove the label part of the labeled URI if there is a label.
      String uri = labeledURI;
      int i = labeledURI.indexOf(' ');
      if (i != -1)
      {
        uri = labeledURI.substring(0, i);
      }
      try
      {
        LDAPURL ldapurl = LDAPURL.decode(uri, false);
        if (ldapurl.getScheme().equalsIgnoreCase("ldap"))
        {
          DN urlBaseDN = targetDN;
          if (!referralDN.equals(ldapurl.getBaseDN()))
          {
            urlBaseDN =
                 EntryContainer.modDN(targetDN,
                                      referralDN.getNumComponents(),
                                      ldapurl.getBaseDN());
          }
          ldapurl.setBaseDN(urlBaseDN);
          if (searchScope == null)
          {
            // RFC 3296, 5.2.  Target Object Considerations:
            // In cases where the URI to be returned is a LDAP URL, the server
            // SHOULD trim any present scope, filter, or attribute list from the
            // URI before returning it.  Critical extensions MUST NOT be trimmed
            // or modified.
            StringBuilder builder = new StringBuilder(uri.length());
            ldapurl.toString(builder, true);
            uri = builder.toString();
          }
          else
          {
            // RFC 3296, 5.3.  Base Object Considerations:
            // In cases where the URI to be returned is a LDAP URL, the server
            // MUST provide an explicit scope specifier from the LDAP URL prior
            // to returning it.
            ldapurl.getAttributes().clear();
            ldapurl.setScope(searchScope);
            ldapurl.setFilter(null);
            uri = ldapurl.toString();
          }
        }
      }
      catch (DirectoryException e)
      {
        if (debugEnabled())
        {
          TRACER.debugCaught(DebugLogLevel.ERROR, e);
        }
        // Return the non-LDAP URI as is.
      }
      URIList.add(uri);
    }
    // Throw a directory referral exception containing the URIs.
    Message msg =
        NOTE_NDB_REFERRAL_RESULT_MESSAGE.get(String.valueOf(referralDN));
    throw new DirectoryException(
            ResultCode.REFERRAL, msg, referralDN, URIList, null);
  }
  /**
   * Process referral entries that are above the target DN of an operation.
   * @param txn      Abstract transaction for this operation.
   * @param targetDN The target DN of the operation, or the base object of a
   * search operation.
   * @param searchScope The scope of the search operation, or null if the
   * operation is not a search operation.
   * @throws DirectoryException If a referral is found at or above the target
   * DN.  The referral URLs will be set appropriately for the references found
   * in the referral entry.
   * @throws NdbApiException An error occurred during a database operation.
   */
  public void targetEntryReferrals(AbstractTransaction txn,
    DN targetDN, SearchScope searchScope)
       throws DirectoryException, NdbApiException
  {
    try {
      // Go up through the DIT hierarchy until we find a referral.
      for (DN dn = getParentWithinBase(targetDN); dn != null;
        dn = getParentWithinBase(dn)) {
        // Construct a set of all the labeled URIs in the referral.
        long id = dn2id.getID(txn, dn, NdbOperation.LockMode.LM_Read);
        Set<String> labeledURIs = dn2id.getReferrals(txn, id);
        if (!labeledURIs.isEmpty()) {
          throwReferralException(targetDN, dn, labeledURIs, searchScope);
        }
      }
    }
    catch (NdbApiException e)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
    }
  }
  /**
   * Finds an existing entry whose DN is the closest ancestor of a given baseDN.
   *
   * @param baseDN  the DN for which we are searching a matched DN
   * @return the DN of the closest ancestor of the baseDN
   * @throws DirectoryException If an error prevented the check of an
   * existing entry from being performed
   */
  private DN getMatchedDN(AbstractTransaction txn, DN baseDN)
    throws DirectoryException, NdbApiException
  {
    DN matchedDN = null;
    DN parentDN  = baseDN.getParentDNInSuffix();
    while ((parentDN != null) && parentDN.isDescendantOf(getBaseDN()))
    {
      if (entryExists(txn, parentDN))
      {
        matchedDN = parentDN;
        break;
      }
      parentDN = parentDN.getParentDNInSuffix();
    }
    return matchedDN;
  }
}
opends/src/server/org/opends/server/backends/ndb/ExportJob.java
New file
@@ -0,0 +1,293 @@
/*
 * 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/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * 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/opends/resource/legal-notices/OpenDS.LICENSE.  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.
 */
package org.opends.server.backends.ndb;
import com.mysql.cluster.ndbj.NdbApiException;
import com.mysql.cluster.ndbj.NdbOperation;
import org.opends.messages.Message;
import org.opends.server.types.DN;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.Entry;
import org.opends.server.types.LDIFExportConfig;
import org.opends.server.util.LDIFException;
import java.io.IOException;
import java.util.*;
import org.opends.server.backends.ndb.OperationContainer.DN2IDSearchCursor;
import org.opends.server.backends.ndb.OperationContainer.SearchCursorResult;
import org.opends.server.types.DebugLogLevel;
import static org.opends.server.loggers.ErrorLogger.logError;
import static org.opends.server.loggers.debug.DebugLogger.*;
import org.opends.server.loggers.debug.DebugTracer;
import static org.opends.messages.NdbMessages.*;
/**
 * Export a NDB backend to LDIF.
 */
public class ExportJob
{
  /**
   * The tracer object for the debug logger.
   */
  private static final DebugTracer TRACER = getTracer();
  /**
   * The requested LDIF export configuration.
   */
  private LDIFExportConfig exportConfig;
  /**
   * The number of milliseconds between job progress reports.
   */
  private long progressInterval = 10000;
  /**
   * The current number of entries exported.
   */
  private long exportedCount = 0;
  /**
   * The current number of entries skipped.
   */
  private long skippedCount = 0;
  /**
   * Create a new export job.
   *
   * @param exportConfig The requested LDIF export configuration.
   */
  public ExportJob(LDIFExportConfig exportConfig)
  {
    this.exportConfig = exportConfig;
  }
  /**
   * Export entries from the backend to an LDIF file.
   * @param rootContainer The root container to export.
   * @throws NdbApiException If an error occurs in the NDB database.
   * @throws IOException If an I/O error occurs while writing an entry.
   * @throws LDIFException If an error occurs while trying to determine whether
   * to write an entry.
   */
  public void exportLDIF(RootContainer rootContainer)
       throws IOException, LDIFException, NdbApiException
  {
    List<DN> includeBranches = exportConfig.getIncludeBranches();
    DN baseDN;
    ArrayList<EntryContainer> exportContainers =
        new ArrayList<EntryContainer>();
    for (EntryContainer entryContainer : rootContainer.getEntryContainers())
    {
      // Skip containers that are not covered by the include branches.
      baseDN = entryContainer.getBaseDN();
      if (includeBranches == null || includeBranches.isEmpty())
      {
        exportContainers.add(entryContainer);
      }
      else
      {
        for (DN includeBranch : includeBranches)
        {
          if (includeBranch.isDescendantOf(baseDN) ||
               includeBranch.isAncestorOf(baseDN))
          {
            exportContainers.add(entryContainer);
          }
        }
      }
    }
    // Make a note of the time we started.
    long startTime = System.currentTimeMillis();
    // Start a timer for the progress report.
    Timer timer = new Timer();
    TimerTask progressTask = new ProgressTask();
    timer.scheduleAtFixedRate(progressTask, progressInterval,
                              progressInterval);
    // Iterate through the containers.
    try
    {
      for (EntryContainer exportContainer : exportContainers)
      {
        if (exportConfig.isCancelled())
        {
          break;
        }
        exportContainer.sharedLock.lock();
        try
        {
          exportContainer(exportContainer);
        }
        finally
        {
          exportContainer.sharedLock.unlock();
        }
      }
    }
    finally
    {
      timer.cancel();
    }
    long finishTime = System.currentTimeMillis();
    long totalTime = (finishTime - startTime);
    float rate = 0;
    if (totalTime > 0)
    {
      rate = 1000f*exportedCount / totalTime;
    }
    Message message = NOTE_NDB_EXPORT_FINAL_STATUS.get(
        exportedCount, skippedCount, totalTime/1000, rate);
    logError(message);
  }
  /**
   * Export the entries in a single entry entryContainer, in other words from
   * one of the base DNs.
   * @param entryContainer The entry container that holds the entries to be
   *                       exported.
   * @throws NdbApiException If an error occurs in the NDB database.
   * @throws IOException If an error occurs while writing an entry.
   * @throws  LDIFException  If an error occurs while trying to determine
   *                         whether to write an entry.
   */
  private void exportContainer(EntryContainer entryContainer)
       throws IOException, LDIFException, NdbApiException
  {
    OperationContainer dn2id = entryContainer.getDN2ID();
    RootContainer rc = entryContainer.getRootContainer();
    DN baseDN = DN.NULL_DN;
    AbstractTransaction txn = new AbstractTransaction(rc);
    DN2IDSearchCursor cursor = dn2id.getSearchCursor(txn, baseDN);
    cursor.open();
    try {
      SearchCursorResult result = cursor.getNext();
      while (result != null) {
        if (exportConfig.isCancelled()) {
          break;
        }
        DN dn = null;
        try {
          dn = DN.decode(result.dn);
        } catch (DirectoryException ex) {
          if (debugEnabled()) {
            TRACER.debugCaught(DebugLogLevel.ERROR, ex);
          }
          skippedCount++;
          continue;
        }
        Entry entry = null;
        AbstractTransaction leafTxn = new AbstractTransaction(rc);
        try {
          entry = dn2id.get(leafTxn, dn,
            NdbOperation.LockMode.LM_CommittedRead);
        } finally {
          if (leafTxn != null) {
            leafTxn.close();
          }
        }
        if ((entry != null) && entry.toLDIF(exportConfig)) {
          exportedCount++;
        } else {
          skippedCount++;
        }
        // Move to the next record.
        result = cursor.getNext();
      }
    } finally {
      cursor.close();
      if (txn != null) {
        txn.close();
      }
    }
  }
  /**
   * This class reports progress of the export job at fixed intervals.
   */
  class ProgressTask extends TimerTask
  {
    /**
     * The number of entries that had been exported at the time of the
     * previous progress report.
     */
    private long previousCount = 0;
    /**
     * The time in milliseconds of the previous progress report.
     */
    private long previousTime;
    /**
     * Create a new export progress task.
     */
    public ProgressTask()
    {
      previousTime = System.currentTimeMillis();
    }
    /**
     * The action to be performed by this timer task.
     */
    public void run()
    {
      long latestCount = exportedCount;
      long deltaCount = (latestCount - previousCount);
      long latestTime = System.currentTimeMillis();
      long deltaTime = latestTime - previousTime;
      if (deltaTime == 0)
      {
        return;
      }
      float rate = 1000f*deltaCount / deltaTime;
      Message message =
          NOTE_NDB_EXPORT_PROGRESS_REPORT.get(latestCount, skippedCount, rate);
      logError(message);
      previousCount = latestCount;
      previousTime = latestTime;
    }
  };
}
opends/src/server/org/opends/server/backends/ndb/IndexFilter.java
New file
@@ -0,0 +1,244 @@
/*
 * 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/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * 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/opends/resource/legal-notices/OpenDS.LICENSE.  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.
 */
package org.opends.server.backends.ndb;
import com.mysql.cluster.ndbj.NdbApiException;
import com.mysql.cluster.ndbj.NdbIndexScanOperation;
import com.mysql.cluster.ndbj.NdbOperation;
import com.mysql.cluster.ndbj.NdbOperation.AbortOption;
import com.mysql.cluster.ndbj.NdbResultSet;
import com.mysql.cluster.ndbj.NdbTransaction;
import com.mysql.cluster.ndbj.NdbTransaction.ExecType;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.opends.server.core.SearchOperation;
import org.opends.server.types.SearchFilter;
/**
 * An index filter is used to apply a search operation to a set of indexes
 * to generate a set of candidate entries.
 */
public class IndexFilter
{
  /**
   * The entry entryContainer holding the attribute indexes.
   */
  private EntryContainer entryContainer;
  /**
   * The search operation provides the search base, scope and filter.
   * It can also be checked periodically for cancellation.
   */
  private SearchOperation searchOp;
  private boolean defined;
  private SearchFilter searchFilter;
  private List<NdbResultSet> rsList;
  private NdbTransaction ndbTxn;
  private AbstractTransaction txn;
  private Iterator<NdbResultSet> resultsIterator;
  private NdbResultSet currentRs;
  private Set<Long> returnedSet;
  /**
   * Construct an index filter for a search operation.
   *
   * @param txn            Abstract transaction.
   * @param entryContainer The entry entryContainer.
   * @param searchOp       The search operation to be evaluated.
   * @throws NdbApiException An error occurred during a database operation.
   */
  public IndexFilter(AbstractTransaction txn,
                     EntryContainer entryContainer,
                     SearchOperation searchOp) throws NdbApiException
  {
    this.txn = txn;
    this.entryContainer = entryContainer;
    this.searchOp = searchOp;
    this.searchFilter = this.searchOp.getFilter();
    this.ndbTxn = txn.getNdbTransaction();
    this.rsList = new ArrayList<NdbResultSet>();
    this.resultsIterator = null;
    this.returnedSet = new HashSet<Long>();
    this.currentRs = null;
    this.defined = false;
  }
  /**
   * Perform index filter scan.
   * @throws NdbApiException An error occurred during a database operation.
   */
  public void scan() throws NdbApiException {
    ndbTxn.execute(ExecType.NoCommit, AbortOption.AbortOnError, true);
    resultsIterator = rsList.iterator();
    currentRs = resultsIterator.next();
  }
  /**
   * Get next entry id from index scan results.
   * @return next entry id or zero if none.
   * @throws NdbApiException An error occurred during a database operation.
   */
  public long getNext() throws NdbApiException {
    long eid = 0;
    while (!currentRs.next()) {
      if (resultsIterator.hasNext()) {
        currentRs = resultsIterator.next();
      } else {
        return eid;
      }
    }
    eid = currentRs.getLong(BackendImpl.EID);
    if (!returnedSet.add(eid)) {
      return getNext();
    }
    return eid;
  }
  /**
   * Evaluate index filter.
   * @return true if the filter is defined, false otherwise.
   * @throws NdbApiException An error occurred during a database operation.
   */
  public boolean evaluate()
    throws NdbApiException
  {
    defined = false;
    return evaluateFilter(searchFilter);
  }
  private boolean evaluateFilter(SearchFilter filter)
    throws NdbApiException
  {
    String attrName = null;
    switch (filter.getFilterType()) {
      case AND:
      case OR:
        for (SearchFilter compFilter :
             filter.getFilterComponents())
        {
          evaluateFilter(compFilter);
        }
        break;
      case EQUALITY:
      case APPROXIMATE_MATCH:
        attrName = filter.getAttributeType().getNameOrOID();
        if (BackendImpl.indexes.contains(attrName)) {
          NdbIndexScanOperation indexScanOp =
            ndbTxn.getSelectIndexScanOperation(BackendImpl.IDX_VAL,
            BackendImpl.IDX_TABLE_PREFIX + attrName,
            NdbOperation.LockMode.LM_CommittedRead);
          indexScanOp.setBoundString(BackendImpl.IDX_VAL,
            NdbIndexScanOperation.BoundType.BoundEQ,
            filter.getAssertionValue().toString());
          indexScanOp.getValue(BackendImpl.EID);
          NdbResultSet rs = indexScanOp.resultData();
          rsList.add(rs);
          defined = true;
        }
        break;
      case GREATER_OR_EQUAL:
        attrName = filter.getAttributeType().getNameOrOID();
        if (BackendImpl.indexes.contains(attrName)) {
          NdbIndexScanOperation indexScanOp =
            ndbTxn.getSelectIndexScanOperation(BackendImpl.IDX_VAL,
            BackendImpl.IDX_TABLE_PREFIX + attrName,
            NdbOperation.LockMode.LM_CommittedRead);
          indexScanOp.setBoundString(BackendImpl.IDX_VAL,
            NdbIndexScanOperation.BoundType.BoundGE,
            filter.getAssertionValue().toString());
          indexScanOp.getValue(BackendImpl.EID);
          NdbResultSet rs = indexScanOp.resultData();
          rsList.add(rs);
          defined = true;
        }
        break;
      case LESS_OR_EQUAL:
        attrName = filter.getAttributeType().getNameOrOID();
        if (BackendImpl.indexes.contains(attrName)) {
          NdbIndexScanOperation indexScanOp =
            ndbTxn.getSelectIndexScanOperation(BackendImpl.IDX_VAL,
            BackendImpl.IDX_TABLE_PREFIX + attrName,
            NdbOperation.LockMode.LM_CommittedRead);
          indexScanOp.setBoundString(BackendImpl.IDX_VAL,
            NdbIndexScanOperation.BoundType.BoundLE,
            filter.getAssertionValue().toString());
          indexScanOp.getValue(BackendImpl.EID);
          NdbResultSet rs = indexScanOp.resultData();
          rsList.add(rs);
          defined = true;
        }
        break;
      case PRESENT:
        attrName = filter.getAttributeType().getNameOrOID();
        if (BackendImpl.indexes.contains(attrName)) {
          NdbIndexScanOperation indexScanOp =
            ndbTxn.getSelectIndexScanOperation(BackendImpl.IDX_VAL,
            BackendImpl.IDX_TABLE_PREFIX + attrName,
            NdbOperation.LockMode.LM_CommittedRead);
          indexScanOp.setBoundString(BackendImpl.IDX_VAL,
            NdbIndexScanOperation.BoundType.BoundLT, "");
          indexScanOp.getValue(BackendImpl.EID);
          NdbResultSet rs = indexScanOp.resultData();
          rsList.add(rs);
          defined = true;
        }
        break;
      case NOT:
      case SUBSTRING:
      case EXTENSIBLE_MATCH:
      default:
        //NYI
        break;
    }
    return defined;
  }
  /**
   * Close index filter.
   */
  public void close() {
    ndbTxn = null;
    txn = null;
  }
}
opends/src/server/org/opends/server/backends/ndb/NDBException.java
New file
@@ -0,0 +1,76 @@
/*
 * 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/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * 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/opends/resource/legal-notices/OpenDS.LICENSE.  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.
 */
package org.opends.server.backends.ndb;
import org.opends.server.types.IdentifiedException;
import org.opends.messages.Message;
/**
 * This class defines an exception that may be thrown if a problem occurs in
 * the NDB backend database.
 */
public class NDBException
     extends IdentifiedException
{
  /**
   * The serial version identifier required to satisfy the compiler because this
   * class extends <CODE>java.lang.Exception</CODE>, which implements the
   * <CODE>java.io.Serializable</CODE> interface.  This value was generated
   * using the <CODE>serialver</CODE> command-line utility included with the
   * Java SDK.
   */
  static final long serialVersionUID = 3110979454298870834L;
  /**
   * Creates a new NDB backend exception with the provided message.
   *
   * @param  message    The message that explains the problem that occurred.
   */
  public NDBException(Message message)
  {
    super(message);
  }
  /**
   * Creates a new NDB backend exception with the provided message and root
   * cause.
   *
   * @param  message    The message that explains the problem that occurred.
   * @param  cause      The exception that was caught to trigger this exception.
   */
  public NDBException(Message message, Throwable cause)
  {
    super(message, cause);
  }
}
opends/src/server/org/opends/server/backends/ndb/OperationContainer.java
New file
@@ -0,0 +1,1708 @@
/*
 * 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/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * 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/opends/resource/legal-notices/OpenDS.LICENSE.  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.
 */
package org.opends.server.backends.ndb;
import com.mysql.cluster.ndbj.Ndb;
import com.mysql.cluster.ndbj.NdbApiException;
import com.mysql.cluster.ndbj.NdbBlob;
import com.mysql.cluster.ndbj.NdbIndexScanOperation;
import com.mysql.cluster.ndbj.NdbOperation;
import com.mysql.cluster.ndbj.NdbOperation.AbortOption;
import com.mysql.cluster.ndbj.NdbResultSet;
import com.mysql.cluster.ndbj.NdbTransaction;
import com.mysql.cluster.ndbj.NdbTransaction.ExecType;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.opends.server.core.DirectoryServer;
import org.opends.server.types.DN;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.types.Attribute;
import org.opends.server.types.AttributeBuilder;
import org.opends.server.types.AttributeType;
import org.opends.server.types.AttributeValue;
import org.opends.server.types.AttributeValues;
import org.opends.server.types.ByteString;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.Entry;
import org.opends.server.types.ObjectClass;
import org.opends.server.types.ObjectClassType;
import static org.opends.server.util.ServerConstants.ATTR_REFERRAL_URL;
import static org.opends.server.loggers.debug.DebugLogger.*;
/**
 * This class represents the DN database, which has one record for each entry.
 */
public class OperationContainer extends DatabaseContainer
{
  /**
   * The tracer object for the debug logger.
   */
  private static final DebugTracer TRACER = getTracer();
  /**
   * The default name of the ordered index for the primary key.
   */
  private static final String PRIMARY_INDEX_NAME = "PRIMARY";
  /**
   * The name of extensible object objectclass.
   */
  private static final String OC_EXTENSIBLEOBJECT = "extensibleObject";
  /**
   * Lowest multivalued attribute id.
   */
  private static final int MIN_MID = 1;
  /**
   * Create a OperationContainer instance for the database in a
   * given entryContainer.
   *
   * @param name The name of the database.
   * @param entryContainer The entryContainer of the database.
   * @throws NdbApiException If an error occurs.
   */
  OperationContainer(String name, EntryContainer entryContainer)
      throws NdbApiException
  {
    super(name, entryContainer);
  }
  /**
   * Insert a new entry into the database.
   * @param txn Abstract transaction to be used for the database operation.
   * @param dn The entry DN.
   * @param id The entry ID.
   * @param entry The entry.
   * @return true if the entry was inserted, false if a entry already exists.
   * @throws NdbApiException If an error occurred while attempting to insert
   * the new entry.
   */
  public boolean insert(AbstractTransaction txn, DN dn, long id, Entry entry)
       throws NdbApiException
  {
    return writeNDBEntry(txn, dn, id, entry, false);
  }
  /**
   * Put an entry to the database.  If an entry already exists, the entry
   * will be replaced, otherwise a new entry will be inserted.
   * @param txn Abstract transaction to be used for the database operation.
   * @param dn The entry DN.
   * @param id The entry ID.
   * @param entry The new entry.
   * @param originalEntry The old entry.
   * @return true if the entry was written, false if it was not written.
   * @throws NdbApiException If an error occurred while attempting to write
   * the entry.
   */
  public boolean put(AbstractTransaction txn, DN dn, long id, Entry entry,
                     Entry originalEntry)
       throws NdbApiException
  {
    // Delete first.
    deleteNDBEntry(txn, originalEntry, id);
    return writeNDBEntry(txn, dn, id, entry, true);
  }
  /**
   * Write an entry to the database.
   * @param txn Abstract transaction to be used for the database operation.
   * @param dn The entry DN.
   * @param id The entry ID.
   * @param entry The entry.
   * @param overwrite Whether or not the entry should be overwritten.
   * @return true if the entry was written, false if it was not written.
   * @throws com.mysql.cluster.ndbj.NdbApiException If an error occurred
   * while attempting to write the entry.
   */
  private boolean writeNDBEntry(AbstractTransaction txn, DN dn,
    long id, Entry entry, boolean overwrite)
    throws NdbApiException
  {
    int nClasses = 0;
    NdbOperation op = null;
    NdbOperation tagOp = null;
    StringBuilder ocBuffer = new StringBuilder();
    StringBuilder xocBuffer = new StringBuilder();
    Map<ObjectClass, String> ocMap = entry.getObjectClasses();
    Map<AttributeType, List<Attribute>> userAttrMap =
      entry.getUserAttributes();
    ArrayList<AttributeType> userAttributes =
      new ArrayList<AttributeType>();
    boolean extensibleObject = false;
    // Update ocs tables.
    NdbTransaction ndbDATxn = null;
    for (Map.Entry<ObjectClass, String> ocEntry : ocMap.entrySet()) {
      ObjectClass oc = ocEntry.getKey();
      String ocName = oc.getNameOrOID();
      Map<Integer, NdbOperation> mvOpMap =
        new HashMap<Integer, NdbOperation>();
      if (nClasses > 0) {
        ocBuffer.append(" ");
      }
      ocBuffer.append(ocName);
      nClasses++;
      if (oc.getObjectClassType() == ObjectClassType.ABSTRACT) {
        continue;
      }
      if (ocName.equalsIgnoreCase(OC_EXTENSIBLEOBJECT)) {
        extensibleObject = true;
      }
      if (ndbDATxn == null) {
        ndbDATxn = txn.getNdbDATransaction(ocName, id);
      }
      if (overwrite) {
        op = ndbDATxn.getWriteOperation(ocName);
      } else {
        op = ndbDATxn.getInsertOperation(ocName);
      }
      op.equalLong(BackendImpl.EID, id);
      op.equalInt(BackendImpl.MID, MIN_MID);
      mvOpMap.put(MIN_MID, op);
      for (AttributeType reqAttr : oc.getRequiredAttributes()) {
        if (userAttributes.contains(reqAttr)) {
          continue;
        }
        if (reqAttr.isOperational()) {
          userAttrMap.put(reqAttr, entry.getOperationalAttribute(reqAttr));
        }
        String attrName = reqAttr.getNameOrOID();
        if (entry.hasAttribute(reqAttr)) {
          boolean indexed = BackendImpl.indexes.contains(attrName);
          List<Attribute> attrList = userAttrMap.get(reqAttr);
          int mid = MIN_MID;
          for (Attribute attr : attrList) {
            if (attr.isVirtual() || attr.isEmpty()) {
              continue;
            }
            // Attribute options.
            Set<String> attrOptionsSet = attr.getOptions();
            if (!attrOptionsSet.isEmpty()) {
              if (overwrite) {
                tagOp =
                  ndbDATxn.getWriteOperation(BackendImpl.TAGS_TABLE);
              } else {
                tagOp =
                  ndbDATxn.getInsertOperation(BackendImpl.TAGS_TABLE);
              }
              tagOp.equalLong(BackendImpl.EID, id);
              tagOp.equalString(BackendImpl.TAG_ATTR, attrName);
              tagOp.equalInt(BackendImpl.MID, mid);
              StringBuilder buffer = new StringBuilder();
              for (String option : attrOptionsSet) {
                buffer.append(';');
                buffer.append(option);
              }
              tagOp.setString(BackendImpl.TAG_TAGS, buffer.toString());
            }
            for (AttributeValue attrVal : attr) {
              String attrStringVal = attrVal.toString();
              NdbOperation attrOp = mvOpMap.get(mid);
              if (attrOp == null) {
                if (overwrite) {
                  attrOp = ndbDATxn.getWriteOperation(ocName);
                } else {
                  attrOp = ndbDATxn.getInsertOperation(ocName);
                }
                attrOp.equalLong(BackendImpl.EID, id);
                attrOp.equalInt(BackendImpl.MID, mid);
                mvOpMap.put(mid, attrOp);
              }
              if (BackendImpl.blobAttributes.contains(attrName)) {
                NdbBlob blob = attrOp.getBlobHandle(attrName);
                blob.setValue(attrVal.getValue().toByteArray());
              } else {
                attrOp.setString(attrName, attrStringVal);
              }
              // Update Indexes.
              if (indexed) {
                NdbOperation idxOp = null;
                if (overwrite) {
                  idxOp = ndbDATxn.getWriteOperation(
                    BackendImpl.IDX_TABLE_PREFIX + attrName);
                } else {
                  idxOp = ndbDATxn.getInsertOperation(
                    BackendImpl.IDX_TABLE_PREFIX + attrName);
                }
                idxOp.equalLong(BackendImpl.EID, id);
                idxOp.equalInt(BackendImpl.MID, mid);
                idxOp.setString(BackendImpl.IDX_VAL, attrStringVal);
              }
              mid++;
            }
          }
          userAttributes.add(reqAttr);
        }
      }
      for (AttributeType optAttr : oc.getOptionalAttributes()) {
        if (userAttributes.contains(optAttr)) {
          continue;
        }
        if (optAttr.isOperational()) {
          userAttrMap.put(optAttr, entry.getOperationalAttribute(optAttr));
        }
        String attrName = optAttr.getNameOrOID();
        if (entry.hasAttribute(optAttr)) {
          boolean indexed = BackendImpl.indexes.contains(attrName);
          List<Attribute> attrList = userAttrMap.get(optAttr);
          int mid = MIN_MID;
          for (Attribute attr : attrList) {
            if (attr.isVirtual() || attr.isEmpty()) {
              continue;
            }
            // Attribute options.
            Set<String> attrOptionsSet = attr.getOptions();
            if (!attrOptionsSet.isEmpty()) {
              if (overwrite) {
                tagOp =
                  ndbDATxn.getWriteOperation(BackendImpl.TAGS_TABLE);
              } else {
                tagOp =
                  ndbDATxn.getInsertOperation(BackendImpl.TAGS_TABLE);
              }
              tagOp.equalLong(BackendImpl.EID, id);
              tagOp.equalString(BackendImpl.TAG_ATTR, attrName);
              tagOp.equalInt(BackendImpl.MID, mid);
              StringBuilder buffer = new StringBuilder();
              for (String option : attrOptionsSet) {
                buffer.append(';');
                buffer.append(option);
              }
              tagOp.setString(BackendImpl.TAG_TAGS, buffer.toString());
            }
            for (AttributeValue attrVal : attr) {
              String attrStringVal = attrVal.toString();
              NdbOperation attrOp = mvOpMap.get(mid);
              if (attrOp == null) {
                if (overwrite) {
                  attrOp = ndbDATxn.getWriteOperation(ocName);
                } else {
                  attrOp = ndbDATxn.getInsertOperation(ocName);
                }
                attrOp.equalLong(BackendImpl.EID, id);
                attrOp.equalInt(BackendImpl.MID, mid);
                mvOpMap.put(mid, attrOp);
              }
              if (BackendImpl.blobAttributes.contains(attrName)) {
                NdbBlob blob = attrOp.getBlobHandle(attrName);
                blob.setValue(attrVal.getValue().toByteArray());
              } else {
                attrOp.setString(attrName, attrStringVal);
              }
              // Update Indexes.
              if (indexed) {
                NdbOperation idxOp = null;
                if (overwrite) {
                  idxOp = ndbDATxn.getWriteOperation(
                    BackendImpl.IDX_TABLE_PREFIX + attrName);
                } else {
                  idxOp = ndbDATxn.getInsertOperation(
                    BackendImpl.IDX_TABLE_PREFIX + attrName);
                }
                idxOp.equalLong(BackendImpl.EID, id);
                idxOp.equalInt(BackendImpl.MID, mid);
                idxOp.setString(BackendImpl.IDX_VAL, attrStringVal);
              }
              mid++;
            }
          }
          userAttributes.add(optAttr);
        }
      }
    }
    // Extensible object.
    if (extensibleObject) {
      int xnClasses = 0;
      for (Map.Entry<AttributeType, List<Attribute>> attrEntry :
           userAttrMap.entrySet())
      {
        AttributeType attrType = attrEntry.getKey();
        if (!userAttributes.contains(attrType)) {
          String attrName = attrType.getNameOrOID();
          String ocName = BackendImpl.attr2Oc.get(attrName);
          Map<Integer, NdbOperation> mvOpMap =
            new HashMap<Integer, NdbOperation>();
          boolean indexed = BackendImpl.indexes.contains(attrName);
          if (ndbDATxn == null) {
            ndbDATxn = txn.getNdbDATransaction(ocName, id);
          }
          if (overwrite) {
            op = ndbDATxn.getWriteOperation(ocName);
          } else {
            op = ndbDATxn.getInsertOperation(ocName);
          }
          op.equalLong(BackendImpl.EID, id);
          op.equalInt(BackendImpl.MID, MIN_MID);
          mvOpMap.put(MIN_MID, op);
          List<Attribute> attrList = userAttrMap.get(attrType);
          int mid = MIN_MID;
          for (Attribute attr : attrList) {
            if (attr.isVirtual() || attr.isEmpty()) {
              continue;
            }
            // Attribute options.
            Set<String> attrOptionsSet = attr.getOptions();
            if (!attrOptionsSet.isEmpty()) {
              if (overwrite) {
                tagOp =
                  ndbDATxn.getWriteOperation(BackendImpl.TAGS_TABLE);
              } else {
                tagOp =
                  ndbDATxn.getInsertOperation(BackendImpl.TAGS_TABLE);
              }
              tagOp.equalLong(BackendImpl.EID, id);
              tagOp.equalString(BackendImpl.TAG_ATTR, attrName);
              tagOp.equalInt(BackendImpl.MID, mid);
              StringBuilder buffer = new StringBuilder();
              for (String option : attrOptionsSet) {
                buffer.append(';');
                buffer.append(option);
              }
              tagOp.setString(BackendImpl.TAG_TAGS, buffer.toString());
            }
            for (AttributeValue attrVal : attr) {
              String attrStringVal = attrVal.toString();
              NdbOperation attrOp = mvOpMap.get(mid);
              if (attrOp == null) {
                if (overwrite) {
                  attrOp = ndbDATxn.getWriteOperation(ocName);
                } else {
                  attrOp = ndbDATxn.getInsertOperation(ocName);
                }
                attrOp.equalLong(BackendImpl.EID, id);
                attrOp.equalInt(BackendImpl.MID, mid);
                mvOpMap.put(mid, attrOp);
              }
              if (BackendImpl.blobAttributes.contains(attrName)) {
                NdbBlob blob = attrOp.getBlobHandle(attrName);
                blob.setValue(attrVal.getValue().toByteArray());
              } else {
                attrOp.setString(attrName, attrStringVal);
              }
              // Update Indexes.
              if (indexed) {
                NdbOperation idxOp = null;
                if (overwrite) {
                  idxOp = ndbDATxn.getWriteOperation(
                    BackendImpl.IDX_TABLE_PREFIX + attrName);
                } else {
                  idxOp = ndbDATxn.getInsertOperation(
                    BackendImpl.IDX_TABLE_PREFIX + attrName);
                }
                idxOp.equalLong(BackendImpl.EID, id);
                idxOp.equalInt(BackendImpl.MID, mid);
                idxOp.setString(BackendImpl.IDX_VAL, attrStringVal);
              }
              mid++;
            }
          }
          userAttributes.add(attrType);
          if (xnClasses > 0) {
            xocBuffer.append(" ");
          }
          xocBuffer.append(ocName);
          xnClasses++;
        }
      }
    }
    // Update operational attributes table.
    if (overwrite) {
      op = ndbDATxn.getWriteOperation(BackendImpl.OPATTRS_TABLE);
    } else {
      op = ndbDATxn.getInsertOperation(BackendImpl.OPATTRS_TABLE);
    }
    op.equalLong(BackendImpl.EID, id);
    for (List<Attribute> attrList :
         entry.getOperationalAttributes().values())
    {
      for (Attribute attr : attrList) {
        if (attr.isVirtual() || attr.isEmpty()) {
          continue;
        }
        if (userAttrMap.containsKey(attr.getAttributeType())) {
          continue;
        }
        String attrName = attr.getAttributeType().getNameOrOID();
        for (AttributeValue attrVal : attr) {
          op.setString(attrName, attrVal.toString());
        }
      }
    }
    // Update dn2id table.
    NdbTransaction ndbTxn = txn.getNdbTransaction();
    if (overwrite) {
      op = ndbTxn.getWriteOperation(name);
    } else {
      op = ndbTxn.getInsertOperation(name);
    }
    int componentIndex = dn.getNumComponents() - 1;
    for (int i=0; i < BackendImpl.DN2ID_DN_NC; i++) {
      while (componentIndex >= 0) {
        op.equalString(BackendImpl.DN2ID_DN + Integer.toString(i),
          dn.getRDN(componentIndex).toNormalizedString());
        componentIndex--;
        i++;
      }
      op.equalString(BackendImpl.DN2ID_DN +
        Integer.toString(i), "");
    }
    op.setLong(BackendImpl.EID, id);
    op.setString(BackendImpl.DN2ID_OC, ocBuffer.toString());
    op.setString(BackendImpl.DN2ID_XOC, xocBuffer.toString());
    return true;
  }
  /**
   * Delete an entry from the database.
   * @param txn Abstract transaction to be used for the database operation.
   * @param originalEntry The original entry.
   * @param id The entry ID.
   * @throws com.mysql.cluster.ndbj.NdbApiException If an error occurred
   * while attempting to write the entry.
   */
  private void deleteNDBEntry(AbstractTransaction txn,
    Entry originalEntry, long id) throws NdbApiException
  {
    NdbOperation op = null;
    NdbOperation tagOp = null;
    NdbTransaction ndbDATxn = null;
    boolean extensibleObject = false;
    // Delete attributes.
    Map<ObjectClass, String> originalOcMap =
      originalEntry.getObjectClasses();
    ArrayList<AttributeType> originalUserAttributes =
      new ArrayList<AttributeType>();
    Map<AttributeType, List<Attribute>> originalUserAttrMap =
      originalEntry.getUserAttributes();
    for (Map.Entry<ObjectClass, String> ocEntry : originalOcMap.entrySet()) {
      ObjectClass oc = ocEntry.getKey();
      String ocName = oc.getNameOrOID();
      Map<Integer, NdbOperation> mvOpMap =
        new HashMap<Integer, NdbOperation>();
      if (oc.getObjectClassType() == ObjectClassType.ABSTRACT) {
        continue;
      }
      if (ocName.equalsIgnoreCase(OC_EXTENSIBLEOBJECT)) {
        extensibleObject = true;
      }
      if (ndbDATxn == null) {
        ndbDATxn = txn.getNdbDATransaction(ocName, id);
      }
      op = ndbDATxn.getDeleteOperation(ocName);
      op.equalLong(BackendImpl.EID, id);
      op.equalInt(BackendImpl.MID, MIN_MID);
      mvOpMap.put(MIN_MID, op);
      for (AttributeType reqAttr : oc.getRequiredAttributes()) {
        String attrName = reqAttr.getNameOrOID();
        if (originalUserAttributes.contains(reqAttr)) {
          continue;
        }
        if (originalEntry.hasUserAttribute(reqAttr)) {
          boolean indexed = BackendImpl.indexes.contains(attrName);
          List<Attribute> attrList = originalUserAttrMap.get(reqAttr);
          int mid = MIN_MID;
          for (Attribute attr : attrList) {
            if (attr.isVirtual() || attr.isEmpty()) {
              continue;
            }
            // Attribute options.
            Set<String> attrOptionsSet = attr.getOptions();
            if (!attrOptionsSet.isEmpty()) {
              tagOp =
                ndbDATxn.getDeleteOperation(BackendImpl.TAGS_TABLE);
              tagOp.equalLong(BackendImpl.EID, id);
              tagOp.equalString(BackendImpl.TAG_ATTR, attrName);
              tagOp.equalInt(BackendImpl.MID, mid);
            }
            for (AttributeValue attrVal : attr) {
              NdbOperation attrOp = mvOpMap.get(mid);
              if (attrOp == null) {
                attrOp = ndbDATxn.getDeleteOperation(ocName);
                attrOp.equalLong(BackendImpl.EID, id);
                attrOp.equalInt(BackendImpl.MID, mid);
                mvOpMap.put(mid, attrOp);
              }
              // Update Indexes.
              if (indexed) {
                NdbOperation idxOp = ndbDATxn.getDeleteOperation(
                  BackendImpl.IDX_TABLE_PREFIX + attrName);
                idxOp.equalLong(BackendImpl.EID, id);
                idxOp.equalInt(BackendImpl.MID, mid);
              }
              mid++;
            }
          }
          originalUserAttributes.add(reqAttr);
        }
      }
      for (AttributeType optAttr : oc.getOptionalAttributes()) {
        String attrName = optAttr.getNameOrOID();
        if (originalUserAttributes.contains(optAttr)) {
          continue;
        }
        if (originalEntry.hasUserAttribute(optAttr)) {
          boolean indexed = BackendImpl.indexes.contains(attrName);
          List<Attribute> attrList = originalUserAttrMap.get(optAttr);
          int mid = MIN_MID;
          for (Attribute attr : attrList) {
            if (attr.isVirtual() || attr.isEmpty()) {
              continue;
            }
            // Attribute options.
            Set<String> attrOptionsSet = attr.getOptions();
            if (!attrOptionsSet.isEmpty()) {
              tagOp =
                ndbDATxn.getDeleteOperation(BackendImpl.TAGS_TABLE);
              tagOp.equalLong(BackendImpl.EID, id);
              tagOp.equalString(BackendImpl.TAG_ATTR, attrName);
              tagOp.equalInt(BackendImpl.MID, mid);
            }
            for (AttributeValue attrVal : attr) {
              NdbOperation attrOp = mvOpMap.get(mid);
              if (attrOp == null) {
                attrOp = ndbDATxn.getDeleteOperation(ocName);
                attrOp.equalLong(BackendImpl.EID, id);
                attrOp.equalInt(BackendImpl.MID, mid);
                mvOpMap.put(mid, attrOp);
              }
              // Update Indexes.
              if (indexed) {
                NdbOperation idxOp = ndbDATxn.getDeleteOperation(
                  BackendImpl.IDX_TABLE_PREFIX + attrName);
                idxOp.equalLong(BackendImpl.EID, id);
                idxOp.equalInt(BackendImpl.MID, mid);
              }
              mid++;
            }
          }
          originalUserAttributes.add(optAttr);
        }
      }
    }
    // Extensible object.
    if (extensibleObject) {
      for (Map.Entry<AttributeType, List<Attribute>> attrEntry :
           originalUserAttrMap.entrySet())
      {
        AttributeType attrType = attrEntry.getKey();
        if (!originalUserAttributes.contains(attrType)) {
          String attrName = attrType.getNameOrOID();
          String ocName = BackendImpl.attr2Oc.get(attrName);
          Map<Integer, NdbOperation> mvOpMap =
            new HashMap<Integer, NdbOperation>();
          boolean indexed = BackendImpl.indexes.contains(attrName);
          if (ndbDATxn == null) {
            ndbDATxn = txn.getNdbDATransaction(ocName, id);
          }
          op = ndbDATxn.getDeleteOperation(ocName);
          op.equalLong(BackendImpl.EID, id);
          op.equalInt(BackendImpl.MID, MIN_MID);
          mvOpMap.put(MIN_MID, op);
          List<Attribute> attrList = originalUserAttrMap.get(attrType);
          int mid = MIN_MID;
          for (Attribute attr : attrList) {
            if (attr.isVirtual() || attr.isEmpty()) {
              continue;
            }
            // Attribute options.
            Set<String> attrOptionsSet = attr.getOptions();
            if (!attrOptionsSet.isEmpty()) {
              tagOp =
                ndbDATxn.getDeleteOperation(BackendImpl.TAGS_TABLE);
              tagOp.equalLong(BackendImpl.EID, id);
              tagOp.equalString(BackendImpl.TAG_ATTR, attrName);
              tagOp.equalInt(BackendImpl.MID, mid);
            }
            for (AttributeValue attrVal : attr) {
              NdbOperation attrOp = mvOpMap.get(mid);
              if (attrOp == null) {
                attrOp = ndbDATxn.getDeleteOperation(ocName);
                attrOp.equalLong(BackendImpl.EID, id);
                attrOp.equalInt(BackendImpl.MID, mid);
                mvOpMap.put(mid, attrOp);
              }
              // Update Indexes.
              if (indexed) {
                NdbOperation idxOp = ndbDATxn.getDeleteOperation(
                  BackendImpl.IDX_TABLE_PREFIX + attrName);
                idxOp.equalLong(BackendImpl.EID, id);
                idxOp.equalInt(BackendImpl.MID, mid);
              }
              mid++;
            }
          }
          originalUserAttributes.add(attrType);
        }
      }
    }
  }
  /**
   * Remove an entry from the database.
   * @param txn Abstract transaction to be used for the database operation.
   * @param entry The entry.
   * @return true if the entry was removed, false if it was not removed.
   * @throws NdbApiException If an error occurred while attempting to remove
   * the entry.
   */
  public boolean remove(AbstractTransaction txn, Entry entry)
       throws NdbApiException
  {
    DN dn = entry.getDN();
    NdbResultSet rs = null;
    NdbTransaction ndbTxn = txn.getNdbTransaction();
    NdbOperation op = ndbTxn.getSelectOperation(name,
      NdbOperation.LockMode.LM_CommittedRead);
    boolean extensibleObject = false;
    int componentIndex = dn.getNumComponents() - 1;
    for (int i=0; i < BackendImpl.DN2ID_DN_NC; i++) {
      while (componentIndex >= 0) {
        op.equalString(BackendImpl.DN2ID_DN + Integer.toString(i),
          dn.getRDN(componentIndex).toNormalizedString());
        componentIndex--;
        i++;
      }
      op.equalString(BackendImpl.DN2ID_DN +
        Integer.toString(i), "");
    }
    op.getValue(BackendImpl.EID);
    op.getValue(BackendImpl.DN2ID_OC);
    op.getValue(BackendImpl.DN2ID_XOC);
    rs = op.resultData();
    ndbTxn.execute(ExecType.NoCommit, AbortOption.AO_IgnoreError, true);
    long eid = 0;
    NdbTransaction ndbDATxn = null;
    String[] ocsStringArray = null;
    String[] xocsStringArray = null;
    List<NdbResultSet> ocRsList = new ArrayList<NdbResultSet>();
    NdbIndexScanOperation indexScanOp = null;
    if (rs.next()) {
      eid = rs.getLong(BackendImpl.EID);
      String ocsString = rs.getString(BackendImpl.DN2ID_OC);
      ocsStringArray = ocsString.split(" ");
      String xocsString = rs.getString(BackendImpl.DN2ID_XOC);
      xocsStringArray = xocsString.split(" ");
      if (xocsString.length() > 0) {
        extensibleObject = true;
      }
      for (String ocName : ocsStringArray) {
        ObjectClass oc =
          DirectoryServer.getObjectClass(ocName, true);
        if (oc.getObjectClassType() == ObjectClassType.ABSTRACT) {
          continue;
        }
        if (ndbDATxn == null) {
          ndbDATxn = txn.getNdbDATransaction(ocName, eid);
        }
        indexScanOp =
          ndbDATxn.getSelectIndexScanOperation(PRIMARY_INDEX_NAME, ocName);
        indexScanOp.setBoundLong(BackendImpl.EID,
            NdbIndexScanOperation.BoundType.BoundEQ, eid);
        indexScanOp.getValue(BackendImpl.MID);
        ocRsList.add(indexScanOp.resultData());
      }
      // Extensible object.
      if (extensibleObject) {
        for (String xocName : xocsStringArray) {
          ObjectClass xoc =
            DirectoryServer.getObjectClass(xocName, true);
          if (xoc.getObjectClassType() == ObjectClassType.ABSTRACT) {
            continue;
          }
          if (ndbDATxn == null) {
            ndbDATxn = txn.getNdbDATransaction(xocName, eid);
          }
          indexScanOp =
            ndbDATxn.getSelectIndexScanOperation(PRIMARY_INDEX_NAME, xocName);
          indexScanOp.setBoundLong(BackendImpl.EID,
            NdbIndexScanOperation.BoundType.BoundEQ, eid);
          indexScanOp.getValue(BackendImpl.MID);
          ocRsList.add(indexScanOp.resultData());
        }
      }
    }
    // Attribute options.
    if (ndbDATxn == null) {
      ndbDATxn = txn.getNdbDATransaction(BackendImpl.TAGS_TABLE, eid);
    }
    indexScanOp = ndbDATxn.getSelectIndexScanOperation(PRIMARY_INDEX_NAME,
      BackendImpl.TAGS_TABLE);
    indexScanOp.setBoundLong(BackendImpl.EID,
      NdbIndexScanOperation.BoundType.BoundEQ, eid);
    indexScanOp.getValue(BackendImpl.TAG_ATTR);
    indexScanOp.getValue(BackendImpl.MID);
    NdbResultSet tagRs = indexScanOp.resultData();
    ndbDATxn.execute(ExecType.NoCommit, AbortOption.AO_IgnoreError, true);
    Iterator<NdbResultSet> rsIterator = ocRsList.iterator();
    for (String ocName : ocsStringArray) {
      ObjectClass oc =
        DirectoryServer.getObjectClass(ocName, true);
      if (oc.getObjectClassType() == ObjectClassType.ABSTRACT) {
        continue;
      }
      NdbResultSet ocRs = rsIterator.next();
      while (ocRs.next()) {
        int mid = ocRs.getInt(BackendImpl.MID);
        op = ndbDATxn.getDeleteOperation(ocName);
        op.equalLong(BackendImpl.EID, eid);
        op.equalInt(BackendImpl.MID, mid);
      }
    }
    // Extensible object.
    if (extensibleObject) {
      for (String xocName : xocsStringArray) {
        ObjectClass xoc =
          DirectoryServer.getObjectClass(xocName, true);
        if (xoc.getObjectClassType() == ObjectClassType.ABSTRACT) {
          continue;
        }
        NdbResultSet ocRs = rsIterator.next();
        while (ocRs.next()) {
          int mid = ocRs.getInt(BackendImpl.MID);
          op = ndbDATxn.getDeleteOperation(xocName);
          op.equalLong(BackendImpl.EID, eid);
          op.equalInt(BackendImpl.MID, mid);
        }
      }
    }
    // Operational attributes.
    op = ndbDATxn.getDeleteOperation(BackendImpl.OPATTRS_TABLE);
    op.equalLong(BackendImpl.EID, eid);
    // Attribute options.
    while (tagRs.next()) {
      String attrName = tagRs.getString(BackendImpl.TAG_ATTR);
      int mid = tagRs.getInt(BackendImpl.MID);
      op = ndbDATxn.getDeleteOperation(BackendImpl.TAGS_TABLE);
      op.equalLong(BackendImpl.EID, eid);
      op.equalString(BackendImpl.TAG_ATTR, attrName);
      op.equalInt(BackendImpl.MID, mid);
    }
    // Indexes.
    for (String attrName : BackendImpl.indexes) {
      AttributeType attributeType =
              DirectoryServer.getAttributeType(
              attrName.toLowerCase(), true);
      if (entry.hasAttribute(attributeType)) {
        List<Attribute> attrList =
          entry.getAttribute(attributeType);
        int mid = MIN_MID;
        for (Attribute attr : attrList) {
          for (AttributeValue attrVal : attr) {
            NdbOperation idxOp = ndbDATxn.getDeleteOperation(
              BackendImpl.IDX_TABLE_PREFIX + attrName);
            idxOp.equalLong(BackendImpl.EID, eid);
            idxOp.equalInt(BackendImpl.MID, mid);
            mid++;
          }
        }
      }
    }
    // dn2id.
    op = ndbTxn.getDeleteOperation(name);
    componentIndex = dn.getNumComponents() - 1;
    for (int i=0; i < BackendImpl.DN2ID_DN_NC; i++) {
      while (componentIndex >= 0) {
        op.equalString(BackendImpl.DN2ID_DN + Integer.toString(i),
          dn.getRDN(componentIndex).toNormalizedString());
        componentIndex--;
        i++;
      }
      op.equalString(BackendImpl.DN2ID_DN +
        Integer.toString(i), "");
    }
    return true;
  }
  /**
   * Fetch the entry for a given ID.
   * @param txn Abstract transaction to be used for the database read.
   * @param eid The ID for which the entry is desired.
   * @param lockMode NDB locking mode for this operation.
   * @return The entry, or null if the given ID is not in the database.
   * @throws NdbApiException If an error occurs in the database.
   * @throws DirectoryException If a problem occurs while trying to
   *         retrieve the entry.
   */
  public Entry get(AbstractTransaction txn, long eid,
    NdbOperation.LockMode lockMode)
       throws NdbApiException, DirectoryException
  {
    NdbIndexScanOperation indexScanOp = null;
    NdbResultSet rs = null;
    DN dn = null;
    NdbTransaction ndbTxn = txn.getNdbTransaction();
    indexScanOp = ndbTxn.getSelectIndexScanOperation(
      BackendImpl.EID, name, lockMode);
    indexScanOp.setBoundLong(BackendImpl.EID,
            NdbIndexScanOperation.BoundType.BoundEQ, eid);
    for (int i = 0; i < BackendImpl.DN2ID_DN_NC; i++) {
      indexScanOp.getValue(BackendImpl.DN2ID_DN +
        Integer.toString(i));
    }
    indexScanOp.getValue(BackendImpl.DN2ID_OC);
    indexScanOp.getValue(BackendImpl.DN2ID_XOC);
    rs = indexScanOp.resultData();
    ndbTxn.execute(ExecType.NoCommit, AbortOption.AO_IgnoreError, true);
    if (rs.next()) {
      StringBuilder dnBuffer = new StringBuilder();
      int dnColumnIndex = BackendImpl.DN2ID_DN_NC - 1;
      while (dnColumnIndex >= 0) {
        String rdnString = rs.getString(BackendImpl.DN2ID_DN +
          Integer.toString(dnColumnIndex));
        if (rdnString.length() > 0) {
          dnBuffer.append(rdnString);
          if (dnColumnIndex > 0) {
            dnBuffer.append(",");
          }
        }
        dnColumnIndex--;
      }
      String dnString = dnBuffer.toString();
      if (dnString.length() == 0) {
        return null;
      }
      dn = DN.decode(dnString);
      return getNDBEntry(txn, rs, dn, eid);
    } else {
      return null;
    }
  }
  /**
   * Fetch the entry for a given DN.
   * @param txn Abstract transaction to be used for the database read.
   * @param dn The DN for which the entry is desired.
   * @param lockMode NDB locking mode for this operation.
   * @return The entry, or null if the given DN is not in the database.
   * @throws NdbApiException If an error occurs in the database.
   */
  public Entry get(AbstractTransaction txn, DN dn,
    NdbOperation.LockMode lockMode) throws NdbApiException
  {
    NdbOperation op = null;
    NdbResultSet rs = null;
    NdbTransaction ndbTxn = txn.getNdbTransaction();
    op = ndbTxn.getSelectOperation(name, lockMode);
    int componentIndex = dn.getNumComponents() - 1;
    for (int i=0; i < BackendImpl.DN2ID_DN_NC; i++) {
      while (componentIndex >= 0) {
        op.equalString(BackendImpl.DN2ID_DN + Integer.toString(i),
          dn.getRDN(componentIndex).toNormalizedString());
        componentIndex--;
        i++;
      }
      op.equalString(BackendImpl.DN2ID_DN +
        Integer.toString(i), "");
    }
    op.getValue(BackendImpl.EID);
    op.getValue(BackendImpl.DN2ID_OC);
    op.getValue(BackendImpl.DN2ID_XOC);
    rs = op.resultData();
    ndbTxn.execute(ExecType.NoCommit, AbortOption.AO_IgnoreError, true);
    if (rs.next()) {
      long eid = rs.getLong(BackendImpl.EID);
      if (eid == 0) {
        return null;
      }
      Entry entry = getNDBEntry(txn, rs, dn, eid);
      if (entry != null) {
        entry.setAttachment(eid);
      }
      return entry;
    } else {
      return null;
    }
  }
  /**
   * Get the entry from the database.
   * @param txn Abstract transaction to be used for the database read.
   * @param rs NDB results set from the initial get entry operation.
   * @param dn The entry DN.
   * @param id The entry ID.
   * @return The entry.
   * @throws NdbApiException If an error occurs in the database.
   */
  private Entry getNDBEntry(
    AbstractTransaction txn,
    NdbResultSet rs,
    DN dn,
    long eid) throws NdbApiException
  {
    NdbOperation op = null;
    NdbIndexScanOperation indexScanOp = null;
    boolean extensibleObject = false;
    String ocsString = rs.getString(BackendImpl.DN2ID_OC);
    String[] ocsStringArray = ocsString.split(" ");
    String xocsString = rs.getString(BackendImpl.DN2ID_XOC);
    String[] xocsStringArray = xocsString.split(" ");
    if (xocsString.length() > 0) {
      extensibleObject = true;
    }
    LinkedHashMap<ObjectClass, String> xObjectClasses =
      new LinkedHashMap<ObjectClass, String>();
    List<NdbResultSet> ocRsList = new ArrayList<NdbResultSet>();
    Map<String, NdbBlob> blobMap = new HashMap<String, NdbBlob>();
    LinkedHashMap<ObjectClass, String> objectClasses =
      new LinkedHashMap<ObjectClass, String>(ocsStringArray.length);
    NdbTransaction ndbDATxn = null;
    NdbIndexScanOperation tagScanOp = null;
    for (String ocName : ocsStringArray) {
      ObjectClass oc =
        DirectoryServer.getObjectClass(ocName, true);
      objectClasses.put(oc, ocName);
      if (oc.getObjectClassType() == ObjectClassType.ABSTRACT) {
        continue;
      }
      if (ndbDATxn == null) {
        ndbDATxn = txn.getNdbDATransaction(ocName, eid);
      }
      indexScanOp =
        ndbDATxn.getSelectIndexScanOperation(
        PRIMARY_INDEX_NAME, ocName,
        NdbOperation.LockMode.LM_CommittedRead);
      indexScanOp.setBoundLong(BackendImpl.EID,
        NdbIndexScanOperation.BoundType.BoundEQ, eid);
      indexScanOp.getValue(BackendImpl.MID);
      for (AttributeType reqAttr : oc.getRequiredAttributes()) {
        String attrName = reqAttr.getNameOrOID();
        if (BackendImpl.blobAttributes.contains(attrName)) {
          NdbBlob blob = indexScanOp.getBlobHandle(attrName);
          blobMap.put(attrName, blob);
        } else {
          indexScanOp.getValue(attrName);
        }
      }
      for (AttributeType optAttr : oc.getOptionalAttributes()) {
        String attrName = optAttr.getNameOrOID();
        if (BackendImpl.blobAttributes.contains(attrName)) {
          NdbBlob blob = indexScanOp.getBlobHandle(attrName);
          blobMap.put(attrName, blob);
        } else {
          indexScanOp.getValue(attrName);
        }
      }
      ocRsList.add(indexScanOp.resultData());
    }
    // Extensible object.
    if (extensibleObject) {
      for (String xocName : xocsStringArray) {
        ObjectClass xoc =
          DirectoryServer.getObjectClass(xocName, true);
        objectClasses.put(xoc, xocName);
        xObjectClasses.put(xoc, xocName);
        if (xoc.getObjectClassType() == ObjectClassType.ABSTRACT) {
          continue;
        }
        if (ndbDATxn == null) {
          ndbDATxn = txn.getNdbDATransaction(xocName, eid);
        }
        indexScanOp =
          ndbDATxn.getSelectIndexScanOperation(
          PRIMARY_INDEX_NAME, xocName,
          NdbOperation.LockMode.LM_CommittedRead);
        indexScanOp.setBoundLong(BackendImpl.EID,
          NdbIndexScanOperation.BoundType.BoundEQ, eid);
        indexScanOp.getValue(BackendImpl.MID);
        for (AttributeType reqAttr : xoc.getRequiredAttributes()) {
          String attrName = reqAttr.getNameOrOID();
          if (BackendImpl.blobAttributes.contains(attrName)) {
            NdbBlob blob = indexScanOp.getBlobHandle(attrName);
            blobMap.put(attrName, blob);
          } else {
            indexScanOp.getValue(attrName);
          }
        }
        for (AttributeType optAttr : xoc.getOptionalAttributes()) {
          String attrName = optAttr.getNameOrOID();
          if (BackendImpl.blobAttributes.contains(attrName)) {
            NdbBlob blob = indexScanOp.getBlobHandle(attrName);
            blobMap.put(attrName, blob);
          } else {
            indexScanOp.getValue(attrName);
          }
        }
        ocRsList.add(indexScanOp.resultData());
      }
    }
    // Operational attributes.
    op = ndbDATxn.getSelectOperation(BackendImpl.OPATTRS_TABLE,
      NdbOperation.LockMode.LM_CommittedRead);
    op.equalLong(BackendImpl.EID, eid);
    for (String attrName : BackendImpl.operationalAttributes) {
      op.getValue(attrName);
    }
    ocRsList.add(op.resultData());
    // Attribute options.
    tagScanOp = ndbDATxn.getSelectIndexScanOperation(
      PRIMARY_INDEX_NAME,
      BackendImpl.TAGS_TABLE,
      NdbOperation.LockMode.LM_CommittedRead);
    tagScanOp.setBoundLong(BackendImpl.EID,
      NdbIndexScanOperation.BoundType.BoundEQ, eid);
    tagScanOp.getValue(BackendImpl.TAG_ATTR);
    tagScanOp.getValue(BackendImpl.MID);
    tagScanOp.getValue(BackendImpl.TAG_TAGS);
    NdbResultSet tagRs = tagScanOp.resultData();
    ndbDATxn.execute(ExecType.NoCommit, AbortOption.AO_IgnoreError, true);
    return decodeNDBEntry(dn, ocRsList, tagRs, objectClasses,
      xObjectClasses, blobMap, extensibleObject);
  }
  /**
   * Decode the entry from NDB results.
   * @param dn The entry DN.
   * @param ocRsList ObjectClass result sets list.
   * @param tagRs Attribute tags result set.
   * @param objectClasses ObjectClasses map.
   * @param xObjectClasses Extensible ObjectClasses map.
   * @param blobMap Blob attributes map.
   * @param extensibleObject true if the entry is Extensible Object,
   * false otherwise.
   * @return The entry.
   * @throws com.mysql.cluster.ndbj.NdbApiException
   */
  private Entry decodeNDBEntry(
    DN dn,
    List<NdbResultSet> ocRsList,
    NdbResultSet tagRs,
    Map<ObjectClass, String> objectClasses,
    Map<ObjectClass, String> xObjectClasses,
    Map<String, NdbBlob> blobMap,
    boolean extensibleObject) throws NdbApiException
  {
    LinkedHashMap<AttributeType, List<Attribute>> userAttributes =
      new LinkedHashMap<AttributeType, List<Attribute>>();
    LinkedHashMap<AttributeType, List<Attribute>> opAttributes =
      new LinkedHashMap<AttributeType, List<Attribute>>();
    // Attribute options.
    Map<String, Map<Integer, LinkedHashSet<String>>> attr2tagMap =
      new HashMap<String, Map<Integer, LinkedHashSet<String>>>();
    while (tagRs.next()) {
      String attrName = tagRs.getString(BackendImpl.TAG_ATTR);
      int mid = tagRs.getInt(BackendImpl.MID);
      String attrOptions = tagRs.getString(BackendImpl.TAG_TAGS);
      if (!tagRs.wasNull()) {
        int currentIndex = attrOptions.indexOf(';');
        int nextIndex = attrOptions.indexOf(';', currentIndex + 1);
        String option = null;
        Map<Integer, LinkedHashSet<String>> mid2tagMap =
          attr2tagMap.get(attrName);
        if (mid2tagMap == null) {
          mid2tagMap = new HashMap<Integer, LinkedHashSet<String>>();
        }
        LinkedHashSet<String> options = new LinkedHashSet<String>();
        while (nextIndex > 0) {
          option =
            attrOptions.substring(currentIndex + 1, nextIndex);
          if (option.length() > 0) {
            options.add(option);
          }
          currentIndex = nextIndex;
          nextIndex = attrOptions.indexOf(';', currentIndex + 1);
        }
        option = attrOptions.substring(currentIndex + 1);
        if (option.length() > 0) {
          options.add(option);
        }
        mid2tagMap.put(mid, options);
        attr2tagMap.put(attrName, mid2tagMap);
      }
    }
    // Object classes and user atributes.
    Iterator<NdbResultSet> ocRsIterator = ocRsList.iterator();
    NdbResultSet ocRs = ocRsIterator.next();
    AttributeBuilder attrBuilder = new AttributeBuilder();
    for (ObjectClass oc : objectClasses.keySet()) {
      if (oc.getObjectClassType() == ObjectClassType.ABSTRACT) {
        continue;
      }
      while (ocRs.next()) {
        int mid = ocRs.getInt(BackendImpl.MID);
        for (AttributeType reqAttr : oc.getRequiredAttributes()) {
          String attrName = reqAttr.getNameOrOID();
          byte[] attrValBytes = null;
          NdbBlob blob = null;
          if (BackendImpl.blobAttributes.contains(attrName)) {
            blob = blobMap.get(attrName);
          } else {
            attrValBytes = ocRs.getStringBytes(attrName);
            if (ocRs.wasNull()) {
              continue;
            }
          }
          AttributeType attributeType =
            DirectoryServer.getAttributeType(
            BackendImpl.attrName2LC.get(attrName), true);
          List<Attribute> attrList = userAttributes.get(attributeType);
          if (attrList == null) {
            attrList = new ArrayList<Attribute>();
          }
          Attribute attr = null;
          LinkedHashSet<String> options = null;
          Map<Integer, LinkedHashSet<String>> mid2tagMap =
            attr2tagMap.get(attrName);
          if (mid2tagMap != null) {
            options = mid2tagMap.get(mid);
          }
          if ((options == null) && !attrList.isEmpty()) {
            attr = attrList.get(attrList.size() - 1);
          }
          if (attr == null) {
            attrBuilder.setAttributeType(attributeType, attrName);
          } else {
            attrBuilder = new AttributeBuilder(attr);
          }
          if (blob != null) {
            if (blob.getNull()) {
              continue;
            }
            int len = blob.getLength().intValue();
            byte[] buf = new byte[len];
            blob.readData(buf, len);
            attrBuilder.add(AttributeValues.create(attributeType,
              ByteString.wrap(buf)));
          } else {
            attrBuilder.add(AttributeValues.create(attributeType,
              ByteString.wrap(attrValBytes)));
          }
          // Create or update an attribute.
          if (options != null) {
            attrBuilder.setOptions(options);
          }
          attr = attrBuilder.toAttribute();
          if (attrList.isEmpty()) {
            attrList.add(attr);
          } else {
            attrList.set(attrList.size() - 1, attr);
          }
          userAttributes.put(attributeType, attrList);
        }
        for (AttributeType optAttr : oc.getOptionalAttributes()) {
          String attrName = optAttr.getNameOrOID();
          byte[] attrValBytes = null;
          NdbBlob blob = null;
          if (BackendImpl.blobAttributes.contains(attrName)) {
            blob = blobMap.get(attrName);
          } else {
            attrValBytes = ocRs.getStringBytes(attrName);
            if (ocRs.wasNull()) {
              continue;
            }
          }
          AttributeType attributeType =
            DirectoryServer.getAttributeType(
            BackendImpl.attrName2LC.get(attrName), true);
          List<Attribute> attrList = userAttributes.get(attributeType);
          if (attrList == null) {
            attrList = new ArrayList<Attribute>();
          }
          Attribute attr = null;
          LinkedHashSet<String> options = null;
          Map<Integer, LinkedHashSet<String>> mid2tagMap =
            attr2tagMap.get(attrName);
          if (mid2tagMap != null) {
            options = mid2tagMap.get(mid);
          }
          if ((options == null) && !attrList.isEmpty()) {
            attr = attrList.get(attrList.size() - 1);
          }
          if (attr == null) {
            attrBuilder.setAttributeType(attributeType, attrName);
          } else {
            attrBuilder = new AttributeBuilder(attr);
          }
          if (blob != null) {
            if (blob.getNull()) {
              continue;
            }
            int len = blob.getLength().intValue();
            byte[] buf = new byte[len];
            blob.readData(buf, len);
            attrBuilder.add(AttributeValues.create(attributeType,
              ByteString.wrap(buf)));
          } else {
            attrBuilder.add(AttributeValues.create(attributeType,
              ByteString.wrap(attrValBytes)));
          }
          // Create or update an attribute.
          if (options != null) {
            attrBuilder.setOptions(options);
          }
          attr = attrBuilder.toAttribute();
          if (attrList.isEmpty()) {
            attrList.add(attr);
          } else {
            attrList.set(attrList.size() - 1, attr);
          }
          userAttributes.put(attributeType, attrList);
        }
      }
      if (ocRsIterator.hasNext()) {
        ocRs = ocRsIterator.next();
      }
    }
    // Operational attributes.
    if (ocRs.next()) {
      for (String attrName : BackendImpl.operationalAttributes) {
        byte[] attrValBytes = ocRs.getStringBytes(attrName);
        if (ocRs.wasNull()) {
          continue;
        }
        AttributeType attributeType =
          DirectoryServer.getAttributeType(
          BackendImpl.attrName2LC.get(attrName), true);
        attrBuilder.setAttributeType(attributeType, attrName);
        attrBuilder.add(AttributeValues.create(attributeType,
          ByteString.wrap(attrValBytes)));
        Attribute attr = attrBuilder.toAttribute();
        List<Attribute> attrList = opAttributes.get(attributeType);
        if (attrList == null) {
          attrList = new ArrayList<Attribute>();
          attrList.add(attr);
          opAttributes.put(attributeType, attrList);
        } else {
          attrList.add(attr);
        }
      }
    }
    // Extensible object.
    if (extensibleObject) {
      for (ObjectClass oc : xObjectClasses.keySet()) {
        objectClasses.remove(oc);
      }
    }
    Entry entry = new Entry(dn, objectClasses, userAttributes, opAttributes);
    if (entry != null) {
      entry.processVirtualAttributes();
    }
    return entry;
  }
  /**
   * Fetch the entry ID for a given DN.
   * @param txn Abstract transaction to be used for the database read.
   * @param dn The DN for which the entry ID is desired.
   * @param lockMode The lock mode for this operation.
   * @return The entry ID, or zero if the given DN is not in the database.
   * @throws NdbApiException If an error occurs in the database.
   */
  public long getID(AbstractTransaction txn, DN dn,
    NdbOperation.LockMode lockMode)
       throws NdbApiException
  {
    NdbOperation op = null;
    NdbResultSet rs = null;
    long eid = 0;
    NdbTransaction ndbTxn = txn.getNdbTransaction();
    op = ndbTxn.getSelectOperation(name, lockMode);
    int componentIndex = dn.getNumComponents() - 1;
    for (int i=0; i < BackendImpl.DN2ID_DN_NC; i++) {
      while (componentIndex >= 0) {
        op.equalString(BackendImpl.DN2ID_DN + Integer.toString(i),
          dn.getRDN(componentIndex).toNormalizedString());
        componentIndex--;
        i++;
      }
      op.equalString(BackendImpl.DN2ID_DN +
        Integer.toString(i), "");
    }
    op.getValue(BackendImpl.EID);
    rs = op.resultData();
    ndbTxn.execute(ExecType.NoCommit, AbortOption.AO_IgnoreError, true);
    if (rs.next()) {
      eid = rs.getLong(BackendImpl.EID);
    }
    return eid;
  }
  /**
   * Get referrals for a given entry ID.
   * @param txn Abstract transaction to be used for the operation.
   * @param id The ID for which the referral is desired.
   * @return The referral set, or empty set if the entry has no referrals.
   * @throws NdbApiException If an error occurs in the database.
   */
  public Set<String> getReferrals(AbstractTransaction txn, long id)
       throws NdbApiException
  {
    NdbIndexScanOperation op = null;
    NdbResultSet rs = null;
    Set<String> referrals = new HashSet<String>();
    NdbTransaction ndbDATxn =
      txn.getNdbDATransaction(BackendImpl.REFERRALS_TABLE, id);
    op = ndbDATxn.getSelectIndexScanOperation(
      PRIMARY_INDEX_NAME, BackendImpl.REFERRALS_TABLE,
      NdbOperation.LockMode.LM_CommittedRead);
    op.setBoundLong(BackendImpl.EID,
            NdbIndexScanOperation.BoundType.BoundEQ, id);
    op.getValue(ATTR_REFERRAL_URL);
    rs = op.resultData();
    ndbDATxn.execute(ExecType.NoCommit, AbortOption.AO_IgnoreError, true);
    while (rs.next()) {
      String referral = rs.getString(ATTR_REFERRAL_URL);
      if (rs.wasNull() || (referral.length() == 0)) {
        break;
      }
      referrals.add(referral);
    }
    return referrals;
  }
  /**
   * Get the count of rows in the database.
   * @return The number of rows in the database.
   * @throws NdbApiException If an error occurs in the database.
   */
  public long getRecordCount() throws NdbApiException
  {
    Ndb ndb = entryContainer.getRootContainer().getNDB();
    try {
      return ndb.selectCount(name);
    } finally {
      if (ndb != null) {
        entryContainer.getRootContainer().releaseNDB(ndb);
      }
    }
  }
  /**
   * Determine if the entry has subordinate entries.
   * @param txn Abstract transaction to be used for the operation.
   * @param dn The entry DN.
   * @return true if the entry has subordinates, false otherwise.
   * @throws com.mysql.cluster.ndbj.NdbApiException If an error
   * occurs in the database.
   */
  public boolean hasSubordinates(AbstractTransaction txn, DN dn)
    throws NdbApiException
  {
    // NdbInterpretedOperation op;
    NdbIndexScanOperation op;
    NdbResultSet rs;
    NdbTransaction ndbTxn = txn.getNdbTransaction();
    op = ndbTxn.getSelectIndexScanOperation(
      PRIMARY_INDEX_NAME, name,
      NdbOperation.LockMode.LM_Read);
    int numComponents = dn.getNumComponents();
    int componentIndex = numComponents - 1;
    for (int i=0; i < numComponents; i++) {
      op.setBoundString(BackendImpl.DN2ID_DN +
        Integer.toString(i),
        NdbIndexScanOperation.BoundType.BoundEQ,
        dn.getRDN(componentIndex).toNormalizedString());
      componentIndex--;
    }
    if (dn.getNumComponents() < BackendImpl.DN2ID_DN_NC) {
      String nextRDNColumn =
        BackendImpl.DN2ID_DN + Integer.toString(numComponents);
      op.setBoundString(nextRDNColumn,
        NdbIndexScanOperation.BoundType.BoundLT, "");
    }
    // FIXME: This is extremely inefficient, need NDB/J API
    // like interpretExitLastRow to count result rows node-
    // side without returning them here for check/count.
    // op.interpretExitLastRow();
    op.getValue(BackendImpl.EID);
    rs = op.resultData();
    ndbTxn.execute(ExecType.NoCommit, AbortOption.AO_IgnoreError, true);
    if (rs.next()) {
      return true;
    }
    return false;
  }
  /**
   * Get a new instance of the Search Cursor object.
   * @param txn Abstract Transaction to be used for the operation.
   * @param baseDN Search Cursor base DN.
   * @return New instance of the Search Cursor object.
   */
  public DN2IDSearchCursor getSearchCursor(
    AbstractTransaction txn, DN baseDN) {
    return new DN2IDSearchCursor(txn, baseDN);
  }
  /**
   * This inner class represents the Search Cursor which can be
   * used to cursor entries in the database starting from some
   * arbitrary base DN.
   */
  protected class DN2IDSearchCursor
  {
    private NdbIndexScanOperation op;
    private NdbResultSet rs;
    private NdbTransaction ndbTxn;
    private AbstractTransaction txn;
    private DN baseDN;
    /**
     * Object constructor.
     * @param txn Abstract Transaction to be used for the operation.
     * @param baseDN Search Cursor base DN.
     */
    public DN2IDSearchCursor(
      AbstractTransaction txn,
      DN baseDN)
    {
      this.txn = txn;
      this.baseDN = baseDN;
    }
    /**
     * Open the cursor.
     * @throws com.mysql.cluster.ndbj.NdbApiException If an error
     * occurs in the database.
     */
    public void open() throws NdbApiException
    {
      ndbTxn = txn.getNdbTransaction();
      op = ndbTxn.getSelectIndexScanOperation(
        PRIMARY_INDEX_NAME, name, NdbOperation.LockMode.LM_CommittedRead);
      int numComponents = baseDN.getNumComponents();
      int componentIndex = numComponents - 1;
      for (int i = 0; i < numComponents; i++) {
        op.setBoundString(BackendImpl.DN2ID_DN +
          Integer.toString(i),
          NdbIndexScanOperation.BoundType.BoundEQ,
          baseDN.getRDN(componentIndex).toNormalizedString());
        componentIndex--;
      }
      if (baseDN.getNumComponents() < BackendImpl.DN2ID_DN_NC) {
        String nextRDNColumn =
          BackendImpl.DN2ID_DN + Integer.toString(numComponents);
        op.setBoundString(nextRDNColumn,
          NdbIndexScanOperation.BoundType.BoundLT, "");
      }
      op.getValue(BackendImpl.EID);
      for (int i = 0; i < BackendImpl.DN2ID_DN_NC; i++) {
        op.getValue(BackendImpl.DN2ID_DN +
          Integer.toString(i));
      }
      rs = op.resultData();
      ndbTxn.execute(ExecType.NoCommit, AbortOption.AbortOnError, true);
    }
    /**
     * Advance one position and return the result.
     * @return An instance of Search Cursor Result.
     * @throws com.mysql.cluster.ndbj.NdbApiException If an error
     * occurs in the database.
     */
    public SearchCursorResult getNext() throws NdbApiException
    {
      if (rs.next()) {
        long eid = rs.getLong(BackendImpl.EID);
        StringBuilder dnBuffer = new StringBuilder();
        int dnColumnIndex = BackendImpl.DN2ID_DN_NC - 1;
        while (dnColumnIndex >= 0) {
          String rdnString = rs.getString(BackendImpl.DN2ID_DN +
            Integer.toString(dnColumnIndex));
          if (rdnString.length() > 0) {
            dnBuffer.append(rdnString);
            if (dnColumnIndex > 0) {
              dnBuffer.append(",");
            }
          }
          dnColumnIndex--;
        }
        String dnString = dnBuffer.toString();
        if ((eid == 0) || (dnString.length() == 0)) {
          return null;
        }
        SearchCursorResult result =
          new SearchCursorResult(dnString, eid);
        return result;
      }
      return null;
    }
    /**
     * Close the cursor.
     */
    public void close()
    {
      ndbTxn = null;
      txn = null;
    }
  }
  /**
   * This inner class represents a Search Cursor Result
   * as returned by the Search Cursor operations.
   */
  protected class SearchCursorResult
  {
    /**
     * Entry DN.
     */
    public String dn;
    /**
     * Entry ID.
     */
    public long id;
    /**
     * Object constructor.
     * @param dn The entry DN.
     * @param id The entry ID.
     */
    public SearchCursorResult(String dn, long id)
    {
      this.dn = dn;
      this.id = id;
    }
  }
}
opends/src/server/org/opends/server/backends/ndb/RootContainer.java
New file
@@ -0,0 +1,499 @@
/*
 * 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/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * 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/opends/resource/legal-notices/OpenDS.LICENSE.  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.
 */
package org.opends.server.backends.ndb;
import com.mysql.cluster.ndbj.Ndb;
import com.mysql.cluster.ndbj.NdbApiException;
import com.mysql.cluster.ndbj.NdbApiTemporaryException;
import com.mysql.cluster.ndbj.NdbClusterConnection;
import org.opends.messages.Message;
import java.util.concurrent.ConcurrentHashMap;
import java.util.*;
import java.util.concurrent.LinkedBlockingQueue;
import org.opends.server.types.DN;
import org.opends.server.types.ConfigChangeResult;
import org.opends.server.types.ResultCode;
import org.opends.server.api.Backend;
import org.opends.server.admin.server.ConfigurationChangeListener;
import org.opends.server.admin.std.server.NdbBackendCfg;
import org.opends.server.config.ConfigException;
import static org.opends.server.loggers.ErrorLogger.logError;
import static org.opends.server.loggers.debug.DebugLogger.*;
import static org.opends.messages.NdbMessages.*;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.types.DebugLogLevel;
/**
 * Root container holds all the entry containers for each base DN.
 * It also maintains all the openings and closings of the entry
 * containers.
 */
public class RootContainer
     implements ConfigurationChangeListener<NdbBackendCfg>
{
  /**
   * The tracer object for the debug logger.
   */
  private static final DebugTracer TRACER = getTracer();
  /**
   * The backend configuration.
   */
  private NdbBackendCfg config;
  /**
   * The backend to which this entry root container belongs.
   */
  private Backend backend;
  /**
   * The base DNs contained in this entryContainer.
   */
  private ConcurrentHashMap<DN, EntryContainer> entryContainers;
  /**
   * NDB connection objects.
   */
  private static NdbClusterConnection[] ndbConns;
  /**
   * NDB handle objects.
   */
  private static LinkedBlockingQueue<Ndb> ndbQueue;
  /**
   * NDB thread count.
   */
  private int ndbThreadCount;
  /**
   * NDB number of connections.
   */
  private int ndbNumConnections;
  /**
   * The range to use when requesting next ID.
   */
  private static final long NDB_NEXTID_RANGE = 1000;
  /**
   * The maximum number of NDB threads.
   */
  private static final int NDB_MAX_THREAD_COUNT = 128;
  /**
   * Timeout for the first node/group to become ready.
   */
  private static final int NDB_TIMEOUT_FIRST_ALIVE = 60;
  /**
   * Timeout for the rest of nodes/groups to become ready.
   */
  private static final int NDB_TIMEOUT_AFTER_FIRST_ALIVE = 60;
  /**
   * Creates a new RootContainer object.
   *
   * @param config The configuration of the NDB backend.
   * @param backend A reference to the NDB backend that is creating this
   *                root container.
   */
  public RootContainer(Backend backend, NdbBackendCfg config)
  {
    this.entryContainers = new ConcurrentHashMap<DN, EntryContainer>();
    this.backend = backend;
    this.config = config;
    this.ndbNumConnections = this.config.getNdbNumConnections();
    this.ndbConns = new NdbClusterConnection[ndbNumConnections];
    this.ndbThreadCount = this.config.getNdbThreadCount();
    if (this.ndbThreadCount > NDB_MAX_THREAD_COUNT) {
      this.ndbThreadCount = NDB_MAX_THREAD_COUNT;
    }
    this.ndbQueue = new LinkedBlockingQueue<Ndb>(
      this.ndbThreadCount);
    config.addNdbChangeListener(this);
  }
  /**
   * Opens the root container using the NDB configuration object provided.
   *
   * @throws NdbApiException If an error occurs when opening.
   * @throws ConfigException If an configuration error occurs while opening.
   * @throws Exception If an unknown error occurs when opening.
   */
  public void open()
      throws NdbApiException, ConfigException, Exception
  {
    // Log a message indicating upcoming NDB connect.
    logError(NOTE_NDB_WAITING_FOR_CLUSTER.get());
    // Connect to the cluster.
    for (int i = 0; i < this.ndbNumConnections; i++) {
      try {
        this.ndbConns[i] = NdbClusterConnection.create(
          this.config.getNdbConnectString());
        this.ndbConns[i].connect(5, 3, true);
        this.ndbConns[i].waitUntilReady(NDB_TIMEOUT_FIRST_ALIVE,
          NDB_TIMEOUT_AFTER_FIRST_ALIVE);
      } catch (NdbApiTemporaryException e) {
        if (debugEnabled()) {
          TRACER.debugCaught(DebugLogLevel.ERROR, e);
        }
        // Retry.
        if (this.ndbConns[i] != null) {
          this.ndbConns[i].close();
          this.ndbConns[i] = null;
        }
        i--;
        continue;
      }
    }
    // Get NDB objects.
    int connsIndex = 0;
    for (int i = 0; i < this.ndbThreadCount; i++) {
      Ndb ndb = ndbConns[connsIndex].createNdb(
        BackendImpl.DATABASE_NAME, 1024);
      connsIndex++;
      if (connsIndex >= this.ndbNumConnections) {
        connsIndex = 0;
      }
      try {
        this.ndbQueue.put(ndb);
      } catch (Exception e) {
        if (debugEnabled())
        {
          TRACER.debugCaught(DebugLogLevel.ERROR, e);
        }
        if (ndb != null) {
          ndb.close();
        }
      }
    }
    openAndRegisterEntryContainers(config.getBaseDN());
  }
  /**
   * Opens the entry container for a base DN. If the entry container does not
   * exist for the base DN, it will be created. The entry container will be
   * opened with the same mode as the root container. Any entry containers
   * opened in a read only root container will also be read only. Any entry
   * containers opened in a non transactional root container will also be non
   * transactional.
   *
   * @param baseDN The base DN of the entry container to open.
   * @return The opened entry container.
   * @throws NdbApiException If an error occurs while opening the entry
   *                           container.
   * @throws ConfigException If an configuration error occurs while opening
   *                         the entry container.
   */
  public EntryContainer openEntryContainer(DN baseDN)
      throws NdbApiException, ConfigException
  {
    String databasePrefix = baseDN.toNormalizedString();
    EntryContainer ec = new EntryContainer(baseDN, databasePrefix,
                                           backend, config, this);
    ec.open();
    return ec;
  }
  /**
   * Registeres the entry container for a base DN.
   *
   * @param baseDN The base DN of the entry container to register.
   * @param entryContainer The entry container to register for the baseDN.
   * @throws Exception If an error occurs while registering the entry
   *                           container.
   */
  public void registerEntryContainer(DN baseDN,
                                     EntryContainer entryContainer)
      throws Exception
  {
    EntryContainer ec1 = this.entryContainers.get(baseDN);
    // If an entry container for this baseDN is already open we don't allow
    // another to be opened.
    if (ec1 != null)
      // FIXME: Should be NDBException instance.
      throw new Exception("An entry container named " +
          ec1.getDatabasePrefix() + " is alreadly registered for base DN " +
          baseDN.toString());
    this.entryContainers.put(baseDN, entryContainer);
  }
  /**
   * Opens the entry containers for multiple base DNs.
   *
   * @param baseDNs The base DNs of the entry containers to open.
   * @throws NdbApiException If an error occurs while opening the entry
   *                           container.
   * @throws ConfigException if a configuration error occurs while opening the
   *                         container.
   */
  private void openAndRegisterEntryContainers(Set<DN> baseDNs)
      throws NdbApiException, ConfigException, Exception
  {
    for(DN baseDN : baseDNs)
    {
      EntryContainer ec = openEntryContainer(baseDN);
      registerEntryContainer(baseDN, ec);
    }
  }
  /**
   * Unregisteres the entry container for a base DN.
   *
   * @param baseDN The base DN of the entry container to close.
   * @return The entry container that was unregistered or NULL if a entry
   * container for the base DN was not registered.
   */
  public EntryContainer unregisterEntryContainer(DN baseDN)
  {
    return entryContainers.remove(baseDN);
  }
  /**
   * Close the root entryContainer.
   *
   * @throws NdbApiException If an error occurs while attempting to close
   * the entryContainer.
   */
  public void close() throws NdbApiException
  {
    for(DN baseDN : entryContainers.keySet())
    {
      EntryContainer ec = unregisterEntryContainer(baseDN);
      ec.exclusiveLock.lock();
      try
      {
        ec.close();
      }
      finally
      {
        ec.exclusiveLock.unlock();
      }
    }
    while (!this.ndbQueue.isEmpty()) {
      Ndb ndb = null;
      try {
        ndb = this.ndbQueue.poll();
        if (ndb != null) {
          ndb.close();
        }
      } catch (Exception e) {
        if (debugEnabled())
        {
          TRACER.debugCaught(DebugLogLevel.ERROR, e);
        }
      }
    }
    this.ndbQueue.clear();
    for (NdbClusterConnection ndbConn : ndbConns) {
      ndbConn.close();
    }
    config.removeNdbChangeListener(this);
  }
  /**
   * Get NDB handle from the queue.
   * @return NDB handle.
   */
  protected Ndb getNDB()
  {
    try {
      return ndbQueue.take();
    } catch (Exception e) {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
      return null;
    }
  }
  /**
   * Release NDB handle to the queue.
   * @param ndb handle to release.
   */
  protected void releaseNDB(Ndb ndb)
  {
    try {
      ndbQueue.put(ndb);
    } catch (Exception e) {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
      if (ndb != null) {
        ndb.close();
      }
      return;
    }
  }
  /**
   * Return all the entry containers in this root container.
   *
   * @return The entry containers in this root container.
   */
  public Collection<EntryContainer> getEntryContainers()
  {
    return entryContainers.values();
  }
  /**
   * Returns all the baseDNs this root container stores.
   *
   * @return The set of DNs this root container stores.
   */
  public Set<DN> getBaseDNs()
  {
    return entryContainers.keySet();
  }
  /**
   * Return the entry container for a specific base DN.
   *
   * @param baseDN The base DN of the entry container to retrive.
   * @return The entry container for the base DN.
   */
  public EntryContainer getEntryContainer(DN baseDN)
  {
    EntryContainer ec = null;
    DN nodeDN = baseDN;
    while (ec == null && nodeDN != null)
    {
      ec = entryContainers.get(nodeDN);
      if (ec == null)
      {
        nodeDN = nodeDN.getParentDNInSuffix();
      }
    }
    return ec;
  }
  /**
   * Get the backend configuration used by this root container.
   *
   * @return The NDB backend configuration used by this root container.
   */
  public NdbBackendCfg getConfiguration()
  {
    return config;
  }
  /**
   * Get the total number of entries in this root container.
   *
   * @return The number of entries in this root container
   * @throws NdbApiException If an error occurs while retrieving the entry
   *                           count.
   */
  public long getEntryCount() throws NdbApiException
  {
    long entryCount = 0;
    for(EntryContainer ec : this.entryContainers.values())
    {
      ec.sharedLock.lock();
      try
      {
        entryCount += ec.getEntryCount();
      }
      finally
      {
        ec.sharedLock.unlock();
      }
    }
    return entryCount;
  }
  /**
   * Assign the next entry ID.
   * @param ndb Ndb handle.
   * @return The assigned entry ID.
   */
  public long getNextEntryID(Ndb ndb)
  {
    long eid = 0;
    try
    {
      eid = ndb.getAutoIncrementValue(BackendImpl.NEXTID_TABLE,
        NDB_NEXTID_RANGE);
    }
    catch (NdbApiException e)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
    }
    return eid;
  }
  /**
   * {@inheritDoc}
   */
  public boolean isConfigurationChangeAcceptable(
      NdbBackendCfg cfg,
      List<Message> unacceptableReasons)
  {
    boolean acceptable = true;
    return acceptable;
  }
  /**
   * {@inheritDoc}
   */
  public ConfigChangeResult applyConfigurationChange(NdbBackendCfg cfg)
  {
    ConfigChangeResult ccr;
    boolean adminActionRequired = false;
    ArrayList<Message> messages = new ArrayList<Message>();
    ccr = new ConfigChangeResult(ResultCode.SUCCESS, adminActionRequired,
                                 messages);
    return ccr;
  }
}
opends/src/server/org/opends/server/backends/ndb/importLDIF/DNContext.java
New file
@@ -0,0 +1,363 @@
/*
 * 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/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * 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/opends/resource/legal-notices/OpenDS.LICENSE.  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.
 */
package org.opends.server.backends.ndb.importLDIF;
import org.opends.server.types.DN;
import org.opends.server.types.LDIFImportConfig;
import org.opends.server.util.LDIFReader;
import org.opends.server.backends.ndb.*;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.*;
import org.opends.server.admin.std.server.NdbBackendCfg;
/**
 * This class represents the import context for a destination base DN.
 */
public class DNContext {
  /**
   * The destination base DN.
   */
  private DN baseDN;
  /**
   * The include branches below the base DN.
   */
  private List<DN> includeBranches;
  /**
   * The exclude branches below the base DN.
   */
  private List<DN> excludeBranches;
  /**
   * The configuration of the destination backend.
   */
  private NdbBackendCfg config;
  /**
   * The requested LDIF import configuration.
   */
  private LDIFImportConfig ldifImportConfig;
  /**
   * A reader for the source LDIF file.
   */
  private LDIFReader ldifReader;
  /**
   * The entry entryContainer for the destination base DN.
   */
  private EntryContainer entryContainer;
  /**
   * The source entryContainer if this is a partial import of a base DN.
   */
  private EntryContainer srcEntryContainer;
  /**
   * A queue of elements that have been read from the LDIF and are ready
   * to be imported.
   */
  private BlockingQueue<WorkElement> workQueue;
  /**
   * Map of pending DNs added to the work queue. Used to check if a parent
   * entry has been added, but isn't in the database.
   */
  private ConcurrentHashMap<DN, DN> pendingMap =
    new ConcurrentHashMap<DN, DN>() ;
  /**
   * The number of LDAP entries added to the database, used to update the
   * entry database record count after import.  The value is not updated
   * for replaced entries.  Multiple threads may be updating this value.
   */
  private AtomicLong entryInsertCount = new AtomicLong(0);
  /**
   * The parent DN of the previous imported entry.
   */
  private DN parentDN;
  /**
   * Get the work queue.
   *
   * @return  The work queue.
   */
  public BlockingQueue<WorkElement> getWorkQueue() {
      return workQueue;
    }
  /**
   * Set the work queue to the specified work queue.
   *
   * @param workQueue The work queue.
   */
  public void
   setWorkQueue(BlockingQueue<WorkElement> workQueue) {
    this.workQueue = workQueue;
  }
  /**
   * Set the destination base DN.
   * @param baseDN The destination base DN.
   */
  public void setBaseDN(DN baseDN)
  {
    this.baseDN = baseDN;
  }
  /**
   * Get the destination base DN.
   * @return The destination base DN.
   */
  public DN getBaseDN()
  {
    return baseDN;
  }
  /**
   * Set the configuration of the destination backend.
   * @param config The destination backend configuration.
   */
  public void setConfig(NdbBackendCfg config)
  {
    this.config = config;
  }
  /**
   * Get the configuration of the destination backend.
   * @return The destination backend configuration.
   */
  public NdbBackendCfg getConfig()
  {
    return config;
  }
  /**
   * Set the requested LDIF import configuration.
   * @param ldifImportConfig The LDIF import configuration.
   */
  public void setLDIFImportConfig(LDIFImportConfig ldifImportConfig)
  {
    this.ldifImportConfig = ldifImportConfig;
  }
  /**
   * Get the requested LDIF import configuration.
   * @return The requested LDIF import configuration.
   */
  public LDIFImportConfig getLDIFImportConfig()
  {
    return ldifImportConfig;
  }
  /**
   * Set the source LDIF reader.
   * @param ldifReader The source LDIF reader.
   */
  public void setLDIFReader(LDIFReader ldifReader)
  {
    this.ldifReader = ldifReader;
  }
  /**
   * Get the source LDIF reader.
   * @return The source LDIF reader.
   */
  public LDIFReader getLDIFReader()
  {
    return ldifReader;
  }
  /**
   * Set the entry entryContainer for the destination base DN.
   * @param entryContainer The entry entryContainer for the destination base DN.
   */
  public void setEntryContainer(EntryContainer entryContainer)
  {
    this.entryContainer = entryContainer;
  }
  /**
   * Get the entry entryContainer for the destination base DN.
   * @return The entry entryContainer for the destination base DN.
   */
  public EntryContainer getEntryContainer()
  {
    return entryContainer;
  }
  /**
   * Set the source entry entryContainer for the destination base DN.
   * @param srcEntryContainer The entry source entryContainer for the
   * destination base DN.
   */
  public void setSrcEntryContainer(EntryContainer srcEntryContainer)
  {
    this.srcEntryContainer = srcEntryContainer;
  }
  /**
   * Get the source entry entryContainer for the destination base DN.
   * @return The source entry entryContainer for the destination base DN.
   */
  public EntryContainer getSrcEntryContainer()
  {
    return srcEntryContainer;
  }
  /**
   * Get the number of new LDAP entries imported into the entry database.
   * @return The number of new LDAP entries imported into the entry database.
   */
  public long getEntryInsertCount()
  {
    return entryInsertCount.get();
  }
  /**
   * Increment the number of new LDAP entries imported into the entry database
   * by the given amount.
   * @param delta The amount to add.
   */
  public void incrEntryInsertCount(long delta)
  {
    entryInsertCount.getAndAdd(delta);
  }
  /**
   * Get the parent DN of the previous imported entry.
   * @return The parent DN of the previous imported entry.
   */
  public DN getParentDN()
  {
    return parentDN;
  }
  /**
   * Set the parent DN of the previous imported entry.
   * @param parentDN The parent DN of the previous imported entry.
   */
  public void setParentDN(DN parentDN)
  {
    this.parentDN = parentDN;
  }
  /**
   * Retrieves the set of base DNs that specify the set of entries to
   * exclude from the import.  The contents of the returned list may
   * be altered by the caller.
   *
   * @return  The set of base DNs that specify the set of entries to
   *          exclude from the import.
   */
  public List<DN> getExcludeBranches() {
    return excludeBranches;
  }
  /**
   * Specifies the set of base DNs that specify the set of entries to
   * exclude from the import.
   *
   * @param  excludeBranches  The set of base DNs that specify the set
   *                          of entries to exclude from the import.
   */
  public void setExcludeBranches(List<DN> excludeBranches) {
    if (excludeBranches == null) {
      this.excludeBranches = new ArrayList<DN>(0);
    } else {
      this.excludeBranches = excludeBranches;
    }
  }
  /**
   * Retrieves the set of base DNs that specify the set of entries to
   * include in the import.  The contents of the returned list may be
   * altered by the caller.
   *
   * @return  The set of base DNs that specify the set of entries to
   *          include in the import.
   */
  public List<DN> getIncludeBranches() {
    return includeBranches;
  }
  /**
   * Specifies the set of base DNs that specify the set of entries to
   * include in the import.
   *
   * @param  includeBranches  The set of base DNs that specify the set
   *                          of entries to include in the import.
   */
  public void setIncludeBranches(List<DN> includeBranches) {
    if (includeBranches == null) {
      this.includeBranches = new ArrayList<DN>(0);
    } else {
      this.includeBranches = includeBranches;
    }
  }
  /**
   * Check if the parent DN is in the pending map.
   *
   * @param parentDN The DN of the parent.
   * @return <CODE>True</CODE> if the parent is in the pending map.
   */
  public boolean isPending(DN parentDN) {
    boolean ret = false;
    if (pendingMap.containsKey(parentDN)) {
      ret = true;
    }
    return ret;
  }
  /**
   * Add specified DN to the pending map.
   *
   * @param dn The DN to add to the map.
   */
  public void addPending(DN dn) {
    pendingMap.putIfAbsent(dn, dn);
  }
  /**
   * Remove the specified DN from the pending map.
   *
   * @param dn The DN to remove from the map.
   */
  public void removePending(DN dn) {
    pendingMap.remove(dn);
  }
}
opends/src/server/org/opends/server/backends/ndb/importLDIF/Importer.java
New file
@@ -0,0 +1,643 @@
/*
 * 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/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * 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/opends/resource/legal-notices/OpenDS.LICENSE.  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.
 */
package org.opends.server.backends.ndb.importLDIF;
import org.opends.server.types.*;
import org.opends.server.loggers.debug.DebugTracer;
import static org.opends.server.loggers.debug.DebugLogger.getTracer;
import static org.opends.server.loggers.debug.DebugLogger.debugEnabled;
import static org.opends.server.loggers.ErrorLogger.logError;
import org.opends.server.util.LDIFReader;
import org.opends.server.util.StaticUtils;
import org.opends.server.util.LDIFException;
import org.opends.server.util.RuntimeInformation;
import static org.opends.server.util.DynamicConstants.BUILD_ID;
import static org.opends.server.util.DynamicConstants.REVISION_NUMBER;
import org.opends.server.config.ConfigException;
import org.opends.server.core.DirectoryServer;
import org.opends.server.backends.ndb.*;
import org.opends.messages.Message;
import org.opends.messages.NdbMessages;
import static org.opends.messages.NdbMessages.*;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.*;
import java.io.IOException;
import org.opends.server.admin.std.server.NdbBackendCfg;
/**
 * Performs a LDIF import.
 */
public class Importer implements Thread.UncaughtExceptionHandler {
  /**
   * The tracer object for the debug logger.
   */
  private static final DebugTracer TRACER = getTracer();
  /**
   * The NDB backend configuration.
   */
  private NdbBackendCfg config;
  /**
   * The root container used for this import job.
   */
  private RootContainer rootContainer;
  /**
   * The LDIF import configuration.
   */
  private LDIFImportConfig ldifImportConfig;
  /**
   * The LDIF reader.
   */
  private LDIFReader reader;
  /**
   * Map of base DNs to their import context.
   */
  private LinkedHashMap<DN, DNContext> importMap =
      new LinkedHashMap<DN, DNContext>();
  /**
   * The number of entries migrated.
   */
  private int migratedCount;
  /**
   * The number of entries imported.
   */
  private int importedCount;
  /**
   * The number of milliseconds between job progress reports.
   */
  private long progressInterval = 10000;
  /**
   * The progress report timer.
   */
  private Timer timer;
  // Thread array.
  private CopyOnWriteArrayList<WorkThread> threads;
  // Progress task.
  private ProgressTask pTask;
  // A thread threw an Runtime exception stop the import.
  private boolean unCaughtExceptionThrown = false;
  /**
   * Create a new import job with the specified ldif import config.
   *
   * @param ldifImportConfig The LDIF import config.
   */
  public Importer(LDIFImportConfig ldifImportConfig)
  {
    this.ldifImportConfig = ldifImportConfig;
    this.threads = new CopyOnWriteArrayList<WorkThread>();
  }
  /**
   * Start the worker threads.
   */
  private void startWorkerThreads() {
    int importThreadCount = config.getImportThreadCount();
    // Create one set of worker threads/buffer managers for each base DN.
    for (DNContext context : importMap.values()) {
      for (int i = 0; i < importThreadCount; i++) {
        WorkThread t =
          new WorkThread(context.getWorkQueue(), i, rootContainer);
        t.setUncaughtExceptionHandler(this);
        threads.add(t);
        t.start();
      }
    }
    // Start a timer for the progress report.
    timer = new Timer();
    TimerTask progressTask = new ProgressTask();
    pTask = (ProgressTask) progressTask;
    timer.scheduleAtFixedRate(progressTask, progressInterval,
                              progressInterval);
  }
  /**
   * Import a ldif using the specified root container.
   *
   * @param rootContainer  The root container.
   * @return A LDIF result.
   * @throws IOException If a IO error occurs.
   * @throws NDBException If a NDB error occurs.
   * @throws ConfigException If a configuration has an error.
   */
  public LDIFImportResult processImport(RootContainer rootContainer)
    throws IOException, ConfigException, NDBException {
    // Create an LDIF reader. Throws an exception if the file does not exist.
    reader = new LDIFReader(ldifImportConfig);
    this.rootContainer = rootContainer;
    this.config = rootContainer.getConfiguration();
    Message message;
    long startTime;
    try {
      int importThreadCount = config.getImportThreadCount();
      message = NOTE_NDB_IMPORT_STARTING.get(DirectoryServer.getVersionString(),
                                                     BUILD_ID, REVISION_NUMBER);
      logError(message);
      message = NOTE_NDB_IMPORT_THREAD_COUNT.get(importThreadCount);
      logError(message);
      RuntimeInformation.logInfo();
      for (EntryContainer entryContainer : rootContainer.getEntryContainers()) {
        DNContext DNContext =  getImportContext(entryContainer);
        if(DNContext != null) {
          importMap.put(entryContainer.getBaseDN(), DNContext);
        }
      }
      // Make a note of the time we started.
      startTime = System.currentTimeMillis();
      startWorkerThreads();
      try {
        importedCount = 0;
        processLDIF();
      } finally {
        if(!unCaughtExceptionThrown) {
          cleanUp();
        }
      }
    }
    finally {
      reader.close();
    }
    importProlog(startTime);
    return new LDIFImportResult(reader.getEntriesRead(),
                                reader.getEntriesRejected(),
                                reader.getEntriesIgnored());
  }
  /**
   * Create and log messages at the end of the successful import.
   *
   * @param startTime The time the import started.
   */
  private void importProlog(long startTime) {
    Message message;
    long finishTime = System.currentTimeMillis();
    long importTime = (finishTime - startTime);
    float rate = 0;
    if (importTime > 0)
    {
      rate = 1000f*importedCount / importTime;
    }
    message = NOTE_NDB_IMPORT_FINAL_STATUS.
        get(reader.getEntriesRead(), importedCount,
            reader.getEntriesIgnored(), reader.getEntriesRejected(),
            migratedCount, importTime/1000, rate);
    logError(message);
  }
  /**
   * Process a LDIF reader.
   *
   * @throws NDBException If a NDB problem occurs.
   */
  private void
  processLDIF() throws NDBException {
    Message message = NOTE_NDB_IMPORT_LDIF_START.get();
    logError(message);
    do {
      if (ldifImportConfig.isCancelled()) {
        break;
      }
      if(threads.size() <= 0) {
        message = ERR_NDB_IMPORT_NO_WORKER_THREADS.get();
        throw new NDBException(message);
      }
      if(unCaughtExceptionThrown) {
        abortImport();
      }
      try {
        // Read the next entry.
        Entry entry = reader.readEntry();
        // Check for end of file.
        if (entry == null) {
          message = NOTE_NDB_IMPORT_LDIF_END.get();
          logError(message);
          break;
        }
        // Route it according to base DN.
        DNContext DNContext = getImportConfig(entry.getDN());
        processEntry(DNContext, entry);
      }  catch (LDIFException e) {
        if (debugEnabled()) {
          TRACER.debugCaught(DebugLogLevel.ERROR, e);
        }
      } catch (DirectoryException e) {
        if (debugEnabled()) {
          TRACER.debugCaught(DebugLogLevel.ERROR, e);
        }
      } catch (Exception e)  {
        if (debugEnabled()) {
          TRACER.debugCaught(DebugLogLevel.ERROR, e);
        }
      }
    } while (true);
  }
  /**
   * Process an entry using the specified import context.
   *
   * @param DNContext The import context.
   * @param entry The entry to process.
   */
  private void processEntry(DNContext DNContext, Entry entry) {
    //Add this DN to the pending map.
    DNContext.addPending(entry.getDN());
    addEntryQueue(DNContext, entry);
  }
  /**
   * Add work item to specified import context's queue.
   * @param context The import context.
   * @param item The work item to add.
   * @return <CODE>True</CODE> if the the work  item was added to the queue.
   */
  private boolean
  addQueue(DNContext context, WorkElement item) {
    try {
      while(!context.getWorkQueue().offer(item, 1000,
                                            TimeUnit.MILLISECONDS)) {
        if(threads.size() <= 0) {
          // All worker threads died. We must stop now.
          return false;
        }
      }
    } catch (InterruptedException e) {
      if (debugEnabled()) {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
    }
    return true;
  }
  /**
   * Wait until the work queue is empty.
   */
  private void drainWorkQueue() {
    if(threads.size() > 0) {
      for (DNContext context : importMap.values()) {
        while (context.getWorkQueue().size() > 0) {
          try {
            Thread.sleep(100);
          } catch (Exception e) {
            // No action needed.
          }
        }
      }
    }
  }
  /**
   * Abort import.
   * @throws org.opends.server.backends.ndb.NDBException
   */
  private void abortImport() throws NDBException {
     // Stop work threads telling them to skip substring flush.
     stopWorkThreads(false);
     timer.cancel();
     Message message = ERR_NDB_IMPORT_LDIF_ABORT.get();
     throw new NDBException(message);
  }
  /**
   * Stop work threads.
   *
   * @param abort <CODE>True</CODE> if stop work threads was called from an
   *              abort.
   * @throws NDBException if a NDB error occurs.
   */
  private void
  stopWorkThreads(boolean abort) throws NDBException {
    for (WorkThread t : threads) {
      t.stopProcessing();
    }
    // Wait for each thread to stop.
    for (WorkThread t : threads) {
      try {
        if(!abort && unCaughtExceptionThrown) {
          timer.cancel();
          Message message = ERR_NDB_IMPORT_LDIF_ABORT.get();
          throw new NDBException(message);
        }
        t.join();
        importedCount += t.getImportedCount();
      } catch (InterruptedException ie) {
        // No action needed?
      }
    }
  }
  /**
   * Clean up after a successful import.
   *
   * @throws NDBException If a NDB error occurs.
   */
  private void cleanUp() throws NDBException {
    // Drain the work queue.
    drainWorkQueue();
    pTask.setPause(true);
    stopWorkThreads(true);
    timer.cancel();
  }
  /**
   * Uncaught exception handler.
   *
   * @param t The thread working when the exception was thrown.
   * @param e The exception.
   */
  public void uncaughtException(Thread t, Throwable e) {
     unCaughtExceptionThrown = true;
     threads.remove(t);
     Message msg = ERR_NDB_IMPORT_THREAD_EXCEPTION.get(
         t.getName(), StaticUtils.stackTraceToSingleLineString(e.getCause()));
     logError(msg);
   }
  /**
   * Return an import context related to the specified DN.
   * @param dn The dn.
   * @return  An import context.
   * @throws DirectoryException If an directory error occurs.
   */
  private DNContext getImportConfig(DN dn) throws DirectoryException {
    DNContext DNContext = null;
    DN nodeDN = dn;
    while (DNContext == null && nodeDN != null) {
      DNContext = importMap.get(nodeDN);
      if (DNContext == null)
      {
        nodeDN = nodeDN.getParentDNInSuffix();
      }
    }
    if (nodeDN == null) {
      // The entry should not have been given to this backend.
      Message message =
              NdbMessages.ERR_NDB_INCORRECT_ROUTING.get(String.valueOf(dn));
      throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message);
    }
    return DNContext;
  }
  /**
   * Creates an import context for the specified entry container.
   *
   * @param entryContainer The entry container.
   * @return Import context to use during import.
   * @throws ConfigException If a configuration contains error.
   */
   private DNContext getImportContext(EntryContainer entryContainer)
      throws ConfigException {
    DN baseDN = entryContainer.getBaseDN();
    EntryContainer srcEntryContainer = null;
    List<DN> includeBranches = new ArrayList<DN>();
    List<DN> excludeBranches = new ArrayList<DN>();
    if(!ldifImportConfig.appendToExistingData() &&
        !ldifImportConfig.clearBackend())
    {
      for(DN dn : ldifImportConfig.getExcludeBranches())
      {
        if(baseDN.equals(dn))
        {
          // This entire base DN was explicitly excluded. Skip.
          return null;
        }
        if(baseDN.isAncestorOf(dn))
        {
          excludeBranches.add(dn);
        }
      }
      if(!ldifImportConfig.getIncludeBranches().isEmpty())
      {
        for(DN dn : ldifImportConfig.getIncludeBranches())
        {
          if(baseDN.isAncestorOf(dn))
          {
            includeBranches.add(dn);
          }
        }
        if(includeBranches.isEmpty())
        {
          // There are no branches in the explicitly defined include list under
          // this base DN. Skip this base DN alltogether.
          return null;
        }
        // Remove any overlapping include branches.
        Iterator<DN> includeBranchIterator = includeBranches.iterator();
        while(includeBranchIterator.hasNext())
        {
          DN includeDN = includeBranchIterator.next();
          boolean keep = true;
          for(DN dn : includeBranches)
          {
            if(!dn.equals(includeDN) && dn.isAncestorOf(includeDN))
            {
              keep = false;
              break;
            }
          }
          if(!keep)
          {
            includeBranchIterator.remove();
          }
        }
        // Remvoe any exclude branches that are not are not under a include
        // branch since they will be migrated as part of the existing entries
        // outside of the include branches anyways.
        Iterator<DN> excludeBranchIterator = excludeBranches.iterator();
        while(excludeBranchIterator.hasNext())
        {
          DN excludeDN = excludeBranchIterator.next();
          boolean keep = false;
          for(DN includeDN : includeBranches)
          {
            if(includeDN.isAncestorOf(excludeDN))
            {
              keep = true;
              break;
            }
          }
          if(!keep)
          {
            excludeBranchIterator.remove();
          }
        }
      }
    }
    // Create an import context.
    DNContext DNContext = new DNContext();
    DNContext.setConfig(config);
    DNContext.setLDIFImportConfig(this.ldifImportConfig);
    DNContext.setLDIFReader(reader);
    DNContext.setBaseDN(baseDN);
    DNContext.setEntryContainer(entryContainer);
    DNContext.setSrcEntryContainer(srcEntryContainer);
    // Create queue.
    LinkedBlockingQueue<WorkElement> works =
        new LinkedBlockingQueue<WorkElement>
                     (config.getImportQueueSize());
    DNContext.setWorkQueue(works);
    // Set the include and exclude branches
    DNContext.setIncludeBranches(includeBranches);
    DNContext.setExcludeBranches(excludeBranches);
    return DNContext;
  }
  /**
   * Add specified context and entry to the work queue.
   *
   * @param context The context related to the entry DN.
   * @param entry The entry to work on.
   * @return  <CODE>True</CODE> if the element was added to the work queue.
   */
  private boolean
  addEntryQueue(DNContext context,  Entry entry) {
    WorkElement element =
            WorkElement.decode(entry, context);
    return addQueue(context, element);
  }
  /**
   * This class reports progress of the import job at fixed intervals.
   */
  private final class ProgressTask extends TimerTask
  {
    /**
     * The number of entries that had been read at the time of the
     * previous progress report.
     */
    private long previousCount = 0;
    /**
     * The time in milliseconds of the previous progress report.
     */
    private long previousTime;
    /**
     * The number of bytes in a megabyte.
     * Note that 1024*1024 bytes may eventually become known as a mebibyte(MiB).
     */
    public static final int bytesPerMegabyte = 1024*1024;
    // Determines if the ldif is being read.
    private boolean ldifRead = false;
    // Suspend output.
    private boolean pause = false;
    /**
     * Create a new import progress task.
     */
    public ProgressTask()
    {
      previousTime = System.currentTimeMillis();
    }
    /**
     * Return if reading the LDIF file.
     */
    public void ldifRead() {
      ldifRead = true;
    }
    /**
     * Suspend output if true.
     *
     * @param v The value to set the suspend value to.
     */
    public void setPause(boolean v) {
    pause=v;
   }
    /**
     * The action to be performed by this timer task.
     */
    public void run() {
      long latestCount = reader.getEntriesRead() + 0;
      long deltaCount = (latestCount - previousCount);
      long latestTime = System.currentTimeMillis();
      long deltaTime = latestTime - previousTime;
      Message message;
      if (deltaTime == 0) {
        return;
      }
      if(pause) {
        return;
      }
      if(!ldifRead) {
        long numRead     = reader.getEntriesRead();
        long numIgnored  = reader.getEntriesIgnored();
        long numRejected = reader.getEntriesRejected();
        float rate = 1000f*deltaCount / deltaTime;
        message = NOTE_NDB_IMPORT_PROGRESS_REPORT.get(
            numRead, numIgnored, numRejected, 0, rate);
        logError(message);
      }
      previousCount = latestCount;
      previousTime = latestTime;
    }
  }
}
opends/src/server/org/opends/server/backends/ndb/importLDIF/WorkElement.java
New file
@@ -0,0 +1,104 @@
/*
 * 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/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * 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/opends/resource/legal-notices/OpenDS.LICENSE.  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.
 */
package org.opends.server.backends.ndb.importLDIF;
import org.opends.server.types.Entry;
/**
 * A work element passed on the work queue.
 */
public class WorkElement {
  // The entry to import.
  private Entry entry;
  // Used in replace mode, this is the entry to replace.
  private Entry existingEntry;
  // The context related to the entry.
  private DNContext context;
  /**
   * Create a work element instance.
   *
   * @param entry The entry to import.
   * @param context The context related to the entry.
   */
  private WorkElement(Entry entry, DNContext context )  {
    this.entry = entry;
    this.context = context;
  }
  /**
   * Static to create an work element.
   *
   * @param entry The entry to import.
   * @param context The context related to the entry.
   * @return  A work element to put on the queue.
   */
  public static
  WorkElement decode(Entry entry, DNContext context ) {
    return new WorkElement(entry, context);
  }
  /**
   * Return the entry to import.
   *
   * @return  The entry to import.
   */
  public Entry getEntry() {
    return entry;
  }
  /**
   * Return the context related to the entry.
   *
   * @return The context.
   */
  public DNContext getContext() {
    return context;
  }
  /**
   * Return an existing entry, used during replace mode.
   *
   * @return An existing entry.
   */
  public Entry getExistingEntry() {
    return existingEntry;
  }
  /**
   * Set the existing entry.
   *
   * @param existingEntry The existing entry to set.
   */
  public void setExistingEntry(Entry existingEntry) {
    this.existingEntry = existingEntry;
  }
}
opends/src/server/org/opends/server/backends/ndb/importLDIF/WorkThread.java
New file
@@ -0,0 +1,240 @@
/*
 * 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/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * 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/opends/resource/legal-notices/OpenDS.LICENSE.  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.
 */
package org.opends.server.backends.ndb.importLDIF;
import static org.opends.server.loggers.debug.DebugLogger.*;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.types.*;
import org.opends.server.api.DirectoryThread;
import org.opends.server.backends.ndb.*;
import org.opends.messages.Message;
import static org.opends.messages.NdbMessages.*;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import org.opends.server.core.AddOperation;
import org.opends.server.protocols.internal.InternalClientConnection;
/**
 * A thread to process import entries from a queue.  Multiple instances of
 * this class process entries from a single shared queue.
 */
public class WorkThread extends DirectoryThread {
  /**
   * The tracer object for the debug logger.
   */
  private static final DebugTracer TRACER = getTracer();
  /**
   * Number of operations to batch on a single transaction.
   */
  private static final int TXN_BATCH_SIZE = 15;
  /*
   * Work queue of work items.
   */
  private BlockingQueue<WorkElement> workQueue;
  /**
   * The number of entries imported by this thread.
   */
  private int importedCount = 0;
  /**
   * Root container.
   */
  private RootContainer rootContainer;
  /**
   * Abstract Transaction object.
   */
  private AbstractTransaction txn;
  /**
   * A flag that is set when the thread has been told to stop processing.
   */
  private boolean stopRequested = false;
  /**
   * The thread number related to a thread.
   */
  private int threadNumber;
  /**
   * Create a work thread instance using the specified parameters.
   *
   * @param workQueue  The work queue to pull work off of.
   * @param threadNumber The thread number.
   * @param rootContainer The root container.
   */
  public WorkThread(BlockingQueue<WorkElement> workQueue, int threadNumber,
                                RootContainer rootContainer)
  {
    super("Import Worker Thread " + threadNumber);
    this.threadNumber = threadNumber;
    this.workQueue = workQueue;
    this.rootContainer = rootContainer;
    this.txn = new AbstractTransaction(rootContainer);
  }
  /**
   * Get the number of entries imported by this thread.
   * @return The number of entries imported by this thread.
   */
   int getImportedCount() {
    return importedCount;
  }
  /**
   * Tells the thread to stop processing.
   */
   void stopProcessing() {
    stopRequested = true;
  }
  /**
   * Run the thread. Read from item from queue and process unless told to stop.
   */
  @Override
  public void run()
  {
    int batchSize = 0;
    try {
      do {
        try {
          WorkElement element = workQueue.poll(1000, TimeUnit.MILLISECONDS);
          if (element != null) {
            process(element);
            batchSize++;
            if (batchSize < TXN_BATCH_SIZE) {
              continue;
            } else {
              batchSize = 0;
              txn.commit();
            }
          }
        }
        catch (InterruptedException e) {
          if (debugEnabled()) {
            TRACER.debugCaught(DebugLogLevel.ERROR, e);
          }
        }
      } while (!stopRequested);
      txn.commit();
    } catch (Exception e) {
      if (debugEnabled()) {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
      throw new RuntimeException(e);
    }
  }
  /**
   * Process a work element.
   *
   * @param element The work elemenet to process.
   *
   * @throws Exception If an error occurs.
   */
  private void process(WorkElement element) throws Exception
  {
    Entry entry = element.getEntry();
    DNContext context = element.getContext();
    EntryContainer ec = context.getEntryContainer();
    DN entryDN = entry.getDN();
    DN parentDN = context.getEntryContainer().getParentWithinBase(entryDN);
    if (parentDN != null) {
      // If the parent is in the pending map, another thread is working on
      // the parent entry; wait until that thread is done with the parent.
      while (context.isPending(parentDN)) {
        try {
          Thread.sleep(50);
        } catch (Exception e) {
          return;
        }
      }
      if (context.getParentDN() == null) {
        Message msg =
                ERR_NDB_IMPORT_PARENT_NOT_FOUND.get(parentDN.toString());
        rejectLastEntry(context, msg);
        context.removePending(entryDN);
        return;
      }
    } else {
      parentDN = entryDN;
    }
    InternalClientConnection conn =
      InternalClientConnection.getRootConnection();
    AddOperation addOperation =
      conn.processAdd(entry.getDN(), entry.getObjectClasses(),
      entry.getUserAttributes(), entry.getOperationalAttributes());
    try {
      ec.addEntryNoCommit(entry, addOperation, txn);
      DN contextParentDN = context.getParentDN();
      if ((contextParentDN == null) ||
        !contextParentDN.equals(parentDN)) {
        txn.commit();
      }
      importedCount++;
    } catch (DirectoryException de) {
      if (de.getResultCode() == ResultCode.ENTRY_ALREADY_EXISTS) {
          Message msg = WARN_NDB_IMPORT_ENTRY_EXISTS.get();
          rejectLastEntry(context, msg);
          context.removePending(entryDN);
          txn.close();
      } else {
        txn.close();
        throw de;
      }
    }
    context.setParentDN(parentDN);
    context.removePending(entryDN);
    return;
  }
  /**
   * The synchronized wrapper method to reject the last entry.
   *
   * @param context Import context.
   * @param msg Reject message.
   */
  private static synchronized void rejectLastEntry(DNContext context,
    Message msg)
  {
    context.getLDIFReader().rejectLastEntry(msg);
  }
}
opends/src/server/org/opends/server/backends/ndb/importLDIF/package-info.java
New file
@@ -0,0 +1,36 @@
/*
 * 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/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * 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/opends/resource/legal-notices/OpenDS.LICENSE.  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.
 */
/**
 * Contains the code for the import LDIF feature of NDB backend.
 */
@org.opends.server.types.PublicAPI(
     stability=org.opends.server.types.StabilityLevel.PRIVATE)
package org.opends.server.backends.ndb.importLDIF;
opends/src/server/org/opends/server/backends/ndb/package-info.java
New file
@@ -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/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * 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/opends/resource/legal-notices/OpenDS.LICENSE.  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.
 */
/**
 * Contains the code for the Directory Server backend that uses the
 * MySQL NDB Cluster as the repository for storing entry and index
 * information.
 */
@org.opends.server.types.PublicAPI(
     stability=org.opends.server.types.StabilityLevel.PRIVATE)
package org.opends.server.backends.ndb;
opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendAddOperation.java
@@ -22,7 +22,7 @@
 * CDDL HEADER END
 *
 *
 *      Copyright 2008 Sun Microsystems, Inc.
 *      Copyright 2008-2009 Sun Microsystems, Inc.
 */
package org.opends.server.workflowelement.localbackend;
@@ -95,29 +95,45 @@
  // The backend in which the entry is to be added.
  private Backend backend;
  /**
   * The backend in which the entry is to be added.
   */
  protected Backend backend;
  // Indicates whether the request includes the LDAP no-op control.
  private boolean noOp;
  /**
   * Indicates whether the request includes the LDAP no-op control.
   */
  protected boolean noOp;
  // The DN of the entry to be added.
  private DN entryDN;
  /**
   * The DN of the entry to be added.
   */
  protected DN entryDN;
  // The entry being added to the server.
  private Entry entry;
  /**
   * The entry being added to the server.
   */
  protected Entry entry;
  // The post-read request control included in the request, if applicable.
  LDAPPostReadRequestControl postReadRequest;
  /**
   * The post-read request control included in the request, if applicable.
   */
  protected LDAPPostReadRequestControl postReadRequest;
  // The set of object classes for the entry to add.
  private Map<ObjectClass, String> objectClasses;
  /**
   * The set of object classes for the entry to add.
   */
  protected Map<ObjectClass, String> objectClasses;
  // The set of operational attributes for the entry to add.
  private Map<AttributeType,List<Attribute>> operationalAttributes;
  /**
   * The set of operational attributes for the entry to add.
   */
  protected Map<AttributeType,List<Attribute>> operationalAttributes;
  // The set of user attributes for the entry to add.
  private Map<AttributeType,List<Attribute>> userAttributes;
  /**
   * The set of user attributes for the entry to add.
   */
  protected Map<AttributeType,List<Attribute>> userAttributes;
@@ -159,7 +175,7 @@
   * @throws CanceledOperationException
   *           if this operation should be cancelled
   */
  void processLocalAdd(final LocalBackendWorkflowElement wfe)
  public void processLocalAdd(final LocalBackendWorkflowElement wfe)
      throws CanceledOperationException
  {
    boolean executePostOpPlugins = false;
@@ -862,7 +878,7 @@
   *                              attributes and the server is configured to
   *                              reject such entries.
   */
  private void addRDNAttributesIfNecessary()
  protected void addRDNAttributesIfNecessary()
          throws DirectoryException
  {
    RDN rdn = entryDN.getRDN();
@@ -1272,7 +1288,7 @@
   * @throws  DirectoryException  If the entry violates the server schema
   *                              configuration.
   */
  private void checkSchema(Entry parentEntry)
  protected void checkSchema(Entry parentEntry)
          throws DirectoryException
  {
    MessageBuilder invalidReason = new MessageBuilder();
@@ -1443,7 +1459,7 @@
   * @throws  DirectoryException  If there is a problem with any of the
   *                              request controls.
   */
  private void processControls(DN parentDN)
  protected void processControls(DN parentDN)
          throws DirectoryException
  {
    List<Control> requestControls = getRequestControls();
@@ -1581,7 +1597,7 @@
  /**
   * Adds the post-read response control to the response.
   */
  private void addPostReadResponse()
  protected void addPostReadResponse()
  {
    Entry addedEntry = entry.duplicate(true);
opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendBindOperation.java
@@ -22,7 +22,7 @@
 * CDDL HEADER END
 *
 *
 *      Copyright 2008 Sun Microsystems, Inc.
 *      Copyright 2008-2009 Sun Microsystems, Inc.
 */
package org.opends.server.workflowelement.localbackend;
@@ -96,15 +96,21 @@
  // The backend in which the bind operation should be processed.
  private Backend backend;
  /**
   * The backend in which the bind operation should be processed.
   */
  protected Backend backend;
  // Indicates whether the bind response should include the first warning for an
  // upcoming password expiration.
  private boolean isFirstWarning;
  /**
   * Indicates whether the bind response should include the first warning
   * for an upcoming password expiration.
   */
  protected boolean isFirstWarning;
  // Indicates whether this bind is using a grace login for the user.
  private boolean isGraceLogin;
  /**
   * Indicates whether this bind is using a grace login for the user.
   */
  protected boolean isGraceLogin;
  // Indicates whether the user must change his/her password before doing
  // anything else.
@@ -117,14 +123,18 @@
  // control in the bind response.
  private boolean returnAuthzID;
  // Indicates whether to execute post-operation plugins.
  private boolean executePostOpPlugins;
  /**
   * Indicates whether to execute post-operation plugins.
   */
  protected boolean executePostOpPlugins;
  // The client connection associated with this bind operation.
  private ClientConnection clientConnection;
  // The bind DN provided by the client.
  private DN bindDN;
  /**
   * The bind DN provided by the client.
   */
  protected DN bindDN;
  // The lookthrough limit that should be enforced for the user.
  private int lookthroughLimit;
@@ -141,11 +151,15 @@
  // The idle time limit that should be enforced for the user.
  private long idleTimeLimit;
  // The password policy that applies to the user.
  private PasswordPolicy policy;
  /**
   * The password policy that applies to the user.
   */
  protected PasswordPolicy policy;
  // The password policy state for the user.
  private PasswordPolicyState pwPolicyState;
  /**
   * The password policy state for the user.
   */
  protected PasswordPolicyState pwPolicyState;
  // The password policy error type for this bind operation.
  private PasswordPolicyErrorType pwPolicyErrorType;
@@ -153,8 +167,10 @@
  // The password policy warning type for this bind operation.
  private PasswordPolicyWarningType pwPolicyWarningType;
  // The plugin config manager for the Directory Server.
  private PluginConfigManager pluginConfigManager;
  /**
   * The plugin config manager for the Directory Server.
   */
  protected PluginConfigManager pluginConfigManager;
  // The SASL mechanism used for this bind operation.
  private String saslMechanism;
@@ -182,7 +198,7 @@
   *          The local backend work-flow element.
   *
   */
  void processLocalBind(LocalBackendWorkflowElement wfe)
  public void processLocalBind(LocalBackendWorkflowElement wfe)
  {
    this.backend = wfe.getBackend();
@@ -475,7 +491,7 @@
   * @throws  DirectoryException  If a problem occurs that should cause the bind
   *                              operation to fail.
   */
  private boolean processSimpleBind()
  protected boolean processSimpleBind()
          throws DirectoryException
  {
    // See if this is an anonymous bind.  If so, then determine whether
@@ -680,10 +696,12 @@
  /**
   * Performs the processing necessary for an anonymous simple bind.
   *
   * @return  {@code true} if processing should continue for the operation, or
   *          {@code false} if not.
   * @throws  DirectoryException  If a problem occurs that should cause the bind
   *                              operation to fail.
   */
  private boolean processAnonymousSimpleBind()
  protected boolean processAnonymousSimpleBind()
          throws DirectoryException
  {
    // If the server is in lockdown mode, then fail.
@@ -907,8 +925,8 @@
   * @throws  DirectoryException  If a problem occurs that should cause the bind
   *                              to fail.
   */
  private void checkPasswordPolicyState(Entry userEntry,
                                        SASLMechanismHandler<?> saslHandler)
  protected void checkPasswordPolicyState(Entry userEntry,
                                          SASLMechanismHandler<?> saslHandler)
          throws DirectoryException
  {
    boolean isSASLBind = (saslHandler != null);
@@ -1119,7 +1137,7 @@
   *
   * @param  userEntry  The entry for the authenticated user.
   */
  private void setResourceLimits(Entry userEntry)
  protected void setResourceLimits(Entry userEntry)
  {
    // See if the user's entry contains a custom size limit.
    AttributeType attrType =
opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendCompareOperation.java
@@ -22,7 +22,7 @@
 * CDDL HEADER END
 *
 *
 *      Copyright 2008 Sun Microsystems, Inc.
 *      Copyright 2008-2009 Sun Microsystems, Inc.
 */
package org.opends.server.workflowelement.localbackend;
@@ -73,17 +73,25 @@
  // The backend in which the comparison is to be performed.
  private Backend backend;
  /**
   * The backend in which the comparison is to be performed.
   */
  protected Backend backend;
  // The client connection for this operation.
  private ClientConnection clientConnection;
  /**
   * The client connection for this operation.
   */
  protected ClientConnection clientConnection;
  // The DN of the entry to compare.
  private DN entryDN;
  /**
   * The DN of the entry to compare.
   */
  protected DN entryDN;
  // The entry to be compared.
  private Entry entry = null;
  /**
   * The entry to be compared.
   */
  protected Entry entry = null;
@@ -121,7 +129,7 @@
   * @throws CanceledOperationException
   *           if this operation should be cancelled
   */
  void processLocalCompare(LocalBackendWorkflowElement wfe)
  public void processLocalCompare(LocalBackendWorkflowElement wfe)
      throws CanceledOperationException
  {
    boolean executePostOpPlugins = false;
@@ -406,7 +414,7 @@
   * @throws  DirectoryException  If a problem occurs that should prevent the
   *                              operation from succeeding.
   */
  private void handleRequestControls()
  protected void handleRequestControls()
          throws DirectoryException
  {
    List<Control> requestControls = getRequestControls();
opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendDeleteOperation.java
@@ -89,20 +89,30 @@
  // The backend in which the operation is to be processed.
  private Backend backend;
  /**
   * The backend in which the operation is to be processed.
   */
  protected Backend backend;
  // Indicates whether the LDAP no-op control has been requested.
  private boolean noOp;
  /**
   * Indicates whether the LDAP no-op control has been requested.
   */
  protected boolean noOp;
  // The client connection on which this operation was requested.
  private ClientConnection clientConnection;
  /**
   * The client connection on which this operation was requested.
   */
  protected ClientConnection clientConnection;
  // The DN of the entry to be deleted.
  private DN entryDN;
  /**
   * The DN of the entry to be deleted.
   */
  protected DN entryDN;
  // The entry to be deleted.
  private Entry entry;
  /**
   * The entry to be deleted.
   */
  protected Entry entry;
  // The pre-read request control included in the request, if applicable.
  private LDAPPreReadRequestControl preReadRequest;
@@ -144,7 +154,7 @@
   * @throws CanceledOperationException
   *           if this operation should be cancelled
   */
  void processLocalDelete(final LocalBackendWorkflowElement wfe)
  public void processLocalDelete(final LocalBackendWorkflowElement wfe)
      throws CanceledOperationException
  {
    boolean executePostOpPlugins = false;
@@ -488,7 +498,7 @@
   * @throws  DirectoryException  If a problem occurs that should cause the
   *                              operation to fail.
   */
  private void handleRequestControls()
  protected void handleRequestControls()
          throws DirectoryException
  {
    List<Control> requestControls = getRequestControls();
@@ -620,7 +630,7 @@
  /**
   * Performs any processing needed for the LDAP pre-read control.
   */
  private void processPreReadControl()
  protected void processPreReadControl()
  {
    if (preReadRequest != null)
    {
@@ -672,7 +682,12 @@
    }
  }
  private boolean handleConflictResolution() {
  /**
   * Handle conflict resolution.
   * @return  {@code true} if processing should continue for the operation, or
   *          {@code false} if not.
   */
  protected boolean handleConflictResolution() {
      boolean returnVal = true;
      for (SynchronizationProvider<?> provider :
@@ -703,7 +718,10 @@
      return returnVal;
  }
  private void processSynchPostOperationPlugins() {
  /**
   * Invoke post operation synchronization providers.
   */
  protected void processSynchPostOperationPlugins() {
      for (SynchronizationProvider<?> provider :
          DirectoryServer.getSynchronizationProviders()) {
@@ -722,7 +740,12 @@
      }
  }
  private boolean processPreOperation() {
  /**
   * Process pre operation.
   * @return  {@code true} if processing should continue for the operation, or
   *          {@code false} if not.
   */
  protected boolean processPreOperation() {
      boolean returnVal = true;
      for (SynchronizationProvider<?> provider :
opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendModifyDNOperation.java
@@ -104,23 +104,35 @@
  // The backend in which the operation is to be processed.
  private Backend backend;
  /**
   * The backend in which the operation is to be processed.
   */
  protected Backend backend;
  // Indicates whether the no-op control was included in the request.
  private boolean noOp;
  /**
   * Indicates whether the no-op control was included in the request.
   */
  protected boolean noOp;
  // The client connection on which this operation was requested.
  private ClientConnection clientConnection;
  /**
   * The client connection on which this operation was requested.
   */
  protected ClientConnection clientConnection;
  // The original DN of the entry.
  DN entryDN;
  /**
   * The original DN of the entry.
   */
  protected DN entryDN;
  // The current entry, before it is renamed.
  private Entry currentEntry;
  /**
   * The current entry, before it is renamed.
   */
  protected Entry currentEntry;
  // The new entry, as it will appear after it has been renamed.
  private Entry newEntry;
  /**
   * The new entry, as it will appear after it has been renamed.
   */
  protected Entry newEntry;
  // The LDAP post-read request control, if present in the request.
  private LDAPPostReadRequestControl postReadRequest;
@@ -128,8 +140,10 @@
  // The LDAP pre-read request control, if present in the request.
  private LDAPPreReadRequestControl preReadRequest;
  // The new RDN for the entry.
  private RDN newRDN;
  /**
   * The new RDN for the entry.
   */
  protected RDN newRDN;
@@ -187,7 +201,7 @@
   * @throws CanceledOperationException
   *           if this operation should be cancelled
   */
  void processLocalModifyDN(final LocalBackendWorkflowElement wfe)
  public void processLocalModifyDN(final LocalBackendWorkflowElement wfe)
      throws CanceledOperationException
  {
    boolean executePostOpPlugins = false;
@@ -684,7 +698,7 @@
   * @throws  DirectoryException  If a problem occurs that should cause the
   *                              modify DN operation to fail.
   */
  private void handleRequestControls()
  protected void handleRequestControls()
          throws DirectoryException
  {
    List<Control> requestControls = getRequestControls();
@@ -836,7 +850,7 @@
   * @throws  DirectoryException  If a problem occurs that should cause the
   *                              modify DN operation to fail.
   */
  private void applyRDNChanges(List<Modification> modifications)
  protected void applyRDNChanges(List<Modification> modifications)
          throws DirectoryException
  {
    // If we should delete the old RDN values from the entry, then do so.
@@ -951,7 +965,7 @@
   * @throws  DirectoryException  If a problem occurs that should cause the
   *                              modify DN operation to fail.
   */
  private void applyPreOpModifications(List<Modification> modifications,
  protected void applyPreOpModifications(List<Modification> modifications,
                                       int startPos)
          throws DirectoryException
  {
@@ -1007,7 +1021,7 @@
   * Performs any necessary processing to create the pre-read and/or post-read
   * response controls and attach them to the response.
   */
  private void processReadEntryControls()
  protected void processReadEntryControls()
  {
    if (preReadRequest != null)
    {
@@ -1109,7 +1123,12 @@
    }
  }
  private boolean handleConflictResolution() {
  /**
   * Handle conflict resolution.
   * @return  {@code true} if processing should continue for the operation, or
   *          {@code false} if not.
   */
  protected boolean handleConflictResolution() {
      boolean returnVal = true;
      for (SynchronizationProvider<?> provider :
@@ -1141,7 +1160,12 @@
      return returnVal;
  }
  private boolean processPreOperation() {
  /**
   * Process pre operation.
   * @return  {@code true} if processing should continue for the operation, or
   *          {@code false} if not.
   */
  protected boolean processPreOperation() {
      boolean returnVal = true;
      for (SynchronizationProvider<?> provider :
@@ -1171,7 +1195,10 @@
      return returnVal;
  }
  private void processSynchPostOperationPlugins() {
  /**
   * Invoke post operation synchronization providers.
   */
  protected void processSynchPostOperationPlugins() {
      for (SynchronizationProvider<?> provider : DirectoryServer
              .getSynchronizationProviders()) {
          try {
opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendModifyOperation.java
@@ -97,45 +97,67 @@
  // The backend in which the target entry exists.
  private Backend backend;
  /**
   * The backend in which the target entry exists.
   */
  protected Backend backend;
  // Indicates whether the request included the user's current password.
  private boolean currentPasswordProvided;
  // Indicates whether the user's account has been enabled or disabled by this
  // modify operation.
  private boolean enabledStateChanged;
  /**
   * Indicates whether the user's account has been enabled or disabled
   * by this modify operation.
   */
  protected boolean enabledStateChanged;
  // Indicates whether the user's account is currently enabled.
  private boolean isEnabled;
  // Indicates whether the request included the LDAP no-op control.
  private boolean noOp;
  /**
   * Indicates whether the request included the LDAP no-op control.
   */
  protected boolean noOp;
  // Indicates whether this modify operation includees a password change.
  private boolean passwordChanged;
  /**
   * Indicates whether this modify operation includees a password change.
   */
  protected boolean passwordChanged;
  // Indicates whether the request included the password policy request control.
  private boolean pwPolicyControlRequested;
  /**
   * Indicates whether the request included the password policy request control.
   */
  protected boolean pwPolicyControlRequested;
  // Indicates whether the password change is a self-change.
  private boolean selfChange;
  /**
   * Indicates whether the password change is a self-change.
   */
  protected boolean selfChange;
  // Indicates whether the user's account was locked before this change.
  private boolean wasLocked;
  /**
   * Indicates whether the user's account was locked before this change.
   */
  protected boolean wasLocked;
  // The client connection associated with this operation.
  private ClientConnection clientConnection;
  /**
   * The client connection associated with this operation.
   */
  protected ClientConnection clientConnection;
  // The DN of the entry to modify.
  private DN entryDN;
  /**
   * The DN of the entry to modify.
   */
  protected DN entryDN;
  // The current entry, before any changes are applied.
  private Entry currentEntry = null;
  /**
   * The current entry, before any changes are applied.
   */
  protected Entry currentEntry = null;
  // The modified entry that will be stored in the backend.
  private Entry modifiedEntry = null;
  /**
   * The modified entry that will be stored in the backend.
   */
  protected Entry modifiedEntry = null;
  // The number of passwords contained in the modify operation.
  private int numPasswords;
@@ -152,14 +174,20 @@
  // The set of clear-text new passwords (if any were provided).
  private List<AttributeValue> newPasswords = null;
  // The set of modifications contained in this request.
  private List<Modification> modifications;
  /**
   * The set of modifications contained in this request.
   */
  protected List<Modification> modifications;
  // The password policy error type for this operation.
  private PasswordPolicyErrorType pwpErrorType;
  /**
   * The password policy error type for this operation.
   */
  protected PasswordPolicyErrorType pwpErrorType;
  // The password policy state for this modify operation.
  private PasswordPolicyState pwPolicyState;
  /**
   * The password policy state for this modify operation.
   */
  protected PasswordPolicyState pwPolicyState;
@@ -273,7 +301,7 @@
   * @throws CanceledOperationException
   *           if this operation should be cancelled
   */
  void processLocalModify(final LocalBackendWorkflowElement wfe)
  public void processLocalModify(final LocalBackendWorkflowElement wfe)
      throws CanceledOperationException
  {
    boolean executePostOpPlugins = false;
@@ -719,7 +747,7 @@
   * @throws  DirectoryException  If a problem is encountered with any of the
   *                              controls.
   */
  private void processRequestControls()
  protected void processRequestControls()
          throws DirectoryException
  {
    List<Control> requestControls = getRequestControls();
@@ -869,7 +897,7 @@
   * @throws  DirectoryException  If a problem is encountered that should cause
   *                              the modify operation to fail.
   */
  private void handleSchemaProcessing() throws DirectoryException
  protected void handleSchemaProcessing() throws DirectoryException
  {
    for (Modification m : modifications)
@@ -982,7 +1010,7 @@
   * @throws  DirectoryException  If a problem is encountered that should cause
   *                              the modify operation to fail.
   */
  private void handleInitialPasswordPolicyProcessing()
  protected void handleInitialPasswordPolicyProcessing()
          throws DirectoryException
  {
    // Declare variables used for password policy state processing.
@@ -1921,7 +1949,7 @@
   * @throws  DirectoryException  If the modify operation should not be allowed
   *                              as a result of the writability check.
   */
  private void checkWritability()
  protected void checkWritability()
          throws DirectoryException
  {
    // If it is not a private backend, then check to see if the server or
@@ -1968,7 +1996,7 @@
   * Handles any account status notifications that may be needed as a result of
   * modify processing.
   */
  private void handleAccountStatusNotifications()
  protected void handleAccountStatusNotifications()
  {
    if (passwordChanged)
    {
@@ -2037,7 +2065,7 @@
   * Handles any processing that is required for the LDAP pre-read and/or
   * post-read controls.
   */
  private void handleReadEntryProcessing()
  protected void handleReadEntryProcessing()
  {
    if (preReadRequest != null)
    {
@@ -2138,7 +2166,12 @@
  private boolean handleConflictResolution() {
  /**
   * Handle conflict resolution.
   * @return  {@code true} if processing should continue for the operation, or
   *          {@code false} if not.
   */
  protected boolean handleConflictResolution() {
      boolean returnVal = true;
      for (SynchronizationProvider<?> provider :
@@ -2169,7 +2202,12 @@
      return returnVal;
  }
  private boolean processPreOperation() {
  /**
   * Process pre operation.
   * @return  {@code true} if processing should continue for the operation, or
   *          {@code false} if not.
   */
  protected boolean processPreOperation() {
      boolean returnVal = true;
      for (SynchronizationProvider<?> provider :
          DirectoryServer.getSynchronizationProviders()) {
@@ -2198,7 +2236,10 @@
      return returnVal;
  }
  private void processSynchPostOperationPlugins() {
  /**
   * Invoke post operation synchronization providers.
   */
  protected void processSynchPostOperationPlugins() {
      for (SynchronizationProvider<?> provider :
          DirectoryServer.getSynchronizationProviders()) {
          try {
opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendSearchOperation.java
@@ -22,7 +22,7 @@
 * CDDL HEADER END
 *
 *
 *      Copyright 2008 Sun Microsystems, Inc.
 *      Copyright 2008-2009 Sun Microsystems, Inc.
 */
package org.opends.server.workflowelement.localbackend;
@@ -82,24 +82,36 @@
  // The backend in which the search is to be performed.
  private Backend backend;
  /**
   * The backend in which the search is to be performed.
   */
  protected Backend backend;
  // Indicates whether we should actually process the search.  This should
  // only be false if it's a persistent search with changesOnly=true.
  private boolean processSearch;
  /**
   * Indicates whether we should actually process the search.  This should
   * only be false if it's a persistent search with changesOnly=true.
   */
  protected boolean processSearch;
  // The client connection for the search operation.
  private ClientConnection clientConnection;
  /**
   * The client connection for the search operation.
   */
  protected ClientConnection clientConnection;
  // The base DN for the search.
  private DN baseDN;
  /**
   * The base DN for the search.
   */
  protected DN baseDN;
  // The persistent search request, if applicable.
  private PersistentSearch persistentSearch;
  /**
   * The persistent search request, if applicable.
   */
  protected PersistentSearch persistentSearch;
  // The filter for the search.
  private SearchFilter filter;
  /**
   * The filter for the search.
   */
  protected SearchFilter filter;
@@ -125,7 +137,7 @@
   * @throws CanceledOperationException
   *           if this operation should be cancelled
   */
  void processLocalSearch(LocalBackendWorkflowElement wfe)
  public void processLocalSearch(LocalBackendWorkflowElement wfe)
      throws CanceledOperationException
  {
    boolean executePostOpPlugins = false;
@@ -316,7 +328,7 @@
   * @throws  DirectoryException  If there is a problem with any of the request
   *                              controls.
   */
  private void handleRequestControls()
  protected void handleRequestControls()
          throws DirectoryException
  {
    List<Control> requestControls  = getRequestControls();
opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendWorkflowElement.java
@@ -22,7 +22,7 @@
 * CDDL HEADER END
 *
 *
 *      Copyright 2008 Sun Microsystems, Inc.
 *      Copyright 2008-2009 Sun Microsystems, Inc.
 */
package org.opends.server.workflowelement.localbackend;
@@ -82,7 +82,7 @@
  // a lock to guarantee safe concurrent access to the registeredLocalBackends
  // variable
  private static Object registeredLocalBackendsLock = new Object();
  private static final Object registeredLocalBackendsLock = new Object();
  // A string indicating the type of the workflow element.
@@ -154,6 +154,7 @@
  /**
   * {@inheritDoc}
   */
  @Override
  public void finalizeWorkflowElement()
  {
    // null all fields so that any use of the finalized object will raise
@@ -477,7 +478,7 @@
   * @return The backend associated with this local backend workflow
   *         element.
   */
  Backend getBackend()
  public Backend getBackend()
  {
    return backend;
  }
opends/src/server/org/opends/server/workflowelement/ndb/NDBAddOperation.java
New file
@@ -0,0 +1,661 @@
/*
 * 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/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * 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/opends/resource/legal-notices/OpenDS.LICENSE.  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.
 */
package org.opends.server.workflowelement.ndb;
import com.mysql.cluster.ndbj.NdbOperation;
import static org.opends.messages.CoreMessages.*;
import static org.opends.server.config.ConfigConstants.*;
import static org.opends.server.loggers.ErrorLogger.*;
import static org.opends.server.loggers.debug.DebugLogger.*;
import static org.opends.server.util.StaticUtils.*;
import java.util.HashSet;
import org.opends.messages.Message;
import org.opends.server.api.ChangeNotificationListener;
import org.opends.server.api.ClientConnection;
import org.opends.server.api.SynchronizationProvider;
import org.opends.server.api.plugin.PluginResult;
import org.opends.server.backends.ndb.AbstractTransaction;
import org.opends.server.backends.ndb.BackendImpl;
import org.opends.server.core.AccessControlConfigManager;
import org.opends.server.core.AddOperation;
import org.opends.server.core.DirectoryServer;
import org.opends.server.core.PluginConfigManager;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.types.AttributeType;
import org.opends.server.types.CanceledOperationException;
import org.opends.server.types.DN;
import org.opends.server.types.DebugLogLevel;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.Entry;
import org.opends.server.types.ObjectClass;
import org.opends.server.types.Privilege;
import org.opends.server.types.ResultCode;
import org.opends.server.types.SynchronizationProviderResult;
import org.opends.server.workflowelement.localbackend.LocalBackendAddOperation;
import
  org.opends.server.workflowelement.localbackend.LocalBackendWorkflowElement;
/**
 * This class defines an operation used to add an entry in a local backend
 * of the Directory Server.
 */
public class NDBAddOperation
       extends LocalBackendAddOperation
{
  /**
   * The tracer object for the debug logger.
   */
  private static final DebugTracer TRACER = getTracer();
  /**
   * Creates a new operation that may be used to add a new entry in a
   * local backend of the Directory Server.
   *
   * @param add The operation to enhance.
   */
  public NDBAddOperation(AddOperation add)
  {
    super(add);
    NDBWorkflowElement.attachLocalOperation (add, this);
  }
  /**
   * Process this add operation against a local backend.
   *
   * @param  wfe The local backend work-flow element.
   *
   * @throws CanceledOperationException if this operation should be
   * cancelled
   */
  @Override
  public void processLocalAdd(final LocalBackendWorkflowElement wfe)
    throws CanceledOperationException {
    boolean executePostOpPlugins = false;
    this.backend = wfe.getBackend();
    BackendImpl ndbBackend = (BackendImpl) backend;
    ClientConnection clientConnection = getClientConnection();
    // Get the plugin config manager that will be used for invoking plugins.
    PluginConfigManager pluginConfigManager =
         DirectoryServer.getPluginConfigManager();
    // Check for a request to cancel this operation.
    checkIfCanceled(false);
    // Create a labeled block of code that we can break out of if a problem is
    // detected.
addProcessing:
    {
      // Process the entry DN and set of attributes to convert them from their
      // raw forms as provided by the client to the forms required for the rest
      // of the add processing.
      entryDN = getEntryDN();
      if (entryDN == null)
      {
        break addProcessing;
      }
      objectClasses = getObjectClasses();
      userAttributes = getUserAttributes();
      operationalAttributes = getOperationalAttributes();
      if ((objectClasses == null ) || (userAttributes == null) ||
          (operationalAttributes == null))
      {
        break addProcessing;
      }
      // Check for a request to cancel this operation.
      checkIfCanceled(false);
      DN parentDN = entryDN.getParentDNInSuffix();
      AbstractTransaction txn =
        new AbstractTransaction(ndbBackend.getRootContainer());
      try
      {
        // Check for a request to cancel this operation.
        checkIfCanceled(false);
        // Invoke any conflict resolution processing that might be needed by the
        // synchronization provider.
        for (SynchronizationProvider provider :
             DirectoryServer.getSynchronizationProviders())
        {
          try
          {
            SynchronizationProviderResult result =
                provider.handleConflictResolution(this);
            if (! result.continueProcessing())
            {
              setResultCode(result.getResultCode());
              appendErrorMessage(result.getErrorMessage());
              setMatchedDN(result.getMatchedDN());
              setReferralURLs(result.getReferralURLs());
              break addProcessing;
            }
          }
          catch (DirectoryException de)
          {
            if (debugEnabled())
            {
              TRACER.debugCaught(DebugLogLevel.ERROR, de);
            }
            logError(ERR_ADD_SYNCH_CONFLICT_RESOLUTION_FAILED.get(
                          getConnectionID(), getOperationID(),
                          getExceptionMessage(de)));
            setResponseData(de);
            break addProcessing;
          }
        }
        for (AttributeType at : userAttributes.keySet())
        {
          // If the attribute type is marked "NO-USER-MODIFICATION" then fail
          // unless this is an internal operation or is related to
          // synchronization in some way.
          // This must be done before running the password policy code
          // and any other code that may add attributes marked as
          // "NO-USER-MODIFICATION"
          //
          // Note that doing this checks at this time
          // of the processing does not make it possible for pre-parse plugins
          // to add NO-USER-MODIFICATION attributes to the entry.
          if (at.isNoUserModification())
          {
            if (! (isInternalOperation() || isSynchronizationOperation()))
            {
              setResultCode(ResultCode.UNWILLING_TO_PERFORM);
              appendErrorMessage(ERR_ADD_ATTR_IS_NO_USER_MOD.get(
                                      String.valueOf(entryDN),
                                      at.getNameOrOID()));
              break addProcessing;
            }
          }
        }
        for (AttributeType at : operationalAttributes.keySet())
        {
          if (at.isNoUserModification())
          {
            if (! (isInternalOperation() || isSynchronizationOperation()))
            {
              setResultCode(ResultCode.UNWILLING_TO_PERFORM);
              appendErrorMessage(ERR_ADD_ATTR_IS_NO_USER_MOD.get(
                                      String.valueOf(entryDN),
                                      at.getNameOrOID()));
              break addProcessing;
            }
          }
        }
        // Get the parent entry, if it exists.
        Entry parentEntry = null;
        if (parentDN != null)
        {
          try
          {
            parentEntry = ndbBackend.getEntryNoCommit(parentDN, txn,
              NdbOperation.LockMode.LM_Read);
            if (parentEntry == null)
            {
              DN matchedDN = parentDN.getParentDNInSuffix();
              while (matchedDN != null)
              {
                try
                {
                  if (DirectoryServer.entryExists(matchedDN))
                  {
                    setMatchedDN(matchedDN);
                    break;
                  }
                }
                catch (Exception e)
                {
                  if (debugEnabled())
                  {
                    TRACER.debugCaught(DebugLogLevel.ERROR, e);
                  }
                  break;
                }
                matchedDN = matchedDN.getParentDNInSuffix();
              }
              // The parent doesn't exist, so this add can't be successful.
              setResultCode(ResultCode.NO_SUCH_OBJECT);
              appendErrorMessage(ERR_ADD_NO_PARENT.get(String.valueOf(entryDN),
                                      String.valueOf(parentDN)));
              break addProcessing;
            }
          }
          catch (DirectoryException de)
          {
            if (debugEnabled())
            {
              TRACER.debugCaught(DebugLogLevel.ERROR, de);
            }
            setResponseData(de);
            break addProcessing;
          }
        }
        // Check to make sure that all of the RDN attributes are included as
        // attribute values.  If not, then either add them or report an error.
        try
        {
          addRDNAttributesIfNecessary();
        }
        catch (DirectoryException de)
        {
          if (debugEnabled())
          {
            TRACER.debugCaught(DebugLogLevel.ERROR, de);
          }
          setResponseData(de);
          break addProcessing;
        }
        // Check to make sure that all objectclasses have their superior classes
        // listed in the entry.  If not, then add them.
        HashSet<ObjectClass> additionalClasses = null;
        for (ObjectClass oc : objectClasses.keySet())
        {
          ObjectClass superiorClass = oc.getSuperiorClass();
          if ((superiorClass != null) &&
              (! objectClasses.containsKey(superiorClass)))
          {
            if (additionalClasses == null)
            {
              additionalClasses = new HashSet<ObjectClass>();
            }
            additionalClasses.add(superiorClass);
          }
        }
        if (additionalClasses != null)
        {
          for (ObjectClass oc : additionalClasses)
          {
            addObjectClassChain(oc);
          }
        }
        // Create an entry object to encapsulate the set of attributes and
        // objectclasses.
        entry = new Entry(entryDN, objectClasses, userAttributes,
                          operationalAttributes);
        // Check to see if the entry includes a privilege specification.  If so,
        // then the requester must have the PRIVILEGE_CHANGE privilege.
        AttributeType privType =
             DirectoryServer.getAttributeType(OP_ATTR_PRIVILEGE_NAME, true);
        if (entry.hasAttribute(privType) &&
            (! clientConnection.hasPrivilege(Privilege.PRIVILEGE_CHANGE, this)))
        {
          appendErrorMessage(
               ERR_ADD_CHANGE_PRIVILEGE_INSUFFICIENT_PRIVILEGES.get());
          setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
          break addProcessing;
        }
        // If it's not a synchronization operation, then check
        // to see if the entry contains one or more passwords and if they
        // are valid in accordance with the password policies associated with
        // the user.  Also perform any encoding that might be required by
        // password storage schemes.
        if (! isSynchronizationOperation())
        {
          try
          {
            handlePasswordPolicy();
          }
          catch (DirectoryException de)
          {
            if (debugEnabled())
            {
              TRACER.debugCaught(DebugLogLevel.ERROR, de);
            }
            setResponseData(de);
            break addProcessing;
          }
        }
        // If the server is configured to check schema and the
        // operation is not a synchronization operation,
        // check to see if the entry is valid according to the server schema,
        // and also whether its attributes are valid according to their syntax.
        if ((DirectoryServer.checkSchema()) && (! isSynchronizationOperation()))
        {
          try
          {
            checkSchema(parentEntry);
          }
          catch (DirectoryException de)
          {
            if (debugEnabled())
            {
              TRACER.debugCaught(DebugLogLevel.ERROR, de);
            }
            setResponseData(de);
            break addProcessing;
          }
        }
        // Get the backend in which the add is to be performed.
        if (backend == null)
        {
          setResultCode(ResultCode.NO_SUCH_OBJECT);
          appendErrorMessage(Message.raw("No backend for entry " +
                                         entryDN.toString())); // TODO: i18n
          break addProcessing;
        }
        // Check to see if there are any controls in the request. If so, then
        // see if there is any special processing required.
        try
        {
          processControls(parentDN);
        }
        catch (DirectoryException de)
        {
          if (debugEnabled())
          {
            TRACER.debugCaught(DebugLogLevel.ERROR, de);
          }
          setResponseData(de);
          break addProcessing;
        }
        // Check to see if the client has permission to perform the add.
        // FIXME: for now assume that this will check all permission
        // pertinent to the operation. This includes proxy authorization
        // and any other controls specified.
        // FIXME: earlier checks to see if the entry already exists or
        // if the parent entry does not exist may have already exposed
        // sensitive information to the client.
        if (AccessControlConfigManager.getInstance().getAccessControlHandler().
                 isAllowed(this) == false)
        {
          setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
          appendErrorMessage(ERR_ADD_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS.get(
                                  String.valueOf(entryDN)));
          break addProcessing;
        }
        // Check for a request to cancel this operation.
        checkIfCanceled(false);
        // If the operation is not a synchronization operation,
        // Invoke the pre-operation add plugins.
        if (! isSynchronizationOperation())
        {
          executePostOpPlugins = true;
          PluginResult.PreOperation preOpResult =
            pluginConfigManager.invokePreOperationAddPlugins(this);
          if (!preOpResult.continueProcessing())
          {
            setResultCode(preOpResult.getResultCode());
            appendErrorMessage(preOpResult.getErrorMessage());
            setMatchedDN(preOpResult.getMatchedDN());
            setReferralURLs(preOpResult.getReferralURLs());
            break addProcessing;
          }
        }
        // If it is not a private backend, then check to see if the server or
        // backend is operating in read-only mode.
        if (! backend.isPrivateBackend())
        {
          switch (DirectoryServer.getWritabilityMode())
          {
            case DISABLED:
              setResultCode(ResultCode.UNWILLING_TO_PERFORM);
              appendErrorMessage(ERR_ADD_SERVER_READONLY.get(
                                      String.valueOf(entryDN)));
              break addProcessing;
            case INTERNAL_ONLY:
              if (! (isInternalOperation() || isSynchronizationOperation()))
              {
                setResultCode(ResultCode.UNWILLING_TO_PERFORM);
                appendErrorMessage(ERR_ADD_SERVER_READONLY.get(
                                        String.valueOf(entryDN)));
                break addProcessing;
              }
              break;
          }
          switch (backend.getWritabilityMode())
          {
            case DISABLED:
              setResultCode(ResultCode.UNWILLING_TO_PERFORM);
              appendErrorMessage(ERR_ADD_BACKEND_READONLY.get(
                                      String.valueOf(entryDN)));
              break addProcessing;
            case INTERNAL_ONLY:
              if (! (isInternalOperation() || isSynchronizationOperation()))
              {
                setResultCode(ResultCode.UNWILLING_TO_PERFORM);
                appendErrorMessage(ERR_ADD_BACKEND_READONLY.get(
                                        String.valueOf(entryDN)));
                break addProcessing;
              }
              break;
          }
        }
        try
        {
          if (noOp)
          {
            appendErrorMessage(INFO_ADD_NOOP.get());
            setResultCode(ResultCode.NO_OPERATION);
          }
          else
          {
            for (SynchronizationProvider provider :
                 DirectoryServer.getSynchronizationProviders())
            {
              try
              {
                SynchronizationProviderResult result =
                    provider.doPreOperation(this);
                if (! result.continueProcessing())
                {
                  setResultCode(result.getResultCode());
                  appendErrorMessage(result.getErrorMessage());
                  setMatchedDN(result.getMatchedDN());
                  setReferralURLs(result.getReferralURLs());
                  break addProcessing;
                }
              }
              catch (DirectoryException de)
              {
                if (debugEnabled())
                {
                  TRACER.debugCaught(DebugLogLevel.ERROR, de);
                }
                logError(ERR_ADD_SYNCH_PREOP_FAILED.get(getConnectionID(),
                              getOperationID(), getExceptionMessage(de)));
                setResponseData(de);
                break addProcessing;
              }
            }
            ndbBackend.addEntry(entry, this, txn);
          }
          if (postReadRequest != null)
          {
            addPostReadResponse();
          }
          if (! noOp)
          {
            setResultCode(ResultCode.SUCCESS);
          }
        }
        catch (DirectoryException de)
        {
          if (debugEnabled())
          {
            TRACER.debugCaught(DebugLogLevel.ERROR, de);
          }
          setResponseData(de);
          break addProcessing;
        }
      }
      finally
      {
        for (SynchronizationProvider provider :
          DirectoryServer.getSynchronizationProviders())
        {
          try
          {
            provider.doPostOperation(this);
          }
          catch (DirectoryException de)
          {
            if (debugEnabled())
            {
              TRACER.debugCaught(DebugLogLevel.ERROR, de);
            }
            logError(ERR_ADD_SYNCH_POSTOP_FAILED.get(getConnectionID(),
                getOperationID(), getExceptionMessage(de)));
            setResponseData(de);
            break;
          }
        }
        try {
          txn.close();
        } catch (Exception ex) {
          if (debugEnabled()) {
            TRACER.debugCaught(DebugLogLevel.ERROR, ex);
          }
        }
      }
    }
    // Invoke the post-operation or post-synchronization add plugins.
    if (isSynchronizationOperation())
    {
      if (getResultCode() == ResultCode.SUCCESS)
      {
        pluginConfigManager.invokePostSynchronizationAddPlugins(this);
      }
    }
    else if (executePostOpPlugins)
    {
      // FIXME -- Should this also be done while holding the locks?
      PluginResult.PostOperation postOpResult =
          pluginConfigManager.invokePostOperationAddPlugins(this);
      if(!postOpResult.continueProcessing())
      {
        setResultCode(postOpResult.getResultCode());
        appendErrorMessage(postOpResult.getErrorMessage());
        setMatchedDN(postOpResult.getMatchedDN());
        setReferralURLs(postOpResult.getReferralURLs());
        return;
      }
    }
    // Register a post-response call-back which will notify persistent
    // searches and change listeners.
    if (getResultCode() == ResultCode.SUCCESS)
    {
      registerPostResponseCallback(new Runnable()
      {
        public void run()
        {
          // Notify change listeners.
          for (ChangeNotificationListener changeListener : DirectoryServer
              .getChangeNotificationListeners())
          {
            try
            {
              changeListener.handleAddOperation(NDBAddOperation.this, entry);
            }
            catch (Exception e)
            {
              if (debugEnabled())
              {
                TRACER.debugCaught(DebugLogLevel.ERROR, e);
              }
              logError(ERR_ADD_ERROR_NOTIFYING_CHANGE_LISTENER
                  .get(getExceptionMessage(e)));
            }
          }
        }
      });
    }
  }
}
opends/src/server/org/opends/server/workflowelement/ndb/NDBBindOperation.java
New file
@@ -0,0 +1,251 @@
/*
 * 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/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * 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/opends/resource/legal-notices/OpenDS.LICENSE.  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.
 */
package org.opends.server.workflowelement.ndb;
import java.util.List;
import org.opends.messages.Message;
import org.opends.server.api.plugin.PluginResult;
import org.opends.server.core.BindOperation;
import org.opends.server.core.DirectoryServer;
import org.opends.server.core.PasswordPolicyState;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.types.AccountStatusNotification;
import org.opends.server.types.AccountStatusNotificationType;
import org.opends.server.types.Attribute;
import org.opends.server.types.AttributeType;
import org.opends.server.types.AuthenticationInfo;
import org.opends.server.types.ByteString;
import org.opends.server.types.DebugLogLevel;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.DN;
import org.opends.server.types.Entry;
import org.opends.server.types.ResultCode;
import org.opends.server.workflowelement.localbackend.LocalBackendBindOperation;
import static org.opends.messages.CoreMessages.*;
import static org.opends.server.loggers.debug.DebugLogger.*;
import static org.opends.server.util.StaticUtils.*;
/**
 * This class defines an operation used to bind against the Directory Server,
 * with the bound user entry within a local backend.
 */
public class NDBBindOperation
       extends LocalBackendBindOperation
{
  /**
   * The tracer object for the debug logger.
   */
  private static final DebugTracer TRACER = getTracer();
  /**
   * Creates a new operation that may be used to bind where
   * the bound user entry is stored in a local backend of the Directory Server.
   *
   * @param bind The operation to enhance.
   */
  public NDBBindOperation(BindOperation bind)
  {
    super(bind);
    NDBWorkflowElement.attachLocalOperation (bind, this);
  }
  /**
   * Performs the processing necessary for a simple bind operation.
   *
   * @return  {@code true} if processing should continue for the operation, or
   *          {@code false} if not.
   *
   * @throws  DirectoryException  If a problem occurs that should cause the bind
   *                              operation to fail.
   */
  @Override
  protected boolean processSimpleBind()
          throws DirectoryException
  {
    // See if this is an anonymous bind.  If so, then determine whether
    // to allow it.
    ByteString simplePassword = getSimplePassword();
    if ((simplePassword == null) || (simplePassword.length() == 0))
    {
      return processAnonymousSimpleBind();
    }
    // See if the bind DN is actually one of the alternate root DNs
    // defined in the server.  If so, then replace it with the actual DN
    // for that user.
    DN actualRootDN = DirectoryServer.getActualRootBindDN(bindDN);
    if (actualRootDN != null)
    {
      bindDN = actualRootDN;
    }
    // Get the user entry based on the bind DN.  If it does not exist,
    // then fail.
    Entry userEntry;
    try {
      userEntry = backend.getEntry(bindDN);
    } catch (DirectoryException de) {
      if (debugEnabled()) {
        TRACER.debugCaught(DebugLogLevel.ERROR, de);
      }
      userEntry = null;
      throw new DirectoryException(ResultCode.INVALID_CREDENTIALS,
        de.getMessageObject());
    }
    if (userEntry == null) {
      throw new DirectoryException(ResultCode.INVALID_CREDENTIALS,
        ERR_BIND_OPERATION_UNKNOWN_USER.get(
        String.valueOf(bindDN)));
    } else {
      setUserEntryDN(userEntry.getDN());
    }
    // Check to see if the user has a password.  If not, then fail.
    // FIXME -- We need to have a way to enable/disable debugging.
    pwPolicyState = new PasswordPolicyState(userEntry, false);
    policy = pwPolicyState.getPolicy();
    AttributeType pwType = policy.getPasswordAttribute();
    List<Attribute> pwAttr = userEntry.getAttribute(pwType);
    if ((pwAttr == null) || (pwAttr.isEmpty())) {
      throw new DirectoryException(ResultCode.INVALID_CREDENTIALS,
        ERR_BIND_OPERATION_NO_PASSWORD.get(
        String.valueOf(bindDN)));
    }
    // Perform a number of password policy state checks for the user.
    checkPasswordPolicyState(userEntry, null);
    // Invoke the pre-operation bind plugins.
    executePostOpPlugins = true;
    PluginResult.PreOperation preOpResult =
      pluginConfigManager.invokePreOperationBindPlugins(this);
    if (!preOpResult.continueProcessing()) {
      setResultCode(preOpResult.getResultCode());
      appendErrorMessage(preOpResult.getErrorMessage());
      setMatchedDN(preOpResult.getMatchedDN());
      setReferralURLs(preOpResult.getReferralURLs());
      return false;
    }
    // Determine whether the provided password matches any of the stored
    // passwords for the user.
    if (pwPolicyState.passwordMatches(simplePassword)) {
      setResultCode(ResultCode.SUCCESS);
      boolean isRoot = DirectoryServer.isRootDN(userEntry.getDN());
      if (DirectoryServer.lockdownMode() && (!isRoot)) {
        throw new DirectoryException(ResultCode.INVALID_CREDENTIALS,
          ERR_BIND_REJECTED_LOCKDOWN_MODE.get());
      }
      setAuthenticationInfo(new AuthenticationInfo(userEntry,
        simplePassword,
        isRoot));
      // Set resource limits for the authenticated user.
      setResourceLimits(userEntry);
      // Perform any remaining processing for a successful simple
      // authentication.
      pwPolicyState.handleDeprecatedStorageSchemes(simplePassword);
      pwPolicyState.clearFailureLockout();
      if (isFirstWarning) {
        pwPolicyState.setWarnedTime();
        int numSeconds = pwPolicyState.getSecondsUntilExpiration();
        Message m = WARN_BIND_PASSWORD_EXPIRING.get(
          secondsToTimeString(numSeconds));
        pwPolicyState.generateAccountStatusNotification(
          AccountStatusNotificationType.PASSWORD_EXPIRING, userEntry, m,
          AccountStatusNotification.createProperties(pwPolicyState,
          false, numSeconds, null, null));
      }
      if (isGraceLogin) {
        pwPolicyState.updateGraceLoginTimes();
      }
      pwPolicyState.setLastLoginTime();
    } else {
      setResultCode(ResultCode.INVALID_CREDENTIALS);
      setAuthFailureReason(ERR_BIND_OPERATION_WRONG_PASSWORD.get());
      if (policy.getLockoutFailureCount() > 0) {
        pwPolicyState.updateAuthFailureTimes();
        if (pwPolicyState.lockedDueToFailures()) {
          AccountStatusNotificationType notificationType;
          Message m;
          boolean tempLocked;
          int lockoutDuration = pwPolicyState.getSecondsUntilUnlock();
          if (lockoutDuration > -1) {
            notificationType =
              AccountStatusNotificationType.ACCOUNT_TEMPORARILY_LOCKED;
            tempLocked = true;
            m = ERR_BIND_ACCOUNT_TEMPORARILY_LOCKED.get(
              secondsToTimeString(lockoutDuration));
          } else {
            notificationType =
              AccountStatusNotificationType.ACCOUNT_PERMANENTLY_LOCKED;
            tempLocked = false;
            m = ERR_BIND_ACCOUNT_PERMANENTLY_LOCKED.get();
          }
          pwPolicyState.generateAccountStatusNotification(
            notificationType, userEntry, m,
            AccountStatusNotification.createProperties(pwPolicyState,
            tempLocked, -1, null, null));
        }
      }
    }
    return true;
  }
}
opends/src/server/org/opends/server/workflowelement/ndb/NDBCompareOperation.java
New file
@@ -0,0 +1,309 @@
/*
 * 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/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * 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/opends/resource/legal-notices/OpenDS.LICENSE.  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.
 */
package org.opends.server.workflowelement.ndb;
import java.util.HashSet;
import java.util.List;
import org.opends.server.api.ClientConnection;
import org.opends.server.api.plugin.PluginResult;
import org.opends.server.core.AccessControlConfigManager;
import org.opends.server.core.CompareOperation;
import org.opends.server.core.DirectoryServer;
import org.opends.server.core.PluginConfigManager;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.types.*;
import org.opends.server.types.operation.PostOperationCompareOperation;
import org.opends.server.types.operation.PostResponseCompareOperation;
import org.opends.server.types.operation.PreOperationCompareOperation;
import
  org.opends.server.workflowelement.localbackend.LocalBackendCompareOperation;
import
  org.opends.server.workflowelement.localbackend.LocalBackendWorkflowElement;
import static org.opends.messages.CoreMessages.*;
import static org.opends.server.loggers.debug.DebugLogger.*;
import static org.opends.server.util.StaticUtils.*;
/**
 * This class defines an operation that may be used to determine whether a
 * specified entry in the Directory Server contains a given attribute-value
 * pair.
 */
public class NDBCompareOperation
       extends LocalBackendCompareOperation
       implements PreOperationCompareOperation, PostOperationCompareOperation,
                  PostResponseCompareOperation
{
  /**
   * The tracer object for the debug logger.
   */
  private static final DebugTracer TRACER = getTracer();
  /**
   * Creates a new compare operation based on the provided compare operation.
   *
   * @param compare  the compare operation
   */
  public NDBCompareOperation(CompareOperation compare)
  {
    super(compare);
    NDBWorkflowElement.attachLocalOperation (compare, this);
  }
  /**
   * Process this compare operation in a NDB backend.
   *
   * @param  wfe The local backend work-flow element.
   *
   * @throws CanceledOperationException if this operation should be
   * cancelled
   */
  @Override
  public void processLocalCompare(LocalBackendWorkflowElement wfe)
    throws CanceledOperationException {
    boolean executePostOpPlugins = false;
    this.backend = wfe.getBackend();
    clientConnection  = getClientConnection();
    // Get the plugin config manager that will be used for invoking plugins.
    PluginConfigManager pluginConfigManager =
         DirectoryServer.getPluginConfigManager();
    // Get a reference to the client connection
    ClientConnection clientConnection = getClientConnection();
    // Check for a request to cancel this operation.
    checkIfCanceled(false);
    // Create a labeled block of code that we can break out of if a problem is
    // detected.
compareProcessing:
    {
      // Process the entry DN to convert it from the raw form to the form
      // required for the rest of the compare processing.
      entryDN = getEntryDN();
      if (entryDN == null)
      {
        break compareProcessing;
      }
      // If the target entry is in the server configuration, then make sure the
      // requester has the CONFIG_READ privilege.
      if (DirectoryServer.getConfigHandler().handlesEntry(entryDN) &&
          (! clientConnection.hasPrivilege(Privilege.CONFIG_READ, this)))
      {
        appendErrorMessage(ERR_COMPARE_CONFIG_INSUFFICIENT_PRIVILEGES.get());
        setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
        break compareProcessing;
      }
      // Check for a request to cancel this operation.
      checkIfCanceled(false);
      // Get the entry.  If it does not exist, then fail.
      try {
        entry = DirectoryServer.getEntry(entryDN);
        if (entry == null) {
          setResultCode(ResultCode.NO_SUCH_OBJECT);
          appendErrorMessage(
            ERR_COMPARE_NO_SUCH_ENTRY.get(String.valueOf(entryDN)));
          // See if one of the entry's ancestors exists.
          DN parentDN = entryDN.getParentDNInSuffix();
          while (parentDN != null) {
            try {
              if (DirectoryServer.entryExists(parentDN)) {
                setMatchedDN(parentDN);
                break;
              }
            } catch (Exception e) {
              if (debugEnabled()) {
                TRACER.debugCaught(DebugLogLevel.ERROR, e);
              }
              break;
            }
            parentDN = parentDN.getParentDNInSuffix();
          }
          break compareProcessing;
        }
      } catch (DirectoryException de) {
        if (debugEnabled()) {
          TRACER.debugCaught(DebugLogLevel.ERROR, de);
        }
        setResultCode(de.getResultCode());
        appendErrorMessage(de.getMessageObject());
        break compareProcessing;
      }
      // Check to see if there are any controls in the request.  If so, then
      // see if there is any special processing required.
      try {
        handleRequestControls();
      } catch (DirectoryException de) {
        if (debugEnabled()) {
          TRACER.debugCaught(DebugLogLevel.ERROR, de);
        }
        setResponseData(de);
        break compareProcessing;
      }
      // Check to see if the client has permission to perform the
      // compare.
      // FIXME: for now assume that this will check all permission
      // pertinent to the operation. This includes proxy authorization
      // and any other controls specified.
      // FIXME: earlier checks to see if the entry already exists may
      // have already exposed sensitive information to the client.
      if (!AccessControlConfigManager.getInstance().
        getAccessControlHandler().isAllowed(this)) {
        setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
        appendErrorMessage(ERR_COMPARE_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS.get(
          String.valueOf(entryDN)));
        break compareProcessing;
      }
      // Check for a request to cancel this operation.
      checkIfCanceled(false);
      // Invoke the pre-operation compare plugins.
      executePostOpPlugins = true;
      PluginResult.PreOperation preOpResult =
        pluginConfigManager.invokePreOperationComparePlugins(this);
      if (!preOpResult.continueProcessing()) {
        setResultCode(preOpResult.getResultCode());
        appendErrorMessage(preOpResult.getErrorMessage());
        setMatchedDN(preOpResult.getMatchedDN());
        setReferralURLs(preOpResult.getReferralURLs());
        break compareProcessing;
      }
      // Get the base attribute type and set of options.
      String baseName;
      HashSet<String> options;
      String rawAttributeType = getRawAttributeType();
      int semicolonPos = rawAttributeType.indexOf(';');
      if (semicolonPos > 0) {
        baseName = toLowerCase(rawAttributeType.substring(0, semicolonPos));
        options = new HashSet<String>();
        int nextPos = rawAttributeType.indexOf(';', semicolonPos + 1);
        while (nextPos > 0) {
          options.add(rawAttributeType.substring(semicolonPos + 1, nextPos));
          semicolonPos = nextPos;
          nextPos = rawAttributeType.indexOf(';', semicolonPos + 1);
        }
        options.add(rawAttributeType.substring(semicolonPos + 1));
      } else {
        baseName = toLowerCase(rawAttributeType);
        options = null;
      }
      // Actually perform the compare operation.
      AttributeType attrType = getAttributeType();
      if (attrType == null) {
        attrType = DirectoryServer.getAttributeType(baseName, true);
        setAttributeType(attrType);
      }
      List<Attribute> attrList = entry.getAttribute(attrType, options);
      if ((attrList == null) || attrList.isEmpty()) {
        setResultCode(ResultCode.NO_SUCH_ATTRIBUTE);
        if (options == null) {
          appendErrorMessage(WARN_COMPARE_OP_NO_SUCH_ATTR.get(
            String.valueOf(entryDN), baseName));
        } else {
          appendErrorMessage(WARN_COMPARE_OP_NO_SUCH_ATTR_WITH_OPTIONS.get(
            String.valueOf(entryDN), baseName));
        }
      } else {
        AttributeValue value = AttributeValues.create(attrType,
          getAssertionValue());
        boolean matchFound = false;
        for (Attribute a : attrList) {
          if (a.contains(value)) {
            matchFound = true;
            break;
          }
        }
        if (matchFound) {
          setResultCode(ResultCode.COMPARE_TRUE);
        } else {
          setResultCode(ResultCode.COMPARE_FALSE);
        }
      }
    }
    // Check for a request to cancel this operation.
    checkIfCanceled(false);
    // Invoke the post-operation compare plugins.
    if (executePostOpPlugins)
    {
      PluginResult.PostOperation postOpResult =
           pluginConfigManager.invokePostOperationComparePlugins(this);
      if (!postOpResult.continueProcessing())
      {
        setResultCode(postOpResult.getResultCode());
        appendErrorMessage(postOpResult.getErrorMessage());
        setMatchedDN(postOpResult.getMatchedDN());
        setReferralURLs(postOpResult.getReferralURLs());
      }
    }
  }
}
opends/src/server/org/opends/server/workflowelement/ndb/NDBDeleteOperation.java
New file
@@ -0,0 +1,428 @@
/*
 * 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/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * 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/opends/resource/legal-notices/OpenDS.LICENSE.  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.
 */
package org.opends.server.workflowelement.ndb;
import com.mysql.cluster.ndbj.NdbOperation;
import org.opends.messages.Message;
import org.opends.server.api.Backend;
import org.opends.server.api.ChangeNotificationListener;
import org.opends.server.api.plugin.PluginResult;
import org.opends.server.backends.ndb.AbstractTransaction;
import org.opends.server.backends.ndb.BackendImpl;
import org.opends.server.core.AccessControlConfigManager;
import org.opends.server.core.DeleteOperation;
import org.opends.server.core.DirectoryServer;
import org.opends.server.core.PluginConfigManager;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.types.CanceledOperationException;
import org.opends.server.types.DebugLogLevel;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.DN;
import org.opends.server.types.ResultCode;
import org.opends.server.types.operation.PostOperationDeleteOperation;
import org.opends.server.types.operation.PostResponseDeleteOperation;
import org.opends.server.types.operation.PreOperationDeleteOperation;
import org.opends.server.types.operation.PostSynchronizationDeleteOperation;
import
  org.opends.server.workflowelement.localbackend.LocalBackendDeleteOperation;
import
  org.opends.server.workflowelement.localbackend.LocalBackendWorkflowElement;
import static org.opends.messages.CoreMessages.*;
import static org.opends.server.loggers.ErrorLogger.*;
import static org.opends.server.loggers.debug.DebugLogger.*;
import static org.opends.server.util.StaticUtils.*;
/**
 * This class defines an operation used to delete an entry in a NDB backend
 * of the Directory Server.
 */
public class NDBDeleteOperation
       extends LocalBackendDeleteOperation
       implements PreOperationDeleteOperation, PostOperationDeleteOperation,
                  PostResponseDeleteOperation,
                  PostSynchronizationDeleteOperation
{
  /**
   * The tracer object for the debug logger.
   */
  private static final DebugTracer TRACER = getTracer();
  /**
   * Creates a new operation that may be used to delete an entry from a
   * NDB backend of the Directory Server.
   *
   * @param delete The operation to enhance.
   */
  public NDBDeleteOperation(DeleteOperation delete)
  {
    super(delete);
    NDBWorkflowElement.attachLocalOperation (delete, this);
  }
  /**
   * Process this delete operation in a NDB backend.
   *
   * @param  wfe The local backend work-flow element.
   *
   * @throws CanceledOperationException if this operation should be
   * cancelled
   */
  @Override
  public void processLocalDelete(final LocalBackendWorkflowElement wfe)
    throws CanceledOperationException {
    boolean executePostOpPlugins = false;
    this.backend = wfe.getBackend();
    BackendImpl ndbBackend = (BackendImpl) backend;
    clientConnection = getClientConnection();
    // Get the plugin config manager that will be used for invoking plugins.
    PluginConfigManager pluginConfigManager =
         DirectoryServer.getPluginConfigManager();
    // Check for a request to cancel this operation.
    checkIfCanceled(false);
    // Create a labeled block of code that we can break out of if a problem is
    // detected.
deleteProcessing:
    {
      // Process the entry DN to convert it from its raw form as provided by the
      // client to the form required for the rest of the delete processing.
      entryDN = getEntryDN();
      if (entryDN == null){
        break deleteProcessing;
      }
      AbstractTransaction txn =
        new AbstractTransaction(ndbBackend.getRootContainer());
      try
      {
        // Get the entry to delete.  If it doesn't exist, then fail.
        try
        {
          entry = ndbBackend.getEntryNoCommit(entryDN, txn,
            NdbOperation.LockMode.LM_Exclusive);
          if (entry == null)
          {
            setResultCode(ResultCode.NO_SUCH_OBJECT);
            appendErrorMessage(ERR_DELETE_NO_SUCH_ENTRY.get(
                                    String.valueOf(entryDN)));
            try
            {
              DN parentDN = entryDN.getParentDNInSuffix();
              while (parentDN != null)
              {
                if (DirectoryServer.entryExists(parentDN))
                {
                  setMatchedDN(parentDN);
                  break;
                }
                parentDN = parentDN.getParentDNInSuffix();
              }
            }
            catch (Exception e)
            {
              if (debugEnabled())
              {
                TRACER.debugCaught(DebugLogLevel.ERROR, e);
              }
            }
            break deleteProcessing;
          }
        }
        catch (DirectoryException de)
        {
          if (debugEnabled())
          {
            TRACER.debugCaught(DebugLogLevel.ERROR, de);
          }
          setResponseData(de);
          break deleteProcessing;
        }
        if(!handleConflictResolution()) {
            break deleteProcessing;
        }
        // Check to see if the client has permission to perform the
        // delete.
        // Check to see if there are any controls in the request.  If so, then
        // see if there is any special processing required.
        try
        {
          handleRequestControls();
        }
        catch (DirectoryException de)
        {
          if (debugEnabled())
          {
            TRACER.debugCaught(DebugLogLevel.ERROR, de);
          }
          setResponseData(de);
          break deleteProcessing;
        }
        // FIXME: for now assume that this will check all permission
        // pertinent to the operation. This includes proxy authorization
        // and any other controls specified.
        // FIXME: earlier checks to see if the entry already exists may
        // have already exposed sensitive information to the client.
        if (! AccessControlConfigManager.getInstance().
                   getAccessControlHandler().isAllowed(this))
        {
          setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
          appendErrorMessage(ERR_DELETE_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS.get(
                                  String.valueOf(entryDN)));
          break deleteProcessing;
        }
        // Check for a request to cancel this operation.
        checkIfCanceled(false);
        // If the operation is not a synchronization operation,
        // invoke the pre-delete plugins.
        if (! isSynchronizationOperation())
        {
          executePostOpPlugins = true;
          PluginResult.PreOperation preOpResult =
               pluginConfigManager.invokePreOperationDeletePlugins(this);
          if (!preOpResult.continueProcessing())
          {
            setResultCode(preOpResult.getResultCode());
            appendErrorMessage(preOpResult.getErrorMessage());
            setMatchedDN(preOpResult.getMatchedDN());
            setReferralURLs(preOpResult.getReferralURLs());
            break deleteProcessing;
          }
        }
        // Get the backend to use for the delete.  If there is none, then fail.
        if (backend == null)
        {
          setResultCode(ResultCode.NO_SUCH_OBJECT);
          appendErrorMessage(ERR_DELETE_NO_SUCH_ENTRY.get(
                                  String.valueOf(entryDN)));
          break deleteProcessing;
        }
        // If it is not a private backend, then check to see if the server or
        // backend is operating in read-only mode.
        if (! backend.isPrivateBackend())
        {
          switch (DirectoryServer.getWritabilityMode())
          {
            case DISABLED:
              setResultCode(ResultCode.UNWILLING_TO_PERFORM);
              appendErrorMessage(ERR_DELETE_SERVER_READONLY.get(
                                      String.valueOf(entryDN)));
              break deleteProcessing;
            case INTERNAL_ONLY:
              if (! (isInternalOperation() || isSynchronizationOperation()))
              {
                setResultCode(ResultCode.UNWILLING_TO_PERFORM);
                appendErrorMessage(ERR_DELETE_SERVER_READONLY.get(
                                        String.valueOf(entryDN)));
                break deleteProcessing;
              }
          }
          switch (backend.getWritabilityMode())
          {
            case DISABLED:
              setResultCode(ResultCode.UNWILLING_TO_PERFORM);
              appendErrorMessage(ERR_DELETE_BACKEND_READONLY.get(
                                      String.valueOf(entryDN)));
              break deleteProcessing;
            case INTERNAL_ONLY:
              if (! (isInternalOperation() || isSynchronizationOperation()))
              {
                setResultCode(ResultCode.UNWILLING_TO_PERFORM);
                appendErrorMessage(ERR_DELETE_BACKEND_READONLY.get(
                                        String.valueOf(entryDN)));
                break deleteProcessing;
              }
          }
        }
        // The selected backend will have the responsibility of making sure that
        // the entry actually exists and does not have any children (or possibly
        // handling a subtree delete).  But we will need to check if there are
        // any subordinate backends that should stop us from attempting the
        // delete.
        Backend[] subBackends = backend.getSubordinateBackends();
        for (Backend b : subBackends)
        {
          DN[] baseDNs = b.getBaseDNs();
          for (DN dn : baseDNs)
          {
            if (dn.isDescendantOf(entryDN))
            {
              setResultCode(ResultCode.NOT_ALLOWED_ON_NONLEAF);
              appendErrorMessage(ERR_DELETE_HAS_SUB_BACKEND.get(
                                      String.valueOf(entryDN),
                                      String.valueOf(dn)));
              break deleteProcessing;
            }
          }
        }
        // Actually perform the delete.
        try
        {
          if (noOp)
          {
            setResultCode(ResultCode.NO_OPERATION);
            appendErrorMessage(INFO_DELETE_NOOP.get());
          }
          else
          {
              if(!processPreOperation()) {
                  break deleteProcessing;
              }
              ndbBackend.deleteEntry(entryDN, entry, this, txn);
          }
          processPreReadControl();
          if (! noOp)
          {
            setResultCode(ResultCode.SUCCESS);
          }
        }
        catch (DirectoryException de)
        {
          if (debugEnabled())
          {
            TRACER.debugCaught(DebugLogLevel.ERROR, de);
          }
          setResponseData(de);
          break deleteProcessing;
        }
      }
      finally
      {
        processSynchPostOperationPlugins();
        try {
          txn.close();
        } catch (Exception ex) {
          if (debugEnabled()) {
            TRACER.debugCaught(DebugLogLevel.ERROR, ex);
          }
        }
      }
    }
    // Invoke the post-operation or post-synchronization delete plugins.
    if (isSynchronizationOperation())
    {
      if (getResultCode() == ResultCode.SUCCESS)
      {
        pluginConfigManager.invokePostSynchronizationDeletePlugins(this);
      }
    }
    else if (executePostOpPlugins)
    {
      PluginResult.PostOperation postOpResult =
          pluginConfigManager.invokePostOperationDeletePlugins(this);
      if (!postOpResult.continueProcessing())
      {
        setResultCode(postOpResult.getResultCode());
        appendErrorMessage(postOpResult.getErrorMessage());
        setMatchedDN(postOpResult.getMatchedDN());
        setReferralURLs(postOpResult.getReferralURLs());
        return;
      }
    }
    // Register a post-response call-back which will notify persistent
    // searches and change listeners.
    if (getResultCode() == ResultCode.SUCCESS)
    {
      registerPostResponseCallback(new Runnable()
      {
        public void run()
        {
          // Notify change listeners.
          for (ChangeNotificationListener changeListener : DirectoryServer
              .getChangeNotificationListeners())
          {
            try
            {
              changeListener.handleDeleteOperation(
                  NDBDeleteOperation.this, entry);
            }
            catch (Exception e)
            {
              if (debugEnabled())
              {
                TRACER.debugCaught(DebugLogLevel.ERROR, e);
              }
              Message message = ERR_DELETE_ERROR_NOTIFYING_CHANGE_LISTENER
                  .get(getExceptionMessage(e));
              logError(message);
            }
          }
        }
      });
    }
  }
}
opends/src/server/org/opends/server/workflowelement/ndb/NDBModifyDNOperation.java
New file
@@ -0,0 +1,532 @@
/*
 * 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/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * 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/opends/resource/legal-notices/OpenDS.LICENSE.  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.
 */
package org.opends.server.workflowelement.ndb;
import com.mysql.cluster.ndbj.NdbOperation;
import java.util.List;
import org.opends.messages.Message;
import org.opends.server.api.Backend;
import org.opends.server.api.ChangeNotificationListener;
import org.opends.server.api.plugin.PluginResult;
import org.opends.server.backends.ndb.AbstractTransaction;
import org.opends.server.backends.ndb.BackendImpl;
import org.opends.server.core.AccessControlConfigManager;
import org.opends.server.core.DirectoryServer;
import org.opends.server.core.ModifyDNOperation;
import org.opends.server.core.PluginConfigManager;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.types.CanceledOperationException;
import org.opends.server.types.DebugLogLevel;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.DN;
import org.opends.server.types.Modification;
import org.opends.server.types.ResultCode;
import org.opends.server.types.operation.PostOperationModifyDNOperation;
import org.opends.server.types.operation.PostResponseModifyDNOperation;
import org.opends.server.types.operation.PreOperationModifyDNOperation;
import org.opends.server.types.operation.PostSynchronizationModifyDNOperation;
import
  org.opends.server.workflowelement.localbackend.LocalBackendModifyDNOperation;
import
  org.opends.server.workflowelement.localbackend.LocalBackendWorkflowElement;
import static org.opends.messages.CoreMessages.*;
import static org.opends.server.loggers.ErrorLogger.*;
import static org.opends.server.loggers.debug.DebugLogger.*;
import static org.opends.server.util.StaticUtils.*;
/**
 * This class defines an operation used to move an entry in a NDB backend
 * of the Directory Server.
 */
public class NDBModifyDNOperation
  extends LocalBackendModifyDNOperation
  implements PreOperationModifyDNOperation,
             PostOperationModifyDNOperation,
             PostResponseModifyDNOperation,
             PostSynchronizationModifyDNOperation
{
  /**
   * The tracer object for the debug logger.
   */
  private static final DebugTracer TRACER = getTracer();
  /**
   * Creates a new operation that may be used to move an entry in a
   * NDB backend of the Directory Server.
   *
   * @param operation The operation to enhance.
   */
  public NDBModifyDNOperation (ModifyDNOperation operation)
  {
    super(operation);
    NDBWorkflowElement.attachLocalOperation (operation, this);
  }
  /**
   * Process this modify DN operation in a NDB backend.
   *
   * @param  wfe The local backend work-flow element.
   *
   * @throws CanceledOperationException if this operation should be
   * cancelled
   */
  @Override
  public void processLocalModifyDN(final LocalBackendWorkflowElement wfe)
    throws CanceledOperationException {
    boolean executePostOpPlugins = false;
    this.backend = wfe.getBackend();
    BackendImpl ndbBackend = (BackendImpl) backend;
    clientConnection = getClientConnection();
    // Get the plugin config manager that will be used for invoking plugins.
    PluginConfigManager pluginConfigManager =
         DirectoryServer.getPluginConfigManager();
    // Check for a request to cancel this operation.
    checkIfCanceled(false);
    // Create a labeled block of code that we can break out of if a problem is
    // detected.
modifyDNProcessing:
    {
      // Process the entry DN, newRDN, and newSuperior elements from their raw
      // forms as provided by the client to the forms required for the rest of
      // the modify DN processing.
      entryDN = getEntryDN();
      newRDN = getNewRDN();
      if (newRDN == null)
      {
        break modifyDNProcessing;
      }
      DN newSuperior = getNewSuperior();
      if ((newSuperior == null) &&
          (getRawNewSuperior() != null))
      {
        break modifyDNProcessing;
      }
      // Construct the new DN to use for the entry.
      DN parentDN;
      if (newSuperior == null)
      {
        parentDN = entryDN.getParentDNInSuffix();
      }
      else
      {
        if(newSuperior.isDescendantOf(entryDN))
        {
          setResultCode(ResultCode.UNWILLING_TO_PERFORM);
          appendErrorMessage(ERR_MODDN_NEW_SUPERIOR_IN_SUBTREE.get(
              String.valueOf(entryDN), String.valueOf(newSuperior)));
          break modifyDNProcessing;
        }
        parentDN = newSuperior;
      }
      if ((parentDN == null) || parentDN.isNullDN())
      {
        setResultCode(ResultCode.UNWILLING_TO_PERFORM);
        appendErrorMessage(ERR_MODDN_NO_PARENT.get(String.valueOf(entryDN)));
        break modifyDNProcessing;
      }
      DN newDN = parentDN.concat(newRDN);
      // Get the backend for the current entry, and the backend for the new
      // entry.  If either is null, or if they are different, then fail.
      Backend currentBackend = backend;
      if (currentBackend == null)
      {
        setResultCode(ResultCode.NO_SUCH_OBJECT);
        appendErrorMessage(ERR_MODDN_NO_BACKEND_FOR_CURRENT_ENTRY.get(
                                String.valueOf(entryDN)));
        break modifyDNProcessing;
      }
      Backend newBackend = DirectoryServer.getBackend(newDN);
      if (newBackend == null)
      {
        setResultCode(ResultCode.NO_SUCH_OBJECT);
        appendErrorMessage(ERR_MODDN_NO_BACKEND_FOR_NEW_ENTRY.get(
                                String.valueOf(entryDN),
                                String.valueOf(newDN)));
        break modifyDNProcessing;
      }
      else if (! currentBackend.equals(newBackend))
      {
        setResultCode(ResultCode.UNWILLING_TO_PERFORM);
        appendErrorMessage(ERR_MODDN_DIFFERENT_BACKENDS.get(
                                String.valueOf(entryDN),
                                String.valueOf(newDN)));
        break modifyDNProcessing;
      }
      // Check for a request to cancel this operation.
      checkIfCanceled(false);
      AbstractTransaction txn =
        new AbstractTransaction(ndbBackend.getRootContainer());
      try
      {
        // Get the current entry from the appropriate backend.  If it doesn't
        // exist, then fail.
        try
        {
          currentEntry = ndbBackend.getEntryNoCommit(entryDN, txn,
            NdbOperation.LockMode.LM_Exclusive);
        }
        catch (DirectoryException de)
        {
          if (debugEnabled())
          {
            TRACER.debugCaught(DebugLogLevel.ERROR, de);
          }
          setResponseData(de);
          break modifyDNProcessing;
        }
        if (getOriginalEntry() == null)
        {
          // See if one of the entry's ancestors exists.
          parentDN = entryDN.getParentDNInSuffix();
          while (parentDN != null)
          {
            try
            {
              if (DirectoryServer.entryExists(parentDN))
              {
                setMatchedDN(parentDN);
                break;
              }
            }
            catch (Exception e)
            {
              if (debugEnabled())
              {
                TRACER.debugCaught(DebugLogLevel.ERROR, e);
              }
              break;
            }
            parentDN = parentDN.getParentDNInSuffix();
          }
          setResultCode(ResultCode.NO_SUCH_OBJECT);
          appendErrorMessage(ERR_MODDN_NO_CURRENT_ENTRY.get(
                                  String.valueOf(entryDN)));
          break modifyDNProcessing;
        }
        if(!handleConflictResolution()) {
            break modifyDNProcessing;
        }
        // Check to see if there are any controls in the request.  If so, then
        // see if there is any special processing required.
        try
        {
          handleRequestControls();
        }
        catch (DirectoryException de)
        {
          if (debugEnabled())
          {
            TRACER.debugCaught(DebugLogLevel.ERROR, de);
          }
          setResponseData(de);
          break modifyDNProcessing;
        }
        // Check to see if the client has permission to perform the
        // modify DN.
        // FIXME: for now assume that this will check all permission
        // pertinent to the operation. This includes proxy authorization
        // and any other controls specified.
        // FIXME: earlier checks to see if the entry or new superior
        // already exists may have already exposed sensitive information
        // to the client.
        if (! AccessControlConfigManager.getInstance().
                   getAccessControlHandler().isAllowed(this))
        {
          setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
          appendErrorMessage(ERR_MODDN_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS.get(
                                  String.valueOf(entryDN)));
          break modifyDNProcessing;
        }
        // Duplicate the entry and set its new DN.  Also, create an empty list
        // to hold the attribute-level modifications.
        newEntry = currentEntry.duplicate(false);
        newEntry.setDN(newDN);
        // init the modifications
        addModification(null);
        List<Modification> modifications = this.getModifications();
        // Apply any changes to the entry based on the change in its RDN.  Also,
        // perform schema checking on the updated entry.
        try
        {
          applyRDNChanges(modifications);
        }
        catch (DirectoryException de)
        {
          if (debugEnabled())
          {
            TRACER.debugCaught(DebugLogLevel.ERROR, de);
          }
          setResponseData(de);
          break modifyDNProcessing;
        }
        // Check for a request to cancel this operation.
        checkIfCanceled(false);
        // Get a count of the current number of modifications.  The
        // pre-operation plugins may alter this list, and we need to be able to
        // identify which changes were made after they're done.
        int modCount = modifications.size();
        // If the operation is not a synchronization operation,
        // Invoke the pre-operation modify DN plugins.
        if (! isSynchronizationOperation())
        {
          executePostOpPlugins = true;
          PluginResult.PreOperation preOpResult =
              pluginConfigManager.invokePreOperationModifyDNPlugins(this);
          if (!preOpResult.continueProcessing())
          {
            setResultCode(preOpResult.getResultCode());
            appendErrorMessage(preOpResult.getErrorMessage());
            setMatchedDN(preOpResult.getMatchedDN());
            setReferralURLs(preOpResult.getReferralURLs());
            break modifyDNProcessing;
          }
        }
        // Check to see if any of the pre-operation plugins made any changes to
        // the entry.  If so, then apply them.
        if (modifications.size() > modCount)
        {
          try
          {
            applyPreOpModifications(modifications, modCount);
          }
          catch (DirectoryException de)
          {
            if (debugEnabled())
            {
              TRACER.debugCaught(DebugLogLevel.ERROR, de);
            }
            setResponseData(de);
            break modifyDNProcessing;
          }
        }
        // Actually perform the modify DN operation.
        // This should include taking
        // care of any synchronization that might be needed.
        try
        {
          // If it is not a private backend, then check to see if the server or
          // backend is operating in read-only mode.
          if (! currentBackend.isPrivateBackend())
          {
            switch (DirectoryServer.getWritabilityMode())
            {
              case DISABLED:
                setResultCode(ResultCode.UNWILLING_TO_PERFORM);
                appendErrorMessage(ERR_MODDN_SERVER_READONLY.get(
                                        String.valueOf(entryDN)));
                break modifyDNProcessing;
              case INTERNAL_ONLY:
                if (! (isInternalOperation() || isSynchronizationOperation()))
                {
                  setResultCode(ResultCode.UNWILLING_TO_PERFORM);
                  appendErrorMessage(ERR_MODDN_SERVER_READONLY.get(
                                          String.valueOf(entryDN)));
                  break modifyDNProcessing;
                }
            }
            switch (currentBackend.getWritabilityMode())
            {
              case DISABLED:
                setResultCode(ResultCode.UNWILLING_TO_PERFORM);
                appendErrorMessage(ERR_MODDN_BACKEND_READONLY.get(
                                        String.valueOf(entryDN)));
                break modifyDNProcessing;
              case INTERNAL_ONLY:
                if (! (isInternalOperation() || isSynchronizationOperation()))
                {
                  setResultCode(ResultCode.UNWILLING_TO_PERFORM);
                  appendErrorMessage(ERR_MODDN_BACKEND_READONLY.get(
                                          String.valueOf(entryDN)));
                  break modifyDNProcessing;
                }
            }
          }
          if (noOp)
          {
            appendErrorMessage(INFO_MODDN_NOOP.get());
            setResultCode(ResultCode.NO_OPERATION);
          }
          else
          {
              if(!processPreOperation()) {
                  break modifyDNProcessing;
              }
              ndbBackend.renameEntry(entryDN, newEntry, this, txn);
          }
          // Attach the pre-read and/or post-read controls to the response if
          // appropriate.
          processReadEntryControls();
          if (! noOp)
          {
            setResultCode(ResultCode.SUCCESS);
          }
        }
        catch (DirectoryException de)
        {
          if (debugEnabled())
          {
            TRACER.debugCaught(DebugLogLevel.ERROR, de);
          }
          setResponseData(de);
          break modifyDNProcessing;
        }
      }
      finally
      {
        processSynchPostOperationPlugins();
        try {
          txn.close();
        } catch (Exception ex) {
          if (debugEnabled()) {
            TRACER.debugCaught(DebugLogLevel.ERROR, ex);
          }
        }
      }
    }
    // Invoke the post-operation or post-synchronization modify DN plugins.
    if (isSynchronizationOperation())
    {
      if (getResultCode() == ResultCode.SUCCESS)
      {
        pluginConfigManager.invokePostSynchronizationModifyDNPlugins(this);
      }
    }
    else if (executePostOpPlugins)
    {
      PluginResult.PostOperation postOpResult =
           pluginConfigManager.invokePostOperationModifyDNPlugins(this);
      if (!postOpResult.continueProcessing())
      {
        setResultCode(postOpResult.getResultCode());
        appendErrorMessage(postOpResult.getErrorMessage());
        setMatchedDN(postOpResult.getMatchedDN());
        setReferralURLs(postOpResult.getReferralURLs());
        return;
      }
    }
    // Register a post-response call-back which will notify persistent
    // searches and change listeners.
    if (getResultCode() == ResultCode.SUCCESS)
    {
      registerPostResponseCallback(new Runnable()
      {
        public void run()
        {
          // Notify change listeners.
          for (ChangeNotificationListener changeListener : DirectoryServer
              .getChangeNotificationListeners())
          {
            try
            {
              changeListener.handleModifyDNOperation(
                  NDBModifyDNOperation.this, currentEntry, newEntry);
            }
            catch (Exception e)
            {
              if (debugEnabled())
              {
                TRACER.debugCaught(DebugLogLevel.ERROR, e);
              }
              Message message = ERR_MODDN_ERROR_NOTIFYING_CHANGE_LISTENER
                  .get(getExceptionMessage(e));
              logError(message);
            }
          }
        }
      });
    }
  }
}
opends/src/server/org/opends/server/workflowelement/ndb/NDBModifyOperation.java
New file
@@ -0,0 +1,529 @@
/*
 * 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/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * 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/opends/resource/legal-notices/OpenDS.LICENSE.  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.
 */
package org.opends.server.workflowelement.ndb;
import com.mysql.cluster.ndbj.NdbOperation;
import org.opends.messages.Message;
import static org.opends.messages.CoreMessages.*;
import static org.opends.server.loggers.ErrorLogger.*;
import static org.opends.server.loggers.debug.DebugLogger.*;
import static org.opends.server.util.ServerConstants.*;
import static org.opends.server.util.StaticUtils.*;
import org.opends.messages.MessageBuilder;
import org.opends.server.api.ChangeNotificationListener;
import org.opends.server.api.plugin.PluginResult;
import org.opends.server.backends.ndb.AbstractTransaction;
import org.opends.server.backends.ndb.BackendImpl;
import org.opends.server.controls.PasswordPolicyErrorType;
import org.opends.server.controls.PasswordPolicyResponseControl;
import org.opends.server.core.AccessControlConfigManager;
import org.opends.server.core.DirectoryServer;
import org.opends.server.core.ModifyOperation;
import org.opends.server.core.PasswordPolicyState;
import org.opends.server.core.PluginConfigManager;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.types.CanceledOperationException;
import org.opends.server.types.Control;
import org.opends.server.types.DN;
import org.opends.server.types.DebugLogLevel;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.ResultCode;
import org.opends.server.types.operation.PostOperationModifyOperation;
import org.opends.server.types.operation.PostResponseModifyOperation;
import org.opends.server.types.operation.PostSynchronizationModifyOperation;
import org.opends.server.types.operation.PreOperationModifyOperation;
import org.opends.server.util.TimeThread;
import
  org.opends.server.workflowelement.localbackend.LocalBackendModifyOperation;
import
  org.opends.server.workflowelement.localbackend.LocalBackendWorkflowElement;
/**
 * This class defines an operation used to modify an entry in a NDB backend
 * of the Directory Server.
 */
public class NDBModifyOperation
       extends LocalBackendModifyOperation
       implements PreOperationModifyOperation, PostOperationModifyOperation,
                  PostResponseModifyOperation,
                  PostSynchronizationModifyOperation
{
  /**
   * The tracer object for the debug logger.
   */
  private static final DebugTracer TRACER = getTracer();
  /**
   * Creates a new operation that may be used to modify an entry in a
   * NDB backend of the Directory Server.
   *
   * @param modify The operation to enhance.
   */
  public NDBModifyOperation(ModifyOperation modify)
  {
    super(modify);
    NDBWorkflowElement.attachLocalOperation (modify, this);
  }
  /**
   * Process this modify operation against a NDB backend.
   *
   * @param  wfe The local backend work-flow element.
   *
   * @throws CanceledOperationException if this operation should be
   * cancelled
   */
  @Override
  public void processLocalModify(final LocalBackendWorkflowElement wfe)
    throws CanceledOperationException {
    boolean executePostOpPlugins = false;
    this.backend = wfe.getBackend();
    BackendImpl ndbBackend = (BackendImpl) backend;
    clientConnection = getClientConnection();
    // Get the plugin config manager that will be used for invoking plugins.
    PluginConfigManager pluginConfigManager =
      DirectoryServer.getPluginConfigManager();
    // Check for a request to cancel this operation.
    checkIfCanceled(false);
    // Create a labeled block of code that we can break out of if a problem is
    // detected.
modifyProcessing:
    {
      entryDN = getEntryDN();
      if (entryDN == null){
        break modifyProcessing;
      }
      // Process the modifications to convert them from their raw form to the
      // form required for the rest of the modify processing.
      modifications = getModifications();
      if (modifications == null)
      {
        break modifyProcessing;
      }
      if (modifications.isEmpty())
      {
        setResultCode(ResultCode.CONSTRAINT_VIOLATION);
        appendErrorMessage(ERR_MODIFY_NO_MODIFICATIONS.get(
                                String.valueOf(entryDN)));
        break modifyProcessing;
      }
      // If the user must change their password before doing anything else, and
      // if the target of the modify operation isn't the user's own entry, then
      // reject the request.
      if ((! isInternalOperation()) && clientConnection.mustChangePassword())
      {
        DN authzDN = getAuthorizationDN();
        if ((authzDN != null) && (! authzDN.equals(entryDN)))
        {
          // The user will not be allowed to do anything else before the
          // password gets changed.  Also note that we haven't yet checked the
          // request controls so we need to do that now to see if the password
          // policy request control was provided.
          for (Control c : getRequestControls())
          {
            if (c.getOID().equals(OID_PASSWORD_POLICY_CONTROL))
            {
              pwPolicyControlRequested = true;
              pwpErrorType = PasswordPolicyErrorType.CHANGE_AFTER_RESET;
              break;
            }
          }
          setResultCode(ResultCode.UNWILLING_TO_PERFORM);
          appendErrorMessage(ERR_MODIFY_MUST_CHANGE_PASSWORD.get());
          break modifyProcessing;
        }
      }
      // Check for a request to cancel this operation.
      checkIfCanceled(false);
      AbstractTransaction txn =
        new AbstractTransaction(ndbBackend.getRootContainer());
      try
      {
        // Check for a request to cancel this operation.
        checkIfCanceled(false);
        try
        {
          // Get the entry to modify.  If it does not exist, then fail.
          currentEntry = ndbBackend.getEntryNoCommit(entryDN, txn,
            NdbOperation.LockMode.LM_Exclusive);
          if (currentEntry == null)
          {
            setResultCode(ResultCode.NO_SUCH_OBJECT);
            appendErrorMessage(ERR_MODIFY_NO_SUCH_ENTRY.get(
                String.valueOf(entryDN)));
            // See if one of the entry's ancestors exists.
            try
            {
              DN parentDN = entryDN.getParentDNInSuffix();
              while (parentDN != null)
              {
                if (DirectoryServer.entryExists(parentDN))
                {
                  setMatchedDN(parentDN);
                  break;
                }
                parentDN = parentDN.getParentDNInSuffix();
              }
            }
            catch (Exception e)
            {
              if (debugEnabled())
              {
                TRACER.debugCaught(DebugLogLevel.ERROR, e);
              }
            }
            break modifyProcessing;
          }
          // Check to see if there are any controls in the request.  If so, then
          // see if there is any special processing required.
          processRequestControls();
          // Get the password policy state object for the entry that can be used
          // to perform any appropriate password policy processing.  Also, see
          // if the entry is being updated by the end user or an administrator.
          selfChange = entryDN.equals(getAuthorizationDN());
          // FIXME -- Need a way to enable debug mode.
          pwPolicyState = new PasswordPolicyState(currentEntry, false,
                                                  TimeThread.getTime(), true);
        }
        catch (DirectoryException de)
        {
          if (debugEnabled())
          {
            TRACER.debugCaught(DebugLogLevel.ERROR, de);
          }
          setResponseData(de);
          break modifyProcessing;
        }
        // Create a duplicate of the entry and apply the changes to it.
        modifiedEntry = currentEntry.duplicate(false);
        if (! noOp)
        {
            if(!handleConflictResolution()) {
                break modifyProcessing;
            }
        }
        try
        {
          handleSchemaProcessing();
        }
        catch (DirectoryException de)
        {
          if (debugEnabled())
          {
            TRACER.debugCaught(DebugLogLevel.ERROR, de);
          }
          setResponseData(de);
          break modifyProcessing;
        }
        // Check to see if the client has permission to perform the modify.
        // The access control check is not made any earlier because the handler
        // needs access to the modified entry.
        // FIXME: for now assume that this will check all permissions
        // pertinent to the operation. This includes proxy authorization
        // and any other controls specified.
        // FIXME: earlier checks to see if the entry already exists may have
        // already exposed sensitive information to the client.
        if (! AccessControlConfigManager.getInstance().
                   getAccessControlHandler().isAllowed(this))
        {
          setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
          appendErrorMessage(ERR_MODIFY_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS.get(
                                  String.valueOf(entryDN)));
          break modifyProcessing;
        }
        try
        {
          handleInitialPasswordPolicyProcessing();
          wasLocked = false;
          if (passwordChanged)
          {
            performAdditionalPasswordChangedProcessing();
          }
        }
        catch (DirectoryException de)
        {
          if (debugEnabled())
          {
            TRACER.debugCaught(DebugLogLevel.ERROR, de);
          }
          setResponseData(de);
          break modifyProcessing;
        }
        if ((! passwordChanged) && (! isInternalOperation()) &&
            pwPolicyState.mustChangePassword())
        {
          // The user will not be allowed to do anything else before the
          // password gets changed.
          pwpErrorType = PasswordPolicyErrorType.CHANGE_AFTER_RESET;
          setResultCode(ResultCode.UNWILLING_TO_PERFORM);
          appendErrorMessage(ERR_MODIFY_MUST_CHANGE_PASSWORD.get());
          break modifyProcessing;
        }
        // If the server is configured to check the schema and the
        // operation is not a sycnhronization operation,
        // make sure that the new entry is valid per the server schema.
        if ((DirectoryServer.checkSchema()) && (! isSynchronizationOperation()))
        {
          MessageBuilder invalidReason = new MessageBuilder();
          if (! modifiedEntry.conformsToSchema(null, false, false, false,
              invalidReason))
          {
            setResultCode(ResultCode.OBJECTCLASS_VIOLATION);
            appendErrorMessage(ERR_MODIFY_VIOLATES_SCHEMA.get(
                                    String.valueOf(entryDN), invalidReason));
            break modifyProcessing;
          }
        }
        // Check for a request to cancel this operation.
        checkIfCanceled(false);
        // If the operation is not a synchronization operation,
        // Invoke the pre-operation modify plugins.
        if (! isSynchronizationOperation())
        {
          executePostOpPlugins = true;
          PluginResult.PreOperation preOpResult =
            pluginConfigManager.invokePreOperationModifyPlugins(this);
          if (!preOpResult.continueProcessing())
          {
            setResultCode(preOpResult.getResultCode());
            appendErrorMessage(preOpResult.getErrorMessage());
            setMatchedDN(preOpResult.getMatchedDN());
            setReferralURLs(preOpResult.getReferralURLs());
            break modifyProcessing;
          }
        }
        // Actually perform the modify operation.  This should also include
        // taking care of any synchronization that might be needed.
        if (backend == null)
        {
          setResultCode(ResultCode.NO_SUCH_OBJECT);
          appendErrorMessage(ERR_MODIFY_NO_BACKEND_FOR_ENTRY.get(
                                  String.valueOf(entryDN)));
          break modifyProcessing;
        }
        try
        {
          try
          {
            checkWritability();
          }
          catch (DirectoryException de)
          {
            if (debugEnabled())
            {
              TRACER.debugCaught(DebugLogLevel.ERROR, de);
            }
            setResponseData(de);
            break modifyProcessing;
          }
          if (noOp)
          {
            appendErrorMessage(INFO_MODIFY_NOOP.get());
            setResultCode(ResultCode.NO_OPERATION);
          }
          else
          {
              if(!processPreOperation()) {
                  break modifyProcessing;
              }
            ndbBackend.replaceEntry(currentEntry, modifiedEntry, this, txn);
            // See if we need to generate any account status notifications as a
            // result of the changes.
            if (passwordChanged || enabledStateChanged || wasLocked)
            {
              handleAccountStatusNotifications();
            }
          }
          // Handle any processing that may be needed for the pre-read and/or
          // post-read controls.
          handleReadEntryProcessing();
          if (! noOp)
          {
            setResultCode(ResultCode.SUCCESS);
          }
        }
        catch (DirectoryException de)
        {
          if (debugEnabled())
          {
            TRACER.debugCaught(DebugLogLevel.ERROR, de);
          }
          setResponseData(de);
          break modifyProcessing;
        }
      }
      finally
      {
        processSynchPostOperationPlugins();
        try {
          txn.close();
        } catch (Exception ex) {
          if (debugEnabled()) {
            TRACER.debugCaught(DebugLogLevel.ERROR, ex);
          }
        }
      }
    }
    // If the password policy request control was included, then make sure we
    // send the corresponding response control.
    if (pwPolicyControlRequested)
    {
      addResponseControl(new PasswordPolicyResponseControl(null, 0,
                                                           pwpErrorType));
    }
    // Invoke the post-operation or post-synchronization modify plugins.
    if (isSynchronizationOperation())
    {
      if (getResultCode() == ResultCode.SUCCESS)
      {
        pluginConfigManager.invokePostSynchronizationModifyPlugins(this);
      }
    }
    else if (executePostOpPlugins)
    {
      // FIXME -- Should this also be done while holding the locks?
      PluginResult.PostOperation postOpResult =
           pluginConfigManager.invokePostOperationModifyPlugins(this);
      if (!postOpResult.continueProcessing())
      {
        setResultCode(postOpResult.getResultCode());
        appendErrorMessage(postOpResult.getErrorMessage());
        setMatchedDN(postOpResult.getMatchedDN());
        setReferralURLs(postOpResult.getReferralURLs());
        return;
      }
    }
    // Register a post-response call-back which will notify persistent
    // searches and change listeners.
    if (getResultCode() == ResultCode.SUCCESS)
    {
      registerPostResponseCallback(new Runnable()
      {
        public void run()
        {
          // Notify change listeners.
          for (ChangeNotificationListener changeListener : DirectoryServer
              .getChangeNotificationListeners())
          {
            try
            {
              changeListener
                  .handleModifyOperation(NDBModifyOperation.this,
                      currentEntry, modifiedEntry);
            }
            catch (Exception e)
            {
              if (debugEnabled())
              {
                TRACER.debugCaught(DebugLogLevel.ERROR, e);
              }
              Message message = ERR_MODIFY_ERROR_NOTIFYING_CHANGE_LISTENER
                  .get(getExceptionMessage(e));
              logError(message);
            }
          }
        }
      });
    }
  }
}
opends/src/server/org/opends/server/workflowelement/ndb/NDBSearchOperation.java
New file
@@ -0,0 +1,437 @@
/*
 * 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/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * 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/opends/resource/legal-notices/OpenDS.LICENSE.  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.
 */
package org.opends.server.workflowelement.ndb;
import java.util.List;
import org.opends.server.api.plugin.PluginResult;
import org.opends.server.controls.LDAPAssertionRequestControl;
import org.opends.server.controls.MatchedValuesControl;
import org.opends.server.controls.ProxiedAuthV1Control;
import org.opends.server.controls.ProxiedAuthV2Control;
import org.opends.server.core.AccessControlConfigManager;
import org.opends.server.core.DirectoryServer;
import org.opends.server.core.PluginConfigManager;
import org.opends.server.core.SearchOperation;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.types.CanceledOperationException;
import org.opends.server.types.Control;
import org.opends.server.types.DN;
import org.opends.server.types.DebugLogLevel;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.Entry;
import org.opends.server.types.Privilege;
import org.opends.server.types.ResultCode;
import org.opends.server.types.SearchFilter;
import org.opends.server.types.operation.PostOperationSearchOperation;
import org.opends.server.types.operation.PreOperationSearchOperation;
import org.opends.server.types.operation.SearchEntrySearchOperation;
import org.opends.server.types.operation.SearchReferenceSearchOperation;
import
  org.opends.server.workflowelement.localbackend.LocalBackendSearchOperation;
import
  org.opends.server.workflowelement.localbackend.LocalBackendWorkflowElement;
import static org.opends.messages.CoreMessages.*;
import static org.opends.server.loggers.debug.DebugLogger.*;
import static org.opends.server.util.ServerConstants.*;
import static org.opends.server.util.StaticUtils.*;
/**
 * This class defines an operation used to search for entries in a NDB backend
 * of the Directory Server.
 */
public class NDBSearchOperation
       extends LocalBackendSearchOperation
       implements PreOperationSearchOperation, PostOperationSearchOperation,
                  SearchEntrySearchOperation, SearchReferenceSearchOperation
{
  /**
   * The tracer object for the debug logger.
   */
  private static final DebugTracer TRACER = getTracer();
  /**
   * Creates a new operation that may be used to search for entries in a NDB
   * backend of the Directory Server.
   *
   * @param  search  The operation to process.
   */
  public NDBSearchOperation(SearchOperation search)
  {
    super(search);
    NDBWorkflowElement.attachLocalOperation(search, this);
  }
  /**
   * Process this search operation against a NDB backend.
   *
   * @param  wfe The local backend work-flow element.
   *
   * @throws CanceledOperationException if this operation should be
   * cancelled
   */
  @Override
  public void processLocalSearch(LocalBackendWorkflowElement wfe)
    throws CanceledOperationException {
    boolean executePostOpPlugins = false;
    this.backend = wfe.getBackend();
    clientConnection = getClientConnection();
    // Get the plugin config manager that will be used for invoking plugins.
    PluginConfigManager pluginConfigManager =
      DirectoryServer.getPluginConfigManager();
    processSearch = true;
    // Check for a request to cancel this operation.
    checkIfCanceled(false);
    // Create a labeled block of code that we can break out of if a problem is
    // detected.
searchProcessing:
    {
      // Process the search base and filter to convert them from their raw forms
      // as provided by the client to the forms required for the rest of the
      // search processing.
      baseDN = getBaseDN();
      filter = getFilter();
      if ((baseDN == null) || (filter == null)){
        break searchProcessing;
      }
      // Check to see if there are any controls in the request.  If so, then
      // see if there is any special processing required.
      try
      {
        handleRequestControls();
      }
      catch (DirectoryException de)
      {
        if (debugEnabled())
        {
          TRACER.debugCaught(DebugLogLevel.ERROR, de);
        }
        setResponseData(de);
        break searchProcessing;
      }
      // Check to see if the client has permission to perform the
      // search.
      // FIXME: for now assume that this will check all permission
      // pertinent to the operation. This includes proxy authorization
      // and any other controls specified.
      if (! AccessControlConfigManager.getInstance().getAccessControlHandler().
                 isAllowed(this))
      {
        setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
        appendErrorMessage(ERR_SEARCH_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS.get(
                                String.valueOf(baseDN)));
        break searchProcessing;
      }
      // Check for a request to cancel this operation.
      checkIfCanceled(false);
      // Invoke the pre-operation search plugins.
      executePostOpPlugins = true;
      PluginResult.PreOperation preOpResult =
          pluginConfigManager.invokePreOperationSearchPlugins(this);
      if (!preOpResult.continueProcessing())
      {
        setResultCode(preOpResult.getResultCode());
        appendErrorMessage(preOpResult.getErrorMessage());
        setMatchedDN(preOpResult.getMatchedDN());
        setReferralURLs(preOpResult.getReferralURLs());
        break searchProcessing;
      }
      // Check for a request to cancel this operation.
      checkIfCanceled(false);
      // Get the backend that should hold the search base.  If there is none,
      // then fail.
      if (backend == null)
      {
        setResultCode(ResultCode.NO_SUCH_OBJECT);
        appendErrorMessage(ERR_SEARCH_BASE_DOESNT_EXIST.get(
                                String.valueOf(baseDN)));
        break searchProcessing;
      }
      // We'll set the result code to "success".  If a problem occurs, then it
      // will be overwritten.
      setResultCode(ResultCode.SUCCESS);
      // Process the search in the backend and all its subordinates.
      try
      {
        if (processSearch)
        {
          backend.search(this);
        }
      }
      catch (DirectoryException de)
      {
        if (debugEnabled())
        {
          TRACER.debugCaught(DebugLogLevel.VERBOSE, de);
        }
        setResponseData(de);
        break searchProcessing;
      }
      catch (CanceledOperationException coe)
      {
        throw coe;
      }
      catch (Exception e)
      {
        if (debugEnabled())
        {
          TRACER.debugCaught(DebugLogLevel.ERROR, e);
        }
        setResultCode(DirectoryServer.getServerErrorResultCode());
        appendErrorMessage(ERR_SEARCH_BACKEND_EXCEPTION.get(
                                getExceptionMessage(e)));
        break searchProcessing;
      }
    }
    // Check for a request to cancel this operation.
    checkIfCanceled(false);
    // Invoke the post-operation search plugins.
    if (executePostOpPlugins)
    {
      PluginResult.PostOperation postOpResult =
           pluginConfigManager.invokePostOperationSearchPlugins(this);
      if (!postOpResult.continueProcessing())
      {
        setResultCode(postOpResult.getResultCode());
        appendErrorMessage(postOpResult.getErrorMessage());
        setMatchedDN(postOpResult.getMatchedDN());
        setReferralURLs(postOpResult.getReferralURLs());
      }
    }
  }
  /**
   * Handles any controls contained in the request.
   *
   * @throws  DirectoryException  If there is a problem with any of the request
   *                              controls.
   */
  @Override
  protected void handleRequestControls()
          throws DirectoryException
  {
    List<Control> requestControls  = getRequestControls();
    if ((requestControls != null) && (! requestControls.isEmpty()))
    {
      for (int i=0; i < requestControls.size(); i++)
      {
        Control c   = requestControls.get(i);
        String  oid = c.getOID();
        if (! AccessControlConfigManager.getInstance().
                   getAccessControlHandler().isAllowed(baseDN, this, c))
        {
          throw new DirectoryException(ResultCode.INSUFFICIENT_ACCESS_RIGHTS,
                         ERR_CONTROL_INSUFFICIENT_ACCESS_RIGHTS.get(oid));
        }
        if (oid.equals(OID_LDAP_ASSERTION))
        {
          LDAPAssertionRequestControl assertControl =
                getRequestControl(LDAPAssertionRequestControl.DECODER);
          try
          {
            // FIXME -- We need to determine whether the current user has
            //          permission to make this determination.
            SearchFilter assertionFilter = assertControl.getSearchFilter();
            Entry entry;
            try
            {
              entry = DirectoryServer.getEntry(baseDN);
            }
            catch (DirectoryException de)
            {
              if (debugEnabled())
              {
                TRACER.debugCaught(DebugLogLevel.ERROR, de);
              }
              throw new DirectoryException(de.getResultCode(),
                             ERR_SEARCH_CANNOT_GET_ENTRY_FOR_ASSERTION.get(
                                  de.getMessageObject()));
            }
            if (entry == null)
            {
              throw new DirectoryException(ResultCode.NO_SUCH_OBJECT,
                             ERR_SEARCH_NO_SUCH_ENTRY_FOR_ASSERTION.get());
            }
            if (! assertionFilter.matchesEntry(entry))
            {
              throw new DirectoryException(ResultCode.ASSERTION_FAILED,
                                           ERR_SEARCH_ASSERTION_FAILED.get());
            }
          }
          catch (DirectoryException de)
          {
            if (de.getResultCode() == ResultCode.ASSERTION_FAILED)
            {
              throw de;
            }
            if (debugEnabled())
            {
              TRACER.debugCaught(DebugLogLevel.ERROR, de);
            }
            throw new DirectoryException(ResultCode.PROTOCOL_ERROR,
                           ERR_SEARCH_CANNOT_PROCESS_ASSERTION_FILTER.get(
                                de.getMessageObject()), de);
          }
        }
        else if (oid.equals(OID_PROXIED_AUTH_V1))
        {
          // The requester must have the PROXIED_AUTH privilige in order to be
          // able to use this control.
          if (! clientConnection.hasPrivilege(Privilege.PROXIED_AUTH, this))
          {
            throw new DirectoryException(ResultCode.AUTHORIZATION_DENIED,
                           ERR_PROXYAUTH_INSUFFICIENT_PRIVILEGES.get());
          }
          ProxiedAuthV1Control proxyControl =
              getRequestControl(ProxiedAuthV1Control.DECODER);
          Entry authorizationEntry = proxyControl.getAuthorizationEntry();
          setAuthorizationEntry(authorizationEntry);
          if (authorizationEntry == null)
          {
            setProxiedAuthorizationDN(DN.nullDN());
          }
          else
          {
            setProxiedAuthorizationDN(authorizationEntry.getDN());
          }
        }
        else if (oid.equals(OID_PROXIED_AUTH_V2))
        {
          // The requester must have the PROXIED_AUTH privilige in order to be
          // able to use this control.
          if (! clientConnection.hasPrivilege(Privilege.PROXIED_AUTH, this))
          {
            throw new DirectoryException(ResultCode.AUTHORIZATION_DENIED,
                           ERR_PROXYAUTH_INSUFFICIENT_PRIVILEGES.get());
          }
          ProxiedAuthV2Control proxyControl =
              getRequestControl(ProxiedAuthV2Control.DECODER);
          Entry authorizationEntry = proxyControl.getAuthorizationEntry();
          setAuthorizationEntry(authorizationEntry);
          if (authorizationEntry == null)
          {
            setProxiedAuthorizationDN(DN.nullDN());
          }
          else
          {
            setProxiedAuthorizationDN(authorizationEntry.getDN());
          }
        }
        else if (oid.equals(OID_LDAP_SUBENTRIES))
        {
          setReturnLDAPSubentries(true);
        }
        else if (oid.equals(OID_MATCHED_VALUES))
        {
          MatchedValuesControl matchedValuesControl =
                getRequestControl(MatchedValuesControl.DECODER);
          setMatchedValuesControl(matchedValuesControl);
        }
        else if (oid.equals(OID_ACCOUNT_USABLE_CONTROL))
        {
          setIncludeUsableControl(true);
        }
        else if (oid.equals(OID_REAL_ATTRS_ONLY))
        {
          setRealAttributesOnly(true);
        }
        else if (oid.equals(OID_VIRTUAL_ATTRS_ONLY))
        {
          setVirtualAttributesOnly(true);
        }
        else if (oid.equals(OID_GET_EFFECTIVE_RIGHTS) &&
          DirectoryServer.isSupportedControl(OID_GET_EFFECTIVE_RIGHTS))
        {
          // Do nothing here and let AciHandler deal with it.
        }
        // NYI -- Add support for additional controls.
        else if (c.isCritical())
        {
          if ((backend == null) || (! backend.supportsControl(oid)))
          {
            throw new DirectoryException(
                           ResultCode.UNAVAILABLE_CRITICAL_EXTENSION,
                           ERR_SEARCH_UNSUPPORTED_CRITICAL_CONTROL.get(oid));
          }
        }
      }
    }
  }
}
opends/src/server/org/opends/server/workflowelement/ndb/NDBWorkflowElement.java
New file
@@ -0,0 +1,436 @@
/*
 * 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/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * 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/opends/resource/legal-notices/OpenDS.LICENSE.  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.
 */
package org.opends.server.workflowelement.ndb;
import java.util.ArrayList;
import java.util.List;
import java.util.TreeMap;
import org.opends.messages.Message;
import org.opends.server.admin.server.ServerManagementContext;
import org.opends.server.admin.std.server.BackendCfg;
import org.opends.server.admin.std.server.LocalBackendWorkflowElementCfg;
import org.opends.server.admin.std.server.RootCfg;
import org.opends.server.api.Backend;
import org.opends.server.config.ConfigException;
import org.opends.server.core.AddOperation;
import org.opends.server.core.BindOperation;
import org.opends.server.core.CompareOperation;
import org.opends.server.core.DeleteOperation;
import org.opends.server.core.DirectoryServer;
import org.opends.server.core.ModifyDNOperation;
import org.opends.server.core.ModifyOperation;
import org.opends.server.core.SearchOperation;
import org.opends.server.types.*;
import
  org.opends.server.workflowelement.localbackend.LocalBackendWorkflowElement;
import static org.opends.server.config.ConfigConstants.*;
/**
 * This class defines a NDB backend workflow element; e-g an entity that
 * handle the processing of an operation against a NDB backend.
 */
public class NDBWorkflowElement extends LocalBackendWorkflowElement
{
  // The backend associated with the NDB workflow element.
  private Backend backend;
  // The set of NDB backend workflow elements registered with the server.
  private static TreeMap<String, NDBWorkflowElement>
       registeredNDBBackends =
            new TreeMap<String, NDBWorkflowElement>();
  // The lock to guarantee safe concurrent access to the
  // registeredNDBBackends variable.
  private static final Object registeredNDBBackendsLock = new Object();
  // The string indicating the type of the workflow element.
  private final String BACKEND_WORKFLOW_ELEMENT = "Backend";
  /**
   * Creates a new instance of the NDB backend workflow element.
   */
  public NDBWorkflowElement()
  {
    // There is nothing to do in this constructor.
  }
  /**
   * Initializes a new instance of the NDB backend workflow element.
   * This method is intended to be called by DirectoryServer when
   * workflow configuration mode is auto as opposed to
   * initializeWorkflowElement which is invoked when workflow
   * configuration mode is manual.
   *
   * @param workflowElementID  the workflow element identifier
   * @param backend  the backend associated to that workflow element
   */
  private void initialize(String workflowElementID, Backend backend)
  {
    // Initialize the workflow ID
    super.initialize(workflowElementID, BACKEND_WORKFLOW_ELEMENT);
    this.backend  = backend;
    if (this.backend != null)
    {
      setPrivate(this.backend.isPrivateBackend());
    }
  }
  /**
   * Initializes a new instance of the NDB backend workflow element.
   * This method is intended to be called by DirectoryServer when
   * workflow configuration mode is manual as opposed to
   * initialize(String,Backend) which is invoked when workflow
   * configuration mode is auto.
   *
   * @param  configuration  The configuration for this NDB backend
   *                        workflow element.
   *
   * @throws  ConfigException  If there is a problem with the provided
   *                           configuration.
   *
   * @throws  InitializationException  If an error occurs while trying
   *                                   to initialize this workflow
   *                                   element that is not related to
   *                                   the provided configuration.
   */
  @Override
  public void initializeWorkflowElement(
      LocalBackendWorkflowElementCfg configuration
      ) throws ConfigException, InitializationException
  {
    configuration.addLocalBackendChangeListener(this);
    // Read configuration and apply changes.
    processWorkflowElementConfig(configuration, true);
  }
  /**
   * {@inheritDoc}
   */
  @Override
  public void finalizeWorkflowElement()
  {
    // null all fields so that any use of the finalized object will raise
    // an NPE
    super.initialize(null, null);
    backend = null;
  }
  /**
   * {@inheritDoc}
   */
  @Override
  public boolean isConfigurationChangeAcceptable(
      LocalBackendWorkflowElementCfg configuration,
      List<Message>                  unacceptableReasons
      )
  {
    boolean isAcceptable =
      processWorkflowElementConfig(configuration, false);
    return isAcceptable;
  }
  /**
   * {@inheritDoc}
   */
  @Override
  public ConfigChangeResult applyConfigurationChange(
      LocalBackendWorkflowElementCfg configuration
      )
  {
    // Returned result.
    ConfigChangeResult changeResult = new ConfigChangeResult(
        ResultCode.SUCCESS, false, new ArrayList<Message>()
        );
    processWorkflowElementConfig(configuration, true);
    return changeResult;
  }
  /**
   * Parses the provided configuration and configure the workflow element.
   *
   * @param configuration  The new configuration containing the changes.
   * @param applyChanges   If true then take into account the new configuration.
   *
   * @return  <code>true</code> if the configuration is acceptable.
   */
  private boolean processWorkflowElementConfig(
      LocalBackendWorkflowElementCfg configuration,
      boolean                        applyChanges
      )
  {
    // returned status
    boolean isAcceptable = true;
    // If the workflow element is disabled then do nothing. Note that the
    // configuration manager could have finalized the object right before.
    if (configuration.isEnabled())
    {
      // Read configuration.
      String newBackendID = configuration.getBackend();
      Backend newBackend  = DirectoryServer.getBackend(newBackendID);
      // If the backend is null (i.e. not found in the list of
      // registered backends, this is probably because we are looking
      // for the config backend
      if (newBackend == null) {
        ServerManagementContext context = ServerManagementContext.getInstance();
        RootCfg root = context.getRootConfiguration();
        try {
          BackendCfg backendCfg = root.getBackend(newBackendID);
          if (backendCfg.getBaseDN().contains(DN.decode(DN_CONFIG_ROOT))) {
            newBackend = DirectoryServer.getConfigHandler();
          }
        } catch (Exception ex) {
          // Unable to find the backend
          newBackend = null;
        }
      }
      // Get the new configuration
      if (applyChanges)
      {
        super.initialize(
          configuration.getWorkflowElementId(), BACKEND_WORKFLOW_ELEMENT);
        backend = newBackend;
      }
    }
    return isAcceptable;
  }
  /**
   * Creates and registers a NDB backend with the server.
   *
   * @param workflowElementID  the identifier of the workflow element to create
   * @param backend            the backend to associate with the NDB backend
   *                           workflow element
   *
   * @return the existing NDB backend workflow element if it was
   *         already created or a newly created NDB backend workflow
   *         element.
   */
  public static NDBWorkflowElement createAndRegister(
      String workflowElementID,
      Backend backend)
  {
    NDBWorkflowElement ndbBackend = null;
    // If the requested workflow element does not exist then create one.
    ndbBackend = registeredNDBBackends.get(workflowElementID);
    if (ndbBackend == null)
    {
      ndbBackend = new NDBWorkflowElement();
      ndbBackend.initialize(workflowElementID, backend);
      // store the new NDB backend in the list of registered backends
      registerNDBBackend(ndbBackend);
    }
    return ndbBackend;
  }
  /**
   * Removes a NDB backend that was registered with the server.
   *
   * @param workflowElementID  the identifier of the workflow element to remove
   */
  public static void remove(String workflowElementID)
  {
    deregisterNDBBackend(workflowElementID);
  }
  /**
   * Removes all the NDB backends that were registered with the server.
   * This function is intended to be called when the server is shutting down.
   */
  public static void removeAll()
  {
    synchronized (registeredNDBBackendsLock)
    {
      for (NDBWorkflowElement ndbBackend:
           registeredNDBBackends.values())
      {
        deregisterNDBBackend(ndbBackend.getWorkflowElementID());
      }
    }
  }
  /**
   * Registers a NDB backend with the server.
   *
   * @param ndbBackend  the NDB backend to register with the server
   */
  private static void registerNDBBackend(
                           NDBWorkflowElement ndbBackend)
  {
    synchronized (registeredNDBBackendsLock)
    {
      String ndbBackendID = ndbBackend.getWorkflowElementID();
      NDBWorkflowElement existingNDBBackend =
        registeredNDBBackends.get(ndbBackendID);
      if (existingNDBBackend == null)
      {
        TreeMap<String, NDBWorkflowElement> newNDBBackends =
          new TreeMap
            <String, NDBWorkflowElement>(registeredNDBBackends);
        newNDBBackends.put(ndbBackendID, ndbBackend);
        registeredNDBBackends = newNDBBackends;
      }
    }
  }
  /**
   * Deregisters a NDB backend with the server.
   *
   * @param workflowElementID  the identifier of the workflow element to remove
   */
  private static void deregisterNDBBackend(String workflowElementID)
  {
    synchronized (registeredNDBBackendsLock)
    {
      NDBWorkflowElement existingNDBBackend =
        registeredNDBBackends.get(workflowElementID);
      if (existingNDBBackend != null)
      {
        TreeMap<String, NDBWorkflowElement> newNDBBackends =
             new TreeMap<String, NDBWorkflowElement>(
                      registeredNDBBackends);
        newNDBBackends.remove(workflowElementID);
        registeredNDBBackends = newNDBBackends;
      }
    }
  }
  /**
   * {@inheritDoc}
   */
  @Override
  public void execute(Operation operation) throws CanceledOperationException {
    switch (operation.getOperationType())
    {
      case BIND:
        NDBBindOperation bindOperation =
             new NDBBindOperation((BindOperation) operation);
        bindOperation.processLocalBind(this);
        break;
      case SEARCH:
        NDBSearchOperation searchOperation =
             new NDBSearchOperation((SearchOperation) operation);
        searchOperation.processLocalSearch(this);
        break;
      case ADD:
        NDBAddOperation addOperation =
             new NDBAddOperation((AddOperation) operation);
        addOperation.processLocalAdd(this);
        break;
      case DELETE:
        NDBDeleteOperation deleteOperation =
             new NDBDeleteOperation((DeleteOperation) operation);
        deleteOperation.processLocalDelete(this);
        break;
      case MODIFY:
        NDBModifyOperation modifyOperation =
             new NDBModifyOperation((ModifyOperation) operation);
        modifyOperation.processLocalModify(this);
        break;
      case MODIFY_DN:
        NDBModifyDNOperation modifyDNOperation =
             new NDBModifyDNOperation((ModifyDNOperation) operation);
        modifyDNOperation.processLocalModifyDN(this);
        break;
      case COMPARE:
        NDBCompareOperation compareOperation =
             new NDBCompareOperation((CompareOperation) operation);
        compareOperation.processLocalCompare(this);
        break;
      case ABANDON:
        // There is no processing for an abandon operation.
        break;
      default:
        throw new AssertionError("Attempted to execute an invalid operation " +
                                 "type:  " + operation.getOperationType() +
                                 " (" + operation + ")");
    }
  }
  /**
   * Gets the backend associated with this NDB backend workflow element.
   *
   * @return The backend associated with this NDB backend workflow
   *         element.
   */
  @Override
  public Backend getBackend()
  {
    return backend;
  }
}
opends/src/server/org/opends/server/workflowelement/ndb/package-info.java
New file
@@ -0,0 +1,36 @@
/*
 * 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/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * 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/opends/resource/legal-notices/OpenDS.LICENSE.  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.
 */
/**
 * This package contains source for the NDB backend workflow element, which
 * is used to process operations against data stored in NDB backend database.
 */
@org.opends.server.types.PublicAPI(
     stability=org.opends.server.types.StabilityLevel.PRIVATE)
package org.opends.server.workflowelement.ndb;