/* * Copyright (c) MuleSoft, Inc. All rights reserved. http://www.mulesoft.com * The software in this package is published under the terms of the CPAL v1.0 * license, a copy of which has been included with this distribution in the * LICENSE.txt file. */ package org.mule.runtime.core.internal.message; import static java.util.Arrays.asList; import static java.util.Collections.emptyMap; import static java.util.Collections.unmodifiableSet; import static java.util.Objects.requireNonNull; import static org.apache.commons.lang.ObjectUtils.defaultIfNull; import static org.apache.commons.lang.SystemUtils.LINE_SEPARATOR; import static org.mule.runtime.api.message.NullAttributes.NULL_ATTRIBUTES; import static org.mule.runtime.core.PropertyScope.INBOUND; import static org.mule.runtime.core.PropertyScope.OUTBOUND; import static org.mule.runtime.core.api.Event.getCurrentEvent; import static org.mule.runtime.core.util.ObjectUtils.getBoolean; import static org.mule.runtime.core.util.ObjectUtils.getByte; import static org.mule.runtime.core.util.ObjectUtils.getDouble; import static org.mule.runtime.core.util.ObjectUtils.getFloat; import static org.mule.runtime.core.util.ObjectUtils.getInt; import static org.mule.runtime.core.util.ObjectUtils.getLong; import static org.mule.runtime.core.util.ObjectUtils.getShort; import static org.mule.runtime.core.util.ObjectUtils.getString; import org.mule.runtime.api.exception.MuleException; import org.mule.runtime.api.metadata.DataType; import org.mule.runtime.api.metadata.DataTypeBuilder; import org.mule.runtime.api.metadata.MediaType; import org.mule.runtime.api.metadata.TypedValue; import org.mule.runtime.api.util.CaseInsensitiveMapWrapper; import org.mule.runtime.core.api.MuleContext; import org.mule.runtime.core.api.message.ExceptionPayload; import org.mule.runtime.core.api.transformer.Transformer; import org.mule.runtime.core.api.transformer.TransformerException; import org.mule.runtime.core.config.i18n.CoreMessages; import org.mule.runtime.core.internal.message.InternalMessage.CollectionBuilder; import org.mule.runtime.core.internal.metadata.DefaultCollectionDataType; import org.mule.runtime.core.util.store.DeserializationPostInitialisable; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.TreeSet; import java.util.function.Function; import javax.activation.DataHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class DefaultMessageBuilder implements InternalMessage.Builder, InternalMessage.PayloadBuilder, InternalMessage.AttributesBuilder, InternalMessage.CollectionBuilder { private Object payload; private DataType dataType; private Object attributes = NULL_ATTRIBUTES; private DataType attributesDataType; private ExceptionPayload exceptionPayload; private Map<String, TypedValue<Serializable>> inboundProperties = new CaseInsensitiveMapWrapper<>(); private Map<String, TypedValue<Serializable>> outboundProperties = new CaseInsensitiveMapWrapper<>(); private Map<String, DataHandler> inboundAttachments = new HashMap<>(); private Map<String, DataHandler> outboundAttachments = new HashMap<>(); public DefaultMessageBuilder() {} private void copyMessageAttributes(InternalMessage message) { this.exceptionPayload = message.getExceptionPayload(); message.getInboundPropertyNames().forEach(key -> { if (message.getInboundPropertyDataType(key) != null) { addInboundProperty(key, message.getInboundProperty(key), message.getInboundPropertyDataType(key)); } else { addInboundProperty(key, message.getInboundProperty(key)); } }); message.getOutboundPropertyNames().forEach(key -> { if (message.getOutboundPropertyDataType(key) != null) { addOutboundProperty(key, message.getOutboundProperty(key), message.getOutboundPropertyDataType(key)); } else { addOutboundProperty(key, message.getOutboundProperty(key)); } }); message.getInboundAttachmentNames().forEach(name -> addInboundAttachment(name, message.getInboundAttachment(name))); message.getOutboundAttachmentNames().forEach(name -> addOutboundAttachment(name, message.getOutboundAttachment(name))); } public DefaultMessageBuilder(org.mule.runtime.api.message.Message message) { requireNonNull(message); this.payload = message.getPayload().getValue(); this.dataType = message.getPayload().getDataType(); this.attributes = message.getAttributes().getValue(); this.attributesDataType = message.getAttributes().getDataType(); if (message instanceof InternalMessage) { copyMessageAttributes((InternalMessage) message); } } @Override public InternalMessage.Builder nullPayload() { this.payload = null; return this; } @Override public InternalMessage.Builder payload(Object payload) { this.payload = payload; return this; } @Override public InternalMessage.Builder mediaType(MediaType mediaType) { this.dataType = DataType.builder().mediaType(mediaType).build(); return this; } @Override public InternalMessage.CollectionBuilder streamPayload(Iterator payload, Class<?> clazz) { requireNonNull(payload); this.payload = payload; this.dataType = DataType.builder().streamType(payload.getClass()).itemType(clazz).build(); return this; } @Override public InternalMessage.CollectionBuilder collectionPayload(Collection payload, Class<?> clazz) { requireNonNull(payload); this.payload = payload; this.dataType = DataType.builder().collectionType(payload.getClass()).itemType(clazz).build(); return this; } @Override public InternalMessage.CollectionBuilder collectionPayload(Object[] payload) { requireNonNull(payload); return collectionPayload(asList(payload), payload.getClass().getComponentType()); } @Override public CollectionBuilder itemMediaType(MediaType mediaType) { if (dataType instanceof DefaultCollectionDataType) { dataType = ((DataTypeBuilder.DataTypeCollectionTypeBuilder) DataType.builder(this.dataType)).itemMediaType(mediaType).build(); } else { throw new IllegalStateException("Item MediaType cannot be set, because payload is not a collection"); } return this; } @Override public InternalMessage.Builder nullAttributes() { this.attributes = NULL_ATTRIBUTES; return this; } @Override public InternalMessage.Builder attributes(Object attributes) { this.attributes = attributes; return this; } @Override public InternalMessage.Builder attributesMediaType(MediaType mediaType) { this.attributesDataType = DataType.builder().mediaType(mediaType).build(); return this; } @Override public InternalMessage.Builder exceptionPayload(ExceptionPayload exceptionPayload) { this.exceptionPayload = exceptionPayload; return this; } @Override public InternalMessage.Builder addInboundProperty(String key, Serializable value) { inboundProperties.put(key, new TypedValue(value, value != null ? DataType.fromObject(value) : DataType.OBJECT)); return this; } @Override public InternalMessage.Builder addInboundProperty(String key, Serializable value, MediaType mediaType) { inboundProperties.put(key, new TypedValue(value, DataType.builder().type(value.getClass()).mediaType(mediaType).build())); return this; } @Override public InternalMessage.Builder addInboundProperty(String key, Serializable value, DataType dataType) { inboundProperties.put(key, new TypedValue(value, dataType)); return this; } @Override public InternalMessage.Builder addOutboundProperty(String key, Serializable value) { outboundProperties.put(key, new TypedValue(value, value != null ? DataType.fromObject(value) : DataType.OBJECT)); return this; } @Override public InternalMessage.Builder addOutboundProperty(String key, Serializable value, MediaType mediaType) { outboundProperties.put(key, new TypedValue(value, DataType.builder().type(value.getClass()).mediaType(mediaType).build())); return this; } @Override public InternalMessage.Builder addOutboundProperty(String key, Serializable value, DataType dataType) { outboundProperties.put(key, new TypedValue(value, dataType)); return this; } @Override public InternalMessage.Builder removeInboundProperty(String key) { inboundProperties.remove(key); return this; } @Override public InternalMessage.Builder removeOutboundProperty(String key) { outboundProperties.remove(key); return this; } @Override public InternalMessage.Builder addInboundAttachment(String key, DataHandler value) { inboundAttachments.put(key, value); return this; } @Override public InternalMessage.Builder addOutboundAttachment(String key, DataHandler value) { outboundAttachments.put(key, value); return this; } @Override public InternalMessage.Builder removeInboundAttachment(String key) { inboundAttachments.remove(key); return this; } @Override public InternalMessage.Builder removeOutboundAttachment(String key) { outboundAttachments.remove(key); return this; } @Override public InternalMessage.Builder inboundProperties(Map<String, Serializable> inboundProperties) { requireNonNull(inboundProperties); this.inboundProperties.clear(); inboundProperties.forEach((s, serializable) -> addInboundProperty(s, serializable)); return this; } @Override public InternalMessage.Builder outboundProperties(Map<String, Serializable> outboundProperties) { requireNonNull(outboundProperties); this.outboundProperties.clear(); outboundProperties.forEach((s, serializable) -> addOutboundProperty(s, serializable)); return this; } @Override public InternalMessage.Builder inboundAttachments(Map<String, DataHandler> inboundAttachments) { requireNonNull(inboundAttachments); this.inboundAttachments = new HashMap<>(inboundAttachments); return this; } @Override public InternalMessage.Builder outboundAttachments(Map<String, DataHandler> outbundAttachments) { requireNonNull(outbundAttachments); this.outboundAttachments = new HashMap<>(outbundAttachments); return this; } @Override public InternalMessage build() { return new MessageImplementation(new TypedValue(payload, resolveDataType()), new TypedValue(attributes, resolveAttributesDataType()), inboundProperties, outboundProperties, inboundAttachments, outboundAttachments, exceptionPayload); } private DataType resolveDataType() { if (dataType == null) { return DataType.fromObject(payload); } else { return DataType.builder(dataType).fromObject(payload).build(); } } private DataType resolveAttributesDataType() { if (attributesDataType == null) { return DataType.fromObject(attributes); } else { return DataType.builder(attributesDataType).fromObject(attributes).build(); } } /** * Provides access to the class that implements {@link org.mule.runtime.api.message.Message} which is constructed using the * builder. * <p/> * This method is required to be able to add a custom serializer for the message implementation without having to expose the * class in the API. * * @return the class used to implement {@link org.mule.runtime.api.message.Message} */ public static Class getMessageImplementationClass() { return MessageImplementation.class; } /** * <code>MuleMessageImplementation</code> is a wrapper that contains a payload and properties associated with the payload. */ private static class MessageImplementation implements InternalMessage, DeserializationPostInitialisable { private static final String NOT_SET = "<not set>"; private static final long serialVersionUID = 1541720810851984845L; private static final Logger logger = LoggerFactory.getLogger(MessageImplementation.class); /** * If an exception occurs while processing this message an exception payload will be attached here */ private ExceptionPayload exceptionPayload; /** * Collection of attachments that were attached to the incoming message */ private transient Map<String, DataHandler> inboundAttachments = new HashMap<>(); /** * Collection of attachments that will be sent out with this message */ private transient Map<String, DataHandler> outboundAttachments = new HashMap<>(); private transient TypedValue typedValue; private TypedValue typedAttributes; private Map<String, TypedValue<Serializable>> inboundMap = new CaseInsensitiveMapWrapper<>(); private Map<String, TypedValue<Serializable>> outboundMap = new CaseInsensitiveMapWrapper<>(); private MessageImplementation(TypedValue typedValue, TypedValue typedAttributes, Map<String, TypedValue<Serializable>> inboundProperties, Map<String, TypedValue<Serializable>> outboundProperties, Map<String, DataHandler> inboundAttachments, Map<String, DataHandler> outboundAttachments, ExceptionPayload exceptionPayload) { this.typedValue = typedValue; this.typedAttributes = typedAttributes; this.inboundMap.putAll(inboundProperties); this.outboundMap.putAll(outboundProperties); this.inboundAttachments = inboundAttachments; this.outboundAttachments = outboundAttachments; this.exceptionPayload = exceptionPayload; } /** * {@inheritDoc} */ @Override public ExceptionPayload getExceptionPayload() { return exceptionPayload; } @Override public String toString() { StringBuilder buf = new StringBuilder(120); // format message for multi-line output, single-line is not readable buf.append(LINE_SEPARATOR); buf.append(getClass().getName()); buf.append(LINE_SEPARATOR); buf.append("{"); buf.append(LINE_SEPARATOR); buf.append(" payload=").append(getPayload().getDataType().getType().getName()); buf.append(LINE_SEPARATOR); buf.append(" mediaType=").append(getPayload().getDataType().getMediaType()); buf.append(LINE_SEPARATOR); buf.append(" attributes=").append(getAttributes().getValue().toString()); buf.append(LINE_SEPARATOR); buf.append(" exceptionPayload=").append(defaultIfNull(exceptionPayload, NOT_SET)); buf.append(LINE_SEPARATOR); if (!getInboundPropertyNames().isEmpty() || !getOutboundPropertyNames().isEmpty()) { headersToStringBuilder(this, buf); } // no new line here, as headersToString() adds one buf.append('}'); return buf.toString(); } public static void headersToStringBuilder(InternalMessage m, StringBuilder buf) { buf.append(" Message properties:").append(LINE_SEPARATOR); try { if (!m.getInboundPropertyNames().isEmpty()) { Set<String> inboundNames = new TreeSet(m.getInboundPropertyNames()); buf.append(" ").append(INBOUND.toString().toUpperCase()).append(" scoped properties:").append(LINE_SEPARATOR); appendPropertyValues(m, buf, inboundNames, name -> m.getInboundProperty(name)); } if (!m.getOutboundPropertyNames().isEmpty()) { Set<String> outboundNames = new TreeSet(m.getOutboundPropertyNames()); buf.append(" ").append(OUTBOUND.toString().toUpperCase()).append(" scoped properties:").append(LINE_SEPARATOR); appendPropertyValues(m, buf, outboundNames, name -> m.getOutboundProperty(name)); } } catch (IllegalArgumentException e) { // ignored } } private static void appendPropertyValues(InternalMessage m, StringBuilder buf, Set<String> names, Function<String, Serializable> valueResolver) { for (String name : names) { Serializable value = valueResolver.apply(name); // avoid calling toString recursively on Messages if (value instanceof InternalMessage) { value = "<<<Message>>>"; } if (name.equals("password") || name.toString().contains("secret") || name.equals("pass")) { value = "****"; } buf.append(" ").append(name).append("=").append(value).append(LINE_SEPARATOR); } } @Override public DataHandler getInboundAttachment(String name) { return inboundAttachments.get(name); } @Override public DataHandler getOutboundAttachment(String name) { return outboundAttachments.get(name); } @Override public Set<String> getInboundAttachmentNames() { return unmodifiableSet(inboundAttachments.keySet()); } @Override public Set<String> getOutboundAttachmentNames() { return unmodifiableSet(outboundAttachments.keySet()); } @Override public TypedValue getPayload() { return typedValue; } public static class SerializedDataHandler implements Serializable { private static final long serialVersionUID = 1L; private DataHandler handler; private String contentType; private Object contents; public SerializedDataHandler(String name, DataHandler handler, MuleContext muleContext) throws IOException { if (handler != null && !(handler instanceof Serializable)) { contentType = handler.getContentType(); Object theContent = handler.getContent(); if (theContent instanceof Serializable) { contents = theContent; } else { try { DataType source = DataType.fromObject(theContent); Transformer transformer = muleContext.getRegistry().lookupTransformer(source, DataType.BYTE_ARRAY); if (transformer == null) { throw new TransformerException(CoreMessages.noTransformerFoundForMessage(source, DataType.BYTE_ARRAY)); } contents = transformer.transform(theContent); } catch (TransformerException ex) { String message = String.format( "Unable to serialize the attachment %s, which is of type %s with contents of type %s", name, handler.getClass(), theContent.getClass()); logger.error(message); throw new IOException(message); } } } else { this.handler = handler; } } public DataHandler getHandler() { return contents != null ? new DataHandler(contents, contentType) : handler; } } private void writeObject(ObjectOutputStream out) throws Exception { out.defaultWriteObject(); serializeValue(out); out.writeObject(serializeAttachments(inboundAttachments)); out.writeObject(serializeAttachments(outboundAttachments)); } private Map<String, SerializedDataHandler> serializeAttachments(Map<String, DataHandler> attachments) throws IOException { Map<String, SerializedDataHandler> toWrite; if (attachments == null) { toWrite = null; } else { toWrite = new HashMap<>(attachments.size()); for (Map.Entry<String, DataHandler> entry : attachments.entrySet()) { String name = entry.getKey(); // TODO MULE-10013 remove this logic from here toWrite.put(name, new SerializedDataHandler(name, entry.getValue(), getCurrentEvent().getMuleContext())); } } return toWrite; } protected void serializeValue(ObjectOutputStream out) throws Exception { if (typedValue.getValue() == null || typedValue.getValue() instanceof Serializable) { out.writeBoolean(true); out.writeObject(typedValue.getValue()); out.writeObject(typedValue.getDataType()); } else { out.writeBoolean(false); // TODO MULE-10013 remove this logic from here byte[] valueAsByteArray = (byte[]) getCurrentEvent().getMuleContext().getTransformationService() .transform(this, DataType.BYTE_ARRAY).getPayload().getValue(); out.writeInt(valueAsByteArray.length); new DataOutputStream(out).write(valueAsByteArray); out.writeObject(DataType.BYTE_ARRAY); } } protected Object deserializeValue(ObjectInputStream in) throws Exception { if (in.readBoolean()) { return in.readObject(); } else { int length = in.readInt(); byte[] valueAsByteArray = new byte[length]; new DataInputStream(in).readFully(valueAsByteArray); return valueAsByteArray; } } private Map<String, DataHandler> deserializeAttachments(Map<String, SerializedDataHandler> attachments) throws IOException { Map<String, DataHandler> toReturn; if (attachments == null) { toReturn = emptyMap(); } else { toReturn = new HashMap<>(attachments.size()); for (Map.Entry<String, SerializedDataHandler> entry : attachments.entrySet()) { toReturn.put(entry.getKey(), entry.getValue().getHandler()); } } return toReturn; } private void readObject(ObjectInputStream in) throws Exception { in.defaultReadObject(); typedValue = new TypedValue(deserializeValue(in), (DataType) in.readObject()); inboundAttachments = deserializeAttachments((Map<String, SerializedDataHandler>) in.readObject()); outboundAttachments = deserializeAttachments((Map<String, SerializedDataHandler>) in.readObject()); } /** * Invoked after deserialization. This is called when the marker interface {@link DeserializationPostInitialisable} is used. * This will get invoked after the object has been deserialized passing in the current mulecontext when using either * {@link org.mule.runtime.core.transformer.wire.SerializationWireFormat}, * {@link org.mule.runtime.core.transformer.wire.SerializedMuleMessageWireFormat} or the * {@link org.mule.runtime.core.transformer.simple.ByteArrayToSerializable} transformer. * * @param context the current muleContext instance * @throws MuleException if there is an error initializing */ public void initAfterDeserialisation(MuleContext context) throws MuleException { if (this.inboundAttachments == null) { this.inboundAttachments = new HashMap<>(); } if (this.outboundAttachments == null) { this.outboundAttachments = new HashMap<>(); } } @Override public TypedValue getAttributes() { return typedAttributes; } @Override public Serializable getInboundProperty(String name) { return getInboundProperty(name, null); } @Override public <T extends Serializable> T getInboundProperty(String name, T defaultValue) { return getValueOrDefault((TypedValue<T>) inboundMap.get(name), defaultValue); } @Override public Serializable getOutboundProperty(String name) { return getOutboundProperty(name, null); } @Override public <T extends Serializable> T getOutboundProperty(String name, T defaultValue) { return getValueOrDefault((TypedValue<T>) outboundMap.get(name), defaultValue); } @Override public Set<String> getInboundPropertyNames() { return unmodifiableSet(inboundMap.keySet()); } @Override public Set<String> getOutboundPropertyNames() { return unmodifiableSet(outboundMap.keySet()); } @Override public DataType getInboundPropertyDataType(String name) { TypedValue typedValue = inboundMap.get(name); return typedValue == null ? null : typedValue.getDataType(); } @Override public DataType getOutboundPropertyDataType(String name) { TypedValue typedValue = outboundMap.get(name); return typedValue == null ? null : typedValue.getDataType(); } private <T extends Serializable> T getValueOrDefault(TypedValue<T> typedValue, T defaultValue) { if (typedValue == null) { return defaultValue; } T value = typedValue.getValue(); // Note that we need to keep the (redundant) casts in here because the compiler compiler complains // about primitive types being cast to a generic type if (defaultValue == null) { return value; } else if (defaultValue instanceof Boolean) { return (T) (Boolean) getBoolean(value, (Boolean) defaultValue); } else if (defaultValue instanceof Byte) { return (T) (Byte) getByte(value, (Byte) defaultValue); } else if (defaultValue instanceof Integer) { return (T) (Integer) getInt(value, (Integer) defaultValue); } else if (defaultValue instanceof Short) { return (T) (Short) getShort(value, (Short) defaultValue); } else if (defaultValue instanceof Long) { return (T) (Long) getLong(value, (Long) defaultValue); } else if (defaultValue instanceof Float) { return (T) (Float) getFloat(value, (Float) defaultValue); } else if (defaultValue instanceof Double) { return (T) (Double) getDouble(value, (Double) defaultValue); } else if (defaultValue instanceof String) { return (T) getString(value, (String) defaultValue); } else { if (value == null) { return defaultValue; } // If defaultValue is set and the result is not null, then validate that they are assignable else if (defaultValue.getClass().isAssignableFrom(value.getClass())) { return value; } else { throw new IllegalArgumentException(CoreMessages.objectNotOfCorrectType(value.getClass(), defaultValue.getClass()) .getMessage()); } } } } }