docs: void sendMessage(ProducerProperties props, Message message, JmsDestinationImpl destRef) throws JMSException {
  
          PersistenceType deliveryMode;
          Integer priority;
          long timeToLive;
          JmsMessageImpl jmsMIref;
  
          if (TraceComponent.isAnyTracingEnabled() && tc.isEntryEnabled()) {
              SibTr.entry(tc, "sendMessage(ProducerProps, Message, Destination)");
          }
  
          // This is the core MFP message object contained in the supplied Jetstream message.
          JsJmsMessage jsJmsMessage = null;
  
          // if the message is a jetstream message then cast it, otherwise
          // construct a jetstream message from it.
          JmsMessageImpl foreignMsgHelper = null;
          if (message instanceof JmsMessageImpl) {
              jmsMIref = (JmsMessageImpl) message;
              jsJmsMessage = jmsMIref.getMsgReference();
              // clear the special case properties from the message to be sent
              jmsMIref.clearLocalProperties();
          }
          else {
              if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled())
                  SibTr.debug(this, tc, "Create a JS message object for foreign message");
              foreignMsgHelper = JmsMessageImpl.messageToJmsMessageImpl(message);
              jmsMIref = foreignMsgHelper;
              jsJmsMessage = foreignMsgHelper.getMsgReference();
          }
  
          // The rest of this method (with the exception of the block at the bottom
          // that sets values back into the foreign message should use the MFP message
          // directly - not through the JmsMessageImpl instance.
          // Except for the call to updateReplyReliability which is JmsMessageImpl specific. Yuk.
  
          // check for admin overrides of deliveryMode, priority
          deliveryMode = props.getEffectiveDeliveryMode();
          jsJmsMessage.setJmsDeliveryMode(deliveryMode);
          priority = props.getEffectivePriority();
          jsJmsMessage.setPriority(priority.intValue());
          timeToLive = props.getEffectiveTTL();
  
          // validate the timeToLive and deliveryDelay 
          validateTTLAndDD(timeToLive);
  
          // Set up the forward and reverse routing paths on the message.
          List frpPath = props.getConvertedFRP();
          if (frpPath != null)
              jsJmsMessage.uncheckedSetForwardRoutingPath(frpPath);
  
          List rrpPath = props.getConvertedRRP_Part();
          if (rrpPath != null) {
              // If the destination contained a reverse routing path then we have
              // to be careful how this interacts with any replyTo dest already
              // set on the message. The replyTo dest should end up at the end of
              // the routing path.
              List existingRRP = jsJmsMessage.getReverseRoutingPath();
              if (existingRRP.size() > 0) {
                  // Append each element of the existing RRP into the list obtained
                  // from the destination. It is likely to be only one element in size
                  // (for the replyTo dest).
                  for (int i = 0; i < existingRRP.size(); i++) {
                      rrpPath.add(existingRRP.get(i));
                  }
              }
              jsJmsMessage.uncheckedSetReverseRoutingPath(rrpPath);
              // This step copies the discriminator from the sending destination to the
              // reply discriminator in case the destination is being used for request/reply
              String discrim = props.getDiscriminator();
              if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled())
                  SibTr.debug(this, tc, "Using discriminator from destination: " + discrim);
              if ((discrim != null) && (!discrim.equals(jsJmsMessage.getReplyDiscriminator()))) {
                  jsJmsMessage.setReplyDiscriminator(discrim);
              }
          }
  
          // d317373 perf. Only set the busname in rrp if we need to
          if ((message instanceof JmsMessageImpl)
              && ((JmsMessageImpl) message).isRrpBusNameNeedsUpdating()) {
              // d272113 If the busname is null, resolve it to the local bus so that
              // replies will still work if the msg travels to a non-local bus.
              boolean changed = false;
              List rrp = jsJmsMessage.getReverseRoutingPath();
              if (rrp.size() > 0) {
                  for (int i = 0; i < rrp.size(); i++) {
                      JsDestinationAddress addr = (JsDestinationAddress) rrp.get(i); // addr is byValue, not byRef.
                      if (addr.getBusName() == null) {
                          addr.setBusName(busName);
                          rrp.set(i, addr); // it's not enough to just change addr, we have to set it back into the list.
                          changed = true;
                      }
                  }
              }
              // If we've updated any values then reflect those updates back into the message.
              if (changed)
                  jsJmsMessage.uncheckedSetReverseRoutingPath(rrp);
              // no need to do this again unless the message tells us to by resetting this flag
              ((JmsMessageImpl) message).setRrpBusNameNeedsUpdating(false);
          }
  
          // set up the on-send header properties.
  
          // d317373 the mesage now maintains the MSG_TYPE_PROPERTY
          // so no need to update it here
          if (!destRef._getInhibitJMSDestination()) {
              byte[] destBytes = destRef.encodeToBytes(EncodingLevel.FULL);
              jsJmsMessage.setJmsDestination(destBytes);
              // Set the destination reference cache in the message for the benefit of the sender.
              // (separate code handles the foreign message case at the end of this message)
              // This method sets the reference without touching the coreMsg object.
              if (message instanceof JmsMessageImpl) {
                  ((JmsMessageImpl) message).setDestReference(destRef);
              }
          }
  
          if (!disableMessageID) {
              jsJmsMessage.setApiMessageIdAsBytes(session.createMessageID());
          }
  
          // Tag this message as originating in WPM. (uses a byte field to save space,
          // expands the byte to the required text when get is called).
          jsJmsMessage.setJmsxAppId(MfpConstants.WPM_JMSXAPPID);
  
          // Set JMSXUserID, using api specific call
          jsJmsMessage.setApiUserId(resolvedUserID);
  
          // Only insert a default value for the groupID if this message hasn't previously been sent.
          if (!jsJmsMessage.alreadySent()) {
              // Check for JMSXGroupSeq set, but JMSXGroupID not set, which
              // triggers automatic creation of JMSXGroupID. Using getObjectProperty
              // instead of getIntProperty avoids the need to catch an exception on
              // a common case.
              Object gSeq = jsJmsMessage.getJMSXGroupSeq();
              if (gSeq != null) {
                  Object gID = jsJmsMessage.getObjectProperty("JMSXGroupID");
                  if (gID == null) {
                      // create a groupID by requesting a new messageID, and converting it
                      // to the 'ID:xxxxxx' form.
                      byte[] idBytes = session.createMessageID();
                      StringBuffer sb = new StringBuffer("ID:");
                      HexString.binToHex(idBytes, 0, idBytes.length, sb);
                      String newGroupID = sb.toString();
                      jsJmsMessage.setObjectProperty("JMSXGroupID", newGroupID);
                  }
              }
          }
  
          // The expiration field is based on the current timestamp and so will
          // not be common across invocations unless the value being set is 0.
          timeToLive = props.getEffectiveTTL();
          long timeNow = 0;
          if ((timeToLive != 0) || (!disableTimestamp)) {
              timeNow = System.currentTimeMillis();
          }
          if (timeToLive == 0) {
              jsJmsMessage.setJmsExpiration(0);
          }
          else {
              jsJmsMessage.setJmsExpiration(timeNow + timeToLive);
          }
          jsJmsMessage.uncheckedSetTimeToLive(timeToLive);
  
          //if timeNow is 0 then get the current time
          if (timeNow == 0)
              timeNow = System.currentTimeMillis();
          //set delivery time and the delivery delay
          jsJmsMessage.setJmsDeliveryTime(timeNow + this.defaultDeliveryDelay);
          jsJmsMessage.uncheckedSetDeliveryDelay(this.defaultDeliveryDelay);
  
          if (!disableTimestamp) {
              // The timestamp will always be different - not a lot we can do there.
              jsJmsMessage.setTimestamp(timeNow);
          }
          else {
              // d305036.2 must explicitly set to 0 now that the SIB default is -1
              jsJmsMessage.setTimestamp(0L); // JMS spec says timestamp must be 0 when disabled
          }
  
          // set up jetstream message reliability field.
          // d317816 need to use the replyReliability if it is set in the destination
          Reliability rel = destRef.getReplyReliability();
          if (rel == null) {
              // this is the normal case
              rel = props.getEffectiveReliability();
          }
          jsJmsMessage.setReliability(rel);
  
          // Give the message class a change to update the replyReliability field
          jmsMIref.updateReplyReliability(nonPersistentReliability, persistentReliability);
  
          // Set the topic on the message based on the discrim. Note that for
          // queue type destinations the behaviour of this is not defined, but
          // should not affect the behaviour.
          String discrim = props.getDiscriminator();
          try {
              if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled())
                  SibTr.debug(this, tc, "Setting discrim as '" + discrim + "'");
              if (prod != null) {
                  // If we have already created the producer then we should
                  // be guaranteed that this is a valid discriminator by
                  // this point (sending a message).
                  jsJmsMessage.uncheckedSetDiscriminator(discrim);
              }
              else {
                  // For unidentified producer this still has some value.
                  jsJmsMessage.setDiscriminator(discrim);
              }
          } catch (IllegalArgumentException iae) {
              // No FFDC code needed
              // This will occur if the user is trying to publish to a topic that contains wildcards..
              // d238447 FFDC review. App error, no FFDC.
              throw (JMSException) JmsErrorUtils.newThrowable(
                                                              JMSException.class,
                                                              "INVALID_VALUE_CWSIA0068",
                                                              new Object[] { "discriminator", discrim },
                                                              iae,
                                                              null, // null probeId = no FFDC
                                                              this,
                                                              tc);
          }
  
          // We want the routing destination to be null (normal case).
          jsJmsMessage.setRoutingDestination(null);
  
          // Obtain transaction under which to send the message, if required.
          SITransaction transaction;
          String acknowledgeMode;
          switch (session.getAcknowledgeMode()) {
              case Session.SESSION_TRANSACTED:
                  // If locally transacted, need a transaction.
                  // If an XA transaction is configured (overriding local transaction
                  // flag), need a transaction.
                  transaction = session.getTransaction();
                  acknowledgeMode = XctJmsConstants.XCT_ACK_MODE_TRANSACTED;
                  break;
              case Session.CLIENT_ACKNOWLEDGE:
                  // Must be client ack, as cannot override with XA (since XA is only
                  // available if a managed environment is in use, in which case
                  // client ack is illegal).
                  // Ack mode transactions should only be used on receives, so must not
                  // ask for one.
                  transaction = null;
                  acknowledgeMode = XctJmsConstants.XCT_ACK_MODE_CLIENT;
                  break;
              case Session.DUPS_OK_ACKNOWLEDGE:
                  // Must be dups ok ack, as cannot override with XA (since XA is only
                  // available if a managed environment is in use, in which case
                  // dups ok ack is illegal).
                  // Ack mode transactions should only be used on receives, so must not
                  // ask for one.
                  transaction = null;
                  acknowledgeMode = XctJmsConstants.XCT_ACK_MODE_DUPS_OK;
                  break;
              case Session.AUTO_ACKNOWLEDGE:
                  // If auto ack, no transaction is used.
                  // If an XA transaction is configured (overriding auto ack flag),
                  // need a transaction.
                  // Ask for a transaction in either case, if session is really auto
                  // ack, null will be returned.
                  transaction = session.getTransaction();
                  acknowledgeMode = XctJmsConstants.XCT_ACK_MODE_AUTO;
                  break;
              default:
                  // This should never happen.
                  transaction = session.getTransaction();
                  acknowledgeMode = XctJmsConstants.XCT_ACK_MODE_NONE;
                  break;
          }
  
          // d265587  Msg toString not reset on send
          // Now invalidate the message toString so that the changes we have just made
          // on the underlying MFP message object are reflected the next time the user
          // calls toString.
          jmsMIref.invalidateToStringCache();
  
          // Try to actually send it if this is an identified producer
          if (prod != null) {
              // send it using the previously constructed producer session.
              try {
                  prod.send(jsJmsMessage, transaction);
              } catch (SINotAuthorizedException sinae) {
                  // No FFDC code needed
                  // d238447 FFDC Review. NotAuth is app/config error, no FFDC.
                  throw (JMSSecurityException) JmsErrorUtils.newThrowable(
                                                                          JMSSecurityException.class,
                                                                          "PRODUCER_AUTH_ERROR_CWSIA0069",
                                                                          null,
                                                                          sinae,
                                                                          null, // null probeId = no FFDC
                                                                          this,
                                                                          tc
                                  );
              } catch (SINotPossibleInCurrentConfigurationException dwte) {
                  // No FFDC code needed
                  // Dest not there or wrong type
                  String msgKey = "SEND_FAILED_CWSIA0063";
                  // d238447 FFDC Review. App or config error, no FFDC.
                  throw (InvalidDestinationException) JmsErrorUtils.newThrowable(
                                                                                 InvalidDestinationException.class,
                                                                                 msgKey,
                                                                                 new Object[] { destRef },
                                                                                 dwte,
                                                                                 null, // null probeId = no FFDC
                                                                                 this,
                                                                                 tc
                                  );
              } catch (SIIncorrectCallException e) {
                  // No FFDC code needed
                  // d222942 review - default exception ok (was in SIException block below)
                  throw (JMSException) JmsErrorUtils.newThrowable(
                                                                  JMSException.class,
                                                                  "EXCEPTION_RECEIVED_CWSIA0067",
                                                                  new Object[] { e, "JmsMsgProducerImpl.sendMessage (#4)" },
                                                                  e,
                                                                  "JmsMsgProducerImpl.sendMessage#4",
                                                                  this,
                                                                  tc
                                  );
              } catch (SIException simpe) {
                  // No FFDC code needed
                  // d238447 FFDC review. Exceptions from the send method are:
                  //   SINotAuthorizedException,                     \ Handled above.
                  //   SINotPossibleInCurrentConfigurationException  /
                  //   SIIncorrectCallException        -- FFDC required, moved above
                  //   SISessionUnavailableException, SISessionDroppedException,                 \
                  //   SIConnectionUnavailableException, SIConnectionDroppedException,            > No FFDC
                  //   SIResourceException, SIConnectionLostException, SILimitExceededException, /
                  throw (JMSException) JmsErrorUtils.newThrowable(
                                                                  JMSException.class,
                                                                  "EXCEPTION_RECEIVED_CWSIA0067",
                                                                  new Object[] { simpe, "JmsMsgProducerImpl.sendMessage (#4)" },
                                                                  simpe,
                                                                  null, // null probeId = no FFDC
                                                                  this,
                                                                  tc
                                  );
              }
  
          }
  
          // if this is an unidentified producer, send it using the 'put-one'
          // method on the core connection.
          else {
  
              try {
                  DestinationType dt = null;
  
                  // Check whether we can validate the type of the destination to which the
                  // producer is attaching. In the simple case this is always true, however
                  // is false if there is a forward routing path on this destination.
                  if (destRef.isProducerTypeCheck()) {
                      if (destRef instanceof Queue) {
                          dt = DestinationType.QUEUE;
                      }
                      else if (destRef instanceof Topic) {
                          dt = DestinationType.TOPICSPACE;
                      }
                  }
  
                  SIDestinationAddress sida = destRef.getProducerSIDestinationAddress();
  
                  if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled())
                      SibTr.debug(this, tc, "About to do core unidentified send - transaction: " + transaction + " destAddr: " + sida + " destType: " + dt);
  
                  // CoreSPI unidentified put method (put1)
                  coreConnection.send(jsJmsMessage, transaction, sida, dt, orderingContext, null);
              }
  
              catch (SINotAuthorizedException sinae) {
                  // No FFDC code needed
                  // d238447 FFDC review. NotAuth is an app/config error, no FFDC.
                  throw (JMSSecurityException) JmsErrorUtils.newThrowable(
                                                                          JMSSecurityException.class,
                                                                          "PRODUCER_AUTH_ERROR_CWSIA0069",
                                                                          null,
                                                                          sinae,
                                                                          null, // null probeId = no FFDC
                                                                          this,
                                                                          tc
                                  );
              } catch (SINotPossibleInCurrentConfigurationException dwte) {
                  // No FFDC code needed
                  // Dest not there or wrong type
                  String msgKey = "SEND_FAILED_CWSIA0063";
                  // d38447 FFDC review. App/config error, no FFDC.
                  throw (InvalidDestinationException) JmsErrorUtils.newThrowable(
                                                                                 InvalidDestinationException.class,
                                                                                 msgKey,
                                                                                 new Object[] { destRef },
                                                                                 dwte,
                                                                                 null, // null probeId = no FFDC
                                                                                 this,
                                                                                 tc
                                  );
              } catch (SITemporaryDestinationNotFoundException tdnf) {
                  // No FFDC code needed
                  // Dest not there or wrong type
                  String msgKey = "SEND_FAILED_CWSIA0063";
                  // d238447 FFDC review. App error, no FFDC.
                  throw (InvalidDestinationException) JmsErrorUtils.newThrowable(
                                                                                 InvalidDestinationException.class,
                                                                                 msgKey,
                                                                                 new Object[] { destRef },
                                                                                 tdnf,
                                                                                 null, // null probeId = no FFDC
                                                                                 this,
                                                                                 tc
                                  );
              } catch (SIIncorrectCallException sice) {
                  // No FFDC code needed
                  // d222942 review - default exception ok (was in SIException block below)
                  throw (JMSException) JmsErrorUtils.newThrowable(
                                                                  JMSException.class,
                                                                  "EXCEPTION_RECEIVED_CWSIA0067",
                                                                  new Object[] { sice, "JmsMsgProducerImpl.sendMessage (#7)" },
                                                                  sice,
                                                                  "JmsMsgProducerImpl.sendMessage#7",
                                                                  this,
                                                                  tc
                                  );
              } catch (SIException sice) {
                  // No FFDC code needed
                  // d238447 FFDC review. Need to consider:
                  //   SINotAuthorizedException, SINotPossibleInCurrentConfigurationException \ Covered above
                  //   SITemporaryDestinationNotFoundException,                               /
                  //   SIIncorrectCallException,                                            - Requires FFDC, moved above.
                  //   SIConnectionUnavailableException, SIConnectionDroppedException,           \ No FFDC required
                  //   SIResourceException, SIConnectionLostException, SILimitExceededException, /
                  throw (JMSException) JmsErrorUtils.newThrowable(
                                                                  JMSException.class,
                                                                  "EXCEPTION_RECEIVED_CWSIA0067",
                                                                  new Object[] { sice, "JmsMsgProducerImpl.sendMessage (#7)" },
                                                                  sice,
                                                                  null, // null probeId = no FFDC
                                                                  this,
                                                                  tc
                                  );
              } catch (Exception e) {
                  // No FFDC code needed
                  // d238447 FFDC review - no idea what this covers, so generate FFDC.
                  throw (JMSException) JmsErrorUtils.newThrowable(
                                                                  JMSException.class,
                                                                  "EXCEPTION_RECEIVED_CWSIA0067",
                                                                  new Object[] { e, "JmsMsgProducerImpl.sendMessage (#8)" },
                                                                  e,
                                                                  "JmsMsgProducerImpl.sendMessage#8",
                                                                  this,
                                                                  tc
                                  );
              }
  
          }
  
          // if the message isn't a jetstream message then copy the changed values
          // back into the original message.
          if (!(message instanceof JmsMessageImpl)) {
              if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled())
                  SibTr.debug(this, tc, "Setting properties back into foreign message");
              message.setJMSDeliveryMode(foreignMsgHelper.getJMSDeliveryMode());
              message.setJMSDestination(foreignMsgHelper.getJMSDestination());
              message.setJMSExpiration(foreignMsgHelper.getJMSExpiration());
              message.setJMSMessageID(foreignMsgHelper.getJMSMessageID());
              message.setJMSPriority(foreignMsgHelper.getJMSPriority());
              message.setJMSTimestamp(foreignMsgHelper.getJMSTimestamp());
              Enumeration propertyNames = foreignMsgHelper.getPropertyNames();
              while (propertyNames.hasMoreElements()) {
                  String name = (String) propertyNames.nextElement();
                  if ((name.startsWith("JMSX")) || (name.startsWith("JMS_IBM"))) {
                      Object value = foreignMsgHelper.getObjectProperty(name);
                      try {
                          message.setObjectProperty(name, value);
                      } catch (JMSException e) {
                          // No FFDC code needed
                          // This can occur if the foreign message doesn't support setting
                          // a particular JMSX, or if it polices the use of JMS_* other than
                          // it's own vendor specific set (like we do).
                          if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled())
                              SibTr.debug(this, tc, "foreign message refused setObjProperty(" + name + "): " + e);
                      }
                  }
              }
          }
  
          if (TraceComponent.isAnyTracingEnabled() && tc.isEntryEnabled())
              SibTr.exit(tc, "sendMessage(ProducerProps, Message, Destination)");
      }