@@ -156,20 +156,73 @@ CompileOptions withFetchPolicy(FetchPolicy policy) {
156156 Objects .requireNonNull (policy , "policy" );
157157 return new CompileOptions (remoteFetcher , refRegistry , policy );
158158 }
159+
160+ /// Delegating fetcher selecting implementation per URI scheme
161+ static final class DelegatingRemoteFetcher implements RemoteFetcher {
162+ private final Map <String , RemoteFetcher > byScheme ;
163+
164+ DelegatingRemoteFetcher (RemoteFetcher ... fetchers ) {
165+ Objects .requireNonNull (fetchers , "fetchers" );
166+ if (fetchers .length == 0 ) {
167+ throw new IllegalArgumentException ("At least one RemoteFetcher required" );
168+ }
169+ Map <String , RemoteFetcher > map = new HashMap <>();
170+ for (RemoteFetcher fetcher : fetchers ) {
171+ Objects .requireNonNull (fetcher , "fetcher" );
172+ String scheme = Objects .requireNonNull (fetcher .scheme (), "fetcher.scheme()" ).toLowerCase (Locale .ROOT );
173+ if (scheme .isEmpty ()) {
174+ throw new IllegalArgumentException ("RemoteFetcher scheme must not be empty" );
175+ }
176+ if (map .putIfAbsent (scheme , fetcher ) != null ) {
177+ throw new IllegalArgumentException ("Duplicate RemoteFetcher for scheme: " + scheme );
178+ }
179+ }
180+ this .byScheme = Map .copyOf (map );
181+ }
182+
183+ @ Override
184+ public String scheme () {
185+ return "delegating" ;
186+ }
187+
188+ @ Override
189+ public FetchResult fetch (java .net .URI uri , FetchPolicy policy ) {
190+ Objects .requireNonNull (uri , "uri" );
191+ String scheme = Optional .ofNullable (uri .getScheme ())
192+ .map (s -> s .toLowerCase (Locale .ROOT ))
193+ .orElse ("" );
194+ RemoteFetcher fetcher = byScheme .get (scheme );
195+ if (fetcher == null ) {
196+ LOG .severe (() -> "ERROR: FETCH: " + uri + " - unsupported scheme" );
197+ throw new RemoteResolutionException (uri , RemoteResolutionException .Reason .POLICY_DENIED ,
198+ "No RemoteFetcher registered for scheme: " + scheme );
199+ }
200+ return fetcher .fetch (uri , policy );
201+ }
202+ }
159203 }
160204
161205 /// Remote fetcher SPI for loading external schema documents
162206 interface RemoteFetcher {
207+ String scheme ();
163208 FetchResult fetch (java .net .URI uri , FetchPolicy policy ) throws RemoteResolutionException ;
164209
165210 static RemoteFetcher disallowed () {
166- return (uri , policy ) -> {
167- LOG .severe (() -> "ERROR: FETCH: " + uri + " - policy POLICY_DENIED" );
168- throw new RemoteResolutionException (
169- Objects .requireNonNull (uri , "uri" ),
170- RemoteResolutionException .Reason .POLICY_DENIED ,
171- "Remote fetching is disabled"
172- );
211+ return new RemoteFetcher () {
212+ @ Override
213+ public String scheme () {
214+ return "<disabled>" ;
215+ }
216+
217+ @ Override
218+ public FetchResult fetch (java .net .URI uri , FetchPolicy policy ) {
219+ LOG .severe (() -> "ERROR: FETCH: " + uri + " - policy POLICY_DENIED" );
220+ throw new RemoteResolutionException (
221+ Objects .requireNonNull (uri , "uri" ),
222+ RemoteResolutionException .Reason .POLICY_DENIED ,
223+ "Remote fetching is disabled"
224+ );
225+ }
173226 };
174227 }
175228
@@ -432,59 +485,30 @@ static JsonValue fetchIfNeeded(java.net.URI docUri,
432485
433486 // MVF: Fetch remote document using RemoteFetcher from compile options
434487 LOG .finer (() -> "fetchIfNeeded: fetching remote document: " + docUri );
435- try {
436- // Get the base URI without fragment for document fetching
437- String fragment = docUri .getFragment ();
438- java .net .URI docUriWithoutFragment = fragment != null ?
439- java .net .URI .create (docUri .toString ().substring (0 , docUri .toString ().indexOf ('#' ))) :
440- docUri ;
441-
442- LOG .finest (() -> "fetchIfNeeded: document URI without fragment: " + docUriWithoutFragment );
443-
444- // Enforce allowed schemes
445- String scheme = docUriWithoutFragment .getScheme ();
446- if (scheme == null || !compileOptions .fetchPolicy ().allowedSchemes ().contains (scheme )) {
447- throw new RemoteResolutionException (
448- docUriWithoutFragment ,
449- RemoteResolutionException .Reason .POLICY_DENIED ,
450- "Scheme not allowed by policy: " + scheme
451- );
452- }
453-
454- // Prefer a local file mapping for tests when using file:// URIs
455- java .net .URI fetchUri = docUriWithoutFragment ;
456- if ("file" .equalsIgnoreCase (scheme )) {
457- String base = System .getProperty ("json.schema.test.resources" , "src/test/resources" );
458- String path = fetchUri .getPath ();
459- if (path != null && path .startsWith ("/" )) path = path .substring (1 );
460- java .nio .file .Path abs = java .nio .file .Paths .get (base , path ).toAbsolutePath ();
461- java .net .URI alt = abs .toUri ();
462- fetchUri = alt ;
463- LOG .fine (() -> "fetchIfNeeded: Using file mapping for fetch: " + alt + " (original=" + docUriWithoutFragment + ")" );
464- }
465-
466- // Fetch via provided RemoteFetcher to ensure consistent policy/normalization
467- RemoteFetcher .FetchResult fetchResult ;
468- try {
469- fetchResult = compileOptions .remoteFetcher ().fetch (fetchUri , compileOptions .fetchPolicy ());
470- } catch (RemoteResolutionException e1 ) {
471- // On mapping miss, retry original URI once
472- if (!fetchUri .equals (docUriWithoutFragment )) {
473- fetchResult = compileOptions .remoteFetcher ().fetch (docUriWithoutFragment , compileOptions .fetchPolicy ());
474- } else {
475- throw e1 ;
476- }
477- }
478- JsonValue fetchedDocument = fetchResult .document ();
479-
480- LOG .finer (() -> "fetchIfNeeded: successfully fetched remote document: " + docUriWithoutFragment + ", document type: " + fetchedDocument .getClass ().getSimpleName ());
481- return fetchedDocument ;
482-
483- } catch (Exception e ) {
484- // Network failures are logged by the fetcher; suppress here to avoid duplication
485- throw new RemoteResolutionException (docUri , RemoteResolutionException .Reason .NETWORK_ERROR ,
486- "Failed to fetch remote document: " + docUri , e );
487- }
488+ // Get the base URI without fragment for document fetching
489+ String fragment = docUri .getFragment ();
490+ java .net .URI docUriWithoutFragment = fragment != null ?
491+ java .net .URI .create (docUri .toString ().substring (0 , docUri .toString ().indexOf ('#' ))) :
492+ docUri ;
493+
494+ LOG .finest (() -> "fetchIfNeeded: document URI without fragment: " + docUriWithoutFragment );
495+
496+ // Enforce allowed schemes
497+ String scheme = docUriWithoutFragment .getScheme ();
498+ if (scheme == null || !compileOptions .fetchPolicy ().allowedSchemes ().contains (scheme )) {
499+ throw new RemoteResolutionException (
500+ docUriWithoutFragment ,
501+ RemoteResolutionException .Reason .POLICY_DENIED ,
502+ "Scheme not allowed by policy: " + scheme
503+ );
504+ }
505+
506+ RemoteFetcher .FetchResult fetchResult =
507+ compileOptions .remoteFetcher ().fetch (docUriWithoutFragment , compileOptions .fetchPolicy ());
508+ JsonValue fetchedDocument = fetchResult .document ();
509+
510+ LOG .finer (() -> "fetchIfNeeded: successfully fetched remote document: " + docUriWithoutFragment + ", document type: " + fetchedDocument .getClass ().getSimpleName ());
511+ return fetchedDocument ;
488512 }
489513
490514
0 commit comments