I'm writing Unit test for the below Class Implementation that sends requests to an API the verifies desired Response.
So the Issue I'm facing is how to correctly mock the dependencies within the methods since the corresponding methods depend on the first method that gets an Authentication token.
Kindly assist with any pointers on how to do it correctly and avoid the timeout exception I'm getting.
The class under test
package com.darajaapi.daraja.transactions.services;
import com.darajaapi.daraja.transactions.config.MpesaConfiguration;
import com.darajaapi.daraja.transactions.dtos.*;
import com.darajaapi.daraja.transactions.exceptions.ResponseException;
import com.darajaapi.daraja.transactions.utils.HelperUtility;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import okhttp3.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.io.IOException;
import java.util.Objects;
import static com.darajaapi.daraja.transactions.utils.Constants.*;
#Service
#Slf4j
public class DarajaApiImpl implements DarajaApi {
private MpesaConfiguration mpesaConfiguration;
private OkHttpClient okHttpClient;
private ObjectMapper objectMapper;
public DarajaApiImpl(OkHttpClient okHttpClient, MpesaConfiguration mpesaConfiguration, ObjectMapper objectMapper){
this.okHttpClient = okHttpClient;
this.objectMapper = objectMapper;
this.mpesaConfiguration = mpesaConfiguration;
}
#Override
public AccessTokenResponse getAccessToken() throws ResponseException {
// get the Base64 rep of consumerKey + ":" + consumerSecret
String encodedCredentials = HelperUtility.toBase64String(String.format("%s:%s", mpesaConfiguration.getConsumerKey(),
mpesaConfiguration.getConsumerSecret()));
Request request = new Request.Builder()
.url(String.format("%s?grant_type=%s", mpesaConfiguration.getOauthEndpoint(), mpesaConfiguration.getGrantType()))
.get()
.addHeader(AUTHORIZATION_HEADER_STRING, String.format("%s %s", BASIC_AUTH_STRING, encodedCredentials))
.addHeader(CACHE_CONTROL_HEADER, CACHE_CONTROL_HEADER_VALUE)
.build();
try {
Response response = okHttpClient.newCall(request).execute();
var responseBody = response.body();
var responseString = responseBody != null ? responseBody.string() : null;
if(!StringUtils.hasLength(responseString)){
log.warn("Empty response from Daraja api");
throw new ResponseException("Empty response from Daraja api");
}
// using Jackson to Decode the ResponseBody ...
return objectMapper.readValue(responseString, AccessTokenResponse.class);
} catch (IOException | ResponseException e) {
log.error(String.format("Could not get access token. -> %s", e.getLocalizedMessage()));
throw new ResponseException(e.getLocalizedMessage());
}
}
#Override
public RegisterUrlResponse registerUrl() throws ResponseException {
AccessTokenResponse accessToken = getAccessToken();
RegisterUrlRequest registerUrlRequest = new RegisterUrlRequest();
registerUrlRequest.setShortCode(mpesaConfiguration.getShortcode());
registerUrlRequest.setResponseType(mpesaConfiguration.getResponseType());
registerUrlRequest.setConfirmationURL(mpesaConfiguration.getConfirmationUrl());
registerUrlRequest.setValidationURL(mpesaConfiguration.getValidationUrl());
RequestBody body = RequestBody.create(JSON_MEDIA_TYPE, Objects.requireNonNull(HelperUtility.toJson(registerUrlRequest)));
Request request = new Request.Builder()
.url(mpesaConfiguration.getRegisterUrl())
.post(body)
.addHeader(AUTHORIZATION_HEADER_STRING, String.format("%s %s", BEARER_AUTH_STRING, accessToken.getAccessToken()))
.build();
try {
Response response = okHttpClient.newCall(request).execute();
var responseBody = response.body();
var responseString = responseBody != null ? responseBody.string() : null;
if(!StringUtils.hasLength(responseString)){
log.warn("Empty response from Daraja api");
throw new ResponseException("Empty response from Daraja api");
}
// using Jackson to Decode the ResponseBody ...
return objectMapper.readValue(responseString, RegisterUrlResponse.class);
} catch (IOException e) {
log.error(String.format("Could not register url =>%s", e.getLocalizedMessage()));
return null;
}
}
#Override
public C2bTransactionResponse simulateC2bTransaction(C2bTransactionRequest c2bTransactionRequest) throws ResponseException {
AccessTokenResponse accessTokenResponse = getAccessToken();
RequestBody body = RequestBody.create(JSON_MEDIA_TYPE, Objects.requireNonNull(HelperUtility.toJson(c2bTransactionRequest)));
Request request = new Request.Builder()
.url(mpesaConfiguration.getSimulateC2bTransactionUrl())
.post(body)
.addHeader(AUTHORIZATION_HEADER_STRING, String.format("%s %s", BEARER_AUTH_STRING, accessTokenResponse.getAccessToken()))
.build();
try {
Response response = okHttpClient.newCall(request).execute();
assert response.body() != null;
return objectMapper.readValue(response.body().string(), C2bTransactionResponse.class);
} catch (IOException e) {
log.error(String.format("Could not simulate C2B transaction =>%s", e.getLocalizedMessage()));
return null;
}
}
}
The Tests for the first two methods...
package com.darajaapi.daraja.transactions.services;
import com.darajaapi.daraja.transactions.config.MpesaConfiguration;
import com.darajaapi.daraja.transactions.dtos.AccessTokenResponse;
import com.darajaapi.daraja.transactions.dtos.RegisterUrlResponse;
import com.darajaapi.daraja.transactions.exceptions.ResponseException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import okhttp3.OkHttpClient;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import okhttp3.mockwebserver.RecordedRequest;
import org.junit.jupiter.api.*;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
import org.mockito.MockitoAnnotations;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import java.io.IOException;
import java.net.URL;
import java.util.Objects;
#DisplayName("Writing Tests for Daraja Api Impl / service")
#ExtendWith(MockitoExtension.class)
public class DarajaApiImplTest {
private static final String CUSTOMER_KEY = "GPGd8hIoa26OSAOffCzSomeKeyZjGJrfKg9";
private static final String CUSTOMER_SECRET = "fAzSomeSecret3patXe";
private static final String GRANT_TYPE = "client_credentials";
private MockWebServer mockWebServer;
private DarajaApiImpl darajaApiImpl;
#Mock
private MpesaConfiguration mpesaConfiguration;
#Mock
private ObjectMapper objectMapper;
#Mock
private AccessTokenResponse accessTokenResponse;
#Mock
private RegisterUrlResponse registerUrlResponse;
#BeforeEach
void setUp() throws IOException {
mockWebServer = new MockWebServer();
mockWebServer.start();
MockitoAnnotations.openMocks(this);
URL mockServerBaseUrl = mockWebServer.url("").url();
URL tokenUrl = new URL(mockServerBaseUrl, "/token");
when(mpesaConfiguration.getConsumerKey()).thenReturn(CUSTOMER_KEY);
when(mpesaConfiguration.getConsumerSecret()).thenReturn(CUSTOMER_SECRET);
when(mpesaConfiguration.getOauthEndpoint()).thenReturn(String.valueOf(tokenUrl));
when(mpesaConfiguration.getGrantType()).thenReturn(GRANT_TYPE);
accessTokenResponse.setAccessToken("0W0uNniSomeTokengQpO2x23DaG");
accessTokenResponse.setExpiresIn("3599");
darajaApiImpl = new DarajaApiImpl(new OkHttpClient(), mpesaConfiguration, objectMapper);
}
#AfterEach
void tearDown() throws IOException {
mockWebServer.shutdown();
}
#Test
void getAccessTokenMethod_Returns_Token_Test() throws JsonProcessingException, InterruptedException, ResponseException {
final String responseBody = "{\"access_token\":\"0W0uNniCevTmRFV8LgQpO2x\", \"expires_in\":\"3599\"}";
when(objectMapper.readValue(responseBody, AccessTokenResponse.class)).thenReturn(accessTokenResponse);
mockWebServer.enqueue(new MockResponse()
.setBody(responseBody)
.addHeader("Content-Type", "application/json"));
AccessTokenResponse response = darajaApiImpl.getAccessToken();
RecordedRequest recordedRequest = mockWebServer.takeRequest();
System.out.println(response);
assertNotNull(response);
assertEquals("GET", recordedRequest.getMethod());
assertEquals(response.getAccessToken(), accessTokenResponse.getAccessToken());
}
#Test
void getAccessTokenMethod_Throws_Error_When_Response_IsEmpty() {
mockWebServer.enqueue(new MockResponse()
.setBody("")
.addHeader("Content-Type", "application/json"));
final ResponseException thrown = assertThrows(
ResponseException.class,
() -> darajaApiImpl.getAccessToken()
);
assertEquals("Empty response from Daraja api", thrown.getMessage());
}
#Test
void registerUrlMethod_Returns_Success() throws InterruptedException, ResponseException, JsonProcessingException {
final String responseBody = "{\"access_token\":\"0W0uNniCevTmRFV8LgQpO2x\", \"expires_in\":\"3599\"}";
registerUrlResponse.setResponseCode("0");
registerUrlResponse.setResponseDescription("someString");
registerUrlResponse.setOriginatorCoversationID("someId");
URL registerUrl = mockWebServer.url("/register").url();
when(mpesaConfiguration.getRegisterUrl()).thenReturn(String.valueOf(registerUrl));
when(darajaApiImpl.getAccessToken()).thenReturn(accessTokenResponse);
mockWebServer.enqueue(new MockResponse()
.setBody(String.valueOf(registerUrlResponse))
.addHeader("Content-Type", "application/json"));
RegisterUrlResponse response = darajaApiImpl.registerUrl();
RecordedRequest recordedRequest = mockWebServer.takeRequest();
System.out.println(response);
assertNotNull(response);
assertEquals("POST", recordedRequest.getMethod());
assertEquals(response.getResponseCode(), registerUrlResponse.getResponseCode());
}
}
This is the error I'm getting with the above implementation.
Any pointers will Help.
The first two tests are passing, the issue arises when testing the second method which depends on the first one.
Related
Actually, I want to visit the ElasticSearch directly without kibana. I use Java to design this process. When I used postman to test the login step with token and kbn-xsrf in the headers (picture showed here 1: https://i.stack.imgur.com/ti9Nf.png)
, I can get the result from postman.
However, when I used Java to design this process, it failed with 302. I tried RequestEntity method and WebClient methods, both of them failed (picture showed here). Anyone could help to solve this issue, big thanks.
package com.sap.ngom.collection.client;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLEncoder;
import org.cloudfoundry.uaa.tokens.AbstractToken;
import org.cloudfoundry.uaa.tokens.GetTokenByPasswordResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestOperations;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
#Service
public class ElasticSearchClient {
private static final String CF_TOKEN_URL = "https://uaa.cf.us10.hana.ondemand.com";
private static final String CF_LOGS_URL = "https://logs.cf.us10.hana.ondemand.com";
private static ObjectMapper mapper = new ObjectMapper();
String username = System.getenv("USER");
String password = System.getenv("PWD");
String token = "";
#Autowired
private RestOperations restOperations;
public JsonNode getLogsByElasticSearch() throws JsonParseException, JsonMappingException, IOException{
String query = mapper.readValue(new File(getFileURI("/query.json")), JsonNode.class).toString();
String index = mapper.readValue(new File(getFileURI("/index.json")), JsonNode.class).toString();
String queryBody = index + "\n" + query + "\n" + index + "\n" + query;
ResponseEntity responseEntity = retrieveLog(queryBody);
if (responseEntity.getStatusCode() == HttpStatus.FOUND){
dealWithRedirect(responseEntity);
}
return null;
}
private void dealWithRedirect(ResponseEntity responseEntity){
HttpHeaders responseHeaders = responseEntity.getHeaders();
String loction = responseHeaders.get("Location").toString();
String loginUrl = loction.substring(1, loction.length()-1);
String setCookie = responseHeaders.get("Set-Cookie").toString();
setCookie = setCookie.substring(1, loction.length()-1);
HttpHeaders loginHeaders = new HttpHeaders();
loginHeaders.set("Cookie", setCookie);
loginHeaders.set(HttpHeaders.AUTHORIZATION, "Basic Y2Y6");
loginHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
String body = "grant_type=password&username=jane.wang03#sap.com&password="+ System.getenv("PWD") + "&response_type=user_token";
RequestEntity<String> loginRequestEntity = new RequestEntity<>(body, loginHeaders, HttpMethod.POST, URI.create(loginUrl));
ResponseEntity loginResponse = restOperations.exchange(loginRequestEntity, Object.class);
String temp = "";
}
private void passAuthentication(String state){
String url = CF_TOKEN_URL + "/oauth/authorize?grant_type=authorization_code&client_id=sleeve-app-logs&response_type=code&state=" + state;
HttpHeaders headers = new HttpHeaders();
headers.set(HttpHeaders.AUTHORIZATION, "bearer " + token);
RequestEntity<String> requestEntity = new RequestEntity<>(headers, HttpMethod.GET, URI.create(url));
HttpStatus responseEntity = restOperations.exchange(requestEntity, Object.class).getStatusCode();
}
private ResponseEntity retrieveLog(String queryBody){
if (token == ""){
token = getToken();
}
HttpHeaders headers = new HttpHeaders();
headers.set(HttpHeaders.AUTHORIZATION, "bearer " + token);
headers.set("kbn-xsrf", "reporting");
String url = CF_LOGS_URL + "/elasticsearch/_msearch";
RequestEntity<String> requestEntity = new RequestEntity<>(queryBody, headers, HttpMethod.POST, URI.create(url));
ResponseEntity responseEntity = restOperations.exchange(requestEntity, Object.class);
return responseEntity;
}
private String getToken() {
String body = "grant_type=password";
try {
body += "&username=" + URLEncoder.encode(username, "UTF-8");
body += "&password=" + URLEncoder.encode(password, "UTF-8");
body += "&response_type=user_token";
} catch (UnsupportedEncodingException ex) {
throw new RuntimeException(ex);
}
HttpHeaders headers = new HttpHeaders();
headers.set(HttpHeaders.AUTHORIZATION, "Basic Y2Y6");
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
RequestEntity<String> requestEntity = new RequestEntity<>(body, headers, HttpMethod.POST, URI.create(CF_TOKEN_URL + "/oauth/token"));
ResponseEntity<GetTokenByPasswordResponse> responseEntity = restOperations.exchange(requestEntity, GetTokenByPasswordResponse.class);
AbstractToken token = responseEntity.getBody();
return token.getAccessToken();
}
private URI getFileURI(String path) {
URL ruleURL = ElasticSearchClient.class.getResource(path);
URI uri = null;
try {
uri = ruleURL.toURI();
} catch (URISyntaxException e) {
e.printStackTrace();
}
return uri;
}
}
In a given moment in time an authenticated session is created.
I need to create a jersey client (post method) using that authenticated session.
I've tried set the JSESSIONID in the jersey client but it doesn't recognize the session.
Client client = Client.create();
final String url = "http://localhost:8080/api/send";
WebResource wr = client.resource(url);
javax.ws.rs.core.Cookie cookie=new javax.ws.rs.core.Cookie("JSESSIONID", "521448844J5WE54D");
wr.cookie(cookie);
// Set POST parameters
FormDataMultiPart multipart = new FormDataMultiPart();
FormDataBodyPart fdp = new FormDataBodyPart("file", uploadedInputStream, MediaType.MULTIPART_FORM_DATA_TYPE);
multipart.bodyPart(fdp);
String response = wr.type(MediaType.MULTIPART_FORM_DATA_TYPE).post(String.class, multipart);
System.out.println(response);
I've tried also the code below, that in the jersey client I call first an API to authenticate the session and then try to use the same client object to call another API that require a auth session, didn't work.
Client client = Client.create();
final String url = "http://localhost:8080/api/auth";
WebResource wr = client.resource(url);
//set parametes for request
MultivaluedMap<String, String> queryParams = new MultivaluedMapImpl();
queryParams.add("user", "admin");
queryParams.add("pass", "123456");
wr.queryParams(queryParams);
ClientResponse response = wr.type(MediaType.MULTIPART_FORM_DATA_TYPE).post(ClientResponse.class);
System.out.println(response.getCookies().toString());
//------------------------------------------------------------
final String url2 = "http://localhost:8080/api/send";
WebResource wr2 = client.resource(url2);
// Set POST parameters
FormDataMultiPart multipart = new FormDataMultiPart();
FormDataBodyPart fdp = new FormDataBodyPart("file", uploadedInputStream, MediaType.MULTIPART_FORM_DATA_TYPE);
multipart.bodyPart(fdp);
String response2 = wr2.type(MediaType.MULTIPART_FORM_DATA_TYPE).post(String.class, multipart);
System.out.println(response2);
How can I do that ? I mean, how to use an authenticated JSESSIONID in a new jersey client connection ?
Regards.
I think the best way to do is to use JWT for user Authorization.
I am assuming that you have already authenticated the user via an API Endpoint. Once the user is authenticated, you can reply back a header element. You can read more about JWT # https://jwt.io/introduction/
Your implementation should look like the following steps.
1) Authenticate the user and upon successful authentication, add "Authorization: " token to the response.
2) In every API call, expect the user to pass the Authorization header with each request and use a Filter to authorize the user by parsing the JWT Token. You may want to #Inject the Parser and make sure that your parser is Threadsafe.
3-a) If the JWT Token is valid, you let the request pass through to your resource.
3-b) If the JWT Token is invalid, you reply back wit HTTP 401.
Here is a sample implementation.
import com.google.inject.Inject;
import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.proc.BadJOSEException;
import com.nimbusds.jose.proc.SecurityContext;
import com.nimbusds.jwt.JWT;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.JWTParser;
import com.nimbusds.jwt.proc.ConfigurableJWTProcessor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Priority;
import javax.ws.rs.Priorities;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.container.PreMatching;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.Provider;
import java.io.IOException;
import java.text.ParseException;
#PreMatching
#Priority(Priorities.AUTHENTICATION)
#Provider
#Secured
public class SimpleAuthorizationFilter implements ContainerRequestFilter {
static JWTParser jwtParser = null;
private static final Logger LOGGER = LoggerFactory.getLogger(SimpleAuthorizationFilter.class);
#Inject
private ConfigurableJWTProcessor jwtProcessor;
public SimpleAuthorizationFilter() {
LOGGER.debug("Init {}", getClass().getName());
}
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Began authorization filter for {}", requestContext.getUriInfo().getPath());
}
MultivaluedMap < String, String > headers = requestContext.getHeaders();
JWT jwt = null;
if (headers.containsKey(AccessTokens.AUTHORIZATION)) {
String accessToken = headers.getFirst(AccessTokens.AUTHORIZATION);
try {
jwt = JWTParser.parse(accessToken);
} catch (ParseException parseException) {
LOGGER.error("Unable to parse JWT Token {}, reason {}", requestContext.getUriInfo().getPath(), parseException.getMessage());
throw new WebApplicationException("Unable to parse JWT Token", Response.Status.UNAUTHORIZED);
}
// Check if JWT has been init successfully.
if (jwt == null) {
LOGGER.error("JWT is null {}", requestContext.getUriInfo().getPath());
throw new WebApplicationException("Unable to init JWT", Response.Status.UNAUTHORIZED);
}
try {
if (jwt.getJWTClaimsSet().getExpirationTime().before(new java.util.Date())) {
LOGGER.debug("JWT Token expired on {}, requesting new token ", jwt.getJWTClaimsSet().getExpirationTime().toString());
} else {
// Do nothing, continue as usual.
}
} catch (ParseException e) {
LOGGER.error("Authorization failed # {} , due to {}", requestContext.getUriInfo().getPath(), e.getMessage());
throw new WebApplicationException("Unable to Authorize " + e.getMessage(), Response.Status.UNAUTHORIZED);
}
SecurityContext ctx = null; // optional context parameter, not required here
JWTClaimsSet claimsSet = null;
try {
claimsSet = jwtProcessor.process(accessToken, ctx);
} catch (ParseException e) {
LOGGER.error("Authorization failed # ParseException {} , due to {}", requestContext.getUriInfo().getPath(), e.getMessage());
throw new WebApplicationException("Unable to Authorize " + e.getMessage(), Response.Status.UNAUTHORIZED);
} catch (BadJOSEException e) {
LOGGER.error("Authorization failed # BadJOSEException {} , due to {}", requestContext.getUriInfo().getPath(), e.getMessage());
throw new WebApplicationException("Unable to Authorize " + e.getMessage(), Response.Status.UNAUTHORIZED);
} catch (JOSEException e) {
LOGGER.error("Authorization failed # JOSEException {} , due to {}", requestContext.getUriInfo().getPath(), e.getMessage());
throw new WebApplicationException("Unable to Authorize " + e.getMessage(), Response.Status.UNAUTHORIZED);
}
// This should not have happened.
if (claimsSet == null) {
LOGGER.error("JWT Claim is null failed # {} , due to {}", requestContext.getUriInfo().getPath());
throw new WebApplicationException("Unable to Authorize", Response.Status.UNAUTHORIZED);
}
} else {
LOGGER.error("Authorization header is missing {}", requestContext.getUriInfo().getPath());
throw new WebApplicationException("Authorization header is missing", Response.Status.UNAUTHORIZED);
}
}
}
I actually created an annotation #Secured and any resource method annotated with #Secured will be greeted first with this filter.
Here is my Annotation:
import javax.ws.rs.NameBinding;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
#NameBinding
#Retention(RUNTIME)
#Target({TYPE, METHOD})
public #interface Secured { }
Then I created a DynamicFeature as:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.ws.rs.container.DynamicFeature;
import javax.ws.rs.container.ResourceInfo;
import javax.ws.rs.core.FeatureContext;
import javax.ws.rs.ext.Provider;
#Provider
public class ResourceFilterBindingFeature implements DynamicFeature {
private static final Logger LOGGER = LoggerFactory.getLogger(ResourceFilterBindingFeature.class);
#Override
public void configure(ResourceInfo resourceInfo, FeatureContext context) {
if (resourceInfo.getResourceMethod().isAnnotationPresent(Secured.class)) {
LOGGER.info("{} is annotated to be a secure method " , resourceInfo.getResourceMethod().getName() );
context.register(CustomAuthorizationFilter.class);
}
}
}
You will need to register the above DyamicFeature in Jersey as
register(SimpleAuthorizationFilter.class)
Finally, here is my resource that I used to test
import javax.annotation.security.RolesAllowed;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
#Path("/authorizationTest")
#Consumes({MediaType.APPLICATION_JSON})
#Produces({MediaType.APPLICATION_JSON})
public class AuthorizationTest {
#GET
#Path("/secure")
#Secured
public Response secure(){
return Response.ok(MediaType.APPLICATION_JSON).build();
}
#GET
#Path("/unsecure")
public Response unsecure(){
return Response.ok(MediaType.APPLICATION_JSON).build();
}
}
Hope that helps.
service with the Jersey implementation of JAX-RS. My Question is if it is possible to consume an object that is represented by an URI directly. I'm sorry if my wording is wrong but I'm a beginner when it comes to web-services, REST and Marshalling/Unmarschalling.
To illustrate my problem I've made an example web-service.
First I created a POJO that will be published and consumed by the web-service
package com.test.webapp.resources;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
#XmlAccessorType(XmlAccessType.FIELD)
#XmlRootElement
public class SomeData {
private String name;
private String id;
private String description;
public SomeData() {
}
public SomeData(String id, String name, String description) {
this.id = id;
this.name = name;
this.description = description;
}
public String getName() {
return name;
}
public String getId() {
return id;
}
public String getDescription() {
return description;
}
#Override
public String toString() {
return "SomeData [id="
+ id
+ ", name="
+ name
+ ", description="
+ description + "]";
}
}
Next the web-service that will publish the data:
package com.test.webapp.resources;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Request;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.GenericType;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.api.client.config.ClientConfig;
import com.sun.jersey.api.client.config.DefaultClientConfig;
import com.sun.jersey.api.json.JSONConfiguration;
#Path("/data")
public class DataResource {
#Context
private UriInfo uriInfo;
#Context
private Request request;
private static SomeData firstData = new SomeData("1",
"Important Data",
"First Test Data");
private static SomeData secondData = new SomeData("2",
"Very Important Data",
"Second Test Data");
private static SomeData thirdData = new SomeData("3",
"Some Data",
"Third Test Data");
private static List<SomeData> someDataList = new ArrayList<>();
static {
someDataList.add(firstData);
someDataList.add(secondData);
someDataList.add(thirdData);
}
#GET
#Path("/someData/list")
#Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
public List<SomeData> getSomeData() {
return someDataList;
}
#GET
#Path("/someData/{id}")
#Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
public SomeData getSomeDataSingle(#PathParam("id") int id) {
try {
SomeData data = someDataList.get(id);
return new SomeData(data.getId(),
data.getName(),
data.getDescription());
}
catch (IndexOutOfBoundsException e){
throw new RuntimeException("Data with id: "
+ id + " was not found");
}
}
#POST
#Path("/someSummary/create/all/uri")
#Consumes(MediaType.APPLICATION_XML)
public Response createSumaryFromUrl(String someDataResourceString) {
URI someDataResource = null;
try {
someDataResource = new URI(someDataResourceString);
}
catch (URISyntaxException e1) {
e1.printStackTrace();
}
List<SomeData> theDataList = this.comsumeData(someDataResource);
String summaryString = "";
for(SomeData data : theDataList) {
summaryString += data.getDescription() + " ";
}
return Response.status(201).entity(summaryString).build();
}
private List<SomeData> comsumeData(URI someDataResource) {
ClientConfig clientConfig = new DefaultClientConfig();
clientConfig.getFeatures()
.put(JSONConfiguration.FEATURE_POJO_MAPPING,
Boolean.TRUE);
Client client = Client.create(clientConfig);
WebResource webResource = client.resource(someDataResource);
List<SomeData> dataListFromGet = webResource
.accept(MediaType.APPLICATION_JSON)
.get(new GenericType<List<SomeData>>(){});
return dataListFromGet;
}
}
Now I create a Jersey Client to do a post and create a summary.
package com.test.webapp.client;
import java.net.URI;
import javax.ws.rs.core.MediaType;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.api.client.config.ClientConfig;
import com.sun.jersey.api.client.config.DefaultClientConfig;
import com.sun.jersey.api.json.JSONConfiguration;
public class JerseyClient {
public static void main(String[] args) {
try {
ClientConfig clientConfig = new DefaultClientConfig();
clientConfig.getFeatures().put(JSONConfiguration.FEATURE_POJO_MAPPING, Boolean.TRUE);
Client client = Client.create(clientConfig);
WebResource webResource = client.resource("http://localhost:8080/WebApp");
URI someDataListResource = new URI("http://localhost:8080/WebApp/data/someData/list");
ClientResponse response = webResource
.path("data/someSummary/create/all/uri")
.accept(MediaType.APPLICATION_XML)
.type(MediaType.APPLICATION_XML)
.post(ClientResponse.class, someDataListResource.toString());
if(response.getStatus() != 201) {
throw new RuntimeException("Failed : HTTP error code : " + response.getStatus());
}
System.out.println(response.getEntity(String.class));
}
catch(Exception e) {
e.printStackTrace();
}
}
}
So this works all good and well. However I think this is some kind of workaround to create a client inside the web-service to consume a resource. What I would like to do is remove the client all together inside the web-service and consume the object behind a resource directly.
Something like this:
In the web-service:
#POST
#Path("/someSummary/create/all")
#Consumes(MediaType.APPLICATION_XML)
public Response createSumary(List<SomeData> someDataList) {
String summaryString = "";
for(SomeData data : someDataList) {
summaryString += data.getDescription() + " ";
}
return Response.status(201).entity(summaryString).build();
}
And in the client something like this:
URI someDataListResource = new URI("http://localhost:8080/WebApp/data/someData/list");
ClientResponse response = webResource
.path("data/someSummary/create/all/uri")
.accept(MediaType.APPLICATION_XML)
.type(MediaType.APPLICATION_XML)
.post(ClientResponse.class, someDataListResource);
Is this possible or do I get something wrong?
Sorry if this is a trivial question but I did some research and couldn't find anything probably because my search therms are wrong due to my inexperience.
Thank you for your efforts in advance.
First, yes, if you want to consume URIs, you will need to do it by hand. You could write a custom class like this:
public class SomeDataList extends ArrayList<SomeData> {
public static SomeDataList valueOf(String uri) {
// fetch the URI & create the list with the objects, return it.
}
// other methods
}
And just use this specific class in your request:
#POST
#Path("/someSummary/create/all/uri")
#Consumes(MediaType.APPLICATION_XML)
public Response createSumaryFromUrl(#QueryParam("uri") SomeDataList someDataResourceString) {
//....
}
However, it looks to me that the specific objects you want to retrieve are already in the server, so there's no need to do a round-trip over HTTP+REST - just find them directly.
I'm using Jersey resourse in my project, like:
#Path("/api")
public class MyResource {
#Path("/create")
#POST
#Consumes(MediaType.APPLICATION_XML)
#Produces(MediaType.APPLICATION_XML)
public Response handle(final String xml, #Context final HttpServletRequest request) {
.....
}
and I'm trying to test it:
public class MobipayResourceTest extends JerseyTest {
private MockHttpServletRequest servletRequest;
#Override
#Before
public void setUp() throws Exception {
servletRequest = new MockHttpServletRequest();
servletRequest.setMethod("POST");
}
public MobipayResourceTest() throws TestContainerException {
super("ua.privatbank.mobipay.api.resource");
}
#Test
public void testRes(){
WebResource webResource = resource();
webResource.path("/api/create").post(???); // I need to pass 2 parameters in the request - xml (in the body of post) and HttpServletRequest
}
How can I pass 2 my parameters (String xml and HttpServletRequest) to the resourse in test?
You don't need to pass the HttpServletRequest, I believe.
As to the xml parameter, I think you should have a parameter
of some other class there, not just of type String. For example
Item, Customer, Order, i.e. any business object (bean, POJO).
The way you've done it now, you'd better declare
#Consumes(MediaType.TEXT_PLAIN) because you declare that
you expect just a String in your method. Normally when
you expect XML, this XML value is unmarshalled into
an object of some type (usually a bean, POJO, etc). You
get this on the fly and you can just work with the object.
Here is some sample code of a sample Java client.
package com.company.api.test;
import java.net.URI;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.UriBuilder;
import com.google.gson.Gson;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.api.client.config.ClientConfig;
import com.sun.jersey.api.client.config.DefaultClientConfig;
import com.sun.jersey.api.client.filter.LoggingFilter;
import com.company.common.DateUtil;
import com.company.api.input.ItemOperation;
import com.company.api.input.ItemOperationData;
import com.company.api.result.ItemResult;
public class JavaClientREST {
public static void main(String[] args) throws Exception {
ClientConfig config = new DefaultClientConfig();
Client client = Client.create(config);
client.addFilter(new LoggingFilter());
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
WebResource service = client.resource(getBaseURI());
ItemOperation op1 = new ItemOperation();
op1.setItemID("447");
Date d1 = DateUtil.getDate(2013, Calendar.DECEMBER, 20);
System.out.println("DT1 = " + sdf.format(d1));
op1.setDate(d1);
op1.setOperation("pause");
String res = service.path("Item")
.entity(op1, MediaType.APPLICATION_XML)
.accept(MediaType.APPLICATION_JSON)
.post(String.class);
Gson gson = new Gson();
ItemResult result = gson.fromJson(res, ItemResult.class);
System.out.println("ID = [" + result.getId() + "]");
System.out.println("Error = [" + result.getError() + "]");
System.out.println("DONE!");
}
private static URI getBaseURI() {
return UriBuilder.fromUri("http://localhost:8080/api/service").build();
}
}
I want to use the Google Shortener API. I want to use the google api java client library to post a request and parse the JSON response.
Next, I post the code I have tried:
import java.io.IOException;
import net.sf.json.JSONObject;
import com.google.api.client.googleapis.GoogleHeaders;
import com.google.api.client.googleapis.GoogleTransport;
import com.google.api.client.googleapis.json.JsonCParser;
import com.google.api.client.http.HttpRequest;
import com.google.api.client.http.HttpResponse;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.json.JsonHttpContent;
import com.google.api.client.util.GenericData;
public class GoogleShortener {
public static final String GOOGL_URL = "https://www.googleapis.com/urlshortener/v1/url";
public static void main(String[] args) {
// setup up the HTTP transport
HttpTransport transport = GoogleTransport.create();
// add default headers
GoogleHeaders defaultHeaders = new GoogleHeaders();
transport.defaultHeaders = defaultHeaders;
transport.defaultHeaders.put("Content-Type", "application/json");
transport.addParser(new JsonCParser());
// build the HTTP GET request and URL
GenericData data = new GenericData();
data.put("longUrl", "http://www.google.com/");
JsonHttpContent content = new JsonHttpContent();
content.data = data;
HttpRequest request = transport.buildPostRequest();
request.content = content;
request.setUrl(GOOGL_URL);
HttpResponse response;
try {
JSONObject json = request.execute().parseAs(JSONObject.class);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
When I execute the above code, I get the next output:
Exception in thread "main" java.lang.IllegalArgumentException: data key not found
at com.google.api.client.googleapis.json.JsonCParser.parserForResponse(JsonCParser.java:77)
at com.google.api.client.googleapis.json.JsonCParser.parse(JsonCParser.java:47)
at com.google.api.client.http.HttpResponse.parseAs(HttpResponse.java:261)
at GoogleShortener.main(GoogleShortener.java:43)
Any idea how to set the JsonCParser properly?
ERROR PATH
In the beginning I was not setting properly the request content. As pointed by #dwb, the request content should be set:
GenericData data = new GenericData();
data.put("longUrl", "http://www.google.com/");
JsonHttpContent content = new JsonHttpContent();
content.data = data;
request.content = content;
If you do not set the content properly, you will get the next error
com.google.api.client.http.HttpResponseException:
411 Length Required at
com.google.api.client.http.HttpRequest.execute(HttpRequest.java:209)
at
GoogleShortener.main(GoogleShortener.java:32)
You need to add JSON content to the request body like this:
GenericData data = new GenericData();
data.put("longUrl", "http://www.google.com/");
JsonHttpContent content = new JsonHttpContent();
content.data = data;
request.content = content;
For the response, try using the JsonHttpParser instead of JsonCParser. You'll need to create a subclass of GenericJson that contains fields with a #Key annotation for every JSON property you want to retrieve. You can use response.parseAsString() to see all of the properties available.
Here's a full working example:
import com.google.api.client.googleapis.GoogleHeaders;
import com.google.api.client.googleapis.GoogleTransport;
import com.google.api.client.http.HttpRequest;
import com.google.api.client.http.HttpResponse;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.json.GenericJson;
import com.google.api.client.json.JsonHttpContent;
import com.google.api.client.json.JsonHttpParser;
import com.google.api.client.util.GenericData;
import com.google.api.client.util.Key;
public class Shortener {
public static final String GOOGL_URL = "https://www.googleapis.com/urlshortener/v1/url";
/**
* #param args
*/
public static void main(String[] args) throws Exception {
// setup up the HTTP transport
HttpTransport transport = GoogleTransport.create();
// add default headers
GoogleHeaders defaultHeaders = new GoogleHeaders();
transport.defaultHeaders = defaultHeaders;
transport.defaultHeaders.put("Content-Type", "application/json");
transport.addParser(new JsonHttpParser());
// build the HTTP GET request and URL
HttpRequest request = transport.buildPostRequest();
request.setUrl(GOOGL_URL);
GenericData data = new GenericData();
data.put("longUrl", "http://www.google.com/");
JsonHttpContent content = new JsonHttpContent();
content.data = data;
request.content = content;
HttpResponse response = request.execute();
Result result = response.parseAs(Result.class);
System.out.println(result.shortUrl);
}
public static class Result extends GenericJson {
#Key("id")
public String shortUrl;
}
}
The code given by dwb is correct but it is using deprecated methods of the google client api.
Implementation with current library support is as follows :
import java.util.HashMap;
import java.util.Map;
import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
import com.google.api.client.http.GenericUrl;
import com.google.api.client.http.HttpContent;
import com.google.api.client.http.HttpHeaders;
import com.google.api.client.http.HttpRequest;
import com.google.api.client.http.HttpResponse;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.json.JsonHttpContent;
import com.google.api.client.json.GenericJson;
import com.google.api.client.json.JsonObjectParser;
import com.google.api.client.json.jackson.JacksonFactory;
import com.google.api.client.util.Key;
public class ShortenUrl {
/**
* #param args
*/
public static void main(String[] args) throws Exception {
HttpTransport httpTransport = GoogleNetHttpTransport.newTrustedTransport();
HttpHeaders headers = new HttpHeaders();
JsonObjectParser parser = new JsonObjectParser(new JacksonFactory());
GenericUrl url = new GenericUrl("https://www.googleapis.com/urlshortener/v1/url");
Map<String, String> json = new HashMap<String, String>();
json.put("longUrl", "http://www.google.com/");
final HttpContent content = new JsonHttpContent(new JacksonFactory(), json);
HttpRequest request = httpTransport.createRequestFactory().buildPostRequest(url, content);
try {
HttpResponse response = request.execute();
Result result = response.parseAs(Result.class);
System.out.println(result.shortUrl);
} catch (Exception e) {
e.printStackTrace();
}
}
public static class Result extends GenericJson {
#Key("id")
public String shortUrl;
}
}
Note : You should use your Oauth 2.0 credentials to use google api services.