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.component.dataset; 018 019import java.util.concurrent.atomic.AtomicInteger; 020 021import org.apache.camel.Component; 022import org.apache.camel.Consumer; 023import org.apache.camel.Exchange; 024import org.apache.camel.Message; 025import org.apache.camel.Processor; 026import org.apache.camel.Producer; 027import org.apache.camel.Service; 028import org.apache.camel.component.mock.MockEndpoint; 029import org.apache.camel.processor.ThroughputLogger; 030import org.apache.camel.spi.Metadata; 031import org.apache.camel.spi.UriEndpoint; 032import org.apache.camel.spi.UriParam; 033import org.apache.camel.spi.UriPath; 034import org.apache.camel.util.CamelLogger; 035import org.apache.camel.util.ExchangeHelper; 036import org.apache.camel.util.ObjectHelper; 037import org.apache.camel.util.URISupport; 038import org.slf4j.Logger; 039import org.slf4j.LoggerFactory; 040 041/** 042 * The dataset component provides a mechanism to easily perform load & soak testing of your system. 043 * 044 * It works by allowing you to create DataSet instances both as a source of messages and as a way to assert that the data set is received. 045 * Camel will use the throughput logger when sending dataset's. 046 */ 047@UriEndpoint(firstVersion = "1.3.0", scheme = "dataset", title = "Dataset", syntax = "dataset:name", 048 consumerClass = DataSetConsumer.class, label = "core,testing", lenientProperties = true) 049public class DataSetEndpoint extends MockEndpoint implements Service { 050 private final transient Logger log; 051 private final AtomicInteger receivedCounter = new AtomicInteger(); 052 @UriPath(name = "name", description = "Name of DataSet to lookup in the registry") @Metadata(required = "true") 053 private volatile DataSet dataSet; 054 @UriParam(label = "consumer", defaultValue = "0") 055 private int minRate; 056 @UriParam(label = "consumer", defaultValue = "3") 057 private long produceDelay = 3; 058 @UriParam(label = "producer", defaultValue = "0") 059 private long consumeDelay; 060 @UriParam(label = "consumer", defaultValue = "0") 061 private long preloadSize; 062 @UriParam(label = "consumer", defaultValue = "1000") 063 private long initialDelay = 1000; 064 @UriParam(enums = "strict,lenient,off", defaultValue = "lenient") 065 private String dataSetIndex = "lenient"; 066 067 @Deprecated 068 public DataSetEndpoint() { 069 this.log = LoggerFactory.getLogger(DataSetEndpoint.class); 070 // optimize as we dont need to copy the exchange 071 setCopyOnExchange(false); 072 } 073 074 public DataSetEndpoint(String endpointUri, Component component, DataSet dataSet) { 075 super(endpointUri, component); 076 this.dataSet = dataSet; 077 this.log = LoggerFactory.getLogger(endpointUri); 078 // optimize as we dont need to copy the exchange 079 setCopyOnExchange(false); 080 } 081 082 public static void assertEquals(String description, Object expected, Object actual, Exchange exchange) { 083 if (!ObjectHelper.equal(expected, actual)) { 084 throw new AssertionError(description + " does not match. Expected: " + expected + " but was: " + actual + " on " + exchange + " with headers: " + exchange.getIn().getHeaders()); 085 } 086 } 087 088 @Override 089 public Consumer createConsumer(Processor processor) throws Exception { 090 Consumer answer = new DataSetConsumer(this, processor); 091 configureConsumer(answer); 092 return answer; 093 } 094 095 @Override 096 public Producer createProducer() throws Exception { 097 Producer answer = super.createProducer(); 098 099 long size = getDataSet().getSize(); 100 expectedMessageCount((int) size); 101 102 return answer; 103 } 104 105 @Override 106 public void reset() { 107 super.reset(); 108 receivedCounter.set(0); 109 } 110 111 @Override 112 public int getReceivedCounter() { 113 return receivedCounter.get(); 114 } 115 116 /** 117 * Creates a message exchange for the given index in the {@link DataSet} 118 */ 119 public Exchange createExchange(long messageIndex) throws Exception { 120 Exchange exchange = createExchange(); 121 122 getDataSet().populateMessage(exchange, messageIndex); 123 124 if (!getDataSetIndex().equals("off")) { 125 Message in = exchange.getIn(); 126 in.setHeader(Exchange.DATASET_INDEX, messageIndex); 127 } 128 129 return exchange; 130 } 131 132 @Override 133 protected void waitForCompleteLatch(long timeout) throws InterruptedException { 134 super.waitForCompleteLatch(timeout); 135 136 if (minRate > 0) { 137 int count = getReceivedCounter(); 138 do { 139 // wait as long as we get a decent message rate 140 super.waitForCompleteLatch(1000L); 141 count = getReceivedCounter() - count; 142 } while (count >= minRate); 143 } 144 } 145 146 // Properties 147 //------------------------------------------------------------------------- 148 149 public DataSet getDataSet() { 150 return dataSet; 151 } 152 153 public void setDataSet(DataSet dataSet) { 154 this.dataSet = dataSet; 155 } 156 157 public int getMinRate() { 158 return minRate; 159 } 160 161 /** 162 * Wait until the DataSet contains at least this number of messages 163 */ 164 public void setMinRate(int minRate) { 165 this.minRate = minRate; 166 } 167 168 public long getPreloadSize() { 169 return preloadSize; 170 } 171 172 /** 173 * Sets how many messages should be preloaded (sent) before the route completes its initialization 174 */ 175 public void setPreloadSize(long preloadSize) { 176 this.preloadSize = preloadSize; 177 } 178 179 public long getConsumeDelay() { 180 return consumeDelay; 181 } 182 183 /** 184 * Allows a delay to be specified which causes a delay when a message is consumed by the producer (to simulate slow processing) 185 */ 186 public void setConsumeDelay(long consumeDelay) { 187 this.consumeDelay = consumeDelay; 188 } 189 190 public long getProduceDelay() { 191 return produceDelay; 192 } 193 194 /** 195 * Allows a delay to be specified which causes a delay when a message is sent by the consumer (to simulate slow processing) 196 */ 197 public void setProduceDelay(long produceDelay) { 198 this.produceDelay = produceDelay; 199 } 200 201 public long getInitialDelay() { 202 return initialDelay; 203 } 204 205 /** 206 * Time period in millis to wait before starting sending messages. 207 */ 208 public void setInitialDelay(long initialDelay) { 209 this.initialDelay = initialDelay; 210 } 211 212 /** 213 * Controls the behaviour of the CamelDataSetIndex header. 214 * For Consumers: 215 * - off => the header will not be set 216 * - strict/lenient => the header will be set 217 * For Producers: 218 * - off => the header value will not be verified, and will not be set if it is not present 219 * = strict => the header value must be present and will be verified 220 * = lenient => the header value will be verified if it is present, and will be set if it is not present 221 */ 222 public void setDataSetIndex(String dataSetIndex) { 223 switch (dataSetIndex) { 224 case "off": 225 case "lenient": 226 case "strict": 227 this.dataSetIndex = dataSetIndex; 228 break; 229 default: 230 throw new IllegalArgumentException("Invalid value specified for the dataSetIndex URI parameter:" + dataSetIndex 231 + "Supported values are strict, lenient and off "); 232 } 233 } 234 235 public String getDataSetIndex() { 236 return dataSetIndex; 237 } 238 239 // Implementation methods 240 //------------------------------------------------------------------------- 241 242 @Override 243 protected void performAssertions(Exchange actual, Exchange copy) throws Exception { 244 int receivedCount = receivedCounter.incrementAndGet(); 245 long index = receivedCount - 1; 246 Exchange expected = createExchange(index); 247 248 // now let's assert that they are the same 249 if (log.isDebugEnabled()) { 250 if (copy.getIn().getHeader(Exchange.DATASET_INDEX) != null) { 251 log.debug("Received message: {} (DataSet index={}) = {}", 252 new Object[]{index, copy.getIn().getHeader(Exchange.DATASET_INDEX, Integer.class), copy}); 253 } else { 254 log.debug("Received message: {} = {}", 255 new Object[]{index, copy}); 256 } 257 } 258 259 assertMessageExpected(index, expected, copy); 260 261 if (consumeDelay > 0) { 262 Thread.sleep(consumeDelay); 263 } 264 } 265 266 protected void assertMessageExpected(long index, Exchange expected, Exchange actual) throws Exception { 267 switch (getDataSetIndex()) { 268 case "off": 269 break; 270 case "strict": 271 long actualCounter = ExchangeHelper.getMandatoryHeader(actual, Exchange.DATASET_INDEX, Long.class); 272 assertEquals("Header: " + Exchange.DATASET_INDEX, index, actualCounter, actual); 273 break; 274 case "lenient": 275 default: 276 // Validate the header value if it is present 277 Long dataSetIndexHeaderValue = actual.getIn().getHeader(Exchange.DATASET_INDEX, Long.class); 278 if (dataSetIndexHeaderValue != null) { 279 assertEquals("Header: " + Exchange.DATASET_INDEX, index, dataSetIndexHeaderValue, actual); 280 } else { 281 // set the header if it isn't there 282 actual.getIn().setHeader(Exchange.DATASET_INDEX, index); 283 } 284 break; 285 } 286 287 getDataSet().assertMessageExpected(this, expected, actual, index); 288 } 289 290 protected ThroughputLogger createReporter() { 291 // must sanitize uri to avoid logging sensitive information 292 String uri = URISupport.sanitizeUri(getEndpointUri()); 293 CamelLogger logger = new CamelLogger(uri); 294 ThroughputLogger answer = new ThroughputLogger(logger, (int) this.getDataSet().getReportCount()); 295 answer.setAction("Received"); 296 return answer; 297 } 298 299 @Override 300 protected void doStart() throws Exception { 301 super.doStart(); 302 303 if (reporter == null) { 304 reporter = createReporter(); 305 } 306 307 log.info(this + " expecting " + getExpectedCount() + " messages"); 308 } 309 310}