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