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 DataType to = contract.getInputType(); 065 if (to != null) { 066 DataTypeAware target = (DataTypeAware)exchange.getIn(); 067 DataType from = target.getDataType(); 068 if (!to.equals(from)) { 069 LOG.debug("Looking for transformer for INPUT: from='{}', to='{}'", from, to); 070 doTransform(exchange.getIn(), from, to); 071 target.setDataType(to); 072 } 073 if (contract.isValidateInput()) { 074 doValidate(exchange.getIn(), to); 075 } 076 } 077 return null; 078 } 079 080 @Override 081 public void after(Exchange exchange, Object data) throws Exception { 082 if (exchange.isFailed()) { 083 // TODO can we add FAULT_TYPE processing? 084 return; 085 } 086 087 Message target = exchange.hasOut() ? exchange.getOut() : exchange.getIn(); 088 if (!(target instanceof DataTypeAware)) { 089 return; 090 } 091 DataType to = contract.getOutputType(); 092 if (to != null) { 093 DataTypeAware typeAwareTarget = (DataTypeAware)target; 094 DataType from = typeAwareTarget.getDataType(); 095 if (!to.equals(from)) { 096 LOG.debug("Looking for transformer for OUTPUT: from='{}', to='{}'", from, to); 097 doTransform(target, from, to); 098 typeAwareTarget.setDataType(to); 099 } 100 if (contract.isValidateOutput()) { 101 doValidate(target, to); 102 } 103 } 104 } 105 106 private void doTransform(Message message, DataType from, DataType to) throws Exception { 107 if (from == null) { 108 // If 'from' is null, only Java-Java convertion is performed. 109 // It means if 'to' is other than Java, it's assumed to be already in expected type. 110 convertIfRequired(message, to); 111 return; 112 } 113 114 // transform into 'from' type before performing declared transformation 115 convertIfRequired(message, from); 116 117 if (applyMatchedTransformer(message, from, to)) { 118 // Found matched transformer. Java-Java transformer is also allowed. 119 return; 120 } else if (from.isJavaType()) { 121 // Try TypeConverter as a fallback for Java->Java transformation 122 convertIfRequired(message, to); 123 // If Java->Other transformation required but no transformer matched, 124 // then assume it's already in expected type, i.e. do nothing. 125 return; 126 } else if (applyTransformerChain(message, from, to)) { 127 // Other->Other transformation - found a transformer chain 128 return; 129 } 130 131 throw new IllegalArgumentException("No Transformer found for [from='" + from + "', to='" + to + "']"); 132 } 133 134 private boolean convertIfRequired(Message message, DataType type) throws Exception { 135 // TODO for better performance it may be better to add TypeConverterTransformer 136 // into transformer registry automatically to avoid unnecessary scan in transformer registry 137 if (type != null && type.isJavaType() && type.getName() != null) { 138 CamelContext context = message.getExchange().getContext(); 139 Class<?> typeJava = getClazz(type.getName(), context); 140 if (!typeJava.isAssignableFrom(message.getBody().getClass())) { 141 LOG.debug("Converting to '{}'", typeJava.getName()); 142 message.setBody(message.getMandatoryBody(typeJava)); 143 return true; 144 } 145 } 146 return false; 147 } 148 149 private boolean applyTransformer(Transformer transformer, Message message, DataType from, DataType to) throws Exception { 150 if (transformer != null) { 151 LOG.debug("Applying transformer: from='{}', to='{}', transformer='{}'", from, to, transformer); 152 transformer.transform(message, from, to); 153 return true; 154 } 155 return false; 156 } 157 private boolean applyMatchedTransformer(Message message, DataType from, DataType to) throws Exception { 158 Transformer transformer = message.getExchange().getContext().resolveTransformer(from, to); 159 return applyTransformer(transformer, message, from, to); 160 } 161 162 private boolean applyTransformerChain(Message message, DataType from, DataType to) throws Exception { 163 CamelContext context = message.getExchange().getContext(); 164 Transformer fromTransformer = context.resolveTransformer(from.getModel()); 165 Transformer toTransformer = context.resolveTransformer(to.getModel()); 166 if (fromTransformer != null && toTransformer != null) { 167 LOG.debug("Applying transformer 1/2: from='{}', to='{}', transformer='{}'", from, to, fromTransformer); 168 fromTransformer.transform(message, from, new DataType(Object.class)); 169 LOG.debug("Applying transformer 2/2: from='{}', to='{}', transformer='{}'", from, to, toTransformer); 170 toTransformer.transform(message, new DataType(Object.class), to); 171 return true; 172 } 173 return false; 174 } 175 176 private Class<?> getClazz(String type, CamelContext context) throws Exception { 177 return context.getClassResolver().resolveMandatoryClass(type); 178 } 179 180 private void doValidate(Message message, DataType type) throws ValidationException { 181 Validator validator = message.getExchange().getContext().resolveValidator(type); 182 if (validator != null) { 183 LOG.debug("Applying validator: type='{}', validator='{}'", type, validator); 184 validator.validate(message, type); 185 } else { 186 throw new ValidationException(message.getExchange(), String.format("No Validator found for '%s'", type)); 187 } 188 } 189}