Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Java Rest Template Changes #11

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 40 additions & 23 deletions client-samples/java/rest/README.md
Original file line number Diff line number Diff line change
@@ -1,33 +1,18 @@
# Java REST Client Example Project
This project is a starting template for integrating with REST APIs on the API Platform.

## Retrofit
This example app uses plain Java, with no larger frameworks such as Spring Boot. It uses the retrofit library, which configures API calls as an interface. Please see the [HelloWorldRestService interface](./src/main/java/com/ms/infra/example/application/servies/HelloWorldRestService.java) for an example. You can find the Retrofit docs [here](https://square.github.io/retrofit/).

Below are the example services we have configured:

| Service | Description |
| --------------- |---------------------------------------------------------------------------------------------------------------------------------|
| getServices | Calls a GET request on the `services` endpoint. |
| getStatus | Calls a GET request on the `status` endpoint, showing how to use a void response body, path and query parameters, and a header. |

The `ExampleApplication.java` file shows how to use the MsRetrofitWrapper and makes a GET request to the `services` endpoint.

## Running the Java Client application
It is important to ensure that the Java SDK is installed and the `JAVA_HOME` environment variable has been set.
This can be checked by performing the following:
## Using this template
The first step is to create the DER encoded file and configure the properties file, as described below.

* Windows cmd: `echo %JAVA_HOME%`
* Mac/Linux terminal: `echo $JAVA_HOME`
This template provides two methods for making API calls: using a simple OkHttp request or a Retrofit request. Using retrofit requires more code changes, so it is recommended to initally call the API with the OkHttp request.

If the result is empty, you will need to download and configure Java on your machine.
To configure this application for a different API, the following changes need to be made (there are more details on retrofit below):

Once the Java SDK is installed and the `JAVA_HOME` environment variable has been set it is possible to run the application.
Type in the appropriate command to launch the application
| Method | Main Class | What needs to be changed? |
---------|-------------|---------------------------|
| OkHttp Request | [`MsApiEndpoint`](./src/main/java/com/ms/infra/example/application/morganStanleyServices/MsApiRequest.java) | The `apiEndpoint` variable in [`ExampleApplication`](./src/main/java/com/ms/infra/example/application/ExampleApplication.java). |
| Retrofit | [`MsRetrofitWrapper`](./src/main/java/com/ms/infra/example/application/morganStanleyServices/MsRetrofitWrapper.java) | Create a response type in the [`responseTypes`](./src/main/java/com/ms/infra/example/application/responseTypes/) directory. </br> Create a service in the [`services`](./src/main/java/com/ms/infra/example/application/services/) directory. |

* Windows: `gradlew.bat bootRun`
* Mac/Linux: `./gradlew bootRun`
curl -X GET -v --header "Authorization: Bearer $ACCESS_TOKEN" --header 'Accept: application/json' https://api.morganstanley.com/hello/services'

## Create DER Encoded File
For this example the RSA Private key that was generated, `private_key.pem`, is not in a format that Java will understand and needs to be converted to a binary encoding.
Expand All @@ -53,9 +38,41 @@ Make these changes to the `META-INF/microprofile-config.properties` resource fil
| `client-app-scope` | The scope/s that will be sent to from your Morgan Stanley contact | True |
| `private-key-file` | The path to the private_key.der that has been created | True |
| `public-certificate-file` | The path to the public_key.cer that was created and sent to your Morgan Stanley contact | True |
| `ms-url-api-domain` | Morgan Stanley API Url Domain (Currently set to uat) | True |
| `proxy-host` | Optional proxy host | False |
| `proxy-port` | Optional proxy port | False |

## Retrofit
This example app uses plain Java, with no larger frameworks such as Spring Boot. It uses the retrofit library, which configures API calls as an interface. Please see the [HelloWorldRestService interface](./src/main/java/com/ms/infra/example/application/servies/HelloWorldRestService.java) for an example. You can find the Retrofit docs [here](https://square.github.io/retrofit/).

Below are the example services we have configured (using the hello world endpoint):

| Service | Description |
| --------------- |---------------------------------------------------------------------------------------------------------------------------------|
| getServices | Calls a GET request on the `services` endpoint. |
| getStatus | Calls a GET request on the `status` endpoint, showing how to use a void response body, path and query parameters, and a header. |

The `ExampleApplication.java` file shows how to use the MsRetrofitWrapper and makes a GET request to the `services` endpoint.

### Customising the service interfaces
To switch

## Running the Java Client application
It is important to ensure that the Java SDK is installed and the `JAVA_HOME` environment variable has been set.
This can be checked by performing the following:

* Windows cmd: `echo %JAVA_HOME%`
* Mac/Linux terminal: `echo $JAVA_HOME`

If the result is empty, you will need to download and configure Java on your machine.

Once the Java SDK is installed and the `JAVA_HOME` environment variable has been set it is possible to run the application.
Type in the appropriate command to launch the application

* Windows: `gradlew.bat bootRun`
* Mac/Linux: `./gradlew bootRun`
curl -X GET -v --header "Authorization: Bearer $ACCESS_TOKEN" --header 'Accept: application/json' https://api.morganstanley.com/hello/services'

# Legal

Morgan Stanley makes this available to you under the Apache License, Version 2.0 (the "License"). You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ms.infra.example.application.morganStanleyServices.MsApiRequest;
import com.ms.infra.example.application.morganStanleyServices.MsRetrofitWrapper;
import com.ms.infra.example.application.responseTypes.HelloWorldGetServicesResponse;
import com.ms.infra.example.application.services.HelloWorldRestService;
Expand All @@ -13,21 +14,31 @@
import retrofit2.Response;

public class ExampleApplication {
private static final String helloWorldUrl = "https://api-uat.morganstanley.com/hello/world/v1/";
private static final Logger logger = LoggerFactory.getLogger(ExampleApplication.class);
private static final ObjectMapper objectMapper = new ObjectMapper();
private static final String apiEndpoint = "hello/world/v1/services";

public static void main(String... args) throws Exception {
run();
}

public static void run() throws Exception {
MsRetrofitWrapper msRetrofitWrapper = new MsRetrofitWrapper(helloWorldUrl, HttpLoggingInterceptor.Level.BODY);
HelloWorldRestService helloWorldRestService = msRetrofitWrapper.createService(HelloWorldRestService.class);
callHelloWorldApi(helloWorldRestService);
// Call the endpoint with an okHttpClient request
callHelloWorldApi();

// Call the endpoint with retrofit
callHelloWorldApiWithRetrofit();
}

public static void callHelloWorldApi() throws Exception {
MsApiRequest msApiRequest = new MsApiRequest(HttpLoggingInterceptor.Level.BODY);
msApiRequest.callEndpoint(apiEndpoint);
}

public static void callHelloWorldApi(HelloWorldRestService helloWorldRestService) {
public static void callHelloWorldApiWithRetrofit() throws Exception {
MsRetrofitWrapper msRetrofitWrapper = new MsRetrofitWrapper(HttpLoggingInterceptor.Level.BODY);
HelloWorldRestService helloWorldRestService = msRetrofitWrapper.createService(HelloWorldRestService.class);

Call<HelloWorldGetServicesResponse> call = helloWorldRestService.getServices();

// this function will make an API call
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ public class MicroprofileConfigService {
*/
private String publicCertificateFile;

/**
* Morgan Stanley API Url Domain
*/
private String msUrlDomain;

/**
* Constructor method
* Reads values from config into class
Expand All @@ -70,6 +75,8 @@ public MicroprofileConfigService() {

this.publicCertificateFile = configProvider.getValue("public-certificate-file", String.class);
this.checkFileExtension(publicCertificateFile, ".cer");

this.msUrlDomain = configProvider.getValue("ms-url-api-domain", String.class);
}

/**
Expand All @@ -93,7 +100,7 @@ public static void setProxy(Config configProviderLocal) {
}

/**
* This method returns the MS OAuth2 token URI
* This method returns the MS OAuth2 token URI
* @return MS OAuth2 token URI
*/
public String getMsOAuth2TokenUri() {
Expand Down Expand Up @@ -155,4 +162,12 @@ public X509Certificate getPublicCertificate() throws Exception {
return (X509Certificate) CertificateFactory.getInstance("X.509")
.generateCertificate(fileInputStream);
}

/**
* This method returns the Morgan Stanley api url domain
* @return msUrlDomain
*/
public String getMsUrlDomain() {
return this.msUrlDomain;
}
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,24 @@
package com.ms.infra.example.application.interceptors;

import com.ms.infra.example.application.ExampleApplication;
import com.ms.infra.example.application.morganStanleyServices.MsClientAuthTokenService;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;

/**
* This class is responsible for generating and adding an authorization token to each API call.
*/
public class AuthHeaderInterceptor implements Interceptor {
/**
* Logging
*/
private static final Logger logger = LoggerFactory.getLogger(AuthHeaderInterceptor.class);

/**
* MsClientAuthTokenService is responsible for generating bearer tokens.
*/
Expand All @@ -35,7 +43,9 @@ public Response intercept(Chain chain) throws IOException {
// add Authorization header to every call with this interceptor
Request original = chain.request();
Request.Builder builder = original.newBuilder();
builder.header("Authorization", "Bearer " + msClientAuthTokenService.getAccessToken());
String token = msClientAuthTokenService.getAccessToken();
logger.debug("Retrieved token.");
builder.header("Authorization", "Bearer " + token);
Request request = builder.method(original.method(), original.body())
.build();
return chain.proceed(request);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package com.ms.infra.example.application.morganStanleyServices;

import com.ms.infra.example.application.ExampleApplication;
import com.ms.infra.example.application.config.MicroprofileConfigService;
import com.ms.infra.example.application.interceptors.AuthHeaderInterceptor;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.logging.HttpLoggingInterceptor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;

public class MsApiRequest {
/**
* Logging
*/
private static final Logger logger = LoggerFactory.getLogger(ExampleApplication.class);

/**
* Morgan Stanley Url Domain
*/
private final String urlDomain;

/**
* Configured retrofit instance
*/
private final OkHttpClient okHttpClient;

/**
* Constructor
* @param logLevel Log interceptor level
* @throws Exception throws exception if error when creating msClientAuthTokenService
*/
public MsApiRequest(HttpLoggingInterceptor.Level logLevel) throws Exception {
MicroprofileConfigService microprofileConfigService = new MicroprofileConfigService();
MsClientAuthTokenService msClientAuthTokenService = new MsClientAuthTokenService(microprofileConfigService);
this.urlDomain = microprofileConfigService.getMsUrlDomain();
this.okHttpClient = getOkHttpClient(logLevel, msClientAuthTokenService);
}

/**
* Constructor for custom okHttpClient
* @param okHttpClient
* @param urlDomain
*/
public MsApiRequest(OkHttpClient okHttpClient, String urlDomain) {
this.okHttpClient = okHttpClient;
this.urlDomain = urlDomain;
}

/**
* This method configures the OkHttp Client.
* It adds logging and authorization interceptor.
* @param logLevel log level
* @return configured okhttp client
*/
public OkHttpClient getOkHttpClient(HttpLoggingInterceptor.Level logLevel, MsClientAuthTokenService msClientAuthTokenService) {
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
loggingInterceptor.setLevel(logLevel);

AuthHeaderInterceptor authHeaderInterceptor = new AuthHeaderInterceptor(msClientAuthTokenService);

return new OkHttpClient.Builder()
.addInterceptor(loggingInterceptor)
.addInterceptor(authHeaderInterceptor)
.build();
}

/**
* Call Morgan Stanley API and log output
* @param apiEndpoint api endpoint to call
* @throws IOException
*/
public void callEndpoint(String apiEndpoint) throws IOException {
Request request = new Request.Builder()
.url(urlDomain + apiEndpoint)
.build();
// Execute request and handle response
Response response = okHttpClient.newCall(request).execute();

logger.info("Response code: {}", response.code());
logger.info("Response: {}", response.body().string());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,13 @@ public class MsRetrofitWrapper {

/**
* Constructor
* @param url API url
* @param logLevel Log interceptor level
* @throws Exception throws exception if error when creating msClientAuthTokenService
*/
public MsRetrofitWrapper(String url, Level logLevel) throws Exception {
public MsRetrofitWrapper(Level logLevel) throws Exception {
this.msClientAuthTokenService = new MsClientAuthTokenService(MICROPROFILE_CONFIG_SERVICE);
this.retrofit = new Retrofit.Builder()
.baseUrl(url)
.baseUrl(MICROPROFILE_CONFIG_SERVICE.getMsUrlDomain())
.addConverterFactory(JacksonConverterFactory.create())
.client(this.getOkHttpClient(logLevel))
.build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public interface HelloWorldRestService {
*
* @return A {@link Call} object representing the GET request to the "services" endpoint.
*/
@GET("services")
@GET("hello/world/v1/services")
Call<HelloWorldGetServicesResponse> getServices();

/**
Expand All @@ -31,7 +31,7 @@ public interface HelloWorldRestService {
* @param delay The query parameter representing the delay in seconds.
* @return A {@link Call} object representing the GET request to the "status/{statusCode}" endpoint.
*/
@GET("status/{statusCode}")
@GET("hello/world/v1/status/{statusCode}")
Call<Void> getStatus(@Header("myCustomHeader") String customHeader,
@Path("statusCode") int statusCode,
@Query("delay") int delay);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ client-app-id=<YOUR-CLIENT-ID>
client-app-scope=<YOUR-CLIENT-APP-SCOPE>
private-key-file=<YOUR-FILE-PATH>/private_key.der
public-certificate-file=<YOUR-FILE-PATH>/public_key.cer
ms-url-api-domain=https://api-uat.morganstanley.com/
proxy-host=<optional-proxy-host>
proxy-port=<optional-proxy-port>
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,18 @@

import java.io.IOException;

import com.fasterxml.jackson.databind.ObjectMapper;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.Is.is;

public class TestHelloWorldRestServiceShould {
private MockWebServer mockWebServer;
private HelloWorldRestService helloWorldRestService;
private static final ObjectMapper objectMapper = new ObjectMapper();

private final String EXAMPLE_GET_SERVICE_RESPONSE = "{\"response\":\"value\",\"time\":\"2022-01-01T00:00:00Z\"}";
private final String RESPONSE_VALUE = "value";
private final String TIME_VALUE = "2022-01-01T00:00:00Z";
private final String MOCK_401_ERROR_RESPONSE = "{ \"statusCode\": 401, \"message\": \"Authentication required\" }";
private final String TEST_AUTH_TOKEN = "MOCK_BEARER_TOKEN";
private final String MOCK_BEARER_TOKEN = "MOCK_BEARER_TOKEN";
private final String EXPECTED_ENDPOINT = "/hello/world/v1/services";

@BeforeEach
public void setup_hello_world_rest_service() throws IOException {
Expand All @@ -39,7 +39,7 @@ public void setup_hello_world_rest_service() throws IOException {

// Mock MsClientAuthTokenService to return fake token
MsClientAuthTokenService mockMsClientAuthTokenService = Mockito.mock(MsClientAuthTokenService.class);
Mockito.when(mockMsClientAuthTokenService.getAccessToken()).thenReturn(TEST_AUTH_TOKEN);
Mockito.when(mockMsClientAuthTokenService.getAccessToken()).thenReturn(MOCK_BEARER_TOKEN);
AuthHeaderInterceptor authHeaderInterceptor = new AuthHeaderInterceptor(mockMsClientAuthTokenService);

OkHttpClient okHttpClient = new OkHttpClient.Builder()
Expand Down Expand Up @@ -74,13 +74,13 @@ public void successfully_call_api() throws IOException, InterruptedException {
assertThat("Incorrect Response code", response.code(), is(200));

HelloWorldGetServicesResponse data = response.body();
assertThat("Incorrect value response", data.getResponse(), is("value"));
assertThat("Incorrect time response", data.getTime(), is("2022-01-01T00:00:00Z"));
assertThat("Incorrect value response", data.getResponse(), is(RESPONSE_VALUE));
assertThat("Incorrect time response", data.getTime(), is(TIME_VALUE));

// Check the request
RecordedRequest recordedRequest = mockWebServer.takeRequest();
assertThat("Incorrect path called", recordedRequest.getPath(), is("/services"));
assertThat("Incorrect auth header", recordedRequest.getHeader("Authorization"), is("Bearer " + TEST_AUTH_TOKEN));
assertThat("Incorrect path called", recordedRequest.getPath(), is(EXPECTED_ENDPOINT));
assertThat("Incorrect auth header", recordedRequest.getHeader("Authorization"), is("Bearer " + MOCK_BEARER_TOKEN));
}

@Test
Expand Down
Loading
Loading