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