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 093 // expectedMessageCount((int) size); 094 095 return answer; 096 } 097 098 @Override 099 public Producer createProducer() throws Exception { 100 Producer answer = super.createProducer(); 101 102 long size = getDataSet().getSize(); 103 expectedMessageCount((int) size); 104 105 return answer; 106 } 107 108 @Override 109 public void reset() { 110 super.reset(); 111 receivedCounter.set(0); 112 } 113 114 @Override 115 public int getReceivedCounter() { 116 return receivedCounter.get(); 117 } 118 119 /** 120 * Creates a message exchange for the given index in the {@link DataSet} 121 */ 122 public Exchange createExchange(long messageIndex) throws Exception { 123 Exchange exchange = createExchange(); 124 125 getDataSet().populateMessage(exchange, messageIndex); 126 127 if (!getDataSetIndex().equals("off")) { 128 Message in = exchange.getIn(); 129 in.setHeader(Exchange.DATASET_INDEX, messageIndex); 130 } 131 132 return exchange; 133 } 134 135 @Override 136 protected void waitForCompleteLatch(long timeout) throws InterruptedException { 137 super.waitForCompleteLatch(timeout); 138 139 if (minRate > 0) { 140 int count = getReceivedCounter(); 141 do { 142 // wait as long as we get a decent message rate 143 super.waitForCompleteLatch(1000L); 144 count = getReceivedCounter() - count; 145 } while (count >= minRate); 146 } 147 } 148 149 // Properties 150 //------------------------------------------------------------------------- 151 152 public DataSet getDataSet() { 153 return dataSet; 154 } 155 156 public void setDataSet(DataSet dataSet) { 157 this.dataSet = dataSet; 158 } 159 160 public int getMinRate() { 161 return minRate; 162 } 163 164 /** 165 * Wait until the DataSet contains at least this number of messages 166 */ 167 public void setMinRate(int minRate) { 168 this.minRate = minRate; 169 } 170 171 public long getPreloadSize() { 172 return preloadSize; 173 } 174 175 /** 176 * Sets how many messages should be preloaded (sent) before the route completes its initialization 177 */ 178 public void setPreloadSize(long preloadSize) { 179 this.preloadSize = preloadSize; 180 } 181 182 public long getConsumeDelay() { 183 return consumeDelay; 184 } 185 186 /** 187 * Allows a delay to be specified which causes a delay when a message is consumed by the producer (to simulate slow processing) 188 */ 189 public void setConsumeDelay(long consumeDelay) { 190 this.consumeDelay = consumeDelay; 191 } 192 193 public long getProduceDelay() { 194 return produceDelay; 195 } 196 197 /** 198 * Allows a delay to be specified which causes a delay when a message is sent by the consumer (to simulate slow processing) 199 */ 200 public void setProduceDelay(long produceDelay) { 201 this.produceDelay = produceDelay; 202 } 203 204 public long getInitialDelay() { 205 return initialDelay; 206 } 207 208 /** 209 * Time period in millis to wait before starting sending messages. 210 */ 211 public void setInitialDelay(long initialDelay) { 212 this.initialDelay = initialDelay; 213 } 214 215 /** 216 * Controls the behaviour of the CamelDataSetIndex header. 217 * For Consumers: 218 * - off => the header will not be set 219 * - strict/lenient => the header will be set 220 * For Producers: 221 * - off => the header value will not be verified, and will not be set if it is not present 222 * = strict => the header value must be present and will be verified 223 * = lenient => the header value will be verified if it is present, and will be set if it is not present 224 */ 225 public void setDataSetIndex(String dataSetIndex) { 226 switch (dataSetIndex) { 227 case "off": 228 case "lenient": 229 case "strict": 230 this.dataSetIndex = dataSetIndex; 231 break; 232 default: 233 throw new IllegalArgumentException("Invalid value specified for the dataSetIndex URI parameter:" + dataSetIndex 234 + "Supported values are strict, lenient and off "); 235 } 236 } 237 238 public String getDataSetIndex() { 239 return dataSetIndex; 240 } 241 242 // Implementation methods 243 //------------------------------------------------------------------------- 244 245 @Override 246 protected void performAssertions(Exchange actual, Exchange copy) throws Exception { 247 int receivedCount = receivedCounter.incrementAndGet(); 248 long index = receivedCount - 1; 249 Exchange expected = createExchange(index); 250 251 // now let's assert that they are the same 252 if (log.isDebugEnabled()) { 253 if (copy.getIn().getHeader(Exchange.DATASET_INDEX) != null) { 254 log.debug("Received message: {} (DataSet index={}) = {}", 255 new Object[]{index, copy.getIn().getHeader(Exchange.DATASET_INDEX, Integer.class), copy}); 256 } else { 257 log.debug("Received message: {} = {}", 258 new Object[]{index, copy}); 259 } 260 } 261 262 assertMessageExpected(index, expected, copy); 263 264 if (consumeDelay > 0) { 265 Thread.sleep(consumeDelay); 266 } 267 } 268 269 protected void assertMessageExpected(long index, Exchange expected, Exchange actual) throws Exception { 270 switch (getDataSetIndex()) { 271 case "off": 272 break; 273 case "strict": 274 long actualCounter = ExchangeHelper.getMandatoryHeader(actual, Exchange.DATASET_INDEX, Long.class); 275 assertEquals("Header: " + Exchange.DATASET_INDEX, index, actualCounter, actual); 276 break; 277 case "lenient": 278 default: 279 // Validate the header value if it is present 280 Long dataSetIndexHeaderValue = actual.getIn().getHeader(Exchange.DATASET_INDEX, Long.class); 281 if (dataSetIndexHeaderValue != null) { 282 assertEquals("Header: " + Exchange.DATASET_INDEX, index, dataSetIndexHeaderValue, actual); 283 } else { 284 // set the header if it isn't there 285 actual.getIn().setHeader(Exchange.DATASET_INDEX, index); 286 } 287 break; 288 } 289 290 getDataSet().assertMessageExpected(this, expected, actual, index); 291 } 292 293 protected ThroughputLogger createReporter() { 294 // must sanitize uri to avoid logging sensitive information 295 String uri = URISupport.sanitizeUri(getEndpointUri()); 296 CamelLogger logger = new CamelLogger(uri); 297 ThroughputLogger answer = new ThroughputLogger(logger, (int) this.getDataSet().getReportCount()); 298 answer.setAction("Received"); 299 return answer; 300 } 301 302 @Override 303 protected void doStart() throws Exception { 304 super.doStart(); 305 306 if (reporter == null) { 307 reporter = createReporter(); 308 } 309 310 log.info(this + " expecting " + getExpectedCount() + " messages"); 311 } 312 313}