diff --git a/src/main/java/com/norconex/commons/lang/config/ConfigurationLoader.java b/src/main/java/com/norconex/commons/lang/config/ConfigurationLoader.java index 7452ebda..eb370b3a 100644 --- a/src/main/java/com/norconex/commons/lang/config/ConfigurationLoader.java +++ b/src/main/java/com/norconex/commons/lang/config/ConfigurationLoader.java @@ -552,6 +552,10 @@ private static VelocityEngine createDefaultVelocityEngine() { engine.setProperty(RuntimeConstants.ENCODING_DEFAULT, StandardCharsets.UTF_8.toString()); + engine.setProperty("runtime.custom_directives", + "com.norconex.commons.lang.config.vlt.CustomIncludeDirective," + + "com.norconex.commons.lang.config.vlt.CustomParseDirective"); + engine.setProperty("runtime.log", ""); return engine; } diff --git a/src/main/java/com/norconex/commons/lang/config/vlt/CustomIncludeDirective.java b/src/main/java/com/norconex/commons/lang/config/vlt/CustomIncludeDirective.java new file mode 100644 index 00000000..d424167a --- /dev/null +++ b/src/main/java/com/norconex/commons/lang/config/vlt/CustomIncludeDirective.java @@ -0,0 +1,121 @@ +package com.norconex.commons.lang.config.vlt; + +import com.norconex.commons.lang.config.ConfigurationException; +import org.apache.velocity.app.event.EventHandlerUtil; +import org.apache.velocity.context.InternalContextAdapter; +import org.apache.velocity.exception.MethodInvocationException; +import org.apache.velocity.exception.ResourceNotFoundException; +import org.apache.velocity.exception.VelocityException; +import org.apache.velocity.runtime.directive.Include; +import org.apache.velocity.runtime.parser.node.ASTDirective; +import org.apache.velocity.runtime.parser.node.Node; +import org.apache.velocity.runtime.parser.node.ParserTreeConstants; +import org.apache.velocity.runtime.resource.Resource; +import org.apache.velocity.util.StringUtils; + +import java.io.IOException; +import java.io.Writer; +import java.lang.reflect.Method; + +public class CustomIncludeDirective extends Include { + @Override + public boolean render(InternalContextAdapter context, + Writer writer, Node node) + throws IOException, MethodInvocationException, + ResourceNotFoundException { + try { + // Obtain the protected method 'outputErrorToStream' from the superclass 'Include' + Method outputErrorToStreamMethod = Include.class.getDeclaredMethod( + "outputErrorToStream", + Writer.class, + String.class); + outputErrorToStreamMethod.setAccessible(true); // Make the protected method accessible + + int argCount = node.jjtGetNumChildren(); + + for (int i = 0; i < argCount; i++) { + Node n = node.jjtGetChild(i); + + if (n.getType() == ParserTreeConstants.JJTSTRINGLITERAL || + n.getType() == ParserTreeConstants.JJTREFERENCE) { + if (!renderOutput(n, context, writer)) + outputErrorToStreamMethod.invoke(this, writer, + "error with arg " + i + " please see log."); + } else { + String msg = "invalid #include() argument '" + + n.toString() + "' at " + + StringUtils.formatFileString(this); + log.error(msg); + outputErrorToStreamMethod.invoke(this, writer, + "error with arg " + i + " please see log."); + throw new VelocityException(msg, null, + rsvc.getLogContext().getStackTrace()); + } + } + } catch (Exception e) { + throw new ConfigurationException(e); + } + return true; + } + + private boolean renderOutput(Node node, InternalContextAdapter context, + Writer writer) + throws IOException, MethodInvocationException, + ResourceNotFoundException { + if (node == null) { + log.error("#include() null argument"); + return false; + } + + Object value = node.value(context); + if (value == null) { + log.error("#include() null argument"); + return false; + } + + String sourcearg = value.toString(); + + String arg = EventHandlerUtil.includeEvent(rsvc, context, sourcearg, + context.getCurrentTemplateName(), getName()); + + boolean blockinput = false; + if (arg == null) + blockinput = true; + + Resource resource = null; + + try { + if (!blockinput) + resource = rsvc.getContent(arg, getInputEncoding(context)); + } catch (ResourceNotFoundException rnfe) { + log.error("#include(): cannot find resource '{}', called at {}", + arg, StringUtils.formatFileString(this)); + throw rnfe; + } + + catch (RuntimeException e) { + log.error("#include(): arg = '{}', called at {}", + arg, StringUtils.formatFileString(this)); + throw e; + } catch (Exception e) { + String msg = "#include(): arg = '" + arg + + "', called at " + StringUtils.formatFileString(this); + log.error(msg, e); + throw new VelocityException(msg, e, + rsvc.getLogContext().getStackTrace()); + } + + if (blockinput) + return true; + + else if (resource == null) + return false; + + String prefixSpace = ((ASTDirective) node.jjtGetParent()).getPrefix(); + String indentedData = + ((String) resource.getData()).replaceAll("(?m)^", prefixSpace); + writer.write(indentedData); + + return true; + } +} diff --git a/src/main/java/com/norconex/commons/lang/config/vlt/CustomParseDirective.java b/src/main/java/com/norconex/commons/lang/config/vlt/CustomParseDirective.java new file mode 100644 index 00000000..2f1298f0 --- /dev/null +++ b/src/main/java/com/norconex/commons/lang/config/vlt/CustomParseDirective.java @@ -0,0 +1,157 @@ +package com.norconex.commons.lang.config.vlt; + +import org.apache.velocity.Template; +import org.apache.velocity.app.event.EventHandlerUtil; +import org.apache.velocity.context.InternalContextAdapter; +import org.apache.velocity.exception.MethodInvocationException; +import org.apache.velocity.exception.ParseErrorException; +import org.apache.velocity.exception.ResourceNotFoundException; +import org.apache.velocity.exception.VelocityException; +import org.apache.velocity.runtime.directive.Parse; +import org.apache.velocity.runtime.directive.StopCommand; +import org.apache.velocity.runtime.parser.node.ASTDirective; +import org.apache.velocity.runtime.parser.node.Node; +import org.apache.velocity.runtime.parser.node.SimpleNode; +import org.apache.velocity.util.StringUtils; + +import java.io.IOException; +import java.io.StringWriter; +import java.io.Writer; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; + +public class CustomParseDirective extends Parse { + + @Override + public boolean render(InternalContextAdapter context, + Writer writer, Node node) + throws IOException, ResourceNotFoundException, ParseErrorException, + MethodInvocationException { + + int maxDepth; + try { + Field maxDepthField = Parse.class.getDeclaredField("maxDepth"); + + maxDepthField.setAccessible(true); + maxDepth = (int) maxDepthField.get(this); // Cast to int + + } catch (NoSuchFieldException | IllegalAccessException e) { + throw new RuntimeException(e); + } + + if (node.jjtGetNumChildren() == 0) { + throw new VelocityException("#parse(): argument missing at " + + StringUtils.formatFileString(this), null, + rsvc.getLogContext().getStackTrace()); + } + + Object value = node.jjtGetChild(0).value(context); + if (value == null) { + log.debug("#parse(): null argument at {}", + StringUtils.formatFileString(this)); + } + + String sourcearg = value == null ? null : value.toString(); + + String arg = EventHandlerUtil.includeEvent(rsvc, context, sourcearg, + context.getCurrentTemplateName(), getName()); + + if (strictRef && value == null && arg == null) { + throw new VelocityException( + "The argument to #parse returned null at " + + StringUtils.formatFileString(this), + null, rsvc.getLogContext().getStackTrace()); + } + + if (arg == null) { + return true; + } + + if (maxDepth > 0) { + String[] templateStack = context.getTemplateNameStack(); + if (templateStack.length >= maxDepth) { + StringBuilder path = new StringBuilder(); + for (String aTemplateStack : templateStack) { + path.append(" > ").append(aTemplateStack); + } + log.error("Max recursion depth reached ({}). File stack: {}", + templateStack.length, path); + + return false; + } + } + + Template t = null; + + try { + t = rsvc.getTemplate(arg, getInputEncoding(context)); + } catch (ResourceNotFoundException rnfe) { + log.error("#parse(): cannot find template '{}', called at {}", + arg, StringUtils.formatFileString(this)); + throw rnfe; + } catch (ParseErrorException pee) { + log.error( + "#parse(): syntax error in #parse()-ed template '{}', called at {}", + arg, StringUtils.formatFileString(this)); + throw pee; + } catch (RuntimeException e) { + log.error("Exception rendering #parse({}) at {}", + arg, StringUtils.formatFileString(this)); + throw e; + } catch (Exception e) { + String msg = "Exception rendering #parse(" + arg + ") at " + + StringUtils.formatFileString(this); + log.error(msg, e); + throw new VelocityException(msg, e, + rsvc.getLogContext().getStackTrace()); + } + List