package com.marklogic.client.modulesloader.impl;
import com.marklogic.client.helper.LoggingObject;
import java.util.List;
/**
* Has all the guts for doing static checking, but delegates the execution of an XQuery script to a subclass.
*/
public abstract class AbstractStaticChecker extends LoggingObject implements StaticChecker {
private boolean checkLibraryModules = false;
private boolean bulkCheck = false;
@Override
public void checkLoadedAssets(List<LoadedAsset> assets) {
if (assets == null || assets.isEmpty()) {
return;
}
if (bulkCheck) {
performBulkStaticCheck(assets);
} else {
for (LoadedAsset asset : assets) {
if (asset.isCanBeStaticallyChecked()) {
staticallyCheckModule(asset.getUri());
}
}
}
}
protected abstract void executeQuery(String xquery);
/**
* Statically checks the module at the given URI. Includes support for evaluating a library module by trying to
* extract its namespace and then using xdmp:eval to evaluate a module that imports the library module. If this
* fails to extract its namespace, an error will be reported just like if the module itself has an error in it.
*
* @param uri
*/
protected void staticallyCheckModule(String uri) {
if (logger.isInfoEnabled()) {
logger.info("Performing static check on module at URI: " + uri);
}
String xquery = "let $uri := '" + uri + "' return " + buildXqueryForStaticallyCheckingModule();
executeQuery(xquery);
if (logger.isInfoEnabled()) {
logger.info("Finished static check on module at URI: " + uri);
}
}
protected void performBulkStaticCheck(List<LoadedAsset> assets) {
String xquery = "let $uris := (";
for (LoadedAsset asset : assets) {
if (asset.isCanBeStaticallyChecked()) {
String uri = asset.getUri();
if (!xquery.endsWith("(")) {
xquery += ",";
}
xquery += "'" + uri + "'";
}
}
xquery += ") for $uri in $uris return " + buildXqueryForStaticallyCheckingModule();
if (logger.isInfoEnabled()) {
logger.info("Static checking all loaded modules");
}
executeQuery(xquery);
if (logger.isInfoEnabled()) {
logger.info("Finished static checking all loaded modules");
}
}
/**
* Assumes that there's already a variable in XQuery named "uri" in scope. If the module is a library module, an
* attempt is made to extract its namespace and import it in a statement passed to xdmp:eval. If an error occurs
* in construct that statement, it cannot be distinguished from an error in the actual module. To turn this behavior
* off, set "staticCheckLibraryModules" to false.
*
* @return
*/
protected String buildXqueryForStaticallyCheckingModule() {
String xquery =
"try { xdmp:invoke($uri, (), <options xmlns='xdmp:eval'><static-check>true</static-check></options>) } " +
"catch ($e) { " +
"if ($e/*:code = 'XDMP-NOEXECUTE') then () " +
"else if ($e/*:code = 'XDMP-EVALLIBMOD') then ";
if (checkLibraryModules) {
xquery +=
" let $doc := xdmp:eval('declare variable $URI external; fn:doc($URI)', (xs:QName('URI'), $uri), <options xmlns='xdmp:eval'><database>{xdmp:modules-database()}</database></options>) " +
" let $line := fn:tokenize($doc, '\n')[fn:contains(., 'module namespace')][1] " +
" let $ns := fn:tokenize($line, '=')[2] " +
" let $ns := fn:replace($ns, ';', '') " +
" let $ns := fn:replace($ns, \"'\", \"\") " +
" let $ns := fn:normalize-space(fn:replace($ns, '\"', '')) " +
" let $xquery := fn:concat('import module namespace ns = \"', $ns, '\" at \"', $uri, '\"; ()')" +
" return xdmp:eval($xquery, (), <options xmlns='xdmp:eval'><static-check>true</static-check></options>) ";
} else {
xquery += " () ";
}
return xquery + " else xdmp:rethrow() }";
}
public void setCheckLibraryModules(boolean checkLibraryModules) {
this.checkLibraryModules = checkLibraryModules;
}
public void setBulkCheck(boolean bulkCheck) {
this.bulkCheck = bulkCheck;
}
}