Deepak created CAMEL-24398:
------------------------------

             Summary: [BUG] DefaultAttachmentMessage.hasAttachments() returns 
true for messages with no attachments in Camel 4.x
                 Key: CAMEL-24398
                 URL: https://issues.apache.org/jira/browse/CAMEL-24398
             Project: Camel
          Issue Type: Bug
          Components: camel-attachments
    Affects Versions: 4.14.2
            Reporter: Deepak


*Summary* 
hasAttachments() incorrectly returns true on a plain (non-multipart) message in 
Camel 4.x. This is a regression from
  Camel 3.x introduced during the MessageTrait refactor. It causes 
MimeMultipartDataFormat.marshal() to take the
  multipart code path for plain XML messages, corrupting the message body.



 *Component*

  camel-attachments — org.apache.camel.attachment.DefaultAttachmentMessage

  ---
  *Steps to Reproduce*

  1. Configure an HTTP Sender adapter with a MimeMultipartDataFormat marshal 
step downstream.
  2. POST a plain XML payload (no multipart, no attachments) to the endpoint.
  3. Observe that MimeMultipartDataFormat.marshal() takes the multipart path 
instead of writing the body as-is.

  ---
  *Root Cause*

  The MessageTrait refactor in Camel 4.x introduced a lazy-init pattern in 
getAttachmentsMap() that eagerly registers
  the ATTACHMENTS trait with an empty LinkedHashMap the first time any 
attachment-inspection method is called (e.g.
  getAttachments(), getAttachmentNames()). Once registered, hasAttachments() 
returns true because it only checks for
  trait presence — not whether the map is empty.

  *Call chain for a plain XML POST:*

  DefaultHttpBinding.readRequest()
    → readBody()
      → populateAttachments()           // called unconditionally for every 
request
        → getMessage(AttachmentMessage.class)
          → AttachmentConverter.toAttachmentMessage()
            → new DefaultAttachmentMessage(message)
              → getAttachmentsMap()
                → setPayloadForTrait(ATTACHMENTS, emptyLinkedHashMap)  // ← 
trait registered with empty map

  ... later ...

  MimeMultipartDataFormat.marshal()
    → exchange.getIn(AttachmentMessage.class).hasAttachments()
      → delegate.hasTrait(ATTACHMENTS)  // ← returns true! empty map was 
registered above
        → takes multipart path          // ← wrong for plain XML

  ---
  *Code Comparison*

  Camel 3.14.7 — DefaultAttachmentMessage.hasAttachments() ✅ Correct

  @Override
  public boolean hasAttachments() {
      Map<String, Attachment> map = 
getExchange().getProperty(ATTACHMENT_OBJECTS, Map.class);
      return map != null && !map.isEmpty(); // checks both null AND emptiness
  }

  Attachments were stored as an Exchange property. Reading the property when 
nothing was added returns null, so
  hasAttachments() correctly returns false.

  ---
  Camel 4.14.2 — DefaultAttachmentMessage.hasAttachments() ❌ Broken

  @Override
  public boolean hasAttachments() {
      return delegate.hasTrait(MessageTrait.ATTACHMENTS); // only checks trait 
key presence
  }

  Combined with the lazy-init in getAttachmentsMap():

  private Map<String, Object> getAttachmentsMap() {
      var m = (Map<String, Object>) 
delegate.getPayloadForTrait(MessageTrait.ATTACHMENTS);
      if (m == null) {
          m = new LinkedHashMap<>();
          delegate.setPayloadForTrait(MessageTrait.ATTACHMENTS, m); // 
registers trait even when empty
      }
      return m;
  }

  Any read-only call to getAttachments(), getAttachmentNames(), or 
getAttachmentObjects() silently registers the
  ATTACHMENTS trait with an empty map, causing all subsequent hasAttachments() 
calls to return true.


Observed vs Expected

{*}Expected{*}: hasAttachments() returns false when no attachments have been 
added 
{*}Actual{*}: hasAttachments() returns true after any read-only attachment 
inspection, even with zero attachments 

--------------------



*Debugger Evidence*

  Exchange traits map at MimeMultipartDataFormat.marshal() breakpoint:

  traits = \{EnumMap size=1}
    MessageTrait.ATTACHMENTS → \{LinkedHashMap size=0}   ← trait present, but 
map is EMPTY



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to