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