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 }