This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/main by this push:
     new 5188d47b5489 CAMEL-25509: camel-ldif - Apply newsuperior of modrdn, 
and keep the parent of moddn without it
5188d47b5489 is described below

commit 5188d47b548927db164bd5a0b631224c219a96df
Author: allthingssecurity <[email protected]>
AuthorDate: Sat Oct 10 12:40:48 2026 +0530

    CAMEL-25509: camel-ldif - Apply newsuperior of modrdn, and keep the parent 
of moddn without it
    
    RFC 2849 defines changetype modrdn and moddn as the same change: the
    entry gets the new RDN, and moves under newsuperior only when it is
    given. LdifProducer handled them as two operations: a modrdn with
    newsuperior renamed the entry in place without moving it, and a moddn
    without newsuperior tried to move the entry to a one-RDN DN directly
    under the root, which the server rejects.
    
    Both change types now move the entry with moveAndRename when newsuperior
    is given, and rename it in place otherwise. The requests for moddn with
    newsuperior and modrdn without it are unchanged.
    
    Closes #27642
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
 .../apache/camel/component/ldif/LdifProducer.java  |  23 ++--
 .../apache/camel/component/ldif/LdifModDnTest.java | 122 +++++++++++++++++++++
 .../ROOT/pages/camel-4x-upgrade-guide-4_23.adoc    |   6 +
 3 files changed, 141 insertions(+), 10 deletions(-)

diff --git 
a/components/camel-ldif/src/main/java/org/apache/camel/component/ldif/LdifProducer.java
 
b/components/camel-ldif/src/main/java/org/apache/camel/component/ldif/LdifProducer.java
index 8b73240ea138..0476b13821b8 100644
--- 
a/components/camel-ldif/src/main/java/org/apache/camel/component/ldif/LdifProducer.java
+++ 
b/components/camel-ldif/src/main/java/org/apache/camel/component/ldif/LdifProducer.java
@@ -158,17 +158,20 @@ public class LdifProducer extends DefaultProducer {
                     LOG.debug("attempting delete of {}", ldifEntry);
                 }
                 conn.delete(ldifEntry.getDn());
-            } else if (ldifEntry.isChangeModDn()) {
-                if (LOG.isDebugEnabled()) {
-                    LOG.debug("attempting DN move of {}", ldifEntry);
-                }
-                conn.moveAndRename(ldifEntry.getDn(), new 
Dn(ldifEntry.getNewRdn(), ldifEntry.getNewSuperior()),
-                        ldifEntry.isDeleteOldRdn());
-            } else if (ldifEntry.isChangeModRdn()) {
-                if (LOG.isDebugEnabled()) {
-                    LOG.debug("attempting RDN move of {}", ldifEntry);
+            } else if (ldifEntry.isChangeModDn() || 
ldifEntry.isChangeModRdn()) {
+                // RFC 2849: modrdn and moddn are the same change type; the 
entry moves only with a newsuperior
+                if (ldifEntry.getNewSuperior() != null) {
+                    if (LOG.isDebugEnabled()) {
+                        LOG.debug("attempting DN move of {}", ldifEntry);
+                    }
+                    conn.moveAndRename(ldifEntry.getDn(), new 
Dn(ldifEntry.getNewRdn(), ldifEntry.getNewSuperior()),
+                            ldifEntry.isDeleteOldRdn());
+                } else {
+                    if (LOG.isDebugEnabled()) {
+                        LOG.debug("attempting RDN move of {}", ldifEntry);
+                    }
+                    conn.rename(ldifEntry.getDn(), new 
Rdn(ldifEntry.getNewRdn()), ldifEntry.isDeleteOldRdn());
                 }
-                conn.rename(ldifEntry.getDn(), new Rdn(ldifEntry.getNewRdn()), 
ldifEntry.isDeleteOldRdn());
             }
 
             LOG.debug("ldif success");
diff --git 
a/components/camel-ldif/src/test/java/org/apache/camel/component/ldif/LdifModDnTest.java
 
b/components/camel-ldif/src/test/java/org/apache/camel/component/ldif/LdifModDnTest.java
new file mode 100644
index 000000000000..43ea3dea3346
--- /dev/null
+++ 
b/components/camel-ldif/src/test/java/org/apache/camel/component/ldif/LdifModDnTest.java
@@ -0,0 +1,122 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.ldif;
+
+import java.lang.reflect.Proxy;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.camel.BindToRegistry;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.test.junit6.CamelTestSupport;
+import org.apache.directory.api.ldap.model.name.Dn;
+import org.apache.directory.api.ldap.model.name.Rdn;
+import org.apache.directory.ldap.client.api.LdapConnection;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * RFC 2849: "modrdn" and "moddn" are the same change type. The entry gets the 
new RDN, and moves under the new superior
+ * only when "newsuperior" is given. The connection is a stub that records the 
resulting DN, so this test needs no LDAP
+ * server.
+ */
+class LdifModDnTest extends CamelTestSupport {
+
+    private final List<String> renames = new ArrayList<>();
+
+    @BindToRegistry("conn")
+    private final LdapConnection conn = (LdapConnection) 
Proxy.newProxyInstance(
+            LdapConnection.class.getClassLoader(), new Class<?>[] { 
LdapConnection.class },
+            (proxy, method, args) -> {
+                switch (method.getName()) {
+                    case "hashCode" -> {
+                        return System.identityHashCode(proxy);
+                    }
+                    case "equals" -> {
+                        return proxy == args[0];
+                    }
+                    case "toString" -> {
+                        return "LdapConnection stub";
+                    }
+                    case "rename" -> {
+                        Dn dn = (Dn) args[0];
+                        renames.add(new Dn((Rdn) args[1], dn.getParent()) + " 
deleteOldRdn=" + args[2]);
+                    }
+                    case "moveAndRename" ->
+                        renames.add(args[1] + " deleteOldRdn=" + args[2]);
+                    case "close" -> {
+                        // the producer closes the connection after the LDIF 
is processed
+                    }
+                    default ->
+                        // the producer calls no other method: record it so 
that the assertion shows it
+                        renames.add("unexpected call: " + method.getName());
+                }
+                return method.getReturnType() == boolean.class ? Boolean.FALSE 
: null;
+            });
+
+    @BeforeEach
+    void clear() {
+        renames.clear();
+    }
+
+    @Test
+    void modRdnWithNewSuperiorMovesTheEntry() {
+        assertEquals("uid=b,ou=new,dc=example,dc=org deleteOldRdn=true",
+                rename("modrdn", "1", "newsuperior: 
ou=new,dc=example,dc=org\n"));
+    }
+
+    @Test
+    void modDnWithoutNewSuperiorKeepsTheParent() {
+        assertEquals("uid=b,ou=old,dc=example,dc=org deleteOldRdn=true", 
rename("moddn", "1", ""));
+    }
+
+    @Test
+    void modDnWithNewSuperiorMovesTheEntry() {
+        assertEquals("uid=b,ou=new,dc=example,dc=org deleteOldRdn=false",
+                rename("moddn", "0", "newsuperior: 
ou=new,dc=example,dc=org\n"));
+    }
+
+    @Test
+    void modRdnWithoutNewSuperiorKeepsTheParent() {
+        assertEquals("uid=b,ou=old,dc=example,dc=org deleteOldRdn=false", 
rename("modrdn", "0", ""));
+    }
+
+    private String rename(String changeType, String deleteOldRdn, String 
newSuperior) {
+        String ldif = "version: 1\n"
+                      + "dn: uid=a,ou=old,dc=example,dc=org\n"
+                      + "changetype: " + changeType + "\n"
+                      + "newrdn: uid=b\n"
+                      + "deleteoldrdn: " + deleteOldRdn + "\n"
+                      + newSuperior;
+        List<?> result = template.requestBody("direct:ldif", ldif, List.class);
+        assertEquals(List.of("success"), result);
+        assertEquals(1, renames.size(), "calls on the connection: " + renames);
+        return renames.get(0);
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            @Override
+            public void configure() {
+                from("direct:ldif").to("ldif:conn");
+            }
+        };
+    }
+}
diff --git 
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
index fb3a926c22c2..5a2073eb349b 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
@@ -1004,6 +1004,12 @@ Before, the first five fell back to the charset of the 
exchange (UTF-8 by defaul
 unmarshal sets `CamelCharsetName` to it. A message whose MSH-18 names one of 
these charsets but that is actually
 encoded in another one (such as UTF-8) is now decoded in the charset it names.
 
+=== camel-ldif - modrdn and moddn records
+
+A `changetype: modrdn` record with a `newsuperior` line now moves the entry 
under the new superior, as RFC 2849
+defines; before, the entry was only renamed in place. A `changetype: moddn` 
record without `newsuperior` now renames
+the entry in place; before, it was moved directly under the root (which 
servers normally refuse).
+
 === camel-servlet and camel-jetty - String response in the charset of the 
Content-Type
 
 The servlet and Jetty consumers converted a `String` response body with the 
charset of the exchange (UTF-8 by

Reply via email to