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 }