Skip to content
This repository was archived by the owner on Sep 16, 2024. It is now read-only.

Commit 80d9684

Browse files
committed
#95 Rethrowing REST module failures
1 parent 7dad6a5 commit 80d9684

File tree

6 files changed

+99
-34
lines changed

6 files changed

+99
-34
lines changed

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
group=com.marklogic
22
javadocsDir=../gh-pages-marklogic-java/javadocs
3-
version=3.6.0
3+
version=3.7.0-dev

src/main/java/com/marklogic/client/ext/modulesloader/impl/DefaultModulesLoader.java

Lines changed: 60 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,27 @@
2828
import java.io.File;
2929
import java.io.IOException;
3030
import java.util.*;
31+
import java.util.function.Supplier;
3132
import 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
*/
3753
public 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
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
package com.marklogic.client.ext.modulesloader.impl;
22

3+
/**
4+
* Ideally this would just have been a Consumer that receives an instance of Throwable. And also, it's only for
5+
* loading REST modules, which DefaultModulesLoader loads by default in parallel.
6+
*/
37
public interface LoadModulesFailureListener {
8+
49
void processFailure(Throwable throwable);
10+
511
}

src/main/java/com/marklogic/client/ext/modulesloader/impl/SimpleLoadModulesFailureListener.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,24 @@
22

33
import com.marklogic.client.ext.helper.LoggingObject;
44

5-
public class SimpleLoadModulesFailureListener extends LoggingObject implements LoadModulesFailureListener {
5+
import java.util.function.Supplier;
6+
7+
public class SimpleLoadModulesFailureListener extends LoggingObject implements LoadModulesFailureListener, Supplier<Throwable> {
8+
9+
private Throwable firstThrowable;
610

711
@Override
812
public void processFailure(Throwable throwable) {
13+
if (firstThrowable == null) {
14+
firstThrowable = throwable;
15+
}
916
if (logger.isErrorEnabled()) {
1017
logger.error("Error caught while loading modules, cause: " + throwable.getMessage(), throwable);
1118
}
1219
}
20+
21+
@Override
22+
public Throwable get() {
23+
return firstThrowable;
24+
}
1325
}

src/test/java/com/marklogic/client/ext/modulesloader/impl/LoadModulesTest.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,22 @@ public void pathWithSpaces() {
117117
assertEquals("<example/>", moduleXml.trim());
118118
}
119119

120+
@Test
121+
public void invalidRestModule() {
122+
String dir = Paths.get("src", "test", "resources", "invalid-rest-modules").toString();
123+
124+
try {
125+
modulesLoader.loadModules(dir, new DefaultModulesFinder(), client);
126+
fail("Loading modules should have failed because of an invalid REST options file");
127+
} catch (RuntimeException re) {
128+
assertTrue(re.getMessage().contains("Unexpected character"));
129+
}
130+
131+
// This should now succeed since DefaultModulesLoader won't rethrow the REST module failure
132+
modulesLoader.setRethrowRestModulesFailure(false);
133+
modulesLoader.loadModules(dir, new DefaultModulesFinder(), client);
134+
}
135+
120136
/**
121137
* This test is a little brittle because it assumes the URI of options/services/transforms that are loaded
122138
* into the Modules database.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<options xmlns="http://marklogic.com/appservices/search">
2+
<TheseAre>invalid</TheseAre
3+
</options>

0 commit comments

Comments
 (0)