Skip to content

Commit cd4fadb

Browse files
committed
wip work on node service for xsd to json schema
1 parent 3e45e55 commit cd4fadb

17 files changed

Lines changed: 852 additions & 1660 deletions

File tree

README.md

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,34 @@ git clone https://github.com/Logende/universal-schema-converter.git
2525
cd universal-schema-converter
2626
```
2727

28-
### 2. Build and start with Docker
28+
### 2. Build the sub-packages
29+
30+
#### Java
2931

3032
```bash
31-
docker-compose up --build
33+
cd schema-conversion-orchestrator/external_converters/java
34+
mvn clean package
3235
```
3336

34-
TODO
37+
Result: `target/converter.jar`
38+
39+
#### Node
40+
41+
```bash
42+
cd schema-conversion-orchestrator/external_converters/node
43+
npm install
44+
npm run build
45+
```
46+
47+
Result: `dist/index.js`
48+
49+
### 3. Build and Run the Orchestrator
50+
51+
```bash
52+
cd schema-conversion-orchestrator
53+
pip install -r requirements.txt
54+
python3 app.py
55+
```
3556

3657
### 3. Test the System
3758

schema-conversion-orchestrator/ConvertersLinkMl.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,3 +158,52 @@ def validate_input(self, schema: str) -> bool:
158158
def validate_output(self, schema: str) -> bool:
159159
# Implement validation logic for LinkML schema
160160
return True
161+
162+
163+
class ConverterOwlToLinkMl(ConverterInternal):
164+
def __init__(self):
165+
super().__init__(
166+
name="LinkMl schema_automator OwlImportEngine",
167+
service_address="internal",
168+
source_format=SchemaLanguage.Owl,
169+
target_format=SchemaLanguage.LinkMl,
170+
supported_features=[
171+
SchemaFeature.Comments,
172+
SchemaFeature.Hierarchy,
173+
SchemaFeature.References,
174+
SchemaFeature.Constraints,
175+
SchemaFeature.Properties,
176+
SchemaFeature.Attributes,
177+
SchemaFeature.Composition
178+
]
179+
)
180+
181+
def converter_logic(self, schema: str) -> str:
182+
schema_dict: dict = json.loads(schema)
183+
# LinkML specific clean-up
184+
schema_dict.pop("$schema", None)
185+
# schema_dict.pop("$id", None)
186+
187+
# if no title is present, add a dummy title
188+
if "title" not in schema_dict:
189+
schema_dict["title"] = "ImportedSchema"
190+
191+
schema = json.dumps(schema_dict)
192+
193+
with tempfile.NamedTemporaryFile("w+", suffix=".owl") as f:
194+
f.write(schema)
195+
f.flush()
196+
file_name = f.name
197+
198+
# Import
199+
import_engine = schema_automator.OwlImportEngine()
200+
schema_def: SchemaDefinition = import_engine.convert(file_name)
201+
return yaml_dumper.dumps(schema_def)
202+
203+
def validate_input(self, schema: str) -> bool:
204+
# Implement validation logic for Owl
205+
return True
206+
207+
def validate_output(self, schema: str) -> bool:
208+
# Implement validation logic for LinkML schema
209+
return True

schema-conversion-orchestrator/external_converters/java/pom.xml

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
http://maven.apache.org/xsd/maven-4.0.0.xsd">
66
<modelVersion>4.0.0</modelVersion>
77

8-
<groupId>com.example</groupId>
8+
<groupId>org.logende</groupId>
99
<artifactId>schema-converter-java</artifactId>
1010
<version>1.0.0</version>
1111
<packaging>jar</packaging>
@@ -36,6 +36,34 @@
3636
<artifactId>jackson-annotations</artifactId>
3737
<version>${jackson.version}</version>
3838
</dependency>
39+
40+
<dependency>
41+
<groupId>com.thaiopensource</groupId>
42+
<artifactId>trang</artifactId>
43+
<version>20091111</version>
44+
45+
</dependency>
46+
<dependency>
47+
<groupId>javax.xml.bind</groupId>
48+
<artifactId>jaxb-api</artifactId>
49+
<version>2.3.0</version>
50+
</dependency>
51+
<dependency>
52+
<groupId>com.sun.xml.bind</groupId>
53+
<artifactId>jaxb-core</artifactId>
54+
<version>2.3.0</version>
55+
</dependency>
56+
<dependency>
57+
<groupId>com.sun.xml.bind</groupId>
58+
<artifactId>jaxb-impl</artifactId>
59+
<version>2.3.0</version>
60+
</dependency>
61+
<dependency>
62+
<groupId>javax.activation</groupId>
63+
<artifactId>activation</artifactId>
64+
<version>1.1.1</version>
65+
</dependency>
66+
3967
</dependencies>
4068

4169
<build>

schema-conversion-orchestrator/external_converters/java/src/main/java/org/logende/converter/ConverterService.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
import java.util.stream.Stream;
1717
import org.logende.converter.converters.JsonSchemaToDtdConverter;
1818
import org.logende.converter.converters.XsdToDtdConverter;
19+
import org.logende.converter.converters.DtdToXsdConverter;
20+
import org.logende.converter.converters.XsdToJsonSchemaConverter;
1921

2022
public class ConverterService {
2123
private final ObjectMapper objectMapper = new ObjectMapper();
@@ -64,6 +66,8 @@ private void loadConverters() {
6466
// Just new them up manually
6567
register(new JsonSchemaToDtdConverter());
6668
register(new XsdToDtdConverter());
69+
register(new DtdToXsdConverter());
70+
register(new XsdToJsonSchemaConverter());
6771
// add more as needed
6872
}
6973

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package org.logende.converter.converters;
2+
3+
import java.io.*;
4+
import com.thaiopensource.relaxng.translate.Driver;
5+
6+
import java.util.Collections;
7+
import java.util.List;
8+
import org.logende.converter.ConverterService;
9+
10+
public class DtdToXsdConverter implements ConverterService.Converter {
11+
12+
@Override
13+
public String getName() {
14+
return "Trang";
15+
}
16+
17+
@Override
18+
public String getSourceFormat() {
19+
return "Dtd";
20+
}
21+
22+
@Override
23+
public String getTargetFormat() {
24+
return "Xsd";
25+
}
26+
27+
@Override
28+
public List<String> getSupportedFeatures() {
29+
return Collections.emptyList();
30+
}
31+
32+
@Override
33+
public String convert(String schema) throws Exception {
34+
// Write DTD schema to temp file
35+
File inputFile = File.createTempFile("schema", ".dtd");
36+
try (FileWriter fw = new FileWriter(inputFile)) {
37+
fw.write(schema);
38+
}
39+
40+
File outputFile = File.createTempFile("schema", ".xsd");
41+
42+
// Call Trang programmatically
43+
String[] args = {inputFile.getAbsolutePath(), outputFile.getAbsolutePath()};
44+
Driver.main(args);
45+
46+
// Read result
47+
String result = new String(java.nio.file.Files.readAllBytes(outputFile.toPath()));
48+
49+
inputFile.delete();
50+
outputFile.delete();
51+
52+
return result;
53+
}
54+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
package org.logende.converter.converters;
2+
3+
import java.io.*;
4+
import java.nio.file.*;
5+
import java.util.*;
6+
import java.util.concurrent.TimeUnit;
7+
import java.net.URISyntaxException;
8+
import org.logende.converter.ConverterService;
9+
10+
public class XsdToJsonSchemaConverter implements ConverterService.Converter {
11+
12+
private static final String JSONIX_JAR_NAME = "jsonix-schema-compiler-full-2.3.9.jar";
13+
private static final int TIMEOUT_SECONDS = 30;
14+
15+
@Override
16+
public String getName() {
17+
return "Jsonix";
18+
}
19+
20+
@Override
21+
public String getSourceFormat() {
22+
return "Xsd";
23+
}
24+
25+
@Override
26+
public String getTargetFormat() {
27+
return "JsonSchema";
28+
}
29+
30+
@Override
31+
public List<String> getSupportedFeatures() {
32+
return Collections.emptyList();
33+
}
34+
35+
@Override
36+
public String convert(String schema) throws Exception {
37+
// Resolve Jsonix JAR path relative to this JAR’s directory
38+
String jarDir = getOwnJarDir();
39+
String jsonixJar = Paths.get(jarDir, "lib", JSONIX_JAR_NAME).toString();
40+
41+
// Validate JAR exists
42+
Path jarPath = Paths.get(jsonixJar);
43+
if (!Files.exists(jarPath)) {
44+
throw new RuntimeException("Jsonix JAR not found at: " + jarPath.toAbsolutePath() +
45+
". Please download and place it in the lib directory.");
46+
}
47+
48+
// Write input XSD to a temp file
49+
Path tempDir = Files.createTempDirectory("xsd2jsonschema");
50+
Path xsdFile = tempDir.resolve("input.xsd");
51+
Files.writeString(xsdFile, schema);
52+
53+
// Output directory for Jsonix
54+
Path outDir = tempDir.resolve("out");
55+
Files.createDirectories(outDir);
56+
57+
try {
58+
// Build command to execute Jsonix JAR
59+
List<String> command = Arrays.asList(
60+
"java",
61+
"-jar", jarPath.toAbsolutePath().toString(),
62+
"-generateJsonSchema",
63+
"-d", outDir.toString(),
64+
xsdFile.toString()
65+
);
66+
67+
// Execute the command
68+
ProcessBuilder pb = new ProcessBuilder(command);
69+
pb.directory(tempDir.toFile());
70+
pb.redirectErrorStream(true);
71+
72+
Process process = pb.start();
73+
74+
// Capture output for debugging
75+
StringBuilder output = new StringBuilder();
76+
try (BufferedReader reader = new BufferedReader(
77+
new InputStreamReader(process.getInputStream()))) {
78+
String line;
79+
while ((line = reader.readLine()) != null) {
80+
output.append(line).append("\n");
81+
}
82+
}
83+
84+
// Wait for process to complete with timeout
85+
boolean finished = process.waitFor(TIMEOUT_SECONDS, TimeUnit.SECONDS);
86+
87+
if (!finished) {
88+
process.destroyForcibly();
89+
throw new RuntimeException("Jsonix process timed out after " + TIMEOUT_SECONDS + " seconds");
90+
}
91+
92+
int exitCode = process.exitValue();
93+
if (exitCode != 0) {
94+
throw new RuntimeException("Jsonix failed with exit code " + exitCode +
95+
". Output:\n" + output.toString());
96+
}
97+
98+
} catch (IOException | InterruptedException e) {
99+
cleanupTempFiles(tempDir);
100+
throw new RuntimeException("Failed to execute Jsonix: " + e.getMessage(), e);
101+
}
102+
103+
// Find first .json schema file produced
104+
String result = findJsonSchemaFile(outDir);
105+
106+
// Clean up temp files
107+
cleanupTempFiles(tempDir);
108+
109+
if (result != null) {
110+
return result;
111+
}
112+
113+
throw new RuntimeException("No JSON Schema produced by Jsonix.");
114+
}
115+
116+
private String findJsonSchemaFile(Path outDir) throws IOException {
117+
// Look for .json files in the output directory
118+
try (DirectoryStream<Path> stream = Files.newDirectoryStream(outDir, "*.json")) {
119+
for (Path file : stream) {
120+
return Files.readString(file);
121+
}
122+
}
123+
124+
// Also check subdirectories (Jsonix sometimes creates nested structure)
125+
try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(outDir,
126+
path -> Files.isDirectory(path))) {
127+
for (Path subDir : dirStream) {
128+
try (DirectoryStream<Path> jsonStream = Files.newDirectoryStream(subDir, "*.json")) {
129+
for (Path file : jsonStream) {
130+
return Files.readString(file);
131+
}
132+
}
133+
}
134+
}
135+
136+
return null;
137+
}
138+
139+
private void cleanupTempFiles(Path tempDir) {
140+
try {
141+
Files.walk(tempDir)
142+
.sorted(Comparator.reverseOrder())
143+
.map(Path::toFile)
144+
.forEach(File::delete);
145+
} catch (IOException e) {
146+
System.err.println("Warning: Failed to clean up temporary files in " +
147+
tempDir + ": " + e.getMessage());
148+
}
149+
}
150+
151+
152+
private String getOwnJarDir() throws URISyntaxException {
153+
// Get path of the running JAR
154+
File jarFile = new File(
155+
ConverterService.class.getProtectionDomain()
156+
.getCodeSource()
157+
.getLocation()
158+
.toURI()
159+
);
160+
// Return the parent directory
161+
return jarFile.getParentFile().getAbsolutePath();
162+
}
163+
164+
165+
}

0 commit comments

Comments
 (0)