/** * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.camel.impl; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.apache.camel.CamelContext; import org.apache.camel.Endpoint; import org.apache.camel.Exchange; import org.apache.camel.ExchangePattern; import org.apache.camel.Message; import org.apache.camel.MessageHistory; import org.apache.camel.spi.Synchronization; import org.apache.camel.spi.UnitOfWork; import org.apache.camel.util.CaseInsensitiveMap; import org.apache.camel.util.EndpointHelper; import org.apache.camel.util.ExchangeHelper; import org.apache.camel.util.ObjectHelper; /** * A default implementation of {@link Exchange} * * @version */ public final class DefaultExchange implements Exchange { protected final CamelContext context; private Map<String, Object> properties; private Message in; private Message out; private Exception exception; private String exchangeId; private UnitOfWork unitOfWork; private ExchangePattern pattern; private Endpoint fromEndpoint; private String fromRouteId; private List<Synchronization> onCompletions; public DefaultExchange(CamelContext context) { this(context, ExchangePattern.InOnly); } public DefaultExchange(CamelContext context, ExchangePattern pattern) { this.context = context; this.pattern = pattern; } public DefaultExchange(Exchange parent) { this(parent.getContext(), parent.getPattern()); this.fromEndpoint = parent.getFromEndpoint(); this.fromRouteId = parent.getFromRouteId(); this.unitOfWork = parent.getUnitOfWork(); } public DefaultExchange(Endpoint fromEndpoint) { this(fromEndpoint, ExchangePattern.InOnly); } public DefaultExchange(Endpoint fromEndpoint, ExchangePattern pattern) { this(fromEndpoint.getCamelContext(), pattern); this.fromEndpoint = fromEndpoint; } @Override public String toString() { // do not output information about the message as it may contain sensitive information return String.format("Exchange[%s]", exchangeId == null ? "" : exchangeId); } public Exchange copy() { // to be backwards compatible as today return copy(false); } public Exchange copy(boolean safeCopy) { DefaultExchange exchange = new DefaultExchange(this); if (safeCopy) { exchange.getIn().setBody(getIn().getBody()); exchange.getIn().setFault(getIn().isFault()); if (getIn().hasHeaders()) { exchange.getIn().setHeaders(safeCopyHeaders(getIn().getHeaders())); // just copy the attachments here exchange.getIn().copyAttachments(getIn()); } if (hasOut()) { exchange.getOut().setBody(getOut().getBody()); exchange.getOut().setFault(getOut().isFault()); if (getOut().hasHeaders()) { exchange.getOut().setHeaders(safeCopyHeaders(getOut().getHeaders())); } // Just copy the attachments here exchange.getOut().copyAttachments(getOut()); } } else { // old way of doing copy which is @deprecated // TODO: remove this in Camel 3.0, and always do a safe copy exchange.setIn(getIn().copy()); if (hasOut()) { exchange.setOut(getOut().copy()); } } exchange.setException(getException()); // copy properties after body as body may trigger lazy init if (hasProperties()) { exchange.setProperties(safeCopyProperties(getProperties())); } return exchange; } private static Map<String, Object> safeCopyHeaders(Map<String, Object> headers) { if (headers == null) { return null; } Map<String, Object> answer = new CaseInsensitiveMap(); answer.putAll(headers); return answer; } @SuppressWarnings("unchecked") private static Map<String, Object> safeCopyProperties(Map<String, Object> properties) { if (properties == null) { return null; } // TODO: properties should use same map kind as headers Map<String, Object> answer = new ConcurrentHashMap<String, Object>(properties); // safe copy message history using a defensive copy List<MessageHistory> history = (List<MessageHistory>) answer.remove(Exchange.MESSAGE_HISTORY); if (history != null) { answer.put(Exchange.MESSAGE_HISTORY, new LinkedList<>(history)); } return answer; } public CamelContext getContext() { return context; } public Object getProperty(String name) { if (properties != null) { return properties.get(name); } return null; } public Object getProperty(String name, Object defaultValue) { Object answer = getProperty(name); return answer != null ? answer : defaultValue; } @SuppressWarnings("unchecked") public <T> T getProperty(String name, Class<T> type) { Object value = getProperty(name); if (value == null) { // lets avoid NullPointerException when converting to boolean for null values if (boolean.class.isAssignableFrom(type)) { return (T) Boolean.FALSE; } return null; } // eager same instance type test to avoid the overhead of invoking the type converter // if already same type if (type.isInstance(value)) { return type.cast(value); } return ExchangeHelper.convertToType(this, type, value); } @SuppressWarnings("unchecked") public <T> T getProperty(String name, Object defaultValue, Class<T> type) { Object value = getProperty(name, defaultValue); if (value == null) { // lets avoid NullPointerException when converting to boolean for null values if (boolean.class.isAssignableFrom(type)) { return (T) Boolean.FALSE; } return null; } // eager same instance type test to avoid the overhead of invoking the type converter // if already same type if (type.isInstance(value)) { return type.cast(value); } return ExchangeHelper.convertToType(this, type, value); } public void setProperty(String name, Object value) { if (value != null) { // avoid the NullPointException getProperties().put(name, value); } else { // if the value is null, we just remove the key from the map if (name != null) { getProperties().remove(name); } } } public Object removeProperty(String name) { if (!hasProperties()) { return null; } return getProperties().remove(name); } public boolean removeProperties(String pattern) { return removeProperties(pattern, (String[]) null); } public boolean removeProperties(String pattern, String... excludePatterns) { if (!hasProperties()) { return false; } boolean matches = false; for (Map.Entry<String, Object> entry : properties.entrySet()) { String key = entry.getKey(); if (EndpointHelper.matchPattern(key, pattern)) { if (excludePatterns != null && isExcludePatternMatch(key, excludePatterns)) { continue; } matches = true; properties.remove(entry.getKey()); } } return matches; } public Map<String, Object> getProperties() { if (properties == null) { properties = new ConcurrentHashMap<String, Object>(); } return properties; } public boolean hasProperties() { return properties != null && !properties.isEmpty(); } public void setProperties(Map<String, Object> properties) { this.properties = properties; } public Message getIn() { if (in == null) { in = new DefaultMessage(); configureMessage(in); } return in; } public <T> T getIn(Class<T> type) { Message in = getIn(); // eager same instance type test to avoid the overhead of invoking the type converter // if already same type if (type.isInstance(in)) { return type.cast(in); } // fallback to use type converter return context.getTypeConverter().convertTo(type, this, in); } public void setIn(Message in) { this.in = in; configureMessage(in); } public Message getOut() { // lazy create if (out == null) { out = (in != null && in instanceof MessageSupport) ? ((MessageSupport)in).newInstance() : new DefaultMessage(); configureMessage(out); } return out; } public <T> T getOut(Class<T> type) { if (!hasOut()) { return null; } Message out = getOut(); // eager same instance type test to avoid the overhead of invoking the type converter // if already same type if (type.isInstance(out)) { return type.cast(out); } // fallback to use type converter return context.getTypeConverter().convertTo(type, this, out); } public boolean hasOut() { return out != null; } public void setOut(Message out) { this.out = out; configureMessage(out); } public Exception getException() { return exception; } public <T> T getException(Class<T> type) { return ObjectHelper.getException(type, exception); } public void setException(Throwable t) { if (t == null) { this.exception = null; } else if (t instanceof Exception) { this.exception = (Exception) t; } else { // wrap throwable into an exception this.exception = ObjectHelper.wrapCamelExecutionException(this, t); } } public ExchangePattern getPattern() { return pattern; } public void setPattern(ExchangePattern pattern) { this.pattern = pattern; } public Endpoint getFromEndpoint() { return fromEndpoint; } public void setFromEndpoint(Endpoint fromEndpoint) { this.fromEndpoint = fromEndpoint; } public String getFromRouteId() { return fromRouteId; } public void setFromRouteId(String fromRouteId) { this.fromRouteId = fromRouteId; } public String getExchangeId() { if (exchangeId == null) { exchangeId = createExchangeId(); } return exchangeId; } public void setExchangeId(String id) { this.exchangeId = id; } public boolean isFailed() { if (exception != null) { return true; } return hasOut() ? getOut().isFault() : getIn().isFault(); } public boolean isTransacted() { UnitOfWork uow = getUnitOfWork(); if (uow != null) { return uow.isTransacted(); } else { return false; } } public Boolean isExternalRedelivered() { Boolean answer = null; // check property first, as the implementation details to know if the message // was externally redelivered is message specific, and thus the message implementation // could potentially change during routing, and therefore later we may not know if the // original message was externally redelivered or not, therefore we store this detail // as a exchange property to keep it around for the lifecycle of the exchange if (hasProperties()) { answer = getProperty(Exchange.EXTERNAL_REDELIVERED, null, Boolean.class); } if (answer == null) { // lets avoid adding methods to the Message API, so we use the // DefaultMessage to allow component specific messages to extend // and implement the isExternalRedelivered method. DefaultMessage msg = getIn(DefaultMessage.class); if (msg != null) { answer = msg.isTransactedRedelivered(); // store as property to keep around setProperty(Exchange.EXTERNAL_REDELIVERED, answer); } } return answer; } public boolean isRollbackOnly() { return Boolean.TRUE.equals(getProperty(Exchange.ROLLBACK_ONLY)) || Boolean.TRUE.equals(getProperty(Exchange.ROLLBACK_ONLY_LAST)); } public UnitOfWork getUnitOfWork() { return unitOfWork; } public void setUnitOfWork(UnitOfWork unitOfWork) { this.unitOfWork = unitOfWork; if (unitOfWork != null && onCompletions != null) { // now an unit of work has been assigned so add the on completions // we might have registered already for (Synchronization onCompletion : onCompletions) { unitOfWork.addSynchronization(onCompletion); } // cleanup the temporary on completion list as they now have been registered // on the unit of work onCompletions.clear(); onCompletions = null; } } public void addOnCompletion(Synchronization onCompletion) { if (unitOfWork == null) { // unit of work not yet registered so we store the on completion temporary // until the unit of work is assigned to this exchange by the unit of work if (onCompletions == null) { onCompletions = new ArrayList<Synchronization>(); } onCompletions.add(onCompletion); } else { getUnitOfWork().addSynchronization(onCompletion); } } public boolean containsOnCompletion(Synchronization onCompletion) { if (unitOfWork != null) { // if there is an unit of work then the completions is moved there return unitOfWork.containsSynchronization(onCompletion); } else { // check temporary completions if no unit of work yet return onCompletions != null && onCompletions.contains(onCompletion); } } public void handoverCompletions(Exchange target) { if (onCompletions != null) { for (Synchronization onCompletion : onCompletions) { target.addOnCompletion(onCompletion); } // cleanup the temporary on completion list as they have been handed over onCompletions.clear(); onCompletions = null; } else if (unitOfWork != null) { // let unit of work handover unitOfWork.handoverSynchronization(target); } } public List<Synchronization> handoverCompletions() { List<Synchronization> answer = null; if (onCompletions != null) { answer = new ArrayList<Synchronization>(onCompletions); onCompletions.clear(); onCompletions = null; } return answer; } /** * Configures the message after it has been set on the exchange */ protected void configureMessage(Message message) { if (message instanceof MessageSupport) { MessageSupport messageSupport = (MessageSupport)message; messageSupport.setExchange(this); } } @SuppressWarnings("deprecation") protected String createExchangeId() { String answer = null; if (in != null) { answer = in.createExchangeId(); } if (answer == null) { answer = context.getUuidGenerator().generateUuid(); } return answer; } private static boolean isExcludePatternMatch(String key, String... excludePatterns) { for (String pattern : excludePatterns) { if (EndpointHelper.matchPattern(key, pattern)) { return true; } } return false; } }