diff --git a/CHANGELOG.md b/CHANGELOG.md index 39b995183..637a3140e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,19 @@ The changelog format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [pre-released] +## [v1.3.0] - 03-11-2023 + + +## Added +- New IRS Service which is able to communicate with the IRS component api. +- Added condition to deactivate the IRS +- Added tree manager to manage the tree of components used by each irs process +- Added structure to manage the information coming from the IRS and jobs initiated +- Enabled callback mechanism with the IRS component +- Created `/api/irs/{processId}/tree` and `/api/irs/{processId}/components` APIs + + ## [released] ## [1.2.1] - 31-10-2023 diff --git a/DEPENDENCIES_FRONTEND b/DEPENDENCIES_FRONTEND index b9bfdd228..edd0ee959 100644 --- a/DEPENDENCIES_FRONTEND +++ b/DEPENDENCIES_FRONTEND @@ -37,7 +37,6 @@ npm/npmjs/-/cross-spawn/7.0.3, MIT, approved, clearlydefined npm/npmjs/-/crypto-js/4.2.0, MIT AND BSD-2-Clause, approved, #11347 npm/npmjs/-/cssesc/3.0.0, MIT, approved, clearlydefined npm/npmjs/-/csstype/2.6.21, MIT, approved, clearlydefined -npm/npmjs/-/cypress-keycloak/1.9.0, MIT, approved, #6952 npm/npmjs/-/de-indent/1.0.2, MIT, approved, clearlydefined npm/npmjs/-/debug/4.3.4, MIT, approved, clearlydefined npm/npmjs/-/deep-is/0.1.4, MIT, approved, #2130 diff --git a/charts/digital-product-pass/values-beta.yaml b/charts/digital-product-pass/values-beta.yaml index 9ad978eb2..aa364033e 100644 --- a/charts/digital-product-pass/values-beta.yaml +++ b/charts/digital-product-pass/values-beta.yaml @@ -124,6 +124,16 @@ backend: enabled: true bpn: true edc: true + + irs: + enabled: true + endpoint: "https://materialpass-irs.beta.demo.catena-x.net" + paths: + job: "/irs/jobs" + tree: + fileName: "treeDataModel" + indent: true + callbackUrl: "https://materialpass.beta.demo.catena-x.net/api/irs" dtr: central: false diff --git a/charts/digital-product-pass/values-dev.yaml b/charts/digital-product-pass/values-dev.yaml index 672635d26..f6f25d1d7 100644 --- a/charts/digital-product-pass/values-dev.yaml +++ b/charts/digital-product-pass/values-dev.yaml @@ -125,6 +125,16 @@ backend: bpn: false edc: true + irs: + enabled: true + endpoint: "https://materialpass-irs.dev.demo.catena-x.net" + paths: + job: "/irs/jobs" + tree: + fileName: "treeDataModel" + indent: true + callbackUrl: "https://materialpass.dev.demo.catena-x.net/api/irs" + dtr: central: false centralUrl: 'https://semantics.dev.demo.catena-x.net/registry' diff --git a/charts/digital-product-pass/values-int.yaml b/charts/digital-product-pass/values-int.yaml index b30f8a47e..38a5140ff 100644 --- a/charts/digital-product-pass/values-int.yaml +++ b/charts/digital-product-pass/values-int.yaml @@ -123,6 +123,16 @@ backend: bpn: true edc: true + irs: + enabled: true + endpoint: "https://materialpass-irs.int.demo.catena-x.net" + paths: + job: "/irs/jobs" + tree: + fileName: "treeDataModel" + indent: true + callbackUrl: "https://materialpass.int.demo.catena-x.net/api/irs" + dtr: central: false centralUrl: 'https://semantics.int.demo.catena-x.net/registry' diff --git a/charts/digital-product-pass/values.yaml b/charts/digital-product-pass/values.yaml index 7871119d1..5fd5c5df2 100644 --- a/charts/digital-product-pass/values.yaml +++ b/charts/digital-product-pass/values.yaml @@ -140,6 +140,16 @@ backend: enabled: false bpn: false edc: false + # -- irs configuration + irs: + enabled: true # -- Enable search for children in the requests + endpoint: "https://" # -- IRS endpoint + paths: + job: "/irs/jobs" # -- API path for calling in the IRS endpoints and staring/getting jobs + tree: + fileName: "treeDataModel" # -- Tree dataModel filename created in the processId directory + indent: true # -- Indent tree file + callbackUrl: "https:///api/irs" # -- Backend call back base url for the irs controller # -- digital twin registry configuration dtr: central: false diff --git a/consumer-backend/productpass/pom.xml b/consumer-backend/productpass/pom.xml index 73e0e9ba8..7a7036bb2 100644 --- a/consumer-backend/productpass/pom.xml +++ b/consumer-backend/productpass/pom.xml @@ -33,7 +33,7 @@ org.eclipse.tractusx productpass - 1.2.1 + 1.3.0 jar Catena-X Digital Product Passport Backend Product Passport Consumer Backend System for Product Passport Consumer Frontend Application diff --git a/consumer-backend/productpass/src/main/java/org/eclipse/tractusx/productpass/config/IrsConfig.java b/consumer-backend/productpass/src/main/java/org/eclipse/tractusx/productpass/config/IrsConfig.java new file mode 100644 index 000000000..cc0780782 --- /dev/null +++ b/consumer-backend/productpass/src/main/java/org/eclipse/tractusx/productpass/config/IrsConfig.java @@ -0,0 +1,178 @@ +/********************************************************************************* + * + * Catena-X - Product Passport Consumer Backend + * + * Copyright (c) 2022, 2023 BASF SE, BMW AG, Henkel AG & Co. KGaA + * Copyright (c) 2022, 2023 Contributors to the CatenaX (ng) GitHub Organisation. + * + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the + * License for the specific language govern in permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +package org.eclipse.tractusx.productpass.config; + +import com.sun.source.tree.Tree; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +/** + * This class consists exclusively to define the attributes and methods needed for the IRS configuration. + **/ +@Configuration +@ConfigurationProperties(prefix="configuration.irs") +public class IrsConfig { + + /** ATTRIBUTES **/ + Boolean enabled; + String endpoint; + TreeConfig tree; + Paths paths; + String callbackUrl; + + /** CONSTRUCTOR(S) **/ + public IrsConfig(Boolean enabled, String endpoint, TreeConfig tree, Paths paths, String callbackUrl) { + this.enabled = enabled; + this.endpoint = endpoint; + this.tree = tree; + this.paths = paths; + this.callbackUrl = callbackUrl; + } + public IrsConfig() { + } + + public IrsConfig(String endpoint, TreeConfig tree, Paths paths) { + this.endpoint = endpoint; + this.tree = tree; + this.paths = paths; + } + + public IrsConfig(String endpoint, TreeConfig tree, Paths paths, String callbackUrl) { + this.endpoint = endpoint; + this.tree = tree; + this.paths = paths; + this.callbackUrl = callbackUrl; + } + + /** GETTERS AND SETTERS **/ + + public String getEndpoint() { + return endpoint; + } + + public void setEndpoint(String endpoint) { + this.endpoint = endpoint; + } + + public Paths getPaths() { + return paths; + } + + public void setPaths(Paths paths) { + this.paths = paths; + } + + public TreeConfig getTree() { + return tree; + } + + public void setTree(TreeConfig tree) { + this.tree = tree; + } + + public String getCallbackUrl() { + return callbackUrl; + } + + public void setCallbackUrl(String callbackUrl) { + this.callbackUrl = callbackUrl; + } + + public Boolean getEnabled() { + return enabled; + } + + public void setEnabled(Boolean enabled) { + this.enabled = enabled; + } + + /** INNER CLASSES **/ + + /** + * This class consists exclusively to define the attributes and methods needed for the IRS Tree configurations. + **/ + public static class TreeConfig{ + + /** ATTRIBUTES **/ + String fileName; + + Boolean indent; + + + public TreeConfig(String fileName) { + this.fileName = fileName; + } + + public TreeConfig(String fileName, Boolean indent) { + this.fileName = fileName; + this.indent = indent; + } + + public TreeConfig() { + } + + /** GETTERS AND SETTERS **/ + public String getFileName() { + return fileName; + } + + public void setFileName(String fileName) { + this.fileName = fileName; + } + + public Boolean getIndent() { + return indent; + } + + public void setIndent(Boolean indent) { + this.indent = indent; + } + } + + + /** + * This class consists exclusively to define the attributes and methods needed for the job path configuration. + **/ + public static class Paths{ + String job; + + public Paths(String job) { + this.job = job; + } + + public Paths() { + } + + public String getJob() { + return job; + } + + public void setJob(String job) { + this.job = job; + } + } + +} diff --git a/consumer-backend/productpass/src/main/java/org/eclipse/tractusx/productpass/http/controllers/AppController.java b/consumer-backend/productpass/src/main/java/org/eclipse/tractusx/productpass/http/controllers/AppController.java index 7f43dae0e..c27f8377c 100644 --- a/consumer-backend/productpass/src/main/java/org/eclipse/tractusx/productpass/http/controllers/AppController.java +++ b/consumer-backend/productpass/src/main/java/org/eclipse/tractusx/productpass/http/controllers/AppController.java @@ -33,9 +33,11 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.eclipse.tractusx.productpass.config.DtrConfig; +import org.eclipse.tractusx.productpass.config.IrsConfig; import org.eclipse.tractusx.productpass.config.ProcessConfig; import org.eclipse.tractusx.productpass.exceptions.ControllerException; import org.eclipse.tractusx.productpass.managers.ProcessManager; +import org.eclipse.tractusx.productpass.managers.TreeManager; import org.eclipse.tractusx.productpass.models.catenax.Dtr; import org.eclipse.tractusx.productpass.models.dtregistry.DigitalTwin; import org.eclipse.tractusx.productpass.models.dtregistry.EndPoint; @@ -45,10 +47,12 @@ import org.eclipse.tractusx.productpass.models.http.Response; import org.eclipse.tractusx.productpass.models.http.requests.Search; import org.eclipse.tractusx.productpass.models.manager.History; +import org.eclipse.tractusx.productpass.models.manager.Node; import org.eclipse.tractusx.productpass.models.manager.SearchStatus; import org.eclipse.tractusx.productpass.models.manager.Status; import org.eclipse.tractusx.productpass.services.AasService; import org.eclipse.tractusx.productpass.services.DataPlaneService; +import org.eclipse.tractusx.productpass.services.IrsService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.env.Environment; import org.springframework.web.bind.annotation.*; @@ -83,11 +87,19 @@ public class AppController { PassportUtil passportUtil; @Autowired AasService aasService; + + @Autowired + IrsService irsService; + + @Autowired + TreeManager treeManager; @Autowired DataPlaneService dataPlaneService; @Autowired ProcessManager processManager; @Autowired + IrsConfig irsConfig; + @Autowired DtrConfig dtrConfig; @SuppressWarnings("Unused") private @Autowired ProcessConfig processConfig; @@ -189,7 +201,7 @@ public Response getDigitalTwin(@RequestBody Object body, @PathVariable String pr if (endpoint == null) { throw new ControllerException(this.getClass().getName(), "No EDC endpoint found in DTR SubModel!"); } - Map subProtocolBody = endpoint.getProtocolInformation().getParsedSubprotocolBody(); + Map subProtocolBody = endpoint.getProtocolInformation().parseSubProtocolBody(); connectorAddress = subProtocolBody.get(dtrConfig.getDspEndpointKey()); // Get DSP endpoint address assetId = subProtocolBody.get("id"); // Get Asset Id dataPlaneUrl = endpoint.getProtocolInformation().getEndpointAddress(); // Get the HREF endpoint @@ -208,10 +220,23 @@ public Response getDigitalTwin(@RequestBody Object body, @PathVariable String pr if (connectorAddress.isEmpty() || assetId.isEmpty()) { LogUtil.printError("Failed to parse endpoint [" + connectorAddress + "] or the assetId is not found!"); } - processManager.setEndpoint(processId, connectorAddress, dataPlaneUrl); - processManager.setBpn(processId, dtr.getBpn()); - processManager.setSemanticId(processId, semanticId); + String bpn = dtr.getBpn(); + Boolean childrenCondition = search.getChildren(); + processManager.saveTransferInfo(processId, connectorAddress, semanticId, dataPlaneUrl, bpn, childrenCondition); processManager.saveDigitalTwin(processId, digitalTwin, dtRequestTime); + + // IRS FUNCTIONALITY START + if(this.irsConfig.getEnabled() && search.getChildren()) { + // Update tree + String globalAssetId = digitalTwin.getGlobalAssetId(); + String actualPath = status.getTreeState() + "/" + globalAssetId; + processManager.setTreeState(processId, actualPath); + this.treeManager.setNodeByPath(processId, actualPath, new Node(digitalTwin)); + + // Get children from the node + this.irsService.getChildren(processId, actualPath, globalAssetId, bpn); + } + LogUtil.printDebug("[PROCESS " + processId + "] Digital Twin [" + digitalTwin.getIdentification() + "] and Submodel [" + subModel.getIdentification() + "] with EDC endpoint [" + connectorAddress + "] retrieved from DTR"); processManager.setStatus(processId, "digital-twin-found", new History( assetId, diff --git a/consumer-backend/productpass/src/main/java/org/eclipse/tractusx/productpass/http/controllers/api/ContractController.java b/consumer-backend/productpass/src/main/java/org/eclipse/tractusx/productpass/http/controllers/api/ContractController.java index 415da142c..00cbbcd6c 100644 --- a/consumer-backend/productpass/src/main/java/org/eclipse/tractusx/productpass/http/controllers/api/ContractController.java +++ b/consumer-backend/productpass/src/main/java/org/eclipse/tractusx/productpass/http/controllers/api/ContractController.java @@ -38,6 +38,7 @@ import org.eclipse.tractusx.productpass.config.PassportConfig; import org.eclipse.tractusx.productpass.config.ProcessConfig; import org.eclipse.tractusx.productpass.exceptions.ControllerException; +import org.eclipse.tractusx.productpass.exceptions.ServiceException; import org.eclipse.tractusx.productpass.managers.DtrSearchManager; import org.eclipse.tractusx.productpass.managers.ProcessManager; import org.eclipse.tractusx.productpass.models.catenax.BpnDiscovery; @@ -275,7 +276,12 @@ public Response search(@Valid @RequestBody Search searchBody) { response = httpUtil.getBadRequest("No digital twins are available for this process!"); return httpUtil.buildResponse(response, httpResponse); } - process = processManager.createProcess(processId, httpRequest); + Boolean childrenCondition = searchBody.getChildren(); + if(childrenCondition != null){ + process = processManager.createProcess(processId, childrenCondition, httpRequest); // Store the children condition + }else { + process = processManager.createProcess(processId, httpRequest); + } Status status = processManager.getStatus(processId); if (status == null) { response = httpUtil.getBadRequest("The status is not available!"); @@ -287,7 +293,6 @@ public Response search(@Valid @RequestBody Search searchBody) { response = httpUtil.getBadRequest("No digital twin was found!"); return httpUtil.buildResponse(response, httpResponse); } - // Assing the variables with the content String assetId = assetSearch.getAssetId(); String connectorAddress = assetSearch.getConnectorAddress(); @@ -299,7 +304,7 @@ public Response search(@Valid @RequestBody Search searchBody) { Long startedTime = DateTimeUtil.getTimestamp(); try { dataset = dataService.getContractOfferByAssetId(assetId, connectorAddress); - } catch (ControllerException e) { + } catch (ServiceException e) { response.message = "The EDC is not reachable, it was not possible to retrieve catalog!"; response.status = 502; response.statusText = "Bad Gateway"; diff --git a/consumer-backend/productpass/src/main/java/org/eclipse/tractusx/productpass/http/controllers/api/IrsController.java b/consumer-backend/productpass/src/main/java/org/eclipse/tractusx/productpass/http/controllers/api/IrsController.java new file mode 100644 index 000000000..666f76790 --- /dev/null +++ b/consumer-backend/productpass/src/main/java/org/eclipse/tractusx/productpass/http/controllers/api/IrsController.java @@ -0,0 +1,273 @@ +/********************************************************************************* + * + * Catena-X - Product Passport Consumer Backend + * + * Copyright (c) 2022, 2023 BASF SE, BMW AG, Henkel AG & Co. KGaA + * Copyright (c) 2022, 2023 Contributors to the CatenaX (ng) GitHub Organisation. + * + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the + * License for the specific language govern in permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +package org.eclipse.tractusx.productpass.http.controllers.api; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.eclipse.tractusx.productpass.config.IrsConfig; +import org.eclipse.tractusx.productpass.managers.ProcessManager; +import org.eclipse.tractusx.productpass.managers.TreeManager; +import org.eclipse.tractusx.productpass.models.http.Response; +import org.eclipse.tractusx.productpass.models.http.requests.Search; +import org.eclipse.tractusx.productpass.models.irs.Job; +import org.eclipse.tractusx.productpass.models.irs.JobHistory; +import org.eclipse.tractusx.productpass.models.irs.JobResponse; +import org.eclipse.tractusx.productpass.models.manager.Node; +import org.eclipse.tractusx.productpass.models.manager.SearchStatus; +import org.eclipse.tractusx.productpass.models.manager.Status; +import org.eclipse.tractusx.productpass.services.AuthenticationService; +import org.eclipse.tractusx.productpass.services.IrsService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; +import utils.HttpUtil; +import utils.JsonUtil; +import utils.LogUtil; + +import java.util.Map; +/** + * This class consists exclusively to define the HTTP methods needed for the IRS component. + **/ +@RestController +@RequestMapping("/api/irs") +@Tag(name = "IRS Controller") +@SecurityRequirement(name = "BearerAuthentication") +public class IrsController { + + /** ATTRIBUTES **/ + private @Autowired HttpServletRequest httpRequest; + private @Autowired HttpServletResponse httpResponse; + + private @Autowired AuthenticationService authService; + + private @Autowired HttpUtil httpUtil; + private @Autowired JsonUtil jsonUtil; + + private @Autowired IrsConfig irsConfig; + + private @Autowired IrsService irsService; + private @Autowired TreeManager treeManager; + private @Autowired ProcessManager processManager; + + /** METHODS **/ + + /** + * HTTP GET method which receives and handles the call back from the IRS + *

+ * @param processId + * the {@code String} process id contained in the path of the url + * @param searchId + * the {@code String} unique hash which identifies the callback search + * @param id + * the {@code String} id from the globalAssetId given by the IRS + * @param state + * the {@code String} state of the IRS callback event + * + * @return this {@code Response} HTTP response with an OK message and 200 status code + * + */ + @RequestMapping(value = "/{processId}/{searchId}", method = RequestMethod.GET) + @Operation(summary = "Endpoint called by the IRS to set status completed") + public Response endpoint(@PathVariable String processId, @PathVariable String searchId, @RequestParam String id, @RequestParam String state) { + Response response = httpUtil.getInternalError(); + try { + if (!processManager.checkProcess(processId)) { + return httpUtil.buildResponse(httpUtil.getNotFound("Process not found!"), httpResponse); + } + + Status status = processManager.getStatus(processId); + if(status == null){ + return httpUtil.buildResponse(httpUtil.getNotFound("No status is created"), httpResponse); + } + + if(!status.getJob().getSearchId().equals(searchId)){ + return httpUtil.buildResponse(httpUtil.getNotAuthorizedResponse(), httpResponse); + } + + JobHistory jobHistory = status.getJob(); + + LogUtil.printMessage("["+processId+"] Job callback received with state ["+ state+"]. Requesting Job ["+jobHistory.getJobId()+"]!"); + JobResponse irsJob = this.irsService.getJob(jobHistory.getJobId()); + this.treeManager.populateTree(processId, jobHistory, irsJob); + response = httpUtil.getResponse("OK"); + return httpUtil.buildResponse(response, httpResponse); + } catch (Exception e) { + response.message = e.getMessage(); + return httpUtil.buildResponse(response, httpResponse); + } + } + /** + * HTTP GET method which returns the current component tree in a simplified structure + *

+ * @param processId + * the {@code String} process id contained in the path of the url + * + * @return this {@code Response} HTTP response with the current component tree of the process + * + */ + @RequestMapping(value = "/{processId}/components", method = RequestMethod.GET) + @Operation(summary = "Api called by the frontend to obtain the tree of components") + public Response components(@PathVariable String processId) { + Response response = httpUtil.getInternalError(); + if (!authService.isAuthenticated(httpRequest)) { + response = httpUtil.getNotAuthorizedResponse(); + return httpUtil.buildResponse(response, httpResponse); + } + try { + if (!processManager.checkProcess(processId)) { + return httpUtil.buildResponse(httpUtil.getNotFound("Process not found!"), httpResponse); + } + + Status status = processManager.getStatus(processId); + if(status == null){ + return httpUtil.buildResponse(httpUtil.getNotFound("No status is created"), httpResponse); + } + if(!this.irsConfig.getEnabled()){ + return httpUtil.buildResponse(httpUtil.getForbiddenResponse("The children drill down functionality is not available!"), httpResponse); + } + if(!status.getChildren()){ + return httpUtil.buildResponse(httpUtil.getForbiddenResponse("The children drill down functionality is not available for this process!"), httpResponse); + } + + response = httpUtil.getResponse(); + response.data = this.treeManager.getTreeComponents(processId); // Loads the tree components with a easy structure for frontend component + return httpUtil.buildResponse(response, httpResponse); + } catch (Exception e) { + response.message = e.getMessage(); + return httpUtil.buildResponse(response, httpResponse); + } + } + /** + * HTTP GET method which returns the current tree of digital twins which are found in this process + *

+ * @param processId + * the {@code String} process id contained in the path of the url + * + * @return this {@code Response} HTTP response with the current complete tree data model of the process id + * + */ + @RequestMapping(value = "/{processId}/tree", method = RequestMethod.GET) + @Operation(summary = "Api called by the frontend to obtain the tree of components") + public Response tree( @PathVariable String processId) { + Response response = httpUtil.getInternalError(); + if (!authService.isAuthenticated(httpRequest)) { + response = httpUtil.getNotAuthorizedResponse(); + return httpUtil.buildResponse(response, httpResponse); + } + try { + if (!processManager.checkProcess(processId)) { + return httpUtil.buildResponse(httpUtil.getNotFound("Process not found!"), httpResponse); + } + + Status status = processManager.getStatus(processId); + if(status == null){ + return httpUtil.buildResponse(httpUtil.getNotFound("No status is created"), httpResponse); + } + if(!this.irsConfig.getEnabled()){ + return httpUtil.buildResponse(httpUtil.getForbiddenResponse("The children drill down functionality is not available!"), httpResponse); + } + if(!status.getChildren()){ + return httpUtil.buildResponse(httpUtil.getForbiddenResponse("The children drill down functionality is not available for this process!"), httpResponse); + } + response = httpUtil.getResponse(); + response.data = this.treeManager.getTree(processId); // Loads the tree components with a easy structure for frontend component + return httpUtil.buildResponse(response, httpResponse); + } catch (Exception e) { + response.message = e.getMessage(); + return httpUtil.buildResponse(response, httpResponse); + } + } + + /** + * HTTP GET method which returns the current tree of digital twins which are found in this process + *

+ * @param processId + * the {@code String} process id contained in the path of the url + * + * @return this {@code Response} HTTP response with the current complete tree data model of the process id + * + */ + @RequestMapping(value = "/{processId}/state", method = RequestMethod.GET) + @Operation(summary = "Api called by the frontend to check if the process is finished") + public Response state( @PathVariable String processId) { + Response response = httpUtil.getInternalError(); + if (!authService.isAuthenticated(httpRequest)) { + response = httpUtil.getNotAuthorizedResponse(); + return httpUtil.buildResponse(response, httpResponse); + } + try { + if (!processManager.checkProcess(processId)) { + return httpUtil.buildResponse(httpUtil.getNotFound("Process not found!"), httpResponse); + } + + Status status = processManager.getStatus(processId); + if(status == null){ + return httpUtil.buildResponse(httpUtil.getNotFound("No status is created"), httpResponse); + } + if(!this.irsConfig.getEnabled()){ + response = httpUtil.getResponse("The children drill down functionality is not available!"); + response.status = 503; + response.statusText = "Service Unavailable"; + return httpUtil.buildResponse(response, httpResponse); + } + if(!status.getChildren()){ + response = httpUtil.getResponse("The children drill down functionality is not available for this process!"); + response.status = 503; + response.statusText = "Service Unavailable"; + return httpUtil.buildResponse(response, httpResponse); + } + response = httpUtil.getResponse(); + Integer jobStatus = status.getJob().getChildren(); + + switch (jobStatus){ + case -1: + response.status = 100; + response.statusText = "Continue"; + response.message = "Still searching for the children!"; + break; + + case 0: + response.status = 404; + response.statusText = "Not Found"; + response.message = "No children is available"; + break; + + default: + response.status = 200; + response.statusText = "Success"; + response.message = "Children available!"; + + } + return httpUtil.buildResponse(response, httpResponse); + } catch (Exception e) { + response.message = e.getMessage(); + return httpUtil.buildResponse(response, httpResponse); + } + } + +} diff --git a/consumer-backend/productpass/src/main/java/org/eclipse/tractusx/productpass/managers/ProcessManager.java b/consumer-backend/productpass/src/main/java/org/eclipse/tractusx/productpass/managers/ProcessManager.java index b7841dc82..4e71c5275 100644 --- a/consumer-backend/productpass/src/main/java/org/eclipse/tractusx/productpass/managers/ProcessManager.java +++ b/consumer-backend/productpass/src/main/java/org/eclipse/tractusx/productpass/managers/ProcessManager.java @@ -35,6 +35,7 @@ import org.eclipse.tractusx.productpass.models.edc.Jwt; import org.eclipse.tractusx.productpass.models.http.requests.Search; import org.eclipse.tractusx.productpass.models.http.responses.IdResponse; +import org.eclipse.tractusx.productpass.models.irs.JobHistory; import org.eclipse.tractusx.productpass.models.manager.History; import org.eclipse.tractusx.productpass.models.manager.Process; import org.eclipse.tractusx.productpass.models.manager.SearchStatus; @@ -355,18 +356,6 @@ private String getTmpProcessDir(String processId, Boolean absolute) { } } - /** - * Creates a new process in the given HTTP session. - *

- * @param httpRequest - * the HTTP request. - * - * @return a {@code Process} object representing the created process. - * - */ - public Process createProcess(HttpServletRequest httpRequest) { - return this.createProcess(httpRequest, "", ""); - } /** * Creates a new process in the given HTTP session and set its status in the file system logs. @@ -379,12 +368,12 @@ public Process createProcess(HttpServletRequest httpRequest) { * @return a {@code Process} object representing the created process. * */ - public Process createProcess(HttpServletRequest httpRequest, String connectorAddress, String dataPlaneUrl) { + public Process createProcess(HttpServletRequest httpRequest, String connectorAddress) { Long createdTime = DateTimeUtil.getTimestamp(); Process process = new Process(CrypUtil.getUUID(), "CREATED", createdTime); LogUtil.printMessage("Process Created [" + process.id + "], waiting for user to sign or decline..."); this.setProcess(httpRequest, process); // Add process to session storage - this.newStatusFile(process.id, connectorAddress, dataPlaneUrl, createdTime); // Set the status from the process in file system logs. + this.newStatusFile(process.id, connectorAddress, createdTime, true); // Set the status from the process in file system logs. return process; } @@ -508,7 +497,56 @@ public Process createProcess(String processId, HttpServletRequest httpRequest) { Process process = new Process(processId, "CREATED", createdTime); LogUtil.printMessage("Process Created [" + process.id + "], waiting for user to sign or decline..."); this.setProcess(httpRequest, process); // Add process to session storage - this.newStatusFile(process.id,"", "", createdTime); // Set the status from the process in file system logs. + this.newStatusFile(process.id,"", createdTime, true); // Set the status from the process in file system logs. + return process; + } + /** + * Creates a new Process with the given processId into the given HTTP session. + *

+ * @param processId + * the {@code String} id of the application's process. + * @param childrenCondition + * the {@code Boolean} condition which enables if the search will request the irs for children + * @param httpRequest + * the HTTP request. + * + * @return a {@code Process} object created. + * + * @throws ManagerException + * if unable to create the process. + */ + public Process createProcess(String processId,Boolean childrenCondition, HttpServletRequest httpRequest) { + Long createdTime = DateTimeUtil.getTimestamp(); + Process process = new Process(processId, "CREATED", createdTime); + LogUtil.printMessage("Process Created [" + process.id + "], waiting for user to sign or decline..."); + this.setProcess(httpRequest, process); // Add process to session storage + this.newStatusFile(process.id,"", createdTime, childrenCondition); // Set the status from the process in file system logs. + return process; + } + /** + * Creates a new Process with the given processId into the given HTTP session, + * setting the process with the given BPN number. + *

+ * @param httpRequest + * the HTTP request. + * @param processId + * the {@code String} id of the application's process. + * @param bpn + * the {@code String} BPN number. + * + * @return a {@code Process} object created. + * + * @throws ManagerException + * if unable to create the process. + */ + @SuppressWarnings("Unused") + public Process createProcess(HttpServletRequest httpRequest, String processId, String bpn) { + Long createdTime = DateTimeUtil.getTimestamp(); + Process process = new Process(processId, "CREATED", createdTime); + LogUtil.printMessage("Process Created [" + process.id + "], waiting for user to sign or decline..."); + this.setProcess(httpRequest, process); // Add process to session storage + this.newStatusFile(process.id,"", createdTime, true); // Set the status from the process in file system logs. + this.setBpn(process.id, bpn); return process; } @@ -521,6 +559,8 @@ public Process createProcess(String processId, HttpServletRequest httpRequest) { * the {@code String} URL address the of the connector. * @param created * the {@code Long} timestamp of the creation. + * @param childrenCondition + * the {@code Boolean} condition which enables if the search will request the irs for children * * @return a {@code String} file path of the created file. * @@ -528,7 +568,7 @@ public Process createProcess(String processId, HttpServletRequest httpRequest) { * if unable to create the status file. */ - public String newStatusFile(String processId, String connectorAddress, String dataPlaneAddress, Long created){ + public String newStatusFile(String processId, String connectorAddress, Long created, Boolean childrenCondition){ try { String path = this.getProcessFilePath(processId, this.metaFileName); return jsonUtil.toJsonFile( @@ -536,10 +576,15 @@ public String newStatusFile(String processId, String connectorAddress, String da new Status( processId, "CREATED", - connectorAddress, - dataPlaneAddress, created, - DateTimeUtil.getTimestamp() + DateTimeUtil.getTimestamp(), + new JobHistory(), + connectorAddress, + "", + "", + childrenCondition, + "", + Map.of(), "" ), processConfig.getIndent()); // Store the plain JSON } catch (Exception e) { @@ -628,7 +673,142 @@ public String setBpn(String processId, String bpn) { throw new ManagerException(this.getClass().getName(), e, "It was not possible to create/update the status file"); } } + /** + * Set the children condition in the status file + *

+ * @param processId + * the {@code String} id of the application's process. + * @param childrenCondition + * the {@code Boolean} condition that indicate if the process is accepting children or not + * + * @return a {@code String} file path of the process status file. + * + * @throws ManagerException + * if unable to update the status file. + */ + public String setChildrenCondition(String processId, Boolean childrenCondition) { + try { + String path = this.getProcessFilePath(processId, this.metaFileName); + Status statusFile = null; + if (!fileUtil.pathExists(path)) { + throw new ManagerException(this.getClass().getName(), "Process file does not exists for id ["+processId+"]!"); + } + + statusFile = (Status) jsonUtil.fromJsonFileToObject(path, Status.class); + statusFile.setChildren(childrenCondition); + statusFile.setModified(DateTimeUtil.getTimestamp()); + return jsonUtil.toJsonFile(path, statusFile, processConfig.getIndent()); // Store the plain JSON + } catch (Exception e) { + throw new ManagerException(this.getClass().getName(), e, "It was not possible to create/update the status file"); + } + } + /** + * Set current tree state of the process + *

+ * @param processId + * the {@code String} id of the application's process. + * @param state + * the {@code String} actual state from the tree + * + * @return a {@code String} file path of the process status file. + * + * @throws ManagerException + * if unable to update the status file. + */ + public String setTreeState(String processId, String state) { + try { + String path = this.getProcessFilePath(processId, this.metaFileName); + Status statusFile = null; + if (!fileUtil.pathExists(path)) { + throw new ManagerException(this.getClass().getName(), "Process file does not exists for id ["+processId+"]!"); + } + + statusFile = (Status) jsonUtil.fromJsonFileToObject(path, Status.class); + statusFile.setTreeState(state); + statusFile.setModified(DateTimeUtil.getTimestamp()); + return jsonUtil.toJsonFile(path, statusFile, processConfig.getIndent()); // Store the plain JSON + } catch (Exception e) { + throw new ManagerException(this.getClass().getName(), e, "It was not possible to create/update the status file"); + } + } + + /** + * Adds the job history in the status file + *

+ * @param processId + * the {@code String} id of the application's process. + * @param jobHistory + * the {@code JobHistory} job history from the irs + * + * @return a {@code String} file path of the process status file. + * + * @throws ManagerException + * if unable to update the status file. + */ + public String setJobHistory(String processId, JobHistory jobHistory) { + try { + String path = this.getProcessFilePath(processId, this.metaFileName); + Status statusFile = null; + if (!fileUtil.pathExists(path)) { + throw new ManagerException(this.getClass().getName(), "Process file does not exists for id ["+processId+"]!"); + } + + statusFile = (Status) jsonUtil.fromJsonFileToObject(path, Status.class); + statusFile.setJob(jobHistory); + String searchId = jobHistory.searchId; + statusFile.setHistory(jobHistory.searchId, new History(searchId, searchId+"-DRILLDOWN-STARTED")); + statusFile.setModified(DateTimeUtil.getTimestamp()); + return jsonUtil.toJsonFile(path, statusFile, processConfig.getIndent()); // Store the plain JSON + } catch (Exception e) { + throw new ManagerException(this.getClass().getName(), e, "It was not possible to create/update the status file"); + } + } + /** + * Adds the job history in the status file + *

+ * @param processId + * the {@code String} id of the application's process. + * @param children + * the {@code Integer} number of children found + * + * @return a {@code String} file path of the process status file. + * + * @throws ManagerException + * if unable to update the status file. + */ + public String setJobChildrenFound(String processId, Integer children) { + try { + String path = this.getProcessFilePath(processId, this.metaFileName); + Status statusFile = null; + if (!fileUtil.pathExists(path)) { + throw new ManagerException(this.getClass().getName(), "Process file does not exists for id ["+processId+"]!"); + } + statusFile = (Status) jsonUtil.fromJsonFileToObject(path, Status.class); + JobHistory jobHistory = statusFile.getJob(); + jobHistory.setChildren(children); + statusFile.setJob(jobHistory); + String searchId = jobHistory.searchId; + statusFile.setHistory(searchId, new History(searchId, searchId+"-DRILLDOWN-COMPLETED")); + statusFile.setModified(DateTimeUtil.getTimestamp()); + return jsonUtil.toJsonFile(path, statusFile, processConfig.getIndent()); // Store the plain JSON + } catch (Exception e) { + throw new ManagerException(this.getClass().getName(), e, "It was not possible to create/update the status file"); + } + } + /** + * Sets the semantic id in the status file + *

+ * @param processId + * the {@code String} id of the application's process. + * @param semanticId + * the {@code String} semantic ID in the status file + * + * @return a {@code String} file path of the process status file. + * + * @throws ManagerException + * if unable to update the status file. + */ public String setSemanticId(String processId, String semanticId) { try { String path = this.getProcessFilePath(processId, this.metaFileName); @@ -645,7 +825,39 @@ public String setSemanticId(String processId, String semanticId) { throw new ManagerException(this.getClass().getName(), e, "It was not possible to set the semanticId!"); } } + /** + * Sets the semantic id in the status file + *

+ * @param processId + * the {@code String} id of the application's process. + * @param semanticId + * the {@code String} semantic ID in the status file + * + * @return a {@code String} file path of the process status file. + * + * @throws ManagerException + * if unable to update the status file. + */ + public String saveTransferInfo(String processId, String connectorAddress, String semanticId, String dataPlaneUrl, String bpn, Boolean childrenCondition) { + try { + String path = this.getProcessFilePath(processId, this.metaFileName); + Status statusFile = null; + if (!fileUtil.pathExists(path)) { + throw new ManagerException(this.getClass().getName(), "Process file does not exists for id ["+processId+"]!"); + } + statusFile = (Status) jsonUtil.fromJsonFileToObject(path, Status.class); + statusFile.setSemanticId(semanticId); + statusFile.setEndpoint(connectorAddress); + statusFile.setDataPlaneUrl(dataPlaneUrl); + statusFile.setBpn(bpn); + statusFile.setChildren(childrenCondition); + statusFile.setModified(DateTimeUtil.getTimestamp()); + return jsonUtil.toJsonFile(path, statusFile, processConfig.getIndent()); // Store the plain JSON + } catch (Exception e) { + throw new ManagerException(this.getClass().getName(), e, "It was not possible to set the semanticId!"); + } + } /** * Sets the history of the process's status containing the given processId. *

diff --git a/consumer-backend/productpass/src/main/java/org/eclipse/tractusx/productpass/managers/TreeManager.java b/consumer-backend/productpass/src/main/java/org/eclipse/tractusx/productpass/managers/TreeManager.java new file mode 100644 index 000000000..0f9679673 --- /dev/null +++ b/consumer-backend/productpass/src/main/java/org/eclipse/tractusx/productpass/managers/TreeManager.java @@ -0,0 +1,504 @@ +/********************************************************************************* + * + * Catena-X - Product Passport Consumer Backend + * + * Copyright (c) 2022, 2023 BASF SE, BMW AG, Henkel AG & Co. KGaA + * Copyright (c) 2022, 2023 Contributors to the CatenaX (ng) GitHub Organisation. + * + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the + * License for the specific language govern in permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +package org.eclipse.tractusx.productpass.managers; + +import com.fasterxml.jackson.core.type.TypeReference; +import org.eclipse.tractusx.productpass.config.IrsConfig; +import org.eclipse.tractusx.productpass.exceptions.DataModelException; +import org.eclipse.tractusx.productpass.exceptions.ManagerException; +import org.eclipse.tractusx.productpass.models.catenax.EdcDiscoveryEndpoint; +import org.eclipse.tractusx.productpass.models.dtregistry.DigitalTwin; +import org.eclipse.tractusx.productpass.models.irs.Job; +import org.eclipse.tractusx.productpass.models.irs.JobHistory; +import org.eclipse.tractusx.productpass.models.irs.JobResponse; +import org.eclipse.tractusx.productpass.models.irs.Relationship; +import org.eclipse.tractusx.productpass.models.manager.Node; +import org.eclipse.tractusx.productpass.models.manager.NodeComponent; +import org.eclipse.tractusx.productpass.models.manager.Status; +import org.eclipse.tractusx.productpass.models.negotiation.Negotiation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import utils.*; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +/** + * This class consists exclusively of methods to operate on managing the Tree Structure for the IRS component. + * + *

The methods defined here are intended to do every needed operations to run the processes. + * + */ +@Component +public class TreeManager { + + /** ATTRIBUTES **/ + private FileUtil fileUtil; + private JsonUtil jsonUtil; + private ProcessManager processManager; + private IrsConfig irsConfig; + private final String PATH_SEP = "/"; + + /** CONSTRUCTOR(S) **/ + @Autowired + public TreeManager(FileUtil fileUtil, JsonUtil jsonUtil, ProcessManager processManager, IrsConfig irsConfig) { + this.fileUtil = fileUtil; + this.jsonUtil = jsonUtil; + this.processManager = processManager; + this.irsConfig = irsConfig; + } + + /** METHODS **/ + + /** + * Gets the Tree File Path by processId + *

+ * @param processId + * the {@code String} id of the application's process. + * + * @return a {@code String} Absolute file path for the tree file + * + */ + public String getTreeFilePath(String processId){ + return processManager.getProcessFilePath(processId, this.irsConfig.getTree().getFileName()); + } + /** + * Checks if the tree data model exists + *

+ * @param processId + * id from the process + * + * @return a {@code Boolean} Confirms if the tree data model exists + * + */ + public Boolean treeExists(String processId){ + String path = this.getTreeFilePath(processId); + return fileUtil.pathExists(path); + } + /** + * Creates a new tree file + *

+ * @param processId + * the {@code String} id of the application's process. + * + * @return a {@code String} Path to the tree file created + * + * @throws ManagerException + * if unable to create the file + */ + public String createTreeFile(String processId){ + try { + return this.saveTree(processId, Map.of()); // Save the tree + } catch (Exception e) { + throw new ManagerException(this.getClass().getName()+".newTree()", e, "It was not possible to create the tree data model file"); + } + } + /** + * Generates a new unique md5 hash (searchId) which is bounded by the globalAssetId and the ProcessId + *

+ * @param processId + * the {@code String} id of the application's process. + * @param globalAssetId + * global asset id {@code String} found in the digital twin asset + * + * @return a {@code String} Search id hash generated + * + */ + public static String generateSearchId(String processId, String globalAssetId) { + return CrypUtil.md5(DateTimeUtil.getDateTimeFormatted("yyyyMMddHHmmssSSS") + processId + globalAssetId); + } + /** + * Returns the tree component from the file system + *

+ * @param processId + * the {@code String} id of the application's process. + * + * @return a {@code Map} Map with the tree content + * + * @throws ManagerException + * if unable to get the tree + */ + public Map getTree(String processId){ + try { + String path = this.getTreeFilePath(processId); // Get filepath from tree + if(!fileUtil.pathExists(path)){ + this.createTreeFile(processId); // Create empty tree + return Map.of(); + } + return (Map) jsonUtil.fromJsonFileToObject(path, Map.class); // Store the plain JSON + } catch (Exception e) { + throw new ManagerException(this.getClass().getName()+".getTree()", e, "It was not possible to load the tree"); + } + } + + /** + * Recursive function to parse children from a node data model structure + *

+ * @param children + * the {@code Map} children objects to translate to list + * + * @return a {@code List} List of objects simplified from the tree + * + */ + public List parseChildren(Map children){ + List components = new ArrayList<>(); // Create a component list + if(children == null || children.size() == 0){ + return components; // Stop condition. Empty list when no children are available + } + List rawChildren = null; + try { + rawChildren = (List) jsonUtil.bindReferenceType(jsonUtil.mapToList(children), new TypeReference>() {}); + } catch (Exception e) { + throw new ManagerException(this.getClass().getName(), e, "Could not bind the reference type for the Node!"); + } + if(rawChildren == null){ + LogUtil.printWarning(this.getClass().getName() + ".parseChildren() It was not possible to parse children as a list of Nodes"); + return components; + } + rawChildren.forEach( + k -> { + List parsedChildren = this.parseChildren(k.getChildren()); // Parse the children below + NodeComponent childComponent = new NodeComponent(k,parsedChildren); // Add the existing children to a node component + components.add(childComponent); // Add to the list of components + } + ); + return components; // Return the components + } + + /** + * Recursive parsing of the current nodes, main parent function. + *

+ * @param currentNodes + * the {@code Map} objects to be translated to a list of children + * + * @return a {@code List} List of objects simplified from the tree + * + * @throws ManagerException + * if unable to parse the children recursively + */ + public List recursiveParseChildren(Map currentNodes){ + try { + return this.parseChildren(currentNodes); + }catch (Exception e) { + throw new ManagerException(this.getClass().getName()+".recursiveParseChildren()", e, "It was not possible to parse tree of nodes"); + } + + } + /** + * Gets the children components from the tree dataModel simplified + *

+ * @param processId + * the {@code String} id of the application's process. + * + * @return a {@code List} List of objects simplified from the tree + * + * @throws ManagerException + * if unable to get the tree components + */ + public List getTreeComponents(String processId){ + try { + Map treeDataModel = this.getTree(processId); // Get filepath from tree + return this.recursiveParseChildren(treeDataModel); + } catch (Exception e) { + throw new ManagerException(this.getClass().getName()+".getTreeComponents()", e, "It was not possible to get the tree components!"); + } + } + /** + * This method can search a digital twin inside of a digital twin list by id + *

+ * @param digitalTwinList + * the {@code List} id of the application's process. + * @param digitalTwinId + * the {@code String} id from the digital twin to be searched + * + * @return a {@code DigitalTwin} from the search result OR {@code null} if not found + * + */ + public DigitalTwin searchDigitalTwin(List digitalTwinList, String digitalTwinId){ + // Use a parallel search to make the search faster + return digitalTwinList.parallelStream().filter(digitalTwin -> digitalTwin.getGlobalAssetId().equals(digitalTwinId)).findFirst().orElse(new DigitalTwin()); + } + /** + * Populates a tree with the Job content and the relationships from an specific data model + *

+ * + * @param treeDataModel + * the {@code Map} tree data model to populate with children + * @param processId + * the {@code String} id of the application's process. + * @param jobHistory + * the {@code JobHistory} job history which contains the identification of the job + * @param job + * the {@code JobResponse} job response containing the IRS content + * + * @return a {@code String} data model file path + * + * @throws ManagerException + * if unable to populate the tree + */ + public String populateTree(Map treeDataModel, String processId, JobHistory jobHistory, JobResponse job){ + try { + List relationships = job.getRelationships(); + String parentPath = jobHistory.getPath(); + Node parent = this.getNodeByPath(treeDataModel, parentPath); + // All the relationships will be of depth one, so we just need to add them in the parent + List digitalTwinList = null; + try { + digitalTwinList = (List) jsonUtil.bindReferenceType(job.getShells(), new TypeReference>() {}); + } catch (Exception e) { + throw new ManagerException(this.getClass().getName(), e, "Could not bind the reference type for the Digital Twin!"); + } + int childrenFound = relationships.size(); + this.processManager.setJobChildrenFound(processId, childrenFound); + if(childrenFound == 0){ + parent.setChildren(null); // If there is no children return null; + treeDataModel = this.setNodeByPath(treeDataModel, parentPath, parent); // Save the parent node in the tree + return this.saveTree(processId, treeDataModel); + } + for(Relationship relationship : relationships){ + String childId = relationship.getLinkedItem().getChildCatenaXId(); + // Search for the Digital Twin from the child or a new instance + DigitalTwin childDigitalTwin = this.searchDigitalTwin(digitalTwinList, childId); + if(childDigitalTwin.getGlobalAssetId().isEmpty()){ + childDigitalTwin.setGlobalAssetId(childId); + } + // Create child with the digital twin + Node child = new Node(parentPath, childDigitalTwin); + // Add child to the parent + parent.setChild(child, childId); + } + // Set node and save the tree + treeDataModel = this.setNodeByPath(treeDataModel, parentPath, parent); // Save the parent node in the tree + return this.saveTree(processId, treeDataModel); + } catch (Exception e) { + throw new ManagerException(this.getClass().getName() + ".setChild()", e, "It was not possible to get the node from the tree"); + } + } + /** + * Populates a tree with the Job content and the relationships from the data model in the process file + *

+ * @param processId + * the {@code String} id of the application's process. + * @param jobHistory + * the {@code JobHistory} job history which contains the identification of the job + * @param job + * the {@code JobResponse} job response containing the IRS content + * + * @return a {@code String} data model file path + * + * @throws ManagerException + * if unable to populate the tree + */ + public String populateTree(String processId, JobHistory jobHistory, JobResponse job){ + try { + Map treeDataModel = this.getTree(processId); + return this.populateTree(treeDataModel,processId, jobHistory, job); + } catch (Exception e) { + throw new ManagerException(this.getClass().getName() + ".setChild()", e, "It was not possible to get the node from the tree"); + } + } + /** + * Saves the tree data model in the processId file + *

+ * @param processId + * the {@code String} id of the application's process. + * @param treeDataModel + * the {@code Map} tree data model to be saved + * + * @return a {@code String} data model file path + * + * @throws ManagerException + * if unable to save the tree data model + */ + public String saveTree(String processId, Map treeDataModel){ + try { + String path = this.getTreeFilePath(processId); // Get filepath from tree + return jsonUtil.toJsonFile( + path, + treeDataModel, + this.irsConfig.getTree().getIndent() + ); // Store the plain JSON + } catch (Exception e) { + throw new ManagerException(this.getClass().getName()+".saveTree()", e, "It was not possible to save the tree data model file"); + } + } + /** + * Gets a node in the specified tree by path + *

+ * @param treeDataModel + * the {@code Map} tree data model in which the node will be searched + * @param path + * the {@code String} unique path separated by "/" of the node in the tree "example: /node1/node2" + * + * @return a {@code Node} Node if found or {@code null} otherwise + * + * @throws ManagerException + * if unable to get the node by path + */ + public Node getNodeByPath(Map treeDataModel, String path){ + try { + String translatedPath = jsonUtil.translatePathSep(path, PATH_SEP, ".children."); // Join the path with the children + Object rawNode = this.jsonUtil.getValue(treeDataModel, translatedPath, ".", null); // Get the node; + Node node = null; + try { + node = (Node) jsonUtil.bindReferenceType(rawNode, new TypeReference() {}); + } catch (Exception e) { + throw new ManagerException(this.getClass().getName(), e, "Could not bind the reference type for the Node!"); + } + if(node == null){ // Check if the response was successful + throw new ManagerException(this.getClass().getName()+".getNodeByPath()", "It was not possible to set the node in path because the return was null"); + } + return node; + } catch (Exception e) { + throw new ManagerException(this.getClass().getName()+".getNodeByPath()", e, "It was not possible to get the node from the tree"); + } + } + /** + * Gets a node in the specified tree by path + *

+ * @param processId + * the {@code String} process id in which the data model will be retrieved and then searched + * @param path + * the {@code String} unique path separated by "/" of the node in the tree "example: /node1/node2" + * + * @return a {@code Node} Node if found or {@code null} otherwise + * + * @throws ManagerException + * if unable to get the node by path + */ + public Node getNodeByPath(String processId, String path){ + try { + Map treeDataModel = this.getTree(processId); + String translatedPath = jsonUtil.translatePathSep(path, PATH_SEP, ".children."); // Join the path with the children + Object rawNode = this.jsonUtil.getValue(treeDataModel, translatedPath, ".", null); // Get the node; + Node node = null; + try { + node = (Node) jsonUtil.bindReferenceType(rawNode, new TypeReference() {}); + } catch (Exception e) { + throw new ManagerException(this.getClass().getName(), e, "Could not bind the reference type for the Node!"); + } + if(node == null){ // Check if the response was successful + throw new ManagerException(this.getClass().getName()+".getNodeByPath()", "It was not possible to set the node in path because the return was null"); + } + return node; + } catch (Exception e) { + throw new ManagerException(this.getClass().getName()+".getNodeByPath()", e, "It was not possible to get the node from the tree"); + } + } + /** + * Gets a node in the specified tree by path + *

+ * @param treeDataModel + * the {@code Map} tree data model in which the node will be added + * @param path + * the {@code String} unique path separated by "/" of the node in the tree "example: /node1/node2" + * @param node + * the {@code Node} Node object which needs to be added in the data model + * + * @return a {@code (Map)} Data Model in which the node was stored + * + * @throws ManagerException + * if unable to save in the data model the node + */ + public Map setNodeByPath(Map treeDataModel, String path, Node node){ + try { + String translatedPath = jsonUtil.translatePathSep(path, PATH_SEP, ".children."); // Join the path with the children + Object rawDataModel = this.jsonUtil.setValue(treeDataModel, translatedPath, node, ".", null); // Set the node + try { + treeDataModel = (Map) jsonUtil.bindReferenceType(rawDataModel, new TypeReference>() {}); + } catch (Exception e) { + throw new ManagerException(this.getClass().getName(), e, "Could not bind the reference type for the Node!"); + } + if(treeDataModel == null){ // Check if the response was successful + throw new ManagerException(this.getClass().getName()+".setNodeByPath()", "It was not possible to set the node in path because the return was null"); + } + return treeDataModel; + } catch (Exception e) { + throw new ManagerException(this.getClass().getName()+".setNodeByPath()", e, "It was not possible to get the node from the tree"); + } + } + /** + * Gets a node in the specified tree by path + *

+ * @param processId + * the {@code String} process id in which the data model will be retrieved and then substituted + * @param path + * the {@code String} unique path separated by "/" of the node in the tree "example: /node1/node2" + * @param node + * the {@code Node} Node object which needs to be added in the data model + * + * @return a {@code String} Data Model File Path where the node was stored + * + * @throws ManagerException + * if unable to save in the data model the node + */ + public String setNodeByPath(String processId, String path, Node node){ + try { + Map treeDataModel = this.getTree(processId); + String translatedPath = jsonUtil.translatePathSep(path, PATH_SEP, ".children."); // Join the path with the children + Object rawDataModel = this.jsonUtil.setValue(treeDataModel, translatedPath, node, ".", null); // Set the node + try { + treeDataModel = (Map) jsonUtil.bindReferenceType(rawDataModel, new TypeReference>() {}); + } catch (Exception e) { + throw new ManagerException(this.getClass().getName(), e, "Could not bind the reference type for the Tree Data Model!"); + } + return this.saveTree(processId, treeDataModel); + } catch (Exception e) { + throw new ManagerException(this.getClass().getName()+".setNodeByPath()", e, "It was not possible to get the node from the tree"); + } + } + /** + * Gets a node in the specified tree by path + *

+ * @param processId + * the {@code String} process id in which the data model will be retrieved and then substituted + * @param parentPath + * the {@code String} parent node unique path separated by "/" of the node in the tree "example: /node1/node2" + * @param childNode + * the {@code Node} children node which will be added to the father + * + * @return a {@code String} Data Model File Path where the node was stored + * + * @throws ManagerException + * if unable to save in the data model and in the parent the child node + */ + public String setChild(String processId, String parentPath, Node childNode, String globalAssetId){ + try { + Map treeDataModel = this.getTree(processId); + Node parentNode = this.getNodeByPath(treeDataModel, parentPath); // Get parent node + parentNode.setChild(childNode, globalAssetId); // Add the child to the parent node + treeDataModel = this.setNodeByPath(treeDataModel, parentPath, parentNode); // Save the parent node in the tree + return this.saveTree(processId, treeDataModel); + } catch (Exception e) { + throw new ManagerException(this.getClass().getName()+".setChild()", e, "It was not possible to get the node from the tree"); + } + } + + + + + +} diff --git a/consumer-backend/productpass/src/main/java/org/eclipse/tractusx/productpass/models/dtregistry/DigitalTwin.java b/consumer-backend/productpass/src/main/java/org/eclipse/tractusx/productpass/models/dtregistry/DigitalTwin.java index 6462e2313..79bdc0363 100644 --- a/consumer-backend/productpass/src/main/java/org/eclipse/tractusx/productpass/models/dtregistry/DigitalTwin.java +++ b/consumer-backend/productpass/src/main/java/org/eclipse/tractusx/productpass/models/dtregistry/DigitalTwin.java @@ -29,6 +29,9 @@ import com.fasterxml.jackson.databind.JsonNode; import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; /** * This class consists exclusively to define attributes related to the designed model of the Digital Twin's improved version. @@ -53,43 +56,11 @@ public class DigitalTwin { @JsonProperty("id") String identification; @JsonProperty("specificAssetIds") - ArrayList specificAssetIds; + ArrayList specificAssetIds; @JsonProperty("submodelDescriptors") ArrayList submodelDescriptors; - /** CONSTRUCTOR(S) **/ - @SuppressWarnings("Unused") - public DigitalTwin() { - } - @SuppressWarnings("Unused") - public DigitalTwin(ArrayList description, String idShort, String identification, ArrayList specificAssetIds, ArrayList submodelDescriptors) { - this.description = description; - this.idShort = idShort; - this.identification = identification; - this.specificAssetIds = specificAssetIds; - this.submodelDescriptors = submodelDescriptors; - } - @SuppressWarnings("Unused") - public DigitalTwin(ArrayList description, String idShort, Object displayName, String identification, ArrayList specificAssetIds, ArrayList submodelDescriptors) { - this.description = description; - this.idShort = idShort; - this.displayName = displayName; - this.identification = identification; - this.specificAssetIds = specificAssetIds; - this.submodelDescriptors = submodelDescriptors; - } - @SuppressWarnings("Unused") - public DigitalTwin(ArrayList description, String idShort, String globalAssetId, Object displayName, String identification, ArrayList specificAssetIds, ArrayList submodelDescriptors) { - this.description = description; - this.idShort = idShort; - this.globalAssetId = globalAssetId; - this.displayName = displayName; - this.identification = identification; - this.specificAssetIds = specificAssetIds; - this.submodelDescriptors = submodelDescriptors; - } - @SuppressWarnings("Unused") - public DigitalTwin(ArrayList description, String idShort, String assetKind, String assetType, String globalAssetId, Object displayName, String identification, ArrayList specificAssetIds, ArrayList submodelDescriptors) { + public DigitalTwin(ArrayList description, String idShort, String assetKind, String assetType, String globalAssetId, Object displayName, String identification, ArrayList specificAssetIds, ArrayList submodelDescriptors) { this.description = description; this.idShort = idShort; this.assetKind = assetKind; @@ -101,7 +72,94 @@ public DigitalTwin(ArrayList description, String idShort, String asset this.submodelDescriptors = submodelDescriptors; } + /** INNER CLASSES **/ + + /** + * This class consists exclusively to define attributes and methods related to the digital twin specific asset ids + **/ + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonInclude(JsonInclude.Include.NON_NULL) + static class SpecificAssetId{ + /** ATTRIBUTES **/ + + @JsonProperty("supplementalSemanticIds") + List supplementalSemanticIds; + + @JsonProperty("name") + String name; + + @JsonProperty("value") + String value; + + @JsonProperty("externalSubjectId") + Object externalSubjectId; + /** CONSTRUCTOR(S) **/ + public SpecificAssetId(List supplementalSemanticIds, String name, String value, Object externalSubjectId) { + this.supplementalSemanticIds = supplementalSemanticIds; + this.name = name; + this.value = value; + this.externalSubjectId = externalSubjectId; + } + /** GETTERS AND SETTERS **/ + public List getSupplementalSemanticIds() { + return supplementalSemanticIds; + } + + public void setSupplementalSemanticIds(List supplementalSemanticIds) { + this.supplementalSemanticIds = supplementalSemanticIds; + } + + public String getName() { + return name; + } + + public String parseLowerCaseName() { + return name.toLowerCase(); + } + + + public void setName(String name) { + this.name = name; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public Object getExternalSubjectId() { + return externalSubjectId; + } + + public void setExternalSubjectId(Object externalSubjectId) { + this.externalSubjectId = externalSubjectId; + } + } + + /** CONSTRUCTOR(S) **/ + @SuppressWarnings("Unused") + public DigitalTwin() { + } + + /** GETTERS AND SETTERS **/ + public ArrayList getSpecificAssetIds() { + return specificAssetIds; + } + + public void setSpecificAssetIds(ArrayList specificAssetIds) { + this.specificAssetIds = specificAssetIds; + } + + public Map mapSpecificAssetIds() { + return this.getSpecificAssetIds().stream().collect( + Collectors.toMap(SpecificAssetId::parseLowerCaseName, SpecificAssetId::getValue) + ); + } + public ArrayList getDescription() { return description; } @@ -121,14 +179,7 @@ public String getIdentification() { public void setIdentification(String identification) { this.identification = identification; } - @SuppressWarnings("Unused") - public ArrayList getSpecificAssetIds() { - return specificAssetIds; - } - @SuppressWarnings("Unused") - public void setSpecificAssetIds(ArrayList specificAssetIds) { - this.specificAssetIds = specificAssetIds; - } + public ArrayList getSubmodelDescriptors() { return submodelDescriptors; } diff --git a/consumer-backend/productpass/src/main/java/org/eclipse/tractusx/productpass/models/dtregistry/EndPoint.java b/consumer-backend/productpass/src/main/java/org/eclipse/tractusx/productpass/models/dtregistry/EndPoint.java index d31749912..4a0aa63b2 100644 --- a/consumer-backend/productpass/src/main/java/org/eclipse/tractusx/productpass/models/dtregistry/EndPoint.java +++ b/consumer-backend/productpass/src/main/java/org/eclipse/tractusx/productpass/models/dtregistry/EndPoint.java @@ -65,6 +65,8 @@ public void setProtocolInformation(String endpointAddress, String endpointProtoc /** * This class consists exclusively to define attributes related to the needed protocol information for the improved version. **/ + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonInclude(JsonInclude.Include.NON_NULL) public static class ProtocolInformation3 { /** ATTRIBUTES **/ @@ -156,7 +158,7 @@ public void setSubprotocol(String subprotocol) { public String getSubprotocolBody() { return subprotocolBody; } - public Map getParsedSubprotocolBody() { + public Map parseSubProtocolBody() { try { return Splitter.on(';').withKeyValueSeparator('=').split(this.subprotocolBody); }catch (Exception e){ diff --git a/consumer-backend/productpass/src/main/java/org/eclipse/tractusx/productpass/models/http/requests/Search.java b/consumer-backend/productpass/src/main/java/org/eclipse/tractusx/productpass/models/http/requests/Search.java index 0d8fc9268..726db22d7 100644 --- a/consumer-backend/productpass/src/main/java/org/eclipse/tractusx/productpass/models/http/requests/Search.java +++ b/consumer-backend/productpass/src/main/java/org/eclipse/tractusx/productpass/models/http/requests/Search.java @@ -48,6 +48,12 @@ public class Search { String idType = "partInstanceId"; @JsonProperty(value = "dtIndex", defaultValue = "0") Integer dtIndex = 0; + + @JsonProperty(value = "children", defaultValue = "true") + Boolean children = true; + + @JsonProperty(value = "idShort", defaultValue = "batteryPass") + String idShort = "batteryPass"; @JsonProperty(value = "semanticId") String semanticId; @@ -65,6 +71,16 @@ public Search(String processId, String id, String version, String idType, Intege this.semanticId = semanticId; } + public Search(String processId, String id, String version, String idType, Integer dtIndex, Boolean children, String idShort, String semanticId) { + this.processId = processId; + this.id = id; + this.version = version; + this.idType = idType; + this.dtIndex = dtIndex; + this.children = children; + this.idShort = idShort; + this.semanticId = semanticId; + } /** GETTERS AND SETTERS **/ public String getSemanticId() { return semanticId; @@ -102,4 +118,12 @@ public String getVersion() { public void setVersion(String version) { this.version = version; } + + public Boolean getChildren() { + return children; + } + + public void setChildren(Boolean children) { + this.children = children; + } } diff --git a/consumer-backend/productpass/src/main/java/org/eclipse/tractusx/productpass/models/irs/Job.java b/consumer-backend/productpass/src/main/java/org/eclipse/tractusx/productpass/models/irs/Job.java new file mode 100644 index 000000000..c6f136399 --- /dev/null +++ b/consumer-backend/productpass/src/main/java/org/eclipse/tractusx/productpass/models/irs/Job.java @@ -0,0 +1,181 @@ +/********************************************************************************* + * + * Catena-X - Product Passport Consumer Backend + * + * Copyright (c) 2022, 2023 BASF SE, BMW AG, Henkel AG & Co. KGaA + * Copyright (c) 2022, 2023 Contributors to the CatenaX (ng) GitHub Organisation. + * + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the + * License for the specific language govern in permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +package org.eclipse.tractusx.productpass.models.irs; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.ArrayList; +/** + * This class consists exclusively to define attributes related to the Job. + **/ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +public class Job { + + /** ATTRIBUTES **/ + @JsonProperty("id") + public String id; + + @JsonProperty("globalAssetId") + public String globalAssetId; + + @JsonProperty("state") + public String state; + + @JsonProperty("exception") + public Object exception; + + @JsonProperty("createdOn") + public String createdOn; + + @JsonProperty("startedOn") + public String startedOn; + + @JsonProperty("lastModifiedOn") + public String lastModifiedOn; + + @JsonProperty("completedOn") + public String completedOn; + + @JsonProperty("owner") + public String owner; + + @JsonProperty("summary") + public Object summary; + + @JsonProperty("parameter") + public JobRequest parameter; + + /** CONSTRUCTOR(S) **/ + public Job() { + } + + public Job(String id, String globalAssetId, String state, Object exception, String createdOn, String startedOn, String lastModifiedOn, String completedOn, String owner, Object summary, JobRequest parameter) { + this.id = id; + this.globalAssetId = globalAssetId; + this.state = state; + this.exception = exception; + this.createdOn = createdOn; + this.startedOn = startedOn; + this.lastModifiedOn = lastModifiedOn; + this.completedOn = completedOn; + this.owner = owner; + this.summary = summary; + this.parameter = parameter; + } + + /** GETTERS AND SETTERS **/ + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getGlobalAssetId() { + return globalAssetId; + } + + public void setGlobalAssetId(String globalAssetId) { + this.globalAssetId = globalAssetId; + } + + public String getState() { + return state; + } + + public void setState(String state) { + this.state = state; + } + + public Object getException() { + return exception; + } + + public void setException(Object exception) { + this.exception = exception; + } + + public String getCreatedOn() { + return createdOn; + } + + public void setCreatedOn(String createdOn) { + this.createdOn = createdOn; + } + + public String getStartedOn() { + return startedOn; + } + + public void setStartedOn(String startedOn) { + this.startedOn = startedOn; + } + + public String getLastModifiedOn() { + return lastModifiedOn; + } + + public void setLastModifiedOn(String lastModifiedOn) { + this.lastModifiedOn = lastModifiedOn; + } + + public String getCompletedOn() { + return completedOn; + } + + public void setCompletedOn(String completedOn) { + this.completedOn = completedOn; + } + + public String getOwner() { + return owner; + } + + public void setOwner(String owner) { + this.owner = owner; + } + + public Object getSummary() { + return summary; + } + + public void setSummary(Object summary) { + this.summary = summary; + } + + public JobRequest getParameter() { + return parameter; + } + + public void setParameter(JobRequest parameter) { + this.parameter = parameter; + } +} diff --git a/consumer-backend/productpass/src/main/java/org/eclipse/tractusx/productpass/models/irs/JobHistory.java b/consumer-backend/productpass/src/main/java/org/eclipse/tractusx/productpass/models/irs/JobHistory.java new file mode 100644 index 000000000..a01c03e3a --- /dev/null +++ b/consumer-backend/productpass/src/main/java/org/eclipse/tractusx/productpass/models/irs/JobHistory.java @@ -0,0 +1,123 @@ +/********************************************************************************* + * + * Catena-X - Product Passport Consumer Backend + * + * Copyright (c) 2022, 2023 BASF SE, BMW AG, Henkel AG & Co. KGaA + * Copyright (c) 2022, 2023 Contributors to the CatenaX (ng) GitHub Organisation. + * + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the + * License for the specific language govern in permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +package org.eclipse.tractusx.productpass.models.irs; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class JobHistory { + + /** ATTRIBUTES **/ + @JsonProperty("jobId") + public String jobId; + @JsonProperty("searchId") + public String searchId; + + @JsonProperty("globalAssetId") + public String globalAssetId; + @JsonProperty("path") + public String path; + @JsonProperty("created") + public Long created; + @JsonProperty("updated") + public Long updated; + @JsonProperty("children") + public Integer children; + + /** CONSTRUCTOR(S) **/ + public JobHistory() { + } + + public JobHistory(String jobId, String searchId, String globalAssetId, String path, Long created, Long updated, Integer children) { + this.jobId = jobId; + this.searchId = searchId; + this.globalAssetId = globalAssetId; + this.path = path; + this.created = created; + this.updated = updated; + this.children = children; + } + + + /** GETTERS AND SETTERS **/ + public String getJobId() { + return jobId; + } + + public void setJobId(String jobId) { + this.jobId = jobId; + } + + + public String getGlobalAssetId() { + return globalAssetId; + } + + public void setGlobalAssetId(String globalAssetId) { + this.globalAssetId = globalAssetId; + } + + + public Long getCreated() { + return created; + } + + public void setCreated(Long created) { + this.created = created; + } + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + + public Long getUpdated() { + return updated; + } + + public void setUpdated(Long updated) { + this.updated = updated; + } + + + public String getSearchId() { + return searchId; + } + + public void setSearchId(String searchId) { + this.searchId = searchId; + } + + public Integer getChildren() { + return children; + } + + public void setChildren(Integer children) { + this.children = children; + } +} diff --git a/consumer-backend/productpass/src/main/java/org/eclipse/tractusx/productpass/models/irs/JobRequest.java b/consumer-backend/productpass/src/main/java/org/eclipse/tractusx/productpass/models/irs/JobRequest.java new file mode 100644 index 000000000..db4522f46 --- /dev/null +++ b/consumer-backend/productpass/src/main/java/org/eclipse/tractusx/productpass/models/irs/JobRequest.java @@ -0,0 +1,230 @@ +/********************************************************************************* + * + * Catena-X - Product Passport Consumer Backend + * + * Copyright (c) 2022, 2023 BASF SE, BMW AG, Henkel AG & Co. KGaA + * Copyright (c) 2022, 2023 Contributors to the CatenaX (ng) GitHub Organisation. + * + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the + * License for the specific language govern in permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +package org.eclipse.tractusx.productpass.models.irs; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.ArrayList; + +/** + * This class states how a Job request for the IRS Component need to be created. + * + *

It defines the structure to follow when requesting a Job Creation. + * This is a model class and is used by the IRS service. + * + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +public class JobRequest { + /** ATTRIBUTES **/ + @JsonProperty("aspects") + public ArrayList aspects; + + @JsonProperty("bomLifecycle") + public String bomLifecycle; + @JsonProperty("lookupBPNs") + public Boolean lookupBPNs; + + @JsonProperty("collectAspects") + public Boolean collectAspects; + + @JsonProperty("direction") + public String direction; + + @JsonProperty("callbackUrl") + public String callbackUrl; + + @JsonProperty("depth") + public Integer depth; + + @JsonProperty("integrityCheck") + public Boolean integrityCheck; + + @JsonProperty("key") + public Key key; + + /** CONSTRUCTOR(S) **/ + public JobRequest(ArrayList aspects, String bomLifecycle, Boolean lookupBPNs, Boolean collectAspects, String direction, Integer depth, Boolean integrityCheck, Key key) { + this.aspects = aspects; + this.bomLifecycle = bomLifecycle; + this.lookupBPNs = lookupBPNs; + this.collectAspects = collectAspects; + this.direction = direction; + this.depth = depth; + this.integrityCheck = integrityCheck; + this.key = key; + } + + public JobRequest() { + } + + public JobRequest(ArrayList aspects, String bomLifecycle, Boolean lookupBPNs, Boolean collectAspects, String direction, Integer depth, Boolean integrityCheck) { + this.aspects = aspects; + this.bomLifecycle = bomLifecycle; + this.lookupBPNs = lookupBPNs; + this.collectAspects = collectAspects; + this.direction = direction; + this.depth = depth; + this.integrityCheck = integrityCheck; + } + public JobRequest(ArrayList aspects, String bomLifecycle, Boolean lookupBPNs, Boolean collectAspects, String direction, Integer depth, Boolean integrityCheck, String callbackUrl) { + this.aspects = aspects; + this.bomLifecycle = bomLifecycle; + this.lookupBPNs = lookupBPNs; + this.collectAspects = collectAspects; + this.direction = direction; + this.callbackUrl = callbackUrl; + this.depth = depth; + this.integrityCheck = integrityCheck; + } + public JobRequest(ArrayList aspects, String bomLifecycle, Boolean lookupBPNs, Boolean collectAspects, String direction, Integer depth, Boolean integrityCheck, Key key, String callbackUrl) { + this.aspects = aspects; + this.bomLifecycle = bomLifecycle; + this.lookupBPNs = lookupBPNs; + this.collectAspects = collectAspects; + this.direction = direction; + this.callbackUrl = callbackUrl; + this.depth = depth; + this.integrityCheck = integrityCheck; + this.key = key; + } + + /** GETTERS AND SETTERS **/ + public ArrayList getAspects() { + return aspects; + } + + public void setAspects(ArrayList aspects) { + this.aspects = aspects; + } + + public String getBomLifecycle() { + return bomLifecycle; + } + + public void setBomLifecycle(String bomLifecycle) { + this.bomLifecycle = bomLifecycle; + } + + public Boolean getLookupBPNs() { + return lookupBPNs; + } + + public void setLookupBPNs(Boolean lookupBPNs) { + this.lookupBPNs = lookupBPNs; + } + + public Boolean getCollectAspects() { + return collectAspects; + } + + public void setCollectAspects(Boolean collectAspects) { + this.collectAspects = collectAspects; + } + + public String getDirection() { + return direction; + } + + public void setDirection(String direction) { + this.direction = direction; + } + + public Integer getDepth() { + return depth; + } + + public void setDepth(Integer depth) { + this.depth = depth; + } + + public Boolean getIntegrityCheck() { + return integrityCheck; + } + + public void setIntegrityCheck(Boolean integrityCheck) { + this.integrityCheck = integrityCheck; + } + + public Key getKey() { + return key; + } + + public void setKey(Key key) { + this.key = key; + } + + public void setKey(String globalAssetId, String bpn) { + this.key = new Key(globalAssetId, bpn); + } + + public String getCallbackUrl() { + return callbackUrl; + } + + public void setCallbackUrl(String callbackUrl) { + this.callbackUrl = callbackUrl; + } + + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonInclude(JsonInclude.Include.NON_NULL) + static class Key { + @JsonProperty("globalAssetId") + String globalAssetId; + @JsonProperty("bpn") + String bpn; + + public Key(String globalAssetId, String bpn) { + this.globalAssetId = globalAssetId; + this.bpn = bpn; + } + + public Key() { + } + + public String getGlobalAssetId() { + return globalAssetId; + } + + public void setGlobalAssetId(String globalAssetId) { + this.globalAssetId = globalAssetId; + } + + public String getBpn() { + return bpn; + } + + public void setBpn(String bpn) { + this.bpn = bpn; + } + } + + + + +} diff --git a/consumer-backend/productpass/src/main/java/org/eclipse/tractusx/productpass/models/irs/JobResponse.java b/consumer-backend/productpass/src/main/java/org/eclipse/tractusx/productpass/models/irs/JobResponse.java new file mode 100644 index 000000000..a4ea1d454 --- /dev/null +++ b/consumer-backend/productpass/src/main/java/org/eclipse/tractusx/productpass/models/irs/JobResponse.java @@ -0,0 +1,118 @@ +/********************************************************************************* + * + * Catena-X - Product Passport Consumer Backend + * + * Copyright (c) 2022, 2023 BASF SE, BMW AG, Henkel AG & Co. KGaA + * Copyright (c) 2022, 2023 Contributors to the CatenaX (ng) GitHub Organisation. + * + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the + * License for the specific language govern in permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +package org.eclipse.tractusx.productpass.models.irs; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.JsonNode; +import org.eclipse.tractusx.productpass.models.dtregistry.DigitalTwin; + +import java.util.ArrayList; + +/** + * This class consists exclusively to define attributes related to the Job Response coming from the IRS. + **/ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +public class JobResponse { + + /** ATTRIBUTES **/ + @JsonProperty("job") + public Job job; + @JsonProperty("relationships") + public ArrayList relationships; + @JsonProperty("shells") + public ArrayList shells; + @JsonProperty("tombstones") + public Object tombstones; + @JsonProperty("submodels") + public ArrayList submodels; + @JsonProperty("bpns") + public ArrayList bpns; + + /** CONSTRUCTOR(S) **/ + public JobResponse(Job job, ArrayList relationships, ArrayList shells, Object tombstones, ArrayList submodels, ArrayList bpns) { + this.job = job; + this.relationships = relationships; + this.shells = shells; + this.tombstones = tombstones; + this.submodels = submodels; + this.bpns = bpns; + } + + public JobResponse() { + } + + /** GETTERS AND SETTERS **/ + public Job getJob() { + return job; + } + + public void setJob(Job job) { + this.job = job; + } + + public ArrayList getRelationships() { + return relationships; + } + + public void setRelationships(ArrayList relationships) { + this.relationships = relationships; + } + + public ArrayList getShells() { + return shells; + } + + public void setShells(ArrayList shells) { + this.shells = shells; + } + + public Object getTombstones() { + return tombstones; + } + + public void setTombstones(Object tombstones) { + this.tombstones = tombstones; + } + + public ArrayList getSubmodels() { + return submodels; + } + + public void setSubmodels(ArrayList submodels) { + this.submodels = submodels; + } + + public ArrayList getBpns() { + return bpns; + } + + public void setBpns(ArrayList bpns) { + this.bpns = bpns; + } +} diff --git a/consumer-backend/productpass/src/main/java/org/eclipse/tractusx/productpass/models/irs/Relationship.java b/consumer-backend/productpass/src/main/java/org/eclipse/tractusx/productpass/models/irs/Relationship.java new file mode 100644 index 000000000..446aee234 --- /dev/null +++ b/consumer-backend/productpass/src/main/java/org/eclipse/tractusx/productpass/models/irs/Relationship.java @@ -0,0 +1,226 @@ +/********************************************************************************* + * + * Catena-X - Product Passport Consumer Backend + * + * Copyright (c) 2022, 2023 BASF SE, BMW AG, Henkel AG & Co. KGaA + * Copyright (c) 2022, 2023 Contributors to the CatenaX (ng) GitHub Organisation. + * + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the + * License for the specific language govern in permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +package org.eclipse.tractusx.productpass.models.irs; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +/** + * This class consists exclusively to define attributes related to the Relationships from the Job Response from the IRS. + **/ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +public class Relationship { + + /** ATTRIBUTES **/ + @JsonProperty("catenaXId") + public String catenaXId; + @JsonProperty("linkedItem") + public Item linkedItem; + @JsonProperty("aspectType") + public String aspectType; + @JsonProperty("bpn") + public String bpn; + + /** CONSTRUCTOR(S) **/ + public Relationship() { + } + + public Relationship(String catenaXId, Item linkedItem, String aspectType, String bpn) { + this.catenaXId = catenaXId; + this.linkedItem = linkedItem; + this.aspectType = aspectType; + this.bpn = bpn; + } + + /** GETTERS AND SETTERS **/ + public String getCatenaXId() { + return catenaXId; + } + + public void setCatenaXId(String catenaXId) { + this.catenaXId = catenaXId; + } + + public Item getLinkedItem() { + return linkedItem; + } + + public void setLinkedItem(Item linkedItem) { + this.linkedItem = linkedItem; + } + + public String getAspectType() { + return aspectType; + } + + public void setAspectType(String aspectType) { + this.aspectType = aspectType; + } + + public String getBpn() { + return bpn; + } + + public void setBpn(String bpn) { + this.bpn = bpn; + } + + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonInclude(JsonInclude.Include.NON_NULL) + public static class Item { + + @JsonProperty("quantity") + Quantity quantity; + @JsonProperty("licecycleContext") + String licecycleContext; + @JsonProperty("assembledOn") + String assembledOn; + @JsonProperty("lastModifiedOn") + String lastModifiedOn; + @JsonProperty("childCatenaXId") + String childCatenaXId; + + public Item(Quantity quantity, String licecycleContext, String assembledOn, String lastModifiedOn, String childCatenaXId) { + this.quantity = quantity; + this.licecycleContext = licecycleContext; + this.assembledOn = assembledOn; + this.lastModifiedOn = lastModifiedOn; + this.childCatenaXId = childCatenaXId; + } + + public Item() { + } + + public Quantity getQuantity() { + return quantity; + } + + public void setQuantity(Quantity quantity) { + this.quantity = quantity; + } + + public String getLicecycleContext() { + return licecycleContext; + } + + public void setLicecycleContext(String licecycleContext) { + this.licecycleContext = licecycleContext; + } + + public String getAssembledOn() { + return assembledOn; + } + + public void setAssembledOn(String assembledOn) { + this.assembledOn = assembledOn; + } + + public String getLastModifiedOn() { + return lastModifiedOn; + } + + public void setLastModifiedOn(String lastModifiedOn) { + this.lastModifiedOn = lastModifiedOn; + } + + public String getChildCatenaXId() { + return childCatenaXId; + } + + public void setChildCatenaXId(String childCatenaXId) { + this.childCatenaXId = childCatenaXId; + } + + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonInclude(JsonInclude.Include.NON_NULL) + public static class Quantity{ + @JsonProperty("quantityNumber") + Double quantityNumber; + @JsonProperty("measurementUnit") + MeasurementUnit measurementUnit; + + public Quantity(Double quantityNumber, MeasurementUnit measurementUnit) { + this.quantityNumber = quantityNumber; + this.measurementUnit = measurementUnit; + } + + public Quantity() { + } + + public Double getQuantityNumber() { + return quantityNumber; + } + + public void setQuantityNumber(Double quantityNumber) { + this.quantityNumber = quantityNumber; + } + + public MeasurementUnit getMeasurementUnit() { + return measurementUnit; + } + + public void setMeasurementUnit(MeasurementUnit measurementUnit) { + this.measurementUnit = measurementUnit; + } + + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonInclude(JsonInclude.Include.NON_NULL) + public static class MeasurementUnit{ + @JsonProperty("datatypeURI") + String datatypeURI; + @JsonProperty("lexicalValue") + String lexicalValue; + + public MeasurementUnit(String datatypeURI, String lexicalValue) { + this.datatypeURI = datatypeURI; + this.lexicalValue = lexicalValue; + } + + public MeasurementUnit() { + } + + public String getDatatypeURI() { + return datatypeURI; + } + + public void setDatatypeURI(String datatypeURI) { + this.datatypeURI = datatypeURI; + } + + public String getLexicalValue() { + return lexicalValue; + } + + public void setLexicalValue(String lexicalValue) { + this.lexicalValue = lexicalValue; + } + } + + } + } + +} diff --git a/consumer-backend/productpass/src/main/java/org/eclipse/tractusx/productpass/models/manager/Node.java b/consumer-backend/productpass/src/main/java/org/eclipse/tractusx/productpass/models/manager/Node.java new file mode 100644 index 000000000..d2b425fd7 --- /dev/null +++ b/consumer-backend/productpass/src/main/java/org/eclipse/tractusx/productpass/models/manager/Node.java @@ -0,0 +1,222 @@ +/********************************************************************************* + * + * Catena-X - Product Passport Consumer Backend + * + * Copyright (c) 2022, 2023 BASF SE, BMW AG, Henkel AG & Co. KGaA + * Copyright (c) 2022, 2023 Contributors to the CatenaX (ng) GitHub Organisation. + * + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the + * License for the specific language govern in permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +package org.eclipse.tractusx.productpass.models.manager; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.eclipse.tractusx.productpass.models.dtregistry.DigitalTwin; +import org.eclipse.tractusx.productpass.models.irs.JobResponse; +import utils.CatenaXUtil; + +import java.util.Map; +/** + * This class consists exclusively to define attributes related to the Node. + **/ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +public class Node { + + /** ATTRIBUTES **/ + @JsonProperty("id") + public String id; + @JsonProperty("globalAssetId") + public String globalAssetId; + @JsonProperty("idShort") + public String idShort; + @JsonProperty("searchId") + public String searchId; + @JsonProperty("path") + public String path; + @JsonProperty("digitalTwin") + public DigitalTwin digitalTwin; + @JsonProperty("job") + public JobResponse job; + @JsonProperty("children") + public Map children; + + /** CONSTRUCTOR(S) **/ + public Node(Node parent, DigitalTwin digitalTwin, Map children){ + this.setup(digitalTwin); + this.setPath(parent, digitalTwin.getIdentification()); + this.children = children; + } + + public Node(String parentPath, DigitalTwin digitalTwin, Map children){ + this.setup(digitalTwin); + this.setPath(parentPath, digitalTwin.getIdentification()); + this.children = children; + } + + public Node(DigitalTwin digitalTwin, Map children){ + this.setup(digitalTwin); + this.setPath("", digitalTwin.getIdentification()); + this.children = children; + } + public Node(Node parent, DigitalTwin digitalTwin){ + this.setup(digitalTwin); + this.setPath(parent, digitalTwin.getIdentification()); + this.children = Map.of(); + } + + public Node(String parentPath, DigitalTwin digitalTwin){ + this.setup(digitalTwin); + this.setPath(parentPath, digitalTwin.getIdentification()); + this.children = Map.of(); + } + + public Node(DigitalTwin digitalTwin){ + this.setup(digitalTwin); + this.setPath("", digitalTwin.getIdentification()); + this.children = Map.of(); + } + + public Node(String id, String globalAssetId, String idShort, String path, DigitalTwin digitalTwin, JobResponse job, Map children) { + this.id = id; + this.globalAssetId = globalAssetId; + this.idShort = idShort; + this.path = path; + this.digitalTwin = digitalTwin; + this.job = job; + this.children = children; + } + + public Node(String id, String globalAssetId, String idShort, String path, DigitalTwin digitalTwin, Map children) { + this.id = id; + this.globalAssetId = globalAssetId; + this.idShort = idShort; + this.path = path; + this.digitalTwin = digitalTwin; + this.children = children; + } + + public Node() { + } + + public Node(String id, String globalAssetId, String idShort, String searchId, String path, DigitalTwin digitalTwin, JobResponse job, Map children) { + this.id = id; + this.globalAssetId = globalAssetId; + this.idShort = idShort; + this.searchId = searchId; + this.path = path; + this.digitalTwin = digitalTwin; + this.job = job; + this.children = children; + } + + /** GETTERS AND SETTERS **/ + + public void setup(DigitalTwin digitalTwin){ + this.id = digitalTwin.getIdentification(); + this.globalAssetId = digitalTwin.getGlobalAssetId(); + this.idShort = digitalTwin.getIdShort(); + this.setSearchId(digitalTwin); + this.digitalTwin = digitalTwin; + + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getGlobalAssetId() { + return globalAssetId; + } + + public void setGlobalAssetId(String globalAssetId) { + this.globalAssetId = globalAssetId; + } + + public String getIdShort() { + return idShort; + } + + public void setIdShort(String idShort) { + this.idShort = idShort; + } + + public String getPath() { + return path; + } + public void setPath(Node parentNode, String id) { + this.path = parentNode.getPath()+"/"+id; + } + public void setPath(String parentPath, String id) { + this.path = parentPath+"/"+id; + } + + public DigitalTwin getDigitalTwin() { + return digitalTwin; + } + + public void setDigitalTwin(DigitalTwin digitalTwin) { + this.digitalTwin = digitalTwin; + } + + public Map getChildren() { + return children; + } + + public void setChildren(Map children) { + this.children = children; + } + public void setChild(Node childNode, String globalAssetId){ + this.children.put(globalAssetId, childNode); + } + + public Node getChild(String childId){ + return this.children.get(childId); + } + + + public void setPath(String path) { + this.path = path; + } + + public JobResponse getJob() { + return job; + } + + public void setJob(JobResponse job) { + this.job = job; + } + + public String getSearchId() { + return searchId; + } + + public void setSearchId(String searchId) { + this.searchId = searchId; + } + + public void setSearchId(DigitalTwin digitalTwin) { + this.searchId = CatenaXUtil.buildDppSearchId(digitalTwin, null); + } +} diff --git a/consumer-backend/productpass/src/main/java/org/eclipse/tractusx/productpass/models/manager/NodeComponent.java b/consumer-backend/productpass/src/main/java/org/eclipse/tractusx/productpass/models/manager/NodeComponent.java new file mode 100644 index 000000000..03eb47923 --- /dev/null +++ b/consumer-backend/productpass/src/main/java/org/eclipse/tractusx/productpass/models/manager/NodeComponent.java @@ -0,0 +1,110 @@ +/********************************************************************************* + * + * Catena-X - Product Passport Consumer Backend + * + * Copyright (c) 2022, 2023 BASF SE, BMW AG, Henkel AG & Co. KGaA + * Copyright (c) 2022, 2023 Contributors to the CatenaX (ng) GitHub Organisation. + * + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the + * License for the specific language govern in permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +package org.eclipse.tractusx.productpass.models.manager; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.List; +/** + * This class consists exclusively to define attributes related to the Node Component. + **/ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +public class NodeComponent { + + /** ATTRIBUTES **/ + @JsonProperty("id") + public String id; + @JsonProperty("name") + public String name; + @JsonProperty("searchId") + public String searchId; + @JsonProperty("path") + public String path; + @JsonProperty("children") + public List children; + + /** CONSTRUCTOR(S) **/ + public NodeComponent() { + } + public NodeComponent(Node node, List children) { + this.id = node.globalAssetId; + this.path = node.path; + this.searchId = node.searchId; + this.name = node.idShort; + this.children = children; + } + public NodeComponent(Node node) { + this.id = node.globalAssetId; + this.path = node.path; + this.searchId = node.searchId; + this.name = node.idShort; + this.children = List.of(); + } + + public NodeComponent(String id, String idShort, String path, List children) { + this.id = id; + this.name = idShort; + this.path = path; + this.children = children; + } + + /** GETTERS AND SETTERS **/ + public String getId() { + return id; + } + public void setId(String id) { + this.id = id; + } + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + public String getPath() { + return path; + } + public void setPath(String path) { + this.path = path; + } + public List getChildren() { + return children; + } + public void setChildren(List children) { + this.children = children; + } + + public String getSearchId() { + return searchId; + } + + public void setSearchId(String searchId) { + this.searchId = searchId; + } +} diff --git a/consumer-backend/productpass/src/main/java/org/eclipse/tractusx/productpass/models/manager/Status.java b/consumer-backend/productpass/src/main/java/org/eclipse/tractusx/productpass/models/manager/Status.java index 11c8183d2..df2c1ab57 100644 --- a/consumer-backend/productpass/src/main/java/org/eclipse/tractusx/productpass/models/manager/Status.java +++ b/consumer-backend/productpass/src/main/java/org/eclipse/tractusx/productpass/models/manager/Status.java @@ -27,7 +27,7 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; -import org.springframework.boot.context.properties.bind.DefaultValue; +import org.eclipse.tractusx.productpass.models.irs.JobHistory; import utils.DateTimeUtil; import java.util.HashMap; @@ -53,6 +53,8 @@ public class Status { @JsonProperty("modified") public Long modified; + @JsonProperty("job") + public JobHistory job; @JsonProperty("endpoint") public String endpoint; @@ -63,6 +65,12 @@ public class Status { @JsonProperty("bpn") public String bpn; + @JsonProperty("children") + public Boolean children; + + @JsonProperty("treeState") + public String treeState; + @JsonProperty("history") public Map history; @@ -178,33 +186,28 @@ public Status(String id, String status, Long created, Long modified, String endp this.bpn = bpn; this.history = new HashMap(); } - @SuppressWarnings("Unused") - public Status(String id, String status, Long created, Long modified, String endpoint, String bpn, String historyId, History history) { - this.id = id; - this.status = status; - this.created = created; - this.modified = modified; - this.endpoint = endpoint; - this.bpn = bpn; - this.history = Map.of(historyId, history); - } + + public Status() { } - public Status(String id, String status, Long created, Long modified, String endpoint, String dataPlaneUrl, String bpn, Map history, String semanticId) { + public Status(String id, String status, Long created, Long modified, JobHistory job, String endpoint, String dataPlaneUrl, String bpn, Boolean children, String treeState, Map history, String semanticId) { this.id = id; this.status = status; this.created = created; this.modified = modified; + this.job = job; this.endpoint = endpoint; this.dataPlaneUrl = dataPlaneUrl; this.bpn = bpn; + this.children = children; + this.treeState = treeState; this.history = history; this.semanticId = semanticId; } - /** GETTERS AND SETTERS **/ + public String getId() { return id; } @@ -290,6 +293,23 @@ public void setBpn(String bpn) { this.bpn = bpn; } + + public String getTreeState() { + return treeState; + } + + public void setTreeState(String treeState) { + this.treeState = treeState; + } + + public Boolean getChildren() { + return children; + } + + public void setChildren(Boolean children) { + this.children = children; + } + public String getSemanticId() { return semanticId; } @@ -297,6 +317,14 @@ public String getSemanticId() { public void setSemanticId(String semanticId) { this.semanticId = semanticId; } + + public JobHistory getJob() { + return job; + } + + public void setJob(JobHistory job) { + this.job = job; + } } diff --git a/consumer-backend/productpass/src/main/java/org/eclipse/tractusx/productpass/services/AasService.java b/consumer-backend/productpass/src/main/java/org/eclipse/tractusx/productpass/services/AasService.java index 7eecbfd71..a0baade3e 100644 --- a/consumer-backend/productpass/src/main/java/org/eclipse/tractusx/productpass/services/AasService.java +++ b/consumer-backend/productpass/src/main/java/org/eclipse/tractusx/productpass/services/AasService.java @@ -622,7 +622,7 @@ public Integer getDtIndex() { @Override public void run() { this.setDigitalTwin(searchDigitalTwin(this.getIdType(), this.getAssetId(), this.getDtIndex(), this.getEdr().getEndpoint(), this.getEdr())); - if(this.semanticId == null || semanticId.isEmpty()){ + if(this.semanticId == null || this.semanticId.isEmpty()){ this.setSubModel(searchSubModelBySemanticId(this.getDigitalTwin())); }else { this.setSubModel(searchSubModelBySemanticId(this.getDigitalTwin(), semanticId)); diff --git a/consumer-backend/productpass/src/main/java/org/eclipse/tractusx/productpass/services/DataTransferService.java b/consumer-backend/productpass/src/main/java/org/eclipse/tractusx/productpass/services/DataTransferService.java index bb782a0f3..50f9c3299 100644 --- a/consumer-backend/productpass/src/main/java/org/eclipse/tractusx/productpass/services/DataTransferService.java +++ b/consumer-backend/productpass/src/main/java/org/eclipse/tractusx/productpass/services/DataTransferService.java @@ -145,7 +145,7 @@ public List getEmptyVariables() { * @throws ControllerException * if unable to check the EDC consumer connection. */ - public String checkEdcConsumerConnection() throws ControllerException { + public String checkEdcConsumerConnection() throws ServiceException { try { String edcConsumerDsp = this.edcEndpoint + CatenaXUtil.edcDataEndpoint; Catalog catalog = this.getContractOfferCatalog(edcConsumerDsp, ""); // Get empty catalog @@ -154,7 +154,7 @@ public String checkEdcConsumerConnection() throws ControllerException { } return catalog.getParticipantId(); } catch (Exception e) { - throw new ControllerException(this.getClass().getName()+".checkEdcConsumerConnection", e, "It was not possible to establish connection with the EDC consumer endpoint [" + this.edcEndpoint+"]"); + throw new ServiceException(this.getClass().getName()+".checkEdcConsumerConnection", e, "It was not possible to establish connection with the EDC consumer endpoint [" + this.edcEndpoint+"]"); } } @@ -171,7 +171,7 @@ public String checkEdcConsumerConnection() throws ControllerException { * @throws ControllerException * if unable to get the contract offer for the assetId. */ - public Dataset getContractOfferByAssetId(String assetId, String providerUrl) throws ControllerException { + public Dataset getContractOfferByAssetId(String assetId, String providerUrl) throws ServiceException { /* * This method receives the assetId and looks up for targets with the same name. */ @@ -205,7 +205,7 @@ public Dataset getContractOfferByAssetId(String assetId, String providerUrl) thr Integer index = contractOffersMap.get(assetId); return contractOffers.get(index); } catch (Exception e) { - throw new ControllerException(this.getClass().getName(), e, "It was not possible to get Contract Offer for assetId [" + assetId + "]"); + throw new ServiceException(this.getClass().getName(), e, "It was not possible to get Contract Offer for assetId [" + assetId + "]"); } } diff --git a/consumer-backend/productpass/src/main/java/org/eclipse/tractusx/productpass/services/IrsService.java b/consumer-backend/productpass/src/main/java/org/eclipse/tractusx/productpass/services/IrsService.java new file mode 100644 index 000000000..cb8422579 --- /dev/null +++ b/consumer-backend/productpass/src/main/java/org/eclipse/tractusx/productpass/services/IrsService.java @@ -0,0 +1,266 @@ +/********************************************************************************* + * + * Catena-X - Product Passport Consumer Backend + * + * Copyright (c) 2022, 2023 BASF SE, BMW AG, Henkel AG & Co. KGaA + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the + * License for the specific language govern in permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +package org.eclipse.tractusx.productpass.services; + +import com.fasterxml.jackson.databind.JsonNode; +import org.eclipse.tractusx.productpass.config.IrsConfig; +import org.eclipse.tractusx.productpass.exceptions.ServiceException; +import org.eclipse.tractusx.productpass.exceptions.ServiceInitializationException; +import org.eclipse.tractusx.productpass.managers.ProcessManager; +import org.eclipse.tractusx.productpass.managers.TreeManager; +import org.eclipse.tractusx.productpass.models.irs.JobHistory; +import org.eclipse.tractusx.productpass.models.irs.JobRequest; +import org.eclipse.tractusx.productpass.models.irs.JobResponse; +import org.eclipse.tractusx.productpass.models.service.BaseService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.env.Environment; +import org.springframework.http.HttpHeaders; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import utils.DateTimeUtil; +import utils.HttpUtil; +import utils.JsonUtil; +import utils.LogUtil; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * This class is a service responsible for handling the communication with a external IRS component. + * + *

It contains all the methods and properties to communicate with the IRS Service. + * Its is called by the IRS Controller and can be used by managers to update information. + */ +@Service +public class IrsService extends BaseService { + + /** ATTRIBUTES **/ + HttpUtil httpUtil; + + JsonUtil jsonUtil; + + String irsEndpoint; + String irsJobPath; + String callbackUrl; + AuthenticationService authService; + IrsConfig irsConfig; + ProcessManager processManager; + TreeManager treeManager; + VaultService vaultService; + + /** CONSTRUCTOR(S) **/ + @Autowired + public IrsService(Environment env, ProcessManager processManager, IrsConfig irsConfig, TreeManager treeManager, HttpUtil httpUtil, VaultService vaultService, JsonUtil jsonUtil, AuthenticationService authService) throws ServiceInitializationException { + this.httpUtil = httpUtil; + this.processManager = processManager; + this.jsonUtil = jsonUtil; + this.authService = authService; + this.irsConfig = irsConfig; + this.treeManager = treeManager; + this.vaultService = vaultService; + this.init(env); + } + + public IrsService() { + } + + /** METHODS **/ + + /** + * Initiates the main needed variables for Data Transfer Service by loading from the environment variables and Vault. + **/ + public void init(Environment env) { + this.irsEndpoint = this.irsConfig.getEndpoint(); + this.irsJobPath = this.irsConfig.getPaths().getJob(); + this.callbackUrl = this.irsConfig.getCallbackUrl(); + } + /** + * Creates a List of missing variables needed to proceed with the request. + *

+ * + * @return an {@code Arraylist} with the environment variables missing in the configuration for the request. + * + */ + @Override + public List getEmptyVariables() { + List missingVariables = new ArrayList<>(); + if (this.irsEndpoint.isEmpty()) { + missingVariables.add("irs.endpoint"); + } + if (this.irsJobPath.isEmpty()) { + missingVariables.add("irs.paths.job"); + } + if (this.callbackUrl.isEmpty()) { + missingVariables.add("irs.callbackUrl"); + } + return missingVariables; + } + /** + * Starts a Job in the IRS for a specific globalAssetId with the backend BPN + *

+ * @param processId + * the {@code String} process id of the job + * @param globalAssetId + * the {@code String} global asset id from the digital twin + * @param searchId + * the {@code String} search id provided by the backend to identify the job + * + * @return a {@code Map} map object with the irs first response + * + * @throws ServiceException + * if unable to start the IRS job + */ + public Map startJob(String processId, String globalAssetId, String searchId) throws ServiceException { + try { + // In case the BPN is not known use the backend BPN. + return this.startJob(processId, globalAssetId, searchId, (String) this.vaultService.getLocalSecret("edc.bpn")); + } catch (Exception e) { + throw new ServiceException(this.getClass().getName() + "." + "startJob", + e, + "It was not possible to start a IRS job! Because of invalid BPN configuration!"); + } + } + /** + * Starts a Job in the IRS for a specific globalAssetId with the param BPN + *

+ * @param processId + * the {@code String} process id of the job + * @param globalAssetId + * the {@code String} global asset id from the digital twin + * @param searchId + * the {@code String} search id provided by the backend to identify the job + * @param bpn + * the {@code String} bpn number from the provider to search + * + * @return a {@code Map} map object with the irs first response + * + * @throws ServiceException + * if unable to start the IRS job + */ + public Map startJob(String processId, String globalAssetId, String searchId, String bpn) { + try { + this.checkEmptyVariables(); + String url = this.irsEndpoint + "/" + this.irsJobPath; + // Build the Job request for the IRS + + String backendUrl = this.callbackUrl + "/" + processId + "/" + searchId; // Add process id and search id + Map params = Map.of( + "id", globalAssetId, + "state", "COMPLETED" + ); + String callbackUrl = httpUtil.buildUrl(backendUrl, params, false); + JobRequest body = new JobRequest( + new ArrayList<>(), + "asBuilt", + false, + false, + "downward", + 1, + false, + callbackUrl + ); + body.setKey(globalAssetId, bpn); + HttpHeaders headers = httpUtil.getHeadersWithToken(this.authService.getToken().getAccessToken()); + headers.add("Content-Type", "application/json"); + + ResponseEntity response = httpUtil.doPost(url, JsonNode.class, headers, httpUtil.getParams(), body, false, false); + JsonNode result = (JsonNode) response.getBody(); + return (Map) jsonUtil.bindJsonNode(result, Map.class); + } catch (Exception e) { + throw new ServiceException(this.getClass().getName() + "." + "startJob", + e, + "It was not possible to start a IRS job!"); + } + } + /** + * Gets the children from a specific node in the tree + *

+ * @param processId + * the {@code String} process id of the job + * @param path + * the {@code String} path of the current node in the tree + * @param globalAssetId + * the {@code String} global asset id from the digital twin + * @param bpn + * the {@code String} bpn number from the provider to search + * + * @return a {@code String} of the Job Id created to get the children asynchronously + * + * @throws ServiceException + * if unable go request the children + */ + public String getChildren(String processId, String path, String globalAssetId, String bpn) { + try { + String searchId = TreeManager.generateSearchId(processId, globalAssetId); + Long created = DateTimeUtil.getTimestamp(); + Map irsResponse = this.startJob(processId, globalAssetId, searchId, bpn); + String jobId = irsResponse.get("id"); + LogUtil.printMessage("[PROCESS "+ processId + "] Job with id [" + jobId + "] created in the IRS for the globalAssetId [" + globalAssetId+"]"); + this.processManager.setJobHistory( + processId, + new JobHistory( + jobId, + searchId, + globalAssetId, + path, + created, + created, + 0 + ) + ); + return jobId; + } catch (Exception e) { + throw new ServiceException(this.getClass().getName() + "." + "getChildren", e, "It was not possible to get the children for the digital twin"); + } + } + /** + * Retrieves a Job from the IRS by id + *

+ * @param jobId + * the {@code String} id from the job to retrieve + * + * @return a {@code JobResponse} object which contains the job information + * + * @throws ServiceException + * if unable to retrieve the job + */ + public JobResponse getJob(String jobId) { + try { + String url = this.irsEndpoint + "/" + this.irsJobPath + "/" + jobId; + Map params = httpUtil.getParams(); + HttpHeaders headers = httpUtil.getHeadersWithToken(this.authService.getToken().getAccessToken()); + ResponseEntity response = httpUtil.doGet(url, String.class, headers, params, true, false); + String responseBody = (String) response.getBody(); + return (JobResponse) jsonUtil.bindJsonNode(jsonUtil.toJsonNode(responseBody), JobResponse.class); + } catch (Exception e) { + throw new ServiceException(this.getClass().getName() + "." + "getJob", + e, + "It was not possible to get the IRS job!"); + } + + } + + +} diff --git a/consumer-backend/productpass/src/main/java/utils/CatenaXUtil.java b/consumer-backend/productpass/src/main/java/utils/CatenaXUtil.java index d7ded75c3..14c5b70a6 100644 --- a/consumer-backend/productpass/src/main/java/utils/CatenaXUtil.java +++ b/consumer-backend/productpass/src/main/java/utils/CatenaXUtil.java @@ -23,10 +23,12 @@ package utils; +import org.eclipse.tractusx.productpass.models.dtregistry.DigitalTwin; import org.springframework.core.env.Environment; import utils.exceptions.UtilException; import java.nio.file.Paths; +import java.util.Map; import java.util.concurrent.ExecutionException; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -42,6 +44,8 @@ public final class CatenaXUtil { public final static String bpnNumberPattern = "BPN[LSA][A-Z0-9]{12}"; public final static String edcDataEndpoint = "/api/v1/dsp"; + public final static String catenaXPrefix = "CX"; + /** * Checks if the given String contains a BPN number pattern in it. *

@@ -83,9 +87,9 @@ public static String buildDataEndpoint(String endpoint) { } /** - * Gets the BPN number from a given String, if exists. + * Gets an aspect name from the semantic id *

- * @param str + * @param semanticId * the {@code String} object to get BPN number from. * * @return the {@code String} BPN number found in the given String or null if it doesn't exist. @@ -99,6 +103,39 @@ public static String getAspectNameFromSemanticId(String semanticId){ } } + /** + * Build the search id from a digital twin. + *

+ * @param digitalTwin + * the {@code DigitalTwin} digital twin to build the dpp search id + * + * @return the {@code String} BPN number found in the given String or null if it doesn't exist. + * + */ + public static String buildDppSearchId(DigitalTwin digitalTwin, String defaultValue){ + try { + Map mappedSpecificAssetIds = digitalTwin.mapSpecificAssetIds(); + // all the mapped values are in lowercase to avoid case sensitivity + String partInstanceId = mappedSpecificAssetIds.get("partinstanceid"); + String manufacturerPartId = mappedSpecificAssetIds.get("manufacturerpartid"); + if(partInstanceId == null || manufacturerPartId == null){ + return defaultValue; // Return default value + } + return String.join(":", catenaXPrefix, manufacturerPartId, partInstanceId); + }catch(Exception e){ + throw new UtilException(CatenaXUtil.class, e, "[ERROR] It was not possible to build the dpp search id"); + } + } + + /** + * Gets the BPN number from a given String, if exists. + *

+ * @param str + * the {@code String} object to get BPN number from. + * + * @return the {@code String} BPN number found in the given String or null if it doesn't exist. + * + */ public static String getBPN(String str) { Pattern pattern = Pattern.compile(bpnNumberPattern); Matcher matcher = pattern.matcher(str); diff --git a/consumer-backend/productpass/src/main/java/utils/HttpUtil.java b/consumer-backend/productpass/src/main/java/utils/HttpUtil.java index f0668a8d5..6fefdf544 100644 --- a/consumer-backend/productpass/src/main/java/utils/HttpUtil.java +++ b/consumer-backend/productpass/src/main/java/utils/HttpUtil.java @@ -216,7 +216,6 @@ public String getAuthorizationToken(HttpServletRequest httpRequest){ } return token; } - /** * Builds the URL with the key/value pair within the given parameters map with or without enconding. *

@@ -229,7 +228,7 @@ public String getAuthorizationToken(HttpServletRequest httpRequest){ * * @return a {@code String} with the build URL. * - */ + @SuppressWarnings("Unused") public String buildUrl(String url, Map params, Boolean encode){ StringBuilder finalUrl = new StringBuilder(url); @@ -246,7 +245,7 @@ public String buildUrl(String url, Map params, Boolean encode){ } return finalUrl.toString(); } - + */ /** * Parses the Map of parameters to a String as URL parameters structure. *

@@ -602,6 +601,25 @@ public URI buildUri(String url, Map params, Boolean encoded){ } return builder.build(encoded).toUri(); } + /** + * Builds the URL with the key/value pair within the given parameters map with or without enconding. + *

+ * @param url + * the base URL. + * @param params + * the Map with key/value pair from each parameter. + * @param encode + * if true will encode the value of each parameter. + * + * @return a {@code String} with the build URL. + */ + public String buildUrl(String url, Map params, Boolean encoded){ + UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(url); + for(Map.Entry entry : params.entrySet()){ + builder.queryParam(entry.getKey(), entry.getValue()); + } + return builder.build(encoded).toUriString(); + } /************************************************** * Generic Request Methods ************************ diff --git a/consumer-backend/productpass/src/main/java/utils/JsonUtil.java b/consumer-backend/productpass/src/main/java/utils/JsonUtil.java index 5db7db719..1689d407f 100644 --- a/consumer-backend/productpass/src/main/java/utils/JsonUtil.java +++ b/consumer-backend/productpass/src/main/java/utils/JsonUtil.java @@ -37,6 +37,7 @@ import utils.exceptions.UtilException; import java.util.*; +import java.util.stream.Collectors; /** @@ -602,6 +603,35 @@ public JsonNode toJsonNode(Map json){ } } + /** + * Parses the JSON object to a Map type object. + *

+ * @param path + * the {@code String} that contains the old path + * @param pathSep + * the {@code String} separator of the path + * @param newPathSep + * the {@code String} separator of the new path to be translated + * + * @return a {@code JsonObject} object parsed with the json data. + * + * @throws UtilException + * if unable to parse the json object. + */ + public String translatePathSep(String path, String pathSep, String newPathSep){ + try{ + String newPath = StringUtil.deepCopy(path); // Deep copy string + if(newPath.startsWith(pathSep)){ // If the path starts with the pathSep remove it so the search can be efficient + newPath = newPath.substring(1); + } + String[] parts = newPath.split(String.format("\\%s",pathSep)); // Split the path in order to get the parts + return String.join(String.format("\\%s",newPathSep), parts); // Join the path with the children + } catch (Exception e) { + throw new UtilException(JsonUtil.class, e, "It was not possible to translate te pathSep"); + } + } + + /** * Parses the JSON object to a Map type object. *

@@ -710,4 +740,14 @@ public Object bindReferenceType (Object json, TypeReference reference) { throw new UtilException(JsonUtil.class, "It was not possible to get reference type -> [" + e.getMessage() + "]"); } } + + + public List mapToList(Map map){ + try{ + return Arrays.asList(map.values().toArray()); + } catch (Exception e) { + throw new UtilException(JsonUtil.class, "It was possible to the map to a list"); + } + } + } diff --git a/consumer-backend/productpass/src/main/java/utils/StringUtil.java b/consumer-backend/productpass/src/main/java/utils/StringUtil.java index ec3951533..d94a00030 100644 --- a/consumer-backend/productpass/src/main/java/utils/StringUtil.java +++ b/consumer-backend/productpass/src/main/java/utils/StringUtil.java @@ -31,5 +31,7 @@ private StringUtil() { public static Boolean isEmpty(String s) { return s == null || s.length() == 0; } + + public static String deepCopy(String s){ return String.valueOf(s);} } diff --git a/consumer-backend/productpass/src/main/resources/application.yml b/consumer-backend/productpass/src/main/resources/application.yml index 867f1e02c..7c9e6f79d 100644 --- a/consumer-backend/productpass/src/main/resources/application.yml +++ b/consumer-backend/productpass/src/main/resources/application.yml @@ -56,10 +56,20 @@ configuration: security: check: - enabled: true + enabled: false bpn: true edc: true + irs: + enabled: true + endpoint: "https://materialpass-irs.int.demo.catena-x.net" + paths: + job: "/irs/jobs" + tree: + fileName: "treeDataModel" + indent: true + callbackUrl: "https://materialpass.int.demo.catena-x.net/api/irs" + dtr: assetType: 'data.core.digitalTwinRegistry' central: false diff --git a/deployment/helm/registry/values.yaml b/deployment/helm/registry/values.yaml index a112b28a6..e85ff2e89 100644 --- a/deployment/helm/registry/values.yaml +++ b/deployment/helm/registry/values.yaml @@ -64,4 +64,4 @@ provider-dtr: auth: username: password: - database: default-database + database: default-database \ No newline at end of file diff --git a/docs/RELEASE_USER.md b/docs/RELEASE_USER.md index 0e9139368..ecb0a949c 100644 --- a/docs/RELEASE_USER.md +++ b/docs/RELEASE_USER.md @@ -23,6 +23,17 @@ # Release Notes Digital Product Pass Application User friendly relase notes without especific technical details. +**November 03 2023 (Version 1.3.0)** +*03.11.2023* + +### Added +#### Added drill down functionality with `IRS` for the Digital Product Pass Aspect +Now the application is able to drill down into its components one level down. +The backend application is communicating with the IRS and managing the job. +Once the IRS completes its job the backend is able to parse it and inform the frontend that the job has completed. + + + **October 31 2023 (Version 1.2.1)** *31.10.2023*