content: /*
   * 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.sjms;
  
  import java.io.File;
  import java.util.Map;
  
  import jakarta.jms.Destination;
  import jakarta.jms.JMSException;
  import jakarta.jms.Message;
  import jakarta.jms.Queue;
  import jakarta.jms.Session;
  import jakarta.jms.Topic;
  
  import org.apache.camel.Exchange;
  import org.apache.camel.RuntimeExchangeException;
  import org.apache.camel.component.sjms.jms.JmsBinding;
  import org.apache.camel.component.sjms.jms.JmsMessageHelper;
  import org.apache.camel.support.DefaultMessage;
  import org.apache.camel.support.ExchangeHelper;
  import org.apache.camel.trait.message.MessageTrait;
  import org.apache.camel.util.ObjectHelper;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  import static org.apache.camel.support.MessageHelper.copyBody;
  
  /**
   * Represents a {@link org.apache.camel.Message} for working with JMS
   */
  public class SjmsMessage extends DefaultMessage {
      private static final Logger LOG = LoggerFactory.getLogger(SjmsMessage.class);
  
      private Message jmsMessage;
      private Session jmsSession;
      private JmsBinding binding;
  
      public SjmsMessage(Exchange exchange, Message jmsMessage, Session jmsSession, JmsBinding binding) {
          super(exchange);
          setJmsMessage(jmsMessage);
          setJmsSession(jmsSession);
          setBinding(binding);
  
          setPayloadForTrait(MessageTrait.REDELIVERY, JmsMessageHelper.evalRedeliveryMessageTrait(jmsMessage));
      }
  
      public void init(Exchange exchange, Message jmsMessage, Session jmsSession, JmsBinding binding) {
          setExchange(exchange);
          setJmsMessage(jmsMessage);
          setJmsSession(jmsSession);
          setBinding(binding);
          // need to populate initial headers when we use pooled exchanges
          populateInitialHeaders(getHeaders());
  
          setPayloadForTrait(MessageTrait.REDELIVERY, JmsMessageHelper.evalRedeliveryMessageTrait(jmsMessage));
      }
  
      @Override
      public void reset() {
          super.reset();
          setExchange(null);
          jmsMessage = null;
          jmsSession = null;
          binding = null;
      }
  
      @Override
      public String toString() {
          // do not print jmsMessage as there could be sensitive details
          if (jmsMessage != null) {
              try {
                  return "SjmsMessage[JmsMessageID: " + jmsMessage.getJMSMessageID() + "]";
              } catch (Exception e) {
                  // ignore
              }
          }
          return "SjmsMessage@" + ObjectHelper.getIdentityHashCode(this);
      }
  
      @Override
      public void copyFrom(org.apache.camel.Message that) {
          if (that == this) {
              // the same instance so do not need to copy
              return;
          }
  
          // must initialize headers before we set the JmsMessage to avoid Camel
          // populating it before we do the copy
          getHeaders().clear();
  
          boolean copyMessageId = true;
          if (that instanceof SjmsMessage) {
              SjmsMessage thatMessage = (SjmsMessage) that;
              this.jmsMessage = thatMessage.jmsMessage;
              if (this.jmsMessage != null) {
                  // for performance lets not copy the messageID if we are a JMS message
                  copyMessageId = false;
              }
          }
          if (copyMessageId) {
              setMessageId(that.getMessageId());
          }
  
          // cover over exchange if none has been assigned
          if (getExchange() == null) {
              setExchange(that.getExchange());
          }
  
          // copy body and fault flag
          copyBody(that, this);
  
          // we have already cleared the headers
          if (that.hasHeaders()) {
              getHeaders().putAll(that.getHeaders());
          }
      }
  
      public JmsBinding getBinding() {
          if (binding == null) {
              binding = ExchangeHelper.getBinding(getExchange(), JmsBinding.class);
          }
          return binding;
      }
  
      public void setBinding(JmsBinding binding) {
          this.binding = binding;
      }
  
      /**
       * Returns the underlying JMS message
       */
      public Message getJmsMessage() {
          return jmsMessage;
      }
  
      public void setJmsMessage(Message jmsMessage) {
          if (jmsMessage != null) {
              try {
                  setMessageId(jmsMessage.getJMSMessageID());
              } catch (JMSException e) {
                  LOG.warn("Unable to retrieve JMSMessageID from JMS Message", e);
              }
          }
          this.jmsMessage = jmsMessage;
      }
  
      /**
       * Returns the underlying JMS session.
       * <p/>
       * This may be <tt>null</tt>.
       */
      public Session getJmsSession() {
          return jmsSession;
      }
  
      public void setJmsSession(Session jmsSession) {
          this.jmsSession = jmsSession;
      }
  
      @Override
      public void setBody(Object body) {
          super.setBody(body);
          if (body == null) {
              // preserver headers even if we set body to null
              ensureInitialHeaders();
              // remove underlying jmsMessage since we mutated body to null
              jmsMessage = null;
          }
      }
  
      @Override
      public Object getHeader(String name) {
          Object answer = null;
  
          // we will exclude using JMS-prefixed headers here to avoid strangeness with some JMS providers
          // e.g. ActiveMQ returns the String not the Destination type for "JMSReplyTo"!
          // only look in jms message directly if we have not populated headers
          if (jmsMessage != null && !hasPopulatedHeaders() && !name.startsWith("JMS")) {
              try {
                  // use binding to do the lookup as it has to consider using encoded keys
                  answer = getBinding().getObjectProperty(jmsMessage, name);
              } catch (JMSException e) {
                  throw new RuntimeExchangeException("Unable to retrieve header from JMS Message: " + name, getExchange(), e);
              }
          }
          // only look if we have populated headers otherwise there are no headers at all
          // if we do lookup a header starting with JMS then force a lookup
          if (answer == null && (hasPopulatedHeaders() || name.startsWith("JMS"))) {
              answer = super.getHeader(name);
          }
          return answer;
      }
  
      @Override
      public Map<String, Object> getHeaders() {
          ensureInitialHeaders();
          return super.getHeaders();
      }
  
      @Override
      public Object removeHeader(String name) {
          ensureInitialHeaders();
          return super.removeHeader(name);
      }
  
      @Override
      public void setHeaders(Map<String, Object> headers) {
          ensureInitialHeaders();
          super.setHeaders(headers);
      }
  
      @Override
      public void setHeader(String name, Object value) {
          ensureInitialHeaders();
          super.setHeader(name, value);
      }
  
      @Override
      public SjmsMessage newInstance() {
          SjmsMessage answer = new SjmsMessage(null, null, null, binding);
          answer.setCamelContext(getCamelContext());
          return answer;
      }
  
      /**
       * Returns true if a new JMS message instance should be created to send to the next component
       */
      public boolean shouldCreateNewMessage() {
          return super.hasPopulatedHeaders();
      }
  
      /**
       * Ensure that the headers have been populated from the underlying JMS message before we start mutating the headers
       */
      protected void ensureInitialHeaders() {
          if (jmsMessage != null && !hasPopulatedHeaders()) {
              // we have not populated headers so force this by creating
              // new headers and set it on super
              super.setHeaders(createHeaders());
          }
      }
  
      @Override
      protected Object createBody() {
          if (jmsMessage != null) {
              return getBinding().extractBodyFromJms(getExchange(), jmsMessage);
          }
          return null;
      }
  
      @Override
      protected void populateInitialHeaders(Map<String, Object> map) {
          if (jmsMessage != null && map != null) {
              map.putAll(getBinding().extractHeadersFromJms(jmsMessage, getExchange()));
              try {
                  map.put(Exchange.MESSAGE_TIMESTAMP, jmsMessage.getJMSTimestamp());
              } catch (JMSException e) {
                  // ignore
              }
          }
      }
  
      @Override
      protected String createMessageId() {
          if (jmsMessage == null) {
              LOG.trace("No jakarta.jms.Message set so generating a new message id");
              return super.createMessageId();
          }
          try {
              String id = getDestinationAsString(jmsMessage.getJMSDestination());
              if (id != null) {
                  id += jmsMessage.getJMSMessageID();
              } else {
                  id = jmsMessage.getJMSMessageID();
              }
              return getSanitizedString(id);
          } catch (JMSException e) {
              throw new RuntimeExchangeException("Unable to retrieve JMSMessageID from JMS Message", getExchange(), e);
          }
      }
  
      private String getDestinationAsString(Destination destination) throws JMSException {
          String result = null;
          if (destination == null) {
              result = "null destination!" + File.separator;
          } else if (destination instanceof Topic) {
              result = "topic" + File.separator + ((Topic) destination).getTopicName() + File.separator;
          } else if (destination instanceof Queue) {
              result = "queue" + File.separator + ((Queue) destination).getQueueName() + File.separator;
          }
          return result;
      }
  
      private String getSanitizedString(Object value) {
          return value != null ? value.toString().replaceAll("[^a-zA-Z0-9\\.\\_\\-]", "_") : "";
      }
  
  }
  