From d3f81b446aa44577ad501950a7030a522228f9ff Mon Sep 17 00:00:00 2001 From: Mateus Molina Date: Fri, 3 May 2024 09:26:48 +0200 Subject: [PATCH] Implement missing Client methods for SubmodelRepository/Service and AasRepository/Service (#256) * Move FileRepository interface/implementations to Common * Add file handling methods to SubmodelService API * Move file related tests from RepositorySuite to ServiceSuite * Refactor SubmodelRepo to use file handling logic from Service * Fix name typo in MongoDBFileRepository * Implement /attachment endpoints to the SubmodelServiceHttpController * Modify injection method of FileRepository in SubmodelServiceFactory * Implement methods for handling thumbnails in the AasService * Move thumbnail rel. tests from AasRepository to AasServiceSuite * Refactor CrudAasRepository to use AasService for thumbnail handling * Override failing tests in AasServiceClient (TBD in another ticket) * Remove FileRepository config beans from AasRepository * Fix inconsistent FileRepository in MongoDBSubmodelRepositoryConfiguration * Fix pagination issues in Client for [Aas, Sm][Service, Repository] * Refactor client pagination deser. to use Base64UrlEncodedCursorResult * Implement thumbnail handling in ConnectedAasService/Repository * Implement file handling in ConnectSubmodelService/Repository * Implement Operation invokation in SmClient * Document current test limitation @ TestConnectedSubmodelRepository * Refactor thumbnail filename to be unique * Address review remarks --- .../client/ConnectedAasRepository.java | 25 +- ...AssetAdministrationShellRepositoryApi.java | 489 +----------------- .../client/TestConnectedAasRepository.java | 59 +-- .../client/ConnectedAasService.java | 67 ++- .../AssetAdministrationShellServiceApi.java | 383 ++++++-------- .../client/TestConnectedAasService.java | 39 +- basyx.common/basyx.core/pom.xml | 8 + .../basyx/core/pagination/CursorResult.java | 9 +- .../pagination/Base64UrlEncodedCursor.java | 4 +- .../Base64UrlEncodedCursorResult.java | 56 ++ .../client/ConnectedSubmodelRepository.java | 46 +- .../internal/SubmodelRepositoryApi.java | 102 ++++ .../TestConnectedSubmodelRepository.java | 128 +---- .../core/SubmodelRepositorySuite.java | 2 +- .../client/ConnectedSubmodelService.java | 89 +++- .../client/internal/SubmodelServiceApi.java | 467 +++++++++++++++++ .../client/TestConnectedSubmodelService.java | 89 +--- .../submodelservice/SubmodelServiceSuite.java | 2 +- 18 files changed, 962 insertions(+), 1102 deletions(-) create mode 100644 basyx.common/basyx.http/src/main/java/org/eclipse/digitaltwin/basyx/http/pagination/Base64UrlEncodedCursorResult.java diff --git a/basyx.aasrepository/basyx.aasrepository-client/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/client/ConnectedAasRepository.java b/basyx.aasrepository/basyx.aasrepository-client/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/client/ConnectedAasRepository.java index 6628436d5..370631eb4 100644 --- a/basyx.aasrepository/basyx.aasrepository-client/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/client/ConnectedAasRepository.java +++ b/basyx.aasrepository/basyx.aasrepository-client/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/client/ConnectedAasRepository.java @@ -39,18 +39,18 @@ import org.eclipse.digitaltwin.basyx.client.internal.ApiException; import org.eclipse.digitaltwin.basyx.core.exceptions.CollidingIdentifierException; import org.eclipse.digitaltwin.basyx.core.exceptions.ElementDoesNotExistException; -import org.eclipse.digitaltwin.basyx.core.exceptions.FeatureNotImplementedException; import org.eclipse.digitaltwin.basyx.core.exceptions.IdentificationMismatchException; import org.eclipse.digitaltwin.basyx.core.exceptions.MissingIdentifierException; import org.eclipse.digitaltwin.basyx.core.pagination.CursorResult; import org.eclipse.digitaltwin.basyx.core.pagination.PaginationInfo; import org.eclipse.digitaltwin.basyx.http.Base64UrlEncodedIdentifier; +import org.eclipse.digitaltwin.basyx.http.Base64UrlEncoder; import org.springframework.http.HttpStatus; /** * Provides access to an Aas Repository on a remote server * - * @author schnicke + * @author schnicke, mateusmolina */ public class ConnectedAasRepository implements AasRepository { @@ -67,12 +67,10 @@ public ConnectedAasRepository(String repoUrl) { this.repoApi = new AssetAdministrationShellRepositoryApi(repoUrl); } - /** - * Not implemented - */ @Override public CursorResult> getAllAas(PaginationInfo pInfo) { - throw new FeatureNotImplementedException(); + String encodedCursor = pInfo.getCursor() == null ? null : Base64UrlEncoder.encode(pInfo.getCursor()); + return repoApi.getAllAssetAdministrationShells(null, null, pInfo.getLimit(), encodedCursor); } @Override @@ -152,28 +150,19 @@ public AssetInformation getAssetInformation(String aasId) throws ElementDoesNotE return getConnectedAasService(aasId).getAssetInformation(); } - /** - * Not implemented - */ @Override public File getThumbnail(String aasId) { - throw new FeatureNotImplementedException(); + return getConnectedAasService(aasId).getThumbnail(); } - /** - * Not implemented - */ @Override public void setThumbnail(String aasId, String fileName, String contentType, InputStream inputStream) { - throw new FeatureNotImplementedException(); + getConnectedAasService(aasId).setThumbnail(fileName, contentType, inputStream); } - /** - * Not implemented - */ @Override public void deleteThumbnail(String aasId) { - throw new FeatureNotImplementedException(); + getConnectedAasService(aasId).deleteThumbnail(); } private String getAasUrl(String aasId) { diff --git a/basyx.aasrepository/basyx.aasrepository-client/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/client/internal/AssetAdministrationShellRepositoryApi.java b/basyx.aasrepository/basyx.aasrepository-client/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/client/internal/AssetAdministrationShellRepositoryApi.java index 94783575f..26289eb78 100644 --- a/basyx.aasrepository/basyx.aasrepository-client/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/client/internal/AssetAdministrationShellRepositoryApi.java +++ b/basyx.aasrepository/basyx.aasrepository-client/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/client/internal/AssetAdministrationShellRepositoryApi.java @@ -24,26 +24,18 @@ ******************************************************************************/ package org.eclipse.digitaltwin.basyx.aasrepository.client.internal; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.File; import java.io.IOException; import java.io.InputStream; -import java.io.OutputStream; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; -import java.nio.channels.Channels; -import java.nio.channels.Pipe; import java.time.Duration; import java.util.ArrayList; import java.util.List; import java.util.StringJoiner; import java.util.function.Consumer; -import org.apache.http.HttpEntity; -import org.apache.http.entity.mime.MultipartEntityBuilder; import org.eclipse.digitaltwin.aas4j.v3.dataformat.json.JsonMapperFactory; import org.eclipse.digitaltwin.aas4j.v3.dataformat.json.SimpleAbstractTypeResolverFactory; import org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell; @@ -54,6 +46,7 @@ import org.eclipse.digitaltwin.basyx.client.internal.ApiResponse; import org.eclipse.digitaltwin.basyx.client.internal.Pair; import org.eclipse.digitaltwin.basyx.core.pagination.CursorResult; +import org.eclipse.digitaltwin.basyx.http.pagination.Base64UrlEncodedCursorResult; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; @@ -198,96 +191,6 @@ private HttpRequest.Builder deleteAssetAdministrationShellByIdRequestBuilder(Str } return localVarRequestBuilder; } - /** - * - * - * @param aasIdentifier The Asset Administration Shell’s unique id (UTF8-BASE64-URL-encoded) (required) - * @throws ApiException if fails to make API call - */ - public void deleteThumbnailAasRepository(String aasIdentifier) throws ApiException { - - deleteThumbnailAasRepositoryWithHttpInfo(aasIdentifier); - } - - /** - * - * - * @param aasIdentifier The Asset Administration Shell’s unique id (UTF8-BASE64-URL-encoded) (required) - * @return ApiResponse<Void> - * @throws ApiException if fails to make API call - */ - public ApiResponse deleteThumbnailAasRepositoryWithHttpInfo(String aasIdentifier) throws ApiException { - String aasIdentifierAsBytes = ApiClient.base64UrlEncode(aasIdentifier); - return deleteThumbnailAasRepositoryWithHttpInfoNoUrlEncoding(aasIdentifierAsBytes); - - } - - - /** - * - * - * @param aasIdentifier The Asset Administration Shell’s unique id (UTF8-BASE64-URL-encoded) (required) - * @return ApiResponse<Void> - * @throws ApiException if fails to make API call - */ - public ApiResponse deleteThumbnailAasRepositoryWithHttpInfoNoUrlEncoding(String aasIdentifier) throws ApiException { - HttpRequest.Builder localVarRequestBuilder = deleteThumbnailAasRepositoryRequestBuilder(aasIdentifier); - try { - HttpResponse localVarResponse = memberVarHttpClient.send( - localVarRequestBuilder.build(), - HttpResponse.BodyHandlers.ofInputStream()); - if (memberVarResponseInterceptor != null) { - memberVarResponseInterceptor.accept(localVarResponse); - } - try { - if (localVarResponse.statusCode()/ 100 != 2) { - throw getApiException("deleteThumbnailAasRepository", localVarResponse); - } - return new ApiResponse( - localVarResponse.statusCode(), - localVarResponse.headers().map(), - null - ); - } finally { - // Drain the InputStream - while (localVarResponse.body().read() != -1) { - // Ignore - } - localVarResponse.body().close(); - } - } catch (IOException e) { - throw new ApiException(e); - } - catch (InterruptedException e) { - Thread.currentThread().interrupt(); - throw new ApiException(e); - } - } - - private HttpRequest.Builder deleteThumbnailAasRepositoryRequestBuilder(String aasIdentifier) throws ApiException { - // verify the required parameter 'aasIdentifier' is set - if (aasIdentifier == null) { - throw new ApiException(400, "Missing the required parameter 'aasIdentifier' when calling deleteThumbnailAasRepository"); - } - - HttpRequest.Builder localVarRequestBuilder = HttpRequest.newBuilder(); - - String localVarPath = "/shells/{aasIdentifier}/asset-information/thumbnail" - .replace("{aasIdentifier}", ApiClient.urlEncode(aasIdentifier.toString())); - - localVarRequestBuilder.uri(URI.create(memberVarBaseUri + localVarPath)); - - localVarRequestBuilder.header("Accept", "application/json"); - - localVarRequestBuilder.method("DELETE", HttpRequest.BodyPublishers.noBody()); - if (memberVarReadTimeout != null) { - localVarRequestBuilder.timeout(memberVarReadTimeout); - } - if (memberVarInterceptor != null) { - memberVarInterceptor.accept(localVarRequestBuilder); - } - return localVarRequestBuilder; - } /** * Deletes the submodel reference from the Asset Administration Shell. Does not delete the submodel itself! * @@ -404,63 +307,17 @@ private HttpRequest.Builder deleteSubmodelReferenceByIdAasRepositoryRequestBuild * A server-generated identifier retrieved from pagingMetadata that * specifies from which position the result listing should continue * (optional) - * @return GetAssetAdministrationShellsResult + * @return CursorResult<List<SubmodelElement>> * @throws ApiException * if fails to make API call */ public CursorResult> getAllAssetAdministrationShells(List assetIds, String idShort, Integer limit, String cursor) throws ApiException { + ApiResponse>> localVarResponse = getAllAssetAdministrationShellsApiResponse(assetIds, idShort, limit, cursor); - ApiResponse>> localVarResponse = getAllAssetAdministrationShellsWithHttpInfo(assetIds, idShort, limit, cursor); return localVarResponse.getData(); } - /** - * Returns all Asset Administration Shells - * - * @param assetIds - * A list of specific Asset identifiers. Each Asset identifier is a - * base64-url-encoded - * [SpecificAssetId](./model-part1.yaml#/components/schemas/SpecificAssetId) - * (optional - * @param idShort - * The Asset Administration Shell’s IdShort (optional) - * @param limit - * The maximum number of elements in the response array (optional) - * @param cursor - * A server-generated identifier retrieved from pagingMetadata that - * specifies from which position the result listing should continue - * (optional) - * @return ApiResponse<GetAssetAdministrationShellsResult> - * @throws ApiException - * if fails to make API call - */ - public ApiResponse>> getAllAssetAdministrationShellsWithHttpInfo(List assetIds, String idShort, Integer limit, String cursor) throws ApiException { - List assetIdsAsBytes = ApiClient.base64UrlEncode(assetIds); - return getAllAssetAdministrationShellsWithHttpInfoNoUrlEncoding(assetIdsAsBytes, idShort, limit, cursor); - - } - - /** - * Returns all Asset Administration Shells - * - * @param assetIds - * A list of specific Asset identifiers. Each Asset identifier is a - * base64-url-encoded - * [SpecificAssetId](./model-part1.yaml#/components/schemas/SpecificAssetId) - * (optional - * @param idShort - * The Asset Administration Shell’s IdShort (optional) - * @param limit - * The maximum number of elements in the response array (optional) - * @param cursor - * A server-generated identifier retrieved from pagingMetadata that - * specifies from which position the result listing should continue - * (optional) - * @return ApiResponse<GetAssetAdministrationShellsResult> - * @throws ApiException - * if fails to make API call - */ - public ApiResponse>> getAllAssetAdministrationShellsWithHttpInfoNoUrlEncoding(List assetIds, String idShort, Integer limit, String cursor) throws ApiException { + private ApiResponse>> getAllAssetAdministrationShellsApiResponse(List assetIds, String idShort, Integer limit, String cursor) throws ApiException { HttpRequest.Builder localVarRequestBuilder = getAllAssetAdministrationShellsRequestBuilder(assetIds, idShort, limit, cursor); try { HttpResponse localVarResponse = memberVarHttpClient.send(localVarRequestBuilder.build(), HttpResponse.BodyHandlers.ofInputStream()); @@ -471,8 +328,8 @@ public ApiResponse>> getAllAssetAdmi if (localVarResponse.statusCode() / 100 != 2) { throw getApiException("getAllAssetAdministrationShells", localVarResponse); } - return new ApiResponse>>(localVarResponse.statusCode(), localVarResponse.headers().map(), - localVarResponse.body() == null ? null : memberVarObjectMapper.readValue(localVarResponse.body(), new TypeReference>>() { + return new ApiResponse<>(localVarResponse.statusCode(), localVarResponse.headers().map(), + localVarResponse.body() == null ? null : memberVarObjectMapper.readValue(localVarResponse.body(), new TypeReference>>() { }) // closes the InputStream ); } finally { @@ -526,123 +383,6 @@ private HttpRequest.Builder getAllAssetAdministrationShellsRequestBuilder(List> getAllSubmodelReferencesAasRepository(String aasIdentifier, Integer limit, String cursor) throws ApiException { - - ApiResponse>> localVarResponse = getAllSubmodelReferencesAasRepositoryWithHttpInfo(aasIdentifier, limit, cursor); - return localVarResponse.getData(); - } - - /** - * Returns all submodel references - * - * @param aasIdentifier The Asset Administration Shell’s unique id (UTF8-BASE64-URL-encoded) (required) - * @param limit The maximum number of elements in the response array (optional) - * @param cursor A server-generated identifier retrieved from pagingMetadata that specifies from which position the result listing should continue (optional) - * @return ApiResponse<GetReferencesResult> - * @throws ApiException if fails to make API call - */ - public ApiResponse>> getAllSubmodelReferencesAasRepositoryWithHttpInfo(String aasIdentifier, Integer limit, String cursor) throws ApiException { - String aasIdentifierAsBytes = ApiClient.base64UrlEncode(aasIdentifier); - return getAllSubmodelReferencesAasRepositoryWithHttpInfoNoUrlEncoding(aasIdentifierAsBytes, limit, cursor); - - } - - - /** - * Returns all submodel references - * - * @param aasIdentifier The Asset Administration Shell’s unique id (UTF8-BASE64-URL-encoded) (required) - * @param limit The maximum number of elements in the response array (optional) - * @param cursor A server-generated identifier retrieved from pagingMetadata that specifies from which position the result listing should continue (optional) - * @return ApiResponse<GetReferencesResult> - * @throws ApiException if fails to make API call - */ - public ApiResponse>> getAllSubmodelReferencesAasRepositoryWithHttpInfoNoUrlEncoding(String aasIdentifier, Integer limit, String cursor) throws ApiException { - HttpRequest.Builder localVarRequestBuilder = getAllSubmodelReferencesAasRepositoryRequestBuilder(aasIdentifier, limit, cursor); - try { - HttpResponse localVarResponse = memberVarHttpClient.send( - localVarRequestBuilder.build(), - HttpResponse.BodyHandlers.ofInputStream()); - if (memberVarResponseInterceptor != null) { - memberVarResponseInterceptor.accept(localVarResponse); - } - try { - if (localVarResponse.statusCode()/ 100 != 2) { - throw getApiException("getAllSubmodelReferencesAasRepository", localVarResponse); - } - return new ApiResponse>>( - localVarResponse.statusCode(), - localVarResponse.headers().map(), - localVarResponse.body() == null ? null : memberVarObjectMapper.readValue(localVarResponse.body(), new TypeReference>>() {}) // closes the InputStream - ); - } finally { - } - } catch (IOException e) { - throw new ApiException(e); - } - catch (InterruptedException e) { - Thread.currentThread().interrupt(); - throw new ApiException(e); - } - } - - private HttpRequest.Builder getAllSubmodelReferencesAasRepositoryRequestBuilder(String aasIdentifier, Integer limit, String cursor) throws ApiException { - // verify the required parameter 'aasIdentifier' is set - if (aasIdentifier == null) { - throw new ApiException(400, "Missing the required parameter 'aasIdentifier' when calling getAllSubmodelReferencesAasRepository"); - } - - HttpRequest.Builder localVarRequestBuilder = HttpRequest.newBuilder(); - - String localVarPath = "/shells/{aasIdentifier}/submodel-refs" - .replace("{aasIdentifier}", ApiClient.urlEncode(aasIdentifier.toString())); - - List localVarQueryParams = new ArrayList<>(); - StringJoiner localVarQueryStringJoiner = new StringJoiner("&"); - String localVarQueryParameterBaseName; - localVarQueryParameterBaseName = "limit"; - localVarQueryParams.addAll(ApiClient.parameterToPairs("limit", limit)); - localVarQueryParameterBaseName = "cursor"; - localVarQueryParams.addAll(ApiClient.parameterToPairs("cursor", cursor)); - - if (!localVarQueryParams.isEmpty() || localVarQueryStringJoiner.length() != 0) { - StringJoiner queryJoiner = new StringJoiner("&"); - localVarQueryParams.forEach(p -> queryJoiner.add(p.getName() + '=' + p.getValue())); - if (localVarQueryStringJoiner.length() != 0) { - queryJoiner.add(localVarQueryStringJoiner.toString()); - } - localVarRequestBuilder.uri(URI.create(memberVarBaseUri + localVarPath + '?' + queryJoiner.toString())); - } else { - localVarRequestBuilder.uri(URI.create(memberVarBaseUri + localVarPath)); - } - - localVarRequestBuilder.header("Accept", "application/json"); - - localVarRequestBuilder.method("GET", HttpRequest.BodyPublishers.noBody()); - if (memberVarReadTimeout != null) { - localVarRequestBuilder.timeout(memberVarReadTimeout); - } - if (memberVarInterceptor != null) { - memberVarInterceptor.accept(localVarRequestBuilder); - } - return localVarRequestBuilder; - } /** * Returns a specific Asset Administration Shell * @@ -904,93 +644,7 @@ private HttpRequest.Builder getAssetInformationAasRepositoryRequestBuilder(Strin } return localVarRequestBuilder; } - /** - * - * - * @param aasIdentifier The Asset Administration Shell’s unique id (UTF8-BASE64-URL-encoded) (required) - * @return File - * @throws ApiException if fails to make API call - */ - public File getThumbnailAasRepository(String aasIdentifier) throws ApiException { - - ApiResponse localVarResponse = getThumbnailAasRepositoryWithHttpInfo(aasIdentifier); - return localVarResponse.getData(); - } - - /** - * - * - * @param aasIdentifier The Asset Administration Shell’s unique id (UTF8-BASE64-URL-encoded) (required) - * @return ApiResponse<File> - * @throws ApiException if fails to make API call - */ - public ApiResponse getThumbnailAasRepositoryWithHttpInfo(String aasIdentifier) throws ApiException { - String aasIdentifierAsBytes = ApiClient.base64UrlEncode(aasIdentifier); - return getThumbnailAasRepositoryWithHttpInfoNoUrlEncoding(aasIdentifierAsBytes); - - } - - /** - * - * - * @param aasIdentifier The Asset Administration Shell’s unique id (UTF8-BASE64-URL-encoded) (required) - * @return ApiResponse<File> - * @throws ApiException if fails to make API call - */ - public ApiResponse getThumbnailAasRepositoryWithHttpInfoNoUrlEncoding(String aasIdentifier) throws ApiException { - HttpRequest.Builder localVarRequestBuilder = getThumbnailAasRepositoryRequestBuilder(aasIdentifier); - try { - HttpResponse localVarResponse = memberVarHttpClient.send( - localVarRequestBuilder.build(), - HttpResponse.BodyHandlers.ofInputStream()); - if (memberVarResponseInterceptor != null) { - memberVarResponseInterceptor.accept(localVarResponse); - } - try { - if (localVarResponse.statusCode()/ 100 != 2) { - throw getApiException("getThumbnailAasRepository", localVarResponse); - } - return new ApiResponse( - localVarResponse.statusCode(), - localVarResponse.headers().map(), - localVarResponse.body() == null ? null : memberVarObjectMapper.readValue(localVarResponse.body(), new TypeReference() {}) // closes the InputStream - ); - } finally { - } - } catch (IOException e) { - throw new ApiException(e); - } - catch (InterruptedException e) { - Thread.currentThread().interrupt(); - throw new ApiException(e); - } - } - - private HttpRequest.Builder getThumbnailAasRepositoryRequestBuilder(String aasIdentifier) throws ApiException { - // verify the required parameter 'aasIdentifier' is set - if (aasIdentifier == null) { - throw new ApiException(400, "Missing the required parameter 'aasIdentifier' when calling getThumbnailAasRepository"); - } - - HttpRequest.Builder localVarRequestBuilder = HttpRequest.newBuilder(); - - String localVarPath = "/shells/{aasIdentifier}/asset-information/thumbnail" - .replace("{aasIdentifier}", ApiClient.urlEncode(aasIdentifier.toString())); - - localVarRequestBuilder.uri(URI.create(memberVarBaseUri + localVarPath)); - - localVarRequestBuilder.header("Accept", "application/octet-stream, application/json"); - - localVarRequestBuilder.method("GET", HttpRequest.BodyPublishers.noBody()); - if (memberVarReadTimeout != null) { - localVarRequestBuilder.timeout(memberVarReadTimeout); - } - if (memberVarInterceptor != null) { - memberVarInterceptor.accept(localVarRequestBuilder); - } - return localVarRequestBuilder; - } /** * Creates a new Asset Administration Shell * @@ -1389,135 +1043,4 @@ private HttpRequest.Builder putAssetInformationAasRepositoryRequestBuilder(Strin } return localVarRequestBuilder; } - /** - * - * - * @param aasIdentifier The Asset Administration Shell’s unique id (UTF8-BASE64-URL-encoded) (required) - * @param fileName (optional) - * @param _file (optional) - * @throws ApiException if fails to make API call - */ - public void putThumbnailAasRepository(String aasIdentifier, String fileName, File _file) throws ApiException { - - putThumbnailAasRepositoryWithHttpInfo(aasIdentifier, fileName, _file); - } - - /** - * - * - * @param aasIdentifier The Asset Administration Shell’s unique id (UTF8-BASE64-URL-encoded) (required) - * @param fileName (optional) - * @param _file (optional) - * @return ApiResponse<Void> - * @throws ApiException if fails to make API call - */ - public ApiResponse putThumbnailAasRepositoryWithHttpInfo(String aasIdentifier, String fileName, File _file) throws ApiException { - String aasIdentifierAsBytes = ApiClient.base64UrlEncode(aasIdentifier); - return putThumbnailAasRepositoryWithHttpInfoNoUrlEncoding(aasIdentifierAsBytes, fileName, _file); - - } - - - /** - * - * - * @param aasIdentifier The Asset Administration Shell’s unique id (UTF8-BASE64-URL-encoded) (required) - * @param fileName (optional) - * @param _file (optional) - * @return ApiResponse<Void> - * @throws ApiException if fails to make API call - */ - public ApiResponse putThumbnailAasRepositoryWithHttpInfoNoUrlEncoding(String aasIdentifier, String fileName, File _file) throws ApiException { - HttpRequest.Builder localVarRequestBuilder = putThumbnailAasRepositoryRequestBuilder(aasIdentifier, fileName, _file); - try { - HttpResponse localVarResponse = memberVarHttpClient.send( - localVarRequestBuilder.build(), - HttpResponse.BodyHandlers.ofInputStream()); - if (memberVarResponseInterceptor != null) { - memberVarResponseInterceptor.accept(localVarResponse); - } - try { - if (localVarResponse.statusCode()/ 100 != 2) { - throw getApiException("putThumbnailAasRepository", localVarResponse); - } - return new ApiResponse( - localVarResponse.statusCode(), - localVarResponse.headers().map(), - null - ); - } finally { - // Drain the InputStream - while (localVarResponse.body().read() != -1) { - // Ignore - } - localVarResponse.body().close(); - } - } catch (IOException e) { - throw new ApiException(e); - } - catch (InterruptedException e) { - Thread.currentThread().interrupt(); - throw new ApiException(e); - } - } - - private HttpRequest.Builder putThumbnailAasRepositoryRequestBuilder(String aasIdentifier, String fileName, File _file) throws ApiException { - // verify the required parameter 'aasIdentifier' is set - if (aasIdentifier == null) { - throw new ApiException(400, "Missing the required parameter 'aasIdentifier' when calling putThumbnailAasRepository"); - } - - HttpRequest.Builder localVarRequestBuilder = HttpRequest.newBuilder(); - - String localVarPath = "/shells/{aasIdentifier}/asset-information/thumbnail" - .replace("{aasIdentifier}", ApiClient.urlEncode(aasIdentifier.toString())); - - localVarRequestBuilder.uri(URI.create(memberVarBaseUri + localVarPath)); - - localVarRequestBuilder.header("Accept", "application/json"); - - MultipartEntityBuilder multiPartBuilder = MultipartEntityBuilder.create(); - boolean hasFiles = false; - multiPartBuilder.addTextBody("fileName", fileName.toString()); - multiPartBuilder.addBinaryBody("file", _file); - hasFiles = true; - HttpEntity entity = multiPartBuilder.build(); - HttpRequest.BodyPublisher formDataPublisher; - if (hasFiles) { - Pipe pipe; - try { - pipe = Pipe.open(); - } catch (IOException e) { - throw new RuntimeException(e); - } - new Thread(() -> { - try (OutputStream outputStream = Channels.newOutputStream(pipe.sink())) { - entity.writeTo(outputStream); - } catch (IOException e) { - e.printStackTrace(); - } - }).start(); - formDataPublisher = HttpRequest.BodyPublishers.ofInputStream(() -> Channels.newInputStream(pipe.source())); - } else { - ByteArrayOutputStream formOutputStream = new ByteArrayOutputStream(); - try { - entity.writeTo(formOutputStream); - } catch (IOException e) { - throw new RuntimeException(e); - } - formDataPublisher = HttpRequest.BodyPublishers - .ofInputStream(() -> new ByteArrayInputStream(formOutputStream.toByteArray())); - } - localVarRequestBuilder - .header("Content-Type", entity.getContentType().getValue()) - .method("PUT", formDataPublisher); - if (memberVarReadTimeout != null) { - localVarRequestBuilder.timeout(memberVarReadTimeout); - } - if (memberVarInterceptor != null) { - memberVarInterceptor.accept(localVarRequestBuilder); - } - return localVarRequestBuilder; - } - } diff --git a/basyx.aasrepository/basyx.aasrepository-client/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/client/TestConnectedAasRepository.java b/basyx.aasrepository/basyx.aasrepository-client/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/client/TestConnectedAasRepository.java index d92cb1efa..0a48057dd 100644 --- a/basyx.aasrepository/basyx.aasrepository-client/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/client/TestConnectedAasRepository.java +++ b/basyx.aasrepository/basyx.aasrepository-client/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/client/TestConnectedAasRepository.java @@ -26,13 +26,9 @@ package org.eclipse.digitaltwin.basyx.aasrepository.client; -import java.io.FileNotFoundException; -import java.io.IOException; - import org.eclipse.digitaltwin.basyx.aasrepository.AasRepository; import org.eclipse.digitaltwin.basyx.aasrepository.AasRepositorySuite; import org.eclipse.digitaltwin.basyx.aasrepository.http.DummyAasRepositoryComponent; -import org.eclipse.digitaltwin.basyx.core.exceptions.FileDoesNotExistException; import org.eclipse.digitaltwin.basyx.core.pagination.PaginationInfo; import org.junit.After; import org.junit.AfterClass; @@ -46,7 +42,7 @@ * Whenever a feature is implemented, the respective test here has to be * removed. * - * @author schnicke + * @author schnicke, mateusmolina */ public class TestConnectedAasRepository extends AasRepositorySuite { @@ -68,59 +64,6 @@ public static void shutdownAASRepo() { appContext.close(); } - @Override - public void getNonExistingThumbnail() { - // TODO Auto-generated method stub - throw new FileDoesNotExistException(); - } - - @Override - public void deleteNonExistingThumbnail() throws IOException { - // TODO Auto-generated method stub - throw new FileDoesNotExistException(); - } - - @Override - public void updateThumbnail() throws FileNotFoundException, IOException { - // TODO Auto-generated method stub - - } - - @Override - public void setThumbnail() throws FileNotFoundException, IOException { - // TODO Auto-generated method stub - } - - @Override - public void getThumbnail() throws IOException { - // TODO Auto-generated method stub - - } - - @Override - public void deleteThumbnail() throws FileNotFoundException, IOException { - // TODO Auto-generated method stub - throw new FileDoesNotExistException(); - } - - @Override - public void getPaginatedAssetAdministrationShellIterating() { - // TODO Auto-generated method stub - - } - - @Override - public void getPaginatedAssetAdministrationShell() { - // TODO Auto-generated method stub - - } - - @Override - public void allAasRetrieval() throws Exception { - // TODO Auto-generated method stub - - } - @Override protected AasRepository getAasRepository() { return new ConnectedAasRepository("http://localhost:8080"); diff --git a/basyx.aasservice/basyx.aasservice-client/src/main/java/org/eclipse/digitaltwin/basyx/aasservice/client/ConnectedAasService.java b/basyx.aasservice/basyx.aasservice-client/src/main/java/org/eclipse/digitaltwin/basyx/aasservice/client/ConnectedAasService.java index 7c4114cd5..7f74f404e 100644 --- a/basyx.aasservice/basyx.aasservice-client/src/main/java/org/eclipse/digitaltwin/basyx/aasservice/client/ConnectedAasService.java +++ b/basyx.aasservice/basyx.aasservice-client/src/main/java/org/eclipse/digitaltwin/basyx/aasservice/client/ConnectedAasService.java @@ -37,16 +37,18 @@ import org.eclipse.digitaltwin.basyx.aasservice.client.internal.AssetAdministrationShellServiceApi; import org.eclipse.digitaltwin.basyx.client.internal.ApiException; import org.eclipse.digitaltwin.basyx.core.exceptions.ElementDoesNotExistException; -import org.eclipse.digitaltwin.basyx.core.exceptions.FeatureNotImplementedException; +import org.eclipse.digitaltwin.basyx.core.exceptions.ElementNotAFileException; +import org.eclipse.digitaltwin.basyx.core.exceptions.FileDoesNotExistException; import org.eclipse.digitaltwin.basyx.core.pagination.CursorResult; import org.eclipse.digitaltwin.basyx.core.pagination.PaginationInfo; +import org.eclipse.digitaltwin.basyx.http.Base64UrlEncoder; import org.springframework.http.HttpStatus; /** * Provides access to a Aas Service on a remote server - regardless if it is * hosted on a Aas Repository or standalone * - * @author schnicke + * @author schnicke, mateusmolina */ public class ConnectedAasService implements AasService { @@ -68,7 +70,8 @@ public AssetAdministrationShell getAAS() throws ElementDoesNotExistException { @Override public CursorResult> getSubmodelReferences(PaginationInfo pInfo) throws ElementDoesNotExistException { try { - return serviceApi.getAllSubmodelReferences(pInfo.getLimit(), pInfo.getCursor()); + String encodedCursor = pInfo.getCursor() == null ? null : Base64UrlEncoder.encode(pInfo.getCursor()); + return serviceApi.getAllSubmodelReferences(pInfo.getLimit(), encodedCursor); } catch (ApiException e) { throw mapAasAccess(e); } @@ -88,7 +91,7 @@ public void removeSubmodelReference(String submodelId) throws ElementDoesNotExis try { serviceApi.deleteSubmodelReferenceById(submodelId); } catch (ApiException e) { - throw mapAasAccess(submodelId, e); + throw mapSubmodelAccess(submodelId, e); } } @@ -110,36 +113,58 @@ public AssetInformation getAssetInformation() throws ElementDoesNotExistExceptio } } - private RuntimeException mapAasAccess(String shellId, ApiException e) { - if (e.getCode() == HttpStatus.NOT_FOUND.value()) { - return new ElementDoesNotExistException(shellId); + @Override + public File getThumbnail() { + try { + return serviceApi.getThumbnail(); + } catch (ApiException e) { + throw mapThumbnailAccess(e); } - - return e; } - private RuntimeException mapAasAccess(ApiException e) { - if (e.getCode() == HttpStatus.NOT_FOUND.value()) { - return new ElementDoesNotExistException(); + @Override + public void setThumbnail(String fileName, String contentType, InputStream inputStream) { + try { + serviceApi.putThumbnail(fileName, contentType, inputStream); + } catch (ApiException e) { + throw mapThumbnailAccess(e); } - return e; } @Override - public File getThumbnail() { - throw new FeatureNotImplementedException(); + public void deleteThumbnail() { + try { + serviceApi.deleteThumbnail(); + } catch (ApiException e) { + throw mapThumbnailAccess(e); + } } - @Override - public void setThumbnail(String fileName, String contentType, InputStream inputStream) { - throw new FeatureNotImplementedException(); + private RuntimeException mapThumbnailAccess(ApiException e) { + if (e.getCode() == HttpStatus.NOT_FOUND.value()) + return new FileDoesNotExistException(); + + if (e.getCode() == HttpStatus.PRECONDITION_FAILED.value()) + return new ElementNotAFileException(); + return e; + } + + private RuntimeException mapSubmodelAccess(String submodelId, ApiException e) { + if (e.getCode() == HttpStatus.NOT_FOUND.value()) + return new ElementDoesNotExistException(submodelId); + + return e; } - @Override - public void deleteThumbnail() { - throw new FeatureNotImplementedException(); + private RuntimeException mapAasAccess(ApiException e) { + if (e.getCode() == HttpStatus.NOT_FOUND.value()) { + return new ElementDoesNotExistException(); + } + + return e; } + } diff --git a/basyx.aasservice/basyx.aasservice-client/src/main/java/org/eclipse/digitaltwin/basyx/aasservice/client/internal/AssetAdministrationShellServiceApi.java b/basyx.aasservice/basyx.aasservice-client/src/main/java/org/eclipse/digitaltwin/basyx/aasservice/client/internal/AssetAdministrationShellServiceApi.java index 72f062022..31b1dcd28 100644 --- a/basyx.aasservice/basyx.aasservice-client/src/main/java/org/eclipse/digitaltwin/basyx/aasservice/client/internal/AssetAdministrationShellServiceApi.java +++ b/basyx.aasservice/basyx.aasservice-client/src/main/java/org/eclipse/digitaltwin/basyx/aasservice/client/internal/AssetAdministrationShellServiceApi.java @@ -24,14 +24,14 @@ ******************************************************************************/ package org.eclipse.digitaltwin.basyx.aasservice.client.internal; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; import java.io.File; +import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.URI; import java.net.http.HttpClient; +import java.net.http.HttpHeaders; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.nio.channels.Channels; @@ -39,12 +39,14 @@ import java.time.Duration; import java.util.ArrayList; import java.util.List; +import java.util.Optional; import java.util.StringJoiner; +import java.util.UUID; import java.util.function.Consumer; import javax.annotation.processing.Generated; -import org.apache.http.HttpEntity; +import org.apache.http.entity.ContentType; import org.apache.http.entity.mime.MultipartEntityBuilder; import org.eclipse.digitaltwin.aas4j.v3.dataformat.json.JsonMapperFactory; import org.eclipse.digitaltwin.aas4j.v3.dataformat.json.SimpleAbstractTypeResolverFactory; @@ -57,6 +59,8 @@ import org.eclipse.digitaltwin.basyx.client.internal.Pair; import org.eclipse.digitaltwin.basyx.core.pagination.CursorResult; import org.eclipse.digitaltwin.basyx.http.description.ServiceDescription; +import org.eclipse.digitaltwin.basyx.http.pagination.Base64UrlEncodedCursorResult; +import org.springframework.util.MimeTypeUtils; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; @@ -263,7 +267,7 @@ private HttpRequest.Builder deleteThumbnailRequestBuilder() throws ApiException HttpRequest.Builder localVarRequestBuilder = HttpRequest.newBuilder(); - String localVarPath = "/assetinformation/thumbnail"; + String localVarPath = "/asset-information/thumbnail"; localVarRequestBuilder.uri(URI.create(memberVarBaseUri + localVarPath)); @@ -278,7 +282,7 @@ private HttpRequest.Builder deleteThumbnailRequestBuilder() throws ApiException } return localVarRequestBuilder; } - + /** * Returns all submodel references * @@ -293,70 +297,33 @@ private HttpRequest.Builder deleteThumbnailRequestBuilder() throws ApiException * if fails to make API call */ public CursorResult> getAllSubmodelReferences(Integer limit, String cursor) throws ApiException { + ApiResponse>> localVarResponse = getAllSubmodelReferencesApiResponse(limit, cursor); + return localVarResponse.getData(); + } - ApiResponse>> localVarResponse = getAllSubmodelReferencesWithHttpInfo(limit, cursor); - return localVarResponse.getData(); - } - - /** - * Returns all submodel references - * - * @param limit - * The maximum number of elements in the response array (optional) - * @param cursor - * A server-generated identifier retrieved from pagingMetadata that - * specifies from which position the result listing should continue - * (optional) - * @return ApiResponse<CursorResult<List<Reference>>> - * @throws ApiException - * if fails to make API call - */ - public ApiResponse>> getAllSubmodelReferencesWithHttpInfo(Integer limit, String cursor) throws ApiException { - return getAllSubmodelReferencesWithHttpInfoNoUrlEncoding(limit, cursor); - - } - - - /** - * Returns all submodel references - * - * @param limit - * The maximum number of elements in the response array (optional) - * @param cursor - * A server-generated identifier retrieved from pagingMetadata that - * specifies from which position the result listing should continue - * (optional) - * @return ApiResponse<CursorResult<List<Reference>>> - * @throws ApiException - * if fails to make API call - */ - public ApiResponse>> getAllSubmodelReferencesWithHttpInfoNoUrlEncoding(Integer limit, String cursor) throws ApiException { - HttpRequest.Builder localVarRequestBuilder = getAllSubmodelReferencesRequestBuilder(limit, cursor); + private ApiResponse>> getAllSubmodelReferencesApiResponse(Integer limit, String cursor) throws ApiException { + HttpRequest.Builder localVarRequestBuilder = getAllSubmodelReferencesRequestBuilder(limit, cursor); try { - HttpResponse localVarResponse = memberVarHttpClient.send( - localVarRequestBuilder.build(), - HttpResponse.BodyHandlers.ofInputStream()); - if (memberVarResponseInterceptor != null) { - memberVarResponseInterceptor.accept(localVarResponse); + HttpResponse localVarResponse = memberVarHttpClient.send(localVarRequestBuilder.build(), HttpResponse.BodyHandlers.ofInputStream()); + if (memberVarResponseInterceptor != null) { + memberVarResponseInterceptor.accept(localVarResponse); } - try { - if (localVarResponse.statusCode()/ 100 != 2) { - throw getApiException("getAllSubmodelReferences", localVarResponse); - } - return new ApiResponse>>( - localVarResponse.statusCode(), - localVarResponse.headers().map(), - localVarResponse.body() == null ? null : memberVarObjectMapper.readValue(localVarResponse.body(), new TypeReference>>() { + try { + if (localVarResponse.statusCode() / 100 != 2) { + throw getApiException("getAllSubmodelReferences", localVarResponse); + } + return new ApiResponse>>( + localVarResponse.statusCode(), localVarResponse.headers().map(), + localVarResponse.body() == null ? null : memberVarObjectMapper.readValue(localVarResponse.body(), new TypeReference>>() { }) // closes the InputStream - ); - } finally { - } - } catch (IOException e) { - throw new ApiException(e); - } - catch (InterruptedException e) { - Thread.currentThread().interrupt(); - throw new ApiException(e); + ); + } finally { + } + } catch (IOException e) { + throw new ApiException(e); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new ApiException(e); } } @@ -715,58 +682,56 @@ public File getThumbnail() throws ApiException { return localVarResponse.getData(); } - /** - * - * - * @return ApiResponse<File> - * @throws ApiException if fails to make API call - */ - public ApiResponse getThumbnailWithHttpInfo() throws ApiException { - return getThumbnailWithHttpInfoNoUrlEncoding(); - - } - + /** + * + * + * @return ApiResponse<File> + * @throws ApiException + * if fails to make API call + */ + public ApiResponse getThumbnailWithHttpInfo() throws ApiException { + return getThumbnailWithHttpInfoNoUrlEncoding(); + } - /** - * - * - * @return ApiResponse<File> - * @throws ApiException if fails to make API call - */ - public ApiResponse getThumbnailWithHttpInfoNoUrlEncoding() throws ApiException { - HttpRequest.Builder localVarRequestBuilder = getThumbnailRequestBuilder(); - try { - HttpResponse localVarResponse = memberVarHttpClient.send( - localVarRequestBuilder.build(), - HttpResponse.BodyHandlers.ofInputStream()); - if (memberVarResponseInterceptor != null) { - memberVarResponseInterceptor.accept(localVarResponse); - } - try { - if (localVarResponse.statusCode()/ 100 != 2) { - throw getApiException("getThumbnail", localVarResponse); - } - return new ApiResponse( - localVarResponse.statusCode(), - localVarResponse.headers().map(), - localVarResponse.body() == null ? null : memberVarObjectMapper.readValue(localVarResponse.body(), new TypeReference() {}) // closes the InputStream - ); - } finally { - } - } catch (IOException e) { - throw new ApiException(e); - } - catch (InterruptedException e) { - Thread.currentThread().interrupt(); - throw new ApiException(e); - } - } + /** + * @return ApiResponse<File> + * @throws ApiException + * if fails to make API call + */ + public ApiResponse getThumbnailWithHttpInfoNoUrlEncoding() throws ApiException { + HttpRequest.Builder localVarRequestBuilder = getThumbnailRequestBuilder(); + try { + HttpResponse localVarResponse = memberVarHttpClient.send(localVarRequestBuilder.build(), HttpResponse.BodyHandlers.ofInputStream()); + if (memberVarResponseInterceptor != null) { + memberVarResponseInterceptor.accept(localVarResponse); + } + if (localVarResponse.statusCode() / 100 != 2) { + throw getApiException("getThumbnail", localVarResponse); + } + if (localVarResponse.body() == null) { + return new ApiResponse(localVarResponse.statusCode(), localVarResponse.headers().map(), null); + } else { + File tempFile = File.createTempFile(buildUniqueFilename(), extractFileName(localVarResponse.headers())); + try (FileOutputStream out = new FileOutputStream(tempFile)) { + localVarResponse.body().transferTo(out); + return new ApiResponse(localVarResponse.statusCode(), localVarResponse.headers().map(), tempFile); + } finally { + localVarResponse.body().close(); + } + } + } catch (IOException e) { + throw new ApiException(e); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new ApiException(e); + } + } private HttpRequest.Builder getThumbnailRequestBuilder() throws ApiException { HttpRequest.Builder localVarRequestBuilder = HttpRequest.newBuilder(); - String localVarPath = "/assetinformation/thumbnail"; + String localVarPath = "/asset-information/thumbnail"; localVarRequestBuilder.uri(URI.create(memberVarBaseUri + localVarPath)); @@ -1060,126 +1025,98 @@ private HttpRequest.Builder putAssetInformationRequestBuilder(AssetInformation a } return localVarRequestBuilder; } - /** - * - * - * @param fileName (optional) - * @param _file (optional) - * @throws ApiException if fails to make API call - */ - public void putThumbnail(String fileName, File _file) throws ApiException { + + /** + * + * @param fileName + * @param contentTypeStr + * @param inputStream + * @throws ApiException + */ + public void putThumbnail(String fileName, String contentTypeStr, InputStream inputStream) throws ApiException { + ContentType contentType = ContentType.DEFAULT_BINARY; + try { + contentType = ContentType.parse(contentTypeStr); + } catch (Exception e) { + System.err.println("Error parsing content type: " + e.getMessage()); + } - putThumbnailWithHttpInfo(fileName, _file); - } + putThumbnailWithHttpInfoNoUrlEncoding(fileName, contentType, inputStream); + } - /** - * - * - * @param fileName (optional) - * @param _file (optional) - * @return ApiResponse<Void> - * @throws ApiException if fails to make API call - */ - public ApiResponse putThumbnailWithHttpInfo(String fileName, File _file) throws ApiException { - return putThumbnailWithHttpInfoNoUrlEncoding(fileName, _file); - - } + private ApiResponse putThumbnailWithHttpInfoNoUrlEncoding(String fileName, ContentType contentType, InputStream inputStream) throws ApiException { + HttpRequest.Builder localVarRequestBuilder = putThumbnailRequestBuilder(fileName, contentType, inputStream); + try { + HttpResponse localVarResponse = memberVarHttpClient.send(localVarRequestBuilder.build(), HttpResponse.BodyHandlers.ofInputStream()); + if (memberVarResponseInterceptor != null) { + memberVarResponseInterceptor.accept(localVarResponse); + } + try { + if (localVarResponse.statusCode() / 100 != 2) { + throw getApiException("putThumbnail", localVarResponse); + } + return new ApiResponse(localVarResponse.statusCode(), localVarResponse.headers().map(), null); + } finally { + localVarResponse.body().close(); + } + } catch (IOException e) { + throw new ApiException(e); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new ApiException(e); + } + } + private HttpRequest.Builder putThumbnailRequestBuilder(String fileName, ContentType contentType, InputStream inputStream) throws ApiException { + HttpRequest.Builder localVarRequestBuilder = HttpRequest.newBuilder(); + String localVarPath = "/asset-information/thumbnail"; + localVarRequestBuilder.uri(URI.create(memberVarBaseUri + localVarPath)); + localVarRequestBuilder.header("Accept", "application/json"); - /** - * - * - * @param fileName (optional) - * @param _file (optional) - * @return ApiResponse<Void> - * @throws ApiException if fails to make API call - */ - public ApiResponse putThumbnailWithHttpInfoNoUrlEncoding(String fileName, File _file) throws ApiException { - HttpRequest.Builder localVarRequestBuilder = putThumbnailRequestBuilder(fileName, _file); - try { - HttpResponse localVarResponse = memberVarHttpClient.send( - localVarRequestBuilder.build(), - HttpResponse.BodyHandlers.ofInputStream()); - if (memberVarResponseInterceptor != null) { - memberVarResponseInterceptor.accept(localVarResponse); - } - try { - if (localVarResponse.statusCode()/ 100 != 2) { - throw getApiException("putThumbnail", localVarResponse); - } - return new ApiResponse( - localVarResponse.statusCode(), - localVarResponse.headers().map(), - null - ); - } finally { - // Drain the InputStream - while (localVarResponse.body().read() != -1) { - // Ignore - } - localVarResponse.body().close(); - } - } catch (IOException e) { - throw new ApiException(e); - } - catch (InterruptedException e) { - Thread.currentThread().interrupt(); - throw new ApiException(e); - } - } + // Building multipart/form-data + var multiPartBuilder = MultipartEntityBuilder.create(); + multiPartBuilder.addTextBody("fileName", fileName); + multiPartBuilder.addBinaryBody("file", inputStream, contentType, fileName); + var entity = multiPartBuilder.build(); + + Pipe pipe; + try { + pipe = Pipe.open(); + } catch (IOException e) { + throw new RuntimeException(e); + } + new Thread(() -> { + try (OutputStream outputStream = Channels.newOutputStream(pipe.sink())) { + entity.writeTo(outputStream); + } catch (IOException e) { + e.printStackTrace(); + } + }).start(); - private HttpRequest.Builder putThumbnailRequestBuilder(String fileName, File _file) throws ApiException { + HttpRequest.BodyPublisher formDataPublisher = HttpRequest.BodyPublishers.ofInputStream(() -> Channels.newInputStream(pipe.source())); - HttpRequest.Builder localVarRequestBuilder = HttpRequest.newBuilder(); + localVarRequestBuilder.header("Content-Type", entity.getContentType().getValue()).method("PUT", formDataPublisher); - String localVarPath = "/assetinformation/thumbnail"; + if (memberVarReadTimeout != null) { + localVarRequestBuilder.timeout(memberVarReadTimeout); + } + if (memberVarInterceptor != null) { + memberVarInterceptor.accept(localVarRequestBuilder); + } + return localVarRequestBuilder; + } - localVarRequestBuilder.uri(URI.create(memberVarBaseUri + localVarPath)); + private static String extractFileName(HttpHeaders headers) { + Optional contentType = headers.firstValue("Content-Type"); + try { + return "." + MimeTypeUtils.parseMimeType(contentType.get()).getSubtype(); + } catch (Exception e) { + return ".tmp"; + } + } - localVarRequestBuilder.header("Accept", "application/json"); + private static String buildUniqueFilename() { + return UUID.randomUUID().toString(); + } - MultipartEntityBuilder multiPartBuilder = MultipartEntityBuilder.create(); - boolean hasFiles = false; - multiPartBuilder.addTextBody("fileName", fileName.toString()); - multiPartBuilder.addBinaryBody("file", _file); - hasFiles = true; - HttpEntity entity = multiPartBuilder.build(); - HttpRequest.BodyPublisher formDataPublisher; - if (hasFiles) { - Pipe pipe; - try { - pipe = Pipe.open(); - } catch (IOException e) { - throw new RuntimeException(e); - } - new Thread(() -> { - try (OutputStream outputStream = Channels.newOutputStream(pipe.sink())) { - entity.writeTo(outputStream); - } catch (IOException e) { - e.printStackTrace(); - } - }).start(); - formDataPublisher = HttpRequest.BodyPublishers.ofInputStream(() -> Channels.newInputStream(pipe.source())); - } else { - ByteArrayOutputStream formOutputStream = new ByteArrayOutputStream(); - try { - entity.writeTo(formOutputStream); - } catch (IOException e) { - throw new RuntimeException(e); - } - formDataPublisher = HttpRequest.BodyPublishers - .ofInputStream(() -> new ByteArrayInputStream(formOutputStream.toByteArray())); - } - localVarRequestBuilder - .header("Content-Type", entity.getContentType().getValue()) - .method("PUT", formDataPublisher); - if (memberVarReadTimeout != null) { - localVarRequestBuilder.timeout(memberVarReadTimeout); - } - if (memberVarInterceptor != null) { - memberVarInterceptor.accept(localVarRequestBuilder); - } - return localVarRequestBuilder; - } - } diff --git a/basyx.aasservice/basyx.aasservice-client/src/test/java/org/eclipse/digitaltwin/basyx/aasservice/client/TestConnectedAasService.java b/basyx.aasservice/basyx.aasservice-client/src/test/java/org/eclipse/digitaltwin/basyx/aasservice/client/TestConnectedAasService.java index 4d62e9f01..2914c786b 100644 --- a/basyx.aasservice/basyx.aasservice-client/src/test/java/org/eclipse/digitaltwin/basyx/aasservice/client/TestConnectedAasService.java +++ b/basyx.aasservice/basyx.aasservice-client/src/test/java/org/eclipse/digitaltwin/basyx/aasservice/client/TestConnectedAasService.java @@ -25,15 +25,11 @@ package org.eclipse.digitaltwin.basyx.aasservice.client; -import java.io.FileNotFoundException; -import java.io.IOException; - import org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell; import org.eclipse.digitaltwin.basyx.aasrepository.AasRepository; import org.eclipse.digitaltwin.basyx.aasrepository.http.DummyAasRepositoryComponent; import org.eclipse.digitaltwin.basyx.aasservice.AasService; import org.eclipse.digitaltwin.basyx.aasservice.AasServiceSuite; -import org.eclipse.digitaltwin.basyx.core.exceptions.FileDoesNotExistException; import org.eclipse.digitaltwin.basyx.core.pagination.PaginationInfo; import org.eclipse.digitaltwin.basyx.http.Base64UrlEncodedIdentifier; import org.junit.After; @@ -43,7 +39,7 @@ import org.springframework.context.ConfigurableApplicationContext; /** - * @author schnicke + * @author schnicke, mateusmolina */ public class TestConnectedAasService extends AasServiceSuite { @@ -73,37 +69,4 @@ protected AasService getAasService(AssetAdministrationShell shell) { return new ConnectedAasService("http://localhost:8080/shells/" + base64UrlEncodedId); } - - @Override - public void updateThumbnail() throws FileNotFoundException, IOException { - // TODO test not implemented - } - - @Override - public void setThumbnail() throws FileNotFoundException, IOException { - // TODO test not implemented - } - - @Override - public void getThumbnail() throws IOException { - // TODO test not implemented - } - - @Override - public void getNonExistingThumbnail() { - // TODO test not implemented - throw new FileDoesNotExistException(); - } - - @Override - public void deleteThumbnail() throws FileNotFoundException, IOException { - // TODO test not implemented - throw new FileDoesNotExistException(); - } - - @Override - public void deleteNonExistingThumbnail() throws IOException { - // TODO test not implemented - throw new FileDoesNotExistException(); - } } diff --git a/basyx.common/basyx.core/pom.xml b/basyx.common/basyx.core/pom.xml index c7d9035d1..e0c89f549 100644 --- a/basyx.common/basyx.core/pom.xml +++ b/basyx.common/basyx.core/pom.xml @@ -22,6 +22,14 @@ org.springframework.boot spring-boot-starter + + com.fasterxml.jackson.core + jackson-core + + + com.fasterxml.jackson.core + jackson-annotations + junit junit diff --git a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/pagination/CursorResult.java b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/pagination/CursorResult.java index 6ae388f87..9fdb776bf 100644 --- a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/pagination/CursorResult.java +++ b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/pagination/CursorResult.java @@ -30,13 +30,6 @@ public class CursorResult { private final String cursor; private final T result; - // For deserialization - @SuppressWarnings("unused") - private CursorResult() { - this.result = null; - this.cursor = null; - } - public CursorResult(String cursor, T result) { this.cursor = cursor; this.result = result; @@ -49,4 +42,4 @@ public String getCursor() { public T getResult() { return result; } -} \ No newline at end of file +} diff --git a/basyx.common/basyx.http/src/main/java/org/eclipse/digitaltwin/basyx/http/pagination/Base64UrlEncodedCursor.java b/basyx.common/basyx.http/src/main/java/org/eclipse/digitaltwin/basyx/http/pagination/Base64UrlEncodedCursor.java index 52f6ea3a0..78cc1737d 100644 --- a/basyx.common/basyx.http/src/main/java/org/eclipse/digitaltwin/basyx/http/pagination/Base64UrlEncodedCursor.java +++ b/basyx.common/basyx.http/src/main/java/org/eclipse/digitaltwin/basyx/http/pagination/Base64UrlEncodedCursor.java @@ -36,13 +36,15 @@ public class Base64UrlEncodedCursor { private final String encodedCursor; + private final String decodedCursor; public Base64UrlEncodedCursor(String encodedCursor) { this.encodedCursor = encodedCursor; + this.decodedCursor = encodedCursor == null ? null : Base64UrlEncoder.decode(encodedCursor); } public String getDecodedCursor() { - return Base64UrlEncoder.decode(encodedCursor); + return decodedCursor; } public String getEncodedCursor() { diff --git a/basyx.common/basyx.http/src/main/java/org/eclipse/digitaltwin/basyx/http/pagination/Base64UrlEncodedCursorResult.java b/basyx.common/basyx.http/src/main/java/org/eclipse/digitaltwin/basyx/http/pagination/Base64UrlEncodedCursorResult.java new file mode 100644 index 000000000..e795df371 --- /dev/null +++ b/basyx.common/basyx.http/src/main/java/org/eclipse/digitaltwin/basyx/http/pagination/Base64UrlEncodedCursorResult.java @@ -0,0 +1,56 @@ +/******************************************************************************* + * Copyright (C) 2024 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + +package org.eclipse.digitaltwin.basyx.http.pagination; + +import org.eclipse.digitaltwin.basyx.core.pagination.CursorResult; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * A encoded implementation of CursorResult. To be used during deserialization. + * + * @author mateusmolina + * + */ +public class Base64UrlEncodedCursorResult extends CursorResult { + + private final Base64UrlEncodedCursor encodedCursor; + + @JsonCreator + public Base64UrlEncodedCursorResult(@JsonProperty("paging_metadata") PagedResultPagingMetadata pagingMetadata, @JsonProperty("result") T result) { + this(pagingMetadata == null ? null : new Base64UrlEncodedCursor(pagingMetadata.getCursor()), result); + } + + public Base64UrlEncodedCursorResult(Base64UrlEncodedCursor base64UrlEncodedCursor, T result) { + super(base64UrlEncodedCursor == null ? null : base64UrlEncodedCursor.getDecodedCursor(), result); + encodedCursor = base64UrlEncodedCursor; + } + + public Base64UrlEncodedCursor getEncodedCursor() { + return encodedCursor; + } +} diff --git a/basyx.submodelrepository/basyx.submodelrepository-client/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/client/ConnectedSubmodelRepository.java b/basyx.submodelrepository/basyx.submodelrepository-client/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/client/ConnectedSubmodelRepository.java index be13d0d3f..e7589bf51 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-client/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/client/ConnectedSubmodelRepository.java +++ b/basyx.submodelrepository/basyx.submodelrepository-client/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/client/ConnectedSubmodelRepository.java @@ -28,6 +28,7 @@ import java.io.File; import java.io.InputStream; +import java.util.ArrayList; import java.util.List; import org.eclipse.digitaltwin.aas4j.v3.model.OperationVariable; @@ -37,12 +38,12 @@ import org.eclipse.digitaltwin.basyx.core.exceptions.CollidingIdentifierException; import org.eclipse.digitaltwin.basyx.core.exceptions.ElementDoesNotExistException; import org.eclipse.digitaltwin.basyx.core.exceptions.ElementNotAFileException; -import org.eclipse.digitaltwin.basyx.core.exceptions.FeatureNotImplementedException; import org.eclipse.digitaltwin.basyx.core.exceptions.FileDoesNotExistException; import org.eclipse.digitaltwin.basyx.core.exceptions.MissingIdentifierException; import org.eclipse.digitaltwin.basyx.core.pagination.CursorResult; import org.eclipse.digitaltwin.basyx.core.pagination.PaginationInfo; import org.eclipse.digitaltwin.basyx.http.Base64UrlEncodedIdentifier; +import org.eclipse.digitaltwin.basyx.http.Base64UrlEncoder; import org.eclipse.digitaltwin.basyx.submodelrepository.SubmodelRepository; import org.eclipse.digitaltwin.basyx.submodelrepository.client.internal.SubmodelRepositoryApi; import org.eclipse.digitaltwin.basyx.submodelservice.client.ConnectedSubmodelService; @@ -54,7 +55,7 @@ /** * Provides access to a Submodel Repository on a remote server * - * @author schnicke + * @author schnicke, mateusmolina */ public class ConnectedSubmodelRepository implements SubmodelRepository { @@ -131,12 +132,18 @@ public ConnectedSubmodelService getConnectedSubmodelService(String submodelId) t } } - /** - * Not Implemented - */ @Override public CursorResult> getAllSubmodels(PaginationInfo pInfo) { - throw new FeatureNotImplementedException(); + try{ + String encodedCursor = pInfo.getCursor() == null ? null : Base64UrlEncoder.encode(pInfo.getCursor()); + return repoApi.getAllSubmodels(null, null, pInfo.getLimit(), encodedCursor, null, null); + } catch (ApiException e) { + if (e.getCode() == HttpStatus.INTERNAL_SERVER_ERROR.value()) + return new CursorResult<>("", new ArrayList<>()); + else + throw e; + } + } @Override @@ -184,44 +191,29 @@ public OperationVariable[] invokeOperation(String submodelId, String idShortPath return getConnectedSubmodelService(submodelId).invokeOperation(idShortPath, input); } - /** - * Not Implemented - */ @Override public SubmodelValueOnly getSubmodelByIdValueOnly(String submodelId) throws ElementDoesNotExistException { - throw new FeatureNotImplementedException(); + return new SubmodelValueOnly(getSubmodelByIdMetadata(submodelId).getSubmodelElements()); } - /** - * Not Implemented - */ @Override public Submodel getSubmodelByIdMetadata(String submodelId) throws ElementDoesNotExistException { - throw new FeatureNotImplementedException(); + return repoApi.getSubmodelById(submodelId, null, null); } - /** - * Not Implemented - */ @Override public File getFileByPathSubmodel(String submodelId, String idShortPath) throws ElementDoesNotExistException, ElementNotAFileException, FileDoesNotExistException { - throw new FeatureNotImplementedException(); + return getConnectedSubmodelService(submodelId).getFileByPath(idShortPath); } - /** - * Not Implemented - */ @Override public void setFileValue(String submodelId, String idShortPath, String fileName, InputStream inputStream) throws ElementDoesNotExistException, ElementNotAFileException { - throw new FeatureNotImplementedException(); + getConnectedSubmodelService(submodelId).setFileValue(idShortPath, fileName, inputStream); } - /** - * Not Implemented - */ @Override public void deleteFileValue(String submodelId, String idShortPath) throws ElementDoesNotExistException, ElementNotAFileException, FileDoesNotExistException { - throw new FeatureNotImplementedException(); + getConnectedSubmodelService(submodelId).deleteFileValue(idShortPath); } private String getSubmodelUrl(String submodelId) { @@ -242,7 +234,7 @@ private RuntimeException mapExceptionSubmodelAccess(String submodelId, ApiExcept @Override public void patchSubmodelElements(String submodelId, List submodelElementList) { - throw new FeatureNotImplementedException(); + getConnectedSubmodelService(submodelId).patchSubmodelElements(submodelElementList); } } diff --git a/basyx.submodelrepository/basyx.submodelrepository-client/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/client/internal/SubmodelRepositoryApi.java b/basyx.submodelrepository/basyx.submodelrepository-client/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/client/internal/SubmodelRepositoryApi.java index 6e4dcb347..fa0f78180 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-client/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/client/internal/SubmodelRepositoryApi.java +++ b/basyx.submodelrepository/basyx.submodelrepository-client/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/client/internal/SubmodelRepositoryApi.java @@ -43,6 +43,8 @@ import org.eclipse.digitaltwin.basyx.client.internal.ApiException; import org.eclipse.digitaltwin.basyx.client.internal.ApiResponse; import org.eclipse.digitaltwin.basyx.client.internal.Pair; +import org.eclipse.digitaltwin.basyx.core.pagination.CursorResult; +import org.eclipse.digitaltwin.basyx.http.pagination.Base64UrlEncodedCursorResult; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; @@ -500,4 +502,104 @@ private HttpRequest.Builder deleteSubmodelByIdRequestBuilder(String submodelIden } return localVarRequestBuilder; } + + /** + * Returns all Submodels + * + * @param semanticId + * The value of the semantic id reference (BASE64-URL-encoded) + * (optional) + * @param idShort + * The Asset Administration Shell’s IdShort (optional) + * @param limit + * The maximum number of elements in the response array (optional) + * @param cursor + * A server-generated identifier retrieved from pagingMetadata that + * specifies from which position the result listing should continue + * (optional) + * @param level + * Determines the structural depth of the respective resource content + * (optional, default to deep) + * @param extent + * Determines to which extent the resource is being serialized + * (optional, default to withoutBlobValue) + * @return GetSubmodelsResult + * @throws ApiException + * if fails to make API call + */ + public CursorResult> getAllSubmodels(String semanticId, String idShort, Integer limit, String cursor, String level, String extent) throws ApiException { + ApiResponse>> localVarResponse = getAllSubmodelsApiResponse(semanticId, idShort, limit, cursor, level, extent); + + return localVarResponse.getData(); + } + + private ApiResponse>> getAllSubmodelsApiResponse(String semanticId, String idShort, Integer limit, String cursor, String level, String extent) throws ApiException { + HttpRequest.Builder localVarRequestBuilder = getAllSubmodelsRequestBuilder(semanticId, idShort, limit, cursor, level, extent); + try { + HttpResponse localVarResponse = memberVarHttpClient.send(localVarRequestBuilder.build(), HttpResponse.BodyHandlers.ofInputStream()); + if (memberVarResponseInterceptor != null) { + memberVarResponseInterceptor.accept(localVarResponse); + } + try { + if (localVarResponse.statusCode() / 100 != 2) { + throw getApiException("getAllSubmodels", localVarResponse); + } + return new ApiResponse<>(localVarResponse.statusCode(), localVarResponse.headers().map(), + localVarResponse.body() == null ? null : memberVarObjectMapper.readValue(localVarResponse.body(), new TypeReference>>() { + }) // closes the InputStream + ); + } finally { + } + } catch (IOException e) { + throw new ApiException(e); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new ApiException(e); + } + } + + private HttpRequest.Builder getAllSubmodelsRequestBuilder(String semanticId, String idShort, Integer limit, String cursor, String level, String extent) throws ApiException { + + HttpRequest.Builder localVarRequestBuilder = HttpRequest.newBuilder(); + + String localVarPath = "/submodels"; + + List localVarQueryParams = new ArrayList<>(); + StringJoiner localVarQueryStringJoiner = new StringJoiner("&"); + String localVarQueryParameterBaseName; + localVarQueryParameterBaseName = "semanticId"; + localVarQueryParams.addAll(ApiClient.parameterToPairs("semanticId", semanticId)); + localVarQueryParameterBaseName = "idShort"; + localVarQueryParams.addAll(ApiClient.parameterToPairs("idShort", idShort)); + localVarQueryParameterBaseName = "limit"; + localVarQueryParams.addAll(ApiClient.parameterToPairs("limit", limit)); + localVarQueryParameterBaseName = "cursor"; + localVarQueryParams.addAll(ApiClient.parameterToPairs("cursor", cursor)); + localVarQueryParameterBaseName = "level"; + localVarQueryParams.addAll(ApiClient.parameterToPairs("level", level)); + localVarQueryParameterBaseName = "extent"; + localVarQueryParams.addAll(ApiClient.parameterToPairs("extent", extent)); + + if (!localVarQueryParams.isEmpty() || localVarQueryStringJoiner.length() != 0) { + StringJoiner queryJoiner = new StringJoiner("&"); + localVarQueryParams.forEach(p -> queryJoiner.add(p.getName() + '=' + p.getValue())); + if (localVarQueryStringJoiner.length() != 0) { + queryJoiner.add(localVarQueryStringJoiner.toString()); + } + localVarRequestBuilder.uri(URI.create(memberVarBaseUri + localVarPath + '?' + queryJoiner.toString())); + } else { + localVarRequestBuilder.uri(URI.create(memberVarBaseUri + localVarPath)); + } + + localVarRequestBuilder.header("Accept", "application/json"); + + localVarRequestBuilder.method("GET", HttpRequest.BodyPublishers.noBody()); + if (memberVarReadTimeout != null) { + localVarRequestBuilder.timeout(memberVarReadTimeout); + } + if (memberVarInterceptor != null) { + memberVarInterceptor.accept(localVarRequestBuilder); + } + return localVarRequestBuilder; + } } diff --git a/basyx.submodelrepository/basyx.submodelrepository-client/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/client/TestConnectedSubmodelRepository.java b/basyx.submodelrepository/basyx.submodelrepository-client/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/client/TestConnectedSubmodelRepository.java index dd0a1a08a..4ee14b0b7 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-client/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/client/TestConnectedSubmodelRepository.java +++ b/basyx.submodelrepository/basyx.submodelrepository-client/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/client/TestConnectedSubmodelRepository.java @@ -25,16 +25,11 @@ package org.eclipse.digitaltwin.basyx.submodelrepository.client; -import java.io.FileNotFoundException; -import java.io.IOException; import java.util.Collection; import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; import org.eclipse.digitaltwin.basyx.core.exceptions.ElementDoesNotExistException; -import org.eclipse.digitaltwin.basyx.core.exceptions.ElementNotAFileException; -import org.eclipse.digitaltwin.basyx.core.exceptions.FileDoesNotExistException; -import org.eclipse.digitaltwin.basyx.core.exceptions.IdentificationMismatchException; -import org.eclipse.digitaltwin.basyx.core.exceptions.NotInvokableException; +import org.eclipse.digitaltwin.basyx.core.exceptions.MissingIdentifierException; import org.eclipse.digitaltwin.basyx.core.pagination.PaginationInfo; import org.eclipse.digitaltwin.basyx.submodelrepository.SubmodelRepository; import org.eclipse.digitaltwin.basyx.submodelrepository.core.SubmodelRepositorySuite; @@ -52,7 +47,7 @@ * Whenever a feature is implemented, the respective test here has to be * removed. * - * @author schnicke + * @author schnicke, mateusmolina */ public class TestConnectedSubmodelRepository extends SubmodelRepositorySuite { private static ConfigurableApplicationContext appContext; @@ -93,122 +88,17 @@ protected ConnectedSubmodelRepository getSubmodelRepository(Collection } @Override - public void getSubmodelElement() { - // TODO Auto-generated method stub - - } - - @Override - public void getPaginatedSubmodel() { - // TODO Auto-generated method stub - - } - - @Override - public void invokeOperation() { - // TODO Auto-generated method stub - - } - - @Override - public void updateFileSMEWithFileSME() throws FileNotFoundException, IOException { - // TODO Auto-generated method stub - - } - - @Override - public void updateFileSMEWithNonFileSME() throws FileNotFoundException, IOException { - // TODO Auto-generated method stub - - } - - @Override - public void updateFile() { - // TODO Auto-generated method stub - - } - - @Override - public void getFile() { - // TODO Auto-generated method stub - - } - - @Override - public void getNonExistingFile() { - // TODO Auto-generated method stub - - throw new FileDoesNotExistException(); - } - - @Override - public void getAllSubmodelsPreconfigured() { - // TODO Auto-generated method stub - - } - - @Override - public void deleteNonExistingFile() throws IOException { - // TODO Auto-generated method stub - - throw new FileDoesNotExistException(); - } - - @Override - public void getFileFromNonFileSME() { - // TODO Auto-generated method stub - throw new ElementNotAFileException(); - } - - @Override - public void deleteFile() throws ElementDoesNotExistException, ElementNotAFileException, FileNotFoundException, IOException { - // TODO Auto-generated method stub - - } - - @Override - public void invokeNonOperation() { - // TODO Auto-generated method stub - - throw new NotInvokableException(); - } - - @Override - public void getAllSubmodelsEmpty() { - // TODO Auto-generated method stub - - } - - @Override - public void getSubmodelElements() { - // TODO Auto-generated method stub - - } - - @Override + @Test(expected = MissingIdentifierException.class) public void updateExistingSubmodelWithMismatchId() { - // TODO Auto-generated method stub - throw new IdentificationMismatchException(); - } - - @Override - public void deleteFileSubmodelElementDeletesFile() throws ElementDoesNotExistException, ElementNotAFileException, FileNotFoundException, IOException { - // TODO Auto-generated method stub + // TODO There should be a way to differentiate between both exceptions through + // the Http response + super.updateExistingSubmodelWithMismatchId(); } @Override protected boolean fileExistsInStorage(String fileValue) { - // TODO Auto-generated method stub - return false; - } - - @Override - public void getPaginatedSubmodelElement() { - // TODO Auto-generated method stub - } - - @Override - public void paginationCursor() { - // TODO Auto-generated method stub + java.io.File file = new java.io.File(fileValue); + + return file.exists(); } } diff --git a/basyx.submodelrepository/basyx.submodelrepository-core/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/core/SubmodelRepositorySuite.java b/basyx.submodelrepository/basyx.submodelrepository-core/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/core/SubmodelRepositorySuite.java index 13a4024a8..cc38fe32f 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-core/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/core/SubmodelRepositorySuite.java +++ b/basyx.submodelrepository/basyx.submodelrepository-core/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/core/SubmodelRepositorySuite.java @@ -62,7 +62,7 @@ * */ public abstract class SubmodelRepositorySuite extends SubmodelServiceSuite { - private static final PaginationInfo NO_LIMIT_PAGINATION_INFO = new PaginationInfo(0, null); + private static final PaginationInfo NO_LIMIT_PAGINATION_INFO = new PaginationInfo(null, null); private static final String EMPTY_ID = " "; private static final String NULL_ID = null; diff --git a/basyx.submodelservice/basyx.submodelservice-client/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/client/ConnectedSubmodelService.java b/basyx.submodelservice/basyx.submodelservice-client/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/client/ConnectedSubmodelService.java index 77d614432..3fb627832 100644 --- a/basyx.submodelservice/basyx.submodelservice-client/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/client/ConnectedSubmodelService.java +++ b/basyx.submodelservice/basyx.submodelservice-client/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/client/ConnectedSubmodelService.java @@ -30,31 +30,39 @@ import java.io.InputStream; import java.util.List; +import org.eclipse.digitaltwin.aas4j.v3.model.OperationRequest; +import org.eclipse.digitaltwin.aas4j.v3.model.OperationResult; import org.eclipse.digitaltwin.aas4j.v3.model.OperationVariable; import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultOperationRequest; import org.eclipse.digitaltwin.basyx.client.internal.ApiException; import org.eclipse.digitaltwin.basyx.core.exceptions.ElementDoesNotExistException; import org.eclipse.digitaltwin.basyx.core.exceptions.ElementNotAFileException; -import org.eclipse.digitaltwin.basyx.core.exceptions.FeatureNotImplementedException; import org.eclipse.digitaltwin.basyx.core.exceptions.FileDoesNotExistException; +import org.eclipse.digitaltwin.basyx.core.exceptions.NotInvokableException; +import org.eclipse.digitaltwin.basyx.core.exceptions.OperationDelegationException; import org.eclipse.digitaltwin.basyx.core.pagination.CursorResult; import org.eclipse.digitaltwin.basyx.core.pagination.PaginationInfo; +import org.eclipse.digitaltwin.basyx.http.Base64UrlEncoder; import org.eclipse.digitaltwin.basyx.submodelservice.SubmodelService; import org.eclipse.digitaltwin.basyx.submodelservice.client.internal.SubmodelServiceApi; import org.eclipse.digitaltwin.basyx.submodelservice.value.SubmodelElementValue; +import org.eclipse.digitaltwin.basyx.submodelservice.value.factory.SubmodelElementValueMapperFactory; import org.springframework.http.HttpStatus; /** * Provides access to a Submodel Service on a remote server - regardless if it * is hosted on a Submodel Repository or standalone * - * @author schnicke + * @author schnicke, mateusmolina */ public class ConnectedSubmodelService implements SubmodelService { private SubmodelServiceApi serviceApi; + private final SubmodelElementValueMapperFactory submodelElementValueMapperFactory; + /** * * @param submodelServiceUrl @@ -63,6 +71,7 @@ public class ConnectedSubmodelService implements SubmodelService { */ public ConnectedSubmodelService(String submodelServiceUrl) { this.serviceApi = new SubmodelServiceApi(submodelServiceUrl); + this.submodelElementValueMapperFactory = new SubmodelElementValueMapperFactory(); } @Override @@ -129,42 +138,90 @@ public void deleteSubmodelElement(String idShortPath) throws ElementDoesNotExist } } - private RuntimeException mapExceptionSubmodelElementAccess(String idShortPath, ApiException e) { - if (e.getCode() == HttpStatus.NOT_FOUND.value()) { - return new ElementDoesNotExistException(idShortPath); - } - - return e; - } - @Override public CursorResult> getSubmodelElements(PaginationInfo pInfo) { - throw new FeatureNotImplementedException(); + String encodedCursor = pInfo.getCursor() == null ? null : Base64UrlEncoder.encode(pInfo.getCursor()); + return serviceApi.getAllSubmodelElements(pInfo.getLimit(), encodedCursor, null, null); } + /** + * Invoke synchronously + */ @Override public OperationVariable[] invokeOperation(String idShortPath, OperationVariable[] input) throws ElementDoesNotExistException { - throw new FeatureNotImplementedException(); + OperationRequest request = new DefaultOperationRequest.Builder().inputArguments(List.of(input)).build(); + try { + OperationResult result = serviceApi.invokeOperation(idShortPath, request); + + return result.getOutputArguments().toArray(new OperationVariable[0]); + } catch (ApiException e) { + throw mapExceptionOperationExecution(idShortPath, e); + } } @Override public void patchSubmodelElements(List submodelElementList) { - throw new FeatureNotImplementedException(); + submodelElementList.forEach(this::patchSubmodelElement); } @Override public File getFileByPath(String idShortPath) throws ElementDoesNotExistException, ElementNotAFileException, FileDoesNotExistException { - throw new FeatureNotImplementedException(); + try { + return serviceApi.getFileByPath(idShortPath); + } catch (ApiException e) { + throw mapExceptionFileAccess(idShortPath, e); + } } @Override public void setFileValue(String idShortPath, String fileName, InputStream inputStream) throws ElementDoesNotExistException, ElementNotAFileException { - throw new FeatureNotImplementedException(); + try { + serviceApi.putFileByPath(idShortPath, fileName, inputStream); + } catch (ApiException e) { + throw mapExceptionFileAccess(idShortPath, e); + } } @Override public void deleteFileValue(String idShortPath) throws ElementDoesNotExistException, ElementNotAFileException, FileDoesNotExistException { - throw new FeatureNotImplementedException(); + try { + serviceApi.deleteFileByPath(idShortPath); + } catch (ApiException e) { + throw mapExceptionFileAccess(idShortPath, e); + } + } + + private RuntimeException mapExceptionFileAccess(String idShortPath, ApiException e) { + if (e.getCode() == HttpStatus.NOT_FOUND.value()) + return new FileDoesNotExistException(idShortPath); + + if (e.getCode() == HttpStatus.PRECONDITION_FAILED.value()) + return new ElementNotAFileException(idShortPath); + + return e; + } + + private RuntimeException mapExceptionSubmodelElementAccess(String idShortPath, ApiException e) { + if (e.getCode() == HttpStatus.NOT_FOUND.value()) { + return new ElementDoesNotExistException(idShortPath); + } + + return e; + } + + private RuntimeException mapExceptionOperationExecution(String idShortPath, ApiException e) { + if (e.getCode() == HttpStatus.FAILED_DEPENDENCY.value()) + return new OperationDelegationException(); + + if (e.getCode() == HttpStatus.METHOD_NOT_ALLOWED.value()) + return new NotInvokableException(idShortPath); + + return mapExceptionSubmodelElementAccess(idShortPath, e); + } + + private void patchSubmodelElement(SubmodelElement submodelElement) { + SubmodelElementValue seValue = submodelElementValueMapperFactory.create(submodelElement).getValue(); + setSubmodelElementValue(submodelElement.getIdShort(), seValue); } } diff --git a/basyx.submodelservice/basyx.submodelservice-client/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/client/internal/SubmodelServiceApi.java b/basyx.submodelservice/basyx.submodelservice-client/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/client/internal/SubmodelServiceApi.java index e28a4c8be..fc5280e0c 100644 --- a/basyx.submodelservice/basyx.submodelservice-client/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/client/internal/SubmodelServiceApi.java +++ b/basyx.submodelservice/basyx.submodelservice-client/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/client/internal/SubmodelServiceApi.java @@ -24,27 +24,42 @@ ******************************************************************************/ package org.eclipse.digitaltwin.basyx.submodelservice.client.internal; +import java.io.File; +import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; import java.net.URI; import java.net.http.HttpClient; +import java.net.http.HttpHeaders; import java.net.http.HttpRequest; import java.net.http.HttpResponse; +import java.nio.channels.Channels; +import java.nio.channels.Pipe; import java.time.Duration; import java.util.ArrayList; import java.util.List; +import java.util.Optional; import java.util.StringJoiner; +import java.util.UUID; import java.util.function.Consumer; import javax.annotation.processing.Generated; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.mime.MultipartEntityBuilder; +import org.eclipse.digitaltwin.aas4j.v3.model.OperationRequest; +import org.eclipse.digitaltwin.aas4j.v3.model.OperationResult; import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement; import org.eclipse.digitaltwin.basyx.client.internal.ApiClient; import org.eclipse.digitaltwin.basyx.client.internal.ApiException; import org.eclipse.digitaltwin.basyx.client.internal.ApiResponse; import org.eclipse.digitaltwin.basyx.client.internal.Pair; +import org.eclipse.digitaltwin.basyx.core.pagination.CursorResult; +import org.eclipse.digitaltwin.basyx.http.pagination.Base64UrlEncodedCursorResult; import org.eclipse.digitaltwin.basyx.submodelservice.value.SubmodelElementValue; +import org.springframework.util.MimeTypeUtils; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; @@ -1021,4 +1036,456 @@ private HttpRequest.Builder deleteSubmodelElementByPathRequestBuilder(String idS } return localVarRequestBuilder; } + + /** + * Returns all submodel elements including their hierarchy + * + * @param limit + * The maximum number of elements in the response array (optional) + * @param cursor + * A server-generated identifier retrieved from pagingMetadata that + * specifies from which position the result listing should continue + * (optional) + * @param level + * Determines the structural depth of the respective resource content + * (optional, default to deep) + * @param extent + * Determines to which extent the resource is being serialized + * (optional, default to withoutBlobValue) + * @return CursorResult<List<SubmodelElement>> + * @throws ApiException + * if fails to make API call + */ + public CursorResult> getAllSubmodelElements(Integer limit, String cursor, String level, String extent) throws ApiException { + ApiResponse>> localVarResponse = getAllSubmodelElementsApiResponse(limit, cursor, level, extent); + + return localVarResponse.getData(); + } + + + private ApiResponse>> getAllSubmodelElementsApiResponse(Integer limit, String cursor, String level, String extent) throws ApiException { + HttpRequest.Builder localVarRequestBuilder = getAllSubmodelElementsRequestBuilder(limit, cursor, level, extent); + try { + HttpResponse localVarResponse = memberVarHttpClient.send(localVarRequestBuilder.build(), HttpResponse.BodyHandlers.ofInputStream()); + if (memberVarResponseInterceptor != null) { + memberVarResponseInterceptor.accept(localVarResponse); + } + try { + if (localVarResponse.statusCode() / 100 != 2) { + throw getApiException("getAllSubmodelElements", localVarResponse); + } + return new ApiResponse>>(localVarResponse.statusCode(), localVarResponse.headers().map(), + localVarResponse.body() == null ? null : memberVarObjectMapper.readValue(localVarResponse.body(), new TypeReference>>() { + }) // closes the InputStream + ); + } finally { + } + } catch (IOException e) { + throw new ApiException(e); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new ApiException(e); + } + } + + private HttpRequest.Builder getAllSubmodelElementsRequestBuilder(Integer limit, String cursor, String level, String extent) throws ApiException { + + HttpRequest.Builder localVarRequestBuilder = HttpRequest.newBuilder(); + + String localVarPath = "/submodel-elements"; + + List localVarQueryParams = new ArrayList<>(); + StringJoiner localVarQueryStringJoiner = new StringJoiner("&"); + String localVarQueryParameterBaseName; + localVarQueryParameterBaseName = "limit"; + localVarQueryParams.addAll(ApiClient.parameterToPairs("limit", limit)); + localVarQueryParameterBaseName = "cursor"; + localVarQueryParams.addAll(ApiClient.parameterToPairs("cursor", cursor)); + localVarQueryParameterBaseName = "level"; + localVarQueryParams.addAll(ApiClient.parameterToPairs("level", level)); + localVarQueryParameterBaseName = "extent"; + localVarQueryParams.addAll(ApiClient.parameterToPairs("extent", extent)); + + if (!localVarQueryParams.isEmpty() || localVarQueryStringJoiner.length() != 0) { + StringJoiner queryJoiner = new StringJoiner("&"); + localVarQueryParams.forEach(p -> queryJoiner.add(p.getName() + '=' + p.getValue())); + if (localVarQueryStringJoiner.length() != 0) { + queryJoiner.add(localVarQueryStringJoiner.toString()); + } + localVarRequestBuilder.uri(URI.create(memberVarBaseUri + localVarPath + '?' + queryJoiner.toString())); + } else { + localVarRequestBuilder.uri(URI.create(memberVarBaseUri + localVarPath)); + } + + localVarRequestBuilder.header("Accept", "application/json"); + + localVarRequestBuilder.method("GET", HttpRequest.BodyPublishers.noBody()); + if (memberVarReadTimeout != null) { + localVarRequestBuilder.timeout(memberVarReadTimeout); + } + if (memberVarInterceptor != null) { + memberVarInterceptor.accept(localVarRequestBuilder); + } + return localVarRequestBuilder; + } + + /** + * Uploads file content to an existing submodel element at a specified path + * within submodel elements hierarchy + * + * @param idShortPath + * IdShort path to the submodel element (dot-separated) (required) + * @param fileName + * (optional) + * @param inputStream + * (optional) + * @throws ApiException + * if fails to make API call + */ + public void putFileByPath(String idShortPath, String fileName, InputStream inputStream) throws ApiException { + putFileByPathApiResponse(idShortPath, fileName, inputStream); + } + + private ApiResponse putFileByPathApiResponse(String idShortPath, String fileName, InputStream inputStream) throws ApiException { + try { + HttpRequest.Builder localVarRequestBuilder = putFileByPathRequestBuilder(idShortPath, fileName, inputStream); + HttpResponse localVarResponse = memberVarHttpClient.send(localVarRequestBuilder.build(), HttpResponse.BodyHandlers.ofInputStream()); + if (memberVarResponseInterceptor != null) { + memberVarResponseInterceptor.accept(localVarResponse); + } + try { + if (localVarResponse.statusCode() / 100 != 2) { + throw getApiException("putFileByPath", localVarResponse); + } + return new ApiResponse(localVarResponse.statusCode(), localVarResponse.headers().map(), null); + } finally { + // Drain the InputStream + while (localVarResponse.body().read() != -1) { + // Ignore + } + localVarResponse.body().close(); + } + } catch (IOException e) { + throw new ApiException(e); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new ApiException(e); + } + } + + private HttpRequest.Builder putFileByPathRequestBuilder(String idShortPath, String fileName, InputStream inputStream) throws ApiException, IOException { + if (idShortPath == null) + throw new ApiException(400, "Missing the required parameter 'idShortPath' when calling putFileByPath"); + if (inputStream == null) + throw new ApiException(400, "Missing the required parameter 'inputStream' when calling putFileByPath"); + + HttpRequest.Builder localVarRequestBuilder = HttpRequest.newBuilder(); + + String localVarPath = "/submodel-elements/{idShortPath}/attachment".replace("{idShortPath}", ApiClient.urlEncode(idShortPath)); + + localVarRequestBuilder.uri(URI.create(memberVarBaseUri + localVarPath)); + + localVarRequestBuilder.header("Accept", "application/json"); + + var multiPartBuilder = MultipartEntityBuilder.create(); + multiPartBuilder.addTextBody("fileName", fileName); + multiPartBuilder.addBinaryBody("file", inputStream, ContentType.DEFAULT_BINARY, fileName); + + var entity = multiPartBuilder.build(); + HttpRequest.BodyPublisher formDataPublisher; + + Pipe pipe = Pipe.open(); + new Thread(() -> { + try (OutputStream outputStream = Channels.newOutputStream(pipe.sink())) { + entity.writeTo(outputStream); + } catch (IOException e) { + e.printStackTrace(); + } + }).start(); + + formDataPublisher = HttpRequest.BodyPublishers.ofInputStream(() -> Channels.newInputStream(pipe.source())); + + localVarRequestBuilder.header("Content-Type", entity.getContentType().getValue()).method("PUT", formDataPublisher); + + if (memberVarReadTimeout != null) { + localVarRequestBuilder.timeout(memberVarReadTimeout); + } + if (memberVarInterceptor != null) { + memberVarInterceptor.accept(localVarRequestBuilder); + } + return localVarRequestBuilder; + } + + /** + * Deletes file content of an existing submodel element at a specified path + * within submodel elements hierarchy + * + * @param idShortPath + * IdShort path to the submodel element (dot-separated) (required) + * @throws ApiException + * if fails to make API call + */ + public void deleteFileByPath(String idShortPath) throws ApiException { + + deleteFileByPathWithHttpInfo(idShortPath); + } + + /** + * Deletes file content of an existing submodel element at a specified path + * within submodel elements hierarchy + * + * @param idShortPath + * IdShort path to the submodel element (dot-separated) (required) + * @return ApiResponse<Void> + * @throws ApiException + * if fails to make API call + */ + public ApiResponse deleteFileByPathWithHttpInfo(String idShortPath) throws ApiException { + return deleteFileByPathWithHttpInfoNoUrlEncoding(idShortPath); + + } + + /** + * Deletes file content of an existing submodel element at a specified path + * within submodel elements hierarchy + * + * @param idShortPath + * IdShort path to the submodel element (dot-separated) (required) + * @return ApiResponse<Void> + * @throws ApiException + * if fails to make API call + */ + public ApiResponse deleteFileByPathWithHttpInfoNoUrlEncoding(String idShortPath) throws ApiException { + HttpRequest.Builder localVarRequestBuilder = deleteFileByPathRequestBuilder(idShortPath); + try { + HttpResponse localVarResponse = memberVarHttpClient.send(localVarRequestBuilder.build(), HttpResponse.BodyHandlers.ofInputStream()); + if (memberVarResponseInterceptor != null) { + memberVarResponseInterceptor.accept(localVarResponse); + } + try { + if (localVarResponse.statusCode() / 100 != 2) { + throw getApiException("deleteFileByPath", localVarResponse); + } + return new ApiResponse(localVarResponse.statusCode(), localVarResponse.headers().map(), null); + } finally { + // Drain the InputStream + while (localVarResponse.body().read() != -1) { + // Ignore + } + localVarResponse.body().close(); + } + } catch (IOException e) { + throw new ApiException(e); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new ApiException(e); + } + } + + private HttpRequest.Builder deleteFileByPathRequestBuilder(String idShortPath) throws ApiException { + // verify the required parameter 'idShortPath' is set + if (idShortPath == null) { + throw new ApiException(400, "Missing the required parameter 'idShortPath' when calling deleteFileByPath"); + } + + HttpRequest.Builder localVarRequestBuilder = HttpRequest.newBuilder(); + + String localVarPath = "/submodel-elements/{idShortPath}/attachment".replace("{idShortPath}", ApiClient.urlEncode(idShortPath.toString())); + + localVarRequestBuilder.uri(URI.create(memberVarBaseUri + localVarPath)); + + localVarRequestBuilder.header("Accept", "application/json"); + + localVarRequestBuilder.method("DELETE", HttpRequest.BodyPublishers.noBody()); + if (memberVarReadTimeout != null) { + localVarRequestBuilder.timeout(memberVarReadTimeout); + } + if (memberVarInterceptor != null) { + memberVarInterceptor.accept(localVarRequestBuilder); + } + return localVarRequestBuilder; + } + + /** + * Downloads file content from a specific submodel element from the Submodel at + * a specified path + * + * @param idShortPath + * IdShort path to the submodel element (dot-separated) (required) + * @return File + * @throws ApiException + * if fails to make API call + */ + public File getFileByPath(String idShortPath) throws ApiException { + ApiResponse localVarResponse = getFileByPathApiResponse(idShortPath); + return localVarResponse.getData(); + } + + private ApiResponse getFileByPathApiResponse(String idShortPath) throws ApiException { + HttpRequest.Builder localVarRequestBuilder = getFileByPathRequestBuilder(idShortPath); + try { + HttpResponse localVarResponse = memberVarHttpClient.send(localVarRequestBuilder.build(), HttpResponse.BodyHandlers.ofInputStream()); + if (memberVarResponseInterceptor != null) { + memberVarResponseInterceptor.accept(localVarResponse); + } + if (localVarResponse.statusCode() / 100 != 2) { + throw getApiException("getFileByPath", localVarResponse); + } + if (localVarResponse.body() == null) { + return new ApiResponse(localVarResponse.statusCode(), localVarResponse.headers().map(), null); + } else { + File tempFile = File.createTempFile(buildUniqueFilename(), extractFileName(localVarResponse.headers())); + try (FileOutputStream out = new FileOutputStream(tempFile)) { + localVarResponse.body().transferTo(out); + return new ApiResponse(localVarResponse.statusCode(), localVarResponse.headers().map(), tempFile); + } finally { + localVarResponse.body().close(); + } + } + } catch (IOException e) { + throw new ApiException(e); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new ApiException(e); + } + } + + private HttpRequest.Builder getFileByPathRequestBuilder(String idShortPath) throws ApiException { + if (idShortPath == null) { + throw new ApiException(400, "Missing the required parameter 'idShortPath' when calling getFileByPath"); + } + + HttpRequest.Builder localVarRequestBuilder = HttpRequest.newBuilder(); + + String localVarPath = "/submodel-elements/{idShortPath}/attachment".replace("{idShortPath}", ApiClient.urlEncode(idShortPath.toString())); + + localVarRequestBuilder.uri(URI.create(memberVarBaseUri + localVarPath)); + + localVarRequestBuilder.header("Accept", "application/octet-stream, application/json"); + + localVarRequestBuilder.method("GET", HttpRequest.BodyPublishers.noBody()); + if (memberVarReadTimeout != null) { + localVarRequestBuilder.timeout(memberVarReadTimeout); + } + if (memberVarInterceptor != null) { + memberVarInterceptor.accept(localVarRequestBuilder); + } + return localVarRequestBuilder; + } + + /** + * Synchronously invokes an Operation at a specified path + * + * @param idShortPath + * IdShort path to the submodel element (dot-separated) (required) + * @param operationRequest + * Operation request object (required) + * @return OperationResult + * @throws ApiException + * if fails to make API call + */ + public OperationResult invokeOperation(String idShortPath, OperationRequest operationRequest) throws ApiException { + + ApiResponse localVarResponse = invokeOperationWithHttpInfo(idShortPath, operationRequest); + return localVarResponse.getData(); + } + + /** + * Synchronously invokes an Operation at a specified path + * + * @param idShortPath + * IdShort path to the submodel element (dot-separated) (required) + * @param operationRequest + * Operation request object (required) + * @return ApiResponse<OperationResult> + * @throws ApiException + * if fails to make API call + */ + public ApiResponse invokeOperationWithHttpInfo(String idShortPath, OperationRequest operationRequest) throws ApiException { + return invokeOperationWithHttpInfoNoUrlEncoding(idShortPath, operationRequest); + + } + + /** + * Synchronously invokes an Operation at a specified path + * + * @param idShortPath + * IdShort path to the submodel element (dot-separated) (required) + * @param operationRequest + * Operation request object (required) + * @return ApiResponse<OperationResult> + * @throws ApiException + * if fails to make API call + */ + public ApiResponse invokeOperationWithHttpInfoNoUrlEncoding(String idShortPath, OperationRequest operationRequest) throws ApiException { + HttpRequest.Builder localVarRequestBuilder = invokeOperationRequestBuilder(idShortPath, operationRequest); + try { + HttpResponse localVarResponse = memberVarHttpClient.send(localVarRequestBuilder.build(), HttpResponse.BodyHandlers.ofInputStream()); + if (memberVarResponseInterceptor != null) { + memberVarResponseInterceptor.accept(localVarResponse); + } + try { + if (localVarResponse.statusCode() / 100 != 2) { + throw getApiException("invokeOperation", localVarResponse); + } + return new ApiResponse(localVarResponse.statusCode(), localVarResponse.headers().map(), + localVarResponse.body() == null ? null : memberVarObjectMapper.readValue(localVarResponse.body(), new TypeReference() { + }) // closes the InputStream + ); + } finally { + } + } catch (IOException e) { + throw new ApiException(e); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new ApiException(e); + } + } + + private HttpRequest.Builder invokeOperationRequestBuilder(String idShortPath, OperationRequest operationRequest) throws ApiException { + // verify the required parameter 'idShortPath' is set + if (idShortPath == null) { + throw new ApiException(400, "Missing the required parameter 'idShortPath' when calling invokeOperation"); + } + // verify the required parameter 'operationRequest' is set + if (operationRequest == null) { + throw new ApiException(400, "Missing the required parameter 'operationRequest' when calling invokeOperation"); + } + + HttpRequest.Builder localVarRequestBuilder = HttpRequest.newBuilder(); + + String localVarPath = "/submodel-elements/{idShortPath}/invoke".replace("{idShortPath}", ApiClient.urlEncode(idShortPath.toString())); + + localVarRequestBuilder.uri(URI.create(memberVarBaseUri + localVarPath)); + + localVarRequestBuilder.header("Content-Type", "application/json"); + localVarRequestBuilder.header("Accept", "application/json"); + + try { + byte[] localVarPostBody = memberVarObjectMapper.writeValueAsBytes(operationRequest); + localVarRequestBuilder.method("POST", HttpRequest.BodyPublishers.ofByteArray(localVarPostBody)); + } catch (IOException e) { + throw new ApiException(e); + } + if (memberVarReadTimeout != null) { + localVarRequestBuilder.timeout(memberVarReadTimeout); + } + if (memberVarInterceptor != null) { + memberVarInterceptor.accept(localVarRequestBuilder); + } + return localVarRequestBuilder; + } + + private static String extractFileName(HttpHeaders headers) { + Optional contentType = headers.firstValue("Content-Type"); + try { + return "." + MimeTypeUtils.parseMimeType(contentType.get()).getSubtype(); + } catch (Exception e) { + return ".tmp"; + } + } + + private static String buildUniqueFilename() { + return UUID.randomUUID().toString(); + } } diff --git a/basyx.submodelservice/basyx.submodelservice-client/src/test/java/org/eclipse/digitaltwin/basyx/submodelservice/client/TestConnectedSubmodelService.java b/basyx.submodelservice/basyx.submodelservice-client/src/test/java/org/eclipse/digitaltwin/basyx/submodelservice/client/TestConnectedSubmodelService.java index b5051fa2a..d14c32b2a 100644 --- a/basyx.submodelservice/basyx.submodelservice-client/src/test/java/org/eclipse/digitaltwin/basyx/submodelservice/client/TestConnectedSubmodelService.java +++ b/basyx.submodelservice/basyx.submodelservice-client/src/test/java/org/eclipse/digitaltwin/basyx/submodelservice/client/TestConnectedSubmodelService.java @@ -27,9 +27,6 @@ package org.eclipse.digitaltwin.basyx.submodelservice.client; import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; -import org.eclipse.digitaltwin.basyx.core.exceptions.ElementNotAFileException; -import org.eclipse.digitaltwin.basyx.core.exceptions.FileDoesNotExistException; -import org.eclipse.digitaltwin.basyx.core.exceptions.NotInvokableException; import org.eclipse.digitaltwin.basyx.http.Base64UrlEncodedIdentifier; import org.eclipse.digitaltwin.basyx.submodelrepository.SubmodelRepository; import org.eclipse.digitaltwin.basyx.submodelrepository.http.DummySubmodelRepositoryComponent; @@ -47,7 +44,7 @@ * Whenever a feature is implemented, the respective test here has to be * removed. * - * @author schnicke + * @author schnicke, mateusmolina */ public class TestConnectedSubmodelService extends SubmodelServiceSuite { @@ -83,88 +80,4 @@ protected boolean fileExistsInStorage(String fileValue) { return file.exists(); } - - @Override - public void getSubmodelElements() { - // TODO Auto-generated method stub - - } - - @Override - public void getPaginatedSubmodelElement() { - // TODO Auto-generated method stub - - } - - @Override - public void paginationCursor() { - // TODO Auto-generated method stub - - } - - @Override - public void invokeOperation() { - // TODO Auto-generated method stub - - } - - @Override - public void invokeNonOperation() { - // TODO Auto-generated method stub - throw new NotInvokableException(); - } - - @Override - public void deleteFileSubmodelElementDeletesFile() { - // TODO Auto-generated method stub - - } - - @Override - public void updateFileSMEWithNonFileSME() { - // TODO Auto-generated method stub - - } - - @Override - public void updateFileSMEWithFileSME() { - // TODO Auto-generated method stub - - } - - @Override - public void getNonExistingFile() { - throw new FileDoesNotExistException(); - - } - - @Override - public void deleteNonExistingFile() { - throw new FileDoesNotExistException(); - - } - - @Override - public void getFileFromNonFileSME() { - throw new ElementNotAFileException(); - - } - - @Override - public void deleteFile() { - // TODO Auto-generated method stub - - } - - @Override - public void getFile() { - // TODO Auto-generated method stub - - } - - @Override - public void updateFile() { - // TODO Auto-generated method stub - - } } diff --git a/basyx.submodelservice/basyx.submodelservice-core/src/test/java/org/eclipse/digitaltwin/basyx/submodelservice/SubmodelServiceSuite.java b/basyx.submodelservice/basyx.submodelservice-core/src/test/java/org/eclipse/digitaltwin/basyx/submodelservice/SubmodelServiceSuite.java index a599d6b90..ad49ae7dc 100644 --- a/basyx.submodelservice/basyx.submodelservice-core/src/test/java/org/eclipse/digitaltwin/basyx/submodelservice/SubmodelServiceSuite.java +++ b/basyx.submodelservice/basyx.submodelservice-core/src/test/java/org/eclipse/digitaltwin/basyx/submodelservice/SubmodelServiceSuite.java @@ -75,7 +75,7 @@ * */ public abstract class SubmodelServiceSuite { - protected static final PaginationInfo NO_LIMIT_PAGINATION_INFO = new PaginationInfo(0, null); + protected static final PaginationInfo NO_LIMIT_PAGINATION_INFO = new PaginationInfo(null, null); protected abstract SubmodelService getSubmodelService(Submodel submodel);