2828import java .io .File ;
2929import java .io .IOException ;
3030import java .util .*;
31+ import java .util .function .Supplier ;
3132import java .util .regex .Pattern ;
3233
3334/**
34- * Default implementation of ModulesLoader. Loads everything except assets via the REST API. Assets are either loaded
35- * via an XccAssetLoader (faster) or via a RestApiAssetLoader (slower, but doesn't require additional privileges).
35+ * Default implementation of ModulesLoader.
36+ * <p>
37+ * REST modules and non-REST modules are handled in different ways. Non-REST modules are loaded via an instance of
38+ * AssetFileLoader, which most likely loads modules via port 8000, directly into the desired modules database.
39+ * </p>
40+ * <p>
41+ * REST modules however have to be loaded via the REST server that they're targeted for. This class also loads them
42+ * by default via multiple threads to increase performance, as it can take a couple seconds to load each set of
43+ * search options, service, and transform.
44+ * </p>
45+ * <p>
46+ * To handle errors while loading REST modules in parallel, an implementation of LoadModulesFailureListener can be
47+ * added to this class (ideally would have just been a Consumer that receives a Throwable). By default, an instance of
48+ * SimpleLoadModulesFailureListener is added. Also by default, any Throwable caught by this class will be rethrown
49+ * after all REST modules have been loaded. This behavior can be disabled by setting rethrowRestModulesFailure to
50+ * false, in which case the failures are just logged.
51+ * </p>
3652 */
3753public class DefaultModulesLoader extends LoggingObject implements ModulesLoader {
3854
@@ -52,6 +68,7 @@ public class DefaultModulesLoader extends LoggingObject implements ModulesLoader
5268 private TokenReplacer tokenReplacer ;
5369
5470 private List <LoadModulesFailureListener > failureListeners = new ArrayList <>();
71+ private boolean rethrowRestModulesFailure = true ;
5572
5673 /**
5774 * When set to true, exceptions thrown while loading transforms and resources will be caught and logged, and the
@@ -136,6 +153,7 @@ public Set<Resource> loadModules(String baseDir, ModulesFinder modulesFinder, Da
136153 loadResources (modules , loadedModules );
137154
138155 waitForTaskExecutorToFinish ();
156+ rethrowRestModulesFailureIfOneExists ();
139157
140158 if (logger .isDebugEnabled ()) {
141159 logger .debug ("Finished loading modules from base directory: " + baseDir );
@@ -144,6 +162,20 @@ public Set<Resource> loadModules(String baseDir, ModulesFinder modulesFinder, Da
144162 return loadedModules ;
145163 }
146164
165+ protected void rethrowRestModulesFailureIfOneExists () {
166+ if (failureListeners != null && rethrowRestModulesFailure ) {
167+ for (LoadModulesFailureListener listener : failureListeners ) {
168+ if (listener instanceof Supplier ) {
169+ Object o = ((Supplier ) listener ).get ();
170+ if (o instanceof Throwable ) {
171+ Throwable t = (Throwable ) o ;
172+ throw new RuntimeException ("Error occurred while loading REST modules: " + t .getMessage (), t );
173+ }
174+ }
175+ }
176+ }
177+ }
178+
147179 /**
148180 * If an AsyncTaskExecutor is used for loading options/services/transforms, we need to wait for the tasks to complete
149181 * before we e.g. release the DatabaseClient.
@@ -273,12 +305,12 @@ protected void applyXmlProperties(ServerConfigurationManager mgr, Resource r, Fi
273305 protected File getFileFromResource (Resource r ) {
274306 try {
275307 return r .getFile ();
276- } catch (IOException ex ) {}
308+ } catch (IOException ex ) {
309+ }
277310 return null ;
278311 }
279312
280313 /**
281- *
282314 * @param modules
283315 * @param loadedModules
284316 */
@@ -306,7 +338,7 @@ protected void loadAssets(Modules modules, Set<Resource> loadedModules) {
306338 assetFileLoader .initializeDocumentFileReader ();
307339 DocumentFileReader dfr = assetFileLoader .getDocumentFileReader ();
308340 if (dfr instanceof DefaultDocumentFileReader ) {
309- DefaultDocumentFileReader reader = (DefaultDocumentFileReader )dfr ;
341+ DefaultDocumentFileReader reader = (DefaultDocumentFileReader ) dfr ;
310342 reader .addDocumentFileProcessor (documentFile -> {
311343 File f = documentFile .getFile ();
312344 if (f == null ) {
@@ -339,7 +371,6 @@ protected void loadAssets(Modules modules, Set<Resource> loadedModules) {
339371 }
340372
341373 /**
342- *
343374 * @param modules
344375 * @param loadedModules
345376 */
@@ -356,7 +387,6 @@ protected void loadQueryOptions(Modules modules, Set<Resource> loadedModules) {
356387 }
357388
358389 /**
359- *
360390 * @param modules
361391 * @param loadedModules
362392 */
@@ -385,7 +415,6 @@ protected void loadTransforms(Modules modules, Set<Resource> loadedModules) {
385415 }
386416
387417 /**
388- *
389418 * @param modules
390419 * @param loadedModules
391420 */
@@ -414,7 +443,6 @@ protected void loadResources(Modules modules, Set<Resource> loadedModules) {
414443 }
415444
416445 /**
417- *
418446 * @param modules
419447 * @param loadedModules
420448 */
@@ -431,7 +459,6 @@ protected void loadNamespaces(Modules modules, Set<Resource> loadedModules) {
431459 }
432460
433461 /**
434- *
435462 * @param r
436463 * @param metadata
437464 * @param methodParams
@@ -456,7 +483,6 @@ public Resource installService(Resource r, final ExtensionMetadata metadata, fin
456483 }
457484
458485 /**
459- *
460486 * @param r
461487 * @param metadata
462488 * @return
@@ -472,21 +498,20 @@ public Resource installTransform(Resource r, final ExtensionMetadata metadata) {
472498
473499 StringHandle h = new StringHandle (readAndReplaceTokens (r ));
474500 executeTask (() -> {
475- if (FilenameUtil .isXslFile (filename )) {
476- mgr .writeXSLTransform (transformName , h , metadata );
477- } else if (FilenameUtil .isJavascriptFile (filename )) {
478- mgr .writeJavascriptTransform (transformName , h , metadata );
479- } else {
480- mgr .writeXQueryTransform (transformName , h , metadata );
481- }
482- });
501+ if (FilenameUtil .isXslFile (filename )) {
502+ mgr .writeXSLTransform (transformName , h , metadata );
503+ } else if (FilenameUtil .isJavascriptFile (filename )) {
504+ mgr .writeJavascriptTransform (transformName , h , metadata );
505+ } else {
506+ mgr .writeXQueryTransform (transformName , h , metadata );
507+ }
508+ });
483509 updateTimestamp (r );
484510
485511 return r ;
486512 }
487513
488514 /**
489- *
490515 * @param r
491516 * @return
492517 */
@@ -502,12 +527,12 @@ public Resource installQueryOptions(Resource r) {
502527
503528 StringHandle h = new StringHandle (readAndReplaceTokens (r ));
504529 executeTask (() -> {
505- if (filename .endsWith (".json" )) {
506- mgr .writeOptions (name , h .withFormat (Format .JSON ));
507- } else {
508- mgr .writeOptions (name , h );
509- }
510- });
530+ if (filename .endsWith (".json" )) {
531+ mgr .writeOptions (name , h .withFormat (Format .JSON ));
532+ } else {
533+ mgr .writeOptions (name , h );
534+ }
535+ });
511536 updateTimestamp (r );
512537 return r ;
513538 }
@@ -543,15 +568,13 @@ protected void executeTask(Runnable r) {
543568 taskExecutor .execute (() -> {
544569 try {
545570 r .run ();
546- }
547- catch (Exception e ) {
571+ } catch (Exception e ) {
548572 failureListeners .forEach (listener -> listener .processFailure (e ));
549573 }
550574 });
551575 }
552576
553577 /**
554- *
555578 * @param r
556579 * @return
557580 */
@@ -582,7 +605,6 @@ public Resource installNamespace(Resource r) {
582605 }
583606
584607 /**
585- *
586608 * @param r
587609 * @return
588610 */
@@ -685,7 +707,8 @@ private boolean hasFileBeenModified(Resource resource) {
685707 try {
686708 File file = resource .getFile ();
687709 modified = modulesManager .hasFileBeenModifiedSinceLastLoaded (file );
688- } catch (IOException e ) {}
710+ } catch (IOException e ) {
711+ }
689712 }
690713 return modified ;
691714 }
@@ -695,7 +718,8 @@ private void updateTimestamp(Resource resource) {
695718 try {
696719 File file = resource .getFile ();
697720 modulesManager .saveLastLoadedTimestamp (file , new Date ());
698- } catch (IOException e ) {}
721+ } catch (IOException e ) {
722+ }
699723 }
700724 }
701725
@@ -718,4 +742,8 @@ public void setTokenReplacer(TokenReplacer tokenReplacer) {
718742 public void setIncludeFilenamePattern (Pattern includeFilenamePattern ) {
719743 this .includeFilenamePattern = includeFilenamePattern ;
720744 }
745+
746+ public void setRethrowRestModulesFailure (boolean rethrowRestModulesFailure ) {
747+ this .rethrowRestModulesFailure = rethrowRestModulesFailure ;
748+ }
721749}
0 commit comments