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