001/**
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.camel.impl;
018
019import java.util.HashSet;
020import java.util.LinkedHashMap;
021import java.util.Map;
022import java.util.Set;
023import java.util.function.Supplier;
024
025import javax.activation.DataHandler;
026
027import org.apache.camel.Attachment;
028import org.apache.camel.CamelContext;
029import org.apache.camel.Exchange;
030import org.apache.camel.spi.HeadersMapFactory;
031import org.apache.camel.util.AttachmentMap;
032import org.apache.camel.util.EndpointHelper;
033import org.apache.camel.util.ObjectHelper;
034
035/**
036 * The default implementation of {@link org.apache.camel.Message}
037 * <p/>
038 * This implementation uses a {@link org.apache.camel.util.CaseInsensitiveMap} storing the headers.
039 * This allows us to be able to lookup headers using case insensitive keys, making it easier for end users
040 * as they do not have to be worried about using exact keys.
041 * See more details at {@link org.apache.camel.util.CaseInsensitiveMap}.
042 * The implementation of the map can be configured by the {@link HeadersMapFactory} which can be set
043 * on the {@link CamelContext}. The default implementation uses the {@link org.apache.camel.util.CaseInsensitiveMap CaseInsensitiveMap}.
044 *
045 * @version 
046 */
047public class DefaultMessage extends MessageSupport {
048    private boolean fault;
049    private Map<String, Object> headers;
050    private Map<String, DataHandler> attachments;
051    private Map<String, Attachment> attachmentObjects;
052
053    /**
054     * @deprecated use {@link #DefaultMessage(CamelContext)}
055     */
056    @Deprecated
057    public DefaultMessage() {
058    }
059
060    public DefaultMessage(CamelContext camelContext) {
061        setCamelContext(camelContext);
062    }
063
064    public boolean isFault() {
065        return fault;
066    }
067
068    public void setFault(boolean fault) {
069        this.fault = fault;
070    }
071
072    public Object getHeader(String name) {
073        if (hasHeaders()) {
074            return getHeaders().get(name);
075        } else {
076            return null;
077        }
078    }
079
080    public Object getHeader(String name, Object defaultValue) {
081        Object answer = getHeaders().get(name);
082        return answer != null ? answer : defaultValue;
083    }
084
085    public Object getHeader(String name, Supplier<Object> defaultValueSupplier) {
086        ObjectHelper.notNull(name, "name");
087        ObjectHelper.notNull(defaultValueSupplier, "defaultValueSupplier");
088        Object answer = getHeaders().get(name);
089        return answer != null ? answer : defaultValueSupplier.get();
090    }
091
092    @SuppressWarnings("unchecked")
093    public <T> T getHeader(String name, Class<T> type) {
094        Object value = getHeader(name);
095        if (value == null) {
096            // lets avoid NullPointerException when converting to boolean for null values
097            if (boolean.class == type) {
098                return (T) Boolean.FALSE;
099            }
100            return null;
101        }
102
103        // eager same instance type test to avoid the overhead of invoking the type converter
104        // if already same type
105        if (type.isInstance(value)) {
106            return type.cast(value);
107        }
108
109        Exchange e = getExchange();
110        if (e != null) {
111            return e.getContext().getTypeConverter().convertTo(type, e, value);
112        } else {
113            return type.cast(value);
114        }
115    }
116
117    @SuppressWarnings("unchecked")
118    public <T> T getHeader(String name, Object defaultValue, Class<T> type) {
119        Object value = getHeader(name, defaultValue);
120        if (value == null) {
121            // lets avoid NullPointerException when converting to boolean for null values
122            if (boolean.class == type) {
123                return (T) Boolean.FALSE;
124            }
125            return null;
126        }
127
128        // eager same instance type test to avoid the overhead of invoking the type converter
129        // if already same type
130        if (type.isInstance(value)) {
131            return type.cast(value);
132        }
133
134        Exchange e = getExchange();
135        if (e != null) {
136            return e.getContext().getTypeConverter().convertTo(type, e, value);
137        } else {
138            return type.cast(value);
139        }
140    }
141
142    @SuppressWarnings("unchecked")
143    public <T> T getHeader(String name, Supplier<Object> defaultValueSupplier, Class<T> type) {
144        ObjectHelper.notNull(name, "name");
145        ObjectHelper.notNull(type, "type");
146        ObjectHelper.notNull(defaultValueSupplier, "defaultValueSupplier");
147        Object value = getHeader(name, defaultValueSupplier);
148        if (value == null) {
149            // lets avoid NullPointerException when converting to boolean for null values
150            if (boolean.class == type) {
151                return (T) Boolean.FALSE;
152            }
153            return null;
154        }
155
156        // eager same instance type test to avoid the overhead of invoking the type converter
157        // if already same type
158        if (type.isInstance(value)) {
159            return type.cast(value);
160        }
161
162        Exchange e = getExchange();
163        if (e != null) {
164            return e.getContext().getTypeConverter().convertTo(type, e, value);
165        } else {
166            return type.cast(value);
167        }
168    }
169
170    public void setHeader(String name, Object value) {
171        if (headers == null) {
172            headers = createHeaders();
173        }
174        headers.put(name, value);
175    }
176
177    public Object removeHeader(String name) {
178        if (!hasHeaders()) {
179            return null;
180        }
181        return headers.remove(name);
182    }
183
184    public boolean removeHeaders(String pattern) {
185        return removeHeaders(pattern, (String[]) null);
186    }
187
188    public boolean removeHeaders(String pattern, String... excludePatterns) {
189        if (!hasHeaders()) {
190            return false;
191        }
192
193        boolean matches = false;
194        // must use a set to store the keys to remove as we cannot walk using entrySet and remove at the same time
195        // due concurrent modification error
196        Set<String> toRemove = new HashSet<>();
197        for (Map.Entry<String, Object> entry : headers.entrySet()) {
198            String key = entry.getKey();
199            if (EndpointHelper.matchPattern(key, pattern)) {
200                if (excludePatterns != null && isExcludePatternMatch(key, excludePatterns)) {
201                    continue;
202                }
203                matches = true;
204                toRemove.add(entry.getKey());
205            }
206        }
207        for (String key : toRemove) {
208            headers.remove(key);
209        }
210
211        return matches;
212    }
213
214    public Map<String, Object> getHeaders() {
215        if (headers == null) {
216            headers = createHeaders();
217        }
218        return headers;
219    }
220
221    public void setHeaders(Map<String, Object> headers) {
222        ObjectHelper.notNull(getCamelContext(), "CamelContext", this);
223
224        if (getCamelContext().getHeadersMapFactory().isInstanceOf(headers)) {
225            this.headers = headers;
226        } else {
227            // create a new map
228            this.headers = getCamelContext().getHeadersMapFactory().newMap(headers);
229        }
230    }
231
232    public boolean hasHeaders() {
233        if (!hasPopulatedHeaders()) {
234            // force creating headers
235            getHeaders();
236        }
237        return headers != null && !headers.isEmpty();
238    }
239
240    public DefaultMessage newInstance() {
241        ObjectHelper.notNull(getCamelContext(), "CamelContext", this);
242
243        return new DefaultMessage(getCamelContext());
244    }
245
246    /**
247     * A factory method to lazily create the headers to make it easy to create
248     * efficient Message implementations which only construct and populate the
249     * Map on demand
250     *
251     * @return return a newly constructed Map possibly containing headers from
252     *         the underlying inbound transport
253     */
254    protected Map<String, Object> createHeaders() {
255        ObjectHelper.notNull(getCamelContext(), "CamelContext", this);
256
257        Map<String, Object> map = getCamelContext().getHeadersMapFactory().newMap();
258        populateInitialHeaders(map);
259        return map;
260    }
261
262    /**
263     * A factory method to lazily create the attachmentObjects to make it easy to
264     * create efficient Message implementations which only construct and
265     * populate the Map on demand
266     *
267     * @return return a newly constructed Map
268     */
269    protected Map<String, Attachment> createAttachments() {
270        Map<String, Attachment> map = new LinkedHashMap<>();
271        populateInitialAttachments(map);
272        return map;
273    }
274
275    /**
276     * A strategy method populate the initial set of headers on an inbound
277     * message from an underlying binding
278     *
279     * @param map is the empty header map to populate
280     */
281    protected void populateInitialHeaders(Map<String, Object> map) {
282        // do nothing by default
283    }
284
285    /**
286     * A strategy method populate the initial set of attachmentObjects on an inbound
287     * message from an underlying binding
288     *
289     * @param map is the empty attachment map to populate
290     */
291    protected void populateInitialAttachments(Map<String, Attachment> map) {
292        // do nothing by default
293    }
294
295    /**
296     * A strategy for component specific messages to determine whether the
297     * message is redelivered or not.
298     * <p/>
299     * <b>Important: </b> It is not always possible to determine if the transacted is a redelivery
300     * or not, and therefore <tt>null</tt> is returned. Such an example would be a JDBC message.
301     * However JMS brokers provides details if a transacted message is redelivered.
302     *
303     * @return <tt>true</tt> if redelivered, <tt>false</tt> if not, <tt>null</tt> if not able to determine
304     */
305    protected Boolean isTransactedRedelivered() {
306        // return null by default
307        return null;
308    }
309
310    public void addAttachment(String id, DataHandler content) {
311        addAttachmentObject(id, new DefaultAttachment(content));
312    }
313
314    public void addAttachmentObject(String id, Attachment content) {
315        if (attachmentObjects == null) {
316            attachmentObjects = createAttachments();
317        }
318        attachmentObjects.put(id, content);
319    }
320
321    public DataHandler getAttachment(String id) {
322        Attachment att = getAttachmentObject(id);
323        if (att == null) {
324            return null;
325        } else {
326            return att.getDataHandler();
327        }
328    }
329
330    @Override
331    public Attachment getAttachmentObject(String id) {
332        return getAttachmentObjects().get(id);
333    }
334
335    public Set<String> getAttachmentNames() {
336        if (attachmentObjects == null) {
337            attachmentObjects = createAttachments();
338        }
339        return attachmentObjects.keySet();
340    }
341
342    public void removeAttachment(String id) {
343        if (attachmentObjects != null && attachmentObjects.containsKey(id)) {
344            attachmentObjects.remove(id);
345        }
346    }
347
348    public Map<String, DataHandler> getAttachments() {
349        if (attachments == null) {
350            attachments = new AttachmentMap(getAttachmentObjects());
351        }
352        return attachments;
353    }
354
355    public Map<String, Attachment> getAttachmentObjects() {
356        if (attachmentObjects == null) {
357            attachmentObjects = createAttachments();
358        }
359        return attachmentObjects;
360    }
361    
362    public void setAttachments(Map<String, DataHandler> attachments) {
363        if (attachments == null) {
364            this.attachmentObjects = null;
365        } else if (attachments instanceof AttachmentMap) {
366            // this way setAttachments(getAttachments()) will tunnel attachment headers
367            this.attachmentObjects = ((AttachmentMap)attachments).getOriginalMap();
368        } else {
369            this.attachmentObjects = new LinkedHashMap<>();
370            for (Map.Entry<String, DataHandler> entry : attachments.entrySet()) {
371                this.attachmentObjects.put(entry.getKey(), new DefaultAttachment(entry.getValue()));
372            }
373        }
374        this.attachments = null;
375    }
376
377    public void setAttachmentObjects(Map<String, Attachment> attachments) {
378        this.attachmentObjects = attachments;
379        this.attachments = null;
380    }
381
382    public boolean hasAttachments() {
383        // optimized to avoid calling createAttachments as that creates a new empty map
384        // that we 99% do not need (only camel-mail supports attachments), and we have
385        // then ensure camel-mail always creates attachments to remedy for this
386        return this.attachmentObjects != null && this.attachmentObjects.size() > 0;
387    }
388
389    /**
390     * Returns true if the headers have been mutated in some way
391     */
392    protected boolean hasPopulatedHeaders() {
393        return headers != null;
394    }
395
396    public String createExchangeId() {
397        return null;
398    }
399
400    private static boolean isExcludePatternMatch(String key, String... excludePatterns) {
401        for (String pattern : excludePatterns) {
402            if (EndpointHelper.matchPattern(key, pattern)) {
403                return true;
404            }
405        }
406        return false;
407    }
408
409}