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.processor; 018 019import org.apache.camel.CamelContext; 020import org.apache.camel.Exchange; 021import org.apache.camel.Message; 022import org.apache.camel.ValidationException; 023import org.apache.camel.spi.Contract; 024import org.apache.camel.spi.DataType; 025import org.apache.camel.spi.DataTypeAware; 026import org.apache.camel.spi.Transformer; 027import org.apache.camel.spi.Validator; 028import org.slf4j.Logger; 029import org.slf4j.LoggerFactory; 030 031/** 032 * A {@link CamelInternalProcessorAdvice} which applies {@link Transformer} and {@link Validator} 033 * according to the data type Contract. 034 * The default camel {@link Message} implements {@link DataTypeAware} which 035 * holds a {@link DataType} to indicate current message type. If the input type 036 * declared by {@link InputTypeDefinition} is diffrent from current IN message type, 037 * camel internal processor look for a Transformer which transforms from the current 038 * message type to the expected message type before routing. 039 * After routing, if the output type declared by {@link OutputTypeDefinition} is different 040 * from current OUT message (or IN message if no OUT), camel look for a Transformer and apply. 041 * 042 * @see {@link Transformer} {@link Validator} 043 * {@link InputTypeDefinition} {@link OutputTypeDefinition} 044 */ 045public class ContractAdvice implements CamelInternalProcessorAdvice { 046 private static final Logger LOG = LoggerFactory.getLogger(CamelInternalProcessor.class); 047 048 private Contract contract; 049 050 public ContractAdvice(Contract contract) { 051 this.contract = contract; 052 } 053 054 @Override 055 public Object before(Exchange exchange) throws Exception { 056 if (!(exchange.getIn() instanceof DataTypeAware)) { 057 return null; 058 } 059 DataTypeAware target = (DataTypeAware)exchange.getIn(); 060 DataType from = target.getDataType(); 061 DataType to = contract.getInputType(); 062 if (to != null) { 063 if (!to.equals(from)) { 064 LOG.debug("Looking for transformer for INPUT: from='{}', to='{}'", from, to); 065 doTransform(exchange.getIn(), from, to); 066 target.setDataType(to); 067 } 068 if (contract.isValidateInput()) { 069 doValidate(exchange.getIn(), to); 070 } 071 } 072 return null; 073 } 074 075 @Override 076 public void after(Exchange exchange, Object data) throws Exception { 077 if (exchange.isFailed()) { 078 // TODO can we add FAULT_TYPE processing? 079 return; 080 } 081 082 Message target = exchange.hasOut() ? exchange.getOut() : exchange.getIn(); 083 if (!(target instanceof DataTypeAware)) { 084 return; 085 } 086 DataTypeAware typeAwareTarget = (DataTypeAware)target; 087 DataType from = typeAwareTarget.getDataType(); 088 DataType to = contract.getOutputType(); 089 if (to != null) { 090 if (!to.equals(from)) { 091 LOG.debug("Looking for transformer for OUTPUT: from='{}', to='{}'", from, to); 092 doTransform(target, from, to); 093 typeAwareTarget.setDataType(to); 094 } 095 if (contract.isValidateOutput()) { 096 doValidate(target, to); 097 } 098 } 099 } 100 101 private void doTransform(Message message, DataType from, DataType to) throws Exception { 102 if (from == null) { 103 // If 'from' is null, only Java-Java convertion is performed. 104 // It means if 'to' is other than Java, it's assumed to be already in expected type. 105 convertIfRequired(message, to); 106 return; 107 } 108 109 // transform into 'from' type before performing declared transformation 110 convertIfRequired(message, from); 111 112 if (applyMatchedTransformer(message, from, to)) { 113 // Found matched transformer. Java-Java transformer is also allowed. 114 return; 115 } else if (from.isJavaType()) { 116 // Try TypeConverter as a fallback for Java->Java transformation 117 convertIfRequired(message, to); 118 // If Java->Other transformation required but no transformer matched, 119 // then assume it's already in expected type, i.e. do nothing. 120 return; 121 } else if (applyTransformerChain(message, from, to)) { 122 // Other->Other transformation - found a transformer chain 123 return; 124 } 125 126 throw new IllegalArgumentException("No Transformer found for [from='" + from + "', to='" + to + "']"); 127 } 128 129 private boolean convertIfRequired(Message message, DataType type) throws Exception { 130 // TODO for better performance it may be better to add TypeConveterTransformer 131 // into transformer registry automatically to avoid unnecessary scan in transformer registry 132 if (type != null && type.isJavaType() && type.getName() != null) { 133 CamelContext context = message.getExchange().getContext(); 134 Class<?> typeJava = getClazz(type.getName(), context); 135 if (!typeJava.isAssignableFrom(message.getBody().getClass())) { 136 LOG.debug("Converting to '{}'", typeJava.getName()); 137 message.setBody(message.getMandatoryBody(typeJava)); 138 return true; 139 } 140 } 141 return false; 142 } 143 144 private boolean applyTransformer(Transformer transformer, Message message, DataType from, DataType to) throws Exception { 145 if (transformer != null) { 146 LOG.debug("Applying transformer: from='{}', to='{}', transformer='{}'", from, to, transformer); 147 transformer.transform(message, from, to); 148 return true; 149 } 150 return false; 151 } 152 private boolean applyMatchedTransformer(Message message, DataType from, DataType to) throws Exception { 153 Transformer transformer = message.getExchange().getContext().resolveTransformer(from, to); 154 return applyTransformer(transformer, message, from, to); 155 } 156 157 private boolean applyTransformerChain(Message message, DataType from, DataType to) throws Exception { 158 CamelContext context = message.getExchange().getContext(); 159 Transformer fromTransformer = context.resolveTransformer(from.getModel()); 160 Transformer toTransformer = context.resolveTransformer(to.getModel()); 161 if (fromTransformer != null && toTransformer != null) { 162 LOG.debug("Applying transformer 1/2: from='{}', to='{}', transformer='{}'", from, to, fromTransformer); 163 fromTransformer.transform(message, from, new DataType(Object.class)); 164 LOG.debug("Applying transformer 2/2: from='{}', to='{}', transformer='{}'", from, to, toTransformer); 165 toTransformer.transform(message, new DataType(Object.class), to); 166 return true; 167 } 168 return false; 169 } 170 171 private Class<?> getClazz(String type, CamelContext context) throws Exception { 172 return context.getClassResolver().resolveMandatoryClass(type); 173 } 174 175 private void doValidate(Message message, DataType type) throws ValidationException { 176 Validator validator = message.getExchange().getContext().resolveValidator(type); 177 if (validator != null) { 178 LOG.debug("Applying validator: type='{}', validator='{}'", type, validator); 179 validator.validate(message, type); 180 } else { 181 throw new ValidationException(message.getExchange(), String.format("No Validator found for '%s'", type)); 182 } 183 } 184}