/** * 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.impl; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; import java.util.function.Supplier; import javax.activation.DataHandler; import org.apache.camel.Attachment; import org.apache.camel.Exchange; import org.apache.camel.util.AttachmentMap; import org.apache.camel.util.CaseInsensitiveMap; import org.apache.camel.util.EndpointHelper; import org.apache.camel.util.ObjectHelper; /** * The default implementation of {@link org.apache.camel.Message} * <p/> * This implementation uses a {@link org.apache.camel.util.CaseInsensitiveMap} storing the headers. * This allows us to be able to lookup headers using case insensitive keys, making it easier for end users * as they do not have to be worried about using exact keys. * See more details at {@link org.apache.camel.util.CaseInsensitiveMap}. * * @version */ public class DefaultMessage extends MessageSupport { private boolean fault; private Map<String, Object> headers; private Map<String, DataHandler> attachments; private Map<String, Attachment> attachmentObjects; public boolean isFault() { return fault; } public void setFault(boolean fault) { this.fault = fault; } public Object getHeader(String name) { if (hasHeaders()) { return getHeaders().get(name); } else { return null; } } public Object getHeader(String name, Object defaultValue) { Object answer = getHeaders().get(name); return answer != null ? answer : defaultValue; } public Object getHeader(String name, Supplier<Object> defaultValueSupplier) { ObjectHelper.notNull(name, "name"); ObjectHelper.notNull(defaultValueSupplier, "defaultValueSupplier"); Object answer = getHeaders().get(name); return answer != null ? answer : defaultValueSupplier.get(); } @SuppressWarnings("unchecked") public <T> T getHeader(String name, Class<T> type) { Object value = getHeader(name); if (value == null) { // lets avoid NullPointerException when converting to boolean for null values if (boolean.class.isAssignableFrom(type)) { return (T) Boolean.FALSE; } return null; } // eager same instance type test to avoid the overhead of invoking the type converter // if already same type if (type.isInstance(value)) { return type.cast(value); } Exchange e = getExchange(); if (e != null) { return e.getContext().getTypeConverter().convertTo(type, e, value); } else { return type.cast(value); } } @SuppressWarnings("unchecked") public <T> T getHeader(String name, Object defaultValue, Class<T> type) { Object value = getHeader(name, defaultValue); if (value == null) { // lets avoid NullPointerException when converting to boolean for null values if (boolean.class.isAssignableFrom(type)) { return (T) Boolean.FALSE; } return null; } // eager same instance type test to avoid the overhead of invoking the type converter // if already same type if (type.isInstance(value)) { return type.cast(value); } Exchange e = getExchange(); if (e != null) { return e.getContext().getTypeConverter().convertTo(type, e, value); } else { return type.cast(value); } } @SuppressWarnings("unchecked") public <T> T getHeader(String name, Supplier<Object> defaultValueSupplier, Class<T> type) { ObjectHelper.notNull(name, "name"); ObjectHelper.notNull(type, "type"); ObjectHelper.notNull(defaultValueSupplier, "defaultValueSupplier"); Object value = getHeader(name, defaultValueSupplier); if (value == null) { // lets avoid NullPointerException when converting to boolean for null values if (boolean.class.isAssignableFrom(type)) { return (T) Boolean.FALSE; } return null; } // eager same instance type test to avoid the overhead of invoking the type converter // if already same type if (type.isInstance(value)) { return type.cast(value); } Exchange e = getExchange(); if (e != null) { return e.getContext().getTypeConverter().convertTo(type, e, value); } else { return type.cast(value); } } public void setHeader(String name, Object value) { if (headers == null) { headers = createHeaders(); } headers.put(name, value); } public Object removeHeader(String name) { if (!hasHeaders()) { return null; } return headers.remove(name); } public boolean removeHeaders(String pattern) { return removeHeaders(pattern, (String[]) null); } public boolean removeHeaders(String pattern, String... excludePatterns) { if (!hasHeaders()) { return false; } boolean matches = false; // must use a set to store the keys to remove as we cannot walk using entrySet and remove at the same time // due concurrent modification error Set<String> toRemove = new HashSet<String>(); for (Map.Entry<String, Object> entry : headers.entrySet()) { String key = entry.getKey(); if (EndpointHelper.matchPattern(key, pattern)) { if (excludePatterns != null && isExcludePatternMatch(key, excludePatterns)) { continue; } matches = true; toRemove.add(entry.getKey()); } } for (String key : toRemove) { headers.remove(key); } return matches; } public Map<String, Object> getHeaders() { if (headers == null) { headers = createHeaders(); } return headers; } public void setHeaders(Map<String, Object> headers) { if (headers instanceof CaseInsensitiveMap) { this.headers = headers; } else { // wrap it in a case insensitive map this.headers = new CaseInsensitiveMap(headers); } } public boolean hasHeaders() { if (!hasPopulatedHeaders()) { // force creating headers getHeaders(); } return headers != null && !headers.isEmpty(); } public DefaultMessage newInstance() { return new DefaultMessage(); } /** * A factory method to lazily create the headers to make it easy to create * efficient Message implementations which only construct and populate the * Map on demand * * @return return a newly constructed Map possibly containing headers from * the underlying inbound transport */ protected Map<String, Object> createHeaders() { Map<String, Object> map = new CaseInsensitiveMap(); populateInitialHeaders(map); return map; } /** * A factory method to lazily create the attachmentObjects to make it easy to * create efficient Message implementations which only construct and * populate the Map on demand * * @return return a newly constructed Map */ protected Map<String, Attachment> createAttachments() { Map<String, Attachment> map = new LinkedHashMap<String, Attachment>(); populateInitialAttachments(map); return map; } /** * A strategy method populate the initial set of headers on an inbound * message from an underlying binding * * @param map is the empty header map to populate */ protected void populateInitialHeaders(Map<String, Object> map) { // do nothing by default } /** * A strategy method populate the initial set of attachmentObjects on an inbound * message from an underlying binding * * @param map is the empty attachment map to populate */ protected void populateInitialAttachments(Map<String, Attachment> map) { // do nothing by default } /** * A strategy for component specific messages to determine whether the * message is redelivered or not. * <p/> * <b>Important: </b> It is not always possible to determine if the transacted is a redelivery * or not, and therefore <tt>null</tt> is returned. Such an example would be a JDBC message. * However JMS brokers provides details if a transacted message is redelivered. * * @return <tt>true</tt> if redelivered, <tt>false</tt> if not, <tt>null</tt> if not able to determine */ protected Boolean isTransactedRedelivered() { // return null by default return null; } public void addAttachment(String id, DataHandler content) { addAttachmentObject(id, new DefaultAttachment(content)); } public void addAttachmentObject(String id, Attachment content) { if (attachmentObjects == null) { attachmentObjects = createAttachments(); } attachmentObjects.put(id, content); } public DataHandler getAttachment(String id) { Attachment att = getAttachmentObject(id); if (att == null) { return null; } else { return att.getDataHandler(); } } @Override public Attachment getAttachmentObject(String id) { return getAttachmentObjects().get(id); } public Set<String> getAttachmentNames() { if (attachmentObjects == null) { attachmentObjects = createAttachments(); } return attachmentObjects.keySet(); } public void removeAttachment(String id) { if (attachmentObjects != null && attachmentObjects.containsKey(id)) { attachmentObjects.remove(id); } } public Map<String, DataHandler> getAttachments() { if (attachments == null) { attachments = new AttachmentMap(getAttachmentObjects()); } return attachments; } public Map<String, Attachment> getAttachmentObjects() { if (attachmentObjects == null) { attachmentObjects = createAttachments(); } return attachmentObjects; } public void setAttachments(Map<String, DataHandler> attachments) { if (attachments == null) { this.attachmentObjects = null; } else if (attachments instanceof AttachmentMap) { // this way setAttachments(getAttachments()) will tunnel attachment headers this.attachmentObjects = ((AttachmentMap)attachments).getOriginalMap(); } else { this.attachmentObjects = new LinkedHashMap<String, Attachment>(); for (Map.Entry<String, DataHandler> entry : attachments.entrySet()) { this.attachmentObjects.put(entry.getKey(), new DefaultAttachment(entry.getValue())); } } this.attachments = null; } public void setAttachmentObjects(Map<String, Attachment> attachments) { this.attachmentObjects = attachments; this.attachments = null; } public boolean hasAttachments() { // optimized to avoid calling createAttachments as that creates a new empty map // that we 99% do not need (only camel-mail supports attachments), and we have // then ensure camel-mail always creates attachments to remedy for this return this.attachmentObjects != null && this.attachmentObjects.size() > 0; } /** * Returns true if the headers have been mutated in some way */ protected boolean hasPopulatedHeaders() { return headers != null; } public String createExchangeId() { return null; } private static boolean isExcludePatternMatch(String key, String... excludePatterns) { for (String pattern : excludePatterns) { if (EndpointHelper.matchPattern(key, pattern)) { return true; } } return false; } }