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