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