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     */
017    package org.apache.camel.processor.idempotent;
018    
019    import org.apache.camel.Exchange;
020    import org.apache.camel.Expression;
021    import org.apache.camel.Processor;
022    import org.apache.camel.impl.ServiceSupport;
023    import org.apache.camel.spi.IdempotentRepository;
024    import org.apache.camel.util.ServiceHelper;
025    import org.apache.commons.logging.Log;
026    import org.apache.commons.logging.LogFactory;
027    
028    /**
029     * An implementation of the <a
030     * href="http://camel.apache.org/idempotent-consumer.html">Idempotent
031     * Consumer</a> pattern.
032     * 
033     * @version $Revision: 752775 $
034     */
035    public class IdempotentConsumer extends ServiceSupport implements Processor {
036        private static final transient Log LOG = LogFactory.getLog(IdempotentConsumer.class);
037        private final Expression messageIdExpression;
038        private final Processor nextProcessor;
039        private final IdempotentRepository idempotentRepository;
040    
041        public IdempotentConsumer(Expression messageIdExpression, IdempotentRepository idempotentRepository,
042                                  Processor nextProcessor) {
043            this.messageIdExpression = messageIdExpression;
044            this.idempotentRepository = idempotentRepository;
045            this.nextProcessor = nextProcessor;
046        }
047    
048        @Override
049        public String toString() {
050            return "IdempotentConsumer[expression=" + messageIdExpression + ", repository=" + idempotentRepository
051                   + ", processor=" + nextProcessor + "]";
052        }
053    
054        @SuppressWarnings("unchecked")
055        public void process(Exchange exchange) throws Exception {
056            String messageId = messageIdExpression.evaluate(exchange, String.class);
057            if (messageId == null) {
058                throw new NoMessageIdException(exchange, messageIdExpression);
059            }
060    
061            if (idempotentRepository.contains(messageId)) {
062                onDuplicateMessage(exchange, messageId);
063            } else {
064                // process it first
065                nextProcessor.process(exchange);
066    
067                // then test wheter it was failed or not
068                if (!exchange.isFailed()) {
069                    onCompletedMessage(exchange, messageId);
070                } else {
071                    onFailedMessage(exchange, messageId);
072                }
073            }
074        }
075    
076        // Properties
077        // -------------------------------------------------------------------------
078        public Expression getMessageIdExpression() {
079            return messageIdExpression;
080        }
081    
082        public IdempotentRepository getIdempotentRepository() {
083            return idempotentRepository;
084        }
085    
086        public Processor getNextProcessor() {
087            return nextProcessor;
088        }
089    
090        // Implementation methods
091        // -------------------------------------------------------------------------
092    
093        protected void doStart() throws Exception {
094            ServiceHelper.startServices(nextProcessor);
095        }
096    
097        protected void doStop() throws Exception {
098            ServiceHelper.stopServices(nextProcessor);
099        }
100    
101        /**
102         * A strategy method to allow derived classes to overload the behaviour of
103         * processing a duplicate message
104         * 
105         * @param exchange the exchange
106         * @param messageId the message ID of this exchange
107         */
108        protected void onDuplicateMessage(Exchange exchange, String messageId) {
109            if (LOG.isDebugEnabled()) {
110                LOG.debug("Ignoring duplicate message with id: " + messageId + " for exchange: " + exchange);
111            }
112        }
113    
114        /**
115         * A strategy method to allow derived classes to overload the behaviour of
116         * processing a completed message
117         *
118         * @param exchange the exchange
119         * @param messageId the message ID of this exchange
120         */
121        @SuppressWarnings("unchecked")
122        protected void onCompletedMessage(Exchange exchange, String messageId) {
123            idempotentRepository.add(messageId);
124            if (LOG.isDebugEnabled()) {
125                LOG.debug("Added to repository with id: " + messageId + " for exchange: " + exchange);
126            }
127        }
128    
129        /**
130         * A strategy method to allow derived classes to overload the behaviour of
131         * processing a failed message
132         *
133         * @param exchange the exchange
134         * @param messageId the message ID of this exchange
135         */
136        protected void onFailedMessage(Exchange exchange, String messageId) {
137            if (LOG.isDebugEnabled()) {
138                LOG.debug("Not added to repository as exchange failed: " + exchange + " with id: " + messageId);
139            }
140        }
141    }