I am trying to understand CompletableFuture in Java 8. As a part of it, I am trying to make some REST calls to solidify my understanding. I am using this library to make REST calls: https://github.com/AsyncHttpClient/async-http-client.
Please note, this library returns a Response object for the GET call.
Following is what I am trying to do:
Call this URL which gives the list of users: https://jsonplaceholder.typicode.com/users
Convert the Response to List of User Objects using GSON.
Iterate over each User object in the list, get the userID and then get the list of Posts made by the user from the following URL: https://jsonplaceholder.typicode.com/posts?userId=1
Convert each post response to Post Object using GSON.
Build a Collection of UserPost objects, each of which has a User Object and a list of posts made by the user.
public class UserPosts {
private final User user;
private final List<Post> posts;
public UserPosts(User user, List<Post> posts) {
this.user = user;
this.posts = posts;
}
#Override
public String toString() {
return "user = " + this.user + " \n" + "post = " + posts+ " \n \n";
}
}
I currently have it implemented as follows:
package com.CompletableFuture;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.asynchttpclient.Response;
import com.http.HttpResponse;
import com.http.HttpUtil;
import com.model.Post;
import com.model.User;
import com.model.UserPosts;
/**
* Created by vm on 8/20/18.
*/
class UserPostResponse {
private final User user;
private final Future<Response> postResponse;
UserPostResponse(User user, Future<Response> postResponse) {
this.user = user;
this.postResponse = postResponse;
}
public User getUser() {
return user;
}
public Future<Response> getPostResponse() {
return postResponse;
}
}
public class HttpCompletableFuture extends HttpResponse {
private Function<Future<Response>, List<User>> userResponseToObject = user -> {
try {
return super.convertResponseToUser(Optional.of(user.get().getResponseBody())).get();
} catch (Exception e) {
e.printStackTrace();
return null;
}
};
private Function<Future<Response>, List<Post>> postResponseToObject = post -> {
try {
return super.convertResponseToPost(Optional.of(post.get().getResponseBody())).get();
} catch (Exception e) {
e.printStackTrace();
return null;
}
};
private Function<UserPostResponse, UserPosts> buildUserPosts = (userPostResponse) -> {
try {
return new UserPosts(userPostResponse.getUser(), postResponseToObject.apply(userPostResponse.getPostResponse()));
} catch (Exception e) {
e.printStackTrace();
return null;
}
};
private Function<User, UserPostResponse> getPostResponseForUser = user -> {
Future<Response> resp = super.getPostsForUser(user.getId());
return new UserPostResponse(user, resp);
};
public HttpCompletableFuture() {
super(HttpUtil.getInstance());
}
public List<UserPosts> getUserPosts() {
try {
CompletableFuture<List<UserPosts>> usersFuture = CompletableFuture
.supplyAsync(() -> super.getUsers())
.thenApply(userResponseToObject)
.thenApply((List<User> users)-> users.stream().map(getPostResponseForUser).collect(Collectors.toList()))
.thenApply((List<UserPostResponse> userPostResponses ) -> userPostResponses.stream().map(buildUserPosts).collect(Collectors.toList()));
List<UserPosts> users = usersFuture.get();
System.out.println(users);
return users;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
However, I am not sure if the way I am doing this is right. More specifically, in userResponseToObject and postResponseToObject Functions, I am calling the get() method on the Future, which will be blocking.
Is there a better way to implement this?
If you plan to use CompletableFuture, you should use the ListenableFuture from async-http-client library. ListenableFuture can be converted to CompletableFuture.
The advantage of using CompletableFuture is that you can write logic that deals with Response object without having to know anything about futures or threads. Suppose you wrote the following 4 methods. 2 to make requests and 2 to parse responses:
ListenableFuture<Response> requestUsers() {
}
ListenableFuture<Response> requestPosts(User u) {
}
List<User> parseUsers(Response r) {
}
List<UserPost> parseUserPosts(Response r, User u) {
}
Now we can write a non-blocking method that retrieves posts for a given user:
CompletableFuture<List<UserPost>> userPosts(User u) {
return requestPosts(u)
.toCompletableFuture()
.thenApply(r -> parseUserPosts(r, u));
}
and a blocking method to read all posts for all users:
List<UserPost> getAllPosts() {
// issue all requests
List<CompletableFuture<List<UserPost>>> postFutures = requestUsers()
.toCompletableFuture()
.thenApply(userRequest -> parseUsers(userRequest)
.stream()
.map(this::userPosts)
.collect(toList())
).join();
// collect the results
return postFutures.stream()
.map(CompletableFuture::join)
.flatMap(List::stream)
.collect(toList());
}
Depending on the policy you want to use to manage blocking response, you can explore at least these implementations:
1) Invoking the overloaded method get of the class CompletableFuture with a timeout:
List<UserPosts> users = usersFuture.get(long timeout, TimeUnit unit);
From the documentation:
Waits if necessary for at most the given time for this future to
complete, and then returns its result, if available.
2) Using the alternative method getNow:
List users = usersFuture.getNow(T valueIfAbsent);
Returns the result value (or throws any encountered exception) if
completed, else returns the given valueIfAbsent.
3) Using CompletableFuture instead of Future, you can force manually the unlocking of get calling complete :
usersFuture.complete("Manual CompletableFuture's Result")
Related
Below is the service method (JsonObjectBuilderService) that converts an object (FeatureCollectionForGeoJson) to a jsonStr. This service method is used in the Get RequestMapping to send a response to the front-end.
The FeatureCollectionForGeoJson object is a class mapped for GeoJson FeatureCollection.
The GeometryForGeoJson is another class that contains the string type with "Point" value and the array that contains the latitude and longitude for the point.
The PropertyForGeoJson class contains information/properties about that pin that will be displayed in the pop-up when the pin is clicked on on the map.
#Getter
#Setter
#ToString
#NoArgsConstructor
#AllArgsConstructor
public class FeatureForGeoJson {
private final String type = "Feature";
private GeometryForGeoJson geometry;
private PropertyForGeoJson properties;
}
#Service
public class JsonObjectBuilderService {
public String transformObjectToGeoJson(FeatureCollectionForGeoJson featureCollectionForGeoJson){
ObjectMapper Obj = new ObjectMapper();
String jsonStr = null;
try {
jsonStr = Obj.writeValueAsString(featureCollectionForGeoJson);
} catch (JsonProcessingException e) {
e.printStackTrace();
} //catch (IOException e) {
return jsonStr;
}
}
This is the GetMapping that sends the response to Angular
#GetMapping("/power-plants")
public ResponseEntity<String> getAllPowerPlants() {
try {
FeatureCollectionForGeoJson powerPlantsToFeatureCollectionForGeoJson ;
//jpa query for the database to return the information
List<PowerPlant> powerPlantList = powerPlantJpaService.findAll();
if (powerPlantList.isEmpty()) {
logger.info("The power plant list is empty.");
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
logger.info("The power plant list is populated and has been returned successfully.");
powerPlantsToFeatureCollectionForGeoJson = transformPowerPlantsToFeaturesCollection.transformPowerPlantToGeoJsonElements(powerPlantList);
String objectToGeoJson = jsonObjectBuilderService.transformObjectToGeoJson(powerPlantsToFeatureCollectionForGeoJson);
logger.info(objectToGeoJson);
return new ResponseEntity<>(objectToGeoJson, HttpStatus.OK);
} catch (Exception e) {
return new ResponseEntity<>(null, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
This is how the response looks like in the browser
This is the Angular method that fetches the response.
This is the Angular component where I call the service method that fetches the response and where I want to add the pins to the map with the pop-ups.
How do I take that response from the API (line 27 from Home.component.ts -right above- or the getAll() method from the PowerPlantService) and process it to extract the Point Geometry, to create a pin with it and extract the properties to add to a pop-up to the pin?
if you use angular you should use Observables and not Promises, also avoid to post images of code, now I can't copy/paste you code.
what you want to do is return an observable in getAll(), something like this:
// in component
this.powerPlantService.getAll$().subscribe(
res => this.featureCollection = res,
err => console.log(err)
);
// in service
getAll$(): Observable<any[]> {
return this.http.get(baseUrl).pipe(
map(data => {
// transform your data here, or remove this pipe if you don't need it
return data;
})
);
}
you can transform your features in a flat object like this:
return this.http.get(baseUrl).pipe(
map(features => {
return features.map(f => {
const pointGeometry: any = {
...f.geometry,
...f.properties
};
return pointGeometry;
});
})
);
If you want to know how the back end formats and sends the response, please check in the body of the question.
Below is the service method that performs a GET request to the back end.
export class PowerPlantService {
constructor(private http: HttpClient) { }
getAll() {
return this.http.get(baseUrl);
}
Below is the component method that subscribes to the answer and adds the elements to the map.
#Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.css']
})
export class HomeComponent implements OnInit {
private latitude: number = 45.6427;
private longitude: number = 25.5887;
private map!: L.Map;
private centroid: L.LatLngExpression = [this.latitude, this.longitude];
ngOnInit(): void {
this.initMap();
}
constructor(private powerPlantService: PowerPlantService) {
}
private initMap(): void {
this.map = L.map('map', {
center: this.centroid,
zoom: 2.8
});
const tiles = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
{
minZoom: 2.8,
attribution: '© OpenStreetMap'
});
tiles.addTo(this.map);
this.powerPlantService.getAll().subscribe((data: any)=>{
console.log(data);
L.geoJSON(data).addTo(this.map)
})
I am new to vertx and async programming.
I have 2 verticles communicating via an event bus as follows:
//API Verticle
public class SearchAPIVerticle extends AbstractVerticle {
public static final String GET_USEARCH_DOCS = "get.usearch.docs";
#Autowired
private Integer defaultPort;
private void sendSearchRequest(RoutingContext routingContext) {
final JsonObject requestMessage = routingContext.getBodyAsJson();
final EventBus eventBus = vertx.eventBus();
eventBus.request(GET_USEARCH_DOCS, requestMessage, reply -> {
if (reply.succeeded()) {
Logger.info("Search Result = " + reply.result().body());
routingContext.response()
.putHeader("content-type", "application/json")
.setStatusCode(200)
.end((String) reply.result().body());
} else {
Logger.info("Document Search Request cannot be processed");
routingContext.response()
.setStatusCode(500)
.end();
}
});
}
#Override
public void start() throws Exception {
Logger.info("Starting the Gateway service (Event Sender) verticle");
// Create a Router
Router router = Router.router(vertx);
//Added bodyhandler so we can process json messages via the event bus
router.route().handler(BodyHandler.create());
// Mount the handler for incoming requests
// Find documents
router.post("/api/search/docs/*").handler(this::sendSearchRequest);
// Create an HTTP Server using default options
HttpServer server = vertx.createHttpServer();
// Handle every request using the router
server.requestHandler(router)
//start listening on port 8083
.listen(config().getInteger("http.port", 8083)).onSuccess(msg -> {
Logger.info("*************** Search Gateway Server started on "
+ server.actualPort() + " *************");
});
}
#Override
public void stop(){
//house keeping
}
}
//Below is the target verticle should be making the multiple web client call and merging the responses
.
#Component
public class SolrCloudVerticle extends AbstractVerticle {
public static final String GET_USEARCH_DOCS = "get.usearch.docs";
#Autowired
private SearchRepository searchRepositoryService;
#Override
public void start() throws Exception {
Logger.info("Starting the Solr Cloud Search Service (Event Consumer) verticle");
super.start();
ConfigStoreOptions fileStore = new ConfigStoreOptions().setType("file")
.setConfig(new JsonObject().put("path", "conf/config.json"));
ConfigRetrieverOptions configRetrieverOptions = new ConfigRetrieverOptions()
.addStore(fileStore);
ConfigRetriever configRetriever = ConfigRetriever.create(vertx, configRetrieverOptions);
configRetriever.getConfig(ar -> {
if (ar.succeeded()) {
JsonObject configJson = ar.result();
EventBus eventBus = vertx.eventBus();
eventBus.<JsonObject>consumer(GET_USEARCH_DOCS).handler(getDocumentService(searchRepositoryService, configJson));
Logger.info("Completed search service event processing");
} else {
Logger.error("Failed to retrieve the config");
}
});
}
private Handler<Message<JsonObject>> getDocumentService(SearchRepository searchRepositoryService, JsonObject configJson) {
return requestMessage -> vertx.<String>executeBlocking(future -> {
try {
//I need to incorporate the logic here that adds futures to list and composes the compositefuture
/*
//Below is my logic to populate the future list
WebClient client = WebClient.create(vertx);
List<Future> futureList = new ArrayList<>();
for (Object collection : searchRepositoryService.findAllCollections(configJson).getJsonArray(SOLR_CLOUD_COLLECTION).getList()) {
Future<String> future1 = client.post(8983, "127.0.0.1", "/solr/" + collection + "/query")
.expect(ResponsePredicate.SC_OK)
.sendJsonObject(requestMessage.body())
.map(HttpResponse::bodyAsString).recover(error -> {
System.out.println(error.getMessage());
return Future.succeededFuture();
});
futureList.add(future1);
}
//Below is the CompositeFuture logic, but the logic and construct does not make sense to me. What goes as first and second argument of executeBlocking method
/*CompositeFuture.join(futureList)
.onSuccess(result -> {
result.list().forEach( x -> {
if(x != null){
requestMessage.reply(result.result());
}
}
);
})
.onFailure(error -> {
System.out.println("We should not fail");
})
*/
future.complete("DAO returns a Json String");
} catch (Exception e) {
future.fail(e);
}
}, result -> {
if (result.succeeded()) {
requestMessage.reply(result.result());
} else {
requestMessage.reply(result.cause()
.toString());
}
});
}
}
I was able to use the org.springframework.web.reactive.function.client.WebClient calls to compose my search result from multiple web client calls, as against using Future<io.vertx.ext.web.client.WebClient> with CompositeFuture.
I was trying to avoid mixing Springboot and Vertx, but unfortunately Vertx CompositeFuture did not work here:
//This method supplies the parameter for the future.complete(..) line in getDocumentService(SearchRepository,JsonObject)
private List<JsonObject> findByQueryParamsAndDataSources(SearchRepository searchRepositoryService,
JsonObject configJson,
JsonObject requestMessage)
throws SolrServerException, IOException {
List<JsonObject> searchResultList = new ArrayList<>();
for (Object collection : searchRepositoryService.findAllCollections(configJson).getJsonArray(SOLR_CLOUD_COLLECTION).getList()) {
searchResultList.add(new JsonObject(doSearchPerCollection(collection.toString(), requestMessage.toString())));
}
return aggregateMultiCollectionSearchResults(searchResultList);
}
public String doSearchPerCollection(String collection, String message) {
org.springframework.web.reactive.function.client.WebClient client =
org.springframework.web.reactive.function.client.WebClient.create();
return client.post()
.uri("http://127.0.0.1:8983/solr/" + collection + "/query")
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromValue(message.toString()))
.retrieve()
.bodyToMono(String.class)
.block();
}
private List<JsonObject> aggregateMultiCollectionSearchResults(List<JsonObject> searchList){
//TODO: Search result aggregation
return searchList;
}
My use case is the second verticle should make multiple vertx web client calls and should combine the responses.
If an API call falls, I want to log the error and still continue processing and merging responses from other calls.
Please, any help on how my code above could be adaptable to handle the use case?
I am looking at vertx CompositeFuture, but no headway or useful example seen yet!
What you are looking for can done with Future coordination with a little bit of additional handling:
CompositeFuture.join(future1, future2, future3).onComplete(ar -> {
if (ar.succeeded()) {
// All succeeded
} else {
// All completed and at least one failed
}
});
The join composition waits until all futures are completed, either with a success or a failure.
CompositeFuture.join
takes several futures arguments (up to 6) and returns a future that is succeeded when all the futures are succeeded, and failed when all the futures are completed and at least one of them is failed
Using join you will wait for all Futures to complete, the issue is that if one of them fails you will not be able to obtain response from others as CompositeFuture will be failed. To avoid this you should add Future<T> recover(Function<Throwable, Future<T>> mapper) on each of your Futures in which you should log the error and pass an empty response so that the future does not fail.
Here is short example:
Future<String> response1 = client.post(8887, "localhost", "work").expect(ResponsePredicate.SC_OK).send()
.map(HttpResponse::bodyAsString).recover(error -> {
System.out.println(error.getMessage());
return Future.succeededFuture();
});
Future<String> response2 = client.post(8887, "localhost", "error").expect(ResponsePredicate.SC_OK).send()
map(HttpResponse::bodyAsString).recover(error -> {
System.out.println(error.getMessage());
return Future.succeededFuture();
});
CompositeFuture.join(response2, response1)
.onSuccess(result -> {
result.list().forEach(x -> {
if(x != null) {
System.out.println(x);
}
});
})
.onFailure(error -> {
System.out.println("We should not fail");
});
Edit 1:
Limit for CompositeFuture.join(Future...) is 6 Futures, in the case you need more you can use: CompositeFuture.join(Arrays.asList(future1, future2, future3)); where you can pass unlimited number of futures.
I have a Java service that queries the database and returns a list of data which I must show in a table in my front with angular, so I did this method that returns a hash map to know if an error occurred when querying or to know that there is no data in the date range so it returns an error code since I want to validate this code in the front and show a message in my front that there is no data or that an error occurred in addition to showing the
Java Controller
#CrossOrigin(origins = "http://localhost:4200")
#RestController
#RequestMapping("/consultValues")
public class ValuesController {
#GetMapping
public Map<String, Object> consultValues(
#RequestParam(required = false, value = "startDate") Integer startDate,
#RequestParam(required = false, value = "endDate") Integer endDate) throws Exception {
Map<String, Object> listValues = new HashMap<>();
try {
listValues = valuesService.listValues(startDate, endDate);
} catch (Exception e) {
LOGGER.error("Error in Values ");
throw e;
}
return listValues;
}
}
Java Service
#Override
public Map<String, Object> listValues(Integer startDate, Integer endDate) throws Exception {
Map<String, Object> response = new HashMap<>();
List<ValuesDto> list = new ArrayList<ValuesDto>();
try {
Integer start = startDate;
Integer end = endDate;
list = valuesRepository.findByDates(start, end);
if (list.isEmpty() || list == null) {
LOGGER.error("There is not values");
response.put("Error", 400);
}
} catch (Exception e) {
LOGGER.error("An error ocurred");
response.put("Error", 500);
response.put("error", e.getMessage());
throw e;
}
response.put("success", true);
response.put("data", list);
return response;
}
Now in my front I have this method of my service.ts
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import {ValoresDto} from '../Models/ValoresDTO';
#Injectable({
providedIn: 'root'
})
export class ConsultValuesService {
constructor(private http: HttpClient) { }
Url = 'http://localhost:8080/consultValues';
consultValues(startDate:any, endDate:any){
return this.http.get<ValuesDTO[]>(this.Url+ `?startDate=${startDate}&endDate=${endDate}`);
}
}
In my Component.ts I have this method but I dont know how to validate the response of my java service, for example if my java service returns a code error 400 means that there is not data and show a message with Swal, if returns 500 ocurred an error and show the message with Swal too, or sucees return the list and fill my table
getValuesDB() {
if (this.valuesForm.valid) {
this.service.consultValues(this.endDate, this.endDate).subscribe({
next: data => {
this.modelConsultValues=data;
},
error: (err) => console.error(err),
complete: () => console.log('Completed')
});
} else {
Swal.fire({
icon: 'error',
text: 'You must select both dates'
})
}
}
anyone helps me please, how can I validate the response of my java service and show the table fill or the messages
This is highly opinionated but I will post a link anyway. class-validator let's you spice up models with decorators and then validate the instances.
https://github.com/typestack/class-validator
I have a multi-module system, where one module handles my database storage. This is the method which saves a document:
public CompletableFuture<?> runTransaction() {
return CompletableFuture.runAsync(() -> {
TransactionBody txnBody = (TransactionBody<String>) () -> {
MongoCollection<Document> collection = transaction.getDatabase().getCollection(transaction.getCollection().toString());
collection.insertOne(session, Document.parse(json));
return "Completed";
};
try {
session.withTransaction(txnBody);
} catch (MongoException ex) {
throw new UncheckedMongoException(ex);
}
});
}
the json instance is passed down in the object constructor. However, since this will be used by several modules, with each individual caching system, I'm trying to figure out how the caller can modify data structure, if this method completed without any errors.
For example
public void createClan(Transaction transaction, int id, int maxPlayers) {
MongoTransaction mongoTransaction = (MongoTransaction) transaction;
Clan clan = new Clan(id, maxPlayers);
String json = gson.toJson(clan);
TransactionExecutor executor = new MongoTransactionExecutor(mongoTransaction, json);
executor.runTransaction(); //Returns the completableFuture instance generated by the method. Modify hashmap here.
}
I've tried reading the docs, however it was a bit confusing, any help is appreciated!
As given in the comments, two options can be considered.
First option is to convert the async nature into sync nature using CompletableFuture#get. This way, the code execution is in a blocking context.
public void createClan(Transaction transaction, int id, int maxPlayers) {
MongoTransaction mongoTransaction = (MongoTransaction) transaction;
Clan clan = new Clan(id, maxPlayers);
String json = gson.toJson(clan);
TransactionExecutor executor = new MongoTransactionExecutor(mongoTransaction, json);
try {
Object obj = executor.runTransaction().get();
// HashMap update here
} catch(Exception e) {
//handle exceptions
}
}
Second option is to keep the async nature as is and chain using thenRun (there are many then options available). This way is more a non-blocking context.
public void createClan(Transaction transaction, int id, int maxPlayers) {
MongoTransaction mongoTransaction = (MongoTransaction) transaction;
final Clan clan = new Clan(id, maxPlayers);
String json = gson.toJson(clan);
TransactionExecutor executor = new MongoTransactionExecutor(mongoTransaction, json);
try {
executor.runTransaction().thenRun(() -> updateHashMap(clan));
} catch(Exception e) {
//handle exceptions
}
}
I need help writing a unit test for class NotificationHandler. so I made NotificationHandlerTest (using junit4) but I don't know how to determine what I should expect as a result versus what the actual result is, so one or more simple test's (for some of its methods) would help me a lot!
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.integration.annotation.Poller;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.messaging.Message;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.stream.Collectors;
#Component
class NotificationHandler {
private static Logger LOG = LoggerFactory.getLogger(NotificationHandler.class);
#Autowired
private NotificationRoutingRepository routingRepository;
#Autowired
private SendNotificationGateway gateway;
#Autowired
private AccessService accessService;
#Autowired
private EndpointService endpointService;
#ServiceActivator(inputChannel = Channels.ASSET_MODIFIED_CHANNEL, poller = #Poller("assetModifiedPoller"), outputChannel = Channels.NULL_CHANNEL)
public Message<?> handle(Message<EventMessage> message) {
final EventMessage event = message.getPayload();
LOG.debug("Generate notification messages: {}, {}", event.getOriginType(), event.getType());
routingRepository.findByOriginTypeAndEventType(event.getOriginType(), event.getType()).stream()
.filter(routing -> routing.getOriginId() == null || routing.getOriginId() == event.getOriginId())
.map(routing -> getNotificationMessages(event, routing))
.flatMap(List::stream)
.forEach(notificationMessage -> {
LOG.debug("Sending message {}", notificationMessage);
gateway.send(notificationMessage);
});
return message;
}enter code here
enter code here`enter code here`
private List<NotificationMessage> getNotificationMessages(EventMessage event, NotificationRouting routing) {
switch (routing.getDestinationType()) {
case "USERS":
LOG.trace("Getting endpoints for users");
return getEndpointsByUsers(routing, event.getOrigin(), event.getOriginType()).stream()
.map(endpoint -> new NotificationMessage(event.getOriginType(), event.getOrigin(), endpoint))
.collect(Collectors.toList());
default:
LOG.trace("Getting default endpoints");
return getEndpoints(routing, event.getOrigin(), event.getOriginType()).stream()
.map(endpoint -> new NotificationMessage(event.getOriginType(), event.getOrigin(), endpoint))
.collect(Collectors.toList());
}
}
private List<Endpoint> getEndpoints(NotificationRouting routing, Object origin, String originType) {
final Asset asset = getAssetForObject(origin, originType);
final List<Long> userIds = accessService.list(asset).stream()
.map(ResourceAccess::getUser)
.map(AbstractEntity::getId)
.collect(Collectors.toList());
userIds.add(asset.getCreatorId());
LOG.trace("getEndpoints usersIds {}", userIds);
final List<Endpoint> endpoints = endpointService.getEndpoints(userIds, routing.getEndpointType());
LOG.trace("Endpoints {}", endpoints.stream().map(Endpoint::getId).collect(Collectors.toList()));
return endpoints;
}
private List<Endpoint> getEndpointsByUsers(NotificationRouting routing, Object origin, String originType) {
final Asset asset = getAssetForObject(origin, originType);
final List<Long> userIds = accessService.list(asset).stream()
.map(ResourceAccess::getUser)
.map(AbstractEntity::getId)
.filter(routing.getDestinations()::contains)
.collect(Collectors.toList());
routing.setDestinations(userIds);
routingRepository.save(routing);
LOG.trace("getEndpointsByUsers usersIds {}", userIds);
final List<Endpoint> endpoints = endpointService.getEndpoints(userIds, routing.getEndpointType());
LOG.trace("Endpoints {}", endpoints.stream().map(Endpoint::getId).collect(Collectors.toList()));
return endpoints;
}
private Asset getAssetForObject(Object origin, String originType) {
switch (originType) {
case EventMessage.POINT:
return (Point) origin;
case EventMessage.FEED:
return ((Feed) origin).getPoint();
case EventMessage.ACTUATOR:
return ((Actuator)origin).getPoint();
case EventMessage.DEVICE:
return (Device) origin;
case EventMessage.ALARM:
return ((Alarm) origin).getPoint();
default:
throw new IllegalArgumentException("Unsupported type: " + originType);
}
}
}
I'd say you start with a simple test if you're not sure what to test. One test that verifies you don't get any exception if you send null as an argument.
E.g.
#Test
public void shouldNotThrowAnyExceptionIfArgumentIsNull() {
// given
NotificationHandler handler = new NotificationHandler();
// when
handler.handle(null);
// then no exception is thrown.
}
After that, you can analyze line by line what the method handle is doing and write tests that verify its behavior.
You can, for example, verify that the method gateway.send(...); was executed or not depending on what you sent in the parameter.
For dependency mocking and behavior verification, I'd recommend you use mockito or a similar tool.
You can follow this tutorial to learn how to do it.