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