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

Commit 2407f79

Browse files
authored
Merge pull request #194 from marklogic/feature/qbv-tweak
Reworking TDE/QBV processors
2 parents a927932 + f01ca0b commit 2407f79

File tree

13 files changed

+114
-191
lines changed

13 files changed

+114
-191
lines changed

src/main/java/com/marklogic/client/ext/schemasloader/impl/DefaultSchemasLoader.java

Lines changed: 31 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
import com.marklogic.client.ext.modulesloader.impl.DefaultFileFilter;
2424
import com.marklogic.client.ext.schemasloader.SchemasLoader;
2525
import com.marklogic.client.io.DocumentMetadataHandle;
26-
import org.springframework.util.StringUtils;
2726

2827
import java.util.ArrayList;
2928
import java.util.List;
@@ -33,30 +32,25 @@
3332

3433
public class DefaultSchemasLoader extends GenericFileLoader implements SchemasLoader {
3534

36-
private DatabaseClient schemasDatabaseClient;
37-
private String tdeValidationDatabase;
38-
protected QbvDocumentFileProcessor qbvDocumentFileProcessor;
35+
private final DatabaseClient schemasDatabaseClient;
36+
private final DatabaseClient contentDatabaseClient;
37+
private final boolean validateTdeTemplates;
38+
private QbvDocumentFileProcessor qbvDocumentFileProcessor;
3939

4040
/**
41-
* Simplest constructor for using this class. Just provide a DatabaseClient, and this will use sensible defaults for
42-
* how documents are read and written. Note that the DatabaseClient will not be released after this class is done
43-
* with it, as this class wasn't the one that created it.
44-
*
45-
* @param schemasDatabaseClient
41+
* @param schemasDatabaseClient for loading files into an application's schemas database
42+
* @param contentDatabaseClient for validating TDEs and generating QBVs
4643
*/
47-
public DefaultSchemasLoader(DatabaseClient schemasDatabaseClient) {
48-
this(schemasDatabaseClient, null);
44+
public DefaultSchemasLoader(DatabaseClient schemasDatabaseClient, DatabaseClient contentDatabaseClient) {
45+
this(schemasDatabaseClient, contentDatabaseClient, true);
4946
}
5047

5148
/**
52-
* If you want to validate TDE templates before they're loaded, you need to provide a second DatabaseClient that
53-
* connects to the content database associated with the schemas database that schemas will be loaded into. This is
54-
* because the "tde.validate" function must run against the content database.
55-
*
56-
* @param schemasDatabaseClient
57-
* @param tdeValidationDatabase
49+
* @param schemasDatabaseClient for loading files into an application's schemas database
50+
* @param contentDatabaseClient for validating TDEs and generating QBVs
51+
* @param validateTdeTemplates if false, TDEs will not be validated nor loaded via tde.templateBatchInsert
5852
*/
59-
public DefaultSchemasLoader(DatabaseClient schemasDatabaseClient, String tdeValidationDatabase) {
53+
public DefaultSchemasLoader(DatabaseClient schemasDatabaseClient, DatabaseClient contentDatabaseClient, boolean validateTdeTemplates) {
6054
super(((Supplier<BatchWriter>) () -> {
6155
RestBatchWriter writer = new RestBatchWriter(schemasDatabaseClient);
6256
// Default this to 1, as it's not typical to have such a large number of schemas to load that multiple threads
@@ -67,31 +61,20 @@ public DefaultSchemasLoader(DatabaseClient schemasDatabaseClient, String tdeVali
6761
}).get());
6862

6963
this.schemasDatabaseClient = schemasDatabaseClient;
70-
this.tdeValidationDatabase = tdeValidationDatabase;
71-
initializeDefaultSchemasLoader();
72-
}
64+
this.contentDatabaseClient = contentDatabaseClient;
65+
this.validateTdeTemplates = validateTdeTemplates;
7366

74-
/**
75-
* Assumes that the BatchWriter has already been initialized.
76-
*
77-
* @param batchWriter
78-
* @deprecated Since 4.6.0; this class needs a DatabaseClient for the schemas database passed to it so that it can
79-
* pass that client on to specific file processors.
80-
*/
81-
@Deprecated
82-
public DefaultSchemasLoader(BatchWriter batchWriter) {
83-
super(batchWriter);
84-
initializeDefaultSchemasLoader();
85-
}
67+
if (this.contentDatabaseClient != null) {
68+
this.qbvDocumentFileProcessor = new QbvDocumentFileProcessor(this.schemasDatabaseClient, this.contentDatabaseClient);
69+
addDocumentFileProcessor(this.qbvDocumentFileProcessor);
70+
}
71+
72+
if (this.validateTdeTemplates && this.contentDatabaseClient != null) {
73+
addDocumentFileProcessor(new TdeDocumentFileProcessor(this.contentDatabaseClient));
74+
} else {
75+
addDocumentFileProcessor(new TdeDocumentFileProcessor(null));
76+
}
8677

87-
/**
88-
* Adds the DocumentFileProcessors and FileFilters specific to loading schemas, which will then be used to construct
89-
* a DocumentFileReader by the parent class.
90-
*/
91-
protected void initializeDefaultSchemasLoader() {
92-
this.qbvDocumentFileProcessor = new QbvDocumentFileProcessor(this.schemasDatabaseClient, this.tdeValidationDatabase);
93-
addDocumentFileProcessor(new TdeDocumentFileProcessor(this.schemasDatabaseClient, this.tdeValidationDatabase));
94-
addDocumentFileProcessor(this.qbvDocumentFileProcessor);
9578
addFileFilter(new DefaultFileFilter());
9679
}
9780

@@ -107,7 +90,7 @@ public List<DocumentFile> loadSchemas(String... paths) {
10790
final List<DocumentFile> documentFiles = super.getDocumentFiles(paths);
10891

10992
if (!documentFiles.isEmpty()) {
110-
if (TdeUtil.templateBatchInsertSupported(schemasDatabaseClient) && StringUtils.hasText(tdeValidationDatabase)) {
93+
if (this.validateTdeTemplates && TdeUtil.templateBatchInsertSupported(schemasDatabaseClient) && contentDatabaseClient != null) {
11194
SchemaFiles schemaFiles = readSchemaFiles(documentFiles);
11295
if (!schemaFiles.tdeFiles.isEmpty()) {
11396
loadTdeTemplatesViaBatchInsert(schemaFiles.tdeFiles);
@@ -122,7 +105,10 @@ public List<DocumentFile> loadSchemas(String... paths) {
122105
writeDocumentFiles(documentFiles);
123106
}
124107
}
125-
this.qbvDocumentFileProcessor.processQbvFiles();
108+
109+
if (this.qbvDocumentFileProcessor != null) {
110+
this.qbvDocumentFileProcessor.processQbvFiles();
111+
}
126112

127113
return documentFiles;
128114
}
@@ -152,11 +138,10 @@ private void loadTdeTemplatesViaBatchInsert(List<DocumentFile> tdeFiles) {
152138
tdeFiles.stream().map(documentFile -> documentFile.getFile().getName()).collect(Collectors.toList()));
153139

154140
String query = buildTdeBatchInsertQuery(tdeFiles);
155-
StringBuilder script = new StringBuilder("declareUpdate(); xdmp.invokeFunction(function() {var tde = require('/MarkLogic/tde.xqy');");
141+
StringBuilder script = new StringBuilder("declareUpdate(); const tde = require('/MarkLogic/tde.xqy'); ");
156142
script.append(query);
157-
script.append(format("}, {database: xdmp.database('%s')})", tdeValidationDatabase));
158143
try {
159-
schemasDatabaseClient.newServerEval().javascript(script.toString()).eval().close();
144+
contentDatabaseClient.newServerEval().javascript(script.toString()).eval().close();
160145
} catch (Exception ex) {
161146
throw new RuntimeException("Unable to load and validate TDE templates via tde.templateBatchInsert; " +
162147
"cause: " + ex.getMessage() + "; the following script can be run in Query Console against your content " +
@@ -216,17 +201,4 @@ public SchemaFiles(List<DocumentFile> tdeFiles, List<DocumentFile> nonTdeFiles)
216201
this.nonTdeFiles = nonTdeFiles;
217202
}
218203
}
219-
220-
public String getTdeValidationDatabase() {
221-
return tdeValidationDatabase;
222-
}
223-
224-
/**
225-
* @param tdeValidationDatabase
226-
* @deprecated Should be set via the constructor and not modified.
227-
*/
228-
@Deprecated
229-
public void setTdeValidationDatabase(String tdeValidationDatabase) {
230-
this.tdeValidationDatabase = tdeValidationDatabase;
231-
}
232204
}

src/main/java/com/marklogic/client/ext/schemasloader/impl/QbvDocumentFileProcessor.java

Lines changed: 27 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -39,32 +39,24 @@
3939
/**
4040
* @since 4.6.0
4141
*/
42-
public class QbvDocumentFileProcessor extends LoggingObject implements DocumentFileProcessor {
42+
class QbvDocumentFileProcessor extends LoggingObject implements DocumentFileProcessor {
4343

4444
public static final String QBV_COLLECTION = "http://marklogic.com/xdmp/qbv";
4545
private static final String QBV_XML_PLAN_NAMESPACE = "http://marklogic.com/plan";
4646
private static final String QBV_XML_ROOT_ELEMENT = "query-based-view";
47-
private static final String JAVASCRIPT_EVAL_TEMPLATE = "declareUpdate(); " +
48-
"xdmp.invokeFunction(function() {'use strict'; const op = require('/MarkLogic/optic'); return %s }, " +
49-
"{database: xdmp.database('%s')})";
50-
private static final String XQUERY_EVAL_TEMPLATE = "xquery version \"1.0-ml\"; " +
51-
"import module namespace op=\"http://marklogic.com/optic\" at \"/MarkLogic/optic.xqy\"; " +
52-
"xdmp:invoke-function(function() {%s},<options xmlns=\"xdmp:eval\"><database>{xdmp:database('%s')}</database></options>)";
5347

54-
final private DatabaseClient schemasDatabaseClient;
55-
final private String qbvGeneratorDatabaseName;
48+
final private DatabaseClient contentDatabaseClient;
5649
final private List<DocumentFile> qbvFiles = new ArrayList<>();
57-
final private XMLDocumentManager docMgr;
50+
final private XMLDocumentManager schemasDocumentManager;
5851

5952

6053
/**
61-
* @param schemasDatabaseClient database client for the application's schemas database
62-
* @param qbvGeneratorDatabaseName the database to run a script against for generating a QBV
54+
* @param schemasDatabaseClient used to write the QBV XML document to the application's schemas database
55+
* @param contentDatabaseClient used to generate the QBV based on a user-provided script
6356
*/
64-
public QbvDocumentFileProcessor(DatabaseClient schemasDatabaseClient, String qbvGeneratorDatabaseName) {
65-
this.schemasDatabaseClient = schemasDatabaseClient;
66-
this.qbvGeneratorDatabaseName = qbvGeneratorDatabaseName;
67-
this.docMgr = schemasDatabaseClient.newXMLDocumentManager();
57+
QbvDocumentFileProcessor(DatabaseClient schemasDatabaseClient, DatabaseClient contentDatabaseClient) {
58+
this.schemasDocumentManager = schemasDatabaseClient.newXMLDocumentManager();
59+
this.contentDatabaseClient = contentDatabaseClient;
6860
}
6961

7062
@Override
@@ -99,29 +91,29 @@ private void processQbvFile(DocumentFile qbvFile) {
9991
ServerEvaluationCall call = getServerEvaluationCall(qbvFile);
10092
if (call != null) {
10193
StringHandle handleString = new StringHandle();
94+
try {
95+
call.eval(handleString);
96+
} catch (Exception e) {
97+
throw new RuntimeException(format("Query-Based View generation failed for file: %s; cause: %s", qbvFile.getFile().getAbsolutePath(), e.getMessage()));
98+
}
99+
if (Format.XML.equals(handleString.getFormat())) {
100+
Document xmlDocument;
102101
try {
103-
call.eval(handleString);
102+
xmlDocument = new SAXBuilder().build(new StringReader(handleString.get()));
104103
} catch (Exception e) {
105104
throw new RuntimeException(format("Query-Based View generation failed for file: %s; cause: %s", qbvFile.getFile().getAbsolutePath(), e.getMessage()));
106105
}
107-
if (Format.XML.equals(handleString.getFormat())) {
108-
Document xmlDocument;
109-
try {
110-
xmlDocument = new SAXBuilder().build(new StringReader(handleString.get()));
111-
} catch (Exception e) {
112-
throw new RuntimeException(format("Query-Based View generation failed for file: %s; cause: %s", qbvFile.getFile().getAbsolutePath(), e.getMessage()));
113-
}
114-
Element root = xmlDocument.getRootElement();
115-
if (QBV_XML_ROOT_ELEMENT.equals(root.getName()) & (root.getNamespace() != null && root.getNamespace().getURI().equals(QBV_XML_PLAN_NAMESPACE))) {
116-
qbvFile.getDocumentMetadata().getCollections().add(QBV_COLLECTION);
117-
String uri = qbvFile.getUri() + ".xml";
118-
docMgr.write(uri, qbvFile.getDocumentMetadata(), new JDOMHandle(xmlDocument));
119-
} else {
120-
throw new RuntimeException(format("Query-Based view generation failed for file: %s; received unexpected response from server: %s", qbvFile.getFile().getAbsolutePath(), handleString.get()));
121-
}
106+
Element root = xmlDocument.getRootElement();
107+
if (QBV_XML_ROOT_ELEMENT.equals(root.getName()) & (root.getNamespace() != null && root.getNamespace().getURI().equals(QBV_XML_PLAN_NAMESPACE))) {
108+
qbvFile.getDocumentMetadata().getCollections().add(QBV_COLLECTION);
109+
String uri = qbvFile.getUri() + ".xml";
110+
schemasDocumentManager.write(uri, qbvFile.getDocumentMetadata(), new JDOMHandle(xmlDocument));
122111
} else {
123-
throw new RuntimeException(format("Query-Based View generation failed for file: %s; ensure your Optic script includes a call to generate a view; received unexpected response from server: %s", qbvFile.getFile().getAbsolutePath(), handleString.get()));
112+
throw new RuntimeException(format("Query-Based view generation failed for file: %s; received unexpected response from server: %s", qbvFile.getFile().getAbsolutePath(), handleString.get()));
124113
}
114+
} else {
115+
throw new RuntimeException(format("Query-Based View generation failed for file: %s; ensure your Optic script includes a call to generate a view; received unexpected response from server: %s", qbvFile.getFile().getAbsolutePath(), handleString.get()));
116+
}
125117
}
126118
}
127119

@@ -133,17 +125,7 @@ private ServerEvaluationCall getServerEvaluationCall(DocumentFile qbvFile) {
133125
throw new RuntimeException(format("Unable to generate Query-Based View; could not read from file %s; cause: %s", qbvFile.getFile().getAbsolutePath(), e.getMessage()));
134126
}
135127
return FilenameUtil.isXqueryFile(qbvFile.getFile().getName()) ?
136-
buildXqueryCall(fileContent) :
137-
buildJavascriptCall(fileContent);
138-
}
139-
140-
private ServerEvaluationCall buildJavascriptCall(String fileContent) {
141-
String script = format(JAVASCRIPT_EVAL_TEMPLATE, fileContent, qbvGeneratorDatabaseName);
142-
return schemasDatabaseClient.newServerEval().javascript(script);
143-
}
144-
145-
private ServerEvaluationCall buildXqueryCall(String fileContent) {
146-
String script = format(XQUERY_EVAL_TEMPLATE, fileContent, qbvGeneratorDatabaseName);
147-
return schemasDatabaseClient.newServerEval().xquery(script);
128+
contentDatabaseClient.newServerEval().xquery(fileContent) :
129+
contentDatabaseClient.newServerEval().javascript(fileContent);
148130
}
149131
}

src/main/java/com/marklogic/client/ext/schemasloader/impl/TdeDocumentFileProcessor.java

Lines changed: 24 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -30,27 +30,17 @@
3030
import java.io.File;
3131
import java.io.IOException;
3232

33-
public class TdeDocumentFileProcessor extends LoggingObject implements DocumentFileProcessor {
33+
class TdeDocumentFileProcessor extends LoggingObject implements DocumentFileProcessor {
3434

35-
private DatabaseClient schemasDatabaseClient;
36-
private String tdeValidationDatabase;
35+
private final DatabaseClient contentDatabaseClient;
3736
private Boolean templateBatchInsertSupported;
3837

3938
/**
40-
* Use this constructor when you don't want any TDE validation to occur.
39+
* @param contentDatabaseClient the database to run a script against for validating a TDE. If null, TDE validation
40+
* will not be performed.
4141
*/
42-
public TdeDocumentFileProcessor() {
43-
}
44-
45-
/**
46-
* Use this constructor when you want to validate a TDE template before writing it to MarkLogic.
47-
*
48-
* @param schemasDatabaseClient database client for the application's schemas database
49-
* @param tdeValidationDatabase the database to run a script against for validating a TDE
50-
*/
51-
public TdeDocumentFileProcessor(DatabaseClient schemasDatabaseClient, String tdeValidationDatabase) {
52-
this.schemasDatabaseClient = schemasDatabaseClient;
53-
this.tdeValidationDatabase = tdeValidationDatabase;
42+
TdeDocumentFileProcessor(DatabaseClient contentDatabaseClient) {
43+
this.contentDatabaseClient = contentDatabaseClient;
5444
}
5545

5646
@Override
@@ -79,22 +69,25 @@ public DocumentFile processDocumentFile(DocumentFile documentFile) {
7969
}
8070

8171
private boolean isTemplateBatchInsertSupported() {
82-
if (this.templateBatchInsertSupported == null) {
72+
if (this.templateBatchInsertSupported == null && contentDatabaseClient != null) {
8373
// Memoize this to avoid repeated calls; the result will always be the same unless the databaseClient is
8474
// modified, in which case templateBatchInsertSupported is set to null
85-
this.templateBatchInsertSupported = TdeUtil.templateBatchInsertSupported(schemasDatabaseClient);
75+
this.templateBatchInsertSupported = TdeUtil.templateBatchInsertSupported(contentDatabaseClient);
8676
}
8777
return this.templateBatchInsertSupported;
8878
}
8979

90-
protected void validateTdeTemplate(DocumentFile documentFile) {
80+
/**
81+
* This mechanism is only needed on older versions of MarkLogic that do not support tde.templateBatchInsert.
82+
*
83+
* @param documentFile
84+
*/
85+
private void validateTdeTemplate(DocumentFile documentFile) {
9186
final File file = documentFile.getFile();
92-
if (schemasDatabaseClient == null) {
93-
logger.info("No DatabaseClient provided for TDE validation, so will not validate TDE templates");
94-
} else if (tdeValidationDatabase == null) {
95-
logger.info("No TDE validation database specified, so will not validate TDE templates");
87+
if (contentDatabaseClient == null) {
88+
logger.info("No content database client provided, so will not validate TDE templates.");
9689
} else if (isTemplateBatchInsertSupported()) {
97-
logger.debug("Not performing TDE validation; it will be performed automatically via tde.templateBatchInsert");
90+
logger.debug("Not performing TDE validation; it will be performed automatically via tde.templateBatchInsert.");
9891
} else {
9992
String fileContent = null;
10093
try {
@@ -124,61 +117,17 @@ protected void validateTdeTemplate(DocumentFile documentFile) {
124117
}
125118
}
126119

127-
protected ServerEvaluationCall buildJavascriptCall(DocumentFile documentFile, String fileContent) {
128-
StringBuilder script = new StringBuilder("var template; xdmp.invokeFunction(function() {var tde = require('/MarkLogic/tde.xqy');");
129-
script.append(format(
130-
"\nreturn tde.validate([xdmp.toJSON(template)], ['%s'])}, {database: xdmp.database('%s')})",
131-
documentFile.getUri(), tdeValidationDatabase
132-
));
133-
return schemasDatabaseClient.newServerEval().javascript(script.toString())
120+
private ServerEvaluationCall buildJavascriptCall(DocumentFile documentFile, String fileContent) {
121+
StringBuilder script = new StringBuilder("const tde = require('/MarkLogic/tde.xqy'); var template; ");
122+
script.append(format("\ntde.validate([xdmp.toJSON(template)], ['%s'])", documentFile.getUri()));
123+
return contentDatabaseClient.newServerEval().javascript(script.toString())
134124
.addVariable("template", new StringHandle(fileContent).withFormat(Format.JSON));
135125
}
136126

137-
protected ServerEvaluationCall buildXqueryCall(DocumentFile documentFile, String fileContent) {
127+
private ServerEvaluationCall buildXqueryCall(DocumentFile documentFile, String fileContent) {
138128
StringBuilder script = new StringBuilder("import module namespace tde = 'http://marklogic.com/xdmp/tde' at '/MarkLogic/tde.xqy'; ");
139129
script.append("\ndeclare variable $template external; ");
140-
script.append("\nxdmp:invoke-function(function() { ");
141-
script.append(format(
142-
"\ntde:validate($template, '%s')}, <options xmlns='xdmp:eval'><database>{xdmp:database('%s')}</database></options>)",
143-
documentFile.getUri(), tdeValidationDatabase
144-
));
145-
return schemasDatabaseClient.newServerEval().xquery(script.toString()).addVariable("template", new StringHandle(fileContent).withFormat(Format.XML));
146-
}
147-
148-
/**
149-
* @return
150-
* @deprecated since 4.6.0, will be removed in 5.0.0
151-
*/
152-
@Deprecated
153-
public DatabaseClient getDatabaseClient() {
154-
return schemasDatabaseClient;
155-
}
156-
157-
/**
158-
* @param databaseClient
159-
* @deprecated since 4.6.0, will be removed in 5.0.0
160-
*/
161-
@Deprecated
162-
public void setDatabaseClient(DatabaseClient databaseClient) {
163-
this.schemasDatabaseClient = databaseClient;
164-
this.templateBatchInsertSupported = null;
165-
}
166-
167-
/**
168-
* @return
169-
* @deprecated since 4.6.0, will be removed in 5.0.0
170-
*/
171-
@Deprecated
172-
public String getTdeValidationDatabase() {
173-
return tdeValidationDatabase;
174-
}
175-
176-
/**
177-
* @param tdeValidationDatabase
178-
* @deprecated since 4.6.0, will be removed in 5.0.0
179-
*/
180-
@Deprecated
181-
public void setTdeValidationDatabase(String tdeValidationDatabase) {
182-
this.tdeValidationDatabase = tdeValidationDatabase;
130+
script.append(format("\ntde:validate($template, '%s')", documentFile.getUri()));
131+
return contentDatabaseClient.newServerEval().xquery(script.toString()).addVariable("template", new StringHandle(fileContent).withFormat(Format.XML));
183132
}
184133
}

0 commit comments

Comments
 (0)