From 8cd00ac07f2c33e8e6e2e6b55261b88f5108f1a1 Mon Sep 17 00:00:00 2001 From: Christos Tranoris Date: Thu, 15 May 2025 10:05:38 +0300 Subject: [PATCH 1/6] Initial commit --- .classpath | 38 ++ .project | 23 + .settings/org.eclipse.core.resources.prefs | 6 + .settings/org.eclipse.jdt.core.prefs | 16 + .settings/org.eclipse.m2e.core.prefs | 4 + LICENSE | 201 ++++++++ pom.xml | 313 ++++++++++++ .../mcp/server/ActiveMQComponentConfig.java | 41 ++ .../org/etsi/osl/mcp/server/JsonMassage.java | 100 ++++ .../mcp/server/OSLMCPServerApplication.java | 101 ++++ .../osl/mcp/server/ProductCatalogQClient.java | 379 ++++++++++++++ .../osl/mcp/server/ProductCatalogTools.java | 206 ++++++++ .../osl/mcp/server/ServiceCatalogQClient.java | 461 ++++++++++++++++++ .../osl/mcp/server/ServiceCatalogTools.java | 307 ++++++++++++ src/main/resources/application.yaml | 199 ++++++++ src/main/resources/banner.txt | 11 + 16 files changed, 2406 insertions(+) create mode 100644 .classpath create mode 100644 .project create mode 100644 .settings/org.eclipse.core.resources.prefs create mode 100644 .settings/org.eclipse.jdt.core.prefs create mode 100644 .settings/org.eclipse.m2e.core.prefs create mode 100644 LICENSE create mode 100644 pom.xml create mode 100644 src/main/java/org/etsi/osl/mcp/server/ActiveMQComponentConfig.java create mode 100644 src/main/java/org/etsi/osl/mcp/server/JsonMassage.java create mode 100644 src/main/java/org/etsi/osl/mcp/server/OSLMCPServerApplication.java create mode 100644 src/main/java/org/etsi/osl/mcp/server/ProductCatalogQClient.java create mode 100644 src/main/java/org/etsi/osl/mcp/server/ProductCatalogTools.java create mode 100644 src/main/java/org/etsi/osl/mcp/server/ServiceCatalogQClient.java create mode 100644 src/main/java/org/etsi/osl/mcp/server/ServiceCatalogTools.java create mode 100644 src/main/resources/application.yaml create mode 100644 src/main/resources/banner.txt diff --git a/.classpath b/.classpath new file mode 100644 index 0000000..e7c4f49 --- /dev/null +++ b/.classpath @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.project b/.project new file mode 100644 index 0000000..ec91655 --- /dev/null +++ b/.project @@ -0,0 +1,23 @@ + + + org.etsi.osl.mcp.server + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + + diff --git a/.settings/org.eclipse.core.resources.prefs b/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 0000000..29abf99 --- /dev/null +++ b/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,6 @@ +eclipse.preferences.version=1 +encoding//src/main/java=UTF-8 +encoding//src/main/resources=UTF-8 +encoding//src/test/java=UTF-8 +encoding//src/test/resources=UTF-8 +encoding/=UTF-8 diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..3328195 --- /dev/null +++ b/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,16 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.methodParameters=generate +org.eclipse.jdt.core.compiler.codegen.targetPlatform=17 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=17 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning +org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning +org.eclipse.jdt.core.compiler.release=enabled +org.eclipse.jdt.core.compiler.source=17 diff --git a/.settings/org.eclipse.m2e.core.prefs b/.settings/org.eclipse.m2e.core.prefs new file mode 100644 index 0000000..f897a7f --- /dev/null +++ b/.settings/org.eclipse.m2e.core.prefs @@ -0,0 +1,4 @@ +activeProfiles= +eclipse.preferences.version=1 +resolveWorkspaceProjects=true +version=1 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://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 governing permissions and + limitations under the License. diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..1dc2802 --- /dev/null +++ b/pom.xml @@ -0,0 +1,313 @@ + + 4.0.0 + org.etsi.osl + org.etsi.osl.mcp.server + 0.0.1-SNAPSHOT + org.etsi.osl.mcp.server + + org.springframework.boot + spring-boot-starter-parent + 3.4.5 + + + + + + + + UTF-8 + UTF-8 + 3.4.5 + 1.0.0-M7 + 1.18.28 + 2.1.0 + 1.5.3.Final + 17 + 4.11.0 + 2.8.11 + 2.0.0 + apache_v2 + 1.7.0 + 1.7.0 + 1.1.0-SNAPSHOT + + + + + + + + + + org.springframework.ai + spring-ai-bom + ${spring-ai.version} + pom + import + + + + org.apache.camel.springboot + camel-spring-boot-dependencies + ${camel.version} + pom + import + + + + com.google.guava + guava + 32.0.0-jre + + + + + + + + + + + + com.jayway.jsonpath + json-path + + + + + org.springframework.ai + spring-ai-starter-mcp-server-webflux + + + + + + + + + + + org.projectlombok + lombok + provided + ${lombok-version} + + + org.openapitools + jackson-databind-nullable + 0.2.6 + + + + + + org.etsi.osl + org.etsi.osl.model.tmf + ${org.etsi.osl.model.tmf.version} + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + + + + + + + + + + + org.mapstruct + mapstruct + ${mapstruct.version} + + + org.mapstruct + mapstruct-processor + ${mapstruct.version} + + + + javax.annotation + javax.annotation-api + 1.3.2 + compile + + + jakarta.validation + jakarta.validation-api + 3.0.2 + + + org.jetbrains + annotations + 13.0 + compile + + + + + org.springframework.boot + spring-boot-starter-activemq + + + org.apache.activemq + activemq-amqp + test + + + org.apache.qpid + proton-j + + + + + org.messaginghub + pooled-jms + + + + + org.apache.camel.springboot + camel-spring-boot-starter + + + org.apache.activemq + activemq-pool + + + org.apache.camel + camel-activemq + + + org.apache.activemq + activemq-broker + + + + + org.apache.camel.springboot + camel-service-starter + + + + org.apache.camel.springboot + camel-http-starter + + + org.apache.camel + camel-jackson + + + org.apache.camel + camel-stream + + + + dk.brics.automaton + automaton + 1.11-8 + + + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + + ${java.version} + ${java.version} + + -parameters + + + + org.mapstruct + mapstruct-processor + ${mapstruct.version} + + + org.projectlombok + lombok + ${lombok-version} + + + + org.projectlombok + lombok-mapstruct-binding + 0.2.0 + + + + + + + + org.codehaus.mojo + license-maven-plugin + ${maven-license-plugin.version} + + false + ========================LICENSE_START================================= + =========================LICENSE_END================================== + *.json + + + + generate-license-headers + + update-file-header + + process-sources + + ${license.licenseName} + + + + + download-licenses + + download-licenses + + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring.boot-version} + + + + repackage + + + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring.boot-version} + + exec + + + + + + \ No newline at end of file diff --git a/src/main/java/org/etsi/osl/mcp/server/ActiveMQComponentConfig.java b/src/main/java/org/etsi/osl/mcp/server/ActiveMQComponentConfig.java new file mode 100644 index 0000000..e3bef57 --- /dev/null +++ b/src/main/java/org/etsi/osl/mcp/server/ActiveMQComponentConfig.java @@ -0,0 +1,41 @@ +/*- + * ========================LICENSE_START================================= + * org.etsi.osl.bugzilla + * %% + * Copyright (C) 2019 openslice.io + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://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 governing permissions and + * limitations under the License. + * =========================LICENSE_END================================== + */ +package org.etsi.osl.mcp.server; + +import org.apache.camel.component.activemq.ActiveMQComponent; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import jakarta.jms.ConnectionFactory; + +/** + * @author ctranoris + * + */ +@Configuration +public class ActiveMQComponentConfig { + + @Bean(name = "activemq") + public ActiveMQComponent createComponent(ConnectionFactory factory) { + ActiveMQComponent activeMQComponent = new ActiveMQComponent(); + activeMQComponent.setConnectionFactory(factory); + return activeMQComponent; + } +} diff --git a/src/main/java/org/etsi/osl/mcp/server/JsonMassage.java b/src/main/java/org/etsi/osl/mcp/server/JsonMassage.java new file mode 100644 index 0000000..d83e6f9 --- /dev/null +++ b/src/main/java/org/etsi/osl/mcp/server/JsonMassage.java @@ -0,0 +1,100 @@ +package org.etsi.osl.mcp.server; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +/** + * @author ctranoris + */ +public class JsonMassage { + + /** + * Filters a JSON object to include only keys that match any token in the provided array. + * + * @param json JSON string to filter + * @param tokens Array of tokens to match against keys + * @return A new JSON string containing only matched keys + * @throws Exception If there's an error processing the JSON + */ + public static String filterJsonByTokens(String json, String[] tokens) throws Exception { + ObjectMapper mapper = new ObjectMapper(); + JsonNode rootNode = mapper.readTree(json); + + Set tokenSet = new HashSet<>(Arrays.asList(tokens)); + JsonNode filteredNode = filterNode(rootNode, tokenSet); + + return mapper.writeValueAsString(filteredNode); + } + + public static JsonNode filterJsonByTokens(Object object, String[] tokens) throws Exception { + ObjectMapper mapper = new ObjectMapper(); + // Convert the Java object to JsonNode + JsonNode rootNode = mapper.valueToTree(object); + + Set tokenSet = new HashSet<>(Arrays.asList(tokens)); + JsonNode filteredNode = filterNode(rootNode, tokenSet); + + //return mapper.writeValueAsString(filteredNode); + return filteredNode; + } + + public static JsonNode filterNode(JsonNode node, Set tokens) { + ObjectMapper mapper = new ObjectMapper(); + + if (node.isObject()) { + ObjectNode resultNode = mapper.createObjectNode(); + Iterator fieldNames = node.fieldNames(); + + while (fieldNames.hasNext()) { + String fieldName = fieldNames.next(); + JsonNode fieldValue = node.get(fieldName); + + // Check if the field name matches any token + if (tokens.contains(fieldName)) { + resultNode.set(fieldName, fieldValue.deepCopy()); + } + + // If it's an object or array, process it recursively and include if not empty + if (fieldValue.isObject() || fieldValue.isArray()) { + JsonNode filteredChild = filterNode(fieldValue, tokens); + + // Only add non-empty objects/arrays when they contain matches + if ((filteredChild.isObject() && filteredChild.size() > 0) || + (filteredChild.isArray() && filteredChild.size() > 0)) { + resultNode.set(fieldName, filteredChild); + } + } + } + + return resultNode; + } else if (node.isArray()) { + ArrayNode resultArray = mapper.createArrayNode(); + + for (JsonNode element : node) { + JsonNode filteredElement = filterNode(element, tokens); + + // Only add non-empty objects/arrays + if ((filteredElement.isObject() || filteredElement.isArray()) && + filteredElement.size() > 0) { + resultArray.add(filteredElement); + } else if (!filteredElement.isObject() && !filteredElement.isArray()) { + resultArray.add(filteredElement); + } + } + + return resultArray; + } else { + // For primitive values, return as is + return node; + } + } + + +} \ No newline at end of file diff --git a/src/main/java/org/etsi/osl/mcp/server/OSLMCPServerApplication.java b/src/main/java/org/etsi/osl/mcp/server/OSLMCPServerApplication.java new file mode 100644 index 0000000..20374bd --- /dev/null +++ b/src/main/java/org/etsi/osl/mcp/server/OSLMCPServerApplication.java @@ -0,0 +1,101 @@ +package org.etsi.osl.mcp.server; + +import java.util.List; +import java.util.Map; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.ai.chat.prompt.Prompt; +import org.springframework.ai.tool.ToolCallback; +import org.springframework.ai.tool.ToolCallbackProvider; +import org.springframework.ai.tool.function.FunctionToolCallback; +import org.springframework.ai.tool.method.MethodToolCallbackProvider; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import io.modelcontextprotocol.server.McpServerFeatures; +import io.modelcontextprotocol.spec.McpSchema; +import io.modelcontextprotocol.spec.McpSchema.GetPromptResult; +import io.modelcontextprotocol.spec.McpSchema.PromptArgument; +import io.modelcontextprotocol.spec.McpSchema.PromptMessage; +import io.modelcontextprotocol.spec.McpSchema.Role; +import io.modelcontextprotocol.spec.McpSchema.TextContent; + +/** + * @author ctranoris + * + */ +@SpringBootApplication +public class OSLMCPServerApplication { + + private static final Logger logger = LoggerFactory.getLogger(OSLMCPServerApplication.class); + + + public static void main(String[] args) { + SpringApplication.run(OSLMCPServerApplication.class, args); + } + + @Bean + public ToolCallbackProvider serviceTools( ServiceCatalogTools oslServices) { + return MethodToolCallbackProvider.builder().toolObjects( oslServices ).build(); + } + + + + @Bean + public ToolCallbackProvider productTools( ProductCatalogTools oslProducts) { + return MethodToolCallbackProvider.builder().toolObjects( oslProducts ).build(); + } + + + + public record TextInput(String input) { + } + + @Bean + public ToolCallback toUpperCase() { + return FunctionToolCallback.builder("toUpperCase", (TextInput input) -> input.input().toUpperCase()) + .inputType(TextInput.class) + .description("Put the text to upper case") + .build(); + } + + @Bean + public List myResources() { + logger.info("calling myResources()"); + var systemInfoResource = new McpSchema.Resource("custom://resource", "name", "description", "mime-type", null); + var resourceSpecification = new McpServerFeatures.SyncResourceSpecification(systemInfoResource, (exchange, request) -> { + try { + var systemInfo = Map.of( ); + String jsonContent = new ObjectMapper().writeValueAsString(systemInfo); + return new McpSchema.ReadResourceResult( + List.of(new McpSchema.TextResourceContents(request.uri(), "application/json", jsonContent))); + } + catch (Exception e) { + throw new RuntimeException("Failed to generate system info", e); + } + }); + + return List.of(resourceSpecification); + + } + + @Bean + public List myPrompts() { + var prompt = new McpSchema.Prompt("greeting", "A friendly greeting prompt", + List.of(new McpSchema.PromptArgument("name", "The name to greet", true))); + + var promptSpecification = new McpServerFeatures.SyncPromptSpecification(prompt, (exchange, getPromptRequest) -> { + String nameArgument = (String) getPromptRequest.arguments().get("name"); + if (nameArgument == null) { nameArgument = "friend"; } + var userMessage = new PromptMessage(Role.USER, new TextContent("Hello " + nameArgument + "! How can I assist you today?")); + return new GetPromptResult("A personalized greeting message", List.of(userMessage)); + }); + + return List.of(promptSpecification); + } + + + + +} diff --git a/src/main/java/org/etsi/osl/mcp/server/ProductCatalogQClient.java b/src/main/java/org/etsi/osl/mcp/server/ProductCatalogQClient.java new file mode 100644 index 0000000..56a16bc --- /dev/null +++ b/src/main/java/org/etsi/osl/mcp/server/ProductCatalogQClient.java @@ -0,0 +1,379 @@ +package org.etsi.osl.mcp.server; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.camel.ProducerTemplate; +import org.apache.camel.builder.RouteBuilder; +import org.etsi.osl.tmf.common.model.service.ServiceSpecificationRef; +import org.etsi.osl.tmf.pcm620.model.Catalog; +import org.etsi.osl.tmf.pcm620.model.Category; +import org.etsi.osl.tmf.pcm620.model.ProductOffering; +import org.etsi.osl.tmf.pcm620.model.ProductSpecification; +import org.etsi.osl.tmf.pcm620.model.ProductSpecificationRef; +import org.etsi.osl.tmf.pim637.model.Product; +import org.etsi.osl.tmf.pim637.model.ProductUpdate; +import org.etsi.osl.tmf.po622.model.ProductOrder; +import org.etsi.osl.tmf.po622.model.ProductOrderCreate; +import org.etsi.osl.tmf.ri639.model.LogicalResource; +import org.etsi.osl.tmf.ri639.model.Resource; +import org.etsi.osl.tmf.scm633.model.ServiceCatalog; +import org.etsi.osl.tmf.scm633.model.ServiceCategory; +import org.etsi.osl.tmf.scm633.model.ServiceSpecification; +import org.etsi.osl.tmf.sim638.model.ServiceUpdate; +import org.etsi.osl.tmf.so641.model.ServiceOrder; +import org.etsi.osl.tmf.so641.model.ServiceOrderCreate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import jakarta.validation.constraints.NotNull; + +/** + * @author ctranoris + * Class to exchange information with TMF API services + * + */ +@Service +public class ProductCatalogQClient extends RouteBuilder { + + + private static final Logger logger = LoggerFactory.getLogger(ProductCatalogQClient.class); + + @Autowired + private ProducerTemplate template; + + + + @Value("${CATALOG_ADD_PRODUCTORDER}") + private String CATALOG_ADD_PRODUCTORDER = ""; + + + @Value("${CATALOG_GET_PRODUCTSPEC_BY_ID}") + private String CATALOG_GET_PRODUCTSPEC_BY_ID = ""; + + + @Value("${CATALOG_GET_PRODUCTORDER_BY_ID}") + private String CATALOG_GET_PRODUCTORDER_BY_ID = ""; + + @Value("${CATALOG_GET_PRODUCTCATALOGS}") + private String CATALOG_GET_PRODUCTCATALOGS = ""; + + @Value("${CATALOG_GET_PRODUCTCATALOG_BY_NAME}") + private String CATALOG_GET_PRODUCTCATALOG_BY_NAME = ""; + + @Value("${CATALOG_GET_PRODUCTCATEGORIES}") + private String CATALOG_GET_PRODUCTCATEGORIES = ""; + + @Value("${CATALOG_GET_PRODUCTCATEGORY_BY_ID}") + private String CATALOG_GET_PRODUCTCATEGORY_BY_ID = ""; + + + @Value("${CATALOG_GET_PRODUCTOFFERING_BY_ID}") + private String CATALOG_GET_PRODUCTOFFERING_BY_ID = ""; + + @Value("${CATALOG_GET_PRODUCTOFFERINGS_BYCATEGORY_ID}") + private String CATALOG_GET_PRODUCTOFFERINGS_BYCATEGORY_ID = ""; + + @Value("${CATALOG_SEARCH_PRODUCTOFFERINGS}") + private String CATALOG_SEARCH_PRODUCTOFFERINGS = ""; + + + @Override + public void configure() throws Exception { + + + + } + + /** + * get product spec by id from model via bus + * @param id + * @return + * @throws IOException + */ + public ProductSpecification retrieveProductSpec(String specid) { + logger.info("will retrieve Product Specification from catalog orderid=" + specid ); + + try { + Object response = template. + requestBody( CATALOG_GET_PRODUCTSPEC_BY_ID, specid); + + if ( !(response instanceof String)) { + logger.error("Product Specification object is wrong."); + return null; + } + ProductSpecification sor = toJsonObj( (String)response, ProductSpecification.class); + //logger.debug("retrieveSpec response is: " + response); + return sor; + + }catch (Exception e) { + logger.error("Cannot retrieve Product Specification details from catalog. " + e.toString()); + } + return null; + } + + + public ProductOffering retrieveProductOffering(String productOfferingId) { + logger.info("will retrieve Product Specification from catalog orderid=" + productOfferingId ); + + try { + Object response = template. + requestBody( CATALOG_GET_PRODUCTOFFERING_BY_ID, productOfferingId); + + if ( !(response instanceof String)) { + logger.error("Product Specification object is wrong."); + return null; + } + ProductOffering sor = toJsonObj( (String)response, ProductOffering.class); + //logger.debug("retrieveSpec response is: " + response); + return sor; + + }catch (Exception e) { + logger.error("Cannot retrieve Product Specification details from catalog. " + e.toString()); + } + return null; + } + + + + public ProductOrder createProductOrder(ProductOrderCreate productOrderCreate) { + logger.info("will create Product Order "); + try { + + + try { + Object response = + template.requestBody(CATALOG_ADD_PRODUCTORDER, toJsonString(productOrderCreate)); + + if (!(response instanceof String)) { + logger.error("Product Order object is wrong."); + return null; + } + logger.debug("createProductOrder response is: " + response); + ProductOrder sor = toJsonObj((String) response, ProductOrder.class); + + return sor; + + } catch (Exception e) { + logger.error("Cannot createProductOrder details to catalog. " + e.toString()); + } + return null; + + + + } catch (Exception e) { + logger.error("Cannot createProductOrder to catalog. " + e.toString()); + } + return null; + + } + + /** + * get product order by id from model via bus + * @param id + * @return + * @throws IOException + */ + public ProductOrder retrieveProductOrder( String orderid) { + logger.info("will retrieve Product Order from catalog orderid=" + orderid ); + try { + Object response = template. + requestBody( CATALOG_GET_PRODUCTORDER_BY_ID, orderid); + + if ( !(response instanceof String)) { + logger.error("Product Order object is wrong."); + return null; + } + logger.debug("retrieveProductOrder response is: " + response); + ProductOrder sor = toJsonObj( (String)response, ProductOrder.class); + + return sor; + + }catch (Exception e) { + logger.error("Cannot retrieve Product Order details from catalog. " + e.toString()); + } + return null; + } + + + + + + + /** + * get product Catalogs via bus + * @param id + * @return + * @throws IOException + */ + public List retrieveProductCatalogs() { + logger.info("will retrieve Product Catalogs " ); + + try { + Object response = template. + requestBody( CATALOG_GET_PRODUCTCATALOGS, "" ); + + if ( !(response instanceof String)) { + logger.error("Product Catalogs object is wrong."); + return null; + } + List sor = toJsonObj( (String)response, ArrayList.class); + //logger.debug("retrieveSpec response is: " + response); + return sor; + + }catch (Exception e) { + logger.error("Cannot retrieve ProductCatalogs. " + e.toString()); + e.printStackTrace(); + } + return null; + } + + + /** + * get product Catalog via bus + * @param id + * @return + * @throws IOException + */ + public Catalog retrieveProductCatalog(String catalogName) { + logger.info("will retrieve Product Catalog details " + catalogName ); + + try { + + Map map = new HashMap<>(); + map.put("catalogName", catalogName ); + + Object response = template.requestBodyAndHeaders( CATALOG_GET_PRODUCTCATALOG_BY_NAME, "", map); + + + if ( !(response instanceof String)) { + logger.error("Product Catalog object is wrong."); + return null; + } + Catalog sor = toJsonObj( (String)response, Catalog.class); + + return sor; + + }catch (Exception e) { + logger.error("Cannot retrieve ProductCatalog. " + e.toString()); + e.printStackTrace(); + } + return null; + } + + + /** + * get product Categories via bus + * @param id + * @return + * @throws IOException + */ + public List retrieveProductCategoriesDetailsOfCatalog(String catalogName) { + logger.info("will retrieve Product Categories Details Of " + catalogName ); + + try { + + Map map = new HashMap<>(); + map.put("catalogName", catalogName ); + + Object response = template.requestBodyAndHeaders( CATALOG_GET_PRODUCTCATEGORIES, "", map); + + + if ( !(response instanceof String)) { + logger.error("Product Catalog object is wrong."); + return null; + } + List sor = toJsonObj( (String)response, ArrayList.class); + + return sor; + + }catch (Exception e) { + logger.error("Cannot retrieve Product Categories. " + e.toString()); + e.printStackTrace(); + } + return null; + } + + /** + * get product Categories via bus + * @param id + * @return + * @throws IOException + */ + public List retrieveProductOfferingsByCategoryId(String categoryId) { + logger.info("will retrieve Product Offering Details Of category " + categoryId ); + + try { + + Map map = new HashMap<>(); + map.put("categoryId", categoryId ); + + Object response = template.requestBodyAndHeaders( CATALOG_GET_PRODUCTOFFERINGS_BYCATEGORY_ID, "", map); + + + if ( !(response instanceof String)) { + logger.error("Product Specs object is wrong."); + return null; + } + List sor = toJsonObj( (String)response, ArrayList.class); + + return sor; + + }catch (Exception e) { + logger.error("Cannot retrieve Product Specs. " + e.toString()); + e.printStackTrace(); + } + return null; + } + + + public List searchProductOfferings(List searchText) { + logger.info("will search Product Offerings " + searchText ); + + try { + + //Map map = new HashMap<>(); + //map.put("searchText", searchText ); + String body = toJsonString(searchText); + Object response = template.requestBody( CATALOG_SEARCH_PRODUCTOFFERINGS, body ); + + + if ( !(response instanceof String)) { + logger.error("Product Specs object is wrong."); + return null; + } + List sor = toJsonObj( (String)response, ArrayList.class); + + return sor; + + }catch (Exception e) { + logger.error("Cannot retrieve Product Specs. " + e.toString()); + e.printStackTrace(); + } + return null; + } + + + static String toJsonString(Object object) throws IOException { + ObjectMapper mapper = new ObjectMapper(); + mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); + return mapper.writeValueAsString(object); + } + + static T toJsonObj(String content, Class valueType) throws IOException { + ObjectMapper mapper = new ObjectMapper(); + mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); + return mapper.readValue(content, valueType); + } + + + + +} diff --git a/src/main/java/org/etsi/osl/mcp/server/ProductCatalogTools.java b/src/main/java/org/etsi/osl/mcp/server/ProductCatalogTools.java new file mode 100644 index 0000000..9de060f --- /dev/null +++ b/src/main/java/org/etsi/osl/mcp/server/ProductCatalogTools.java @@ -0,0 +1,206 @@ +package org.etsi.osl.mcp.server; + +import java.io.IOException; +import java.time.OffsetDateTime; +import java.util.List; +import java.util.Map; +import org.etsi.osl.tmf.common.model.Any; +import org.etsi.osl.tmf.common.model.service.Characteristic; +import org.etsi.osl.tmf.common.model.service.Note; +import org.etsi.osl.tmf.pcm620.model.Catalog; +import org.etsi.osl.tmf.pcm620.model.Category; +import org.etsi.osl.tmf.pcm620.model.ProductOffering; +import org.etsi.osl.tmf.pcm620.model.ProductOfferingRef; +import org.etsi.osl.tmf.pcm620.model.ProductSpecification; +import org.etsi.osl.tmf.pcm620.model.ProductSpecificationRef; +import org.etsi.osl.tmf.pim637.model.Product; +import org.etsi.osl.tmf.pim637.model.ProductUpdate; +import org.etsi.osl.tmf.po622.model.OrderItemActionType; +import org.etsi.osl.tmf.po622.model.ProductOrder; +import org.etsi.osl.tmf.po622.model.ProductOrderCreate; +import org.etsi.osl.tmf.po622.model.ProductOrderItem; +import org.etsi.osl.tmf.po622.model.ProductOrderStateType; +import org.etsi.osl.tmf.prm669.model.RelatedParty; +import org.etsi.osl.tmf.ri639.model.Resource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.ai.tool.annotation.Tool; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * + * @author ctranoris + */ +@org.springframework.stereotype.Service +public class ProductCatalogTools { + + + private static final Logger logger = LoggerFactory.getLogger(ProductCatalogTools.class); + + + //private final RestClient restClient; + + @Autowired + ProductCatalogQClient aCatalogClient; + + + @Tool(description = "Get a list of all published OSL OpenSlice product catalogs." + + "Each catalog contains product categories, that we can search individually to get the details and contents of each category.") + public List getOSLProductCatalogs() { + + logger.info("getOSLProductCatalogs"); + List serviceCatalogs = aCatalogClient.retrieveProductCatalogs(); + + + + return serviceCatalogs; + } + + @Tool(description = "Get OSL product categories in product catalog providing a catalog name") + public List getOSLProductCategories(String catalogName) { + + logger.info("getOSLProductCategories {}", catalogName); + + List productCategories = aCatalogClient.retrieveProductCategoriesDetailsOfCatalog(catalogName); + + return productCategories; + } + + + @Tool(description = "Get a list of OSL product offerings in a product category, given a category ID") + public List getOSLProductOfferingsInCategory(String categoryId) { + + logger.info("getOSLProductOfferingsInCategory {}", categoryId); + List productCategories = aCatalogClient.retrieveProductOfferingsByCategoryId(categoryId); + return productCategories; + + + } + + @Tool(description = "Get all the details of an OSL product offering give a product offering Id") + public ProductOffering getOSLProductOfferingByProductOfferingId(String productOfferingId) { + + logger.info("getOSLProductOfferingByProductOfferingId {}", productOfferingId); + + ProductOffering spec = aCatalogClient.retrieveProductOffering(productOfferingId); + + return spec; + } + + @Tool(description = "Get all the details of an OSL product specification give a product Specification Id") + public ProductSpecification getOSLProductByProductSpecificationId(String productSpecId) { + + logger.info("getOSLProductByProductSpecificationId {}", productSpecId); + + ProductSpecification spec = aCatalogClient.retrieveProductSpec(productSpecId); + + return spec; + } + + + @Tool(description = "Search for OSL product Offerings that are published and available for product ordering in all categories") + public List searchOSLProductOfferings( List searchStrings) { + + logger.info("searchOSLProductOfferings containing workds: {}", searchStrings); + + List spec = aCatalogClient.searchProductOfferings( searchStrings ); + + return spec; + } + + @Tool(description = "Create a product order given a Product Offering id, the Start date an end date of the order. " + + "The user can provide also characteristics of product in the map with format key, value" + + "Date Time has the format YYYY-MM-DDTHH:mm:ss+00:00") + public String createProductOrder(String productOfferingId, String startDate, String endDate, Map characteristics) { + + logger.info("createProductOrder {} {} {} {}", productOfferingId, startDate, endDate, characteristics.toString()); + + ProductOrderCreate ponew = new ProductOrderCreate(); + + OffsetDateTime sDate; + OffsetDateTime eDate; + if (startDate == null) { + sDate = OffsetDateTime.now(); + } else { + sDate = OffsetDateTime.parse(startDate); + } + ponew.setRequestedStartDate( sDate ); + + + if (endDate == null) { + eDate = OffsetDateTime.now().plusDays(1); + } else { + eDate = OffsetDateTime.parse(endDate); + } + ponew.setRequestedCompletionDate( eDate ); + + ponew.setCategory("Automated order from MCP"); + ponew.setDescription("Automatically from MCP "); + + if (ponew.getRelatedParty() == null) { + RelatedParty rp = new RelatedParty(); + rp.setName("MCP"); + rp.setRole("REQUESTER"); + ponew.addRelatedPartyItem(rp); + } + + if (ponew.getNote() == null) { + Note n = new Note(); + + n.setText( "Order created by MCP"); + + ponew.addNoteItem(n); + } + + + ProductOfferingRef prodOffRef = new ProductOfferingRef(); + prodOffRef.setId( productOfferingId ); + + ProductOrderItem poi = new ProductOrderItem(); + poi.action(OrderItemActionType.ADD).productOffering(prodOffRef); + + + ponew.getProductOrderItem().add(poi); + + + if (characteristics!=null) { + for (String charKey : characteristics.keySet()) { + Characteristic servChar = new Characteristic(); + servChar.setUuid(null); + servChar.setName(charKey); + servChar.setValue( new Any( characteristics.get(charKey) )); + + } + } + + + + ProductOrder so = aCatalogClient.createProductOrder(ponew); + + + return "Product Order created with id :" + so.getId() ; + + } + + + @Tool(description = "Provide details for a product order given a Product Order id. " + + "Focus attention to:" + + "- the state of the product order" + + "- and each order item. Especially for each order item focus to the product and especially: status, characteristics and supporting products." + + "- For each supporting product we can retrieve more information by using the product id.") + public String getProductOrder(String productOrderId) { + + logger.info("productOrderId {} {} {} {}", productOrderId); + ProductOrder so = aCatalogClient.retrieveProductOrder(productOrderId); + try { + return ServiceCatalogQClient.toJsonString(so); + } catch (IOException e) { + e.printStackTrace(); + } + return ""; + } + + + + +} diff --git a/src/main/java/org/etsi/osl/mcp/server/ServiceCatalogQClient.java b/src/main/java/org/etsi/osl/mcp/server/ServiceCatalogQClient.java new file mode 100644 index 0000000..a9756af --- /dev/null +++ b/src/main/java/org/etsi/osl/mcp/server/ServiceCatalogQClient.java @@ -0,0 +1,461 @@ +package org.etsi.osl.mcp.server; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.camel.ProducerTemplate; +import org.apache.camel.builder.RouteBuilder; +import org.etsi.osl.tmf.common.model.service.ServiceSpecificationRef; +import org.etsi.osl.tmf.rcm634.model.LogicalResourceSpecification; +import org.etsi.osl.tmf.ri639.model.LogicalResource; +import org.etsi.osl.tmf.ri639.model.Resource; +import org.etsi.osl.tmf.scm633.model.ServiceCatalog; +import org.etsi.osl.tmf.scm633.model.ServiceCategory; +import org.etsi.osl.tmf.scm633.model.ServiceSpecification; +import org.etsi.osl.tmf.sim638.model.ServiceUpdate; +import org.etsi.osl.tmf.so641.model.ServiceOrder; +import org.etsi.osl.tmf.so641.model.ServiceOrderCreate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import jakarta.validation.constraints.NotNull; + +/** + * @author ctranoris + * Class to exchange information with TMF API services + * + */ +@Service +public class ServiceCatalogQClient extends RouteBuilder { + + + private static final Logger logger = LoggerFactory.getLogger(ServiceCatalogQClient.class); + + @Autowired + private ProducerTemplate template; + + + + @Value("${CATALOG_ADD_SERVICEORDER}") + private String CATALOG_ADD_SERVICEORDER = ""; + + + @Value("${CATALOG_GET_SERVICESPEC_BY_ID}") + private String CATALOG_GET_SERVICESPEC_BY_ID = ""; + + @Value("${CATALOG_GET_RESOURCESPEC_BY_ID}") + private String CATALOG_GET_RESOURCESPEC_BY_ID = ""; + + + + @Value("${CATALOG_GET_SERVICEORDER_BY_ID}") + private String CATALOG_GET_SERVICEORDER_BY_ID = ""; + + + @Value("${CATALOG_UPD_SERVICE}") + private String CATALOG_UPD_SERVICE = ""; + + + @Value("${CATALOG_GET_SERVICE_BY_ID}") + private String CATALOG_GET_SERVICE_BY_ID = ""; + + + @Value("${CATALOG_GET_RESOURCE_BY_ID}") + private String CATALOG_GET_RESOURCE_BY_ID = ""; + + + @Value("${CATALOG_GET_SERVICECATALOGS}") + private String CATALOG_GET_SERVICECATALOGS = ""; + + + + @Value("${CATALOG_GET_SERVICECATALOG_BY_NAME}") + private String CATALOG_GET_SERVICECATALOG_BY_NAME = ""; + + @Value("${CATALOG_GET_SERVICECATEGORIES}") + private String CATALOG_GET_SERVICECATEGORIES = ""; + + @Value("${CATALOG_GET_SERVICECATEGORY_BY_ID}") + private String CATALOG_GET_SERVICECATEGORY_BY_ID = ""; + + + @Value("${CATALOG_GET_SERVICESPECREFS_BYCATEGORY_ID}") + private String CATALOG_GET_SERVICESPECREFS_BYCATEGORY_ID = ""; + + @Value("${CATALOG_SEARCH_SERVICESPECREFS}") + private String CATALOG_SEARCH_SERVICESPECREFS = ""; + + + @Override + public void configure() throws Exception { + + + + } + + /** + * get service spec by id from model via bus + * @param id + * @return + * @throws IOException + */ + public ServiceSpecification retrieveServiceSpec(String specid) { + logger.info("will retrieve Service Specification from catalog orderid=" + specid ); + + try { + Object response = template. + requestBody( CATALOG_GET_SERVICESPEC_BY_ID, specid); + + if ( !(response instanceof String)) { + logger.error("Service Specification object is wrong."); + return null; + } + ServiceSpecification sor = toJsonObj( (String)response, ServiceSpecification.class); + //logger.debug("retrieveSpec response is: " + response); + return sor; + + }catch (Exception e) { + logger.error("Cannot retrieve Service Specification details from catalog. " + e.toString()); + } + return null; + } + + + + public ServiceOrder createServiceOrder(ServiceOrderCreate serviceOrderCreate) { + logger.info("will create Service Order "); + try { + + + try { + Object response = + template.requestBody(CATALOG_ADD_SERVICEORDER, toJsonString(serviceOrderCreate)); + + if (!(response instanceof String)) { + logger.error("Service Order object is wrong."); + return null; + } + logger.debug("createServiceOrder response is: " + response); + ServiceOrder sor = toJsonObj((String) response, ServiceOrder.class); + + return sor; + + } catch (Exception e) { + logger.error("Cannot createServiceOrder details to catalog. " + e.toString()); + } + return null; + + + + } catch (Exception e) { + logger.error("Cannot createServiceOrder to catalog. " + e.toString()); + } + return null; + + } + + /** + * get service order by id from model via bus + * @param id + * @return + * @throws IOException + */ + public ServiceOrder retrieveServiceOrder( String orderid) { + logger.info("will retrieve Service Order from catalog orderid=" + orderid ); + try { + Object response = template. + requestBody( CATALOG_GET_SERVICEORDER_BY_ID, orderid); + + if ( !(response instanceof String)) { + logger.error("Service Order object is wrong."); + return null; + } + logger.debug("retrieveServiceOrder response is: " + response); + ServiceOrder sor = toJsonObj( (String)response, ServiceOrder.class); + + return sor; + + }catch (Exception e) { + logger.error("Cannot retrieve Service Order details from catalog. " + e.toString()); + } + return null; + } + + /** + * @param serviceId + * @param s + * @param triggerServiceActionQueue is a cryptic thing. However it is used as follows: if FALSE, to just update the service status in catalog without further taking any action. + * if TRUE then the ServiceUpdate will trigger a ServiceActionQueue to further process the update. So this is needed to avoid these kinds of deadlocks + * @return + */ + public org.etsi.osl.tmf.sim638.model.Service updateService(String serviceId, ServiceUpdate s, boolean triggerServiceActionQueue) { + logger.info("will update Service : " + serviceId ); + try { + Map map = new HashMap<>(); + map.put("serviceid", serviceId ); + map.put("triggerServiceActionQueue", triggerServiceActionQueue ); + + Object response = template.requestBodyAndHeaders( CATALOG_UPD_SERVICE, toJsonString(s), map); + + if ( !(response instanceof String)) { + logger.error("Service Instance object is wrong."); + } + + org.etsi.osl.tmf.sim638.model.Service serviceInstance = toJsonObj( (String)response, org.etsi.osl.tmf.sim638.model.Service.class); + //logger.debug("createService response is: " + response); + return serviceInstance; + + + }catch (Exception e) { + logger.error("Cannot update Service: " + serviceId + ": " + e.toString()); + } + return null; + + } + + /** + * Ger service instance via bus + * @param serviceID + * @return + */ + public org.etsi.osl.tmf.sim638.model.Service retrieveService(String serviceID) { + logger.info("will retrieve Service instance from catalog serviceID=" + serviceID ); + try { + Object response = template. + requestBody( CATALOG_GET_SERVICE_BY_ID, serviceID); + + if ( !(response instanceof String)) { + logger.error("Service object is wrong."); + return null; + } + org.etsi.osl.tmf.sim638.model.Service serviceInstance = toJsonObj( (String)response, org.etsi.osl.tmf.sim638.model.Service.class); + //logger.debug("retrieveService response is: " + response); + return serviceInstance; + + }catch (Exception e) { + logger.error("Cannot retrieve Service details from catalog. " + e.toString()); + } + return null; + } + + public Resource retrieveResource(@NotNull String resourceID) { + logger.info("will retrieve Resource instance from catalog resourceID=" + resourceID ); + try { + Object response = template. + requestBody( CATALOG_GET_RESOURCE_BY_ID, resourceID); + + if ( !(response instanceof String)) { + logger.error("resource object is wrong."); + return null; + } + LogicalResource rInstance = toJsonObj( (String)response, LogicalResource.class); + + return rInstance; + + }catch (Exception e) { + logger.error("Cannot retrieve LogicalResource details from catalog. " + e.toString()); + } + return null; + + } + + + public LogicalResourceSpecification retrieveResourceSpec(@NotNull String resourceSpecID) { + logger.info("will retrieve Resource Specification from catalog resourceSpecID=" + resourceSpecID ); + try { + Object response = template. + requestBody( CATALOG_GET_RESOURCESPEC_BY_ID, resourceSpecID); + + if ( !(response instanceof String)) { + logger.error("resource object is wrong."); + return null; + } + LogicalResourceSpecification rInstance = toJsonObj( (String)response, LogicalResourceSpecification.class); + + return rInstance; + + }catch (Exception e) { + logger.error("Cannot retrieve LogicalResource details from catalog. " + e.toString()); + } + return null; + + } + + + + /** + * get service Catalogs via bus + * @param id + * @return + * @throws IOException + */ + public List retrieveServiceCatalogs() { + logger.info("will retrieve Service Catalogs " ); + + try { + Object response = template. + requestBody( CATALOG_GET_SERVICECATALOGS, "" ); + + if ( !(response instanceof String)) { + logger.error("Service Catalogs object is wrong."); + return null; + } + List sor = toJsonObj( (String)response, ArrayList.class); + //logger.debug("retrieveSpec response is: " + response); + return sor; + + }catch (Exception e) { + logger.error("Cannot retrieve ServiceCatalogs. " + e.toString()); + e.printStackTrace(); + } + return null; + } + + + /** + * get service Catalog via bus + * @param id + * @return + * @throws IOException + */ + public ServiceCatalog retrieveServiceCatalog(String catalogName) { + logger.info("will retrieve Service Catalog details " + catalogName ); + + try { + + Map map = new HashMap<>(); + map.put("catalogName", catalogName ); + + Object response = template.requestBodyAndHeaders( CATALOG_GET_SERVICECATALOG_BY_NAME, "", map); + + + if ( !(response instanceof String)) { + logger.error("Service Catalog object is wrong."); + return null; + } + ServiceCatalog sor = toJsonObj( (String)response, ServiceCatalog.class); + + return sor; + + }catch (Exception e) { + logger.error("Cannot retrieve ServiceCatalog. " + e.toString()); + e.printStackTrace(); + } + return null; + } + + + /** + * get service Categories via bus + * @param id + * @return + * @throws IOException + */ + public List retrieveServiceCategoriesDetailsOfCatalog(String catalogName) { + logger.info("will retrieve Service Categories Details Of " + catalogName ); + + try { + + Map map = new HashMap<>(); + map.put("catalogName", catalogName ); + + Object response = template.requestBodyAndHeaders( CATALOG_GET_SERVICECATEGORIES, "", map); + + + if ( !(response instanceof String)) { + logger.error("Service Catalog object is wrong."); + return null; + } + List sor = toJsonObj( (String)response, ArrayList.class); + + return sor; + + }catch (Exception e) { + logger.error("Cannot retrieve Service Categories. " + e.toString()); + e.printStackTrace(); + } + return null; + } + + /** + * get service Categories via bus + * @param id + * @return + * @throws IOException + */ + public List retrieveServiceSpecsByCategoryId(String categoryId) { + logger.info("will retrieve Service Specs Details Of category " + categoryId ); + + try { + + Map map = new HashMap<>(); + map.put("categoryId", categoryId ); + + Object response = template.requestBodyAndHeaders( CATALOG_GET_SERVICESPECREFS_BYCATEGORY_ID, "", map); + + + if ( !(response instanceof String)) { + logger.error("Service Specs object is wrong."); + return null; + } + List sor = toJsonObj( (String)response, ArrayList.class); + + return sor; + + }catch (Exception e) { + logger.error("Cannot retrieve Service Specs. " + e.toString()); + e.printStackTrace(); + } + return null; + } + + + public List searchServiceSpecs(List searchText) { + logger.info("will search Service Specs " + searchText ); + + try { + + //Map map = new HashMap<>(); + //map.put("searchText", searchText ); + String body = toJsonString(searchText); + Object response = template.requestBody( CATALOG_SEARCH_SERVICESPECREFS, body ); + + + if ( !(response instanceof String)) { + logger.error("Service Specs object is wrong."); + return null; + } + List sor = toJsonObj( (String)response, ArrayList.class); + + return sor; + + }catch (Exception e) { + logger.error("Cannot retrieve Service Specs. " + e.toString()); + e.printStackTrace(); + } + return null; + } + + + static String toJsonString(Object object) throws IOException { + ObjectMapper mapper = new ObjectMapper(); + mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); + return mapper.writeValueAsString(object); + } + + static T toJsonObj(String content, Class valueType) throws IOException { + ObjectMapper mapper = new ObjectMapper(); + mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); + return mapper.readValue(content, valueType); + } + + + +} diff --git a/src/main/java/org/etsi/osl/mcp/server/ServiceCatalogTools.java b/src/main/java/org/etsi/osl/mcp/server/ServiceCatalogTools.java new file mode 100644 index 0000000..3d355ae --- /dev/null +++ b/src/main/java/org/etsi/osl/mcp/server/ServiceCatalogTools.java @@ -0,0 +1,307 @@ +package org.etsi.osl.mcp.server; + +import java.io.IOException; +import java.time.OffsetDateTime; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.etsi.osl.tmf.common.model.Any; +import org.etsi.osl.tmf.common.model.service.Characteristic; +import org.etsi.osl.tmf.common.model.service.Note; +import org.etsi.osl.tmf.common.model.service.ServiceSpecificationRef; +import org.etsi.osl.tmf.prm669.model.RelatedParty; +import org.etsi.osl.tmf.rcm634.model.LogicalResourceSpecification; +import org.etsi.osl.tmf.ri639.model.Resource; +import org.etsi.osl.tmf.scm633.model.ServiceCandidate; +import org.etsi.osl.tmf.scm633.model.ServiceCatalog; +import org.etsi.osl.tmf.scm633.model.ServiceCategory; +import org.etsi.osl.tmf.scm633.model.ServiceCategoryRef; +import org.etsi.osl.tmf.scm633.model.ServiceSpecification; +import org.etsi.osl.tmf.sim638.model.Service; +import org.etsi.osl.tmf.sim638.model.ServiceUpdate; +import org.etsi.osl.tmf.so641.model.ServiceOrder; +import org.etsi.osl.tmf.so641.model.ServiceOrderCreate; +import org.etsi.osl.tmf.so641.model.ServiceOrderItem; +import org.etsi.osl.tmf.so641.model.ServiceOrderStateType; +import org.etsi.osl.tmf.so641.model.ServiceRestriction; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.ai.tool.annotation.Tool; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.web.client.RestClient; +import jakarta.validation.Valid; + +/** + * + * @author ctranoris + */ +@org.springframework.stereotype.Service +public class ServiceCatalogTools { + + + private static final Logger logger = LoggerFactory.getLogger(ServiceCatalogTools.class); + + + //private final RestClient restClient; + + @Autowired + ServiceCatalogQClient aCatalogClient; + + + @Tool(description = "Get a list of all published OSL OpenSlice service catalogs." + + "Each catalog contains service categories, that we can search individually to get the details and contents of each category.") + public JsonNode getOSLServiceCatalogs() { + + logger.info("getOSLServiceCatalogs"); + List serviceCatalogs = aCatalogClient.retrieveServiceCatalogs(); + + + // Filter and get result as JSON string + try { + + String[] tokens = {"id", "name", "description", "@type"}; + JsonNode filtered = JsonMassage.filterJsonByTokens( serviceCatalogs, tokens); + return filtered; + } catch (Exception e) { + e.printStackTrace(); + } + + ObjectMapper mapper = new ObjectMapper(); + //return mapper.writeValueAsString(serviceCatalogs); + JsonNode rootNode = mapper.valueToTree( serviceCatalogs ); + return rootNode; + + } + + @Tool(description = "Get OSL categories in catalog providing a catalog name") + public JsonNode getOSLServiceCategories(String catalogName) { + + logger.info("getOSLServiceCategories {}", catalogName); + + List serviceCategories = aCatalogClient.retrieveServiceCategoriesDetailsOfCatalog(catalogName); + + // Filter and get result as JSON string + try { + + String[] tokens = {"id", "name", "description", "@type"}; + JsonNode filtered = JsonMassage.filterJsonByTokens( serviceCategories, tokens); + return filtered; + } catch (Exception e) { + e.printStackTrace(); + } + + ObjectMapper mapper = new ObjectMapper(); + //return mapper.writeValueAsString(serviceCatalogs); + JsonNode rootNode = mapper.valueToTree( serviceCategories ); + return rootNode; + } + + + @Tool(description = "Get a list of OSL service specification references in a service category, given a category ID") + public List getOSLServiceSpecsInCategory(String categoryId) { + + logger.info("getOSLServiceSpecsInCategory {}", categoryId); + + + List serviceCategories = aCatalogClient.retrieveServiceSpecsByCategoryId(categoryId); + + return serviceCategories; + + + } + + @Tool(description = "Get all the details of an OSL service specification given a service Specification Id") + public ServiceSpecification getOSLServiceSpecificationByServiceSpecificationId(String serviceSpecId) { + + logger.info("getOSLServiceByServiceSpecificationId {}", serviceSpecId); + + ServiceSpecification spec = aCatalogClient.retrieveServiceSpec(serviceSpecId); + + return spec; + } + + + + @Tool(description = "Get all the details of an OSL resource specification given a resource Specification Id") + public LogicalResourceSpecification getOSLResourceSpecificationByResourceSpecificationId(String resourceSpecId) { + + logger.info("getOSLResourceSpecificationByResourceSpecificationId {}", resourceSpecId); + + LogicalResourceSpecification spec = aCatalogClient.retrieveResourceSpec(resourceSpecId); + + return spec; + } + + + + @Tool(description = "Search for OSL service specifications that are published and available for service ordering in all categories") + public List searchOSLServiceSpecifications( List searchStrings) { + + logger.info("searchOSLServiceSpecifications containing words: {}", searchStrings); + + List spec = aCatalogClient.searchServiceSpecs( searchStrings ); + + return spec; + } + + @Tool(description = "Create a service order given a Service Specification id, the Start date an end date of the order. " + + "The user can provide also characteristics of service in the map with format key, value" + + "Date Time has the format YYYY-MM-DDTHH:mm:ss+00:00") + public String createServiceOrder(String serviceSpecId, String startDate, String endDate, Map characteristics) { + + logger.info("createServiceOrder {} {} {} {}", serviceSpecId, startDate, endDate, characteristics.toString()); + + ServiceOrderCreate sonew = new ServiceOrderCreate(); + + OffsetDateTime sDate; + OffsetDateTime eDate; + if (startDate == null) { + sDate = OffsetDateTime.now(); + } else { + sDate = OffsetDateTime.parse(startDate); + } + sonew.setRequestedStartDate( sDate ); + + + if (endDate == null) { + eDate = OffsetDateTime.now().plusDays(1); + } else { + eDate = OffsetDateTime.parse(endDate); + } + sonew.setRequestedCompletionDate( eDate ); + + sonew.setCategory("Automated order from MCP"); + sonew.setDescription("Automatically from MCP "); + + if (sonew.getRelatedParty() == null) { + RelatedParty rp = new RelatedParty(); + rp.setName("MCP"); + rp.setRole("REQUESTER"); + sonew.addRelatedPartyItem(rp); + } + + if (sonew.getNote() == null) { + Note n = new Note(); + + n.setText( "Order created by MCP"); + + sonew.addNoteItem(n); + } + + + ServiceOrderItem soi = new ServiceOrderItem(); + sonew.getOrderItem().add(soi); + soi.setState(ServiceOrderStateType.ACKNOWLEDGED); + + + ServiceSpecification serviceSpec = aCatalogClient.retrieveServiceSpec(serviceSpecId); + + ServiceRestriction serviceRestriction = new ServiceRestriction(); + ServiceSpecificationRef aServiceSpecificationRef = new ServiceSpecificationRef(); + aServiceSpecificationRef.setId( serviceSpecId ); + aServiceSpecificationRef.setName( serviceSpec.getName()); + aServiceSpecificationRef.setVersion( serviceSpec.getVersion()); + + serviceRestriction.setServiceSpecification(aServiceSpecificationRef); + + + if (characteristics!=null) { + for (String charKey : characteristics.keySet()) { + Characteristic servChar = new Characteristic(); + servChar.setUuid(null); + servChar.setName(charKey); + servChar.setValue( new Any( characteristics.get(charKey) )); + serviceRestriction.addServiceCharacteristicItem(servChar); + } + } + + soi.setService(serviceRestriction); + + + ServiceOrder so = aCatalogClient.createServiceOrder(sonew); + + + return "Service Order created with id :" + so.getId() ; + + } + + + @Tool(description = "Provide details for a service order given a Service Order id. " + + "Focus attention to:" + + "- the state of the service order" + + "- and each order item. Especially for each order item focus to the service and especially: status, characteristics and supporting services." + + "- For each supporting service we can retrieve more information by using the service id.") + public String getServiceOrder(String serviceOrderId) { + + logger.info("serviceOrderId {} {} {} {}", serviceOrderId); + ServiceOrder so = aCatalogClient.retrieveServiceOrder(serviceOrderId); + try { + return ServiceCatalogQClient.toJsonString(so); + } catch (IOException e) { + e.printStackTrace(); + } + return ""; + } + + @Tool(description = "Provide details for a service given a Service id. Especially for name, state, characteristics, supporting Services and supporting Resources." + + "We can get details for each supporting service using the service id." + + "and for each supporting resource using the resource id.") + public String getService(String serviceId) { + logger.info("getService {} {} {} {}", serviceId); + Service s = aCatalogClient.retrieveService(serviceId); + try { + return ServiceCatalogQClient.toJsonString(s); + } catch (IOException e) { + e.printStackTrace(); + } + return ""; + } + + @Tool(description = "Provide details for a resource given a Resourceid. Especially for name, status, characteristics." + + "We can get details for each supporting resource using the resource id.") + public String getResource(String resourceId) { + logger.info("getResource {} {} {} {}", resourceId); + Resource r = aCatalogClient.retrieveResource ( resourceId); + try { + return ServiceCatalogQClient.toJsonString(r); + } catch (IOException e) { + e.printStackTrace(); + } + return ""; + } + + + @Tool(description = "Update and change a service given a Service id." + + "We provide also characteristics of service in the map with format key, value") + public String updateService(String serviceId, Map characteristics) { + logger.info("updateService {} {} {} {}", serviceId); + + ServiceUpdate su = new ServiceUpdate(); + + if (characteristics!=null) { + for (String charKey : characteristics.keySet()) { + Characteristic servChar = new Characteristic(); + servChar.setUuid(null); + servChar.setName(charKey); + servChar.setValue( new Any( characteristics.get(charKey) )); + su.addServiceCharacteristicItem(servChar); + } + } + + + Service r = aCatalogClient.updateService(serviceId, su, true); + try { + return ServiceCatalogQClient.toJsonString(r); + } catch (IOException e) { + e.printStackTrace(); + } + return ""; + } +} diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml new file mode 100644 index 0000000..d31c35d --- /dev/null +++ b/src/main/resources/application.yaml @@ -0,0 +1,199 @@ +# Using spring-ai-starter-mcp-server-webmvc + + +server: + port: 13015 + +spring: + autoconfigure.exclude: org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration + ai: + mcp: + server: + name: org.etsi.osl.mcp.server + version: 0.0.1 + type: SYNC + sse-message-endpoint: /mcp/messages + stdio: false + resource-change-notification: true + tool-change-notification: true + prompt-change-notification: true + activemq: + brokerUrl: tcp://localhost:61616?jms.watchTopicAdvisories=false + user: artemis + password: artemis + pool: + enabled: true + max-connections: 100 + packages: + trust-all: true + security: + oauth2: + resourceserver: + jwt: + issuer-uri: http://keycloak:8080/auth/realms/openslice + jwk-set-uri: http://keycloak:8080/auth/realms/openslice/.well-known/openid-configuration + + +logging: + level: + root: INFO + org.etsi.osl.*: DEBUG + org.springframework: INFO + org.apache.camel: INFO + com.zaxxer.hikari: INFO + pattern: + console: "%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" + file: "%d %p %c{1.} [%t] %m%n" + + +#QUEUE MESSAGES +CATALOG_GET_SERVICESPEC_BY_ID: "jms:queue:CATALOG.GET.SERVICESPEC_BY_ID" +CATALOG_ADD_SERVICESPEC: "jms:queue:CATALOG.ADD.SERVICESPEC" +CATALOG_UPD_SERVICESPEC: "jms:queue:CATALOG.UPD.SERVICESPEC" +CATALOG_UPDADD_SERVICESPEC: "jms:queue:CATALOG.UPDADD.SERVICESPEC" +CATALOG_SERVICE_QUEUE_ITEMS_GET: "jms:queue:CATALOG.SERVICEQUEUEITEMS.GET" +CATALOG_SERVICE_QUEUE_ITEM_UPD: "jms:queue:CATALOG.SERVICEQUEUEITEM.UPDATE" +CATALOG_SERVICE_QUEUE_ITEM_DELETE: "jms:queue:CATALOG.SERVICEQUEUEITEM.DELETE" +CATALOG_GET_PARTNER_ORGANIZATON_BY_ID: "jms:queue:CATALOG.GET.PARTNER_ORGANIZATION_BY_ID" +CATALOG_UPDATE_PARTNER_ORGANIZATION: "jms:queue:CATALOG.UPD.PARTNER_ORGANIZATION" +CATALOG_SERVICES_TO_TERMINATE: "jms:queue:CATALOG.GET.SERVICETOTERMINATE" +CATALOG_SERVICES_OF_PARTNERS: "jms:queue:CATALOG.GET.SERVICESOFPARTNERS" +CATALOG_GET_EXTERNAL_SERVICE_PARTNERS: "jms:queue:CATALOG.GET.EXTERNALSERVICEPARTNERS" +CATALOG_UPD_EXTERNAL_SERVICESPEC: "jms:queue:CATALOG.UPD.EXTERNAL_SERVICESPEC" + +#SERVICE_INVENTORY +CATALOG_ADD_SERVICE: "jms:queue:CATALOG.ADD.SERVICE" +CATALOG_UPD_SERVICE: "jms:queue:CATALOG.UPD.SERVICE" +CATALOG_GET_SERVICE_BY_ID: "jms:queue:CATALOG.GET.SERVICE" +CATALOG_GET_SERVICE_BY_ORDERID: "jms:queue:CATALOG.GET.SERVICE_BY_ORDERID" + + +#SERVICE ORDERS +CATALOG_GET_SERVICEORDERS: "jms:queue:CATALOG.GET.SERVICEORDERS" +CATALOG_GET_SERVICEORDER_BY_ID: "jms:queue:CATALOG.GET.SERVICEORDER_BY_ID" +CATALOG_ADD_SERVICEORDER: "jms:queue:CATALOG.ADD.SERVICEORDER" +CATALOG_UPD_SERVICEORDER_BY_ID: "jms:queue:CATALOG.UPD.SERVICEORDER_BY_ID" +CATALOG_GET_INITIAL_SERVICEORDERS_IDS: "jms:queue:CATALOG.GET.INITIAL_SERVICEORDERS" +CATALOG_GET_SERVICEORDER_IDS_BY_STATE: "jms:queue:CATALOG.GET.ACKNOWLEDGED_SERVICEORDERS" + + +#SERVICE CATALOGS, CATEGORIES +CATALOG_GET_SERVICECATALOGS: "jms:queue:CATALOG.GET.SERVICECATALOGS" +CATALOG_GET_SERVICECATALOG_BY_ID: "jms:queue:CATALOG.GET.SERVICECATALOG_BY_ID" +CATALOG_GET_SERVICECATALOG_BY_NAME: "jms:queue:CATALOG.GET.SERVICECATALOG_BY_NAME" + +CATALOG_GET_SERVICECATEGORIES: "jms:queue:CATALOG.GET.SERVICECATEGORIES" +CATALOG_GET_SERVICECATEGORY_BY_ID: "jms:queue:CATALOG.GET.SERVICECATEGORY_BY_ID" + +CATALOG_GET_SERVICESPECREFS_BYCATEGORY_ID: "jms:queue:CATALOG.GETSERVICESPECREFS.SERVICECATEGORY_BY_ID" +CATALOG_SEARCH_SERVICESPECREFS: "jms:queue:CATALOG.CATALOG_SEARCH_SERVICESPECREFS" + + +#PRODUCT CATALOGS +CATALOG_GET_PRODUCTSPEC_BY_ID: "jms:queue:CATALOG.GET.PRODUCTSPEC_BY_ID" +CATALOG_ADD_PRODUCTSPEC: "jms:queue:CATALOG.ADD.PRODUCTSPEC" +CATALOG_UPD_PRODUCTSPEC: "jms:queue:CATALOG.UPD.PRODUCTSPEC" +CATALOG_UPDADD_PRODUCTSPEC: "jms:queue:CATALOG.UPDADD.PRODUCTSPEC" +CATALOG_GET_PRODUCTOFFERING_BY_ID: "jms:queue:CATALOG.GET.PRODUCTOFFERING_BY_ID" +CATALOG_GET_PRODUCTORDERS: "jms:queue:CATALOG.GET.PRODUCTORDERS" +CATALOG_GET_PRODUCTORDER_BY_ID: "jms:queue:CATALOG.GET.PRODUCTORDER_BY_ID" +CATALOG_ADD_PRODUCTORDER: "jms:queue:CATALOG.ADD.PRODUCTORDER" +CATALOG_UPD_PRODUCTORDER_BY_ID: "jms:queue:CATALOG.UPD.PRODUCTORDER_BY_ID" +CATALOG_GET_INITIAL_PRODUCTORDERS_IDS: "jms:queue:CATALOG.GET.INITIAL_PRODUCTORDERS" +CATALOG_GET_PRODUCTORDER_IDS_BY_STATE: "jms:queue:CATALOG.GET.ACKNOWLEDGED_PRODUCTORDERS" +CATALOG_GET_PRODUCTCATALOGS: "jms:queue:CATALOG.GET.PRODUCTCATALOGS" +CATALOG_GET_PRODUCTCATALOG_BY_ID: "jms:queue:CATALOG.GET.PRODUCTCATALOG_BY_ID" +CATALOG_GET_PRODUCTCATALOG_BY_NAME: "jms:queue:CATALOG.GET.PRODUCTCATALOG_BY_NAME" +CATALOG_GET_PRODUCTCATEGORIES: "jms:queue:CATALOG.GET.PRODUCTCATEGORIES" +CATALOG_GET_PRODUCTCATEGORY_BY_ID: "jms:queue:CATALOG.GET.PRODUCTCATEGORY_BY_ID" +CATALOG_GET_PRODUCTOFFERINGS_BYCATEGORY_ID: "jms:queue:CATALOG.GET.PRODUCTOFFERINGS.CATEGORY_ID" +CATALOG_SEARCH_PRODUCTOFFERINGS: "jms:queue:CATALOG.CATALOG_SEARCH_PRODUCTOFFERINGS" + + + + +#PM_MEASUREMENT_COLLECTION +PM_MEASUREMENT_COLLECTION_GET_JOB_BY_ID: "jms:queue:PM.MEASUREMENTCOLLECTIONJOB.GET_BY_ID" +PM_MEASUREMENT_COLLECTION_JOBS_GET: "jms:queue:PM.MEASUREMENTCOLLECTIONJOBS.GET" +PM_MEASUREMENT_COLLECTION_JOB_ADD: "jms:queue:PM.MEASUREMENTCOLLECTIONJOB.ADD" +PM_MEASUREMENT_COLLECTION_JOB_CREATED: "jms:queue:PM.MEASUREMENTCOLLECTIONJOB.CREATED" +PM_MEASUREMENT_COLLECTION_JOB_UPDATE: "jms:queue:PM.MEASUREMENTCOLLECTIONJOB.UPDATE" +PM_MEASUREMENT_COLLECTION_JOB_GET_INPRORGESS_OR_PENDING: "jms:queue:PM.MEASUREMENTCOLLECTIONJOB.GET_INPRORGESS_OR_PENDING" + +#ALARMS +ALARMS_ADD_ALARM: "jms:queue:ALARMS.ADD.ALARM" +ALARMS_UPDATE_ALARM: "jms:queue:ALARMS.UPDATE.ALARM" +ALARMS_GET_ALARM: "jms:queue:ALARMS.GET.ALARM" + +#EVENT TOPICS IN Message Bus +EVENT_SERVICE_CREATE: "jms:topic:EVENT.SERVICE.CREATE" +EVENT_SERVICE_STATE_CHANGED: "jms:topic:EVENT.SERVICE.STATECHANGED" +EVENT_SERVICE_DELETE: "jms:topic:EVENT.SERVICE.DELETE" +EVENT_SERVICE_ATTRIBUTE_VALUE_CHANGED: "jms:topic:EVENT.SERVICE.ATTRCHANGED" +EVENT_SERVICE_ORDER_CREATE: "jms:topic:EVENT.SERVICEORDER.CREATE" +EVENT_SERVICE_ORDER_STATE_CHANGED: "jms:topic:EVENT.SERVICEORDER.STATECHANGED" +EVENT_SERVICE_ORDER_DELETE: "jms:topic:EVENT.SERVICEORDER.DELETE" +EVENT_SERVICE_ORDER_ATTRIBUTE_VALUE_CHANGED: "jms:topic:EVENT.SERVICEORDER.ATTRCHANGED" +EVENT_CUSTOMER_CREATE: "jms:topic:EVENT.CUSTOMER.CREATE" +EVENT_CUSTOMER_CHANGED: "jms:topic:EVENT.CUSTOMER.CHANGE" +EVENT_INDIVIDUAL_CREATE: "jms:topic:EVENT.INDIVIDUAL.CREATE" +EVENT_INDIVIDUAL_CHANGED: "jms:topic:EVENT.INDIVIDUAL.CHANGE" +EVENT_ORGANIZATION_CREATE: "jms:topic:EVENT.ORGANIZATION.CREATE" +EVENT_ORGANIZATION_CHANGED: "jms:topic:EVENT.ORGANIZATION.CHANGE" +EVENT_ALARM_CREATE: "jms:topic:EVENT.ALARM.CREATE" +EVENT_MEASUREMENT_COLLECTION_JOB_CREATE: "jms:topic:EVENT.MEASUREMENTCOLLECTIONJOB.CREATE" +EVENT_MEASUREMENT_COLLECTION_JOB_EXECUTION_STATE_CHANGED: "jms:topic:EVENT.MEASUREMENTCOLLECTIONJOB.STATECHANGED" +EVENT_MEASUREMENT_COLLECTION_JOB_DELETE: "jms:topic:EVENT.MEASUREMENTCOLLECTIONJOB.DELETE" +EVENT_MEASUREMENT_COLLECTION_JOB_ATTRIBUTE_VALUE_CHANGED: "jms:topic:EVENT.MEASUREMENTCOLLECTIONJOB.ATTRCHANGED" + +EVENT_PRODUCT_ORDER_CREATE: "jms:topic:EVENT.PRODUCTORDER.CREATE" +EVENT_PRODUCT_ORDER_STATE_CHANGED: "jms:topic:EVENT.PRODUCTORDER.STATECHANGED" +EVENT_PRODUCT_ORDER_DELETE: "jms:topic:EVENT.PRODUCTORDER.DELETE" +EVENT_PRODUCT_ORDER_ATTRIBUTE_VALUE_CHANGED: "jms:topic:EVENT.PRODUCTORDER.ATTRCHANGED" + +#QUEUE MESSSAGES WITH VNFNSD CATALOG +NFV_CATALOG_GET_NSD_BY_ID: "jms:queue:NFVCATALOG.GET.NSD_BY_ID" + +#MISC +GET_USER_BY_USERNAME: "jms:queue:GET.USER_BY_USERNAME" + +#RESOURCES MESSAGES +CATALOG_ADD_RESOURCE: "jms:queue:CATALOG.ADD.RESOURCE" +CATALOG_UPD_RESOURCE: "jms:queue:CATALOG.UPD.RESOURCE" +CATALOG_UPDADD_RESOURCE: "jms:queue:CATALOG.UPDADD.RESOURCE" +CATALOG_GET_RESOURCE_BY_ID: "jms:queue:CATALOG.GET.RESOURCE" +CATALOG_ADD_RESOURCESPEC: "jms:queue:CATALOG.ADD.RESOURCESPEC" +CATALOG_UPD_RESOURCESPEC: "jms:queue:CATALOG.UPD.RESOURCESPEC" +CATALOG_UPDADD_RESOURCESPEC: "jms:queue:CATALOG.UPDADD.RESOURCESPEC" +CATALOG_GET_RESOURCESPEC_BY_ID: "jms:queue:CATALOG.GET.RESOURCESPEC_BY_ID" +CATALOG_GET_RESOURCESPEC_BY_NAME_CATEGORY: "jms:queue:CATALOG.GET.RESOURCESPEC_BY_NAME_CATEGORY" +EVENT_RESOURCE_CREATE: "jms:topic:EVENT.RESOURCE.CREATE" +EVENT_RESOURCE_STATE_CHANGED: "jms:topic:EVENT.RESOURCE.STATECHANGED" +EVENT_RESOURCE_DELETE: "jms:topic:EVENT.SERVICE.RESOURCE" +EVENT_RESOURCE_ATTRIBUTE_VALUE_CHANGED: "jms:topic:EVENT.RESOURCE.ATTRCHANGED" +CATALOG_RESOURCES_OF_PARTNERS: "jms:queue:CATALOG.GET.SERVICESOFPARTNERS" + +#RESOURCE_ACTIVATION +CATALOG_ADD_RESOURCEACTIVATION: "jms:queue:CATALOG.ADD.RESOURCEACTIVATION" +CATALOG_UPD_RESOURCEACTIVATION: "jms:queue:CATALOG.UPD.RESOURCEACTIVATION" +CATALOG_UPDADD_RESOURCEACTIVATION: "jms:queue:CATALOG.UPDADD.RESOURCEACTIVATION" +CATALOG_GET_RESOURCEACTIVATION_BY_ID: "jms:queue:CATALOG.GET.RESOURCEACTIVATION" + +#LCM MESSAGES +CATALOG_GET_LCMRULE_BY_ID: "jms:queue:CATALOG.GET.LCMRULE" +CATALOG_GET_LCMRULES_BY_SPECID_PHASE: "jms:queue:CATALOG.GET.LCMRULES_BY_SPECID_PHASE" + + +#SERVICE_TEST +CATALOG_GET_SERVICETESTSPEC_BY_ID: "jms:queue:CATALOG.GET.SERVICETESTSPEC_BY_ID" +CATALOG_ADD_SERVICETEST: "jms:queue:CATALOG.ADD.SERVICETEST" +CATALOG_UPD_SERVICETEST: "jms:queue:CATALOG.UPD.SERVICETEST" +CATALOG_GET_SERVICETEST_BY_ID: "jms:queue:CATALOG.GET.SERVICETEST" + +#NS ACTIONS +NFV_CATALOG_NSACTIONS_SCALE: "jms:queue:NSACTIONS.SCALE" +NFV_CATALOG_NS_LCMCHANGED: "jms:topic:NFV_CATALOG_NS_LCMCHANGED" + +#RESERVATION_API +RESERVATION_CREATE: "jms:queue:RESERVATION.ADD" +RESERVATION_AVAILABILITY_CHECK: "jms:queue:RESERVATION.AVAILABILITYCHECK" \ No newline at end of file diff --git a/src/main/resources/banner.txt b/src/main/resources/banner.txt new file mode 100644 index 0000000..74229cd --- /dev/null +++ b/src/main/resources/banner.txt @@ -0,0 +1,11 @@ + ___ ____ _ _ + / _ \ _ __ ___ _ __ / ___|| (_) ___ ___ + | | | | '_ \ / _ \ '_ \\___ \| | |/ __/ _ \ + | |_| | |_) | __/ | | |___) | | | (_| __/ + \___/| .__/ \___|_| |_|____/|_|_|\___\___| + |_| + __ __________________ + / / __ __ / __/_ __/ __/ _/ + / _ \/ // / / _/ / / _\ \_/ / + /_.__/\_, / /___/ /_/ /___/___/ + /___/ \ No newline at end of file -- GitLab From 331e0ef665e91f12ad69c75c7557b6193d5553e1 Mon Sep 17 00:00:00 2001 From: Christos Tranoris Date: Thu, 15 May 2025 11:45:10 +0300 Subject: [PATCH 2/6] update responses --- .../mcp/server/OSLMCPServerApplication.java | 14 +- .../osl/mcp/server/ProductCatalogTools.java | 123 ++++++++++++++--- .../osl/mcp/server/ServiceCatalogTools.java | 130 ++++++++++++++---- 3 files changed, 221 insertions(+), 46 deletions(-) diff --git a/src/main/java/org/etsi/osl/mcp/server/OSLMCPServerApplication.java b/src/main/java/org/etsi/osl/mcp/server/OSLMCPServerApplication.java index 20374bd..7d75775 100644 --- a/src/main/java/org/etsi/osl/mcp/server/OSLMCPServerApplication.java +++ b/src/main/java/org/etsi/osl/mcp/server/OSLMCPServerApplication.java @@ -52,13 +52,13 @@ public class OSLMCPServerApplication { public record TextInput(String input) { } - @Bean - public ToolCallback toUpperCase() { - return FunctionToolCallback.builder("toUpperCase", (TextInput input) -> input.input().toUpperCase()) - .inputType(TextInput.class) - .description("Put the text to upper case") - .build(); - } +// @Bean +// public ToolCallback toUpperCase() { +// return FunctionToolCallback.builder("toUpperCase", (TextInput input) -> input.input().toUpperCase()) +// .inputType(TextInput.class) +// .description("Put the text to upper case") +// .build(); +// } @Bean public List myResources() { diff --git a/src/main/java/org/etsi/osl/mcp/server/ProductCatalogTools.java b/src/main/java/org/etsi/osl/mcp/server/ProductCatalogTools.java index 9de060f..f47f159 100644 --- a/src/main/java/org/etsi/osl/mcp/server/ProductCatalogTools.java +++ b/src/main/java/org/etsi/osl/mcp/server/ProductCatalogTools.java @@ -4,6 +4,8 @@ import java.io.IOException; import java.time.OffsetDateTime; import java.util.List; import java.util.Map; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; import org.etsi.osl.tmf.common.model.Any; import org.etsi.osl.tmf.common.model.service.Characteristic; import org.etsi.osl.tmf.common.model.service.Note; @@ -46,66 +48,146 @@ public class ProductCatalogTools { @Tool(description = "Get a list of all published OSL OpenSlice product catalogs." + "Each catalog contains product categories, that we can search individually to get the details and contents of each category.") - public List getOSLProductCatalogs() { + public JsonNode getOSLProductCatalogs() { logger.info("getOSLProductCatalogs"); List serviceCatalogs = aCatalogClient.retrieveProductCatalogs(); - return serviceCatalogs; + // Filter and get result as JSON string + try { + + String[] tokens = {"id", "name", "description", "@type"}; + JsonNode filtered = JsonMassage.filterJsonByTokens( serviceCatalogs, tokens); + return filtered; + } catch (Exception e) { + e.printStackTrace(); + } + + ObjectMapper mapper = new ObjectMapper(); + //return mapper.writeValueAsString(serviceCatalogs); + JsonNode rootNode = mapper.valueToTree( serviceCatalogs ); + return rootNode; } @Tool(description = "Get OSL product categories in product catalog providing a catalog name") - public List getOSLProductCategories(String catalogName) { + public JsonNode getOSLProductCategories(String catalogName) { logger.info("getOSLProductCategories {}", catalogName); List productCategories = aCatalogClient.retrieveProductCategoriesDetailsOfCatalog(catalogName); - return productCategories; + // Filter and get result as JSON string + try { + + String[] tokens = {"id", "name", "description", "@type"}; + JsonNode filtered = JsonMassage.filterJsonByTokens( productCategories, tokens); + return filtered; + } catch (Exception e) { + e.printStackTrace(); + } + + ObjectMapper mapper = new ObjectMapper(); + //return mapper.writeValueAsString(serviceCatalogs); + JsonNode rootNode = mapper.valueToTree( productCategories ); + return rootNode; } @Tool(description = "Get a list of OSL product offerings in a product category, given a category ID") - public List getOSLProductOfferingsInCategory(String categoryId) { + public JsonNode getOSLProductOfferingsInCategory(String categoryId) { logger.info("getOSLProductOfferingsInCategory {}", categoryId); List productCategories = aCatalogClient.retrieveProductOfferingsByCategoryId(categoryId); - return productCategories; + // Filter and get result as JSON string + try { + + String[] tokens = {"id", "name", "description"}; + JsonNode filtered = JsonMassage.filterJsonByTokens( productCategories, tokens); + return filtered; + } catch (Exception e) { + e.printStackTrace(); + } + + ObjectMapper mapper = new ObjectMapper(); + //return mapper.writeValueAsString(serviceCatalogs); + JsonNode rootNode = mapper.valueToTree( productCategories ); + return rootNode; } @Tool(description = "Get all the details of an OSL product offering give a product offering Id") - public ProductOffering getOSLProductOfferingByProductOfferingId(String productOfferingId) { + public JsonNode getOSLProductOfferingByProductOfferingId(String productOfferingId) { logger.info("getOSLProductOfferingByProductOfferingId {}", productOfferingId); ProductOffering spec = aCatalogClient.retrieveProductOffering(productOfferingId); - return spec; + // Filter and get result as JSON string + try { + + String[] tokens = {"id", "name", "description", "isBundle", "@type", "configurable", "valueType", "isBundle" }; + JsonNode filtered = JsonMassage.filterJsonByTokens( spec, tokens); + return filtered; + } catch (Exception e) { + e.printStackTrace(); + } + + ObjectMapper mapper = new ObjectMapper(); + //return mapper.writeValueAsString(serviceCatalogs); + JsonNode rootNode = mapper.valueToTree( spec ); + return rootNode; + } @Tool(description = "Get all the details of an OSL product specification give a product Specification Id") - public ProductSpecification getOSLProductByProductSpecificationId(String productSpecId) { + public JsonNode getOSLProductByProductSpecificationId(String productSpecId) { logger.info("getOSLProductByProductSpecificationId {}", productSpecId); ProductSpecification spec = aCatalogClient.retrieveProductSpec(productSpecId); - return spec; + // Filter and get result as JSON string + try { + + String[] tokens = {"id", "name", "description", "isBundle", "@type", "configurable", "valueType", "isBundle", + "productNumber", "brand", "isBundle", "isBundle", "isBundle", "isBundle" }; + JsonNode filtered = JsonMassage.filterJsonByTokens( spec, tokens); + return filtered; + } catch (Exception e) { + e.printStackTrace(); + } + + ObjectMapper mapper = new ObjectMapper(); + //return mapper.writeValueAsString(serviceCatalogs); + JsonNode rootNode = mapper.valueToTree( spec ); + return rootNode; } @Tool(description = "Search for OSL product Offerings that are published and available for product ordering in all categories") - public List searchOSLProductOfferings( List searchStrings) { + public JsonNode searchOSLProductOfferings( List searchStrings) { logger.info("searchOSLProductOfferings containing workds: {}", searchStrings); List spec = aCatalogClient.searchProductOfferings( searchStrings ); - return spec; + // Filter and get result as JSON string + try { + + String[] tokens = {"id", "name", "description", "isBundle", "@type", "configurable", "valueType", "isBundle" }; + JsonNode filtered = JsonMassage.filterJsonByTokens( spec, tokens); + return filtered; + } catch (Exception e) { + e.printStackTrace(); + } + + ObjectMapper mapper = new ObjectMapper(); + //return mapper.writeValueAsString(serviceCatalogs); + JsonNode rootNode = mapper.valueToTree( spec ); + return rootNode; } @Tool(description = "Create a product order given a Product Offering id, the Start date an end date of the order. " @@ -188,16 +270,25 @@ public class ProductCatalogTools { + "- the state of the product order" + "- and each order item. Especially for each order item focus to the product and especially: status, characteristics and supporting products." + "- For each supporting product we can retrieve more information by using the product id.") - public String getProductOrder(String productOrderId) { + public JsonNode getProductOrder(String productOrderId) { logger.info("productOrderId {} {} {} {}", productOrderId); ProductOrder so = aCatalogClient.retrieveProductOrder(productOrderId); + // Filter and get result as JSON string try { - return ServiceCatalogQClient.toJsonString(so); - } catch (IOException e) { + + String[] tokens = {"id", "name", "description", "orderDate", "completionDate", + "expectedCompletionDate", "requestedCompletionDate" , "requestedStartDate" , "startDate" , "category" , "state" , "action" , "value" }; + JsonNode filtered = JsonMassage.filterJsonByTokens( so, tokens); + return filtered; + } catch (Exception e) { e.printStackTrace(); } - return ""; + + ObjectMapper mapper = new ObjectMapper(); + //return mapper.writeValueAsString(serviceCatalogs); + JsonNode rootNode = mapper.valueToTree( so ); + return rootNode; } diff --git a/src/main/java/org/etsi/osl/mcp/server/ServiceCatalogTools.java b/src/main/java/org/etsi/osl/mcp/server/ServiceCatalogTools.java index 3d355ae..365fb6b 100644 --- a/src/main/java/org/etsi/osl/mcp/server/ServiceCatalogTools.java +++ b/src/main/java/org/etsi/osl/mcp/server/ServiceCatalogTools.java @@ -105,50 +105,103 @@ public class ServiceCatalogTools { @Tool(description = "Get a list of OSL service specification references in a service category, given a category ID") - public List getOSLServiceSpecsInCategory(String categoryId) { + public JsonNode getOSLServiceSpecsInCategory(String categoryId) { logger.info("getOSLServiceSpecsInCategory {}", categoryId); List serviceCategories = aCatalogClient.retrieveServiceSpecsByCategoryId(categoryId); - return serviceCategories; + // Filter and get result as JSON string + try { + + String[] tokens = {"id", "name", "description"}; + JsonNode filtered = JsonMassage.filterJsonByTokens( serviceCategories, tokens); + return filtered; + } catch (Exception e) { + e.printStackTrace(); + } + + ObjectMapper mapper = new ObjectMapper(); + //return mapper.writeValueAsString(serviceCatalogs); + JsonNode rootNode = mapper.valueToTree( serviceCategories ); + return rootNode; + + } @Tool(description = "Get all the details of an OSL service specification given a service Specification Id") - public ServiceSpecification getOSLServiceSpecificationByServiceSpecificationId(String serviceSpecId) { + public JsonNode getOSLServiceSpecificationByServiceSpecificationId(String serviceSpecId) { logger.info("getOSLServiceByServiceSpecificationId {}", serviceSpecId); ServiceSpecification spec = aCatalogClient.retrieveServiceSpec(serviceSpecId); - return spec; + // Filter and get result as JSON string + try { + + String[] tokens = {"id", "name", "description", "isBundle", "@type", "configurable", "valueType", "isBundle" }; + JsonNode filtered = JsonMassage.filterJsonByTokens( spec, tokens); + return filtered; + } catch (Exception e) { + e.printStackTrace(); + } + + ObjectMapper mapper = new ObjectMapper(); + //return mapper.writeValueAsString(serviceCatalogs); + JsonNode rootNode = mapper.valueToTree( spec ); + return rootNode; } @Tool(description = "Get all the details of an OSL resource specification given a resource Specification Id") - public LogicalResourceSpecification getOSLResourceSpecificationByResourceSpecificationId(String resourceSpecId) { + public JsonNode getOSLResourceSpecificationByResourceSpecificationId(String resourceSpecId) { logger.info("getOSLResourceSpecificationByResourceSpecificationId {}", resourceSpecId); LogicalResourceSpecification spec = aCatalogClient.retrieveResourceSpec(resourceSpecId); + // Filter and get result as JSON string + try { - return spec; + String[] tokens = {"id", "name", "description", "@type", "configurable", "valueType", "isBundle" }; + JsonNode filtered = JsonMassage.filterJsonByTokens( spec, tokens); + return filtered; + } catch (Exception e) { + e.printStackTrace(); + } + + ObjectMapper mapper = new ObjectMapper(); + //return mapper.writeValueAsString(serviceCatalogs); + JsonNode rootNode = mapper.valueToTree( spec ); + return rootNode; } @Tool(description = "Search for OSL service specifications that are published and available for service ordering in all categories") - public List searchOSLServiceSpecifications( List searchStrings) { + public JsonNode searchOSLServiceSpecifications( List searchStrings) { logger.info("searchOSLServiceSpecifications containing words: {}", searchStrings); List spec = aCatalogClient.searchServiceSpecs( searchStrings ); - return spec; + // Filter and get result as JSON string + try { + + String[] tokens = {"id", "name", "description", "@type", "configurable", "valueType", "isBundle" }; + JsonNode filtered = JsonMassage.filterJsonByTokens( spec, tokens); + return filtered; + } catch (Exception e) { + e.printStackTrace(); + } + + ObjectMapper mapper = new ObjectMapper(); + //return mapper.writeValueAsString(serviceCatalogs); + JsonNode rootNode = mapper.valueToTree( spec ); + return rootNode; } @Tool(description = "Create a service order given a Service Specification id, the Start date an end date of the order. " @@ -238,43 +291,74 @@ public class ServiceCatalogTools { + "- the state of the service order" + "- and each order item. Especially for each order item focus to the service and especially: status, characteristics and supporting services." + "- For each supporting service we can retrieve more information by using the service id.") - public String getServiceOrder(String serviceOrderId) { + public JsonNode getServiceOrder(String serviceOrderId) { logger.info("serviceOrderId {} {} {} {}", serviceOrderId); ServiceOrder so = aCatalogClient.retrieveServiceOrder(serviceOrderId); - try { - return ServiceCatalogQClient.toJsonString(so); - } catch (IOException e) { - e.printStackTrace(); - } - return ""; + + // Filter and get result as JSON string + try { + + String[] tokens = {"id", "name", "description", "orderDate", "completionDate", + "expectedCompletionDate", "requestedCompletionDate" , "requestedStartDate" , "startDate" , "category" , "state" , "action" , "value" }; + JsonNode filtered = JsonMassage.filterJsonByTokens( so, tokens); + return filtered; + } catch (Exception e) { + e.printStackTrace(); + } + + ObjectMapper mapper = new ObjectMapper(); + //return mapper.writeValueAsString(serviceCatalogs); + JsonNode rootNode = mapper.valueToTree( so ); + return rootNode; + + } @Tool(description = "Provide details for a service given a Service id. Especially for name, state, characteristics, supporting Services and supporting Resources." + "We can get details for each supporting service using the service id." + "and for each supporting resource using the resource id.") - public String getService(String serviceId) { + public JsonNode getService(String serviceId) { logger.info("getService {} {} {} {}", serviceId); Service s = aCatalogClient.retrieveService(serviceId); + // Filter and get result as JSON string try { - return ServiceCatalogQClient.toJsonString(s); - } catch (IOException e) { + + String[] tokens = {"id", "name", "description", + "category" , "serviceDate" , "serviceType" , "state" , "value" }; + JsonNode filtered = JsonMassage.filterJsonByTokens( s, tokens); + return filtered; + } catch (Exception e) { e.printStackTrace(); } - return ""; + + ObjectMapper mapper = new ObjectMapper(); + //return mapper.writeValueAsString(serviceCatalogs); + JsonNode rootNode = mapper.valueToTree( s ); + return rootNode; + } @Tool(description = "Provide details for a resource given a Resourceid. Especially for name, status, characteristics." + "We can get details for each supporting resource using the resource id.") - public String getResource(String resourceId) { + public JsonNode getResource(String resourceId) { logger.info("getResource {} {} {} {}", resourceId); Resource r = aCatalogClient.retrieveResource ( resourceId); + // Filter and get result as JSON string try { - return ServiceCatalogQClient.toJsonString(r); - } catch (IOException e) { + + String[] tokens = {"id", "name", "description", "startOperatingDate", "endOperatingDate", + "category" , "resourceStatus" , "state" , "value" , "valueType" }; + JsonNode filtered = JsonMassage.filterJsonByTokens( r, tokens); + return filtered; + } catch (Exception e) { e.printStackTrace(); } - return ""; + + ObjectMapper mapper = new ObjectMapper(); + //return mapper.writeValueAsString(serviceCatalogs); + JsonNode rootNode = mapper.valueToTree( r ); + return rootNode; } -- GitLab From bc8476220d0b28b133c0d972494f05b50cfe4cb3 Mon Sep 17 00:00:00 2001 From: Kostis Trantzas Date: Thu, 15 May 2025 11:51:05 +0300 Subject: [PATCH 3/6] Updating pom to depend to main/pom.xml and uploading Dockerfile (fix for #1) --- Dockerfile | 7 +++++++ pom.xml | 54 +++++++++++++++++++++++++++++++++++++----------------- 2 files changed, 44 insertions(+), 17 deletions(-) create mode 100644 Dockerfile diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..792f27a --- /dev/null +++ b/Dockerfile @@ -0,0 +1,7 @@ +FROM ibm-semeru-runtimes:open-17.0.7_7-jdk +MAINTAINER osl.etsi.org + +RUN mkdir /opt/osl + +COPY org.etsi.osl.mcp.server-1.0.0-SNAPSHOT-exec.jar /opt/osl +CMD ["java", "-jar", "/opt/osl/org.etsi.osl.mcp.server-1.0.0-SNAPSHOT-exec.jar"] diff --git a/pom.xml b/pom.xml index 1dc2802..e79ee35 100644 --- a/pom.xml +++ b/pom.xml @@ -2,17 +2,23 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 + + + org.etsi.osl + org.etsi.osl.main + 2025Q2-SNAPSHOT + ../org.etsi.osl.main + + org.etsi.osl org.etsi.osl.mcp.server - 0.0.1-SNAPSHOT + ${org.etsi.osl.mcp.server.version} org.etsi.osl.mcp.server - - org.springframework.boot - spring-boot-starter-parent - 3.4.5 - - + + OpenSlice by ETSI + https://osl.etsi.org + @@ -31,15 +37,37 @@ apache_v2 1.7.0 1.7.0 - 1.1.0-SNAPSHOT - + + + gitlab-maven + https://labs.etsi.org/rep/api/v4/groups/260/-/packages/maven + + + + + gitlab-maven + ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/maven + + + gitlab-maven + ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/maven + + + + + org.springframework.boot + spring-boot-dependencies + ${spring.boot-version} + pom + import + org.springframework.ai spring-ai-bom @@ -68,14 +96,6 @@ - - - - com.jayway.jsonpath - json-path - - - org.springframework.ai spring-ai-starter-mcp-server-webflux -- GitLab From 1e67870fe10ae8eb686a1345552b29238ee956db Mon Sep 17 00:00:00 2001 From: Kostis Trantzas Date: Thu, 15 May 2025 11:55:28 +0300 Subject: [PATCH 4/6] uploading pipeline files (fix for #1) --- .gitlab-ci.yml | 40 ++++++++++++++++++++++++++++++++++++++++ ci_settings.xml | 16 ++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 .gitlab-ci.yml create mode 100644 ci_settings.xml diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..40be386 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,40 @@ +include: + - project: osl/code/org.etsi.osl.main + ref: main + file: + - ci-templates/default.yml + - ci-templates/build.yml + rules: + - if: '$CI_COMMIT_REF_NAME == "main"' + + - project: osl/code/org.etsi.osl.main + ref: develop + file: + - ci-templates/default.yml + - ci-templates/build.yml + rules: + - if: '$CI_COMMIT_REF_NAME == "develop"' + + - project: osl/code/org.etsi.osl.main + ref: $CI_COMMIT_REF_NAME + file: + - ci-templates/default.yml + - ci-templates/build.yml + rules: + - if: '$CI_COMMIT_REF_PROTECTED == "true" && $CI_COMMIT_REF_NAME != "main" && $CI_COMMIT_REF_NAME != "develop"' + + - project: osl/code/org.etsi.osl.main + ref: develop + file: + - ci-templates/default.yml + - ci-templates/build_unprotected.yml + rules: + - if: '$CI_COMMIT_REF_NAME != "main" && $CI_COMMIT_REF_NAME != "develop" && $CI_COMMIT_REF_PROTECTED == "false"' + +maven_build: + extends: .maven_build + +docker_build: + extends: .docker_build + needs: + - maven_build diff --git a/ci_settings.xml b/ci_settings.xml new file mode 100644 index 0000000..69ad06e --- /dev/null +++ b/ci_settings.xml @@ -0,0 +1,16 @@ + + + + gitlab-maven + + + + Job-Token + ${CI_JOB_TOKEN} + + + + + + -- GitLab From b2bea79acbbd0ac426ed96ca68f96c860bb5214f Mon Sep 17 00:00:00 2001 From: Kostis Trantzas Date: Thu, 15 May 2025 12:01:00 +0300 Subject: [PATCH 5/6] Updating Dockerfile (fix for #1) --- Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 792f27a..89cd5a2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,5 +3,6 @@ MAINTAINER osl.etsi.org RUN mkdir /opt/osl -COPY org.etsi.osl.mcp.server-1.0.0-SNAPSHOT-exec.jar /opt/osl +COPY target/org.etsi.osl.mcp.server-1.0.0-SNAPSHOT-exec.jar /opt/osl CMD ["java", "-jar", "/opt/osl/org.etsi.osl.mcp.server-1.0.0-SNAPSHOT-exec.jar"] + -- GitLab From 9c60f6a63d52e3a8c180e9fc04bc112d9afed5be Mon Sep 17 00:00:00 2001 From: trantzas Date: Fri, 18 Jul 2025 11:33:56 +0000 Subject: [PATCH 6/6] Preparing for the Release 2025Q2 --- Dockerfile | 4 ++-- pom.xml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 89cd5a2..0767d57 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,6 +3,6 @@ MAINTAINER osl.etsi.org RUN mkdir /opt/osl -COPY target/org.etsi.osl.mcp.server-1.0.0-SNAPSHOT-exec.jar /opt/osl -CMD ["java", "-jar", "/opt/osl/org.etsi.osl.mcp.server-1.0.0-SNAPSHOT-exec.jar"] +COPY target/org.etsi.osl.mcp.server-1.0.0-exec.jar /opt/osl +CMD ["java", "-jar", "/opt/osl/org.etsi.osl.mcp.server-1.0.0-exec.jar"] diff --git a/pom.xml b/pom.xml index e79ee35..a5a1572 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ org.etsi.osl org.etsi.osl.main - 2025Q2-SNAPSHOT + 2025Q2 ../org.etsi.osl.main -- GitLab