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 java.util.ArrayList;
020    import java.util.List;
021    import java.util.concurrent.atomic.AtomicLong;
022    
023    import org.apache.camel.AsyncCallback;
024    import org.apache.camel.AsyncProcessor;
025    import org.apache.camel.Exchange;
026    import org.apache.camel.Expression;
027    import org.apache.camel.Navigate;
028    import org.apache.camel.Processor;
029    import org.apache.camel.spi.IdempotentRepository;
030    import org.apache.camel.support.ServiceSupport;
031    import org.apache.camel.util.AsyncProcessorConverterHelper;
032    import org.apache.camel.util.AsyncProcessorHelper;
033    import org.apache.camel.util.ServiceHelper;
034    import org.slf4j.Logger;
035    import org.slf4j.LoggerFactory;
036    
037    /**
038     * An implementation of the <a
039     * href="http://camel.apache.org/idempotent-consumer.html">Idempotent Consumer</a> pattern.
040     */
041    public class IdempotentConsumer extends ServiceSupport implements AsyncProcessor, Navigate<Processor> {
042        private static final transient Logger LOG = LoggerFactory.getLogger(IdempotentConsumer.class);
043        private final Expression messageIdExpression;
044        private final AsyncProcessor processor;
045        private final IdempotentRepository<String> idempotentRepository;
046        private final boolean eager;
047        private final boolean skipDuplicate;
048        private final boolean removeOnFailure;
049        private final AtomicLong duplicateMessageCount = new AtomicLong();
050    
051        public IdempotentConsumer(Expression messageIdExpression, IdempotentRepository<String> idempotentRepository,
052                                  boolean eager, boolean skipDuplicate, boolean removeOnFailure, Processor processor) {
053            this.messageIdExpression = messageIdExpression;
054            this.idempotentRepository = idempotentRepository;
055            this.eager = eager;
056            this.skipDuplicate = skipDuplicate;
057            this.removeOnFailure = removeOnFailure;
058            this.processor = AsyncProcessorConverterHelper.convert(processor);
059        }
060    
061        @Override
062        public String toString() {
063            return "IdempotentConsumer[" + messageIdExpression + " -> " + processor + "]";
064        }
065    
066        public void process(Exchange exchange) throws Exception {
067            AsyncProcessorHelper.process(this, exchange);
068        }
069    
070        public boolean process(Exchange exchange, AsyncCallback callback) {
071            final String messageId = messageIdExpression.evaluate(exchange, String.class);
072            if (messageId == null) {
073                throw new NoMessageIdException(exchange, messageIdExpression);
074            }
075    
076            boolean newKey;
077            if (eager) {
078                // add the key to the repository
079                newKey = idempotentRepository.add(messageId);
080            } else {
081                // check if we already have the key
082                newKey = !idempotentRepository.contains(messageId);
083            }
084    
085    
086            if (!newKey) {
087                // mark the exchange as duplicate
088                exchange.setProperty(Exchange.DUPLICATE_MESSAGE, Boolean.TRUE);
089    
090                // we already have this key so its a duplicate message
091                onDuplicate(exchange, messageId);
092    
093                if (skipDuplicate) {
094                    // if we should skip duplicate then we are done
095                    LOG.debug("Ignoring duplicate message with id: {} for exchange: {}", messageId, exchange);
096                    callback.done(true);
097                    return true;
098                }
099            }
100    
101            // register our on completion callback
102            exchange.addOnCompletion(new IdempotentOnCompletion(idempotentRepository, messageId, eager, removeOnFailure));
103    
104            // process the exchange
105            return processor.process(exchange, callback);
106        }
107    
108        public List<Processor> next() {
109            if (!hasNext()) {
110                return null;
111            }
112            List<Processor> answer = new ArrayList<Processor>(1);
113            answer.add(processor);
114            return answer;
115        }
116    
117        public boolean hasNext() {
118            return processor != null;
119        }
120    
121        // Properties
122        // -------------------------------------------------------------------------
123        public Expression getMessageIdExpression() {
124            return messageIdExpression;
125        }
126    
127        public IdempotentRepository<String> getIdempotentRepository() {
128            return idempotentRepository;
129        }
130    
131        public Processor getProcessor() {
132            return processor;
133        }
134    
135        public long getDuplicateMessageCount() {
136            return duplicateMessageCount.get();
137        }
138    
139        // Implementation methods
140        // -------------------------------------------------------------------------
141    
142        protected void doStart() throws Exception {
143            ServiceHelper.startServices(processor);
144        }
145    
146        protected void doStop() throws Exception {
147            ServiceHelper.stopServices(processor);
148        }
149    
150        /**
151         * Resets the duplicate message counter to <code>0L</code>.
152         */
153        public void resetDuplicateMessageCount() {
154            duplicateMessageCount.set(0L);
155        }
156    
157        private void onDuplicate(Exchange exchange, String messageId) {
158            duplicateMessageCount.incrementAndGet();
159    
160            onDuplicateMessage(exchange, messageId);
161        }
162    
163        /**
164         * A strategy method to allow derived classes to overload the behaviour of
165         * processing a duplicate message
166         *
167         * @param exchange  the exchange
168         * @param messageId the message ID of this exchange
169         */
170        protected void onDuplicateMessage(Exchange exchange, String messageId) {
171            // noop
172        }
173    
174    }