Apache Camel Reducing Number though each Split Iteration - java

I have a directory of three files in the body of a Camel exchange, and I'm going to iterate through those 3 files using a Split. What I would like to do, is for each iteration, update a property with the total of all files (3 for this example) minus the current iteration. So at the some point, the goal is to have a property set to 1, so on another part of the code I can do logic based on this. I've tried a couple of different approaches but failed, and here I am. Here's a snippet of something I tried (simplified):
`private void test() {
from("timer:test?fixedRate=true&period=10000")
.process(exchange -> {
Path pathD = FileSystems.getDefault().getPath("Lmao").toAbsolutePath();
File folder = new File(pathD.toString());
String[] fileList = folder.list();
if (fileList != null) {
exchange.setProperty("PROPERTY", fileList.length);
}
exchange.getIn().setBody(fileList);
})
.split(body(), new AggregationStrategy() {
#Override
public Exchange aggregate(Exchange oldExchange, Exchange newExchange) {
if (oldExchange == null) {
Integer iterationsLeft = newExchange.getProperty("PROPERTY", Integer.class);
Integer iterationsLeftMinusOne = iterationsLeft - 1;
newExchange.setProperty("PROPERTY", iterationsLeftMinusOne);
return newExchange;
} else {
Integer iterationsLeft = oldExchange.getProperty("PROPERTY", Integer.class);
oldExchange.setProperty("PROPERTY", iterationsLeft - 1);
return oldExchange;
}
}
})
.process(exchange -> {
Integer test = exchange.getProperty("PROPERTY", Integer.class);
System.out.println(test);
})
.end();
}
`
This code keeps printing 3 all the time and I wanted 3,2,1

Related

Java 8 Streams Nested ForEach with different conditions

i'm totally new to java 8 streams. just want know how to write the below code using java stream api. Not sure on how to write nested loops with filters to map the data.
public AccountByCustomerDto getAccountDetails(int customerId, HttpServletRequest request) throws Exception {
List<Accountowner> accountOwnerList = repo.getAccountOwners(customerId);
List<AccountByCustomerDto.AccountDto> aDtoList = new ArrayList<AccountByCustomerDto.AccountDto>();
for (Accountowner accountOwner : accountOwnerList) {
String currency = accountOwner.getAccount1().getAccountCurrency();
if(accountOwner != null && currency.startsWith("USD")) {
List<Accountbalance> accountBalanceList = accountOwner.getAccount1().getAccountbalances();
List<AccountByCustomerDto.BalancesDto> balanceDtoList = new ArrayList<AccountByCustomerDto.BalancesDto>();
for (Accountbalance balance : accountBalanceList) {
String creditInclude = balance.getCreditLimitIncluded();
if(balance != null && creditInclude.equals("Y")) {
AccountByCustomerDto.BalancesDto balanceDto = AccountByCustomerDto.BalancesDto.builder()
.balanceType(balance.getBalanceType()).baDto(null)
.referenceDate(
balance.getReferenceDate().toInstant().atZone(ZoneId.systemDefault()).toLocalDate())
.build();
balanceDtoList.add(balanceDto);
}
}
String accountId = Integer.toString(accountOwner.getAccount1().getAccountId()) + ":"
+ accountOwner.getAccount1().getAccountCurrency();
AccountByCustomerDto.AccountDto adto = AccountByCustomerDto.AccountDto.builder()
.accountId(Utility.encrypt(accountId))
.accountNumberDisplay(accountOwner.getAccount1().getAccountDisplay())
.balances(balanceDtoList).accountLink(null).build();
aDtoList.add(adto);
}
}
return AccountByCustomerDto.builder().accounts(aDtoList).build();
}
I think just switching the code to Stream would make the code even less readable. So when one method gets very long it's a good idea to split it into smaller methods.
When the code is split up into smaller methods streams provide a real benefit when it comes to readability compared to traditional loops.
Element in stream remains in stream if accountOwnerCurrencyIsUSD is true. The remaining elements are mapped to a AccountDto using createAccountDto and the result is collected into a AccountDto list. Is much easier to read than create list, loop through other list, get currency, check currency, create another list...
public AccountByCustomerDto getAccountDetails(int customerId, HttpServletRequest request) throws Exception {
List<AccountByCustomerDto.AccountDto> aDtoList = accountOwnerList.stream()
.filter(this::accountOwnerCurrencyIsUSD)
.map(this::createAccountDto)
.collect(Collectors.toList());
return AccountByCustomerDto.builder().accounts(aDtoList).build();
}
private AccountCustomerDto.AccountDto createAccountDto(Accountowner owner) {
String accountId = accountOwner.getAccount1().getAccountId() + ":" + accountOwner.getAccount1().getAccountCurrency();
List<AccountByCustomerDto.BalancesDto> balanceDtoList = accountOwner.getAccount1().getAccountbalances()
.stream()
.filter(this::includesCredit)
.map(this::createBalanceDto)
.collect(Collectors.toList());
return AccountByCustomerDto.AccountDto.builder()
.accountId(Utility.encrypt(accountId))
.accountNumberDisplay(accountOwner.getAccount1().getAccountDisplay())
.balances(balanceDtoList)
.accountLink(null)
.build();
}
private AccountByCustomerDto.BalancesDto createBalanceDto(Accountbalance balance) {
return AccountByCustomerDto.BalancesDto.builder()
.balanceType(balance.getBalanceType())
.baDto(null)
.referenceDate(alance.getReferenceDate().toInstant().atZone(ZoneId.systemDefault()).toLocalDate())
.build();
}
private boolean accountOwnerCurrencyIsUSD(Accountowner owner) {
return accountOwner != null && "USD".eqauls(accountOwner.getAccount1().getAccountCurrency());
}
private boolean includesCredit(Accountbalance balance) {
return balance != null && "Y".equals(balance.getCreditLimitIncluded());
}
I have not way to test the code, so take it with a grain of salt.

How to run a method a the required number of times in spring boot?

I'm learning RabbitMq and simply want to send message required number of times.
I have Sender class that sends message to exchange, and I want to repeat it certain times.
I'm using scheduled annotation, but It has different purpose, and don't match to me because it doesn't stop.
#Scheduled(initialDelay = 1000,fixedDelay = 1500)
public void sendNumbers(){
int index = atomicInteger.getAndIncrement();
UUID uuid = uuids.get(index);
Pair<Integer,Integer> pair = sumPair.get(uuid);
MessagePostProcessor messagePostProcessor = message -> {
MessageProperties messageProperties = message.getMessageProperties();
messageProperties.setCorrelationId(uuid.toString());
messageProperties.setReplyTo(response.getName());
return message;
};
rabbitTemplate.convertAndSend(directExchange.getName(),routingKey,pair,messagePostProcessor);
}
How can I do it?
Just use a for loop to repeat the number of times you want.
You can do it in an ApplicationRunner.
#Bean
ApplicationRunner runner(RabbitTemplate template) {
return () -> {
for (int i = 0; i < 10; i++) {
...
template.convertAndSend(...)
}
};

Camel Exchange body record size

Scenario:
CSV file is sent to my endpoint, Pojo transforms the data for java and message sent to one of my route lets say ("direct:consume") route, then a processor processes the file manipulating the message and creating a new output.
Issue:
file contains only one line the code breaks
file contains multiple lines the code works
Tried:
tried to find a way to determine the amount of record coming in the exchange.getIn().getBody()
read on stackoverflow
read camel documentation about exchange
check java codes for Object/Objects to List conversion without knowing record amount
Code:
public void process(Exchange exchange) throws Exception {
List<Cars> output = new ArrayList<Cars>();
**List<Wehicle> rows = (List<Wehicle>) exchange.getIn().getBody(); <-- Fails**
for (Wehicle row: rows) {
output.add(new Cars(row));
}
exchange.getIn().setBody(output);
exchange.getIn().setHeader("CamelOverruleFileName", "CarEntries.csv");
}
Wehicle
...
#CsvRecord(separator = ",", skipFirstLine = true, crlf = "UNIX")
public class Wehicle {
#DataField(pos = 1)
public String CouponCode;
#DataField(pos = 2)
public String Price;
}
...
Cars
#CsvRecord(separator = ",", crlf = "UNIX", generateHeaderColumns = true)
public class Cars {
#DataField(pos = 1, columnName = "CouponCode")
private String CouponCode;
#DataField(pos = 2, columnName = "Price")
private String Price;
public Cars(Wehicle origin) {
this.CouponCode = Utilities.addQuotesToString(origin.CouponCode);
this.Price = origin.Price;
}
}
Input:
"CouponCode","Price"
"ASD/785", 1900000
"BWM/758", 2000000
Question:
How to create dynamicall a List regardless if i get one object or multiple objects?
-- exchange.getIn().getBody() returns object
How to check the amount of records from camel exchange message ?
-- exchange.getIn().getBody() no size/length method
Any other way of doing this?
Haven't used java for a long time, plus quiet new to camel.
After re checking the official documentation it seems the following changes are solving the issue.
Code:
public void process(Exchange exchange) throws Exception {
List<Cars> output = new ArrayList<Cars>();
List records = exchange.getIn().getBody(List.class);
(List<Wehicle>) rows = (List<Wehicle>) records;
for (Wehicle row: rows) {
output.add(new Cars(row));
}
exchange.getIn().setBody(output);
exchange.getIn().setHeader("CamelOverruleFileName", "CarEntries.csv");
}

Getting the line number of the Mono/Flux that returned Mono.empty()

Let's say I have a long chain of Monos. Some monos in the chain might return Mono.empty().
I can recover with switchIfEmpty, but I'd like to know which mono raised the empty (maybe so I can know where to add smarter empty handling).
Is there a way to programmatically get this information?
Silly example. In cases where I return how did I get here?, how I can know if the first flatMap or the second flatMap triggered the empty handler?
Mono.just("data")
.flatMap(t -> {
if (System.currentTimeMillis() % 2 == 0) {
return Mono.empty();
}
return Mono.just("happy1");
})
.flatMap(t -> {
if (System.currentTimeMillis() % 2 == 0) {
return Mono.empty();
}
return Mono.just("happy2");
})
.map(s -> {
return "successful complete: " + s;
})
.switchIfEmpty(Mono.fromCallable(() -> {
return "how did I get here?";
}))
.block();
Due to the dynamic nature of Flux and Mono, and to the fact that the onComplete signal is considered neutral enough that it is usually just passed through, there is no generic solution for this.
In your particular example, you could replace the Mono.empty() with something like Mono.empty().doOnComplete(() -> /* log something */).
You could even directly perform the logging in the if block, but the decorated empty trick is probably adaptable to more situations.
Another possibility is to turn emptiness into an error, rather than a switch on onComplete signal.
Errors are less neutral, so there are ways to enrich them for debugging purposes. For instance, with a .checkpoint("flatMapX") statement after each flatMap, you'd get additional stacktrace parts that would point to the flatMap which failed due to emptyness.
A way of turning emptiness to error in Mono is .single(), which will enforce exactly one onNext() or propagate onError(NoSuchElementException).
One thing to keep in mind with this trick is that the placement of checkpoint matters: it MUST be AFTER the single() so that the error raised from the single() gets detected and enriched.
So if I build on your snippet:
static final String PARSEABLE_MARKER = "PARSEABLE MARKER: <";
static final char MARKER_END = '>';
String parseLocation(Exception e) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
e.printStackTrace(pw);
String trace = sw.toString();
int start = trace.indexOf(PARSEABLE_MARKER);
if (start > 0) {
trace = trace.substring(start + PARSEABLE_MARKER.length());
trace = trace.substring(0, trace.indexOf(MARKER_END));
return trace;
}
return "I don't know";
}
String testInner() {
Random random = new Random();
final boolean first = random.nextBoolean();
return Mono.just("data")
.flatMap(t -> {
if (System.currentTimeMillis() % 2 == 0 && first) {
return Mono.empty();
}
return Mono.just("happy1");
})
.single()
.checkpoint(PARSEABLE_MARKER + "the first flatMap" + MARKER_END)
.flatMap(t -> {
if (System.currentTimeMillis() % 2 == 0 && !first) {
return Mono.empty();
}
return Mono.just("happy2");
})
.single()
.checkpoint(PARSEABLE_MARKER + "the second flatMap" + MARKER_END)
.map(s -> {
return "successful complete: " + s;
})
.onErrorResume(NoSuchElementException.class, e ->
Mono.just("how did I get here? " + parseLocation(e)))
.block();
}
This can be run in a loop in a test for instance:
#Test
void test() {
int successCount = 0;
int firstCount = 0;
int secondCount = 0;
for (int i = 0; i < 100; i++) {
String message = testInner();
if (message.startsWith("how")) {
if (message.contains("first")) {
firstCount++;
}
else if (message.contains("second")) {
secondCount++;
}
else {
System.out.println(message);
}
}
else {
successCount++;
}
}
System.out.printf("Stats: %d successful, %d detected first, %d detected second", successCount, firstCount, secondCount);
}
Which prints something like:
Stats: 85 successful, 5 detected first, 10 detected second

How to set multiple conditional opearator in java8

I am trying to convert the below code in java 8, but not sure where I am going wrong. I have 2 code snippets which I want to convert. This is the first one:
for (WebElement value :values) {
WebElement dateElement = SharedWebDriver.getInstance()
.findOptionalElement(By.className("text"), value);
WebElement groupElement =
SharedWebDriver.getInstance().findOptionalElement(By.id("label"),
value);
WebElement typeElement =
SharedWebDriver.getInstance().findOptionalElement(By.id("type"),
value);
if (dateElement != null) {
dateValue = dateElement.getText().trim();
}
if (groupElement != null) {
groupValue = groupElement.getText().trim();
}
if(typeElement!= null){
typeValue = typeElement.getText().trim();
}
}
And here I want to set value using java 8. I tried it with using the filter option, but it's not working.
for (WebElement header : headers) {
if (header != null) {
if (header.getText().equals("A")) {
entry.setDate(dateValue);
} else if (header.getText().equals("B")) {
entry.setGroup(groupValue);
} else if (header.getText().equals("C")) {
entry.setType(typeValue);
}
}
}
Can anyone help me?
The problem with those code snippets is that they modifiy variables defined outside of the loop (dateValue, groupValue and typeValue for the first one, and entry for the second one).
But lambda expressions are not really supposed to alter variables that are not defined in their scope event though you can achieve that throught methods.
For example, inside a lambda expression :
word = "hello" will not work whereas website.setTitle("title") will
I converted your code snippets in Java 8, I didn't take the time to test it but if I am if i am not mistaken, the first one will not work whereas the second one will, for the reason explained above.
values.stream()
.map(value -> new WebElement[] {
SharedWebDriver.getInstance().findOptionalElement(By.className("text"), value),
SharedWebDriver.getInstance().findOptionalElement(By.id("label"), value)),
SharedWebDriver.getInstance().findOptionalElement(By.id("type"), value) })
.forEach(webElements[] -> {
if (webElements[0] != null) {
dateValue = webElements[0].getText().trim();
}
if (webElements[1] != null) {
groupValue = webElements[1].getText().trim();
}
if(webElements[2] != null){
typeValue = webElements[2].getText().trim();
}
});
headers.stream()
.filter(Objects::nonNull)
.forEach(header -> {
if (header.getText().equals("A")) {
entry.setDate(dateValue);
} else if (header.getText().equals("B")) {
entry.setGroup(groupValue);
} else if (header.getText().equals("C")) {
entry.setType(typeValue);
}
});

Categories

Resources