Juan Hernandez has uploaded a new change for review.

Change subject: core: Simple template engine
......................................................................

core: Simple template engine

This patch introduces a simple template engine that uses a similar
syntax to JSP. This template engine will be used by later patches to
simplify the generation of LDAP queries.

Change-Id: I14bf2a4d707002c5a1ddcd4ae1e7729957f359b8
Signed-off-by: Juan Hernandez <[email protected]>
---
A 
backend/manager/modules/utils/src/main/java/org/ovirt/engine/core/utils/templates/Template.java
A 
backend/manager/modules/utils/src/main/java/org/ovirt/engine/core/utils/templates/TemplateCompiler.java
A 
backend/manager/modules/utils/src/main/java/org/ovirt/engine/core/utils/templates/TemplateException.java
A 
backend/manager/modules/utils/src/main/java/org/ovirt/engine/core/utils/templates/TemplateOption.java
A 
backend/manager/modules/utils/src/test/java/org/ovirt/engine/core/utils/templates/TemplateTest.java
A 
backend/manager/modules/utils/src/test/resources/org/ovirt/engine/core/utils/templates/comments.expected
A 
backend/manager/modules/utils/src/test/resources/org/ovirt/engine/core/utils/templates/comments.template
A 
backend/manager/modules/utils/src/test/resources/org/ovirt/engine/core/utils/templates/ldap.expected
A 
backend/manager/modules/utils/src/test/resources/org/ovirt/engine/core/utils/templates/ldap.template
A 
backend/manager/modules/utils/target/test-classes/org/ovirt/engine/core/utils/templates/empty.expected
A 
backend/manager/modules/utils/target/test-classes/org/ovirt/engine/core/utils/templates/empty.template
11 files changed, 918 insertions(+), 0 deletions(-)


  git pull ssh://gerrit.ovirt.org:29418/ovirt-engine refs/changes/50/14650/1

diff --git 
a/backend/manager/modules/utils/src/main/java/org/ovirt/engine/core/utils/templates/Template.java
 
b/backend/manager/modules/utils/src/main/java/org/ovirt/engine/core/utils/templates/Template.java
new file mode 100644
index 0000000..cf94ab1
--- /dev/null
+++ 
b/backend/manager/modules/utils/src/main/java/org/ovirt/engine/core/utils/templates/Template.java
@@ -0,0 +1,121 @@
+package org.ovirt.engine.core.utils.templates;
+
+import java.io.CharArrayWriter;
+import java.io.PrintWriter;
+import java.util.Map;
+
+import javax.script.Bindings;
+import javax.script.CompiledScript;
+import javax.script.ScriptException;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.log4j.Logger;
+
+/**
+ * This class represents a precompiled template. Don't try to instantiate it
+ * directly, use {@link TemplateCompiler#compile(String, TemplateOption...))}
+ * instead to compile new templates.
+ */
+public class Template {
+    // The log:
+    private static final Logger log = Logger.getLogger(Template.class);
+
+    // The source of the template, before processing it:
+    private String source;
+
+    // The script generated from the source of the template and compiled for
+    // better performance:
+    private CompiledScript script;
+
+    /**
+     * This constructor is intended to be use by the template compiler once
+     * the source is already compiled, don't try to use it directly.
+     *
+     * @param source the source of the template
+     * @param script the script used to evaluate the template
+     */
+    Template(String source, CompiledScript script) {
+        this.source = source;
+        this.script = script;
+    }
+
+    /**
+     * Returns the source code of the template.
+     */
+    public String getSource() {
+        return source;
+    }
+
+    /**
+     * Evaluates the template with the given parameters. Each of the entries
+     * of the parameters map will be converted into an object available
+     * in expressions inside the map.
+     *
+     * @param parameters a map containing the parameters to pass to the
+     *   template
+     * @return returns the result of evaluating the template
+     * @throws TemplateException if anything fails during the evaluation of
+     *   the template
+     */
+    public String eval(Map<String, Object> parameters) throws 
TemplateException {
+        // Prepare the stream where the script will generate the
+        // output of the template:
+        CharArrayWriter chars = new CharArrayWriter(source.length());
+        PrintWriter out = new PrintWriter(chars);
+
+        // Prepare a set of bindings containing the parameters given by the
+        // user plus the builtin objects:
+        Bindings bindings = script.getEngine().createBindings();
+        if (parameters != null) {
+            bindings.putAll(parameters);
+        }
+        bindings.put("out", out);
+
+        // Run the script:
+        try {
+            script.eval(bindings);
+        }
+        catch (ScriptException exception) {
+            throw new TemplateException("Error evaluating template.", 
exception);
+        }
+
+        // Flush the output in case the generate script didn't:
+        out.flush();
+
+        String text = chars.toString();
+
+        // Dump the generated text to the log:
+        if (log.isInfoEnabled()) {
+            log.info("Template output follows:\n" + text);
+        }
+
+        return text;
+    }
+
+    /**
+     * Return the string representation of the template, which is actually
+     * the source used to create it.
+     *
+     * @return the string representation of the template
+     */
+    @Override
+    public String toString() {
+        return source;
+    }
+
+    @Override
+    public int hashCode() {
+        return source.hashCode();
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        try {
+            Template that = (Template) obj;
+            return StringUtils.equals(this.source,  that.source);
+        }
+        catch (ClassCastException exception) {
+            return false;
+        }
+    }
+}
diff --git 
a/backend/manager/modules/utils/src/main/java/org/ovirt/engine/core/utils/templates/TemplateCompiler.java
 
b/backend/manager/modules/utils/src/main/java/org/ovirt/engine/core/utils/templates/TemplateCompiler.java
new file mode 100644
index 0000000..7da243d
--- /dev/null
+++ 
b/backend/manager/modules/utils/src/main/java/org/ovirt/engine/core/utils/templates/TemplateCompiler.java
@@ -0,0 +1,584 @@
+package org.ovirt.engine.core.utils.templates;
+
+import java.io.CharArrayReader;
+import java.io.CharArrayWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.Reader;
+import java.io.Writer;
+import java.util.EnumSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.script.Compilable;
+import javax.script.CompiledScript;
+import javax.script.ScriptEngine;
+import javax.script.ScriptEngineManager;
+import javax.script.ScriptException;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.log4j.Logger;
+
+/**
+ * As simple templating engine. Templates hava a syntax similar to JSP, but
+ * using Javascript for the scriptlets instead of Java. An example of a
+ * template that generates a LDAP filter could be the following:
+ *
+ * <pre>
+ * (&
+ *   (dc=${domain})
+ *   (|
+ *     <% for (id in ids) %>
+ *       (uid=${id})
+ *     <% } %>
+ *   )
+ * )
+ * </pre>
+ *
+ * This template is assuming that it will be evaluated passing as parameters
+ * a <code>domain</code> and an array of <code>ids</code>. The template can
+ * be compiled and evaluated as follows:
+ *
+ * <pre>
+ * {@code
+ * try {
+ *   String source = "(&(dc=...";
+ *   TemplateCompiler compiler = TemplateCompiler.getInstance();
+ *   Template template = compiler.compile(source, 
TemplateOptions.IGNORE_WHITESPACE);
+ *   Map<String, Object> parameters = new HashMap<>();
+ *   parameters.put("domain", "example.com");
+ *   int[] ids = new int[] { 1, 2, 3 };
+ *   parameters.put("ids", ids);
+ *   String result = template.eval(parameters);
+ *   ...
+ * }
+ * catch (TemplateException exception) {
+ *   ...
+ * }
+ * </pre>
+ *
+ * Once a template is compiled it can be safely reused, even from different
+ * threads.
+ *
+ * Compiled templates are cached, so if the same source is provided twice the
+ * second invocation to {@link #compile(String, TemplateOption...)} will
+ * return the same template. This cache is currently never cleaned, so refrain
+ * from using templates that are generated dynamically as they may cause a
+ * memory leak.
+ */
+public class TemplateCompiler {
+    // The log:
+    private static final Logger log = Logger.getLogger(TemplateCompiler.class);
+
+    // The name of the scripting language to use:
+    private static final String LANGUAGE = "JavaScript";
+
+    // This is a singleton, and this is the instance:
+    private static volatile TemplateCompiler instance;
+
+    /**
+     * Get the reference to the singleton instance of the template
+     * compiler.
+     */
+    public static TemplateCompiler getInstance() {
+        if (instance == null) {
+            synchronized(TemplateCompiler.class) {
+                instance = new TemplateCompiler();
+            }
+        }
+        return instance;
+    }
+
+    // A cache containing already compiled templates, indexe by the source
+    // code of the template:
+    private Map<Key, Template> cache = new ConcurrentHashMap<>();
+
+    // The scripting engine used to compile the scripts generated from
+    // the templates:
+    private Compilable compiler;
+
+    private TemplateCompiler() {
+        // Find the scripting engine:
+        final ScriptEngineManager manager = new ScriptEngineManager();
+        final ScriptEngine engine = manager.getEngineByName(LANGUAGE);
+        if (engine == null) {
+            log.error(
+                "Can't find scripting engine for language \"" + LANGUAGE + 
"\"."
+            );
+            return;
+        }
+
+        // For performance reasons we only support scripting languages that
+        // support compilation, so we check it:
+        try {
+            compiler = (Compilable) engine;
+        }
+        catch (ClassCastException exception) {
+            log.error(
+                "The scripting language \"" + LANGUAGE + "\" doesn't support " 
+
+                "compilation.", exception
+            );
+        }
+    }
+
+    /**
+     * The states of the machine used to parse the templates.
+     */
+    private static enum State {
+        BEGIN,
+        MARK_0,
+        MARK_1,
+        COMMENT_0,
+        COMMENT_1,
+        COMMENT_2,
+        COMMENT_3,
+        COMMENT_4,
+        SCRIPTLET_0,
+        SCRIPTLET_1,
+        VALUE_0,
+        VALUE_1,
+        EXPRESSION_0,
+        EXPRESSION_1,
+        END
+    }
+
+    /**
+     * The key used to index templates in the cache. It is composed by the
+     * source of the template and the compilation options.
+     */
+    private static final class Key {
+        private String source;
+        private Set<TemplateOption> options;
+
+        public Key(String source, Set<TemplateOption> options) {
+            this.source = source;
+            this.options = options;
+        }
+
+        @Override
+        public int hashCode () {
+            return
+                source.hashCode() +
+                options.hashCode()
+            ;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            try {
+                Key that = (Key) obj;
+                return
+                    StringUtils.equals(this.source, that.source) &&
+                    this.options.equals(that.options)
+                ;
+            }
+            catch (ClassCastException exception) {
+                return false;
+            }
+        }
+    }
+
+    private void handleText(final StringBuilder buffer, final PrintWriter out, 
final Set<TemplateOption> options) {
+        // Check the length of the text and do nothing if it is empty:
+        final int length = buffer.length();
+        if (length == 0) {
+            return;
+        }
+
+        // Generate the statement that generates the given text, escaping the
+        // content so it will be a correct string of the scripting language:
+        out.print("out.print(\"");
+        for (int i = 0; i < length; i++) {
+            final char c = buffer.charAt(i);
+            if (options.contains(TemplateOption.IGNORE_WHITESPACE) && 
Character.isWhitespace(c)) {
+                continue;
+            }
+            if (c == '\n') {
+                out.print("\\n");
+            }
+            else if (c == '\t') {
+                out.print("\\t");
+            }
+            else if (c == '"') {
+                out.print("\\");
+                out.print(c);
+            }
+            else {
+                out.print(c);
+            }
+        }
+        out.println("\");");
+
+        // Clear the buffer:
+        buffer.setLength(0);
+    }
+
+    private void handleComment(final StringBuilder buffer, final PrintWriter 
out) {
+        buffer.setLength(0);
+    }
+
+    private void handleScriptlet(final StringBuilder buffer, final PrintWriter 
out) {
+        out.print(buffer);
+        buffer.setLength(0);
+    }
+
+    private void handleValue(final StringBuilder buffer, final PrintWriter 
out) {
+        out.print("out.print(" + buffer + ");");
+        buffer.setLength(0);
+    }
+
+    private void handleExpression(final StringBuilder buffer, final 
PrintWriter out) {
+        out.print("out.print(" + buffer + ");");
+        buffer.setLength(0);
+    }
+
+    private void handleError(final String message, final int line, final int 
column) throws TemplateException {
+        throw new TemplateException(message + "(" + line + ", " + column + 
").");
+    }
+
+    public void generateScript(final Reader in, final Writer out, final 
Set<TemplateOption> options) throws TemplateException {
+        // Prepare the output for the generated script source:
+        final PrintWriter printer = new PrintWriter(out);
+
+        // Initialize the line and column indexes:
+        int line = 0;
+        int column = 0;
+
+        // Initialize the state machine:
+        State state = State.BEGIN;
+        final StringBuilder buffer = new StringBuilder();
+
+        while (state != State.END) {
+
+            // Read the next character from the input:
+            int c;
+            try {
+                c = in.read();
+            }
+            catch (IOException exception) {
+                throw new TemplateException("IO error while generating 
script.", exception);
+            }
+
+            // Update the line and column indexes:
+            if (c == '\n') {
+                line++;
+                column = 0;
+            }
+            else {
+                column++;
+            }
+
+            // Process the next character according to the current state:
+            switch (state) {
+
+            case BEGIN:
+                if (c == -1) {
+                    handleText(buffer, printer, options);
+                    state = State.END;
+                }
+                else if (c == '<') {
+                    handleText(buffer, printer, options);
+                    state = State.MARK_0;
+                }
+                else if (c == '$') {
+                    handleText(buffer, printer, options);
+                    state = State.EXPRESSION_0;
+                }
+                else {
+                    buffer.append((char) c);
+                    state = State.BEGIN;
+                }
+                break;
+
+            case MARK_0: // <
+                if (c == -1) {
+                    buffer.append('<');
+                    handleText(buffer, printer, options);
+                    state = State.END;
+                }
+                else if (c == '<') {
+                    buffer.append('<');
+                    state = State.MARK_0;
+                }
+                else if (c == '%') {
+                    handleText(buffer, printer, options);
+                    state = State.MARK_1;
+                }
+                else {
+                    buffer.append('<');
+                    buffer.append((char) c);
+                    state = State.BEGIN;
+                }
+                break;
+
+            case MARK_1: // <%
+                if (c == -1) {
+                    handleError("EOF inside mark", line, column);
+                }
+                else if (c == '-') {
+                    state = State.COMMENT_0;
+                }
+                else if (c == '=') {
+                    handleText(buffer, printer, options);
+                    state = State.VALUE_0;
+                }
+                else if (c == '%') {
+                    handleText(buffer, printer, options);
+                    state = State.SCRIPTLET_1;
+                }
+                else {
+                    buffer.append((char) c);
+                    state = State.SCRIPTLET_0;
+                }
+                break;
+
+            case COMMENT_0: // <%-
+                if (c == -1) {
+                    handleError("EOF inside comment", line, column);
+                }
+                else if (c == '-') {
+                    state = State.COMMENT_1;
+                }
+                else {
+                    buffer.append('-');
+                    buffer.append((char) c);
+                    state = State.SCRIPTLET_0;
+                }
+                break;
+
+            case COMMENT_1: // <%--
+                if (c == -1) {
+                    handleError("EOF inside comment", line, column);
+                }
+                else if (c == '-') {
+                    state = State.COMMENT_2;
+                }
+                else {
+                    buffer.append((char) c);
+                    state = State.COMMENT_1;
+                }
+                break;
+
+            case COMMENT_2: // <%--.-
+                if (c == -1) {
+                    handleError("EOF inside comment", line, column);
+                }
+                else if (c == '-') {
+                    state = State.COMMENT_3;
+                }
+                else {
+                    buffer.append('-');
+                    buffer.append((char) c);
+                    state = State.COMMENT_1;
+                }
+                break;
+
+            case COMMENT_3: // <%--.--
+                if (c == -1) {
+                    handleError("EOF inside comment", line, column);
+                }
+                else if (c == '%') {
+                    state = State.COMMENT_4;
+                }
+                else if (c == '-') {
+                    buffer.append('-');
+                    state = State.COMMENT_3;
+                }
+                else {
+                    buffer.append('-');
+                    buffer.append((char) c);
+                    state = State.COMMENT_1;
+                }
+                break;
+
+            case COMMENT_4: // <%--.--%
+                if (c == -1) {
+                    handleError("EOF inside comment", line, column);
+                }
+                else if (c == '>') {
+                    handleComment(buffer, printer);
+                    state = State.BEGIN;
+                }
+                else if (c == '-') {
+                    buffer.append("--%");
+                    state = State.COMMENT_2;
+                }
+                else {
+                    buffer.append("--%");
+                    buffer.append((char) c);
+                    state = State.COMMENT_1;
+                }
+                break;
+
+            case SCRIPTLET_0: // <%[^%]*
+                if (c == -1) {
+                    handleError("EOF inside mark", line, column);
+                }
+                else if (c == '%')  {
+                    state = State.SCRIPTLET_1;
+                }
+                else {
+                    buffer.append((char) c);
+                    state = State.SCRIPTLET_0;
+                }
+                break;
+
+            case SCRIPTLET_1: // <%[^%]*%
+                if (c == -1) {
+                    handleError("EOF inside scriptlet", line, column);
+                }
+                else if (c == '>') {
+                    handleScriptlet(buffer, printer);
+                    state = State.BEGIN;
+                }
+                else {
+                    buffer.append('%');
+                    buffer.append((char) c);
+                    state = State.SCRIPTLET_0;
+                }
+                break;
+
+            case VALUE_0: // <%=[^%]*
+                if (c == -1) {
+                    handleError("EOF inside value", line, column);
+                }
+                else if (c == '%') {
+                    state = State.VALUE_1;
+                }
+                else {
+                    buffer.append((char) c);
+                    state = State.VALUE_0;
+                }
+                break;
+
+            case VALUE_1: // <%=[^%]*%
+                if (c == -1) {
+                    handleError("EOF inside expression", line, column);
+                }
+                else if (c == '>') {
+                    handleValue(buffer, printer);
+                    state = State.BEGIN;
+                }
+                else {
+                    buffer.append('%');
+                    buffer.append((char) c);
+                    state = State.VALUE_0;
+                }
+                break;
+
+            case EXPRESSION_0: // $
+                if (c == -1) {
+                    handleError("EOF inside expression", line, column);
+                }
+                else if (c == '{') {
+                    state = State.EXPRESSION_1;
+                }
+                else {
+                    buffer.append('$');
+                    buffer.append((char) c);
+                    state = State.BEGIN;
+                }
+                break;
+
+            case EXPRESSION_1: // ${
+                if (c == -1) {
+                    handleError("EOF inside expression", line, column);
+                }
+                else if (c == '}') {
+                    handleExpression(buffer, printer);
+                    state = State.BEGIN;
+                }
+                else {
+                    buffer.append((char) c);
+                    state = State.EXPRESSION_1;
+                }
+                break;
+
+            case END:
+                // Will never happen, but helps avoid warnings from the
+                // compiler and other code checking tools.
+                break;
+            }
+        }
+    }
+
+    /**
+     * Generates the script corresponding to the template text and compiles
+     * it so that execution will have better performance. The returned
+     * template object can then be used repeatedly and even simultaneously
+     * from different threads.
+     *
+     * @param source the source of the template
+     * @param options flags that alter the behaviour of the template compiler
+     * @return returns a template object that can then be repeatedly used
+     *   to evaluate the template with different parameters
+     * @throws TemplateException if something fails during the compilation
+     *   of the template
+     */
+    public Template compile(final String source, final Set<TemplateOption> 
options) throws TemplateException {
+        // Check if the template is available in the cache (note that this 
check
+        // is not atomic, so if two threads invoke this method simultaeously
+        // the template can end up being compiled more than once, but this is
+        // not a big issue, as templates are inmutable):
+        Key key = new Key(source, options);
+        Template template = cache.get(key);
+        if (template != null) {
+            return template;
+        }
+
+        // Check that we have a script compiler available:
+        if (compiler == null) {
+            throw new TemplateException("Script compiler is not available.");
+        }
+
+        // Dump the template source to the log:
+        if (log.isInfoEnabled()) {
+            log.info("Template source follows:\n" + source);
+        }
+
+        // Generate the source of the script from the template text:
+        final CharArrayReader sourceReader = new 
CharArrayReader(source.toCharArray());
+        final CharArrayWriter scriptWriter = new CharArrayWriter();
+        generateScript(sourceReader, scriptWriter, options);
+        final char[] scriptSource = scriptWriter.toCharArray();
+
+        // Dump the generated script source to the log:
+        if (log.isInfoEnabled()) {
+            String script = new String(scriptSource);
+            log.info("Template script follows:\n" + script);
+        }
+
+        // Compile the generated script:
+        final CharArrayReader scriptReader = new CharArrayReader(scriptSource);
+        CompiledScript script;
+        try {
+            script = compiler.compile(scriptReader);
+        }
+        catch (ScriptException exception) {
+            throw new TemplateException("Error while compiling script.", 
exception);
+        }
+
+        // Create and a new template containing the original source
+        // and the compiled script:
+        template = new Template(source, script);
+
+        // Update the cache:
+        cache.put(key, template);
+
+        return template;
+    }
+
+    /**
+     * Thi is equivalent to {@link #compile(String, TemplateOption...)} but it
+     * it accepts a variable list of options, thus making it simpler to use
+     * when options are hardcoded in the calling code.
+     */
+    public Template compile(final String source, final TemplateOption... 
options) throws TemplateException {
+        final Set<TemplateOption> set = EnumSet.noneOf(TemplateOption.class);
+        for (final TemplateOption option : options) {
+            set.add(option);
+        }
+        return compile(source, set);
+    }
+}
diff --git 
a/backend/manager/modules/utils/src/main/java/org/ovirt/engine/core/utils/templates/TemplateException.java
 
b/backend/manager/modules/utils/src/main/java/org/ovirt/engine/core/utils/templates/TemplateException.java
new file mode 100644
index 0000000..75abd63
--- /dev/null
+++ 
b/backend/manager/modules/utils/src/main/java/org/ovirt/engine/core/utils/templates/TemplateException.java
@@ -0,0 +1,13 @@
+package org.ovirt.engine.core.utils.templates;
+
+public class TemplateException extends Exception {
+    private static final long serialVersionUID = -5532073525436872546L;
+
+    public TemplateException (String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public TemplateException (String message) {
+        super(message);
+    }
+}
diff --git 
a/backend/manager/modules/utils/src/main/java/org/ovirt/engine/core/utils/templates/TemplateOption.java
 
b/backend/manager/modules/utils/src/main/java/org/ovirt/engine/core/utils/templates/TemplateOption.java
new file mode 100644
index 0000000..a7a80c8
--- /dev/null
+++ 
b/backend/manager/modules/utils/src/main/java/org/ovirt/engine/core/utils/templates/TemplateOption.java
@@ -0,0 +1,35 @@
+package org.ovirt.engine.core.utils.templates;
+
+/**
+ * Options that can be passed to the template compiler to alter its
+ * behaviour.
+ */
+public enum TemplateOption {
+    /**
+     * If this OPTION is set all the white space inside the template will
+     * be ignored. This is useful when wanting to write templates that are
+     * readable but the output can't contain white space. For example, if you
+     * want to generate a template for an LDAP filter you can write something
+     * like this, using white space to make it readable:
+     *
+     * <pre>
+     * (&
+     *   (dc=${domain})
+     *   (|
+     *     <% for (id in ids) %>
+     *       (uid=${id})
+     *     <% } %>
+     *   )
+     * )
+     * </pre>
+     *
+     * But the result will be like this, without the white space:
+     *
+     * <pre>
+     * (&(dc=example.com)(|(uid=id1)(uid=id2)))
+     * </pre>
+     *
+     * The default is to preserve white space.
+     */
+    IGNORE_WHITESPACE,
+}
diff --git 
a/backend/manager/modules/utils/src/test/java/org/ovirt/engine/core/utils/templates/TemplateTest.java
 
b/backend/manager/modules/utils/src/test/java/org/ovirt/engine/core/utils/templates/TemplateTest.java
new file mode 100644
index 0000000..3be572f
--- /dev/null
+++ 
b/backend/manager/modules/utils/src/test/java/org/ovirt/engine/core/utils/templates/TemplateTest.java
@@ -0,0 +1,150 @@
+package org.ovirt.engine.core.utils.templates;
+
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class TemplateTest {
+
+    /**
+     * Test that the template can be evaluated correctly when no parameters
+     * are provided.
+     */
+    @Test
+    public void testNullParameters() throws Exception {
+        testTemplate("empty", null);
+    }
+
+    /**
+     * Test that if the same template is compiled twice in a row exactly
+     * the same template instance is returned from the cache.
+     */
+    @Test
+    public void testCached() throws Exception {
+        String source = "";
+        Template first = TemplateCompiler.getInstance().compile(source);
+        Template last = TemplateCompiler.getInstance().compile(source);
+        Assert.assertSame(first, last);
+    }
+
+    /**
+     * Test that if the same template is compiled twice in a row with different
+     * source the instances returned from the cache are different.
+     */
+    @Test
+    public void testNotCached() throws Exception {
+        Template first = TemplateCompiler.getInstance().compile("first");
+        Template last = TemplateCompiler.getInstance().compile("second");
+        Assert.assertNotSame(first, last);
+    }
+
+    /**
+     * Test that if the same template is compiled twice in a row with different
+     * options the instances returned from the cache are different.
+     */
+    @Test
+    public void testNotCachedDueToOptions() throws Exception {
+        String source = "";
+        Template first = TemplateCompiler.getInstance().compile(source);
+        Template last = TemplateCompiler.getInstance().compile(source, 
TemplateOption.IGNORE_WHITESPACE);
+        Assert.assertNotSame(first, last);
+    }
+
+    /**
+     * Test that the empty template produces a empty result.
+     */
+    @Test
+    public void testEmpty() throws Exception {
+        Map<String, Object> parameters = Collections.emptyMap();
+        testTemplate("empty", parameters);
+    }
+
+    /**
+     * Test that comments are removed.
+     */
+    @Test
+    public void testComments() throws Exception {
+        Map<String, Object> parameters = Collections.emptyMap();
+        testTemplate("comments", parameters);
+    }
+
+    /**
+     * Test a template that generates a simple LDAP filter. An important
+     * aspect of this test is that the template contains white space that
+     * must be ignored.
+     */
+    @Test
+    public void testLdap() throws Exception {
+        Map<String, Object> parameters = new HashMap<>();
+        parameters.put("domain", "example.com");
+        int[] ids = new int[10];
+        for (int i = 0; i < 10; i++) {
+            ids[i] = i;
+        }
+        parameters.put("ids", ids);
+        testTemplate("ldap", parameters, TemplateOption.IGNORE_WHITESPACE);
+    }
+
+    /**
+     * This method loads a template and its expected result from resources, 
then
+     * evaluates it with the given parameters and checks that the result is
+     * equal to the expected result.
+     *
+     * @param name the name use to build the names of the resources adding the
+     *   <code>.template</code> and <code>.expected</code> suffixes
+     * @param parameters the parameters for the template
+     * @param options the compilation options for the template
+     * @throws Exception if something fails during the compilation or
+     *   evaluation
+     */
+    public void testTemplate(String name, Map<String, Object> parameters, 
TemplateOption...options) throws Exception {
+        String source = getResource(name + ".template");
+        String expected = getResource(name + ".expected");
+        Template template = TemplateCompiler.getInstance().compile(source, 
options);
+        String actual = template.eval(parameters);
+        Assert.assertEquals(expected, actual);
+    }
+
+    /**
+     * Loads an string from a resource.
+     *
+     * @param name the name of the resource, relative to the class
+     * @return a string with all the text contained in the resource
+     * @throws Exception if something fails while loading the resource
+     */
+    private String getResource(String name) throws Exception {
+        URL url = TemplateTest.class.getResource(name);
+        if (url == null) {
+            throw new Exception("Can't find resource for name \"" + name + 
"\".");
+        }
+        InputStream in = null;
+        ByteArrayOutputStream out = null;
+        try {
+            in = url.openStream();
+            out = new ByteArrayOutputStream(1024);
+            byte[] buffer = new byte[1024];
+            int count;
+            while ((count = in.read(buffer)) != -1) {
+                out.write(buffer, 0, count);
+            }
+        }
+        finally {
+            if (in != null) {
+                in.close();
+            }
+            if (out != null) {
+                out.close();
+            }
+        }
+        if (out != null) {
+            return out.toString();
+        }
+        return null;
+    }
+}
diff --git 
a/backend/manager/modules/utils/src/test/resources/org/ovirt/engine/core/utils/templates/comments.expected
 
b/backend/manager/modules/utils/src/test/resources/org/ovirt/engine/core/utils/templates/comments.expected
new file mode 100644
index 0000000..0ae574e
--- /dev/null
+++ 
b/backend/manager/modules/utils/src/test/resources/org/ovirt/engine/core/utils/templates/comments.expected
@@ -0,0 +1,3 @@
+Some text.
+
+More text.
\ No newline at end of file
diff --git 
a/backend/manager/modules/utils/src/test/resources/org/ovirt/engine/core/utils/templates/comments.template
 
b/backend/manager/modules/utils/src/test/resources/org/ovirt/engine/core/utils/templates/comments.template
new file mode 100644
index 0000000..06687d2
--- /dev/null
+++ 
b/backend/manager/modules/utils/src/test/resources/org/ovirt/engine/core/utils/templates/comments.template
@@ -0,0 +1,3 @@
+Some text.
+<%-- A comment. --%>
+More text.
\ No newline at end of file
diff --git 
a/backend/manager/modules/utils/src/test/resources/org/ovirt/engine/core/utils/templates/ldap.expected
 
b/backend/manager/modules/utils/src/test/resources/org/ovirt/engine/core/utils/templates/ldap.expected
new file mode 100644
index 0000000..be5f36e
--- /dev/null
+++ 
b/backend/manager/modules/utils/src/test/resources/org/ovirt/engine/core/utils/templates/ldap.expected
@@ -0,0 +1 @@
+(&(domain=EXAMPLE.COM)(|(uid=0)(uid=1)(uid=2)(uid=3)(uid=4)(uid=5)(uid=6)(uid=7)(uid=8)(uid=9)))
\ No newline at end of file
diff --git 
a/backend/manager/modules/utils/src/test/resources/org/ovirt/engine/core/utils/templates/ldap.template
 
b/backend/manager/modules/utils/src/test/resources/org/ovirt/engine/core/utils/templates/ldap.template
new file mode 100644
index 0000000..b21c05c
--- /dev/null
+++ 
b/backend/manager/modules/utils/src/test/resources/org/ovirt/engine/core/utils/templates/ldap.template
@@ -0,0 +1,8 @@
+(&
+  (domain=${domain.toUpperCase()})
+  (|
+    <% for (id in ids) { %>
+      (uid=${id})
+    <% } %>
+  )
+)
\ No newline at end of file
diff --git 
a/backend/manager/modules/utils/target/test-classes/org/ovirt/engine/core/utils/templates/empty.expected
 
b/backend/manager/modules/utils/target/test-classes/org/ovirt/engine/core/utils/templates/empty.expected
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ 
b/backend/manager/modules/utils/target/test-classes/org/ovirt/engine/core/utils/templates/empty.expected
diff --git 
a/backend/manager/modules/utils/target/test-classes/org/ovirt/engine/core/utils/templates/empty.template
 
b/backend/manager/modules/utils/target/test-classes/org/ovirt/engine/core/utils/templates/empty.template
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ 
b/backend/manager/modules/utils/target/test-classes/org/ovirt/engine/core/utils/templates/empty.template


--
To view, visit http://gerrit.ovirt.org/14650
To unsubscribe, visit http://gerrit.ovirt.org/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: I14bf2a4d707002c5a1ddcd4ae1e7729957f359b8
Gerrit-PatchSet: 1
Gerrit-Project: ovirt-engine
Gerrit-Branch: master
Gerrit-Owner: Juan Hernandez <[email protected]>
_______________________________________________
Engine-patches mailing list
[email protected]
http://lists.ovirt.org/mailman/listinfo/engine-patches

Reply via email to