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