How to implement the observer pattern for REST API's? - spring

I'm looking to create a REST API to which clients subscribe to certain data. When the data changes (due to some external event) I want to notify the clients (observers) with the new data.
I want to use Spring for the REST API's, I have no clue how to register and notify the observers though.
Some guidance and or good practises would be very helpful.
Thank you

In spring boot you can register call back urls, an example controller is:
#RestController
public class Controller {
private List<Listener> listeners = new ArrayList<>();
#RequestMapping(value = "/register/{name}", method = RequestMethod.POST)
public ResponseEntity<Void> register(#PathVariable("name") String name, #RequestParam("callbackurl") String callBackUrl) throws Exception {
System.out.println("register, name=" + name + ", callBackUrl=" + callBackUrl);
Listener listener = new Listener(name, URLDecoder.decode(callBackUrl, "UTF-8"));
listeners.add(listener);
System.out.println(listener);
return new ResponseEntity<>(HttpStatus.OK);
}
#RequestMapping(value = "/callback/*", method = RequestMethod.POST)
public ResponseEntity callBack(#RequestBody String message) {
System.out.println("call back with message=" + message);
return new ResponseEntity(HttpStatus.OK);
}
#Scheduled(fixedRate = 10000)
public void notifyListeners() {
System.out.println("notifying listeners");
for (Listener listener : listeners) {
System.out.println("listener " + listener);
CloseableHttpClient client = HttpClients.createDefault();
HttpPost httpPost = new HttpPost(listener.getCallBackUrl());
try {
httpPost.setEntity(new StringEntity("hello listener " + listener));
CloseableHttpResponse response = client.execute(httpPost);
client.close();
} catch (Exception e) {
}
}
}
}
Can be tested like so, register 2 call backs, the URL http://127.0.0.1:8080/callback/app1 is encoded so it can be a paramter.
curl -X POST http://127.0.0.1:8080/register/listener1?callbackurl=http%3A%2F%2F127.0.0.1%3A8080%2Fcallback%2Fapp1
curl -X POST http://127.0.0.1:8080/register/listener1?callbackurl=http%3A%2F%2F127.0.0.1%3A8080%2Fcallback%2Fapp2
In my case for simplicity the client and server are the same application, but they could be different.

You can use Spring 5 with WebFlux. It's a combination of an Iterator and the Observer pattern. The client always gets a new Object, whenever there is one on the server. You can start learning more on that on the Spring documentation pages or on e.g.
New in Spring 5: Functional Web Framework

Related

meesenger4j how to handle request from diffrent facbook app

I would like to create a rest api that handle user messenger app credential (token,appsecret,verifToken) as parameters instead of define them as env variable.
So that more than one user (facebook app) can subscribe to my rest api throw messenger webhook .
Is that possible?
First, i tried with credential in app.prop and injected the Messenger4j client in Restcontroller constructor and it works like charm (webhook call, conversation...).
Now is it possible to do that for more than one facebook app to communicate with my rest api :
the logic will be:
first connect(accesToken,appSecret) to our backend and save app credential and get response with myBackendApiUrl and generate verifToken.
#RequestMapping(value = "/connect", method = RequestMethod.POST)
public ResponseEntity<String> connect(#RequestParam final String pageAccessToken,
#RequestParam final String appSecret,
) {
logger.debug(" connect ");
try {
logger.debug("********");
//Messenger messenger = Messenger.create(pageAccessToken, appSecret, verifyToken).;
String verifyToken= UUID.randomUUID().toString();
MessengerCredentials msgerCred = new MessengerCredentials(pageAccessToken,appSecret,verifyToken);
messengerCredentialRepo.save(msgerCred);
return ResponseEntity.ok("webhookurl: myurl"+ "verifToken:"+verifyToken);
} catch (Exception e) {
logger.warn("failed to connect", e.getMessage());
return ResponseEntity.status(HttpStatus.FORBIDDEN).body(e.getMessage());
}
}
After that the user should configure messenger app webhook with url and verif token recived in the response body method connect() to avonke the webhook handler and this is how it may be like
#RequestMapping(method = RequestMethod.GET)
public ResponseEntity<String> verifyWebhook(#RequestParam(MODE_REQUEST_PARAM_NAME) final String mode,
#RequestParam(CHALLENGE_REQUEST_PARAM_NAME) final String challenge,
#RequestParam(VERIFY_TOKEN_REQUEST_PARAM_NAME) final String verifyToken
) {
logger.debug("Received Webhook verification request - mode: {} | verifyToken: {} | challenge: {}", mode, verifyToken, challenge);
try {
logger.debug("********");
this.messenger.verifyWebhook(mode, verifyToken);
return ResponseEntity.ok(challenge);
} catch (MessengerVerificationException e) {
logger.warn("Webhook verification failed: {}", e.getMessage());
return ResponseEntity.status(HttpStatus.FORBIDDEN).body(e.getMessage());
}
}
Is that possible?!
and how can i deal with post handler to handel users events it my Messenger4j bean not instanciate yet.

Testing a Post multipart/form-data request on REST Controller

I've written a typical spring boot application, now I want to add integration tests to that application.
I've got the following controller and test:
Controller:
#RestController
public class PictureController {
#RequestMapping(value = "/uploadpicture", method = RequestMethod.POST)
public ResponseEntity<VehicleRegistrationData> uploadPicturePost(#RequestPart("userId") String userId, #RequestPart("file") MultipartFile file) {
try {
return ResponseEntity.ok(sPicture.saveAndParsePicture(userId, file));
} catch (IOException e) {
logger.error(e.getMessage(), e);
}
return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
}
}
Test:
#Test
public void authorizedGetRequest() throws Exception {
File data = ResourceUtils.getFile(testImageResource);
byte[] bytes = FileUtils.readFileToByteArray(data);
ObjectMapper objectMapper = new ObjectMapper();
MockMultipartFile file = new MockMultipartFile("file", "test.jpg", MediaType.IMAGE_JPEG_VALUE, bytes);
MockMultipartFile userId =
new MockMultipartFile("userId",
"userId",
MediaType.MULTIPART_FORM_DATA_VALUE,
objectMapper.writeValueAsString("123456").getBytes()
);
this.mockMvc.perform(multipart("/uploadPicture")
.file(userId)
.file(file)
.header(API_KEY_HEADER, API_KEY)).andExpect(status().isOk());
}
Testing the controller with the OkHttp3 client on android works seamlessly, but I can't figure out how to make that request work on the MockMvc
I expect 200 as a status code, but get 404 since, I guess, the format is not the correct one for that controller
What am I doing wrong?
It must be a typo.
In your controller, you claim the request URL to be /uploadpicture, but you visit /uploadPicture for unit test.

Spring Boot Camel Route - get data from rest endpoint

I want to create camel route in Spring Boot (2.1.1) project to get the data from some (rest) endpoint (http://localhost:8080/getAllUsers) and to send that data to activeMq.
I have tried with timer data to send it on activeMq and to consume it and it is working. But I have problem with collecting data from endpoint.
I have tried several things but no success. This is what I have tried.
In this example I am not sending the data to ActiveMq, I just want to see the response...
public void createNewRoute() {
CamelContext context = new DefaultCamelContext();
try {
ProducerTemplate template = context.createProducerTemplate();
context.start();
Exchange exchange = template.request("http://localhost:8080/getAllUsers",
new Processor() {
public void process(Exchange exchange) throws Exception {
}
});
if (null != exchange) {
Message out = exchange.getOut();
int responseCode = out.getHeader(Exchange.HTTP_RESPONSE_CODE, Integer.class);
System.out.println("Response: " + String.valueOf(responseCode));
}
Thread.sleep(1000 * 3);
context.stop();
} catch (Exception ex) {
System.out.println("Exception: " + ex);
}
System.out.println("DONE!!");
}
Another route:
from("servlet://localhost:8080/getAllUsers").to("activemq://all-users");
And another:
rest("//localhost:8080/getAllUsers")
.get().consumes("application/json")
.to("activemq://all-users");
I will go with your second example:
from("timer://test?repeatCount=1").routeId("newRoute")
.streamCaching()
.process(exchange -> exchange.getIn()
.setBody(exchange.getIn()
.getBody()))
.marshal()
.json(JsonLibrary.Jackson)
.setHeader(Exchange.HTTP_METHOD, constant("GET"))
.setHeader(Exchange.CONTENT_TYPE, constant("application/json"))
.to("http://localhost:8080/getAllUsers")
.log(LoggingLevel.INFO, "This is my body: ${body}")
.to("activemq:queue://new-queue");
This will trigger it once.
Try this without context.start() ....
CamelContext camelContext = new DefaultCamelContext();
ProducerTemplate template = camelContext.createProducerTemplate();
Exchange exchange = template.send("http://localhost:8080/getAllUsers", new Processor() {
public void process(Exchange exchange) throws Exception {}
});
Message out = exchange.getOut();
The http components are streaming based, so you can ask Camel to give you the response as string instead.
String s = exchange.getMessage().getBody(String.class);
See more in these links
http://camel.apache.org/stream-caching
http://camel.apache.org/why-is-my-message-body-empty.html

Retrotif2 + RxJava sending POST request failed

I want to send POST request with Retrofit + RxJava, but it is failing and I don't know the reason. In one activity it's working, in another - don't want to work:
private void sendMerchantInfo() {
try {
String advertiserOriginalDeepLink = "https://mywebsite.com/main-1?param1=value1&param2=value2";
String urlGetParams = LinkParser.getUrlGETParams(advertiserOriginalDeepLink);
Map<Object, Object> merchantInfo = LinkParser.parseUrlGetParams(urlGetParams);
String merchantInfoJson = new Gson().toJson(merchantInfo); //{"param1":"value1","param2":"value2"}
String url = "https://api.endpoint.com/v1/system/merchant/process";
userService = this.serviceGenerator.createService(UserService.class, true);
final Observable observable = userService.sendUserInfo(
url, new RetrofitMapBody(merchantInfo))
.doOnNext(new Consumer<ResponseBody>() {
#Override
public void accept(ResponseBody responseBody) throws Exception {
//handle 200 OK.
}
})
.onErrorResumeNext((ObservableSource<? extends ResponseBody>) v ->
Crashlytics.log("Send user info attempt failed."))
.subscribeOn(Schedulers.from(threadExecutor))
.observeOn(postExecutionThread.getScheduler());
addDisposable(observable.subscribe());
}
} catch (Exception exception) {
Crashlytics.log("Send user info attempt failed. " + exception.getMessage());
}
}
I suspect that problem in this part, I am trying to send request in OnCreate() method:
.subscribeOn(Schedulers.from(threadExecutor))
.observeOn(postExecutionThread.getScheduler());
Tried to use this, but no effect:
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
What I am doing wrong? It always call onErrorResumeNext() It's probably something with threads because one time I got exception: networkonmainthreadexception. Please help.
Try using RxJava2 Adapter, it will save you a lot!
Step 1: Retrofit client setup
private Retrofit getRetrofitClient() {
return new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create()) //option 1
.addCallAdapterFactory(RxJava2CallAdapterFactory.createWithScheduler(Schedulers.newThread())) //option 2
.build();
}
Step 2: APIService interface (Example)
#GET("endpoint")
Single<ResponseModel> fetch();
Step 3: Usage
Single<ResponseModel> fetch() {
return getRetrofitClient()
.create(APIService.class)
.fetch();
}
Any non-2xx HTTP response will be wrapped in HttpException from which you can extract the status code, the status message and the full HTTP response.
Any connection errors will be wrapped in IOException
And that is all you need to do to wrap your network call in any RxJava stream.

Why this externa web service call go into error only when the call is performed using Spring RestTemplate?

I am working on a Spring project implementing a simple console application that have to call an external REST web service passing to it a parameter and obtaining a response from it.
The call to this webservice is:
http://5.249.148.180:8280/GLIS_Registration/6
where 6 is the specified ID. If you open this address in the browser (or by cURL tool) you will obtain the expected error message:
<?xml version="1.0" encoding="UTF-8"?>
<response>
<sampleid>IRGC 100000</sampleid>
<genus>Oryza</genus>
<error>PGRFA sampleid [IRGC 100000], genus [Oryza] already registered for this owner</error>
</response>
This error message is the expected response for this request and I correctly obtain it also using cURL tool to perform the request.
So I have to perform this GET request from my Spring application.
To do it I create this getResponse() method into a RestClient class:
#Service
#Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
public class RestClient {
RestTemplate restTemplate;
String uriResourceRegistrationApi;
public RestClient() {
super();
restTemplate = new RestTemplate();
uriResourceRegistrationApi = "http://5.249.148.180:8280/GLIS_Registration/7";
}
public ResponseEntity<String> getResponse() {
ResponseEntity<String> response = restTemplate.getForEntity(uriResourceRegistrationApi, String.class);
return response;
}
}
Then I call this method from this test method:
#Test
public void singleResourceRestTest() {
System.out.println("singleResourceRestTest() START");
ResponseEntity<String> result = restClient.getResponse();
System.out.println("singleResourceRestTest() END");
}
But I am experiencing a very strange behavior, what it happens is:
1)The call to my external web service seems that happens (I saw it from the web services log).
2) The web service retrieve the parameter having value 7 but then it seems that can't use it as done without problem performing the request from the browser or by the shell statment:
curl -v http://5.249.148.180:8280/GLIS_Registration/7
But now, calling in this way, my webservice (I can't post the code because it is a WSO2 ESB flow) give me this error message:
<200 OK,<?xml version="1.0" encoding="UTF-8"?>
<response>
<error>Location information not correct</error>
<error>At least one between <genus> and <cropname> is required</error>
<error>Sample ID is required</error>
<error>Date is required</error>
<error>Creation method is required</error>
</response>,{Vary=[Accept-Encoding], Content-Type=[text/html; charset=UTF-8], Date=[Fri, 05 May 2017 14:07:09 GMT], Transfer-Encoding=[chunked], Connection=[keep-alive]}>
Looking the web service log it seems that performing the call using RestTemplate it have some problem to use the retrieved ID=7 to perform a database query.
I know it looks terribly strange and you can see: "The problem is of your web service and not of the Spring RestTemplate". This is only partially true because I implemented this custom method that perform a low level Http GET call, this callWsOldStyle() (putted into the previous RestClient class):
public void callWsOldStyle() {
HttpURLConnection connection = null;
BufferedReader reader = null;
try {
URL restAPIUrl = new URL("http://5.249.148.180:8280/GLIS_Registration/7");
connection = (HttpURLConnection) restAPIUrl.openConnection();
connection.setRequestMethod("GET");
// Read the response
reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
StringBuilder jsonData = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
jsonData.append(line);
}
System.out.println(jsonData.toString());
}catch(Exception e) {
e.printStackTrace();
}
finally {
// Clean up
IOUtils.closeQuietly(reader);
if(connection != null)
connection.disconnect();
}
}
Using this method instead the RestTemplate one it works fine and this line:
System.out.println(jsonData.toString());
print the expected result:
<?xml version="1.0" encoding="UTF-8"?><response><sampleid>IRGC 100005</sampleid><genus>Oryza</genus><error>PGRFA sampleid [IRGC 100005], genus [Oryza] already registered for this owner</error></response>
To summarize:
Calling my WS from the browser it works.
Calling my WS using cURL it works.
Calling my WS using my callWsOldStyle() method it works.
Calling my WS using the method that use RestTemplate it go into error when my WS receive and try to handle the request.
So, what can be the cause of this issue? What am I missing? Maybe can depend by some wrong header or something like this?
As Pete said you are receiving an internal server error (status code 500) so you should check the server side of this rest service.
In any case you can do the following for the resttemplate
create an org.springframework.web.client.RequestCallback object if
you need to do something in the request
create an org.springframework.web.client.ResponseExtractor<String>
object in order to extract your data
use the resttemplate
org.springframework.web.client.RequestCallback
public class SampleRequestCallBack implements RequestCallback
{
#Override
public void doWithRequest(ClientHttpRequest request) throws IOException
{
}
}
org.springframework.web.client.ResponseExtractor
public class CustomResponseExtractor implements ResponseExtractor<String>
{
private static final Logger logger = LoggerFactory.getLogger(CustomResponseExtractor.class.getName());
#Override
public String extractData(ClientHttpResponse response) throws IOException
{
try
{
String result = org.apache.commons.io.IOUtils.toString(response.getBody(), Charset.forName("UTF8"));
if( logger.isInfoEnabled() )
{
logger.info("Response received.\nStatus code: {}\n Result: {}",response.getStatusCode().value(), result);
}
return result;
}
catch (Exception e)
{
throw new IOException(e);
}
}
}
REST TEMPLATE CALL
#Test
public void testStack()
{
try
{
String url = "http://5.249.148.180:8280/GLIS_Registration/6";
String response = restTemplate.execute(url, HttpMethod.GET, new SampleRequestCallBack(), new CustomResponseExtractor());;
logger.info(response);
}
catch (Exception e)
{
logger.error("Errore", e);
}
}
Angelo

Resources