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.model; 018 019import javax.xml.bind.annotation.XmlAccessType; 020import javax.xml.bind.annotation.XmlAccessorType; 021import javax.xml.bind.annotation.XmlAttribute; 022import javax.xml.bind.annotation.XmlRootElement; 023import javax.xml.bind.annotation.XmlTransient; 024 025import org.apache.camel.Expression; 026import org.apache.camel.Processor; 027import org.apache.camel.model.language.ExpressionDefinition; 028import org.apache.camel.processor.idempotent.IdempotentConsumer; 029import org.apache.camel.spi.IdempotentRepository; 030import org.apache.camel.spi.Metadata; 031import org.apache.camel.spi.RouteContext; 032import org.apache.camel.util.ObjectHelper; 033 034/** 035 * Filters out duplicate messages 036 */ 037@Metadata(label = "eip,routing") 038@XmlRootElement(name = "idempotentConsumer") 039@XmlAccessorType(XmlAccessType.FIELD) 040public class IdempotentConsumerDefinition extends ExpressionNode { 041 @XmlAttribute(required = true) 042 private String messageIdRepositoryRef; 043 @XmlAttribute @Metadata(defaultValue = "true") 044 private Boolean eager; 045 @XmlAttribute @Metadata(defaultValue = "true") 046 private Boolean skipDuplicate; 047 @XmlAttribute @Metadata(defaultValue = "true") 048 private Boolean removeOnFailure; 049 @XmlTransient 050 private IdempotentRepository<?> idempotentRepository; 051 052 public IdempotentConsumerDefinition() { 053 } 054 055 public IdempotentConsumerDefinition(Expression messageIdExpression, IdempotentRepository<?> idempotentRepository) { 056 super(messageIdExpression); 057 this.idempotentRepository = idempotentRepository; 058 } 059 060 @Override 061 public String toString() { 062 return "IdempotentConsumer[" + getExpression() + " -> " + getOutputs() + "]"; 063 } 064 065 @Override 066 public String getLabel() { 067 return "idempotentConsumer[" + getExpression() + "]"; 068 } 069 070 // Fluent API 071 //------------------------------------------------------------------------- 072 073 /** 074 * Sets the reference name of the message id repository 075 * 076 * @param messageIdRepositoryRef the reference name of message id repository 077 * @return builder 078 */ 079 public IdempotentConsumerDefinition messageIdRepositoryRef(String messageIdRepositoryRef) { 080 setMessageIdRepositoryRef(messageIdRepositoryRef); 081 return this; 082 } 083 084 /** 085 * Sets the the message id repository for the idempotent consumer 086 * 087 * @param idempotentRepository the repository instance of idempotent 088 * @return builder 089 */ 090 public IdempotentConsumerDefinition messageIdRepository(IdempotentRepository<?> idempotentRepository) { 091 setMessageIdRepository(idempotentRepository); 092 return this; 093 } 094 095 /** 096 * Sets whether to eagerly add the key to the idempotent repository or wait until the exchange 097 * is complete. Eager is default enabled. 098 * 099 * @param eager <tt>true</tt> to add the key before processing, <tt>false</tt> to wait until 100 * the exchange is complete. 101 * @return builder 102 */ 103 public IdempotentConsumerDefinition eager(boolean eager) { 104 setEager(eager); 105 return this; 106 } 107 108 /** 109 * Sets whether to remove or keep the key on failure. 110 * <p/> 111 * The default behavior is to remove the key on failure. 112 * 113 * @param removeOnFailure <tt>true</tt> to remove the key, <tt>false</tt> to keep the key 114 * if the exchange fails. 115 * @return builder 116 */ 117 public IdempotentConsumerDefinition removeOnFailure(boolean removeOnFailure) { 118 setRemoveOnFailure(removeOnFailure); 119 return this; 120 } 121 122 /** 123 * Sets whether to skip duplicates or not. 124 * <p/> 125 * The default behavior is to skip duplicates. 126 * <p/> 127 * A duplicate message would have the Exchange property {@link org.apache.camel.Exchange#DUPLICATE_MESSAGE} set 128 * to a {@link Boolean#TRUE} value. A none duplicate message will not have this property set. 129 * 130 * @param skipDuplicate <tt>true</tt> to skip duplicates, <tt>false</tt> to allow duplicates. 131 * @return builder 132 */ 133 public IdempotentConsumerDefinition skipDuplicate(boolean skipDuplicate) { 134 setSkipDuplicate(skipDuplicate); 135 return this; 136 } 137 138 /** 139 * Expression used to calculate the correlation key to use for duplicate check. 140 * The Exchange which has the same correlation key is regarded as a duplicate and will be rejected. 141 */ 142 @Override 143 public void setExpression(ExpressionDefinition expression) { 144 // override to include javadoc what the expression is used for 145 super.setExpression(expression); 146 } 147 148 public String getMessageIdRepositoryRef() { 149 return messageIdRepositoryRef; 150 } 151 152 public void setMessageIdRepositoryRef(String messageIdRepositoryRef) { 153 this.messageIdRepositoryRef = messageIdRepositoryRef; 154 } 155 156 public IdempotentRepository<?> getMessageIdRepository() { 157 return idempotentRepository; 158 } 159 160 public void setMessageIdRepository(IdempotentRepository<?> idempotentRepository) { 161 this.idempotentRepository = idempotentRepository; 162 } 163 164 public Boolean getEager() { 165 return eager; 166 } 167 168 public void setEager(Boolean eager) { 169 this.eager = eager; 170 } 171 172 public Boolean getSkipDuplicate() { 173 return skipDuplicate; 174 } 175 176 public void setSkipDuplicate(Boolean skipDuplicate) { 177 this.skipDuplicate = skipDuplicate; 178 } 179 180 public Boolean getRemoveOnFailure() { 181 return removeOnFailure; 182 } 183 184 public void setRemoveOnFailure(Boolean removeOnFailure) { 185 this.removeOnFailure = removeOnFailure; 186 } 187 188 @Override 189 @SuppressWarnings("unchecked") 190 public Processor createProcessor(RouteContext routeContext) throws Exception { 191 Processor childProcessor = this.createChildProcessor(routeContext, true); 192 193 IdempotentRepository<String> idempotentRepository = 194 (IdempotentRepository<String>) resolveMessageIdRepository(routeContext); 195 ObjectHelper.notNull(idempotentRepository, "idempotentRepository", this); 196 197 // add as service to CamelContext so we can managed it and it ensures it will be shutdown when camel shutdowns 198 routeContext.getCamelContext().addService(idempotentRepository); 199 200 Expression expression = getExpression().createExpression(routeContext); 201 202 // these boolean should be true by default 203 boolean eager = getEager() == null || getEager(); 204 boolean duplicate = getSkipDuplicate() == null || getSkipDuplicate(); 205 boolean remove = getRemoveOnFailure() == null || getRemoveOnFailure(); 206 207 return new IdempotentConsumer(expression, idempotentRepository, eager, duplicate, remove, childProcessor); 208 } 209 210 /** 211 * Strategy method to resolve the {@link org.apache.camel.spi.IdempotentRepository} to use 212 * 213 * @param routeContext route context 214 * @return the repository 215 */ 216 protected IdempotentRepository<?> resolveMessageIdRepository(RouteContext routeContext) { 217 if (messageIdRepositoryRef != null) { 218 idempotentRepository = routeContext.mandatoryLookup(messageIdRepositoryRef, IdempotentRepository.class); 219 } 220 return idempotentRepository; 221 } 222}