diff --git a/brapi-java-client/src/main/java/org/brapi/client/v2/BrAPIClient.java b/brapi-java-client/src/main/java/org/brapi/client/v2/BrAPIClient.java index e1f29491..f8194e5e 100644 --- a/brapi-java-client/src/main/java/org/brapi/client/v2/BrAPIClient.java +++ b/brapi-java-client/src/main/java/org/brapi/client/v2/BrAPIClient.java @@ -12,6 +12,7 @@ package org.brapi.client.v2; +import com.fasterxml.jackson.databind.JsonNode; import com.google.gson.*; import com.google.gson.reflect.TypeToken; import okhttp3.*; @@ -674,6 +675,8 @@ public RequestBody serialize(Object obj, String contentType) throws ApiException } else if (obj instanceof File) { // File body parameter support. return RequestBody.create(MediaType.parse(contentType), (File) obj); + } else if (obj instanceof JsonNode) { + return RequestBody.create(MediaType.parse(contentType), obj.toString()); } else if (isJsonMime(contentType)) { String content; if (obj != null) { @@ -759,7 +762,8 @@ public File prepareDownloadFile(Response response) throws IOException { * @return ApiResponse<T> */ public ApiResponse execute(Call call) throws ApiException { - return execute(call, null); + Type returnType = null; + return execute(call, returnType); } /** @@ -784,6 +788,29 @@ public ApiResponse execute(Call call, Type returnType) throws ApiExceptio } } + /** + * Execute HTTP call and deserialize the HTTP response body into a return type based off of the submitted ResponseDeserializer. + * + * @param call Call + * @param responseDeserializer A function passed through which will be applied to the final response. + * In particular, this function needs to set the return datatype. + * See ListsApi.deserializeListItemsResponse() for usage examples. + * @param The return type corresponding to (same with) returnType + * @return ApiResponse object containing response status, headers and data, + * which is a Java object deserialized from response body and would be + * null when returnType is null. + * @throws ApiException If fail to execute the call + */ + public ApiResponse execute(Call call, ResponseDeserializer responseDeserializer) throws ApiException { + try { + Response response = call.execute(); + T data = handleResponse(response, responseDeserializer); + return new ApiResponse(response.code(), response.headers().toMultimap(), data); + } catch (IOException e) { + throw new ApiException(e); + } + } + /** * Execute HTTP call and deserialize the HTTP response body into the given * return type. @@ -908,6 +935,42 @@ public T handleResponse(Response response, Type returnType) throws ApiExcept } } + /** + * Handle the given response, return the deserialized object when the response + * is successful, and apply the additional passed ResponseDeserializer logic. + * + * @param response Response + * @param deserializer A function passed through which will be applied to the final response. + * In particular, this function needs to set the return datatype. + * See ListsApi.deserializeListItemsResponse() for usage examples. * @throws ApiException If the response has a unsuccessful status code or fail + * to deserialize the response body + * @return Type + */ + public T handleResponse(Response response, ResponseDeserializer deserializer) throws ApiException, IOException { + if (response.isSuccessful()) { + if (response.code() == 204) { + // returning null if the returnType is not defined, + // or the status code is 204 (No Content) + if (response.body() != null) { + response.body().close(); + } + return null; + } else { + return deserializer.deserialize(response); + } + } else { + String respBody = null; + if (response.body() != null) { + try { + respBody = response.body().string(); + } catch (IOException e) { + throw new ApiException(response.message(), e, response.code(), response.headers().toMultimap()); + } + } + throw new ApiException(response.message(), response.code(), response.headers().toMultimap(), respBody); + } + } + /** * Build HTTP call with the given options. * diff --git a/brapi-java-client/src/main/java/org/brapi/client/v2/ResponseDeserializer.java b/brapi-java-client/src/main/java/org/brapi/client/v2/ResponseDeserializer.java new file mode 100644 index 00000000..a082d381 --- /dev/null +++ b/brapi-java-client/src/main/java/org/brapi/client/v2/ResponseDeserializer.java @@ -0,0 +1,15 @@ +package org.brapi.client.v2; + +import okhttp3.Response; +import org.brapi.client.v2.model.exceptions.ApiException; + +import java.io.IOException; + +/** + * Generic interface to apply extra deserialization logic to handle responses in the BrAPIClient. + * @param The response type this response deserializer should apply to. + */ +@FunctionalInterface +public interface ResponseDeserializer { + T deserialize(Response response) throws ApiException, IOException; +} diff --git a/brapi-java-client/src/main/java/org/brapi/client/v2/modules/core/ListsApi.java b/brapi-java-client/src/main/java/org/brapi/client/v2/modules/core/ListsApi.java index 93ec1402..b2f5e404 100644 --- a/brapi-java-client/src/main/java/org/brapi/client/v2/modules/core/ListsApi.java +++ b/brapi-java-client/src/main/java/org/brapi/client/v2/modules/core/ListsApi.java @@ -12,15 +12,22 @@ package org.brapi.client.v2.modules.core; +import com.fasterxml.jackson.databind.JsonNode; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; import com.google.gson.reflect.TypeToken; import okhttp3.Call; + +import java.io.IOException; import java.lang.reflect.Type; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; +import okhttp3.Response; import org.apache.commons.lang3.tuple.Pair; import org.brapi.client.v2.ApiCallback; import org.brapi.client.v2.BrAPIClient; @@ -29,11 +36,14 @@ import org.brapi.client.v2.model.exceptions.ApiException; import org.brapi.client.v2.model.queryParams.core.ListQueryParams; import org.brapi.v2.model.BrAPIAcceptedSearchResponse; +import org.brapi.v2.model.core.BrAPIListTypes; import org.brapi.v2.model.core.request.BrAPIListNewRequest; +import org.brapi.v2.model.core.response.BrAPIListItemsListResponse; import org.brapi.v2.model.core.response.BrAPIListResponse; import org.brapi.v2.model.core.request.BrAPIListSearchRequest; import org.brapi.v2.model.core.response.BrAPIListsListResponse; import org.brapi.v2.model.core.response.BrAPIListsSingleResponse; +import org.brapi.v2.model.germ.BrAPIGermplasm; public class ListsApi { private BrAPIClient apiClient; @@ -54,6 +64,12 @@ public void setApiClient(BrAPIClient apiClient) { this.apiClient = apiClient; } + private static final Map> supportedItemClassByBrAPIListTypes = new HashMap<>(); + + static { + supportedItemClassByBrAPIListTypes.put(BrAPIListTypes.GERMPLASM, BrAPIGermplasm.class); + } + /** * Build call for listsGet * @param queryParams @@ -537,6 +553,45 @@ private Call searchListsPostCall(BrAPIListSearchRequest body) throws ApiExceptio return apiClient.buildCall(localVarPath, "POST", localVarQueryParams, localVarCollectionQueryParams, localVarPostBody, localVarHeaderParams, localVarFormParams, localVarAuthNames); } + /** + * Build call for searchListsPost + * @param body (optional) + * @return Call to execute + * @throws ApiException If fail to serialize the request body object + */ + private Call searchListItemsPostCall(String listDbId, JsonNode body) throws ApiException { + if(body == null) { + throw new IllegalArgumentException("body cannot be null"); + } + Object localVarPostBody = body; + + // create path and map variables + String localVarPath = "/search/lists/{listDbId}" + .replaceAll("\\{" + "listDbId" + "\\}", apiClient.escapeString(listDbId)) + "/items"; + + Map localVarQueryParams = new HashMap<>(); + Map localVarCollectionQueryParams = new HashMap<>(); + + Map localVarHeaderParams = new HashMap(); + + Map localVarFormParams = new HashMap(); + + final String[] localVarAccepts = { + "application/json" + }; + final String localVarAccept = apiClient.selectHeaderAccept(localVarAccepts); + if (localVarAccept != null) localVarHeaderParams.put("Accept", localVarAccept); + + final String[] localVarContentTypes = { + "application/json" + }; + final String localVarContentType = apiClient.selectHeaderContentType(localVarContentTypes); + localVarHeaderParams.put("Content-Type", localVarContentType); + + String[] localVarAuthNames = new String[] { "AuthorizationToken" }; + return apiClient.buildCall(localVarPath, "POST", localVarQueryParams, localVarCollectionQueryParams, localVarPostBody, localVarHeaderParams, localVarFormParams, localVarAuthNames); + } + /** * Submit a search request for Lists * Advanced searching for the list resource. See Search Services for additional implementation details. @@ -642,4 +697,50 @@ public Call searchListsSearchResultsDbIdGetAsync(String searchResultsDbId, Integ apiClient.executeAsync(call, localVarReturnType, callback); return call; } + + /** + * Submit a search request for ListItems + * Unlike other endpoints, this endpoint is not strongly typed to the search request, and is detected based off of the submitted listDbId. + * To accommodate for this, a functional interface is passed to the apiClient to ensure the parameterized is properly initialized to the right BrAPI model object. + * Async and searchDbIds are not currently supported for this endpoint. + * @param listDbId (required) + * @param body (required) + * @return ApiResponse<ListsListResponse> + * @throws ApiException If fail to call the API, e.g. server error or cannot deserialize the response body + */ + public ApiResponse> searchListItemsByList(String listDbId, JsonNode body) throws ApiException { + Call call = searchListItemsPostCall(listDbId, body); + + return apiClient.execute(call, this::deserializeListItemsResponse); + } + + private BrAPIListItemsListResponse deserializeListItemsResponse(Response response) throws ApiException, IOException { + + // Ignore NPE check here, this function is used after a response check has already occurred further downstream + String json = response.body().string(); + + JsonParser parser = new JsonParser(); + + JsonObject root = parser.parse(json).getAsJsonObject(); + + // The prod server response will return with the data type. + BrAPIListTypes listType = BrAPIListTypes.fromValue(root.getAsJsonPrimitive("resultListType").getAsString()); + + // Get the BrAPI model object that corresponds to the returned data type. + Class itemClass = supportedItemClassByBrAPIListTypes.get(listType); + + if (itemClass == null) { + throw new ApiException("Unsupported list type: " + listType); + } + + // Ensure that BrAPIListItemsListResponse has its parameterized set with the appropriate object, which will + // populate all the way down to BrAPIListItemWithFullItem + Type responseType = TypeToken.getParameterized( + BrAPIListItemsListResponse.class, + itemClass + ).getType(); + + // Apply deserialization of the response + return apiClient.getJSON().deserialize(json, responseType); + } } diff --git a/brapi-java-model/pom.xml b/brapi-java-model/pom.xml index a682c4a0..0fd7cd76 100644 --- a/brapi-java-model/pom.xml +++ b/brapi-java-model/pom.xml @@ -40,6 +40,11 @@ jackson-annotations ${jackson.annotations.version} + + com.fasterxml.jackson.core + jackson-databind + ${jackson.annotations.version} + javax.validation validation-api diff --git a/brapi-java-model/src/main/java/org/brapi/v2/model/core/BrAPIListItem.java b/brapi-java-model/src/main/java/org/brapi/v2/model/core/BrAPIListItem.java new file mode 100644 index 00000000..8cc02e0a --- /dev/null +++ b/brapi-java-model/src/main/java/org/brapi/v2/model/core/BrAPIListItem.java @@ -0,0 +1,85 @@ +package org.brapi.v2.model.core; + +import java.util.Objects; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * ListItem + */ +public class BrAPIListItem { + @JsonProperty("listItemDbId") + private String listItemDbId = null; + + @JsonProperty("position") + private Integer position = null; + + // This represents the actual value stored in the list_item table + @JsonProperty("item") + private String item = null; + + + public String getListItemDbId() { + return listItemDbId; + } + + public void setListItemDbId(String listItemDbId) { + this.listItemDbId = listItemDbId; + } + + public Integer getPosition() { + return position; + } + + public void setPosition(Integer position) { + this.position = position; + } + + public String getItem() { + return item; + } + + public void setItem(String item) { + this.item = item; + } + + @Override + public boolean equals(java.lang.Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + BrAPIListItem ListItem = (BrAPIListItem) o; + return Objects.equals(this.listItemDbId, ListItem.listItemDbId) && + super.equals(o); + } + + @Override + public int hashCode() { + return Objects.hash(listItemDbId, super.hashCode()); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class ListItem {\n"); + sb.append(" ").append(toIndentedString(super.toString())).append("\n"); + sb.append(" listDbId: ").append(toIndentedString(listItemDbId)).append("\n"); + sb.append("}"); + return sb.toString(); + } + + /** + * Convert the given object to string with each line indented by 4 spaces + * (except the first line). + */ + private String toIndentedString(java.lang.Object o) { + if (o == null) { + return "null"; + } + return o.toString().replace("\n", "\n "); + } +} + + diff --git a/brapi-java-model/src/main/java/org/brapi/v2/model/core/BrAPIListItemWithFullItem.java b/brapi-java-model/src/main/java/org/brapi/v2/model/core/BrAPIListItemWithFullItem.java new file mode 100644 index 00000000..68717424 --- /dev/null +++ b/brapi-java-model/src/main/java/org/brapi/v2/model/core/BrAPIListItemWithFullItem.java @@ -0,0 +1,71 @@ +package org.brapi.v2.model.core; + +import java.util.Objects; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * ListItemWithFullItem + */ +public class BrAPIListItemWithFullItem { + @JsonProperty("listItem") + private BrAPIListItem listItem = null; + + @JsonProperty("item") + private T item = null; + + public BrAPIListItem getListItem() { + return listItem; + } + + public void setListItem(BrAPIListItem listItem) { + this.listItem = listItem; + } + + public T getItem() { + return item; + } + + public void setItem(T item) { + this.item = item; + } + + @Override + public boolean equals(java.lang.Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + BrAPIListItemWithFullItem listItemWithFullItem = (BrAPIListItemWithFullItem) o; + return Objects.equals(this.listItem, listItemWithFullItem.listItem) && + super.equals(o); + } + + @Override + public int hashCode() { + return Objects.hash(listItem, super.hashCode()); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class ListItemWithFullItem {\n"); + sb.append(" ").append(toIndentedString(super.toString())).append("\n"); + sb.append(" listItem: ").append(toIndentedString(listItem)).append("\n"); + sb.append("}"); + return sb.toString(); + } + + /** + * Convert the given object to string with each line indented by 4 spaces + * (except the first line). + */ + private String toIndentedString(java.lang.Object o) { + if (o == null) { + return "null"; + } + return o.toString().replace("\n", "\n "); + } +} + diff --git a/brapi-java-model/src/main/java/org/brapi/v2/model/core/response/BrAPIListItemsListResponse.java b/brapi-java-model/src/main/java/org/brapi/v2/model/core/response/BrAPIListItemsListResponse.java new file mode 100644 index 00000000..f71fa84d --- /dev/null +++ b/brapi-java-model/src/main/java/org/brapi/v2/model/core/response/BrAPIListItemsListResponse.java @@ -0,0 +1,111 @@ +package org.brapi.v2.model.core.response; + +import java.util.Objects; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.brapi.v2.model.BrAPIContext; +import org.brapi.v2.model.BrAPIMetadata; +import org.brapi.v2.model.BrAPIResponse; +import org.brapi.v2.model.core.BrAPIListTypes; + +/** + * ListItemsListResponse + */ +public class BrAPIListItemsListResponse implements BrAPIResponse> { + @JsonProperty("@context") + private BrAPIContext _atContext = null; + + @JsonProperty("metadata") + private BrAPIMetadata metadata = null; + + @JsonProperty("result") + private BrAPIListItemsListResponseResult result = null; + + @JsonProperty("resultListType") + private BrAPIListTypes resultListType = null; + + public BrAPIListItemsListResponse() { + this._atContext = new BrAPIContext(); + this._atContext.add("context"); + } + + + public void set_atContext(BrAPIContext _atContext) { + this._atContext = _atContext; + } + public BrAPIListItemsListResponse metadata(BrAPIMetadata metadata) { + this.metadata = metadata; + return this; + } + + public BrAPIMetadata getMetadata() { + return metadata; + } + + public void setMetadata(BrAPIMetadata metadata) { + this.metadata = metadata; + } + + public BrAPIListItemsListResponse result(BrAPIListItemsListResponseResult result) { + this.result = result; + return this; + } + + + public BrAPIListItemsListResponseResult getResult() { + return result; + } + + public void setResult(BrAPIListItemsListResponseResult result) { + this.result = result; + } + + public BrAPIListTypes getResultListType() { + return resultListType; + } + + public void setResultListType(BrAPIListTypes resultListType) { + this.resultListType = resultListType; + } + + @Override + public boolean equals(java.lang.Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + BrAPIListItemsListResponse listItemsListResponse = (BrAPIListItemsListResponse) o; + return Objects.equals(this._atContext, listItemsListResponse._atContext) + && Objects.equals(this.metadata, listItemsListResponse.metadata) + && Objects.equals(this.result, listItemsListResponse.result); + } + + @Override + public int hashCode() { + return Objects.hash(_atContext, metadata, result); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class ListItemsListResponse {\n"); + + sb.append(" _atContext: ").append(toIndentedString(_atContext)).append("\n"); + sb.append(" metadata: ").append(toIndentedString(metadata)).append("\n"); + sb.append(" result: ").append(toIndentedString(result)).append("\n"); + sb.append("}"); + return sb.toString(); + } + + /** + * Convert the given object to string with each line indented by 4 spaces + * (except the first line). + */ + private String toIndentedString(java.lang.Object o) { + if (o == null) { + return "null"; + } + return o.toString().replace("\n", "\n "); + } +} diff --git a/brapi-java-model/src/main/java/org/brapi/v2/model/core/response/BrAPIListItemsListResponseResult.java b/brapi-java-model/src/main/java/org/brapi/v2/model/core/response/BrAPIListItemsListResponseResult.java new file mode 100644 index 00000000..d5d4221d --- /dev/null +++ b/brapi-java-model/src/main/java/org/brapi/v2/model/core/response/BrAPIListItemsListResponseResult.java @@ -0,0 +1,74 @@ +package org.brapi.v2.model.core.response; + +import java.util.Objects; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.brapi.v2.model.BrAPIResponseResult; +import org.brapi.v2.model.core.BrAPIListItemWithFullItem; + +import java.util.ArrayList; +import java.util.List; + +/** + * ListItemsListResponseResult + */ +public class BrAPIListItemsListResponseResult implements BrAPIResponseResult> { + @JsonProperty("data") + private List> data = new ArrayList<>(); + + public BrAPIListItemsListResponseResult data(List> data) { + this.data = data; + return this; + } + + public BrAPIListItemsListResponseResult addDataItem(BrAPIListItemWithFullItem dataItem) { + this.data.add(dataItem); + return this; + } + public List> getData() { + return data; + } + + public void setData(List> data) { + this.data = data; + } + + + @Override + public boolean equals(java.lang.Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + BrAPIListItemsListResponseResult listItemsListResponseResult = (BrAPIListItemsListResponseResult) o; + return Objects.equals(this.data, listItemsListResponseResult.data); + } + + @Override + public int hashCode() { + return Objects.hash(data); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class ListItemsListResponseResult {\n"); + + sb.append(" data: ").append(toIndentedString(data)).append("\n"); + sb.append("}"); + return sb.toString(); + } + + /** + * Convert the given object to string with each line indented by 4 spaces + * (except the first line). + */ + private String toIndentedString(java.lang.Object o) { + if (o == null) { + return "null"; + } + return o.toString().replace("\n", "\n "); + } +} +