diff --git a/.github/workflows/java-ci.yml b/.github/workflows/java-ci.yml new file mode 100644 index 00000000..03978376 --- /dev/null +++ b/.github/workflows/java-ci.yml @@ -0,0 +1,36 @@ +name: Java CI + +on: + push: + paths: + - 'converters/java/**' + - 'core-spec/**' + - '.github/workflows/java-ci.yml' + pull_request: + paths: + - 'converters/java/**' + - 'core-spec/**' + - '.github/workflows/java-ci.yml' + +jobs: + build-and-test: + name: Build and test + runs-on: ubuntu-latest + defaults: + run: + working-directory: converters/java + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Java + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: '17' + cache: maven + cache-dependency-path: 'converters/java/**/pom.xml' + + - name: Maven verify + run: mvn --batch-mode --no-transfer-progress clean verify diff --git a/.gitignore b/.gitignore index 77ca2ead..eb526ad3 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ **/htmlcov/ **/dist/ **/target/ +**/.idea/ # Go CLI cli/dist/ diff --git a/ROADMAP.md b/ROADMAP.md index 72d564c4..c3f0fec6 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -432,8 +432,8 @@ Broad ecosystem adoption depends on practical tools that let teams validate thei - [Validation Script (validate.py)](validation/validate.py) — validates Ossie YAML against JSON Schema, unique names, references, and SQL syntax - [Snowflake Converter](converters/snowflake/) — Ossie → Snowflake Cortex Analyst YAML converter - [GoodData Converter](converters/gooddata/) — bidirectional Ossie ↔ GoodData LDM converter -- [Salesforce Converter](converters/salesforce/) — Ossie ↔ Salesforce converter -- [Apache Polaris Converter](converters/polaris/) — Ossie → Apache Polaris converter +- [Salesforce Converter](converters/java/salesforce/) — Ossie ↔ Salesforce converter +- [Apache Polaris Converter](converters/java/polaris/) — Ossie → Apache Polaris converter - [OrionBelt Converter](converters/orionbelt/) — bidirectional Ossie ↔ OrionBelt OBML converter **Related Issues:** diff --git a/converters/README.md b/converters/README.md index 8daf5d06..b2f0abde 100644 --- a/converters/README.md +++ b/converters/README.md @@ -23,6 +23,12 @@ An Ossie Converter translates between the Ossie semantic model format and a specific vendor's semantic implementation. This enables teams to author a semantic model once in the Ossie standard and then generate the corresponding vendor-specific representation automatically. +Java converters can reuse the schema-derived POJOs provided by the +[`ossie-common`](java/ossie-common/) Maven module. Its model classes are generated +from the canonical core specification during each Maven build. +All Java modules are grouped in the [`java`](java/) Maven reactor; non-Java +converters keep their existing top-level layout. + ## Hub-and-Spoke Model Ossie converters follow a **hub-and-spoke** architecture: diff --git a/converters/java/README.md b/converters/java/README.md new file mode 100644 index 00000000..6d746d7a --- /dev/null +++ b/converters/java/README.md @@ -0,0 +1,46 @@ + + +# Apache Ossie Java Converters + +This Maven reactor contains the shared schema-generated Java model and all Java +converters: + +- `ossie-common` generates `org.apache.ossie.model` from the canonical Ossie + JSON Schema. +- `polaris` consumes those model classes directly for YAML parsing, generation, + import, and export. +- `salesforce` uses those model classes at its Ossie input and output boundaries + while retaining its dynamic vendor-mapping pipeline internally. + +## Build + +Java 17 or newer and Maven 3.9.12 or newer are required to build the reactor. + +```bash +cd converters/java +mvn clean verify +``` + +Build one converter together with its required modules using `-am`: + +```bash +mvn -pl polaris -am clean package +mvn -pl salesforce -am clean package +``` diff --git a/converters/java/ossie-common/README.md b/converters/java/ossie-common/README.md new file mode 100644 index 00000000..bf5b43f3 --- /dev/null +++ b/converters/java/ossie-common/README.md @@ -0,0 +1,59 @@ + + +# Apache Ossie Common + +This Java module generates Jackson-compatible POJOs from the canonical +[`core-spec/osi-schema.json`](../../../core-spec/osi-schema.json) with +`jsonschema2pojo-maven-plugin`. Generated sources are written under `target/` +and are not checked into source control. + +## Build + +Java 17 or newer and Maven 3.9.12 or newer are required by the code-generation +plugin. The generated artifact targets Java 11 so it can be shared by the +existing Java converters. + +```bash +mvn clean verify +``` + +The generated model classes use the `org.apache.ossie.model` package. Install +the artifact into the local Maven repository for use by another converter: + +```bash +mvn install +``` + +```xml + + org.apache.ossie + ossie-common + 0.1.0-SNAPSHOT + +``` + +## Validation boundary + +The generated classes are transport POJOs, not a JSON Schema validator. +Constraints such as `required`, `const`, and `minItems` must be checked against +the canonical schema before deserialization. In particular, `ai_context` is +generated as `Object` because its schema accepts either a string or a structured +object and `jsonschema2pojo` does not generate a typed `oneOf` union. Consumers +should validate input first and then handle this value as `String` or `Map`. diff --git a/converters/java/ossie-common/pom.xml b/converters/java/ossie-common/pom.xml new file mode 100644 index 00000000..9c576d29 --- /dev/null +++ b/converters/java/ossie-common/pom.xml @@ -0,0 +1,121 @@ + + + + + 4.0.0 + + + org.apache.ossie + ossie-java-converters + 0.1.0-SNAPSHOT + ../pom.xml + + + ossie-common + jar + + Apache Ossie Common + Generated Java model for the Apache Ossie core metadata specification + + + + 11 + UTF-8 + + 2.18.9 + 1.3.3 + 5.11.4 + + + + + com.fasterxml.jackson.core + jackson-annotations + ${jackson.version} + + + com.fasterxml.jackson.core + jackson-databind + ${jackson.version} + test + + + org.junit.jupiter + junit-jupiter + ${junit.version} + test + + + + + + + org.jsonschema2pojo + jsonschema2pojo-maven-plugin + ${jsonschema2pojo.version} + + + generate-osi-model + + generate + + + ${project.basedir}/../../../core-spec + + osi-schema.json + + jsonschema + org.apache.ossie.model + true + 11 + jackson2 + true + true + true + true + + + + + + + + org.apache.rat + apache-rat-plugin + + + verify + + check + + + + + true + + **/target/** + + + + + + diff --git a/converters/java/ossie-common/src/test/java/org/apache/ossie/model/OsiModelTest.java b/converters/java/ossie-common/src/test/java/org/apache/ossie/model/OsiModelTest.java new file mode 100644 index 00000000..595effb0 --- /dev/null +++ b/converters/java/ossie-common/src/test/java/org/apache/ossie/model/OsiModelTest.java @@ -0,0 +1,116 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ +package org.apache.ossie.model; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Map; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Test; + +class OsiModelTest { + + private final ObjectMapper objectMapper = new ObjectMapper(); + + @Test + void deserializesAllModelTypesAndCanonicalPropertyNames() throws Exception { + String json = String.join("\n", + "{", + " \"version\": \"0.2.0.dev0\",", + " \"semantic_model\": [{", + " \"name\": \"sales\",", + " \"ai_context\": {", + " \"instructions\": \"Use for revenue analysis\",", + " \"synonyms\": [\"orders\"]", + " },", + " \"datasets\": [{", + " \"name\": \"orders\",", + " \"source\": \"analytics.public.orders\",", + " \"primary_key\": [\"order_id\", \"line_id\"],", + " \"unique_keys\": [[\"external_id\", \"line_id\"]],", + " \"fields\": [{", + " \"name\": \"order_id\",", + " \"expression\": {\"dialects\": [{", + " \"dialect\": \"ANSI_SQL\",", + " \"expression\": \"order_id\"", + " }]},", + " \"dimension\": {\"is_time\": false},", + " \"ai_context\": \"order identifier\",", + " \"custom_extensions\": [{", + " \"vendor_name\": \"COMMON\",", + " \"data\": \"{}\"", + " }]", + " }]", + " }, {", + " \"name\": \"customers\",", + " \"source\": \"analytics.public.customers\"", + " }],", + " \"relationships\": [{", + " \"name\": \"orders_to_customers\",", + " \"from\": \"orders\",", + " \"to\": \"customers\",", + " \"from_columns\": [\"customer_id\", \"tenant_id\"],", + " \"to_columns\": [\"customer_id\", \"tenant_id\"]", + " }],", + " \"metrics\": [{", + " \"name\": \"revenue\",", + " \"expression\": {\"dialects\": [{", + " \"dialect\": \"SNOWFLAKE\",", + " \"expression\": \"SUM(amount)\"", + " }]}", + " }],", + " \"custom_extensions\": [{", + " \"vendor_name\": \"SNOWFLAKE\",", + " \"data\": \"{\\\"warehouse\\\":\\\"ANALYTICS_WH\\\"}\"", + " }]", + " }]", + "}"); + + OsiModel schema = objectMapper.readValue(json, OsiModel.class); + + assertEquals("0.2.0.dev0", schema.getVersion()); + assertEquals("sales", schema.getSemanticModel().get(0).getName()); + assertEquals("order_id", schema.getSemanticModel().get(0) + .getDatasets().get(0).getPrimaryKey().get(0)); + assertEquals("line_id", schema.getSemanticModel().get(0) + .getDatasets().get(0).getUniqueKeys().get(0).get(1)); + assertEquals(DialectExpression.Dialect.ANSI_SQL, schema.getSemanticModel().get(0) + .getDatasets().get(0).getFields().get(0) + .getExpression().getDialects().get(0).getDialect()); + assertEquals("order identifier", schema.getSemanticModel().get(0) + .getDatasets().get(0).getFields().get(0).getAiContext()); + assertEquals("tenant_id", schema.getSemanticModel().get(0) + .getRelationships().get(0).getToColumns().get(1)); + assertEquals(DialectExpression.Dialect.SNOWFLAKE, schema.getSemanticModel().get(0) + .getMetrics().get(0).getExpression().getDialects().get(0).getDialect()); + assertEquals("SNOWFLAKE", schema.getSemanticModel().get(0) + .getCustomExtensions().get(0).getVendorName()); + + Map aiContext = assertInstanceOf( + Map.class, schema.getSemanticModel().get(0).getAiContext()); + assertEquals("Use for revenue analysis", aiContext.get("instructions")); + + String serialized = objectMapper.writeValueAsString(schema); + assertTrue(serialized.contains("\"semantic_model\"")); + assertTrue(serialized.contains("\"primary_key\"")); + } +} diff --git a/converters/polaris/README.md b/converters/java/polaris/README.md similarity index 87% rename from converters/polaris/README.md rename to converters/java/polaris/README.md index d2f252f6..7bf6e936 100644 --- a/converters/polaris/README.md +++ b/converters/java/polaris/README.md @@ -19,17 +19,20 @@ # Apache Ossie Polaris Converter -A two-way converter between [Ossie semantic models](../../core-spec/spec.md) and [Apache Polaris](https://polaris.apache.org/) catalogs. +A two-way converter between [Ossie semantic models](../../../core-spec/spec.md) and [Apache Polaris](https://polaris.apache.org/) catalogs. Apache Polaris is an open-source catalog for Apache Iceberg. This converter communicates with Polaris via the Iceberg REST Catalog API to import catalog metadata into Ossie format and export Ossie models back into Polaris. ## Building ```bash -mvn clean package +cd converters/java +mvn -pl polaris -am clean package ``` -Requires Java 11+. +The generated converter bytecode targets Java 11. Building the reactor requires +Java 17+ and Maven 3.9.12+ because `ossie-common` generates the model classes. +The resulting Polaris jar is self-contained and includes `ossie-common`. ## Usage @@ -38,7 +41,7 @@ Requires Java 11+. Reads all namespaces and tables from a Polaris catalog and generates an Ossie YAML file. ```bash -java -jar target/ossie-polaris-converter-0.1.0-SNAPSHOT.jar import \ +java -jar polaris/target/ossie-polaris-converter-0.1.0-SNAPSHOT-all.jar import \ --url http://localhost:8181 \ --catalog my_catalog \ --client-id \ @@ -53,7 +56,7 @@ Each Polaris namespace becomes a separate Ossie semantic model containing datase Reads an Ossie YAML file and creates namespaces and Iceberg tables in a Polaris catalog. ```bash -java -jar target/ossie-polaris-converter-0.1.0-SNAPSHOT.jar export \ +java -jar polaris/target/ossie-polaris-converter-0.1.0-SNAPSHOT-all.jar export \ --url http://localhost:8181 \ --catalog my_catalog \ --client-id \ @@ -135,10 +138,10 @@ Since Ossie fields are expression-based and don't carry explicit types, the expo ## Dependencies -- [SnakeYAML 2.2](https://bitbucket.org/snakeyaml/snakeyaml/) — Ossie YAML parsing and generation -- [Jackson Databind 2.17](https://github.com/FasterXML/jackson-databind) — JSON handling for Polaris REST API +- `ossie-common` — schema-generated Ossie Java model +- Jackson 2.18 — Ossie YAML binding and JSON handling for the Polaris REST API - [JUnit 5](https://junit.org/junit5/) — testing ## License -Apache License 2.0 — see [LICENSE](../../LICENSE). +Apache License 2.0 — see [LICENSE](../../../LICENSE). diff --git a/converters/polaris/pom.xml b/converters/java/polaris/pom.xml similarity index 58% rename from converters/polaris/pom.xml rename to converters/java/polaris/pom.xml index df9def73..b86654ce 100644 --- a/converters/polaris/pom.xml +++ b/converters/java/polaris/pom.xml @@ -24,15 +24,13 @@ 4.0.0 - org.apache - apache - 39 - + org.apache.ossie + ossie-java-converters + 0.1.0-SNAPSHOT + ../pom.xml - org.apache.ossie ossie-polaris-converter - 0.1.0-SNAPSHOT jar Apache Ossie Polaris Converter @@ -43,26 +41,29 @@ 11 11 UTF-8 - 2.2 2.18.9 5.10.2 + 3.6.0 - - org.yaml - snakeyaml - ${snakeyaml.version} + org.apache.ossie + ossie-common - com.fasterxml.jackson.core jackson-databind ${jackson.version} + + com.fasterxml.jackson.dataformat + jackson-dataformat-yaml + ${jackson.version} + + org.junit.jupiter @@ -76,15 +77,37 @@ org.apache.maven.plugins - maven-jar-plugin - 3.3.0 - - - - org.apache.ossie.converter.polaris.OsiPolarisConverter - - - + maven-shade-plugin + ${maven-shade-plugin.version} + + + package + + shade + + + false + true + all + + + org.apache.ossie.converter.polaris.OsiPolarisConverter + + + + + + *:* + + META-INF/*.SF + META-INF/*.DSA + META-INF/*.RSA + + + + + + diff --git a/converters/java/polaris/src/main/java/org/apache/ossie/converter/polaris/OsiModelParser.java b/converters/java/polaris/src/main/java/org/apache/ossie/converter/polaris/OsiModelParser.java new file mode 100644 index 00000000..d69ca60e --- /dev/null +++ b/converters/java/polaris/src/main/java/org/apache/ossie/converter/polaris/OsiModelParser.java @@ -0,0 +1,57 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +package org.apache.ossie.converter.polaris; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +import org.apache.ossie.model.OsiModel; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; + +/** + * Parses an Ossie YAML document into the schema-generated {@link OsiModel} model. + */ +public class OsiModelParser { + + private final ObjectMapper yamlMapper = new ObjectMapper(new YAMLFactory()); + + /** + * Parse an Ossie YAML file from the given path. + */ + public OsiModel parse(Path yamlPath) throws IOException { + try (InputStream inputStream = Files.newInputStream(yamlPath)) { + return yamlMapper.readValue(inputStream, OsiModel.class); + } + } + + /** + * Parse an Ossie YAML file from an input stream. + */ + public OsiModel parse(InputStream inputStream) { + try { + return yamlMapper.readValue(inputStream, OsiModel.class); + } catch (IOException e) { + throw new IllegalArgumentException("Invalid Ossie YAML", e); + } + } +} diff --git a/converters/polaris/src/main/java/org/apache/ossie/converter/polaris/OsiPolarisConverter.java b/converters/java/polaris/src/main/java/org/apache/ossie/converter/polaris/OsiPolarisConverter.java similarity index 96% rename from converters/polaris/src/main/java/org/apache/ossie/converter/polaris/OsiPolarisConverter.java rename to converters/java/polaris/src/main/java/org/apache/ossie/converter/polaris/OsiPolarisConverter.java index c9eb50d7..929ed178 100644 --- a/converters/polaris/src/main/java/org/apache/ossie/converter/polaris/OsiPolarisConverter.java +++ b/converters/java/polaris/src/main/java/org/apache/ossie/converter/polaris/OsiPolarisConverter.java @@ -19,7 +19,7 @@ package org.apache.ossie.converter.polaris; -import org.apache.ossie.converter.polaris.model.OsiModel; +import org.apache.ossie.model.OsiModel; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -119,7 +119,7 @@ private static void doImport(PolarisClient client, String outputFile) throws Exc PolarisImporter importer = new PolarisImporter(client); OsiModel model = importer.importCatalog(); - if (model.getSemanticModels().isEmpty()) { + if (model.getSemanticModel().isEmpty()) { System.err.println("Warning: no tables found in catalog."); } @@ -143,7 +143,7 @@ private static void doExport(PolarisClient client, String inputFile) throws Exce OsiModelParser parser = new OsiModelParser(); OsiModel model = parser.parse(Paths.get(inputFile)); - if (model.getSemanticModels().isEmpty()) { + if (model.getSemanticModel().isEmpty()) { System.err.println("Error: no semantic_model found in " + inputFile); System.exit(1); } @@ -151,7 +151,7 @@ private static void doExport(PolarisClient client, String inputFile) throws Exce PolarisExporter exporter = new PolarisExporter(client); exporter.exportModel(model); - System.out.println("Exported " + model.getSemanticModels().size() + System.out.println("Exported " + model.getSemanticModel().size() + " semantic model(s) to Polaris catalog."); } diff --git a/converters/java/polaris/src/main/java/org/apache/ossie/converter/polaris/OsiYamlGenerator.java b/converters/java/polaris/src/main/java/org/apache/ossie/converter/polaris/OsiYamlGenerator.java new file mode 100644 index 00000000..fb6c81cd --- /dev/null +++ b/converters/java/polaris/src/main/java/org/apache/ossie/converter/polaris/OsiYamlGenerator.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +package org.apache.ossie.converter.polaris; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator; +import org.apache.ossie.model.OsiModel; + +/** + * Generates Ossie YAML from the schema-generated {@link OsiModel} model. + */ +public class OsiYamlGenerator { + + private final ObjectMapper yamlMapper; + + public OsiYamlGenerator() { + YAMLFactory yamlFactory = new YAMLFactory() + .disable(YAMLGenerator.Feature.WRITE_DOC_START_MARKER) + .enable(YAMLGenerator.Feature.MINIMIZE_QUOTES) + .enable(YAMLGenerator.Feature.LITERAL_BLOCK_STYLE); + this.yamlMapper = new ObjectMapper(yamlFactory); + } + + /** + * Generate an Ossie YAML string from a model. + */ + public String generate(OsiModel model) { + try { + return yamlMapper.writeValueAsString(model); + } catch (JsonProcessingException e) { + throw new IllegalArgumentException("Unable to generate Ossie YAML", e); + } + } +} diff --git a/converters/polaris/src/main/java/org/apache/ossie/converter/polaris/PolarisClient.java b/converters/java/polaris/src/main/java/org/apache/ossie/converter/polaris/PolarisClient.java similarity index 100% rename from converters/polaris/src/main/java/org/apache/ossie/converter/polaris/PolarisClient.java rename to converters/java/polaris/src/main/java/org/apache/ossie/converter/polaris/PolarisClient.java diff --git a/converters/polaris/src/main/java/org/apache/ossie/converter/polaris/PolarisExporter.java b/converters/java/polaris/src/main/java/org/apache/ossie/converter/polaris/PolarisExporter.java similarity index 95% rename from converters/polaris/src/main/java/org/apache/ossie/converter/polaris/PolarisExporter.java rename to converters/java/polaris/src/main/java/org/apache/ossie/converter/polaris/PolarisExporter.java index a864cbd1..975b6dfe 100644 --- a/converters/polaris/src/main/java/org/apache/ossie/converter/polaris/PolarisExporter.java +++ b/converters/java/polaris/src/main/java/org/apache/ossie/converter/polaris/PolarisExporter.java @@ -22,11 +22,12 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; -import org.apache.ossie.converter.polaris.model.OsiModel; -import org.apache.ossie.converter.polaris.model.OsiModel.*; +import org.apache.ossie.model.Dataset; +import org.apache.ossie.model.Field; +import org.apache.ossie.model.OsiModel; +import org.apache.ossie.model.SemanticModel; import java.io.IOException; -import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -53,7 +54,7 @@ public PolarisExporter(PolarisClient client) { * Each semantic model becomes a namespace, and each dataset becomes a table. */ public void exportModel(OsiModel model) throws IOException, InterruptedException { - for (SemanticModel sm : model.getSemanticModels()) { + for (SemanticModel sm : model.getSemanticModel()) { exportSemanticModel(sm); } } @@ -161,7 +162,8 @@ private String inferIcebergType(Field field) { } // Time dimension -> timestamp - if (field.isTime()) { + if (field.getDimension() != null + && Boolean.TRUE.equals(field.getDimension().getIsTime())) { return "timestamptz"; } diff --git a/converters/polaris/src/main/java/org/apache/ossie/converter/polaris/PolarisImporter.java b/converters/java/polaris/src/main/java/org/apache/ossie/converter/polaris/PolarisImporter.java similarity index 89% rename from converters/polaris/src/main/java/org/apache/ossie/converter/polaris/PolarisImporter.java rename to converters/java/polaris/src/main/java/org/apache/ossie/converter/polaris/PolarisImporter.java index ef4841cc..f97d8cc3 100644 --- a/converters/polaris/src/main/java/org/apache/ossie/converter/polaris/PolarisImporter.java +++ b/converters/java/polaris/src/main/java/org/apache/ossie/converter/polaris/PolarisImporter.java @@ -20,8 +20,14 @@ package org.apache.ossie.converter.polaris; import com.fasterxml.jackson.databind.JsonNode; -import org.apache.ossie.converter.polaris.model.OsiModel; -import org.apache.ossie.converter.polaris.model.OsiModel.*; +import org.apache.ossie.model.CustomExtension; +import org.apache.ossie.model.Dataset; +import org.apache.ossie.model.DialectExpression; +import org.apache.ossie.model.Dimension; +import org.apache.ossie.model.Expression; +import org.apache.ossie.model.Field; +import org.apache.ossie.model.OsiModel; +import org.apache.ossie.model.SemanticModel; import java.io.IOException; import java.util.ArrayList; @@ -56,7 +62,7 @@ public OsiModel importCatalog() throws IOException, InterruptedException { for (List namespace : namespaces) { SemanticModel sm = importNamespace(namespace); if (sm != null && !sm.getDatasets().isEmpty()) { - model.getSemanticModels().add(sm); + model.getSemanticModel().add(sm); } } @@ -90,7 +96,7 @@ public SemanticModel importNamespace(List namespace) throws IOException, /** * Map an Iceberg table's metadata to an Ossie dataset. */ - private Dataset mapTableToDataset(List namespace, String tableName, JsonNode tableMetadata) { + Dataset mapTableToDataset(List namespace, String tableName, JsonNode tableMetadata) { Dataset dataset = new Dataset(); dataset.setName(tableName); @@ -121,7 +127,9 @@ private Dataset mapTableToDataset(List namespace, String tableName, Json // Store Polaris-specific table properties as custom extension JsonNode properties = metadata.get("properties"); if (properties != null && properties.isObject() && properties.size() > 0) { - CustomExtension ext = new CustomExtension("COMMON", properties.toString()); + CustomExtension ext = new CustomExtension(); + ext.setVendorName("COMMON"); + ext.setData(properties.toString()); dataset.setCustomExtensions(Collections.singletonList(ext)); } } @@ -183,12 +191,18 @@ private Field mapColumnToField(JsonNode column) { field.setName(name); // The expression is just the column name (direct mapping) - DialectExpression expr = new DialectExpression("ANSI_SQL", name); - field.setExpressions(Collections.singletonList(expr)); + DialectExpression expr = new DialectExpression(); + expr.setDialect(DialectExpression.Dialect.ANSI_SQL); + expr.setExpression(name); + Expression expression = new Expression(); + expression.setDialects(Collections.singletonList(expr)); + field.setExpression(expression); // Detect time-based dimensions from Iceberg types if (isTemporalType(icebergType)) { - field.setTime(true); + Dimension dimension = new Dimension(); + dimension.setIsTime(true); + field.setDimension(dimension); } // Add type information as description diff --git a/converters/polaris/src/test/java/org/apache/ossie/converter/polaris/OsiPolarisConverterTest.java b/converters/java/polaris/src/test/java/org/apache/ossie/converter/polaris/OsiPolarisConverterTest.java similarity index 83% rename from converters/polaris/src/test/java/org/apache/ossie/converter/polaris/OsiPolarisConverterTest.java rename to converters/java/polaris/src/test/java/org/apache/ossie/converter/polaris/OsiPolarisConverterTest.java index 33eba4b9..ae28b820 100644 --- a/converters/polaris/src/test/java/org/apache/ossie/converter/polaris/OsiPolarisConverterTest.java +++ b/converters/java/polaris/src/test/java/org/apache/ossie/converter/polaris/OsiPolarisConverterTest.java @@ -22,12 +22,17 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.Test; -import org.apache.ossie.converter.polaris.model.OsiModel; -import org.apache.ossie.converter.polaris.model.OsiModel.*; +import org.apache.ossie.model.Dataset; +import org.apache.ossie.model.DialectExpression; +import org.apache.ossie.model.Dimension; +import org.apache.ossie.model.Expression; +import org.apache.ossie.model.Field; +import org.apache.ossie.model.OsiModel; +import org.apache.ossie.model.Relationship; +import org.apache.ossie.model.SemanticModel; import java.io.ByteArrayInputStream; import java.nio.charset.StandardCharsets; -import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -41,6 +46,7 @@ class OsiPolarisConverterTest { + "semantic_model:\n" + " - name: test_model\n" + " description: A test model\n" + + " ai_context: sales analysis\n" + " datasets:\n" + " - name: orders\n" + " source: catalog.ns.orders\n" @@ -102,10 +108,11 @@ void testParseMinimalModel() { new ByteArrayInputStream(MINIMAL_MODEL.getBytes(StandardCharsets.UTF_8))); assertEquals("0.2.0.dev0", model.getVersion()); - assertEquals(1, model.getSemanticModels().size()); + assertEquals(1, model.getSemanticModel().size()); - SemanticModel sm = model.getSemanticModels().get(0); + SemanticModel sm = model.getSemanticModel().get(0); assertEquals("test_model", sm.getName()); + assertEquals("sales analysis", sm.getAiContext()); assertEquals(2, sm.getDatasets().size()); assertEquals(1, sm.getRelationships().size()); assertEquals(1, sm.getMetrics().size()); @@ -117,7 +124,7 @@ void testParseDatasetFields() { OsiModel model = parser.parse( new ByteArrayInputStream(MINIMAL_MODEL.getBytes(StandardCharsets.UTF_8))); - Dataset orders = model.getSemanticModels().get(0).getDatasets().get(0); + Dataset orders = model.getSemanticModel().get(0).getDatasets().get(0); assertEquals("orders", orders.getName()); assertEquals("catalog.ns.orders", orders.getSource()); assertEquals(3, orders.getFields().size()); @@ -125,7 +132,8 @@ void testParseDatasetFields() { Field computed = orders.getFields().get(1); assertEquals("total_amount", computed.getName()); - assertEquals("quantity * unit_price", computed.getExpressions().get(0).getExpression()); + assertEquals("quantity * unit_price", + computed.getExpression().getDialects().get(0).getExpression()); } @Test @@ -134,10 +142,10 @@ void testParseTimeDimension() { OsiModel model = parser.parse( new ByteArrayInputStream(MINIMAL_MODEL.getBytes(StandardCharsets.UTF_8))); - Dataset orders = model.getSemanticModels().get(0).getDatasets().get(0); + Dataset orders = model.getSemanticModel().get(0).getDatasets().get(0); Field orderDate = orders.getFields().get(2); assertEquals("order_date", orderDate.getName()); - assertTrue(orderDate.isTime()); + assertTrue(orderDate.getDimension().getIsTime()); } @Test @@ -146,7 +154,7 @@ void testParseRelationship() { OsiModel model = parser.parse( new ByteArrayInputStream(MINIMAL_MODEL.getBytes(StandardCharsets.UTF_8))); - Relationship rel = model.getSemanticModels().get(0).getRelationships().get(0); + Relationship rel = model.getSemanticModel().get(0).getRelationships().get(0); assertEquals("orders_to_customer", rel.getName()); assertEquals("orders", rel.getFrom()); assertEquals("customer", rel.getTo()); @@ -166,24 +174,25 @@ void testYamlGenerationRoundTrip() { String yaml = generator.generate(model); // Verify key elements are present in generated YAML - assertTrue(yaml.contains("version: \"0.2.0.dev0\"")); + assertTrue(yaml.contains("version:")); assertTrue(yaml.contains("name: test_model")); assertTrue(yaml.contains("name: orders")); assertTrue(yaml.contains("source: catalog.ns.orders")); - assertTrue(yaml.contains("primary_key: [order_id]")); + assertTrue(yaml.contains("primary_key:")); assertTrue(yaml.contains("name: total_amount")); assertTrue(yaml.contains("is_time: true")); assertTrue(yaml.contains("name: orders_to_customer")); - assertTrue(yaml.contains("from_columns: [customer_id]")); + assertTrue(yaml.contains("from_columns:")); assertTrue(yaml.contains("name: total_revenue")); assertTrue(yaml.contains("SUM(orders.total_amount)")); // Re-parse the generated YAML to verify it's valid OsiModel reparsed = parser.parse( new ByteArrayInputStream(yaml.getBytes(StandardCharsets.UTF_8))); - assertEquals(1, reparsed.getSemanticModels().size()); - assertEquals("test_model", reparsed.getSemanticModels().get(0).getName()); - assertEquals(2, reparsed.getSemanticModels().get(0).getDatasets().size()); + assertEquals(1, reparsed.getSemanticModel().size()); + assertEquals("test_model", reparsed.getSemanticModel().get(0).getName()); + assertEquals("sales analysis", reparsed.getSemanticModel().get(0).getAiContext()); + assertEquals(2, reparsed.getSemanticModel().get(0).getDatasets().size()); } // -- Exporter tests (Iceberg schema generation) ------------------------- @@ -197,7 +206,7 @@ void testExporterBuildCreateTableRequest() throws Exception { PolarisClient client = new PolarisClient("http://localhost:8181", "test_catalog"); PolarisExporter exporter = new PolarisExporter(client); - Dataset orders = model.getSemanticModels().get(0).getDatasets().get(0); + Dataset orders = model.getSemanticModel().get(0).getDatasets().get(0); String json = exporter.buildCreateTableRequest(orders); ObjectMapper mapper = new ObjectMapper(); @@ -277,7 +286,6 @@ void testImporterMapTableToDataset() throws Exception { PolarisClient client = new PolarisClient("http://localhost:8181", "test_catalog"); PolarisImporter importer = new PolarisImporter(client); - // We test the mapping logic by creating a model and verifying YAML output OsiModel model = new OsiModel(); model.setVersion("0.2.0.dev0"); @@ -285,32 +293,17 @@ void testImporterMapTableToDataset() throws Exception { sm.setName("test_ns"); sm.setDescription("Test namespace"); - // Manually build what the importer would produce - Dataset ds = new Dataset(); - ds.setName("test_table"); - ds.setSource("test_catalog.test_ns.test_table"); - ds.setPrimaryKey(Collections.singletonList("id")); + Dataset ds = importer.mapTableToDataset( + Collections.singletonList("test_ns"), "test_table", tableMetadata); + assertEquals(5, ds.getFields().size()); + assertEquals(DialectExpression.Dialect.ANSI_SQL, + ds.getFields().get(0).getExpression().getDialects().get(0).getDialect()); + assertTrue(ds.getFields().get(2).getDimension().getIsTime()); + assertEquals("test_user", new ObjectMapper().readTree( + ds.getCustomExtensions().get(0).getData()).get("owner").asText()); - // Map fields from the metadata - JsonNode schema = tableMetadata.get("metadata").get("schemas").get(0); - JsonNode fields = schema.get("fields"); - - List osiFields = new java.util.ArrayList<>(); - for (JsonNode col : fields) { - Field f = new Field(); - f.setName(col.get("name").asText()); - f.setExpressions(Collections.singletonList( - new DialectExpression("ANSI_SQL", col.get("name").asText()))); - - String type = col.get("type").isTextual() ? col.get("type").asText() : col.get("type").get("type").asText(); - if ("timestamptz".equals(type) || "timestamp".equals(type) || "date".equals(type)) { - f.setTime(true); - } - osiFields.add(f); - } - ds.setFields(osiFields); sm.setDatasets(Collections.singletonList(ds)); - model.setSemanticModels(Collections.singletonList(sm)); + model.setSemanticModel(Collections.singletonList(sm)); // Generate YAML and verify OsiYamlGenerator generator = new OsiYamlGenerator(); @@ -318,7 +311,7 @@ void testImporterMapTableToDataset() throws Exception { assertTrue(yaml.contains("name: test_table")); assertTrue(yaml.contains("source: test_catalog.test_ns.test_table")); - assertTrue(yaml.contains("primary_key: [id]")); + assertTrue(yaml.contains("primary_key:")); assertTrue(yaml.contains("name: id")); assertTrue(yaml.contains("name: name")); assertTrue(yaml.contains("name: created_at")); @@ -353,7 +346,7 @@ void testTypeInference() throws Exception { ds.setFields(fields); sm.setDatasets(Collections.singletonList(ds)); - model.setSemanticModels(Collections.singletonList(sm)); + model.setSemanticModel(Collections.singletonList(sm)); PolarisClient client = new PolarisClient("http://localhost:8181", "cat"); PolarisExporter exporter = new PolarisExporter(client); @@ -381,7 +374,7 @@ void testEmptyModel() { new ByteArrayInputStream(emptyYaml.getBytes(StandardCharsets.UTF_8))); assertEquals("0.2.0.dev0", model.getVersion()); - assertTrue(model.getSemanticModels().isEmpty()); + assertTrue(model.getSemanticModel().isEmpty()); } // -- Helpers ------------------------------------------------------------ @@ -389,8 +382,22 @@ void testEmptyModel() { private Field makeField(String name, boolean isTime) { Field f = new Field(); f.setName(name); - f.setExpressions(Collections.singletonList(new DialectExpression("ANSI_SQL", name))); - f.setTime(isTime); + f.setExpression(makeExpression(name)); + if (isTime) { + Dimension dimension = new Dimension(); + dimension.setIsTime(true); + f.setDimension(dimension); + } return f; } + + private Expression makeExpression(String expressionValue) { + DialectExpression dialectExpression = new DialectExpression(); + dialectExpression.setDialect(DialectExpression.Dialect.ANSI_SQL); + dialectExpression.setExpression(expressionValue); + + Expression expression = new Expression(); + expression.setDialects(Collections.singletonList(dialectExpression)); + return expression; + } } diff --git a/converters/java/pom.xml b/converters/java/pom.xml new file mode 100644 index 00000000..7e0f6edc --- /dev/null +++ b/converters/java/pom.xml @@ -0,0 +1,56 @@ + + + + + 4.0.0 + + + org.apache + apache + 39 + + + + org.apache.ossie + ossie-java-converters + 0.1.0-SNAPSHOT + pom + + Apache Ossie Java Converters + Reactor for the shared Ossie Java model and Java converters + + + ossie-common + polaris + salesforce + + + + + + org.apache.ossie + ossie-common + ${project.version} + + + + diff --git a/converters/salesforce/README.md b/converters/java/salesforce/README.md similarity index 75% rename from converters/salesforce/README.md rename to converters/java/salesforce/README.md index 616c8a52..396aeed9 100644 --- a/converters/salesforce/README.md +++ b/converters/java/salesforce/README.md @@ -19,40 +19,41 @@ # Apache Ossie Salesforce Converter -A two-way converter between [Ossie semantic models](../../core-spec/spec.md) and [Salesforce Semantic Model](https://developer.salesforce.com/docs/data/semantic-layer/guide/salesforce-semantic-model-schema.html). +A two-way converter between [Ossie semantic models](../../../core-spec/spec.md) and [Salesforce Semantic Model](https://developer.salesforce.com/docs/data/semantic-layer/guide/salesforce-semantic-model-schema.html). -This converter provides lossless, bidirectional conversion between Ossie YAML format and Salesforce Semantic Model JSON format. +This converter provides bidirectional conversion between Ossie YAML format and +Salesforce Semantic Model JSON format, preserving unmapped vendor properties in +`custom_extensions` where supported. ## Requirements - **Java 17+** -- **Maven 3.6+** — required to build the jar +- **Maven 3.9.12+** — required to build the Java reactor ## Building Build the executable jar from source: ```bash -mvn clean package +cd converters/java +mvn -pl salesforce -am clean package ``` -This produces a self-contained executable jar at `target/ossie-salesforce-converter-0.1.0-SNAPSHOT.jar` with all dependencies bundled. +This produces a self-contained executable jar at +`salesforce/target/ossie-salesforce-converter-0.1.0-SNAPSHOT-all.jar` with all +dependencies bundled. ## Setup -Both schemas must be obtained and placed under `src/main/resources/schemas/` before building, so they get bundled into the jar. +The canonical Apache Ossie schema is copied automatically from `core-spec` at +build time. The Salesforce schema must be obtained separately and placed under +`salesforce/src/main/resources/schemas/` so it can be bundled into the jar. ### Salesforce Semantic Model Schema 1. Visit the [Salesforce Semantic Model Schema documentation](https://developer.salesforce.com/docs/data/semantic-layer/guide/salesforce-semantic-model-schema.html) 2. Copy the JSON schema content from the page -3. Save it to `src/main/resources/schemas/salesforce-semantic-model-schema.json` - -### Apache Ossie Schema - -1. Visit the [Ossie schema on GitHub](https://github.com/apache/ossie/blob/main/core-spec/osi-schema.json) -2. Copy the raw JSON contents -3. Save it to `src/main/resources/schemas/osi-schema.json` +3. Save it to `salesforce/src/main/resources/schemas/salesforce-semantic-model-schema.json` ## Usage @@ -63,16 +64,16 @@ Both schemas must be obtained and placed under `src/main/resources/schemas/` bef Convert a Salesforce Semantic Model JSON file to Ossie YAML format: ```bash -java -jar target/ossie-salesforce-converter-0.1.0-SNAPSHOT.jar toOSI input.json +java -jar salesforce/target/ossie-salesforce-converter-0.1.0-SNAPSHOT-all.jar toOSI input.json # Output: Customer_Orders_Model.yaml (named after model's 'name' field) # Created in the same directory as the input file ``` Example: ```bash -java -jar target/ossie-salesforce-converter-0.1.0-SNAPSHOT.jar toOSI \ - src/test/resources/examples/salesforceToOsi.json -# Output: src/test/resources/examples/Customer_Orders_Model.yaml +java -jar salesforce/target/ossie-salesforce-converter-0.1.0-SNAPSHOT-all.jar toOSI \ + salesforce/src/test/resources/examples/salesforceToOsi.json +# Output: salesforce/src/test/resources/examples/Customer_Orders_Model.yaml ``` #### Export (Apache Ossie → Salesforce) @@ -80,16 +81,16 @@ java -jar target/ossie-salesforce-converter-0.1.0-SNAPSHOT.jar toOSI \ Convert an Ossie YAML file to Salesforce Semantic Model JSON format: ```bash -java -jar target/ossie-salesforce-converter-0.1.0-SNAPSHOT.jar toSF input.yaml +java -jar salesforce/target/ossie-salesforce-converter-0.1.0-SNAPSHOT-all.jar toSF input.yaml # Output: Customer_Orders_Model.json (named after model's 'apiName' field) # Created in the same directory as the input file ``` Example: ```bash -java -jar target/ossie-salesforce-converter-0.1.0-SNAPSHOT.jar toSF \ - src/test/resources/examples/osiToSalesforce.yaml -# Output: src/test/resources/examples/Customer_Orders_Model.json +java -jar salesforce/target/ossie-salesforce-converter-0.1.0-SNAPSHOT-all.jar toSF \ + salesforce/src/test/resources/examples/osiToSalesforce.yaml +# Output: salesforce/src/test/resources/examples/Customer_Orders_Model.json ``` ### Programmatic API @@ -128,10 +129,13 @@ osiToSf.convert(Paths.get("input/model.yaml"), Paths.get("output/")); ### Features - **Schema-validated** - Input is validated against JSON Schema before processing -- **Lossless conversion** - Unmapped properties are preserved in `custom_extensions` -- **Bidirectional** - Full bi-directional conversion without data loss +- **Extension preservation** - Unmapped Salesforce properties are preserved in `custom_extensions` +- **Bidirectional** - Supports both Ossie-to-Salesforce and Salesforce-to-Ossie flows - **Supports Ossie Specification v0.2.0.dev0** +Ossie metrics currently have no Salesforce export mapping and are omitted in +the Ossie-to-Salesforce direction. + ## Mapping Reference ### Import (Salesforce → Apache Ossie) @@ -163,7 +167,7 @@ osiToSf.convert(Paths.get("input/model.yaml"), Paths.get("output/")); | `expression.dialects[].expression` | `dataObjectFieldName` | | `relationships[]` | `semanticRelationships[]` | | `from_columns` + `to_columns` | `criteria[]` | -| `metrics[]` | `semanticCalculatedMeasurements[]` | +| `metrics[]` | Not currently mapped (omitted) | | `ai_context` | `businessPreferences` | | `custom_extensions` (vendor: `SALESFORCE`) | Restored properties | @@ -224,17 +228,17 @@ Fields are automatically classified as dimensions or measurements based on expre **GenericMappingEngine** — Path-based property mapping using `mappings.yaml` configuration -**CustomExtensionHandler** — Preserves unmapped Salesforce properties in Ossie's `custom_extensions` for lossless bi-directional conversion +**CustomExtensionHandler** — Preserves supported unmapped Salesforce properties in Ossie's `custom_extensions` for round-trip conversion **SchemaValidator** — Validates input against JSON schemas before conversion ## Examples See the test suite for sample models demonstrating various features: -- `src/test/resources/examples/osiToSalesforce.yaml` - Ossie model example -- `src/test/java/org/apache/ossie/OsiToSalesforceConverterTest.java` - Ossie to Salesforce conversion tests -- `src/test/java/org/apache/ossie/SalesforceToOsiConverterTest.java` - Salesforce to Ossie conversion tests +- `salesforce/src/test/resources/examples/osiToSalesforce.yaml` - Ossie model example +- `salesforce/src/test/java/org/apache/ossie/OsiToSalesforceConverterTest.java` - Ossie to Salesforce conversion tests +- `salesforce/src/test/java/org/apache/ossie/SalesforceToOsiConverterTest.java` - Salesforce to Ossie conversion tests ## License -Apache License 2.0 — see [LICENSE](../../LICENSE). +Apache License 2.0 — see [LICENSE](../../../LICENSE). diff --git a/converters/salesforce/pom.xml b/converters/java/salesforce/pom.xml similarity index 92% rename from converters/salesforce/pom.xml rename to converters/java/salesforce/pom.xml index 89c5b03f..e0c8fdca 100644 --- a/converters/salesforce/pom.xml +++ b/converters/java/salesforce/pom.xml @@ -24,15 +24,13 @@ 4.0.0 - org.apache - apache - 39 - + org.apache.ossie + ossie-java-converters + 0.1.0-SNAPSHOT + ../pom.xml - org.apache.ossie ossie-salesforce-converter - 0.1.0-SNAPSHOT jar Apache Ossie Salesforce Converter @@ -55,6 +53,11 @@ + + org.apache.ossie + ossie-common + + com.fasterxml.jackson.core jackson-databind @@ -100,7 +103,7 @@ Copy the canonical Ossie core-spec JSON schema into this module's classpath under /schemas/osi-schema.json so SchemaValidator can load it at runtime. Single source of truth lives in - ../../core-spec/osi-schema.json; do not maintain a duplicate + ../../../core-spec/osi-schema.json; do not maintain a duplicate copy in this module's src/main/resources. --> @@ -118,7 +121,7 @@ ${project.build.outputDirectory}/schemas - ${project.basedir}/../../core-spec + ${project.basedir}/../../../core-spec osi-schema.json @@ -141,6 +144,8 @@ false + true + all org.apache.ossie.app.OsiSalesforceConverter diff --git a/converters/salesforce/src/main/java/org/apache/ossie/app/OsiSalesforceConverter.java b/converters/java/salesforce/src/main/java/org/apache/ossie/app/OsiSalesforceConverter.java similarity index 100% rename from converters/salesforce/src/main/java/org/apache/ossie/app/OsiSalesforceConverter.java rename to converters/java/salesforce/src/main/java/org/apache/ossie/app/OsiSalesforceConverter.java diff --git a/converters/salesforce/src/main/java/org/apache/ossie/converter/AbstractConverter.java b/converters/java/salesforce/src/main/java/org/apache/ossie/converter/AbstractConverter.java similarity index 100% rename from converters/salesforce/src/main/java/org/apache/ossie/converter/AbstractConverter.java rename to converters/java/salesforce/src/main/java/org/apache/ossie/converter/AbstractConverter.java diff --git a/converters/salesforce/src/main/java/org/apache/ossie/converter/ConversionDirection.java b/converters/java/salesforce/src/main/java/org/apache/ossie/converter/ConversionDirection.java similarity index 100% rename from converters/salesforce/src/main/java/org/apache/ossie/converter/ConversionDirection.java rename to converters/java/salesforce/src/main/java/org/apache/ossie/converter/ConversionDirection.java diff --git a/converters/salesforce/src/main/java/org/apache/ossie/converter/Converter.java b/converters/java/salesforce/src/main/java/org/apache/ossie/converter/Converter.java similarity index 100% rename from converters/salesforce/src/main/java/org/apache/ossie/converter/Converter.java rename to converters/java/salesforce/src/main/java/org/apache/ossie/converter/Converter.java diff --git a/converters/salesforce/src/main/java/org/apache/ossie/converter/ConverterConstants.java b/converters/java/salesforce/src/main/java/org/apache/ossie/converter/ConverterConstants.java similarity index 100% rename from converters/salesforce/src/main/java/org/apache/ossie/converter/ConverterConstants.java rename to converters/java/salesforce/src/main/java/org/apache/ossie/converter/ConverterConstants.java diff --git a/converters/salesforce/src/main/java/org/apache/ossie/converter/ConverterFactory.java b/converters/java/salesforce/src/main/java/org/apache/ossie/converter/ConverterFactory.java similarity index 100% rename from converters/salesforce/src/main/java/org/apache/ossie/converter/ConverterFactory.java rename to converters/java/salesforce/src/main/java/org/apache/ossie/converter/ConverterFactory.java diff --git a/converters/salesforce/src/main/java/org/apache/ossie/converter/ConverterImpl.java b/converters/java/salesforce/src/main/java/org/apache/ossie/converter/ConverterImpl.java similarity index 80% rename from converters/salesforce/src/main/java/org/apache/ossie/converter/ConverterImpl.java rename to converters/java/salesforce/src/main/java/org/apache/ossie/converter/ConverterImpl.java index a6b5cff6..7460a86d 100644 --- a/converters/salesforce/src/main/java/org/apache/ossie/converter/ConverterImpl.java +++ b/converters/java/salesforce/src/main/java/org/apache/ossie/converter/ConverterImpl.java @@ -26,8 +26,9 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.ossie.converter.pipeline.*; -import org.apache.ossie.converter.pipeline.*; import org.apache.ossie.exception.ConversionException; +import org.apache.ossie.model.OsiModel; +import org.apache.ossie.model.SemanticModel; import org.apache.ossie.validator.SchemaValidator; import java.util.ArrayList; @@ -90,18 +91,19 @@ public List convert(String content) { schemaValidator.validate(sourceData); if (direction == ConversionDirection.OSI_TO_SALESFORCE) { - return convertOsiToSalesforce(sourceData); + OsiModel osiModel = OsiModelBinding.fromRootMap(jsonMapper, sourceData); + return convertOsiToSalesforce(osiModel); } else { return convertSalesforceToOsi(sourceData); } } - private List convertOsiToSalesforce(Map osiRoot) { - List semanticModels = getList(osiRoot, SEMANTIC_MODEL); + private List convertOsiToSalesforce(OsiModel osiRoot) { List results = new ArrayList<>(); - for (Object modelObj : semanticModels) { - Map sourceData = asMap(modelObj); + for (SemanticModel semanticModel : osiRoot.getSemanticModel()) { + Map sourceData = OsiModelBinding.toPipelineMap( + jsonMapper, semanticModel); String result = executePipeline(sourceData); results.add(result); } @@ -111,13 +113,10 @@ private List convertOsiToSalesforce(Map osiRoot) { private List convertSalesforceToOsi(Map sourceData) { String result = executePipeline(sourceData); - // Wrap output in Ossie root structure try { - Map outputData = yamlMapper.readValue(result, new TypeReference<>() {}); - Map osiRoot = new LinkedHashMap<>(); - osiRoot.put(VERSION, OSI_VERSION); - osiRoot.put(SEMANTIC_MODEL, List.of(outputData)); - return List.of(toYaml(osiRoot)); + OsiModel osiRoot = OsiModelBinding.wrapPipelineYaml( + yamlMapper, result, OSI_VERSION); + return List.of(yamlMapper.writeValueAsString(osiRoot)); } catch (JsonProcessingException e) { throw new ConversionException("Failed to wrap output in Ossie root", e); } @@ -150,21 +149,16 @@ protected String getFileExtension() { @Override protected String extractModelName(String result) { try { - Map data = JSON.equals(directionConfig.getOutputFormat()) - ? jsonMapper.readValue(result, new TypeReference<>() {}) - : yamlMapper.readValue(result, new TypeReference<>() {}); - - String field = directionConfig.getExtractModelNameFrom(); - - // Handle Ossie format (wrapped in semantic_model array) if (direction == ConversionDirection.SALESFORCE_TO_OSI) { - List models = getList(data, SEMANTIC_MODEL); - if (models != null && !models.isEmpty()) { - Map firstModel = asMap(models.get(0)); - return firstModel.get(field).toString(); + OsiModel osiModel = yamlMapper.readValue(result, OsiModel.class); + if (!osiModel.getSemanticModel().isEmpty()) { + return osiModel.getSemanticModel().get(0).getName(); } } + Map data = jsonMapper.readValue(result, new TypeReference<>() {}); + + String field = directionConfig.getExtractModelNameFrom(); return data.get(field).toString(); } catch (JsonProcessingException e) { throw new ConversionException("Failed to extract model name", e); diff --git a/converters/salesforce/src/main/java/org/apache/ossie/converter/CustomExtensionHandler.java b/converters/java/salesforce/src/main/java/org/apache/ossie/converter/CustomExtensionHandler.java similarity index 100% rename from converters/salesforce/src/main/java/org/apache/ossie/converter/CustomExtensionHandler.java rename to converters/java/salesforce/src/main/java/org/apache/ossie/converter/CustomExtensionHandler.java diff --git a/converters/salesforce/src/main/java/org/apache/ossie/converter/DatasetMappingHandler.java b/converters/java/salesforce/src/main/java/org/apache/ossie/converter/DatasetMappingHandler.java similarity index 100% rename from converters/salesforce/src/main/java/org/apache/ossie/converter/DatasetMappingHandler.java rename to converters/java/salesforce/src/main/java/org/apache/ossie/converter/DatasetMappingHandler.java diff --git a/converters/salesforce/src/main/java/org/apache/ossie/converter/FieldMappingHandler.java b/converters/java/salesforce/src/main/java/org/apache/ossie/converter/FieldMappingHandler.java similarity index 100% rename from converters/salesforce/src/main/java/org/apache/ossie/converter/FieldMappingHandler.java rename to converters/java/salesforce/src/main/java/org/apache/ossie/converter/FieldMappingHandler.java diff --git a/converters/salesforce/src/main/java/org/apache/ossie/converter/GenericMappingEngine.java b/converters/java/salesforce/src/main/java/org/apache/ossie/converter/GenericMappingEngine.java similarity index 100% rename from converters/salesforce/src/main/java/org/apache/ossie/converter/GenericMappingEngine.java rename to converters/java/salesforce/src/main/java/org/apache/ossie/converter/GenericMappingEngine.java diff --git a/converters/salesforce/src/main/java/org/apache/ossie/converter/MetricMappingHandler.java b/converters/java/salesforce/src/main/java/org/apache/ossie/converter/MetricMappingHandler.java similarity index 100% rename from converters/salesforce/src/main/java/org/apache/ossie/converter/MetricMappingHandler.java rename to converters/java/salesforce/src/main/java/org/apache/ossie/converter/MetricMappingHandler.java diff --git a/converters/java/salesforce/src/main/java/org/apache/ossie/converter/OsiModelBinding.java b/converters/java/salesforce/src/main/java/org/apache/ossie/converter/OsiModelBinding.java new file mode 100644 index 00000000..f2e51e6d --- /dev/null +++ b/converters/java/salesforce/src/main/java/org/apache/ossie/converter/OsiModelBinding.java @@ -0,0 +1,58 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +package org.apache.ossie.converter; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.ossie.model.OsiModel; +import org.apache.ossie.model.SemanticModel; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * Binds the Salesforce converter's dynamic pipeline maps to the shared Ossie model. + */ +final class OsiModelBinding { + + private OsiModelBinding() { + } + + static OsiModel fromRootMap(ObjectMapper mapper, Map root) { + return mapper.convertValue(root, OsiModel.class); + } + + static Map toPipelineMap(ObjectMapper mapper, SemanticModel semanticModel) { + return mapper.convertValue( + semanticModel, new TypeReference>() {}); + } + + static OsiModel wrapPipelineYaml( + ObjectMapper yamlMapper, String semanticModelYaml, String version) + throws JsonProcessingException { + SemanticModel semanticModel = yamlMapper.readValue(semanticModelYaml, SemanticModel.class); + OsiModel root = new OsiModel(); + root.setVersion(version); + root.setSemanticModel(List.of(semanticModel)); + return root; + } +} diff --git a/converters/salesforce/src/main/java/org/apache/ossie/converter/RelationshipMappingHandler.java b/converters/java/salesforce/src/main/java/org/apache/ossie/converter/RelationshipMappingHandler.java similarity index 100% rename from converters/salesforce/src/main/java/org/apache/ossie/converter/RelationshipMappingHandler.java rename to converters/java/salesforce/src/main/java/org/apache/ossie/converter/RelationshipMappingHandler.java diff --git a/converters/salesforce/src/main/java/org/apache/ossie/converter/SemanticModelMappingHandler.java b/converters/java/salesforce/src/main/java/org/apache/ossie/converter/SemanticModelMappingHandler.java similarity index 100% rename from converters/salesforce/src/main/java/org/apache/ossie/converter/SemanticModelMappingHandler.java rename to converters/java/salesforce/src/main/java/org/apache/ossie/converter/SemanticModelMappingHandler.java diff --git a/converters/salesforce/src/main/java/org/apache/ossie/converter/pipeline/DirectionConfig.java b/converters/java/salesforce/src/main/java/org/apache/ossie/converter/pipeline/DirectionConfig.java similarity index 100% rename from converters/salesforce/src/main/java/org/apache/ossie/converter/pipeline/DirectionConfig.java rename to converters/java/salesforce/src/main/java/org/apache/ossie/converter/pipeline/DirectionConfig.java diff --git a/converters/salesforce/src/main/java/org/apache/ossie/converter/pipeline/HandlerFactory.java b/converters/java/salesforce/src/main/java/org/apache/ossie/converter/pipeline/HandlerFactory.java similarity index 100% rename from converters/salesforce/src/main/java/org/apache/ossie/converter/pipeline/HandlerFactory.java rename to converters/java/salesforce/src/main/java/org/apache/ossie/converter/pipeline/HandlerFactory.java diff --git a/converters/salesforce/src/main/java/org/apache/ossie/converter/pipeline/PipelineConfig.java b/converters/java/salesforce/src/main/java/org/apache/ossie/converter/pipeline/PipelineConfig.java similarity index 100% rename from converters/salesforce/src/main/java/org/apache/ossie/converter/pipeline/PipelineConfig.java rename to converters/java/salesforce/src/main/java/org/apache/ossie/converter/pipeline/PipelineConfig.java diff --git a/converters/salesforce/src/main/java/org/apache/ossie/converter/pipeline/PipelineConfigLoader.java b/converters/java/salesforce/src/main/java/org/apache/ossie/converter/pipeline/PipelineConfigLoader.java similarity index 100% rename from converters/salesforce/src/main/java/org/apache/ossie/converter/pipeline/PipelineConfigLoader.java rename to converters/java/salesforce/src/main/java/org/apache/ossie/converter/pipeline/PipelineConfigLoader.java diff --git a/converters/salesforce/src/main/java/org/apache/ossie/converter/pipeline/PipelineStep.java b/converters/java/salesforce/src/main/java/org/apache/ossie/converter/pipeline/PipelineStep.java similarity index 100% rename from converters/salesforce/src/main/java/org/apache/ossie/converter/pipeline/PipelineStep.java rename to converters/java/salesforce/src/main/java/org/apache/ossie/converter/pipeline/PipelineStep.java diff --git a/converters/salesforce/src/main/java/org/apache/ossie/exception/ConversionException.java b/converters/java/salesforce/src/main/java/org/apache/ossie/exception/ConversionException.java similarity index 100% rename from converters/salesforce/src/main/java/org/apache/ossie/exception/ConversionException.java rename to converters/java/salesforce/src/main/java/org/apache/ossie/exception/ConversionException.java diff --git a/converters/salesforce/src/main/java/org/apache/ossie/exception/InvalidInputException.java b/converters/java/salesforce/src/main/java/org/apache/ossie/exception/InvalidInputException.java similarity index 100% rename from converters/salesforce/src/main/java/org/apache/ossie/exception/InvalidInputException.java rename to converters/java/salesforce/src/main/java/org/apache/ossie/exception/InvalidInputException.java diff --git a/converters/salesforce/src/main/java/org/apache/ossie/exception/ValidationException.java b/converters/java/salesforce/src/main/java/org/apache/ossie/exception/ValidationException.java similarity index 100% rename from converters/salesforce/src/main/java/org/apache/ossie/exception/ValidationException.java rename to converters/java/salesforce/src/main/java/org/apache/ossie/exception/ValidationException.java diff --git a/converters/salesforce/src/main/java/org/apache/ossie/mapper/FileBasedPropertyMapper.java b/converters/java/salesforce/src/main/java/org/apache/ossie/mapper/FileBasedPropertyMapper.java similarity index 100% rename from converters/salesforce/src/main/java/org/apache/ossie/mapper/FileBasedPropertyMapper.java rename to converters/java/salesforce/src/main/java/org/apache/ossie/mapper/FileBasedPropertyMapper.java diff --git a/converters/salesforce/src/main/java/org/apache/ossie/mapper/PropertyMapper.java b/converters/java/salesforce/src/main/java/org/apache/ossie/mapper/PropertyMapper.java similarity index 100% rename from converters/salesforce/src/main/java/org/apache/ossie/mapper/PropertyMapper.java rename to converters/java/salesforce/src/main/java/org/apache/ossie/mapper/PropertyMapper.java diff --git a/converters/salesforce/src/main/java/org/apache/ossie/util/DataStructureUtils.java b/converters/java/salesforce/src/main/java/org/apache/ossie/util/DataStructureUtils.java similarity index 100% rename from converters/salesforce/src/main/java/org/apache/ossie/util/DataStructureUtils.java rename to converters/java/salesforce/src/main/java/org/apache/ossie/util/DataStructureUtils.java diff --git a/converters/salesforce/src/main/java/org/apache/ossie/util/MappingUtils.java b/converters/java/salesforce/src/main/java/org/apache/ossie/util/MappingUtils.java similarity index 100% rename from converters/salesforce/src/main/java/org/apache/ossie/util/MappingUtils.java rename to converters/java/salesforce/src/main/java/org/apache/ossie/util/MappingUtils.java diff --git a/converters/salesforce/src/main/java/org/apache/ossie/util/PathUtils.java b/converters/java/salesforce/src/main/java/org/apache/ossie/util/PathUtils.java similarity index 100% rename from converters/salesforce/src/main/java/org/apache/ossie/util/PathUtils.java rename to converters/java/salesforce/src/main/java/org/apache/ossie/util/PathUtils.java diff --git a/converters/salesforce/src/main/java/org/apache/ossie/validator/SchemaValidator.java b/converters/java/salesforce/src/main/java/org/apache/ossie/validator/SchemaValidator.java similarity index 100% rename from converters/salesforce/src/main/java/org/apache/ossie/validator/SchemaValidator.java rename to converters/java/salesforce/src/main/java/org/apache/ossie/validator/SchemaValidator.java diff --git a/converters/salesforce/src/main/resources/mappings.yaml b/converters/java/salesforce/src/main/resources/mappings.yaml similarity index 100% rename from converters/salesforce/src/main/resources/mappings.yaml rename to converters/java/salesforce/src/main/resources/mappings.yaml diff --git a/converters/salesforce/src/main/resources/osi-salesforce-converter-config.yaml b/converters/java/salesforce/src/main/resources/osi-salesforce-converter-config.yaml similarity index 100% rename from converters/salesforce/src/main/resources/osi-salesforce-converter-config.yaml rename to converters/java/salesforce/src/main/resources/osi-salesforce-converter-config.yaml diff --git a/converters/salesforce/src/test/java/org/apache/ossie/OsiToSalesforceConverterTest.java b/converters/java/salesforce/src/test/java/org/apache/ossie/OsiToSalesforceConverterTest.java similarity index 100% rename from converters/salesforce/src/test/java/org/apache/ossie/OsiToSalesforceConverterTest.java rename to converters/java/salesforce/src/test/java/org/apache/ossie/OsiToSalesforceConverterTest.java diff --git a/converters/salesforce/src/test/java/org/apache/ossie/SalesforceToOsiConverterTest.java b/converters/java/salesforce/src/test/java/org/apache/ossie/SalesforceToOsiConverterTest.java similarity index 100% rename from converters/salesforce/src/test/java/org/apache/ossie/SalesforceToOsiConverterTest.java rename to converters/java/salesforce/src/test/java/org/apache/ossie/SalesforceToOsiConverterTest.java diff --git a/converters/java/salesforce/src/test/java/org/apache/ossie/converter/OsiModelBindingTest.java b/converters/java/salesforce/src/test/java/org/apache/ossie/converter/OsiModelBindingTest.java new file mode 100644 index 00000000..051c99d3 --- /dev/null +++ b/converters/java/salesforce/src/test/java/org/apache/ossie/converter/OsiModelBindingTest.java @@ -0,0 +1,73 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +package org.apache.ossie.converter; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +import org.apache.ossie.model.DialectExpression; +import org.apache.ossie.model.OsiModel; +import org.junit.jupiter.api.Test; + +import java.util.LinkedHashMap; +import java.util.Map; + +class OsiModelBindingTest { + + private final ObjectMapper yamlMapper = new ObjectMapper(new YAMLFactory()); + + @Test + void bindsBothSidesOfTheDynamicPipelineWithoutVendorSchema() throws Exception { + String rootYaml = String.join("\n", + "version: 0.2.0.dev0", + "semantic_model:", + " - name: sales", + " datasets:", + " - name: orders", + " source: analytics.orders", + " fields:", + " - name: order_id", + " expression:", + " dialects:", + " - dialect: ANSI_SQL", + " expression: order_id"); + Map rootMap = yamlMapper.readValue( + rootYaml, new TypeReference>() {}); + + OsiModel typedRoot = OsiModelBinding.fromRootMap(yamlMapper, rootMap); + assertEquals(DialectExpression.Dialect.ANSI_SQL, typedRoot.getSemanticModel().get(0) + .getDatasets().get(0).getFields().get(0) + .getExpression().getDialects().get(0).getDialect()); + + Map pipelineMap = OsiModelBinding.toPipelineMap( + yamlMapper, typedRoot.getSemanticModel().get(0)); + assertInstanceOf(Iterable.class, pipelineMap.get("datasets")); + + String pipelineYaml = yamlMapper.writeValueAsString( + typedRoot.getSemanticModel().get(0)); + OsiModel wrapped = OsiModelBinding.wrapPipelineYaml( + yamlMapper, pipelineYaml, ConverterConstants.OSI_VERSION); + assertEquals("sales", wrapped.getSemanticModel().get(0).getName()); + assertEquals("0.2.0.dev0", wrapped.getVersion()); + } +} diff --git a/converters/salesforce/src/test/resources/examples/osiToSalesforce.yaml b/converters/java/salesforce/src/test/resources/examples/osiToSalesforce.yaml similarity index 100% rename from converters/salesforce/src/test/resources/examples/osiToSalesforce.yaml rename to converters/java/salesforce/src/test/resources/examples/osiToSalesforce.yaml diff --git a/converters/salesforce/src/test/resources/examples/salesforceToOsi.json b/converters/java/salesforce/src/test/resources/examples/salesforceToOsi.json similarity index 100% rename from converters/salesforce/src/test/resources/examples/salesforceToOsi.json rename to converters/java/salesforce/src/test/resources/examples/salesforceToOsi.json diff --git a/converters/polaris/src/main/java/org/apache/ossie/converter/polaris/OsiModelParser.java b/converters/polaris/src/main/java/org/apache/ossie/converter/polaris/OsiModelParser.java deleted file mode 100644 index 8f9201ec..00000000 --- a/converters/polaris/src/main/java/org/apache/ossie/converter/polaris/OsiModelParser.java +++ /dev/null @@ -1,216 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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. - */ - -package org.apache.ossie.converter.polaris; - -import org.apache.ossie.converter.polaris.model.OsiModel; -import org.apache.ossie.converter.polaris.model.OsiModel.*; -import org.yaml.snakeyaml.Yaml; - -import java.io.IOException; -import java.io.InputStream; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -/** - * Parses an Ossie YAML file into an {@link OsiModel}. - */ -public class OsiModelParser { - - /** - * Parse an Ossie YAML file from the given path. - */ - public OsiModel parse(Path yamlPath) throws IOException { - try (InputStream is = Files.newInputStream(yamlPath)) { - return parse(is); - } - } - - /** - * Parse an Ossie YAML file from an input stream. - */ - @SuppressWarnings("unchecked") - public OsiModel parse(InputStream is) { - Yaml yaml = new Yaml(); - Map root = yaml.load(is); - - OsiModel model = new OsiModel(); - model.setVersion((String) root.get("version")); - - List> smList = (List>) root.get("semantic_model"); - if (smList == null) { - return model; - } - - List semanticModels = new ArrayList<>(); - for (Map smMap : smList) { - semanticModels.add(parseSemanticModel(smMap)); - } - model.setSemanticModels(semanticModels); - return model; - } - - @SuppressWarnings("unchecked") - private SemanticModel parseSemanticModel(Map map) { - SemanticModel sm = new SemanticModel(); - sm.setName((String) map.get("name")); - sm.setDescription((String) map.get("description")); - - // Datasets - List> dsList = (List>) map.get("datasets"); - if (dsList != null) { - List datasets = new ArrayList<>(); - for (Map dsMap : dsList) { - datasets.add(parseDataset(dsMap)); - } - sm.setDatasets(datasets); - } - - // Relationships - List> relList = (List>) map.get("relationships"); - if (relList != null) { - List relationships = new ArrayList<>(); - for (Map relMap : relList) { - relationships.add(parseRelationship(relMap)); - } - sm.setRelationships(relationships); - } - - // Metrics - List> metricList = (List>) map.get("metrics"); - if (metricList != null) { - List metrics = new ArrayList<>(); - for (Map mMap : metricList) { - metrics.add(parseMetric(mMap)); - } - sm.setMetrics(metrics); - } - - return sm; - } - - @SuppressWarnings("unchecked") - private Dataset parseDataset(Map map) { - Dataset ds = new Dataset(); - ds.setName((String) map.get("name")); - ds.setSource((String) map.get("source")); - ds.setDescription((String) map.get("description")); - - List pk = (List) map.get("primary_key"); - if (pk != null) { - ds.setPrimaryKey(new ArrayList<>(pk)); - } - - List> uniqueKeys = (List>) map.get("unique_keys"); - if (uniqueKeys != null) { - ds.setUniqueKeys(uniqueKeys); - } - - List> fieldList = (List>) map.get("fields"); - if (fieldList != null) { - List fields = new ArrayList<>(); - for (Map fMap : fieldList) { - fields.add(parseField(fMap)); - } - ds.setFields(fields); - } - - List> extList = (List>) map.get("custom_extensions"); - if (extList != null) { - List extensions = new ArrayList<>(); - for (Map extMap : extList) { - CustomExtension ext = new CustomExtension(); - ext.setVendorName((String) extMap.get("vendor_name")); - ext.setData((String) extMap.get("data")); - extensions.add(ext); - } - ds.setCustomExtensions(extensions); - } - - return ds; - } - - @SuppressWarnings("unchecked") - private Field parseField(Map map) { - Field field = new Field(); - field.setName((String) map.get("name")); - field.setDescription((String) map.get("description")); - - // Dimension - Map dim = (Map) map.get("dimension"); - if (dim != null) { - Object isTime = dim.get("is_time"); - field.setTime(Boolean.TRUE.equals(isTime)); - } - - // Expressions - field.setExpressions(parseDialectExpressions(map)); - return field; - } - - @SuppressWarnings("unchecked") - private Relationship parseRelationship(Map map) { - Relationship rel = new Relationship(); - rel.setName((String) map.get("name")); - rel.setFrom((String) map.get("from")); - rel.setTo((String) map.get("to")); - - List fromCols = (List) map.get("from_columns"); - if (fromCols != null) { - rel.setFromColumns(new ArrayList<>(fromCols)); - } - List toCols = (List) map.get("to_columns"); - if (toCols != null) { - rel.setToColumns(new ArrayList<>(toCols)); - } - return rel; - } - - @SuppressWarnings("unchecked") - private Metric parseMetric(Map map) { - Metric metric = new Metric(); - metric.setName((String) map.get("name")); - metric.setDescription((String) map.get("description")); - metric.setExpressions(parseDialectExpressions(map)); - return metric; - } - - @SuppressWarnings("unchecked") - private List parseDialectExpressions(Map map) { - List result = new ArrayList<>(); - Map exprBlock = (Map) map.get("expression"); - if (exprBlock == null) { - return result; - } - List> dialects = (List>) exprBlock.get("dialects"); - if (dialects == null) { - return result; - } - for (Map d : dialects) { - String dialect = (String) d.get("dialect"); - Object exprValue = d.get("expression"); - String expression = exprValue != null ? exprValue.toString() : null; - result.add(new DialectExpression(dialect, expression)); - } - return result; - } -} diff --git a/converters/polaris/src/main/java/org/apache/ossie/converter/polaris/OsiYamlGenerator.java b/converters/polaris/src/main/java/org/apache/ossie/converter/polaris/OsiYamlGenerator.java deleted file mode 100644 index 2533b26e..00000000 --- a/converters/polaris/src/main/java/org/apache/ossie/converter/polaris/OsiYamlGenerator.java +++ /dev/null @@ -1,195 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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. - */ - -package org.apache.ossie.converter.polaris; - -import org.apache.ossie.converter.polaris.model.OsiModel; -import org.apache.ossie.converter.polaris.model.OsiModel.*; - -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -/** - * Generates Ossie YAML from an {@link OsiModel}. - *

- * Produces well-formatted YAML that conforms to the Ossie specification. - */ -public class OsiYamlGenerator { - - /** - * Generate Ossie YAML string from a model. - */ - public String generate(OsiModel model) { - StringBuilder sb = new StringBuilder(); - sb.append("version: \"").append(model.getVersion()).append("\"\n\n"); - sb.append("semantic_model:\n"); - - for (SemanticModel sm : model.getSemanticModels()) { - generateSemanticModel(sb, sm); - } - - return sb.toString(); - } - - private void generateSemanticModel(StringBuilder sb, SemanticModel sm) { - sb.append(" - name: ").append(sm.getName()).append("\n"); - if (sm.getDescription() != null) { - sb.append(" description: \"").append(escapeYaml(sm.getDescription())).append("\"\n"); - } - - // Datasets - if (!sm.getDatasets().isEmpty()) { - sb.append(" datasets:\n"); - for (Dataset ds : sm.getDatasets()) { - generateDataset(sb, ds); - } - } - - // Relationships - if (!sm.getRelationships().isEmpty()) { - sb.append(" relationships:\n"); - for (Relationship rel : sm.getRelationships()) { - generateRelationship(sb, rel); - } - } - - // Metrics - if (!sm.getMetrics().isEmpty()) { - sb.append(" metrics:\n"); - for (Metric metric : sm.getMetrics()) { - generateMetric(sb, metric); - } - } - } - - private void generateDataset(StringBuilder sb, Dataset ds) { - sb.append(" - name: ").append(ds.getName()).append("\n"); - sb.append(" source: ").append(ds.getSource()).append("\n"); - - if (!ds.getPrimaryKey().isEmpty()) { - sb.append(" primary_key: [").append(String.join(", ", ds.getPrimaryKey())).append("]\n"); - } - - if (!ds.getUniqueKeys().isEmpty()) { - sb.append(" unique_keys:\n"); - for (List uk : ds.getUniqueKeys()) { - sb.append(" - [").append(String.join(", ", uk)).append("]\n"); - } - } - - if (ds.getDescription() != null) { - sb.append(" description: \"").append(escapeYaml(ds.getDescription())).append("\"\n"); - } - - if (!ds.getFields().isEmpty()) { - sb.append(" fields:\n"); - for (Field field : ds.getFields()) { - generateField(sb, field); - } - } - - if (!ds.getCustomExtensions().isEmpty()) { - sb.append(" custom_extensions:\n"); - for (CustomExtension ext : ds.getCustomExtensions()) { - sb.append(" - vendor_name: ").append(ext.getVendorName()).append("\n"); - sb.append(" data: '").append(ext.getData()).append("'\n"); - } - } - } - - private void generateField(StringBuilder sb, Field field) { - sb.append(" - name: ").append(field.getName()).append("\n"); - - if (!field.getExpressions().isEmpty()) { - sb.append(" expression:\n"); - sb.append(" dialects:\n"); - for (DialectExpression de : field.getExpressions()) { - sb.append(" - dialect: ").append(de.getDialect()).append("\n"); - sb.append(" expression: "); - String expr = de.getExpression(); - if (needsQuoting(expr)) { - sb.append("\"").append(escapeYaml(expr)).append("\""); - } else { - sb.append(expr); - } - sb.append("\n"); - } - } - - if (field.isTime()) { - sb.append(" dimension:\n"); - sb.append(" is_time: true\n"); - } - - if (field.getDescription() != null) { - sb.append(" description: \"").append(escapeYaml(field.getDescription())).append("\"\n"); - } - } - - private void generateRelationship(StringBuilder sb, Relationship rel) { - sb.append(" - name: ").append(rel.getName()).append("\n"); - sb.append(" from: ").append(rel.getFrom()).append("\n"); - sb.append(" to: ").append(rel.getTo()).append("\n"); - sb.append(" from_columns: [").append(String.join(", ", rel.getFromColumns())).append("]\n"); - sb.append(" to_columns: [").append(String.join(", ", rel.getToColumns())).append("]\n"); - } - - private void generateMetric(StringBuilder sb, Metric metric) { - sb.append(" - name: ").append(metric.getName()).append("\n"); - - if (!metric.getExpressions().isEmpty()) { - sb.append(" expression:\n"); - sb.append(" dialects:\n"); - for (DialectExpression de : metric.getExpressions()) { - sb.append(" - dialect: ").append(de.getDialect()).append("\n"); - sb.append(" expression: "); - String expr = de.getExpression(); - if (needsQuoting(expr)) { - sb.append("\"").append(escapeYaml(expr)).append("\""); - } else { - sb.append(expr); - } - sb.append("\n"); - } - } - - if (metric.getDescription() != null) { - sb.append(" description: \"").append(escapeYaml(metric.getDescription())).append("\"\n"); - } - } - - private boolean needsQuoting(String value) { - if (value == null) return false; - return value.contains("'") || value.contains("\"") || value.contains(":") - || value.contains("{") || value.contains("}") || value.contains("[") - || value.contains("]") || value.contains(",") || value.contains("&") - || value.contains("*") || value.contains("#") || value.contains("?") - || value.contains("|") || value.contains("-") || value.contains("<") - || value.contains(">") || value.contains("=") || value.contains("!") - || value.contains("%") || value.contains("@") || value.contains("`") - || value.contains(" "); - } - - private String escapeYaml(String value) { - if (value == null) return ""; - return value.replace("\\", "\\\\").replace("\"", "\\\""); - } -} diff --git a/converters/polaris/src/main/java/org/apache/ossie/converter/polaris/model/OsiModel.java b/converters/polaris/src/main/java/org/apache/ossie/converter/polaris/model/OsiModel.java deleted file mode 100644 index e36a5e1d..00000000 --- a/converters/polaris/src/main/java/org/apache/ossie/converter/polaris/model/OsiModel.java +++ /dev/null @@ -1,199 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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. - */ - -package org.apache.ossie.converter.polaris.model; - -import java.util.ArrayList; -import java.util.List; - -/** - * Java representation of an Ossie semantic model parsed from YAML. - */ -public class OsiModel { - - private String version; - private List semanticModels = new ArrayList<>(); - - public String getVersion() { - return version; - } - - public void setVersion(String version) { - this.version = version; - } - - public List getSemanticModels() { - return semanticModels; - } - - public void setSemanticModels(List semanticModels) { - this.semanticModels = semanticModels; - } - - // ----------------------------------------------------------------------- - // Nested model classes - // ----------------------------------------------------------------------- - - public static class SemanticModel { - private String name; - private String description; - private List datasets = new ArrayList<>(); - private List relationships = new ArrayList<>(); - private List metrics = new ArrayList<>(); - - public String getName() { return name; } - public void setName(String name) { this.name = name; } - - public String getDescription() { return description; } - public void setDescription(String description) { this.description = description; } - - public List getDatasets() { return datasets; } - public void setDatasets(List datasets) { this.datasets = datasets; } - - public List getRelationships() { return relationships; } - public void setRelationships(List relationships) { this.relationships = relationships; } - - public List getMetrics() { return metrics; } - public void setMetrics(List metrics) { this.metrics = metrics; } - } - - public static class Dataset { - private String name; - private String source; - private List primaryKey = new ArrayList<>(); - private List> uniqueKeys = new ArrayList<>(); - private String description; - private List fields = new ArrayList<>(); - private List customExtensions = new ArrayList<>(); - - public String getName() { return name; } - public void setName(String name) { this.name = name; } - - public String getSource() { return source; } - public void setSource(String source) { this.source = source; } - - public List getPrimaryKey() { return primaryKey; } - public void setPrimaryKey(List primaryKey) { this.primaryKey = primaryKey; } - - public List> getUniqueKeys() { return uniqueKeys; } - public void setUniqueKeys(List> uniqueKeys) { this.uniqueKeys = uniqueKeys; } - - public String getDescription() { return description; } - public void setDescription(String description) { this.description = description; } - - public List getFields() { return fields; } - public void setFields(List fields) { this.fields = fields; } - - public List getCustomExtensions() { return customExtensions; } - public void setCustomExtensions(List customExtensions) { this.customExtensions = customExtensions; } - } - - public static class Field { - private String name; - private String description; - private List expressions = new ArrayList<>(); - private boolean isTime; - - public String getName() { return name; } - public void setName(String name) { this.name = name; } - - public String getDescription() { return description; } - public void setDescription(String description) { this.description = description; } - - public List getExpressions() { return expressions; } - public void setExpressions(List expressions) { this.expressions = expressions; } - - public boolean isTime() { return isTime; } - public void setTime(boolean time) { isTime = time; } - } - - public static class DialectExpression { - private String dialect; - private String expression; - - public DialectExpression() {} - - public DialectExpression(String dialect, String expression) { - this.dialect = dialect; - this.expression = expression; - } - - public String getDialect() { return dialect; } - public void setDialect(String dialect) { this.dialect = dialect; } - - public String getExpression() { return expression; } - public void setExpression(String expression) { this.expression = expression; } - } - - public static class Relationship { - private String name; - private String from; - private String to; - private List fromColumns = new ArrayList<>(); - private List toColumns = new ArrayList<>(); - - public String getName() { return name; } - public void setName(String name) { this.name = name; } - - public String getFrom() { return from; } - public void setFrom(String from) { this.from = from; } - - public String getTo() { return to; } - public void setTo(String to) { this.to = to; } - - public List getFromColumns() { return fromColumns; } - public void setFromColumns(List fromColumns) { this.fromColumns = fromColumns; } - - public List getToColumns() { return toColumns; } - public void setToColumns(List toColumns) { this.toColumns = toColumns; } - } - - public static class Metric { - private String name; - private String description; - private List expressions = new ArrayList<>(); - - public String getName() { return name; } - public void setName(String name) { this.name = name; } - - public String getDescription() { return description; } - public void setDescription(String description) { this.description = description; } - - public List getExpressions() { return expressions; } - public void setExpressions(List expressions) { this.expressions = expressions; } - } - - public static class CustomExtension { - private String vendorName; - private String data; - - public CustomExtension() {} - - public CustomExtension(String vendorName, String data) { - this.vendorName = vendorName; - this.data = data; - } - - public String getVendorName() { return vendorName; } - public void setVendorName(String vendorName) { this.vendorName = vendorName; } - - public String getData() { return data; } - public void setData(String data) { this.data = data; } - } -} diff --git a/core-spec/osi-schema.json b/core-spec/osi-schema.json index 385e18a9..b59580e7 100644 --- a/core-spec/osi-schema.json +++ b/core-spec/osi-schema.json @@ -1,6 +1,7 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://github.com/apache/ossie/core-spec/osi-schema.json", + "javaName": "OsiModel", "title": "Apache Ossie Core Metadata Specification", "description": "JSON Schema for validating Apache Ossie semantic model definitions", "type": "object",