Repository: xlate/staedi
Pull request #239 (2021-12-21 13:26:55)

Allow loading included schemas on the class-path

coveralls: ## Pull Request Test Coverage Report for [Build 1606980923](https://coveralls.io/builds/45159803)

* **14** of **15**   **(93.33%)**  changed or added relevant lines in **2** files are covered.
* No unchanged relevant lines lost coverage.
* Overall coverage decreased (**-0.01%**) to **98.196%**

---

|  Changes Missing Coverage | Covered Lines | Changed/Added Lines | % |
| :-----|--------------|--------|---: |
| [src/main/java/io/xlate/edi/internal/schema/ClasspathURLStreamHandler.java](https://coveralls.io/builds/45159803/source?filename=src%2Fmain%2Fjava%2Fio%2Fxlate%2Fedi%2Finternal%2Fschema%2FClasspathURLStreamHandler.java#L23) | 7 | 8 | 87.5%
<!-- | **Total:** | **14** | **15** | **93.33%** | -->


|  Totals | [![Coverage Status](https://coveralls.io/builds/45159803/badge)](https://coveralls.io/builds/45159803) |
| :-- | --: |
| Change from base [Build 1602241622](https://coveralls.io/builds/45130238): |  -0.01% |
| Covered Lines: | 4953 |
| Relevant Lines: | 5044 |

---
##### 💛  - [Coveralls](https://coveralls.io)

sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!&nbsp; &nbsp; ![Quality Gate passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/passed-16px.png 'Quality Gate passed')

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=xlate_staedi&pullRequest=239&resolved=false&types=BUG) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=xlate_staedi&pullRequest=239&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=xlate_staedi&pullRequest=239&resolved=false&types=BUG)  
[![Vulnerability](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability-16px.png 'Vulnerability')](https://sonarcloud.io/project/issues?id=xlate_staedi&pullRequest=239&resolved=false&types=VULNERABILITY) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=xlate_staedi&pullRequest=239&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=xlate_staedi&pullRequest=239&resolved=false&types=VULNERABILITY)  
[![Security Hotspot](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot-16px.png 'Security Hotspot')](https://sonarcloud.io/project/security_hotspots?id=xlate_staedi&pullRequest=239&resolved=false&types=SECURITY_HOTSPOT) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/security_hotspots?id=xlate_staedi&pullRequest=239&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=xlate_staedi&pullRequest=239&resolved=false&types=SECURITY_HOTSPOT)  
[![Code Smell](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell-16px.png 'Code Smell')](https://sonarcloud.io/project/issues?id=xlate_staedi&pullRequest=239&resolved=false&types=CODE_SMELL) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=xlate_staedi&pullRequest=239&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=xlate_staedi&pullRequest=239&resolved=false&types=CODE_SMELL)

[![90.5%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/90-16px.png '90.5%')](https://sonarcloud.io/component_measures?id=xlate_staedi&pullRequest=239&metric=new_coverage&view=list) [90.5% Coverage](https://sonarcloud.io/component_measures?id=xlate_staedi&pullRequest=239&metric=new_coverage&view=list)  
[![0.0%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3-16px.png '0.0%')](https://sonarcloud.io/component_measures?id=xlate_staedi&pullRequest=239&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=xlate_staedi&pullRequest=239&metric=new_duplicated_lines_density&view=list)



Patch:
diff --git a/src/main/java/io/xlate/edi/internal/schema/ClasspathURLStreamHandler.java b/src/main/java/io/xlate/edi/internal/schema/ClasspathURLStreamHandler.java
new file mode 100644
index 0000000..bb69521
--- /dev/null
+++ b/src/main/java/io/xlate/edi/internal/schema/ClasspathURLStreamHandler.java
@@ -0,0 +1,28 @@
+package io.xlate.edi.internal.schema;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLStreamHandler;
+
+class ClasspathURLStreamHandler extends URLStreamHandler {
+    /** The classloader to find resources from. */
+    private final ClassLoader classLoader;
+
+    public ClasspathURLStreamHandler(ClassLoader classLoader) {
+        this.classLoader = classLoader;
+    }
+
+    @Override
+    protected URLConnection openConnection(URL u) throws IOException {
+        final String resourcePath = u.getPath();
+        final URL resourceUrl = classLoader.getResource(resourcePath);
+
+        if (resourceUrl == null) {
+            throw new FileNotFoundException("Class-path resource not found: " + resourcePath);
+        }
+
+        return resourceUrl.openConnection();
+    }
+}
diff --git a/src/main/java/io/xlate/edi/internal/schema/SchemaReaderV4.java b/src/main/java/io/xlate/edi/internal/schema/SchemaReaderV4.java
index 5febfb4..aaf6f47 100644
--- a/src/main/java/io/xlate/edi/internal/schema/SchemaReaderV4.java
+++ b/src/main/java/io/xlate/edi/internal/schema/SchemaReaderV4.java
@@ -48,7 +48,18 @@ public class SchemaReaderV4 extends SchemaReaderV3 {
                 }
             }
 
-            URL schemaLocation = context != null ? new URL(context, location) : new URL(location);
+            URL schemaLocation;
+
+            if (location.startsWith("classpath:")) {
+                ClassLoader loader = Thread.currentThread().getContextClassLoader();
+                if (location.startsWith("classpath:/")) {
+                    location = location.substring(0, "classpath:".length()) + location.substring("classpath:/".length());
+                }
+                schemaLocation = new URL(null, location, new ClasspathURLStreamHandler(loader));
+            } else {
+                schemaLocation = new URL(context, location);
+            }
+
             types.putAll(StaEDISchemaFactory.readSchemaTypes(schemaLocation, super.properties));
             reader.nextTag(); // End of include
         } catch (Exception e) {


Test patch:
diff --git a/src/test/java/io/xlate/edi/internal/schema/ClasspathURLStreamHandlerTest.java b/src/test/java/io/xlate/edi/internal/schema/ClasspathURLStreamHandlerTest.java
new file mode 100644
index 0000000..7d79125
--- /dev/null
+++ b/src/test/java/io/xlate/edi/internal/schema/ClasspathURLStreamHandlerTest.java
@@ -0,0 +1,40 @@
+package io.xlate.edi.internal.schema;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLConnection;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+class ClasspathURLStreamHandlerTest {
+
+    ClasspathURLStreamHandler cut;
+
+    @BeforeEach
+    void setup() {
+        cut = new ClasspathURLStreamHandler(getClass().getClassLoader());
+    }
+
+    @Test
+    void testResourceUrlResolvesNull() throws MalformedURLException {
+        URL target = new URL(null, "classpath:does/not/exist.txt", cut);
+        @SuppressWarnings("unused")
+        FileNotFoundException ex = assertThrows(FileNotFoundException.class, () -> {
+            target.openConnection();
+        });
+    }
+
+    @Test
+    void testResourceUrlFound() throws IOException {
+        URL target = new URL(null, "classpath:logging.properties", cut);
+        URLConnection connection = target.openConnection();
+        assertNotNull(connection);
+    }
+
+}
diff --git a/src/test/resources/x12/005010X222/837_REF_impls.xml b/src/test/resources/x12/005010X222/837_REF_impls.xml
index d27d657..4e71e66 100644
--- a/src/test/resources/x12/005010X222/837_REF_impls.xml
+++ b/src/test/resources/x12/005010X222/837_REF_impls.xml
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
 <schema xmlns="http://xlate.io/EDISchema/v4">
 
-  <include schemaLocation="file:./target/test-classes/x12/005010/837.xml"/>
+  <include schemaLocation="classpath:/x12/005010/837.xml"/>
 
   <implementation>
     <sequence>
diff --git a/src/test/resources/x12/005010X222/837_loop1000_only.xml b/src/test/resources/x12/005010X222/837_loop1000_only.xml
index a1355d6..8a28455 100644
--- a/src/test/resources/x12/005010X222/837_loop1000_only.xml
+++ b/src/test/resources/x12/005010X222/837_loop1000_only.xml
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
 <schema xmlns="http://xlate.io/EDISchema/v4">
 
-  <include schemaLocation="file:./target/test-classes/x12/005010/837.xml"/>
+  <include schemaLocation="classpath:x12/005010/837.xml"/>
 
   <implementation>
     <sequence>