How can I subscribe to incoming SSE events using Spring - java

I wrote a Spring RestController that returns a SseEmitter (for server-sent-event), and adds HATEOAS links to each event. Here is a simplified but working example of this controller:
package hello;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn;
import hello.Greeting.Status;
import java.io.IOException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitter;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
#RestController
public class GreetingController {
private static final Logger log = LoggerFactory.getLogger(GreetingController.class);
private static final String template = "Hello, %s!";
class GreetingRequestHandler implements Runnable {
private ResponseBodyEmitter emitter;
private Greeting greeting;
public GreetingRequestHandler(final ResponseBodyEmitter emitter, final Greeting greeting) {
this.emitter = emitter;
this.greeting = greeting;
}
#Override
public void run() {
try {
log.info(this.greeting.toString());
this.emitter.send(this.greeting);
Thread.sleep(5000);
if (Status.COMPLETE.equals(this.greeting.getStatus())) {
this.emitter.complete();
} else {
this.greeting.incrementStatus();
new Thread(new GreetingRequestHandler(this.emitter, this.greeting)).start();
}
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}
}
#RequestMapping(path = "/greeting")
public SseEmitter greeting(#RequestParam(value = "name", defaultValue = "World") final String name) {
SseEmitter emitter = new SseEmitter();
Greeting greeting = new Greeting(String.format(template, name));
greeting.add(linkTo(methodOn(GreetingController.class).greeting(name)).withSelfRel());
new Thread(new GreetingRequestHandler(emitter, greeting)).start();
log.info("returning emitter");
return emitter;
}
}
The Greeting class is the following:
package hello;
import java.util.concurrent.atomic.AtomicInteger;
import org.springframework.hateoas.ResourceSupport;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
#JsonIgnoreProperties(ignoreUnknown = true)
public class Greeting extends ResourceSupport {
private final String content;
private final static AtomicInteger idProvider = new AtomicInteger();
private int greetingId;
private Status status;
enum Status {
ENQUEUED,
PROCESSING,
COMPLETE;
}
#JsonCreator
public Greeting(#JsonProperty("content") final String content) {
this.greetingId = idProvider.addAndGet(1);
this.status = Status.ENQUEUED;
this.content = content;
}
public Status getStatus() {
return this.status;
}
protected void setStatus(final Status status) {
this.status = status;
}
public int getGreetingId() {
return this.greetingId;
}
public String getContent() {
return this.content;
}
#Override
public String toString() {
return "Greeting{id='" + this.greetingId + "', status='" + this.status + "' content='" + this.content + "', " + super.toString() + "}";
}
public void incrementStatus() {
switch (this.status) {
case ENQUEUED:
this.status = Status.PROCESSING;
break;
case PROCESSING:
this.status = Status.COMPLETE;
break;
default:
break;
}
}
}
This code works perfectly. If I try to reach the REST service using a web browser, I see events appearing with correct content and link.
The result looks like (each event appearing 5 seconds after the previous one):
data:{"content":"Hello, Kraal!","greetingId":8,"status":"ENQUEUED","_links":{"self":{"href":"http://localhost:8080/greeting?name=Kraal"}}}
data:{"content":"Hello, Kraal!","greetingId":8,"status":"PROCESSING","_links":{"self":{"href":"http://localhost:8080/greeting?name=Kraal"}}}
data:{"content":"Hello, Kraal!","greetingId":8,"status":"COMPLETE","_links":{"self":{"href":"http://localhost:8080/greeting?name=Kraal"}}}
Now I need to call this REST service and read these events from another Spring application... But I have no clue how to write the client code using Spring. This does not work as RestTemplate is designed for synchronous client side HTTP access...
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper.registerModule(new Jackson2HalModule());
// required for HATEOAS
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
converter.setSupportedMediaTypes(MediaType.parseMediaTypes("application/hal+json"));
converter.setObjectMapper(mapper);
// required in order to be able to read serialized objects
MappingJackson2HttpMessageConverter converter2 = new MappingJackson2HttpMessageConverter();
converter2.setSupportedMediaTypes(MediaType.parseMediaTypes("application/octet-stream"));
converter2.setObjectMapper(mapper);
// required to understand SSE events
MappingJackson2HttpMessageConverter converter3 = new MappingJackson2HttpMessageConverter();
converter3.setSupportedMediaTypes(MediaType.parseMediaTypes("text/event-stream"));
List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
converters.add(converter);
converters.add(converter2);
converters.add(converter3);
// probably wrong template
RestTemplate restTemplate = new RestTemplate();
restTemplate = new RestTemplate(converters);
// this does not work as I receive events and no a single object
Greeting greeting = restTemplate.getForObject("http://localhost:8080/greeting/?name=Kraal", Greeting.class);
log.info(greeting.toString());
The error message I get is:
Caused by: com.fasterxml.jackson.core.JsonParseException: Unrecognized token 'data': was expecting ('true', 'false' or 'null')
Indeed each event is a SSE event and starts with "data:"...
So the questions are:
what ObjectMapper module should I register in order to be able to map SSE with Jackson ?
how can I subscribe to incoming SSE events (observer pattern) using Spring ?
Thanks in advance.
Side note: As I'm struggling doing it using Spring I tried to do it using Jersey SSE support as follows. Using Jersey I receive events as expected, but then I can't cast them to a Greeting class (for the same reason as above I guess which is that I don't have the right converter module .):
Client client = ClientBuilder.newBuilder().register(converter).register(SseFeature.class).build();
WebTarget target = client.target("http://localhost:8080/greeting/?name=Kraal");
EventInput eventInput = target.request().get(EventInput.class);
while (!eventInput.isClosed()) {
final InboundEvent inboundEvent = eventInput.read();
if (inboundEvent == null) {
// connection has been closed
break;
}
// this works fine and prints out events as they are incoming
System.out.println(inboundEvent.readData(String.class));
// but this doesn't as no proper way to deserialize the
// class with HATEOAS links can be found
// Greeting greeting = inboundEvent.readData(Greeting.class);
// System.out.println(greeting.toString());
}

as per the documentation
you can use inboundEvent.readData(Class<T> type)

Related

ResponseEntity is returning null when imposing class object

I am new to Spring boot and sorry in case it's very basic but I am posting as I have tried other ways and checked similar threads as well.
If I use below code it's returning correct response
ResponseEntity<String> responseEntityString = restTemplate.exchange(url, HttpMethod.GET, requestEntity, String.class);
Output
[{"Id":"123aa","TenId":5198,"Name":"test","Description":"test11","Tags":[]}]
Now I have created workspace class like below (getter/setter/arg constructor and no-arg construcntor is also there)
public class Workspace {
private String Id;
private String TenId;
private String Name;
private String Description;
private List<String> Tags;
}
Now I execute the below code -
ResponseEntity<List<Workspace>> response = restTemplate.exchange(
url,
HttpMethod.GET,
requestEntity,
new ParameterizedTypeReference<List<Workspace>>(){});
List<Workspace> employees = response.getBody();
employees.stream().forEach(entry -> System.out.println(entry.getId() + ": " + entry.getName()));
It's returning
null: null
Below is returning true
System.out.println("Value "+ response.hasBody());
Below is returning - New Values [com.pratik.model.Workspace#3cbf1ba4]
New Values [com.pratik.model.Workspace#3cbf1ba4]
So please advise what needs to change to get the values
================================================================
Initialized resttemplate bean like below
public class app1 {
static RestTemplate restTemplate = new RestTemplate();
static String url = url;
public static void main(String[] args) {
SpringApplication.run(app1.class, args);
getCallSample();
}
===============================================================
Update on the latest code
ResponseEntity<Workspace[]> responseNew = restTemplate
.exchange(
url,
HttpMethod.GET,
requestEntity,
Workspace[].class);
Workspace [] employees1 = responseNew.getBody();
List<Workspace> list = Arrays.asList(employees1);
list.stream().forEach(entry -> System.out.println(entry.getId() + ": " + entry.getName()));
Still the response is
null: null
===============================================================
Another update
When tried with String.class it's returning
[{"Id":"abc","TenId":11,"Name":"tt1 Workspace","Description":"testtenant Workspace (System Generated)","Tags":[]}]
But when using workspace class - it's returning -
[Id=null, TenId=null, Name=null, Description=null, Tags=null, getId()=null, getTenId()=null, getName()=null, getDescription()=null, getTags()=null]
So is using Workspace[].class would be the right method ?
Replace your static RestTemplate restTemplate = new RestTemplate(); variable for a real bean:
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.http.converter.json.MappingJacksonHttpMessageConverter;
import org.springframework.web.client.RestTemplate;
#Configuration
public class app1 {
//remove this variable
//static RestTemplate restTemplate = new RestTemplate();
static String url = "your_url";
public static void main(String[] args) {
SpringApplication.run(app1.class, args);
//getCallSample();
}
//create a proper RestTemplate bean
#Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
//add a converter so you can unmarshall the json content
MappingJacksonHttpMessageConverter converter = new MappingJacksonHttpMessageConverter();
//this is an example to set an ObjectMapper instance
//you can define a bean to configure the ObjectMapper
//with specific details like avoid unmarshalling unknown fields
converter.setObjectMapper(new ObjectMapper());
restTemplate.getMessageConverters().add(converter);
return restTemplate;
}
}
Now, in the method you're using the rest template. Get it from Spring's application context rather than using your own static bean. Example:
#Component
public class WorkspaceService {
private final RestTemplate restTemplate;
public WorkspaceService(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
public List<Workspace> getWorkspaces() {
ResponseEntity<List<Workspace>> response = restTemplate.exchange(
url,
HttpMethod.GET,
requestEntity,
new ParameterizedTypeReference<List<Workspace>>(){});
List<Workspace> employees = response.getBody();
employees.stream().forEach(entry -> System.out.println(entry.getId() + ": " + entry.getName()));
return employees;
}
}
Now you can use this bean in your components. For example, if you want to use it in main class:
#Configuration
public class app1 {
static String url = "your_url";
public static void main(String[] args) {
ApplicationContext ctx = SpringApplication.run(app1.class, args);
WorkspaceService ws = ctx.getBean(WorkspaceService.class);
ws.getWorkspaces();
}
//create a proper bean
#Bean
public RestTemplate restTemplate() {
/* code from above... */
}
}
The issue is resolved by changing the Pojo class (used this to get the class https://json2csharp.com/code-converters/json-to-pojo) to
public class Root{
#JsonProperty("Id")
public String id;
#JsonProperty("TenantId")
public int tenantId;
#JsonProperty("Name")
public String name;
#JsonProperty("Description")
public String description;
#JsonProperty("Tags")
public ArrayList<Object> tags;
}
and the code is used
ResponseEntity<Workspace[]> responseNew = restTemplate
.exchange(
url,
HttpMethod.GET,
requestEntity,
Workspace[].class);
Workspace [] employees1 = responseNew.getBody();
list.stream().forEach(entry -> System.out.println(entry.getDescription()+": "+ entry.getId() + ": " + entry.getName()));
Thanks for the responses , got to learn a lot from the answers

How to manipulate data form microservice response in Java, Spring Boot [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 1 year ago.
Improve this question
I am working on a project in Spring Boot that has a microservice architecture. I needed to make a call from one microservice to another and with data that I get as response do something. I am very new to all microservice architecture and Spring Boot, so I figured I need a small push :)
So I have this class:
HttpDataClient.java
public class HttpDataClient implements DataClient{
private final static Logger LOGGER = LoggerFactory.getLogger(HttpDataClient.class);
private final RestTemplate restTemplate;
private final ObjectMapper objectMapper = new ObjectMapper();
public HttpDataClient(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
#Override
public DataResponse getData(String dataId) {
try{
JsonNode node = restTemplate.exchange(
String.format("/data/%s", dataId),
HttpMethod.POST,
new HttpEntity<>(buildRequest(dataId), headers()),
JsonNode.class
).getBody();
return dataResponse(node);
}catch (HttpStatusCodeException e) {
String msg = String.format(
"Error getting data for dataId: %s",
dataId,
e.getStatusCode(),
e.getResponseBodyAsString());
LOGGER.error(msg);
return dataResponse.failed();
}
}
private MultiValueMap<String, String> headers() {
final LinkedMultiValueMap<String, String> mv = new LinkedMultiValueMap<>();
mv.set(HttpHeaders.CONTENT_TYPE, "application/json");
return mv;
}
private DataResponse dataResponse(JsonNode node) {
return DataResponse.dataResponse(
asString(node, "dataId"),
asString(node, "author"),
asString(node, "authorDataId"),
asString(node, "serverSideDataId")
);
}
private JsonNode buildRequest(String dataId) {
ObjectNode root = objectMapper.createObjectNode();
root.put("dataId", dataId);
return root;
}
}
And the interface
public interface DataClient {
DataResponse getData(String dataId);
}
And from this class when I get response I should be able to do next if author is "Philadelphia" then athorDataId and serverSideId are the same and if author is not "Philadelphia" then athorDataId and serverSideId are not the same.
For now, I have created this class:
public class DataResolver {
private final HttpDataClient client;
public DataResolver(HttpDataClient client) {
this.client = client;
}
}
And in this class, I should execute all of this:
And from this class when I get response I should be able to do next if author is "Philadelphia" then athorDataId and serverSideId are the same and if author is not "Philadelphia" then athorDataId and serverSideId are not the same.
But I don't know how to start. I know I suppose to use client to get the response data. I am not sure how to use it.. And then I should probably do something like this :
if (author == Philadelphia) {
authorDataId == serverSideDataId
} elseif(author != Philadelphia) {
authorDataId != serverSideDataId
}
UPDATE
Is it possible to create new class like this
public class DataResolver {
private final HttpDataClient client;
public DataResolver(HttpDataClient client) {
this.client = client;
}
}
And in this class access data from response and manipulate it in some way?
Something like this:
public class DataResolver {
private final HttpDataClient client;
public DataResolver(HttpDataClient client) {
this.client = client;
}
public DataIdResolver idResolver() {
if (author == Philadelphia) {
authorDataId == serverSideDataId
} elseif(author != Philadelphia) {
authorDataId != serverSideDataId
}
}
}
If the response that you expect should contain the mentioned attributes, then you should create the following class and use it as the return type in restTemplate.exchange instead of JsonNode.class (assuming you have Jackson in your classpath):
public class DataClient {
private String dataId;
private String author;
private String authorDataId;
private String serverSideDataId;
// getters and setters
}
So you would have something like in the HttpDataClient class:
#Override
public DataClient getData(String dataId) {
try{
DataClient data = restTemplate.exchange(
String.format("/data/%s", dataId),
HttpMethod.POST,
new HttpEntity<>(buildRequest(dataId), headers()),
DataClient.class).getBody();
return data;
}catch (HttpStatusCodeException e) {
String msg = String.format(
"Error getting data for dataId: %s",
dataId,
e.getStatusCode(),
e.getResponseBodyAsString());
LOGGER.error(msg);
return dataResponse.failed();
}
}
And DataResolver:
#Component
public class DataResolver {
private final HttpDataClient client;
public DataResolver(HttpDataClient client) {
this.client = client;
}
public DataClient idResolver(String dataId) {
DataClient data = client.getData(dataId);
// Whatever logic you need
return data;
}
}
Maybe one important thing here is that you most likely would want to make HttpDataClient a Spring bean by adding the #Service annotation. By doing this you can autowire it to any other Spring bean you need.

Get the request body in play framework using filters and actions.(in java)

Not able to find request body for incoming request in play framework java.
I need to log all the incoming request in play framework api. For that, I try to use filters and action compositions. But I am able to get the request body.
I try to use filters and action compositions.
public class LoggingFilter extends EssentialFilter {
private final Executor executor;
#Inject
public LoggingFilter(Executor executor) {
super();
this.executor = executor;
}
#Override
public EssentialAction apply(EssentialAction next) {
return EssentialAction.of(request -> {
long startTime = System.currentTimeMillis();
Accumulator<ByteString, Result> accumulator = next.apply(request);
System.out.println(request);
return accumulator.map(result -> {
long endTime = System.currentTimeMillis();
long requestTime = endTime - startTime;
Logger.info("{} {} took {}ms and returned {}",
request.method(), request.uri(), requestTime, result.status());
return result.withHeader("Request-Time", "" + requestTime);
}, executor);
});
}
}
From this request, I am just able to get boolean whether body is present or not. But not able to get the actual body.
You can get request body and log it as you want in action composition:
import play.Logger;
import play.mvc.Http;
import play.mvc.Result;
import java.util.concurrent.CompletionStage;
public class ClientLogger extends play.mvc.Action.Simple {
public CompletionStage<Result> call(Http.Context context) {
StringBuffer logMessage = new StringBuffer();
logMessage.append(context.request().toString())
.append("\n")
.append("body json: " + context.request().body().asJson().toString());
Logger.info(logMessage.toString());
return delegate.call(context);
}
}

Springboot: How to make Rest Service Controller non-blocking

I have a spring boot application which validates a file for a given client id and returns a json response of validation errors and warnings, while performing load test we noticed most the requests being blocked, so am trying to make our application non blocking by leveraging Spring's non blocking api
Below is my spring version
springBootVersion = '1.5.3.RELEASE'
springVersion = '4.3.8.RELEASE'
Below is my springboot ValidationController.groovy which is blocking requests
#Controller
#ResponseBody
class ValidationController {
#RequestMapping(value = "/validate", method = RequestMethod.POST, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
ResponseEntity<ValidationResult> validate(#RequestParam(value = "file", required = true) MultipartFile file,
#RequestParam(value = "client_id", required = true) String clientId)
{
if (clientId.isEmpty()) {
String msg = "client id is required"
if (LOGGER.isErrorEnabled()) {
LOGGER.error(msg)
}
return ResponseEntity.status(HttpStatus.UNPROCESSABLE_ENTITY).body(msg);
}
String contentType = file.contentType.toLowerCase();
if (LOGGER.isDebugEnabled()) LOGGER.debug("content type = $contentType");
Client client = clientRepository.findByExternalId(clientId)
if (client == null) {
String msg = "client id is invalid"
if (LOGGER.isErrorEnabled()) {
LOGGER.error(msg)
}
return ResponseEntity.status(HttpStatus.UNPROCESSABLE_ENTITY).body(msg);
}
if (file.isEmpty()) {
String msg = "file is empty"
if(LOGGER.isErrorEnabled()) {
LOGGER.error(msg)
}
return ResponseEntity.status(HttpStatus.UNPROCESSABLE_ENTITY).body(msg);
}
ValidationResult result = validationService.validate(file, client);
return ResponseEntity.ok(result)
}
}
class ValidationResult {
private List<Warning> warnings
private List<Error> errors
//getters setters for warnings and errors
}
class Warning {
private String message
private String type
//getters setters for message and type
}
class Error {
private String message
private String type
//getters setters for message and type
}
I have modified my ValidationController.groovy as below
#Controller
#ResponseBody
class ValidationController {
#Autowired
#Qualifier("postRequestExecutorService")
private ExecutorService postRequestExecutor;
#RequestMapping(value = "/validate", method = RequestMethod.POST, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public DeferredResult<ResponseEntity<ValidationResult>> validate(#RequestParam(value = "file", required = true) MultipartFile file,
#RequestParam(value = "client_id", required = true) String clientId)
{
DeferredResult<ResponseEntity<ValidationResult>> deferredResult = new DeferredResult<>();
CompletableFuture.supplyAsync(() -> validate(clientId, file), postRequestExecutor)
.whenComplete((result, throwable) ->
{
deferredResult.setResult(result);
} );
}
private ResponseEntity<ValidationResult> validateLedes(String clientId, MultipartFile file) {
ValidationResult result;
try{
if (clientId.isEmpty()) {
String msg = messageSource.getMessage("client.id.required", null, Locale.getDefault())
LOGGER.error(msg)
return ResponseEntity.status(HttpStatus.UNPROCESSABLE_ENTITY).body(msg);
}
String contentType = file.contentType.toLowerCase();
if (LOGGER.isDebugEnabled()) LOGGER.debug("content type = $contentType");
Client client = clientRepository.findByExternalId(clientId)
if (client == null) {
String msg = messageSource.getMessage("client.id.invalid", null, Locale.getDefault())
LOGGER.error(msg)
return ResponseEntity.status(HttpStatus.UNPROCESSABLE_ENTITY).body(msg);
}
if (file.isEmpty()) {
String msg = messageSource.getMessage("ledes.file.empty", null, Locale.getDefault())
LOGGER.error(msg)
return ResponseEntity.status(HttpStatus.UNPROCESSABLE_ENTITY).body(msg);
}
result = validationService.validate(file, Ledesxmlebilling21.class, client);
}
catch (Exception ex){
LOGGER.error("Exception in validateLedes = "+ex.message)
LOGGER.error("StackTrace in validateLedes = "+ex.stackTrace)
}
return ResponseEntity.ok(result)
}
}
And below is my ExecutorServiceConfiguration
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
#Configuration
public class RequestsExecuterServiceConfiguration {
/**
* Dedicated Thread Modeling to handle POST Requests
*/
#Bean
public ExecutorService postRequestExecutorService() {
final ThreadFactory threadFactory = new ThreadFactoryBuilder()
.setNameFormat("postRequestExecutor-%d")
.setDaemon(true)
.build();
ExecutorService es = Executors.newFixedThreadPool(10,threadFactory);
return es;
}
}
Since my controller is a groovy class am seeing some compiler errors for the CompletableFuture lambda expression, can someone please help me make it work for groovy controller?
UPDATE1
As per the Answer I've changed the labda expression to closure as below
#RequestMapping(value = "/validate", method = RequestMethod.POST, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public DeferredResult<ResponseEntity<ValidationResult>> validate(#RequestParam(value = "file", required = true) MultipartFile file,
#RequestParam(value = "client_id", required = true) String clientId)
{
DeferredResult<ResponseEntity<ValidationResult>> deferredResult = new DeferredResult<ResponseEntity<ValidationResult>>();
CompletableFuture.supplyAsync({ -> validateLedes(clientId, file) }, postRequestExecutor)
.whenComplete({ futureResult, throwable -> deferredResult.setResult(futureResult);})
deferredResult
}
With the above controller, am not getting below errors
2018-04-11 15:07:45 - Exception in validateLedes = failed to lazily initialize a collection of role: com.validation.entity.Client.ruleConfigurations, could not initialize proxy - no Session
2018-04-11 15:07:45 - StackTrace in validateLedes = org.hibernate.collection.internal.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:587), org.hibernate.collection.internal.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection.java:204), org.hibernate.collection.internal.AbstractPersistentCollection.readSize(AbstractPersistentCollection.java:148), org.hibernate.collection.internal.PersistentSet.size(PersistentSet.java:143)
Looks like the issue is, Hibernate session is not bound to ExecutorService and the new thread in which validateLedes method is executing it's unable to read from the database, can someone please me to binding Hibernate session to the the ExecutorService's thread pool?
You can't just stick lambdas into Groovy (until Groovy 3)
You'll need to translate them to Closures, so for example:
() -> validate(clientId, file)
becomes:
{ -> validate(clientId, file) }
And
(result, throwable) ->
{
deferredResult.setResult(result);
}
would be:
{ result, throwable -> deferredResult.setResult(result) }

How to create a oAuth request using java?

I need to make a connection with Viagogo website using oAuth. Referring to their documentation I need to create a request similar to the following one
Using the example in step 1A, this means you may generate a signature base string that looks like the following:
GET&http%3A%2F%2Fapi.viagogo.net%2FPublic%2FSimpleOAuthAccessRequest&oauth_consumer_key%3Dtestkey%26oauth_nonce%3Dmyn0nc3%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1292404912%26oauth_version%3D1.0%26scope%3DAPI.Public
I am using the following code but when I comment lines 1,2 it return unauthorized error, and when I use them it shows oauthService.signRequest returns void.
TradeKingAPI.java
import org.scribe.builder.api.DefaultApi10a;
import org.scribe.model.Token;
public class TradeKingAPI extends DefaultApi10a {
#Override
public String getRequestTokenEndpoint() {
return "http://api.viagogo.net/Public/SimpleOAuthAccessRequest";
}
#Override
public String getAccessTokenEndpoint() {
return "http://api.viagogo.net/Public/SimpleOAuthAccessRequest";
}
#Override
public String getAuthorizationUrl(Token requestToken) {
return "http://api.viagogo.net/Public/SimpleOAuthAccessRequest";
}
}
main.java
import org.scribe.builder.ServiceBuilder;
import org.scribe.model.OAuthRequest;
import org.scribe.model.Response;
import org.scribe.model.Token;
import org.scribe.model.Verb;
import org.scribe.oauth.OAuthService;
import api.TradeKingAPI;
import org.scribe.builder.api.DefaultApi10a;
import org.scribe.model.OAuthConstants;
import org.scribe.oauth.OAuthService;
........
OAuthService oauthService = new ServiceBuilder()
.provider(TradeKingAPI.class)
.apiKey("My consumer key")
.apiSecret("My secret")
.scope("API.Public")
.build();
Long seconds = (System.currentTimeMillis() / 1000);
System.out.println(">>>" + seconds);
String stSeconds = seconds.toString();
OAuthRequest request = new OAuthRequest(Verb.GET, "http://api.viagogo.net/Public
/SimpleOAuthAccessRequest");
request.addOAuthParameter(OAuthConstants.CONSUMER_KEY, "My consumer key");
request.addOAuthParameter(OAuthConstants.NONCE, "myn0nc3");
request.addOAuthParameter(OAuthConstants.SIGN_METHOD, "HMAC-SHA1");
request.addOAuthParameter(OAuthConstants.TIMESTAMP, seconds.toString());
request.addOAuthParameter(OAuthConstants.VERSION, "1.0");
request.addOAuthParameter("scope", "API.Public");
1 String signature = oauthService.signRequest(OAuthConstants.EMPTY_TOKEN, request);
2 request.addOAuthParameter(OAuthConstants.SIGNATURE,signature);
Response response = request.send();
System.err.println(">>" + response.isSuccessful());
System.err.println(">>" + response.getMessage());
System.err.println(">>" + response.getBody());
From what I understand from Viagogo public API access documentation, the token you get in the step 1, is the equivalent to a request token in a complete OAuth 1.0a "dance".
So, you should be able to use scribe-java internal classes to get this token without doing it by hand. The only difference is that in scribe, this request sends also a callback url to the OAuth server for the next step of OAuth "dance".
As I can't get a consumer account I can only make assumption here. So let's have 2 scenarios :
Scenario 1 : Viagogo server tolerate extra parameter (i.e. call back URL)
so you can go with this code
import org.scribe.builder.api.DefaultApi10a;
import org.scribe.model.Token;
public class TradeKingAPI extends DefaultApi10a {
#Override
public Verb getRequestTokenVerb()
{
return Verb.GET;
}
#Override
public String getRequestTokenEndpoint() {
return "http://api.viagogo.net/Public/SimpleOAuthAccessRequest";
}
#Override
public String getAccessTokenEndpoint() {
return "none";
}
#Override
public String getAuthorizationUrl(Token requestToken) {
return "none";
}
}
Then your calling code will be :
OAuthService service = new ServiceBuilder()
.provider(TradeKingAPI.class)
.signatureType(QueryString)
.apiKey("My consumer key")
.apiSecret("My secret")
.scope("API.Public")
.build();
Token requestToken = service.getRequestToken();
//make your API calls
OAuthRequest request = new OAuthRequest(Verb.GET,
"http://api.viagogo.net/Public/Event/235");
service.signRequest(requestToken, request);
Response response = request.send();
System.out.println(response.getBody());
But as I said, if Viagogo security is a bit strict and it refuses the useless param oauth_callback, you'll need to switch to scenario 2
Scenario 2 : Build your own OAuthService
In this scenario you have to create a new OAuthService to avoid dealing with OAuthCallback parameter.
import org.scribe.builder.api.DefaultApi10a;
import org.scribe.model.*;
import org.scribe.oauth.OAuth10aServiceImpl;
import java.util.Map;
public class OAuth10aServiceForViagogo extends OAuth10aServiceImpl {
private OAuthConfig config;
private DefaultApi10a api;
public OAuth10aServiceForViagogo(DefaultApi10a api, OAuthConfig config) {
super(api, config);
this.api = api;
this.config = config;
}
private void addOAuthParams(OAuthRequest request, Token token) {
request.addOAuthParameter(OAuthConstants.TIMESTAMP, api.getTimestampService().getTimestampInSeconds());
request.addOAuthParameter(OAuthConstants.NONCE, api.getTimestampService().getNonce());
request.addOAuthParameter(OAuthConstants.CONSUMER_KEY, config.getApiKey());
request.addOAuthParameter(OAuthConstants.SIGN_METHOD, api.getSignatureService().getSignatureMethod());
request.addOAuthParameter(OAuthConstants.VERSION, getVersion());
request.addOAuthParameter(OAuthConstants.SCOPE, config.getScope());
request.addOAuthParameter(OAuthConstants.SIGNATURE, getSignature(request, token));
}
private String getSignature(OAuthRequest request, Token token) {
String baseString = api.getBaseStringExtractor().extract(request);
String signature = api.getSignatureService().getSignature(baseString, config.getApiSecret(), token.getSecret());
return signature;
}
private void appendSignature(OAuthRequest request) {
for (Map.Entry<String, String> entry : request.getOauthParameters().entrySet()) {
request.addQuerystringParameter(entry.getKey(), entry.getValue());
}
}
#Override
public Token getRequestToken(RequestTuner tuner) {
OAuthRequest request = new OAuthRequest(api.getRequestTokenVerb(), api.getRequestTokenEndpoint());
addOAuthParams(request, OAuthConstants.EMPTY_TOKEN);
appendSignature(request);
Response response = request.send(tuner);
String body = response.getBody();
return api.getRequestTokenExtractor().extract(body);
}
}
TrakingApi class will be slightly different to create the an OAuth10aServiceForViagogo when calling createService :
import org.scribe.builder.api.DefaultApi10a;
import org.scribe.model.Token;
public class TradeKingAPI extends DefaultApi10a {
#override
public OAuthService createService(OAuthConfig config)
{
return new OAuth10aServiceForViagogo(this, config);
}
#Override
public Verb getRequestTokenVerb()
{
return Verb.GET;
}
#Override
public String getRequestTokenEndpoint() {
return "http://api.viagogo.net/Public/SimpleOAuthAccessRequest";
}
#Override
public String getAccessTokenEndpoint() {
return "none";
}
#Override
public String getAuthorizationUrl(Token requestToken) {
return "none";
}
}
Then your calling code will be the same :
OAuthService service = new ServiceBuilder()
.provider(TradeKingAPI.class)
.signatureType(QueryString)
.apiKey("My consumer key")
.apiSecret("My secret")
.scope("API.Public")
.build();
Token requestToken = service.getRequestToken();
//make your API calls
OAuthRequest request = new OAuthRequest(Verb.GET,
"http://api.viagogo.net/Public/Event/235");
service.signRequest(requestToken, request);
Response response = request.send();
System.out.println(response.getBody());
I didn't test all this code because I can't access consumer and secret key, but it should be close to what you need.
I'm assuming you're trying to get the access token (e.g you're calling SimpleOAuthAccessRequest). Scribe's OauthService has methods to handle this.
BUT ... if you're going to do it manually, here is what's wrong with your code - at least with what you've listed here. I'm assuming you've configured scribe correctly.
don't pass the consumer secret with your request, that is only for signing the request
you should use addOauthParameter vs addQueryStringParameter
you should use the Scribe constants
you need to sign the request (again, Scribe's OauthService has help method for signing request)
Here's your updated snippet of code.
UPDATE:
Have Scribe provide all the Oauth parameters for you
OAuthRequest request = new OAuthRequest(Verb.GET, ...
//since you're just passing Oauth parameters and nothing else,
//you can use signRequest will create Oauth Parameters for you
service.signRequest(OAuthConstants.EMPTY_TOKEN, request)
Response response = request.send()

Categories