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.HashMap;
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 HashMap<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 }