org.apache.camel.builder.NotifyBuilder Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of camel-core Show documentation
Show all versions of camel-core Show documentation
The Core Camel Java DSL based router
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.camel.builder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EventObject;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.camel.CamelContext;
import org.apache.camel.Endpoint;
import org.apache.camel.Exchange;
import org.apache.camel.Expression;
import org.apache.camel.Predicate;
import org.apache.camel.Producer;
import org.apache.camel.component.direct.DirectEndpoint;
import org.apache.camel.component.mock.MockEndpoint;
import org.apache.camel.management.event.ExchangeCompletedEvent;
import org.apache.camel.management.event.ExchangeCreatedEvent;
import org.apache.camel.management.event.ExchangeFailedEvent;
import org.apache.camel.management.event.ExchangeSentEvent;
import org.apache.camel.support.EventNotifierSupport;
import org.apache.camel.util.EndpointHelper;
import org.apache.camel.util.ObjectHelper;
import org.apache.camel.util.ServiceHelper;
import org.apache.camel.util.StringHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A builder to build an expression based on {@link org.apache.camel.spi.EventNotifier} notifications
* about {@link Exchange} being routed.
*
* This builder can be used for testing purposes where you want to know when a test is supposed to be done.
* The idea is that you can build an expression that explains when the test is done. For example when Camel
* have finished routing 5 messages. You can then in your test await for this condition to occur.
*
* @version
*/
public class NotifyBuilder {
private static final Logger LOG = LoggerFactory.getLogger(NotifyBuilder.class);
private final CamelContext context;
// notifier to hook into Camel to listen for events
private final EventNotifierSupport eventNotifier;
// the predicates build with this builder
private final List predicates = new ArrayList<>();
// latch to be used to signal predicates matches
private CountDownLatch latch = new CountDownLatch(1);
// the current state while building an event predicate where we use a stack and the operation
private final List stack = new ArrayList<>();
private EventOperation operation;
private boolean created;
// keep state of how many wereSentTo we have added
private int wereSentToIndex;
// computed value whether all the predicates matched
private volatile boolean matches;
/**
* Creates a new builder.
*
* @param context the Camel context
*/
public NotifyBuilder(CamelContext context) {
this.context = context;
eventNotifier = new ExchangeNotifier();
try {
ServiceHelper.startService(eventNotifier);
} catch (Exception e) {
throw ObjectHelper.wrapRuntimeCamelException(e);
}
context.getManagementStrategy().addEventNotifier(eventNotifier);
}
/**
* Optionally a from endpoint which means that this expression should only be based
* on {@link Exchange} which is originated from the particular endpoint(s).
*
* @param endpointUri uri of endpoint or pattern (see the EndpointHelper javadoc)
* @return the builder
* @see org.apache.camel.util.EndpointHelper#matchEndpoint(org.apache.camel.CamelContext, String, String)
*/
public NotifyBuilder from(final String endpointUri) {
stack.add(new EventPredicateSupport() {
@Override
public boolean isAbstract() {
// is abstract as its a filter
return true;
}
@Override
public boolean onExchange(Exchange exchange) {
// filter non matching exchanges
return EndpointHelper.matchEndpoint(context, exchange.getFromEndpoint().getEndpointUri(), endpointUri);
}
public boolean matches() {
// should be true as we use the onExchange to filter
return true;
}
@Override
public String toString() {
return "from(" + endpointUri + ")";
}
});
return this;
}
/**
* Optionally a from route which means that this expression should only be based
* on {@link Exchange} which is originated from the particular route(s).
*
* @param routeId id of route or pattern (see the EndpointHelper javadoc)
* @return the builder
* @see org.apache.camel.util.EndpointHelper#matchEndpoint(org.apache.camel.CamelContext, String, String)
*/
public NotifyBuilder fromRoute(final String routeId) {
stack.add(new EventPredicateSupport() {
@Override
public boolean isAbstract() {
// is abstract as its a filter
return true;
}
@Override
public boolean onExchange(Exchange exchange) {
String id = EndpointHelper.getRouteIdFromEndpoint(exchange.getFromEndpoint());
if (id == null) {
id = exchange.getFromRouteId();
}
// filter non matching exchanges
return EndpointHelper.matchPattern(id, routeId);
}
public boolean matches() {
// should be true as we use the onExchange to filter
return true;
}
@Override
public String toString() {
return "fromRoute(" + routeId + ")";
}
});
return this;
}
private NotifyBuilder fromRoutesOnly() {
// internal and should always be in top of stack
stack.add(0, new EventPredicateSupport() {
@Override
public boolean isAbstract() {
// is abstract as its a filter
return true;
}
@Override
public boolean onExchange(Exchange exchange) {
// always accept direct endpoints as they are a special case as it will create the UoW beforehand
// and just continue to route that on the consumer side, which causes the EventNotifier not to
// emit events when the consumer received the exchange, as its already done. For example by
// ProducerTemplate which creates the UoW before producing messages.
if (exchange.getFromEndpoint() instanceof DirectEndpoint) {
return true;
}
return EndpointHelper.matchPattern(exchange.getFromRouteId(), "*");
}
public boolean matches() {
// should be true as we use the onExchange to filter
return true;
}
@Override
public String toString() {
// we dont want any to string output as this is an internal predicate to match only from routes
return "";
}
});
return this;
}
/**
* Optionally a filter to only allow matching {@link Exchange} to be used for matching.
*
* @param predicate the predicate to use for the filter
* @return the builder
*/
public NotifyBuilder filter(final Predicate predicate) {
stack.add(new EventPredicateSupport() {
@Override
public boolean isAbstract() {
// is abstract as its a filter
return true;
}
@Override
public boolean onExchange(Exchange exchange) {
// filter non matching exchanges
return predicate.matches(exchange);
}
public boolean matches() {
// should be true as we use the onExchange to filter
return true;
}
@Override
public String toString() {
return "filter(" + predicate + ")";
}
});
return this;
}
/**
* Optionally a filter to only allow matching {@link Exchange} to be used for matching.
*
* @return the builder
*/
public ExpressionClauseSupport filter() {
final ExpressionClauseSupport clause = new ExpressionClauseSupport<>(this);
stack.add(new EventPredicateSupport() {
@Override
public boolean isAbstract() {
// is abstract as its a filter
return true;
}
@Override
public boolean onExchange(Exchange exchange) {
// filter non matching exchanges
Expression exp = clause.createExpression(exchange.getContext());
return exp.evaluate(exchange, Boolean.class);
}
public boolean matches() {
// should be true as we use the onExchange to filter
return true;
}
@Override
public String toString() {
return "filter(" + clause + ")";
}
});
return clause;
}
/**
* Optionally a sent to endpoint which means that this expression should only be based
* on {@link Exchange} which has been sent to the given endpoint uri.
*
* Notice the {@link Exchange} may have been sent to other endpoints as well. This condition will match
* if the {@link Exchange} has been sent at least once to the given endpoint.
*
* @param endpointUri uri of endpoint or pattern (see the EndpointHelper javadoc)
* @return the builder
* @see org.apache.camel.util.EndpointHelper#matchEndpoint(org.apache.camel.CamelContext, String, String)
*/
public NotifyBuilder wereSentTo(final String endpointUri) {
// insert in start of stack but after the previous wereSentTo
stack.add(wereSentToIndex++, new EventPredicateSupport() {
private ConcurrentMap sentTo = new ConcurrentHashMap<>();
@Override
public boolean isAbstract() {
// is abstract as its a filter
return true;
}
@Override
public boolean onExchangeSent(Exchange exchange, Endpoint endpoint, long timeTaken) {
if (EndpointHelper.matchEndpoint(context, endpoint.getEndpointUri(), endpointUri)) {
sentTo.put(exchange.getExchangeId(), exchange.getExchangeId());
}
return onExchange(exchange);
}
@Override
public boolean onExchange(Exchange exchange) {
// filter only when sentTo
String sent = sentTo.get(exchange.getExchangeId());
return sent != null;
}
public boolean matches() {
// should be true as we use the onExchange to filter
return true;
}
@Override
public void reset() {
sentTo.clear();
}
@Override
public String toString() {
return "wereSentTo(" + endpointUri + ")";
}
});
return this;
}
/**
* Sets a condition when number of {@link Exchange} has been received.
*
* The number matching is at least based which means that if more messages received
* it will match also.
*
* @param number at least number of messages
* @return the builder
*/
public NotifyBuilder whenReceived(final int number) {
stack.add(new EventPredicateSupport() {
private AtomicInteger current = new AtomicInteger();
@Override
public boolean onExchangeCreated(Exchange exchange) {
current.incrementAndGet();
return true;
}
public boolean matches() {
return current.get() >= number;
}
@Override
public void reset() {
current.set(0);
}
@Override
public String toString() {
return "whenReceived(" + number + ")";
}
});
return this;
}
/**
* Sets a condition when number of {@link Exchange} is done being processed.
*
* The number matching is at least based which means that if more messages received
* it will match also.
*
* The difference between done and completed is that done can also include failed
* messages, where as completed is only successful processed messages.
*
* @param number at least number of messages
* @return the builder
*/
public NotifyBuilder whenDone(final int number) {
stack.add(new EventPredicateSupport() {
private final AtomicInteger current = new AtomicInteger();
@Override
public boolean onExchangeCompleted(Exchange exchange) {
current.incrementAndGet();
return true;
}
@Override
public boolean onExchangeFailed(Exchange exchange) {
current.incrementAndGet();
return true;
}
public boolean matches() {
return current.get() >= number;
}
@Override
public void reset() {
current.set(0);
}
@Override
public String toString() {
return "whenDone(" + number + ")";
}
});
return this;
}
/**
* Sets a condition when tne n'th (by index) {@link Exchange} is done being processed.
*
* The difference between done and completed is that done can also include failed
* messages, where as completed is only successful processed messages.
*
* @param index the message by index to be done
* @return the builder
*/
public NotifyBuilder whenDoneByIndex(final int index) {
stack.add(new EventPredicateSupport() {
private AtomicInteger current = new AtomicInteger();
private String id;
private AtomicBoolean done = new AtomicBoolean();
@Override
public boolean onExchangeCreated(Exchange exchange) {
if (current.get() == index) {
id = exchange.getExchangeId();
}
current.incrementAndGet();
return true;
}
@Override
public boolean onExchangeCompleted(Exchange exchange) {
if (exchange.getExchangeId().equals(id)) {
done.set(true);
}
return true;
}
@Override
public boolean onExchangeFailed(Exchange exchange) {
if (exchange.getExchangeId().equals(id)) {
done.set(true);
}
return true;
}
public boolean matches() {
return done.get();
}
@Override
public void reset() {
current.set(0);
id = null;
done.set(false);
}
@Override
public String toString() {
return "whenDoneByIndex(" + index + ")";
}
});
return this;
}
/**
* Sets a condition when number of {@link Exchange} has been completed.
*
* The number matching is at least based which means that if more messages received
* it will match also.
*
* The difference between done and completed is that done can also include failed
* messages, where as completed is only successful processed messages.
*
* @param number at least number of messages
* @return the builder
*/
public NotifyBuilder whenCompleted(final int number) {
stack.add(new EventPredicateSupport() {
private AtomicInteger current = new AtomicInteger();
@Override
public boolean onExchangeCompleted(Exchange exchange) {
current.incrementAndGet();
return true;
}
public boolean matches() {
return current.get() >= number;
}
@Override
public void reset() {
current.set(0);
}
@Override
public String toString() {
return "whenCompleted(" + number + ")";
}
});
return this;
}
/**
* Sets a condition when number of {@link Exchange} has failed.
*
* The number matching is at least based which means that if more messages received
* it will match also.
*
* @param number at least number of messages
* @return the builder
*/
public NotifyBuilder whenFailed(final int number) {
stack.add(new EventPredicateSupport() {
private AtomicInteger current = new AtomicInteger();
@Override
public boolean onExchangeFailed(Exchange exchange) {
current.incrementAndGet();
return true;
}
public boolean matches() {
return current.get() >= number;
}
@Override
public void reset() {
current.set(0);
}
@Override
public String toString() {
return "whenFailed(" + number + ")";
}
});
return this;
}
/**
* Sets a condition when number of {@link Exchange} is done being processed.
*
* messages, where as completed is only successful processed messages.
*
* @param number exactly number of messages
* @return the builder
*/
public NotifyBuilder whenExactlyDone(final int number) {
stack.add(new EventPredicateSupport() {
private AtomicInteger current = new AtomicInteger();
@Override
public boolean onExchangeCompleted(Exchange exchange) {
current.incrementAndGet();
return true;
}
@Override
public boolean onExchangeFailed(Exchange exchange) {
current.incrementAndGet();
return true;
}
public boolean matches() {
return current.get() == number;
}
@Override
public void reset() {
current.set(0);
}
@Override
public String toString() {
return "whenExactlyDone(" + number + ")";
}
});
return this;
}
/**
* Sets a condition when number of {@link Exchange} has been completed.
*
* The difference between done and completed is that done can also include failed
* messages, where as completed is only successful processed messages.
*
* @param number exactly number of messages
* @return the builder
*/
public NotifyBuilder whenExactlyCompleted(final int number) {
stack.add(new EventPredicateSupport() {
private AtomicInteger current = new AtomicInteger();
@Override
public boolean onExchangeCompleted(Exchange exchange) {
current.incrementAndGet();
return true;
}
public boolean matches() {
return current.get() == number;
}
@Override
public void reset() {
current.set(0);
}
@Override
public String toString() {
return "whenExactlyCompleted(" + number + ")";
}
});
return this;
}
/**
* Sets a condition when number of {@link Exchange} has failed.
*
* @param number exactly number of messages
* @return the builder
*/
public NotifyBuilder whenExactlyFailed(final int number) {
stack.add(new EventPredicateSupport() {
private AtomicInteger current = new AtomicInteger();
@Override
public boolean onExchangeFailed(Exchange exchange) {
current.incrementAndGet();
return true;
}
public boolean matches() {
return current.get() == number;
}
@Override
public void reset() {
current.set(0);
}
@Override
public String toString() {
return "whenExactlyFailed(" + number + ")";
}
});
return this;
}
/**
* Sets a condition that any received {@link Exchange} should match the {@link Predicate}
*
* @param predicate the predicate
* @return the builder
*/
public NotifyBuilder whenAnyReceivedMatches(final Predicate predicate) {
return doWhenAnyMatches(predicate, true);
}
/**
* Sets a condition that any done {@link Exchange} should match the {@link Predicate}
*
* @param predicate the predicate
* @return the builder
*/
public NotifyBuilder whenAnyDoneMatches(final Predicate predicate) {
return doWhenAnyMatches(predicate, false);
}
private NotifyBuilder doWhenAnyMatches(final Predicate predicate, final boolean received) {
stack.add(new EventPredicateSupport() {
private final AtomicBoolean matches = new AtomicBoolean();
@Override
public boolean onExchangeCompleted(Exchange exchange) {
if (!received && !matches.get()) {
matches.set(predicate.matches(exchange));
}
return true;
}
@Override
public boolean onExchangeFailed(Exchange exchange) {
if (!received && !matches.get()) {
matches.set(predicate.matches(exchange));
}
return true;
}
@Override
public boolean onExchangeCreated(Exchange exchange) {
if (received && !matches.get()) {
matches.set(predicate.matches(exchange));
}
return true;
}
public boolean matches() {
return matches.get();
}
@Override
public void reset() {
matches.set(false);
}
@Override
public String toString() {
if (received) {
return "whenAnyReceivedMatches(" + predicate + ")";
} else {
return "whenAnyDoneMatches(" + predicate + ")";
}
}
});
return this;
}
/**
* Sets a condition that all received {@link Exchange} should match the {@link Predicate}
*
* @param predicate the predicate
* @return the builder
*/
public NotifyBuilder whenAllReceivedMatches(final Predicate predicate) {
return doWhenAllMatches(predicate, true);
}
/**
* Sets a condition that all done {@link Exchange} should match the {@link Predicate}
*
* @param predicate the predicate
* @return the builder
*/
public NotifyBuilder whenAllDoneMatches(final Predicate predicate) {
return doWhenAllMatches(predicate, false);
}
private NotifyBuilder doWhenAllMatches(final Predicate predicate, final boolean received) {
stack.add(new EventPredicateSupport() {
private final AtomicBoolean matches = new AtomicBoolean(true);
@Override
public boolean onExchangeCompleted(Exchange exchange) {
if (!received && matches.get()) {
matches.set(predicate.matches(exchange));
}
return true;
}
@Override
public boolean onExchangeFailed(Exchange exchange) {
if (!received && matches.get()) {
matches.set(predicate.matches(exchange));
}
return true;
}
@Override
public boolean onExchangeCreated(Exchange exchange) {
if (received && matches.get()) {
matches.set(predicate.matches(exchange));
}
return true;
}
public boolean matches() {
return matches.get();
}
@Override
public void reset() {
matches.set(true);
}
@Override
public String toString() {
if (received) {
return "whenAllReceivedMatches(" + predicate + ")";
} else {
return "whenAllDoneMatches(" + predicate + ")";
}
}
});
return this;
}
/**
* Sets a condition when the provided mock is satisfied based on {@link Exchange}
* being sent to it when they are done.
*
* The idea is that you can use Mock for setting fine grained expectations
* and then use that together with this builder. The mock provided does NOT
* have to already exist in the route. You can just create a new pseudo mock
* and this builder will send the done {@link Exchange} to it. So its like
* adding the mock to the end of your route(s).
*
* @param mock the mock
* @return the builder
*/
public NotifyBuilder whenDoneSatisfied(final MockEndpoint mock) {
return doWhenSatisfied(mock, false);
}
/**
* Sets a condition when the provided mock is satisfied based on {@link Exchange}
* being sent to it when they are received.
*
* The idea is that you can use Mock for setting fine grained expectations
* and then use that together with this builder. The mock provided does NOT
* have to already exist in the route. You can just create a new pseudo mock
* and this builder will send the done {@link Exchange} to it. So its like
* adding the mock to the end of your route(s).
*
* @param mock the mock
* @return the builder
*/
public NotifyBuilder whenReceivedSatisfied(final MockEndpoint mock) {
return doWhenSatisfied(mock, true);
}
private NotifyBuilder doWhenSatisfied(final MockEndpoint mock, final boolean received) {
stack.add(new EventPredicateSupport() {
private Producer producer;
@Override
public boolean onExchangeCreated(Exchange exchange) {
if (received) {
sendToMock(exchange);
}
return true;
}
@Override
public boolean onExchangeFailed(Exchange exchange) {
if (!received) {
sendToMock(exchange);
}
return true;
}
@Override
public boolean onExchangeCompleted(Exchange exchange) {
if (!received) {
sendToMock(exchange);
}
return true;
}
private void sendToMock(Exchange exchange) {
// send the exchange when its completed to the mock
try {
if (producer == null) {
producer = mock.createProducer();
}
producer.process(exchange);
} catch (Exception e) {
throw ObjectHelper.wrapRuntimeCamelException(e);
}
}
public boolean matches() {
try {
return mock.await(0, TimeUnit.SECONDS);
} catch (InterruptedException e) {
throw ObjectHelper.wrapRuntimeCamelException(e);
}
}
@Override
public void reset() {
mock.reset();
}
@Override
public String toString() {
if (received) {
return "whenReceivedSatisfied(" + mock + ")";
} else {
return "whenDoneSatisfied(" + mock + ")";
}
}
});
return this;
}
/**
* Sets a condition when the provided mock is not satisfied based on {@link Exchange}
* being sent to it when they are received.
*
* The idea is that you can use Mock for setting fine grained expectations
* and then use that together with this builder. The mock provided does NOT
* have to already exist in the route. You can just create a new pseudo mock
* and this builder will send the done {@link Exchange} to it. So its like
* adding the mock to the end of your route(s).
*
* @param mock the mock
* @return the builder
*/
public NotifyBuilder whenReceivedNotSatisfied(final MockEndpoint mock) {
return doWhenNotSatisfied(mock, true);
}
/**
* Sets a condition when the provided mock is not satisfied based on {@link Exchange}
* being sent to it when they are done.
*
* The idea is that you can use Mock for setting fine grained expectations
* and then use that together with this builder. The mock provided does NOT
* have to already exist in the route. You can just create a new pseudo mock
* and this builder will send the done {@link Exchange} to it. So its like
* adding the mock to the end of your route(s).
*
* @param mock the mock
* @return the builder
*/
public NotifyBuilder whenDoneNotSatisfied(final MockEndpoint mock) {
return doWhenNotSatisfied(mock, false);
}
private NotifyBuilder doWhenNotSatisfied(final MockEndpoint mock, final boolean received) {
stack.add(new EventPredicateSupport() {
private Producer producer;
@Override
public boolean onExchangeCreated(Exchange exchange) {
if (received) {
sendToMock(exchange);
}
return true;
}
@Override
public boolean onExchangeFailed(Exchange exchange) {
if (!received) {
sendToMock(exchange);
}
return true;
}
@Override
public boolean onExchangeCompleted(Exchange exchange) {
if (!received) {
sendToMock(exchange);
}
return true;
}
private void sendToMock(Exchange exchange) {
// send the exchange when its completed to the mock
try {
if (producer == null) {
producer = mock.createProducer();
}
producer.process(exchange);
} catch (Exception e) {
throw ObjectHelper.wrapRuntimeCamelException(e);
}
}
public boolean matches() {
try {
return !mock.await(0, TimeUnit.SECONDS);
} catch (InterruptedException e) {
throw ObjectHelper.wrapRuntimeCamelException(e);
}
}
@Override
public void reset() {
mock.reset();
}
@Override
public String toString() {
if (received) {
return "whenReceivedNotSatisfied(" + mock + ")";
} else {
return "whenDoneNotSatisfied(" + mock + ")";
}
}
});
return this;
}
/**
* Sets a condition that the bodies is expected to be received in the order as well.
*
* This condition will discard any additional messages. If you need a more strict condition
* then use {@link #whenExactBodiesReceived(Object...)}
*
* @param bodies the expected bodies
* @return the builder
* @see #whenExactBodiesReceived(Object...)
*/
public NotifyBuilder whenBodiesReceived(Object... bodies) {
List