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}