camel split/aggregate and merge List<A> that contains List<B> that contains List<C> - spring-boot

Given a structure similar to this one:
#Data
public class A {
//..other fields..
private List<B> bs;
}
#Data
public class B {
//..other fields..
private List<C> cs;
}
I have to process a list of type A in multiple steps/routes. Some operation are in the A level, others in other levels like C.
The problem I'm trying to solve is to process every single C given an A and then some logic afterwards have to work with the updated A model.
I can successfully split and aggregate back the list<C> but now i'm stuck trying to rebuild A given the output for B and C.
This what I have at the moment:
from("direct:my-A-Item")
.id("direct-a")
.autoStartup(true)
.split(ExpressionBuilder.beanExpression(new CSplitter(), "getBs"), new MyAggregationStrategy())
.streaming()
.split(ExpressionBuilder.beanExpression(new CSplitter(), "getCs"), new MyAggregationStrategy())
.streaming()
.bean(processor, "doStuff")//Working on a since C instance
.end()
.bean(processor, "test") //Here I get the worked List<C>
.end()
//.bean(processor, "thisProcessorNeedsA").end(); //TODO get the original A and the output List<C> so i can make further work on them
How can I update the B instances with the new list of C and then do the same to update A?
public class CSplitter {
public List<B> getBs(A a) {
return a.getBs();
}
public List<C> getCs(B b) {
return b.getCs();
}
}
public class MyAggregationStrategy implements AggregationStrategy {
#Override
public Exchange aggregate(Exchange oldExchange, Exchange newExchange) {
Object newBody = newExchange.getIn().getBody();
ArrayList<Object> list = null;
if (oldExchange == null) {
list = new ArrayList<Object>();
list.add(newBody);
newExchange.getIn().setBody(list);
return newExchange;
} else {
list = oldExchange.getIn().getBody(ArrayList.class);
list.add(newBody);
return oldExchange;
}
}
}
Checking the documentations and online resources i could not find any example aggregating having also the body from a previous step... any tips is welcome :)

I would capture the body A as an exchange property before splitting, then it will be available later.
from("direct:my-A-Item")
.id("direct-a")
.autoStartup(true)
.setProperty("originalA", body())
.split
// etc.
.end()
.bean(processor, "myMethod(${property.originalA}, ${body})").end();

Related

Return object from async method into CompletableFuture<Object> list?

I have a list of objects (from model class Name) that I want to process using an async method. The async method will take each Name object in the list and process it (check if it's not taken), I want to have a list of Names after they all get processed by the async method to get returned to the isNameAvailable(LinkedHashSet<Name> nameList) method
My executor config
#Configuration
#ManagedResource
public class ThreadConfig {
#Bean(name = "nameAvailabilityExecutor")
public Executor nameAvailabilityExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(100);
executor.setMaxPoolSize(1200);
executor.setQueueCapacity(10000);
executor.setThreadNamePrefix("nameAvailabilityExecutor-");
executor.initialize();
return executor;
}
}
Service class which will call the Async method in another class
public LinkedHashSet<Name> isNameAvailable(LinkedHashSet<Name> nameList) {
LinkedHashSet<Name> nameCheckedList = new LinkedHashSet<>();
for (Name nameObj : nameList) {
Name nameCheckedObj = domainAvailabilityServiceThread.isNameAvailable(nameObj);
nameCheckedList.add(nameCheckedObj);
}
return nameCheckedList;
}
The async method which will do the processing
#Async("nameAvailabilityExecutor")
public Name isNameAvailable(Name nameObj) {
String name = nameObj.getName();
if (getByNameCheck(name)) {
nameObj.setAvailable(true);
} else {
nameObj.setAvailable(false);
}
return nameObj;
}
From my understanding CompletableFuture is what I need to use here? What is the correct way using CompletableFuture in this scenario?
You can simply make isNameAvailable(Name) return a CompletableFuture:
#Async("nameAvailabilityExecutor")
public CompletableFuture<Name> isNameAvailable(Name nameObj) {
String name = nameObj.getName();
if (getByNameCheck(name)) {
nameObj.setAvailable(true);
} else {
nameObj.setAvailable(false);
}
return CompletableFuture.completedFuture(nameObj);
}
Spring #Async will deal with the asynchronous execution as you intended.
You will also need to change the return type of isNameAvailable(LinkedHashSet) to a simple List or something similar, since it does not really make sense to store CompletableFutures in a Set:
public List<CompletableFuture<Name>> isNameAvailable(LinkedHashSet<Name> nameList) {
List<CompletableFuture<Name>> nameCheckedList = new ArrayList<>();
for (Name nameObj : nameList) {
CompletableFuture<Name> nameCheckedObj = domainAvailabilityServiceThread.isNameAvailable(nameObj);
nameCheckedList.add(nameCheckedObj);
}
return nameCheckedList;
}
Note that it is probably not a good idea to asynchronously modify the state of an object like you are doing here with Name, as it makes it more difficult to guarantee what state will be visible to the calling thread. It might be preferable to work with CompletableFuture<Boolean>:
#Async("nameAvailabilityExecutor")
public CompletableFuture<Boolean> isNameAvailable(Name nameObj) {
String name = nameObj.getName();
return CompletableFuture.completedFuture(getByNameCheck(name));
}
and return a Map<Name, CompletableFuture<Boolean>>:
public Map<Name, CompletableFuture<Boolean>> isNameAvailable(LinkedHashSet<Name> nameList) {
Map<Name, CompletableFuture<Boolean>> nameCheckedList = new HashMap<>();
for (Name nameObj : nameList) {
CompletableFuture<Boolean>> nameCheckedObj = domainAvailabilityServiceThread.isNameAvailable(nameObj);
nameCheckedList.put(nameObj, nameCheckedObj);
}
return nameCheckedList;
}
and let the calling thread do whatever is needed with that check.

Mockito, how to mock call by reference method on same class

Why I can not mock callRefMethod method (call method by reference) on below code? The problem is real method of callRefMethod always being called.
public class ManageUserService {
public void callRefMethod(List<String> lsStr, boolean flag){
if (flag){
lsStr.add("one");
lsStr.add("two");
}
}
public void methodA(){
List<String> lsStr = new ArrayList<>();
lsStr.add("zero");
this.callRefMethod(lsStr, true);
for(String str : lsStr){
System.out.println(str);
}
}
}
Unit tests:
public class ManageUserServiceTest {
#InjectMocks
private ManageUserService manageUserService;
private AutoCloseable closeable;
#BeforeEach
public void init() {
closeable = MockitoAnnotations.openMocks(this);
}
#AfterEach
void closeService() throws Exception {
closeable.close();
}
#Test
void methodATest(){
List<String> lsData = new ArrayList<>();
lsData.add("start");
ManageUserService manageUserServiceA = new ManageUserService();
ManageUserService userSpy = spy(manageUserServiceA);
doNothing().when(userSpy).callRefMethod(lsData, true);
userSpy.methodA();
verify(userSpy).callRefMethod(ArgumentMatchers.any(ArrayList.class), ArgumentMatchers.any(Boolean.class));
}
}
The result :
zero
one
two
The problem is the difference between the list you're creating in the test method, which is used to match the expected parameters when "doing nothing":
List<String> lsData = new ArrayList<>();
lsData.add("start");
...
doNothing().when(userSpy).callRefMethod(lsData, true);
and the list created in the tested method, passed to the spy object:
List<String> lsStr = new ArrayList<>();
lsStr.add("zero");
this.callRefMethod(lsStr, true);
You're telling Mockito to doNothing if the list is: ["start"], but such list is never passed to the callRefMethod. ["zero"] is passed there, which does not match the expected params, so actual method is called.
Mockito uses equals to compare the actual argument with an expected parameter value - see: the documentation. To work around that ArgumentMatchers can be used.
You can either fix the value added to the list in the test or match the expected parameter in a less strict way (e.g. using anyList() matcher).
ok i did it by using : where manageUserServiceOne is spy of ManageUserService class
void methodATest(){
List<String> lsData = new ArrayList<>();
lsData.add("start");
doAnswer((invocation) -> {
System.out.println(invocation.getArgument(0).toString());
List<String> lsModify = invocation.getArgument(0);
lsModify.add("mockA");
lsModify.add("mockB");
return null;
}).when(manageUserServiceOne).callRefMethod(anyList(), anyBoolean());
manageUserServiceOne.methodA();
verify(manageUserServiceOne).callRefMethod(ArgumentMatchers.any(ArrayList.class), ArgumentMatchers.any(Boolean.class));
}

How to use a Flux inside an object as JSON response?

In my Spring Webflux API gateway I am receiving a Flux from a microservice via REST:
public Flux<MyObject> getMyObjects(String id) {
Flux<MyObject> myObjects = webClient.get().uri(nextServerUrl + "/myobject" + issueId)
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToFlux(MyObject.class);
return myObjects;
}
I have to rearrange the information received by the microservice in the API gateway for the response to the client. I tried to do it in two ways:
Use the Flux as far as possible:
private Rearranged createClientResponse(String id) {
Rearranged rearranged = new Rearranged();
Flux<MyObject> myObjects = myObjectService.getMyObjects(id);
rearranged.setMyObjects(myObjects);
myObjects.map(myObject -> {
rearranged.setInfo(myObject.getInfo());
//set more
return myObjects;
});
return rearranged;
}
public class Rearranged {
private Flux<MyObject> myObjects;
//more attributes
}
Result: Following empty object:
{
"information": null,
"myObjects": {
"scanAvailable": true,
"prefetch": -1
}
}
Block the Flux and work with synchronous objects
private Rearranged createClientResponse(String id) {
Rearranged rearranged = new Rearranged();
List<MyObject> myObjects = myObjectService.getMyObjects(id).collectList().block();
rearranged.setMyObjects(myObjects);
rearranged.setInfo(myObjects.get(0).getInfo());
return rearranged;
}
public class Rearranged {
private List<MyObject> myObjects;
//more attributes
}
Result: receiving the exception block()/blockFirst()/blockLast() are blocking which is not supported in thread
What would be the right way to achieve the possibility of rearranging the information from the microservice response to respond to the client?
How would I be able to block for the Flux to complete? I understand that a block is possible when I am returning a "synchronous" object (like I am doing but still getting the exception)?
First of all, your model should not countains reactive stream. Use plain object or list.
public class Rearranged {
private MyObject myObject;
}
Or
public class Rearranged {
private List<MyObject> myObjects;
}
If you block the thread, reactor threads will exhausted in a moments. If your getMyObjects method only receives one object (if not, look at the end of the comment), then you should handle it as a Mono.
Then in the createClientResponse, you have to return with Mono<Rearranged>
Now you can easily map from one Mono to another using the .map method.
private Mono<Rearranged> createClientResponse(String id) {
Mono<MyObject> myObjects = myObjectService.getMyObjects(id);
return myObjects.map(myObject -> {
retrun new Rearranged(myObject)
//create the proper object here
});
}
If you need more object, you can use the same method, for example, the collectList() collect the elements from the Flux<> into Mono<List<>>, then the same method can be accepted.

How to make the PUT method work? I want to update my data in Table

This is my POST method and it is successful and run well. My question is how to do the PUT request method so that it can update the data well?
Post method
public void addRecipe(RecipeDTO recipedto)
{
Category categoryTitle = categoryRepository.findByCategoryTitle(recipedto.getCategoryTitle());
Recipe recipe = new Recipe();
/*I map my dto data original model*/
recipe.setRID(recipedto.getrID());
recipe.setRecipeTitle(recipedto.getRecipeTitle());
recipe.setDescription(recipedto.getDescription());
recipe.setCookTime(recipedto.getCookTime());
List categoryList = new ArrayList<>();
categoryList.add(categoryTitle);
recipe.setCategories(categoryList);
Recipe savedRecipe = recipeRepository.save(recipe);
/*I map the data in ingredientDTO and setpDTO to actual model */
List ingredientList = new ArrayList<>();
for(IngredientDTO ingredientdto : recipedto.getIngredients())
{
Ingredient ingredient = new Ingredient();
ingredient.setIID(ingredientdto.getiID());
ingredient.setIngredientName(ingredientdto.getIngredientName());
ingredient.setRecipe(savedRecipe);
ingredientList.add(ingredient);
}
List stepList = new ArrayList<>();
for(StepDTO stepdto : recipedto.getSteps())
{
Step step = new Step();
step.setSID(stepdto.getsID());
step.setStepDescription(stepdto.getStepDescription());
step.setStepNumber(stepdto.getStepNumber());
step.setRecipe(savedRecipe);
stepList.add(step);
}
ingredientRepository.save(ingredientList);
stepRepository.save(stepList);
}
This is my put method and it wont work, how should I do it, because I have no idea. Please teach me to do this method, if it is better.
public void updateRecipe(RecipeDTO recipedto, String id)
{
Recipe recipe = recipeRepository.findByrID(recipedto.getrID());
if(id==recipedto.getrID().toString())
{
recipeRepository.save(recipe);
}
}
When building REST services in Java you usually use an Framework to help you with this.
Like "jax-rs": https://mvnrepository.com/artifact/javax.ws.rs/javax.ws.rs-api/2.0
If using jax-rs then you mark your method as an Http PUT method with the annotation #PUT, ex:
#PUT
#Path("ex/foo")
public Response somePutMethod() {
return Response.ok().entity("Put some Foos!").build();
}
If using Spring as an Framework you mark your PUT method with the #RequestMapping annotation, ex:
#RequestMapping(value = "/ex/foo", method = PUT)
public String putFoos() {
return "Put some Foos";
}
Firstly and very importantly, you DO NOT use String == String for checking equality. Your code:
public void updateRecipe(RecipeDTO recipedto, String id)
{
Recipe recipe = recipeRepository.findByrID(recipedto.getrID());
if(id==recipedto.getrID().toString())
{
recipeRepository.save(recipe);
}
}
It should be:
public void updateRecipe(RecipeDTO recipedto, String id)
{
Recipe recipe = recipeRepository.findByrID(recipedto.getrID());
if(recipedto.getrID().toString().equals(id))
{
recipeRepository.save(recipe);
}
}
Why?
Because equality with == checks if objects have the same memory address. In other words:
new Integer(1) == new Integer(1) //false
1 == 1 //true
new String("hello") == new String("hello") //false
"hello" == "hello" //true because literal strings are stored in a String pool
new String("hello") == "hello" //false
Secondly, you SHOULD ALWAYS use generics with Collection APIs.
Your code:
List categoryList = new ArrayList<>();
Should be:
List<Category> categoryList = new ArrayList<>();
And lastly, like askepan said, you have not defined what framework you are using. In case of Jersey (JAX-RS implementation) you have HTTP Request Methods:
#GET, #POST, #PUT, #DELETE, #HEAD, #OPTIONS.
#PUT
#Produces("text/plain")
#Consumes("text/plain")
public Response putContainer() {
System.out.println("PUT CONTAINER " + container);
URI uri = uriInfo.getAbsolutePath();
Container c = new Container(container, uri.toString());
Response r;
if (!MemoryStore.MS.hasContainer(c)) {
r = Response.created(uri).build();
} else {
r = Response.noContent().build();
}
MemoryStore.MS.createContainer(c);
return r;
}
If you use Spring, there are #RequestMapping(method = ), or short versions:
#GetMapping, #PutMapping, #PostMapping, #DeleteMapping.
#GetMapping("/{id}")
public Person getPerson(#PathVariable Long id) {
// ...
}
#PutMapping
public void add(#RequestBody Person person) {
// ...
}
According to the annotation, the method will be called accordingly.
More information in:
Spring,JAX-RS

Update list items in PagingLibrary w/o using Room (Network only)

I'm using Paging Library to load data from network using ItemKeyedDataSource. After fetching items user can edit them, this updates are done inside in Memory cache (no database like Room is used).
Now since the PagedList itself cannot be updated (discussed here) I have to recreate PagedList and pass it to the PagedListAdapter.
The update itself is no problem but after updating the recyclerView with the new PagedList, the list jumps to the beginning of the list destroying previous scroll position. Is there anyway to update PagedList while keeping scroll position (like how it works with Room)?
DataSource is implemented this way:
public class MentionKeyedDataSource extends ItemKeyedDataSource<Long, Mention> {
private Repository repository;
...
private List<Mention> cachedItems;
public MentionKeyedDataSource(Repository repository, ..., List<Mention> cachedItems){
super();
this.repository = repository;
this.teamId = teamId;
this.inboxId = inboxId;
this.filter = filter;
this.cachedItems = new ArrayList<>(cachedItems);
}
#Override
public void loadInitial(#NonNull LoadInitialParams<Long> params, final #NonNull ItemKeyedDataSource.LoadInitialCallback<Mention> callback) {
Observable.just(cachedItems)
.filter(() -> return cachedItems != null && !cachedItems.isEmpty())
.switchIfEmpty(repository.getItems(..., params.requestedLoadSize).map(...))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(response -> callback.onResult(response.data.list));
}
#Override
public void loadAfter(#NonNull LoadParams<Long> params, final #NonNull ItemKeyedDataSource.LoadCallback<Mention> callback) {
repository.getOlderItems(..., params.key, params.requestedLoadSize)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(response -> callback.onResult(response.data.list));
}
#Override
public void loadBefore(#NonNull LoadParams<Long> params, final #NonNull ItemKeyedDataSource.LoadCallback<Mention> callback) {
repository.getNewerItems(..., params.key, params.requestedLoadSize)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(response -> callback.onResult(response.data.list));
}
#NonNull
#Override
public Long getKey(#NonNull Mention item) {
return item.id;
}
}
The PagedList created like this:
PagedList.Config config = new PagedList.Config.Builder()
.setPageSize(PAGE_SIZE)
.setInitialLoadSizeHint(preFetchedItems != null && !preFetchedItems.isEmpty()
? preFetchedItems.size()
: PAGE_SIZE * 2
).build();
pagedMentionsList = new PagedList.Builder<>(new MentionKeyedDataSource(mRepository, team.id, inbox.id, mCurrentFilter, preFetchedItems)
, config)
.setFetchExecutor(ApplicationThreadPool.getBackgroundThreadExecutor())
.setNotifyExecutor(ApplicationThreadPool.getUIThreadExecutor())
.build();
The PagedListAdapter is created like this:
public class ItemAdapter extends PagedListAdapter<Item, ItemAdapter.ItemHolder> { //Adapter from google guide, Nothing special here.. }
mAdapter = new ItemAdapter(new DiffUtil.ItemCallback<Mention>() {
#Override
public boolean areItemsTheSame(Item oldItem, Item newItem) {
return oldItem.id == newItem.id;
}
#Override
public boolean areContentsTheSame(Item oldItem, Item newItem) {
return oldItem.equals(newItem);
}
});
, and updated like this:
mAdapter.submitList(pagedList);
P.S: If there is a better way to update list items without using Room please share.
All you need to do is to invalidate current DataSource each time you update your data.
What I would do:
move networking logic from MentionKeyedDataSource to new class that extends PagedList.BoundaryCallback and set it when building your PagedList.
move all you data to some DataProvider that holds all your downloaded data and has reference to DataSource. Each time data is updated in DataProvider invalidate current DataSource
Something like that maybe
val dataProvider = PagedDataProvider()
val dataSourceFactory = InMemoryDataSourceFactory(dataProvider = dataProvider)
where
class PagedDataProvider : DataProvider<Int, DataRow> {
private val dataCache = mutableMapOf<Int, List<DataRow>>()
override val sourceLiveData = MutableLiveData<DataSource<Int, DataRow>>()
//implement data add/remove/update here
//and after each modification call
//sourceLiveData.value?.invalidate()
}
and
class InMemoryDataSourceFactory<Key, Value>(
private val dataProvider: DataProvider<Key, Value>
) : DataSource.Factory<Key, Value>() {
override fun create(): DataSource<Key, Value> {
val source = InMemoryDataSource(dataProvider = dataProvider)
dataProvider.sourceLiveData.postValue(source)
return source
}
}
This approach is very similar to what Room does - every time table row is updated - current DataSource is invalidated and DataSourceFactory creates new data source.
You can modify directly in your adapter if you called currentList like that
class ItemsAdapter(): PagedListAdapter<Item, ItemsAdapter.ViewHolder(ITEMS_COMPARATOR) {
...
fun changeItem(position: Int,newData:String) {
currentList?.get(position)?.data = newData
notifyItemChanged(position)
}
}

Resources