You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cayenne.apache.org by aa...@apache.org on 2014/11/02 16:36:54 UTC

[3/6] git commit: CAY-1966 SQLTemplate/SQLSelect positional parameter binding

CAY-1966 SQLTemplate/SQLSelect positional parameter binding

* new org.apache.cayenne.velocity package
* generics fixes in unit tests


Project: http://git-wip-us.apache.org/repos/asf/cayenne/repo
Commit: http://git-wip-us.apache.org/repos/asf/cayenne/commit/c8709542
Tree: http://git-wip-us.apache.org/repos/asf/cayenne/tree/c8709542
Diff: http://git-wip-us.apache.org/repos/asf/cayenne/diff/c8709542

Branch: refs/heads/master
Commit: c870954210bbe2838b49acd13ea3f4455c4e44fb
Parents: d3c5b72
Author: aadamchik <aa...@apache.org>
Authored: Sun Nov 2 15:49:38 2014 +0300
Committer: aadamchik <aa...@apache.org>
Committed: Sun Nov 2 16:10:21 2014 +0300

----------------------------------------------------------------------
 .../cayenne/access/jdbc/BindDirective.java      | 175 ------------
 .../cayenne/access/jdbc/BindEqualDirective.java |  56 ----
 .../access/jdbc/BindNotEqualDirective.java      |  55 ----
 .../access/jdbc/BindObjectEqualDirective.java   | 163 -----------
 .../jdbc/BindObjectNotEqualDirective.java       |  69 -----
 .../cayenne/access/jdbc/ChainDirective.java     | 112 --------
 .../cayenne/access/jdbc/ChunkDirective.java     |  75 ------
 .../cayenne/access/jdbc/ResultDirective.java    | 205 --------------
 .../cayenne/access/jdbc/SQLTemplateAction.java  |   1 +
 .../access/jdbc/SQLTemplateProcessor.java       | 168 ------------
 .../access/jdbc/SQLTemplateRenderingUtils.java  |  37 ---
 .../access/jdbc/SQLTemplateResourceManager.java | 106 --------
 .../apache/cayenne/velocity/BindDirective.java  | 176 ++++++++++++
 .../cayenne/velocity/BindEqualDirective.java    |  57 ++++
 .../cayenne/velocity/BindNotEqualDirective.java |  56 ++++
 .../velocity/BindObjectEqualDirective.java      | 164 ++++++++++++
 .../velocity/BindObjectNotEqualDirective.java   |  70 +++++
 .../apache/cayenne/velocity/ChainDirective.java | 112 ++++++++
 .../apache/cayenne/velocity/ChunkDirective.java |  75 ++++++
 .../cayenne/velocity/ResultDirective.java       | 206 ++++++++++++++
 .../cayenne/velocity/SQLTemplateProcessor.java  | 155 +++++++++++
 .../velocity/SQLTemplateRenderingUtils.java     |  37 +++
 .../velocity/SQLTemplateResourceManager.java    | 106 ++++++++
 .../cayenne/access/jdbc/BindDirectiveIT.java    | 267 ------------------
 .../access/jdbc/MockupRuntimeServices.java      |  42 ---
 .../cayenne/access/jdbc/ResultDirectiveIT.java  | 187 -------------
 .../jdbc/SQLTemplateProcessorChainTest.java     | 219 ---------------
 .../jdbc/SQLTemplateProcessorSelectTest.java    | 109 --------
 .../access/jdbc/SQLTemplateProcessorTest.java   | 260 ------------------
 .../jdbc/SQLTemplateResourceManagerTest.java    |  65 -----
 .../cayenne/velocity/BindDirectiveIT.java       | 268 +++++++++++++++++++
 .../cayenne/velocity/ResultDirectiveIT.java     | 188 +++++++++++++
 .../velocity/SQLTemplateProcessorChainTest.java | 221 +++++++++++++++
 .../SQLTemplateProcessorSelectTest.java         | 112 ++++++++
 .../velocity/SQLTemplateProcessorTest.java      | 235 ++++++++++++++++
 .../SQLTemplateResourceManagerTest.java         |  77 ++++++
 36 files changed, 2316 insertions(+), 2370 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cayenne/blob/c8709542/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/BindDirective.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/BindDirective.java b/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/BindDirective.java
deleted file mode 100644
index 59245a5..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/BindDirective.java
+++ /dev/null
@@ -1,175 +0,0 @@
-/*****************************************************************
- *   Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you under the Apache License, Version 2.0 (the
- *  "License"); you may not use this file except in compliance
- *  with the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing,
- *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *  KIND, either express or implied.  See the License for the
- *  specific language governing permissions and limitations
- *  under the License.
- ****************************************************************/
-
-package org.apache.cayenne.access.jdbc;
-
-import java.io.IOException;
-import java.io.Writer;
-import java.util.Collection;
-import java.util.Iterator;
-
-import org.apache.cayenne.dba.TypesMapping;
-import org.apache.cayenne.util.ConversionUtil;
-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.runtime.directive.Directive;
-import org.apache.velocity.runtime.parser.node.Node;
-
-/**
- * A custom Velocity directive to create a PreparedStatement parameter text. There are the
- * following possible invocation formats inside the template:
- * 
- * <pre>
- * #bind(value) - e.g. #bind($xyz)
- * #bind(value jdbc_type_name) - e.g. #bind($xyz 'VARCHAR'). This is the most common and useful form.
- * #bind(value jdbc_type_name, scale) - e.g. #bind($xyz 'VARCHAR' 2)
- * </pre>
- * <p>
- * Other examples:
- * </p>
- * <p>
- * <strong>Binding literal parameter value:</strong>
- * </p>
- * <p>
- * <code>"WHERE SOME_COLUMN &gt; #bind($xyz)"</code> produces
- * <code>"WHERE SOME_COLUMN &gt; ?"</code> and also places the value of the "xyz" parameter
- * in the context "bindings" collection.
- * </p>
- * <p>
- * <strong>Binding ID column of a DataObject value:</strong>
- * </p>
- * <p>
- * <code>"WHERE ID_COL1 = #bind($helper.cayenneExp($xyz, 'db:ID_COL2')) 
- * AND ID_COL2 = #bind($helper.cayenneExp($xyz, 'db:ID_COL2'))"</code> produces <code>"WHERE ID_COL1 = ? AND ID_COL2 = ?"</code> and also places the
- * values of id columns of the DataObject parameter "xyz" in the context "bindings"
- * collection.
- * </p>
- * 
- * @since 1.1
- */
-public class BindDirective extends Directive {
-
-    @Override
-    public String getName() {
-        return "bind";
-    }
-
-    @Override
-    public int getType() {
-        return LINE;
-    }
-
-    /**
-     * Extracts the value of the object property to render and passes control to
-     * {@link #render(InternalContextAdapter, Writer, ParameterBinding)} to do the actual
-     * rendering.
-     */
-    @Override
-    public boolean render(InternalContextAdapter context, Writer writer, Node node)
-            throws IOException, ResourceNotFoundException, ParseErrorException,
-            MethodInvocationException {
-
-        Object value = getChild(context, node, 0);
-        Object type = getChild(context, node, 1);
-        int scale = ConversionUtil.toInt(getChild(context, node, 2), -1);
-        String typeString = type != null ? type.toString() : null;
-
-        if (value instanceof Collection) {
-            Iterator<?> it = ((Collection) value).iterator();
-            while (it.hasNext()) {
-                render(context, writer, node, it.next(), typeString, scale);
-
-                if (it.hasNext()) {
-                    writer.write(',');
-                }
-            }
-        }
-        else {
-            render(context, writer, node, value, typeString, scale);
-        }
-
-        return true;
-    }
-
-    /**
-     * @since 3.0
-     */
-    protected void render(
-            InternalContextAdapter context,
-            Writer writer,
-            Node node,
-            Object value,
-            String typeString,
-            int scale) throws IOException, ParseErrorException {
-
-        int jdbcType = TypesMapping.NOT_DEFINED;
-        if (typeString != null) {
-            jdbcType = TypesMapping.getSqlTypeByName(typeString);
-        }
-        else if (value != null) {
-            jdbcType = TypesMapping.getSqlTypeByJava(value.getClass());
-        } else {
-            // value is null, set JDBC type to NULL
-        	jdbcType = TypesMapping.getSqlTypeByName(TypesMapping.SQL_NULL);
-        }
-
-        if (jdbcType == TypesMapping.NOT_DEFINED) {
-            throw new ParseErrorException("Can't determine JDBC type of binding ("
-                    + value
-                    + ", "
-                    + typeString
-                    + ") at line "
-                    + node.getLine()
-                    + ", column "
-                    + node.getColumn());
-        }
-
-        render(context, writer, new ParameterBinding(value, jdbcType, scale));
-    }
-
-    protected void render(
-            InternalContextAdapter context,
-            Writer writer,
-            ParameterBinding binding) throws IOException {
-
-        bind(context, binding);
-        writer.write('?');
-    }
-
-    protected Object getChild(InternalContextAdapter context, Node node, int i)
-            throws MethodInvocationException {
-        return (i >= 0 && i < node.jjtGetNumChildren()) ? node.jjtGetChild(i).value(
-                context) : null;
-    }
-
-    /**
-     * Adds value to the list of bindings in the context.
-     */
-    protected void bind(InternalContextAdapter context, ParameterBinding binding) {
-
-        Collection bindings = (Collection) context.getInternalUserContext().get(
-                SQLTemplateProcessor.BINDINGS_LIST_KEY);
-
-        if (bindings != null) {
-            bindings.add(binding);
-        }
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c8709542/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/BindEqualDirective.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/BindEqualDirective.java b/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/BindEqualDirective.java
deleted file mode 100644
index 773daa6..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/BindEqualDirective.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*****************************************************************
- *   Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you under the Apache License, Version 2.0 (the
- *  "License"); you may not use this file except in compliance
- *  with the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing,
- *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *  KIND, either express or implied.  See the License for the
- *  specific language governing permissions and limitations
- *  under the License.
- ****************************************************************/
-
-package org.apache.cayenne.access.jdbc;
-
-import java.io.IOException;
-import java.io.Writer;
-
-import org.apache.velocity.context.InternalContextAdapter;
-
-/**
- * A custom Velocity directive to create a PreparedStatement parameter text
- * for "= ?". If null value is encountered, generated text will look like "IS NULL".
- * Usage in Velocity template is "WHERE SOME_COLUMN #bindEqual($xyz)".
- * 
- * @since 1.1
- */
-public class BindEqualDirective extends BindDirective {
-
-    @Override
-    public String getName() {
-        return "bindEqual";
-    }
-
-    @Override
-    protected void render(
-        InternalContextAdapter context,
-        Writer writer,
-        ParameterBinding binding)
-        throws IOException {
-
-        if (binding.getValue() != null) {
-            bind(context, binding);
-            writer.write("= ?");
-        }
-        else {
-            writer.write("IS NULL");
-        }
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c8709542/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/BindNotEqualDirective.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/BindNotEqualDirective.java b/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/BindNotEqualDirective.java
deleted file mode 100644
index ee54602..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/BindNotEqualDirective.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*****************************************************************
- *   Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you under the Apache License, Version 2.0 (the
- *  "License"); you may not use this file except in compliance
- *  with the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing,
- *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *  KIND, either express or implied.  See the License for the
- *  specific language governing permissions and limitations
- *  under the License.
- ****************************************************************/
-
-package org.apache.cayenne.access.jdbc;
-
-import java.io.IOException;
-import java.io.Writer;
-
-import org.apache.velocity.context.InternalContextAdapter;
-
-/**
- * A custom Velocity directive to create a PreparedStatement parameter text for "&lt;&gt;?".
- * If null value is encountered, generated text will look like "IS NOT NULL". Usage in
- * Velocity template is "WHERE SOME_COLUMN #bindNotEqual($xyz)".
- * 
- * @since 1.1
- */
-public class BindNotEqualDirective extends BindDirective {
-
-    @Override
-    public String getName() {
-        return "bindNotEqual";
-    }
-
-    @Override
-    protected void render(
-            InternalContextAdapter context,
-            Writer writer,
-            ParameterBinding binding) throws IOException {
-
-        if (binding.getValue() != null) {
-            bind(context, binding);
-            writer.write("<> ?");
-        }
-        else {
-            writer.write("IS NOT NULL");
-        }
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c8709542/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/BindObjectEqualDirective.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/BindObjectEqualDirective.java b/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/BindObjectEqualDirective.java
deleted file mode 100644
index f35906c..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/BindObjectEqualDirective.java
+++ /dev/null
@@ -1,163 +0,0 @@
-/*****************************************************************
- *   Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you under the Apache License, Version 2.0 (the
- *  "License"); you may not use this file except in compliance
- *  with the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing,
- *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *  KIND, either express or implied.  See the License for the
- *  specific language governing permissions and limitations
- *  under the License.
- ****************************************************************/
-package org.apache.cayenne.access.jdbc;
-
-import java.io.IOException;
-import java.io.Writer;
-import java.sql.Types;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Map;
-
-import org.apache.cayenne.ObjectId;
-import org.apache.cayenne.Persistent;
-import org.apache.cayenne.dba.TypesMapping;
-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.runtime.parser.node.Node;
-
-/**
- * A custom Velocity directive to create a set of SQL conditions to match an ObjectId of
- * an object. Usage in Velocity template is "WHERE #bindObjectEqual($object)" or "WHERE
- * #bindObjectEqual($object $columns $idValues)".
- * 
- * @since 3.0
- */
-public class BindObjectEqualDirective extends BindDirective {
-
-    @Override
-    public String getName() {
-        return "bindObjectEqual";
-    }
-
-    @Override
-    public boolean render(InternalContextAdapter context, Writer writer, Node node)
-            throws IOException, ResourceNotFoundException, ParseErrorException,
-            MethodInvocationException {
-
-        Object object = getChild(context, node, 0);
-        Map idMap = toIdMap(object);
-
-        Object sqlColumns = getChild(context, node, 1);
-        Object idColumns = getChild(context, node, 2);
-
-        if (idMap == null) {
-            // assume null object, and bind all null values
-
-            if (sqlColumns == null || idColumns == null) {
-                throw new ParseErrorException("Invalid parameters. "
-                        + "Either object has to be set "
-                        + "or sqlColumns and idColumns or both.");
-            }
-
-            idMap = Collections.EMPTY_MAP;
-        }
-        else if (sqlColumns == null || idColumns == null) {
-            // infer SQL columns from ID columns
-            sqlColumns = idMap.keySet().toArray();
-            idColumns = sqlColumns;
-        }
-
-        Object[] sqlColumnsArray = toArray(sqlColumns);
-        Object[] idColumnsArray = toArray(idColumns);
-
-        if (sqlColumnsArray.length != idColumnsArray.length) {
-            throw new ParseErrorException(
-                    "SQL columns and ID columns arrays have different sizes.");
-        }
-
-        for (int i = 0; i < sqlColumnsArray.length; i++) {
-
-            Object value = idMap.get(idColumnsArray[i]);
-
-            int jdbcType = (value != null) ? TypesMapping.getSqlTypeByJava(value
-                    .getClass()) : Types.INTEGER;
-
-            renderColumn(context, writer, sqlColumnsArray[i], i);
-            writer.write(' ');
-            render(context, writer, new ParameterBinding(value, jdbcType, -1));
-        }
-
-        return true;
-    }
-
-    protected Object[] toArray(Object columns) {
-        if (columns instanceof Collection) {
-            return ((Collection) columns).toArray();
-        }
-        else if (columns.getClass().isArray()) {
-            return (Object[]) columns;
-        }
-        else {
-            return new Object[] {
-                columns
-            };
-        }
-    }
-
-    protected Map toIdMap(Object object) throws ParseErrorException {
-        if (object instanceof Persistent) {
-            return ((Persistent) object).getObjectId().getIdSnapshot();
-        }
-        else if (object instanceof ObjectId) {
-            return ((ObjectId) object).getIdSnapshot();
-        }
-        else if(object instanceof Map) {
-            return (Map) object;
-        }
-        else if (object != null) {
-            throw new ParseErrorException(
-                    "Invalid object parameter, expected Persistent or ObjectId or null: "
-                            + object);
-        }
-        else {
-            return null;
-        }
-    }
-
-    protected void renderColumn(
-            InternalContextAdapter context,
-            Writer writer,
-            Object columnName,
-            int columnIndex) throws IOException {
-
-        if (columnIndex > 0) {
-            writer.write(" AND ");
-        }
-
-        writer.write(columnName.toString());
-    }
-
-    @Override
-    protected void render(
-            InternalContextAdapter context,
-            Writer writer,
-            ParameterBinding binding) throws IOException {
-
-        if (binding.getValue() != null) {
-            bind(context, binding);
-            writer.write("= ?");
-        }
-        else {
-            writer.write("IS NULL");
-        }
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c8709542/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/BindObjectNotEqualDirective.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/BindObjectNotEqualDirective.java b/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/BindObjectNotEqualDirective.java
deleted file mode 100644
index 0a51eed..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/BindObjectNotEqualDirective.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*****************************************************************
- *   Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you under the Apache License, Version 2.0 (the
- *  "License"); you may not use this file except in compliance
- *  with the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing,
- *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *  KIND, either express or implied.  See the License for the
- *  specific language governing permissions and limitations
- *  under the License.
- ****************************************************************/
-package org.apache.cayenne.access.jdbc;
-
-import java.io.IOException;
-import java.io.Writer;
-
-import org.apache.velocity.context.InternalContextAdapter;
-
-/**
- * A custom Velocity directive to create a set of SQL conditions to check unequality of an
- * ObjectId of an object. Usage in Velocity template is "WHERE
- * #bindObjectNotEqual($object)" or "WHERE #bindObjectNotEqual($object $columns
- * $idValues)".
- * 
- * @since 3.0
- */
-public class BindObjectNotEqualDirective extends BindObjectEqualDirective {
-
-    @Override
-    public String getName() {
-        return "bindObjectNotEqual";
-    }
-
-    @Override
-    protected void renderColumn(
-            InternalContextAdapter context,
-            Writer writer,
-            Object columnName,
-            int columnIndex) throws IOException {
-
-        if (columnIndex > 0) {
-            writer.write(" OR ");
-        }
-
-        writer.write(columnName.toString());
-    }
-
-    @Override
-    protected void render(
-            InternalContextAdapter context,
-            Writer writer,
-            ParameterBinding binding) throws IOException {
-
-        if (binding.getValue() != null) {
-            bind(context, binding);
-            writer.write("<> ?");
-        }
-        else {
-            writer.write("IS NOT NULL");
-        }
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c8709542/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/ChainDirective.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/ChainDirective.java b/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/ChainDirective.java
deleted file mode 100644
index 30a7816..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/ChainDirective.java
+++ /dev/null
@@ -1,112 +0,0 @@
-/*****************************************************************
- *   Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you under the Apache License, Version 2.0 (the
- *  "License"); you may not use this file except in compliance
- *  with the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing,
- *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *  KIND, either express or implied.  See the License for the
- *  specific language governing permissions and limitations
- *  under the License.
- ****************************************************************/
-
-package org.apache.cayenne.access.jdbc;
-
-import java.io.IOException;
-import java.io.StringWriter;
-import java.io.Writer;
-
-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.runtime.directive.Directive;
-import org.apache.velocity.runtime.parser.node.ASTDirective;
-import org.apache.velocity.runtime.parser.node.Node;
-
-/**
- * A custom Velocity directive to conditionally join a number of {@link ChunkDirective chunks}.
- * Usage of chain is the following:
- * 
- * <pre>
- * #chain(operator) - e.g. #chain(' AND ')
- * #chain(operator prefix) - e.g. #chain(' AND ' 'WHERE ')</pre>
- * 
- * <p><code>operator</code> (e.g. AND, OR, etc.) is used to join chunks that are included
- * in a chain. <code>prefix</code> is inserted if a chain contains at least one chunk.
- * </p>
- * 
- * @since 1.1
- */
-public class ChainDirective extends Directive {
-
-    @Override
-    public String getName() {
-        return "chain";
-    }
-
-    @Override
-    public int getType() {
-        return BLOCK;
-    }
-
-    @Override
-    public boolean render(InternalContextAdapter context, Writer writer, Node node)
-        throws
-            IOException,
-            ResourceNotFoundException,
-            ParseErrorException,
-            MethodInvocationException {
-
-        int size = node.jjtGetNumChildren();
-        if (size == 0) {
-            return true;
-        }
-
-        // BLOCK is the last child
-        Node block = node.jjtGetChild(node.jjtGetNumChildren() - 1);
-        String join = (size > 1) ? (String) node.jjtGetChild(0).value(context) : "";
-        String prefix = (size > 2) ? (String) node.jjtGetChild(1).value(context) : "";
-
-        // if there is a conditional prefix, use a separate buffer ofr children
-        StringWriter childWriter = new StringWriter(30);
-
-        int len = block.jjtGetNumChildren();
-        int includedChunks = 0;
-        for (int i = 0; i < len; i++) {
-            Node child = block.jjtGetChild(i);
-
-            // if this is a "chunk", evaluate its expression and prepend join if included...
-            if (child instanceof ASTDirective
-                && "chunk".equals(((ASTDirective) child).getDirectiveName())) {
-
-                if (child.jjtGetNumChildren() < 2
-                    || child.jjtGetChild(0).value(context) != null) {
-
-                    if (includedChunks > 0) {
-                        childWriter.write(join);
-                    }
-
-                    includedChunks++;
-                }
-            }
-
-            child.render(context, childWriter);
-        }
-
-        if (includedChunks > 0) {
-            childWriter.flush();
-            writer.write(prefix);
-            writer.write(childWriter.toString());
-        }
-
-        return true;
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c8709542/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/ChunkDirective.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/ChunkDirective.java b/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/ChunkDirective.java
deleted file mode 100644
index fb7c870..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/ChunkDirective.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*****************************************************************
- *   Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you under the Apache License, Version 2.0 (the
- *  "License"); you may not use this file except in compliance
- *  with the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing,
- *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *  KIND, either express or implied.  See the License for the
- *  specific language governing permissions and limitations
- *  under the License.
- ****************************************************************/
-
-package org.apache.cayenne.access.jdbc;
-
-import java.io.IOException;
-import java.io.Writer;
-
-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.runtime.directive.Directive;
-import org.apache.velocity.runtime.parser.node.Node;
-
-/**
- * A custom Velocity directive to describe a conditional chunk of a {@link ChainDirective chain}.
- * Usage of chunk is the following:
- * 
- * <pre>
- * #chunk()...#end - e.g. #chunk()A = 5#end
- * #chunk($paramKey)...#end - e.g. #chunk($a)A = $a#end
- * </pre>
- * <p>
- * If context contains paramKey and it's value isn't null, chunk is included in the
- * chain, and if it is not the first chunk, it is prefixed with chain join (OR/AND).
- * If context doesn't contain paramKey or it's value is null, chunk is skipped.
- * @since 1.1
- */
-public class ChunkDirective extends Directive {
-
-    @Override
-    public String getName() {
-        return "chunk";
-    }
-
-    @Override
-    public int getType() {
-        return BLOCK;
-    }
-
-    @Override
-    public boolean render(InternalContextAdapter context, Writer writer, Node node)
-            throws IOException, ResourceNotFoundException, ParseErrorException,
-            MethodInvocationException {
-
-        // first child is an expression, second is BLOCK
-        if (node.jjtGetNumChildren() > 1 && node.jjtGetChild(0).value(context) == null) {
-            // skip this chunk
-            return false;
-        }
-
-        // BLOCK is the last child
-        Node block = node.jjtGetChild(node.jjtGetNumChildren() - 1);
-        block.render(context, writer);
-        return true;
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c8709542/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/ResultDirective.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/ResultDirective.java b/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/ResultDirective.java
deleted file mode 100644
index c24804a..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/ResultDirective.java
+++ /dev/null
@@ -1,205 +0,0 @@
-/*****************************************************************
- *   Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you under the Apache License, Version 2.0 (the
- *  "License"); you may not use this file except in compliance
- *  with the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing,
- *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *  KIND, either express or implied.  See the License for the
- *  specific language governing permissions and limitations
- *  under the License.
- ****************************************************************/
-
-package org.apache.cayenne.access.jdbc;
-
-import java.io.IOException;
-import java.io.Writer;
-import java.math.BigDecimal;
-import java.math.BigInteger;
-import java.sql.Date;
-import java.sql.Time;
-import java.sql.Timestamp;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Map;
-
-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.runtime.directive.Directive;
-import org.apache.velocity.runtime.parser.node.Node;
-import org.apache.cayenne.util.Util;
-
-/**
- * A custom Velocity directive to describe a ResultSet column. There are the following
- * possible invocation formats inside the template:
- * 
- * <pre>
- *       #result(column_name) - e.g. #result('ARTIST_ID')
- *       #result(column_name java_type) - e.g. #result('ARTIST_ID' 'String')
- *       #result(column_name java_type column_alias) - e.g. #result('ARTIST_ID' 'String' 'ID')
- *       #result(column_name java_type column_alias data_row_key) - e.g. #result('ARTIST_ID' 'String' 'ID' 'toArtist.ID')
- * </pre>
- * 
- * <p>
- * 'data_row_key' is needed if SQL 'column_alias' is not appropriate as a DataRow key on
- * the Cayenne side. One common case when this happens is when a DataRow retrieved from a
- * query is mapped using joint prefetch keys. In this case DataRow must use DB_PATH
- * expressions for joint column keys, and their format is incompatible with most databases
- * alias format.
- * </p>
- * <p>
- * Most common Java types used in JDBC can be specified without a package. This includes
- * all numeric types, primitives, String, SQL dates, BigDecimal and BigInteger.
- * </p>
- * 
- * @since 1.1
- */
-public class ResultDirective extends Directive {
-
-    private static final Map<String, String> typesGuess;
-
-    static {
-        // init default types
-        typesGuess = new HashMap<String, String>();
-
-        // primitives
-        typesGuess.put("long", Long.class.getName());
-        typesGuess.put("double", Double.class.getName());
-        typesGuess.put("byte", Byte.class.getName());
-        typesGuess.put("boolean", Boolean.class.getName());
-        typesGuess.put("float", Float.class.getName());
-        typesGuess.put("short", Short.class.getName());
-        typesGuess.put("int", Integer.class.getName());
-
-        // numeric
-        typesGuess.put("Long", Long.class.getName());
-        typesGuess.put("Double", Double.class.getName());
-        typesGuess.put("Byte", Byte.class.getName());
-        typesGuess.put("Boolean", Boolean.class.getName());
-        typesGuess.put("Float", Float.class.getName());
-        typesGuess.put("Short", Short.class.getName());
-        typesGuess.put("Integer", Integer.class.getName());
-
-        // other
-        typesGuess.put("String", String.class.getName());
-        typesGuess.put("Date", Date.class.getName());
-        typesGuess.put("Time", Time.class.getName());
-        typesGuess.put("Timestamp", Timestamp.class.getName());
-        typesGuess.put("BigDecimal", BigDecimal.class.getName());
-        typesGuess.put("BigInteger", BigInteger.class.getName());
-    }
-
-    @Override
-    public String getName() {
-        return "result";
-    }
-
-    @Override
-    public int getType() {
-        return LINE;
-    }
-
-    @Override
-    public boolean render(InternalContextAdapter context, Writer writer, Node node)
-            throws IOException, ResourceNotFoundException, ParseErrorException,
-            MethodInvocationException {
-
-        String column = getChildAsString(context, node, 0);
-        if (column == null) {
-            throw new ParseErrorException("Column name expected at line "
-                    + node.getLine()
-                    + ", column "
-                    + node.getColumn());
-        }
-
-        String alias = getChildAsString(context, node, 2);
-        String dataRowKey = getChildAsString(context, node, 3);
-
-        // determine what we want to name this column in a resulting DataRow...
-        String label = (!Util.isEmptyString(dataRowKey)) ? dataRowKey : (!Util
-                .isEmptyString(alias)) ? alias : null;
-
-        ColumnDescriptor columnDescriptor = new ColumnDescriptor();
-        columnDescriptor.setName(column);
-        columnDescriptor.setDataRowKey(label);
-
-        String type = getChildAsString(context, node, 1);
-        if (type != null) {
-            columnDescriptor.setJavaClass(guessType(type));
-        }
-
-        // TODO: andrus 6/27/2007 - this is an unofficial jdbcType parameter that is added
-        // temporarily pending CAY-813 implementation for the sake of EJBQL query...
-        Object jdbcType = getChild(context, node, 4);
-        if (jdbcType instanceof Number) {
-            columnDescriptor.setJdbcType(((Number) jdbcType).intValue());
-        }
-
-        writer.write(column);
-
-        // append column alias if needed.
-
-        // Note that if table aliases are used, this logic will result in SQL like
-        // "t0.ARTIST_NAME AS ARTIST_NAME". Doing extra regex matching to handle this
-        // won't probably buy us much.
-        if (!Util.isEmptyString(alias) && !alias.equals(column)) {
-            writer.write(" AS ");
-            writer.write(alias);
-        }
-
-        bindResult(context, columnDescriptor);
-        return true;
-    }
-
-    protected Object getChild(InternalContextAdapter context, Node node, int i)
-            throws MethodInvocationException {
-        return (i >= 0 && i < node.jjtGetNumChildren()) ? node.jjtGetChild(i).value(
-                context) : null;
-    }
-
-    /**
-     * Returns a directive argument at a given index converted to String.
-     * 
-     * @since 1.2
-     */
-    protected String getChildAsString(InternalContextAdapter context, Node node, int i)
-            throws MethodInvocationException {
-        Object value = getChild(context, node, i);
-        return (value != null) ? value.toString() : null;
-    }
-
-    /**
-     * Converts "short" type notation to the fully qualified class name. Right now
-     * supports all major standard SQL types, including primitives. All other types are
-     * expected to be fully qualified, and are not converted.
-     */
-    protected String guessType(String type) {
-        String guessed = typesGuess.get(type);
-        return guessed != null ? guessed : type;
-    }
-
-    /**
-     * Adds value to the list of result columns in the context.
-     */
-    protected void bindResult(
-            InternalContextAdapter context,
-            ColumnDescriptor columnDescriptor) {
-
-        Collection<Object> resultColumns = (Collection<Object>) context
-                .getInternalUserContext()
-                .get(SQLTemplateProcessor.RESULT_COLUMNS_LIST_KEY);
-
-        if (resultColumns != null) {
-            resultColumns.add(columnDescriptor);
-        }
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c8709542/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/SQLTemplateAction.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/SQLTemplateAction.java b/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/SQLTemplateAction.java
index e86f04b..50e8691 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/SQLTemplateAction.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/SQLTemplateAction.java
@@ -49,6 +49,7 @@ import org.apache.cayenne.query.QueryMetadata;
 import org.apache.cayenne.query.SQLAction;
 import org.apache.cayenne.query.SQLTemplate;
 import org.apache.cayenne.util.Util;
+import org.apache.cayenne.velocity.SQLTemplateProcessor;
 import org.apache.commons.collections.IteratorUtils;
 
 /**

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c8709542/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/SQLTemplateProcessor.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/SQLTemplateProcessor.java b/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/SQLTemplateProcessor.java
deleted file mode 100644
index d5f487e..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/SQLTemplateProcessor.java
+++ /dev/null
@@ -1,168 +0,0 @@
-/*****************************************************************
- *   Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you under the Apache License, Version 2.0 (the
- *  "License"); you may not use this file except in compliance
- *  with the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing,
- *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *  KIND, either express or implied.  See the License for the
- *  specific language governing permissions and limitations
- *  under the License.
- ****************************************************************/
-
-package org.apache.cayenne.access.jdbc;
-
-import java.io.StringReader;
-import java.io.StringWriter;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import org.apache.cayenne.CayenneRuntimeException;
-import org.apache.velocity.VelocityContext;
-import org.apache.velocity.context.InternalContextAdapterImpl;
-import org.apache.velocity.runtime.RuntimeConstants;
-import org.apache.velocity.runtime.RuntimeInstance;
-import org.apache.velocity.runtime.log.NullLogChute;
-import org.apache.velocity.runtime.parser.ParseException;
-import org.apache.velocity.runtime.parser.node.SimpleNode;
-
-/**
- * Processor for SQL velocity templates.
- * 
- * @see org.apache.cayenne.query.SQLTemplate
- * @since 1.1
- */
-class SQLTemplateProcessor {
-
-    private static RuntimeInstance sharedRuntime;
-
-    static final String BINDINGS_LIST_KEY = "bindings";
-    static final String RESULT_COLUMNS_LIST_KEY = "resultColumns";
-    static final String HELPER_KEY = "helper";
-
-    private static final SQLTemplateRenderingUtils sharedUtils = new SQLTemplateRenderingUtils();
-
-    RuntimeInstance velocityRuntime;
-    SQLTemplateRenderingUtils renderingUtils;
-
-    static {
-        initVelocityRuntime();
-    }
-
-    private static void initVelocityRuntime() {
-        // init static velocity engine
-        sharedRuntime = new RuntimeInstance();
-
-        // set null logger
-        sharedRuntime.addProperty(
-                RuntimeConstants.RUNTIME_LOG_LOGSYSTEM,
-                new NullLogChute());
-
-        sharedRuntime.addProperty(
-                RuntimeConstants.RESOURCE_MANAGER_CLASS,
-                SQLTemplateResourceManager.class.getName());
-        sharedRuntime.addProperty("userdirective", BindDirective.class.getName());
-        sharedRuntime.addProperty("userdirective", BindEqualDirective.class.getName());
-        sharedRuntime.addProperty("userdirective", BindNotEqualDirective.class.getName());
-        sharedRuntime.addProperty("userdirective", BindObjectEqualDirective.class
-                .getName());
-        sharedRuntime.addProperty("userdirective", BindObjectNotEqualDirective.class
-                .getName());
-        sharedRuntime.addProperty("userdirective", ResultDirective.class.getName());
-        sharedRuntime.addProperty("userdirective", ChainDirective.class.getName());
-        sharedRuntime.addProperty("userdirective", ChunkDirective.class.getName());
-        try {
-            sharedRuntime.init();
-        }
-        catch (Exception ex) {
-            throw new CayenneRuntimeException(
-                    "Error setting up Velocity RuntimeInstance.",
-                    ex);
-        }
-    }
-
-    SQLTemplateProcessor() {
-        this.velocityRuntime = sharedRuntime;
-        this.renderingUtils = sharedUtils;
-    }
-
-    SQLTemplateProcessor(RuntimeInstance velocityRuntime,
-            SQLTemplateRenderingUtils renderingUtils) {
-        this.velocityRuntime = velocityRuntime;
-        this.renderingUtils = renderingUtils;
-    }
-
-    /**
-     * Builds and returns a SQLStatement based on SQL template and a set of parameters.
-     * During rendering, VelocityContext exposes the following as variables: all
-     * parameters in the map, {@link SQLTemplateRenderingUtils} as a "helper" variable and
-     * SQLStatement object as "statement" variable.
-     */
-	SQLStatement processTemplate(String template, Map<String, ?> parameters) throws Exception {
-		// have to make a copy of parameter map since we are gonna modify it..
-		Map<String, Object> internalParameters = (parameters != null && !parameters.isEmpty()) ? new HashMap<String, Object>(
-				parameters) : new HashMap<String, Object>(5);
-
-		List<ParameterBinding> bindings = new ArrayList<ParameterBinding>();
-		List<ColumnDescriptor> results = new ArrayList<ColumnDescriptor>();
-		internalParameters.put(BINDINGS_LIST_KEY, bindings);
-		internalParameters.put(RESULT_COLUMNS_LIST_KEY, results);
-		internalParameters.put(HELPER_KEY, renderingUtils);
-
-		String sql = buildStatement(new VelocityContext(internalParameters), template);
-
-		ParameterBinding[] bindingsArray = new ParameterBinding[bindings.size()];
-		bindings.toArray(bindingsArray);
-
-		ColumnDescriptor[] resultsArray = new ColumnDescriptor[results.size()];
-		results.toArray(resultsArray);
-
-		return new SQLStatement(sql, resultsArray, bindingsArray);
-	}
-
-    String buildStatement(VelocityContext context, String template)
-            throws Exception {
-        // Note: this method is a reworked version of
-        // org.apache.velocity.app.Velocity.evaluate(..)
-        // cleaned up to avoid using any Velocity singletons
-
-        StringWriter out = new StringWriter(template.length());
-        SimpleNode nodeTree = null;
-
-        try {
-            nodeTree = velocityRuntime.parse(new StringReader(template), template);
-        }
-        catch (ParseException pex) {
-            throw new CayenneRuntimeException("Error parsing template '"
-                    + template
-                    + "' : "
-                    + pex.getMessage());
-        }
-
-        if (nodeTree == null) {
-            throw new CayenneRuntimeException("Error parsing template " + template);
-        }
-
-        // ... not sure what InternalContextAdapter is for...
-        InternalContextAdapterImpl ica = new InternalContextAdapterImpl(context);
-        ica.pushCurrentTemplateName(template);
-
-        try {
-            nodeTree.init(ica, velocityRuntime);
-            nodeTree.render(ica, out);
-            return out.toString();
-        }
-        finally {
-            ica.popCurrentTemplateName();
-        }
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c8709542/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/SQLTemplateRenderingUtils.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/SQLTemplateRenderingUtils.java b/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/SQLTemplateRenderingUtils.java
deleted file mode 100644
index 6350f56..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/SQLTemplateRenderingUtils.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*****************************************************************
- *   Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you under the Apache License, Version 2.0 (the
- *  "License"); you may not use this file except in compliance
- *  with the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing,
- *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *  KIND, either express or implied.  See the License for the
- *  specific language governing permissions and limitations
- *  under the License.
- ****************************************************************/
-
-package org.apache.cayenne.access.jdbc;
-
-import org.apache.cayenne.exp.Expression;
-
-/**
- * Implements utility methods used inside Velocity templates
- * when rendering SQLTemplates.
- * 
- * @since 1.1
- */
-public class SQLTemplateRenderingUtils {
-    /**
-     * Returns the result of evaluation of expression with object.
-     */
-    public Object cayenneExp(Object object, String expression) {
-        return Expression.fromString(expression).evaluate(object);
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c8709542/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/SQLTemplateResourceManager.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/SQLTemplateResourceManager.java b/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/SQLTemplateResourceManager.java
deleted file mode 100644
index d339d71..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/SQLTemplateResourceManager.java
+++ /dev/null
@@ -1,106 +0,0 @@
-/*****************************************************************
- *   Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you under the Apache License, Version 2.0 (the
- *  "License"); you may not use this file except in compliance
- *  with the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing,
- *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *  KIND, either express or implied.  See the License for the
- *  specific language governing permissions and limitations
- *  under the License.
- ****************************************************************/
-
-package org.apache.cayenne.access.jdbc;
-
-import java.io.ByteArrayInputStream;
-import java.io.InputStream;
-import java.util.Map;
-
-import org.apache.commons.collections.ExtendedProperties;
-import org.apache.commons.collections.map.LRUMap;
-import org.apache.velocity.Template;
-import org.apache.velocity.exception.ParseErrorException;
-import org.apache.velocity.exception.ResourceNotFoundException;
-import org.apache.velocity.runtime.RuntimeServices;
-import org.apache.velocity.runtime.resource.Resource;
-import org.apache.velocity.runtime.resource.ResourceManager;
-import org.apache.velocity.runtime.resource.loader.ResourceLoader;
-
-/**
- * An implementation of the Velocity ResourceManager and ResourceLoader that
- * creates templates from in-memory Strings.
- * 
- * @since 1.1
- */
-// class must be public since it is instantiated by Velocity via reflection.
-public class SQLTemplateResourceManager
-    extends ResourceLoader
-    implements ResourceManager {
-
-    protected Map<String, Template> templateCache;
-
-    public void initialize(RuntimeServices rs) throws Exception {
-        super.rsvc = rs;
-        this.templateCache = new LRUMap(100);
-    }
-
-    public void clearCache() {
-        templateCache.clear();
-    }
-
-    /**
-     * Returns a Velocity Resource which is a Template for the given SQL.
-     */
-    public Resource getResource(String resourceName, int resourceType, String encoding)
-        throws ResourceNotFoundException, ParseErrorException, Exception {
-
-        synchronized (templateCache) {
-            Template resource = templateCache.get(resourceName);
-
-            if (resource == null) {
-                resource = new Template();
-                resource.setRuntimeServices(rsvc);
-                resource.setResourceLoader(this);
-                resource.setName(resourceName);
-                resource.setEncoding(encoding);
-                resource.process();
-
-                templateCache.put(resourceName, resource);
-            }
-
-            return resource;
-        }
-    }
-
-    public String getLoaderNameForResource(String resourceName) {
-        return getClass().getName();
-    }
-
-    @Override
-    public long getLastModified(Resource resource) {
-        return -1;
-    }
-
-    @Override
-    public InputStream getResourceStream(String source)
-        throws ResourceNotFoundException {
-        return new ByteArrayInputStream(source.getBytes());
-    }
-
-    @Override
-    public void init(ExtendedProperties configuration) {
-
-    }
-
-    @Override
-    public boolean isSourceModified(Resource resource) {
-        return false;
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c8709542/cayenne-server/src/main/java/org/apache/cayenne/velocity/BindDirective.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/velocity/BindDirective.java b/cayenne-server/src/main/java/org/apache/cayenne/velocity/BindDirective.java
new file mode 100644
index 0000000..3ff81b7
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/velocity/BindDirective.java
@@ -0,0 +1,176 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.velocity;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.Collection;
+import java.util.Iterator;
+
+import org.apache.cayenne.access.jdbc.ParameterBinding;
+import org.apache.cayenne.dba.TypesMapping;
+import org.apache.cayenne.util.ConversionUtil;
+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.runtime.directive.Directive;
+import org.apache.velocity.runtime.parser.node.Node;
+
+/**
+ * A custom Velocity directive to create a PreparedStatement parameter text. There are the
+ * following possible invocation formats inside the template:
+ * 
+ * <pre>
+ * #bind(value) - e.g. #bind($xyz)
+ * #bind(value jdbc_type_name) - e.g. #bind($xyz 'VARCHAR'). This is the most common and useful form.
+ * #bind(value jdbc_type_name, scale) - e.g. #bind($xyz 'VARCHAR' 2)
+ * </pre>
+ * <p>
+ * Other examples:
+ * </p>
+ * <p>
+ * <strong>Binding literal parameter value:</strong>
+ * </p>
+ * <p>
+ * <code>"WHERE SOME_COLUMN &gt; #bind($xyz)"</code> produces
+ * <code>"WHERE SOME_COLUMN &gt; ?"</code> and also places the value of the "xyz" parameter
+ * in the context "bindings" collection.
+ * </p>
+ * <p>
+ * <strong>Binding ID column of a DataObject value:</strong>
+ * </p>
+ * <p>
+ * <code>"WHERE ID_COL1 = #bind($helper.cayenneExp($xyz, 'db:ID_COL2')) 
+ * AND ID_COL2 = #bind($helper.cayenneExp($xyz, 'db:ID_COL2'))"</code> produces <code>"WHERE ID_COL1 = ? AND ID_COL2 = ?"</code> and also places the
+ * values of id columns of the DataObject parameter "xyz" in the context "bindings"
+ * collection.
+ * </p>
+ * 
+ * @since 1.1
+ */
+public class BindDirective extends Directive {
+
+    @Override
+    public String getName() {
+        return "bind";
+    }
+
+    @Override
+    public int getType() {
+        return LINE;
+    }
+
+    /**
+     * Extracts the value of the object property to render and passes control to
+     * {@link #render(InternalContextAdapter, Writer, ParameterBinding)} to do the actual
+     * rendering.
+     */
+    @Override
+    public boolean render(InternalContextAdapter context, Writer writer, Node node)
+            throws IOException, ResourceNotFoundException, ParseErrorException,
+            MethodInvocationException {
+
+        Object value = getChild(context, node, 0);
+        Object type = getChild(context, node, 1);
+        int scale = ConversionUtil.toInt(getChild(context, node, 2), -1);
+        String typeString = type != null ? type.toString() : null;
+
+        if (value instanceof Collection) {
+            Iterator<?> it = ((Collection) value).iterator();
+            while (it.hasNext()) {
+                render(context, writer, node, it.next(), typeString, scale);
+
+                if (it.hasNext()) {
+                    writer.write(',');
+                }
+            }
+        }
+        else {
+            render(context, writer, node, value, typeString, scale);
+        }
+
+        return true;
+    }
+
+    /**
+     * @since 3.0
+     */
+    protected void render(
+            InternalContextAdapter context,
+            Writer writer,
+            Node node,
+            Object value,
+            String typeString,
+            int scale) throws IOException, ParseErrorException {
+
+        int jdbcType = TypesMapping.NOT_DEFINED;
+        if (typeString != null) {
+            jdbcType = TypesMapping.getSqlTypeByName(typeString);
+        }
+        else if (value != null) {
+            jdbcType = TypesMapping.getSqlTypeByJava(value.getClass());
+        } else {
+            // value is null, set JDBC type to NULL
+        	jdbcType = TypesMapping.getSqlTypeByName(TypesMapping.SQL_NULL);
+        }
+
+        if (jdbcType == TypesMapping.NOT_DEFINED) {
+            throw new ParseErrorException("Can't determine JDBC type of binding ("
+                    + value
+                    + ", "
+                    + typeString
+                    + ") at line "
+                    + node.getLine()
+                    + ", column "
+                    + node.getColumn());
+        }
+
+        render(context, writer, new ParameterBinding(value, jdbcType, scale));
+    }
+
+    protected void render(
+            InternalContextAdapter context,
+            Writer writer,
+            ParameterBinding binding) throws IOException {
+
+        bind(context, binding);
+        writer.write('?');
+    }
+
+    protected Object getChild(InternalContextAdapter context, Node node, int i)
+            throws MethodInvocationException {
+        return (i >= 0 && i < node.jjtGetNumChildren()) ? node.jjtGetChild(i).value(
+                context) : null;
+    }
+
+    /**
+     * Adds value to the list of bindings in the context.
+     */
+    protected void bind(InternalContextAdapter context, ParameterBinding binding) {
+
+        Collection bindings = (Collection) context.getInternalUserContext().get(
+                SQLTemplateProcessor.BINDINGS_LIST_KEY);
+
+        if (bindings != null) {
+            bindings.add(binding);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c8709542/cayenne-server/src/main/java/org/apache/cayenne/velocity/BindEqualDirective.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/velocity/BindEqualDirective.java b/cayenne-server/src/main/java/org/apache/cayenne/velocity/BindEqualDirective.java
new file mode 100644
index 0000000..b773cf5
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/velocity/BindEqualDirective.java
@@ -0,0 +1,57 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.velocity;
+
+import java.io.IOException;
+import java.io.Writer;
+
+import org.apache.cayenne.access.jdbc.ParameterBinding;
+import org.apache.velocity.context.InternalContextAdapter;
+
+/**
+ * A custom Velocity directive to create a PreparedStatement parameter text
+ * for "= ?". If null value is encountered, generated text will look like "IS NULL".
+ * Usage in Velocity template is "WHERE SOME_COLUMN #bindEqual($xyz)".
+ * 
+ * @since 1.1
+ */
+public class BindEqualDirective extends BindDirective {
+
+    @Override
+    public String getName() {
+        return "bindEqual";
+    }
+
+    @Override
+    protected void render(
+        InternalContextAdapter context,
+        Writer writer,
+        ParameterBinding binding)
+        throws IOException {
+
+        if (binding.getValue() != null) {
+            bind(context, binding);
+            writer.write("= ?");
+        }
+        else {
+            writer.write("IS NULL");
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c8709542/cayenne-server/src/main/java/org/apache/cayenne/velocity/BindNotEqualDirective.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/velocity/BindNotEqualDirective.java b/cayenne-server/src/main/java/org/apache/cayenne/velocity/BindNotEqualDirective.java
new file mode 100644
index 0000000..a49a8d6
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/velocity/BindNotEqualDirective.java
@@ -0,0 +1,56 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.velocity;
+
+import java.io.IOException;
+import java.io.Writer;
+
+import org.apache.cayenne.access.jdbc.ParameterBinding;
+import org.apache.velocity.context.InternalContextAdapter;
+
+/**
+ * A custom Velocity directive to create a PreparedStatement parameter text for "&lt;&gt;?".
+ * If null value is encountered, generated text will look like "IS NOT NULL". Usage in
+ * Velocity template is "WHERE SOME_COLUMN #bindNotEqual($xyz)".
+ * 
+ * @since 1.1
+ */
+public class BindNotEqualDirective extends BindDirective {
+
+    @Override
+    public String getName() {
+        return "bindNotEqual";
+    }
+
+    @Override
+    protected void render(
+            InternalContextAdapter context,
+            Writer writer,
+            ParameterBinding binding) throws IOException {
+
+        if (binding.getValue() != null) {
+            bind(context, binding);
+            writer.write("<> ?");
+        }
+        else {
+            writer.write("IS NOT NULL");
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c8709542/cayenne-server/src/main/java/org/apache/cayenne/velocity/BindObjectEqualDirective.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/velocity/BindObjectEqualDirective.java b/cayenne-server/src/main/java/org/apache/cayenne/velocity/BindObjectEqualDirective.java
new file mode 100644
index 0000000..ed1f7d0
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/velocity/BindObjectEqualDirective.java
@@ -0,0 +1,164 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+package org.apache.cayenne.velocity;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.sql.Types;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+
+import org.apache.cayenne.ObjectId;
+import org.apache.cayenne.Persistent;
+import org.apache.cayenne.access.jdbc.ParameterBinding;
+import org.apache.cayenne.dba.TypesMapping;
+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.runtime.parser.node.Node;
+
+/**
+ * A custom Velocity directive to create a set of SQL conditions to match an ObjectId of
+ * an object. Usage in Velocity template is "WHERE #bindObjectEqual($object)" or "WHERE
+ * #bindObjectEqual($object $columns $idValues)".
+ * 
+ * @since 3.0
+ */
+public class BindObjectEqualDirective extends BindDirective {
+
+    @Override
+    public String getName() {
+        return "bindObjectEqual";
+    }
+
+    @Override
+    public boolean render(InternalContextAdapter context, Writer writer, Node node)
+            throws IOException, ResourceNotFoundException, ParseErrorException,
+            MethodInvocationException {
+
+        Object object = getChild(context, node, 0);
+        Map idMap = toIdMap(object);
+
+        Object sqlColumns = getChild(context, node, 1);
+        Object idColumns = getChild(context, node, 2);
+
+        if (idMap == null) {
+            // assume null object, and bind all null values
+
+            if (sqlColumns == null || idColumns == null) {
+                throw new ParseErrorException("Invalid parameters. "
+                        + "Either object has to be set "
+                        + "or sqlColumns and idColumns or both.");
+            }
+
+            idMap = Collections.EMPTY_MAP;
+        }
+        else if (sqlColumns == null || idColumns == null) {
+            // infer SQL columns from ID columns
+            sqlColumns = idMap.keySet().toArray();
+            idColumns = sqlColumns;
+        }
+
+        Object[] sqlColumnsArray = toArray(sqlColumns);
+        Object[] idColumnsArray = toArray(idColumns);
+
+        if (sqlColumnsArray.length != idColumnsArray.length) {
+            throw new ParseErrorException(
+                    "SQL columns and ID columns arrays have different sizes.");
+        }
+
+        for (int i = 0; i < sqlColumnsArray.length; i++) {
+
+            Object value = idMap.get(idColumnsArray[i]);
+
+            int jdbcType = (value != null) ? TypesMapping.getSqlTypeByJava(value
+                    .getClass()) : Types.INTEGER;
+
+            renderColumn(context, writer, sqlColumnsArray[i], i);
+            writer.write(' ');
+            render(context, writer, new ParameterBinding(value, jdbcType, -1));
+        }
+
+        return true;
+    }
+
+    protected Object[] toArray(Object columns) {
+        if (columns instanceof Collection) {
+            return ((Collection) columns).toArray();
+        }
+        else if (columns.getClass().isArray()) {
+            return (Object[]) columns;
+        }
+        else {
+            return new Object[] {
+                columns
+            };
+        }
+    }
+
+    protected Map toIdMap(Object object) throws ParseErrorException {
+        if (object instanceof Persistent) {
+            return ((Persistent) object).getObjectId().getIdSnapshot();
+        }
+        else if (object instanceof ObjectId) {
+            return ((ObjectId) object).getIdSnapshot();
+        }
+        else if(object instanceof Map) {
+            return (Map) object;
+        }
+        else if (object != null) {
+            throw new ParseErrorException(
+                    "Invalid object parameter, expected Persistent or ObjectId or null: "
+                            + object);
+        }
+        else {
+            return null;
+        }
+    }
+
+    protected void renderColumn(
+            InternalContextAdapter context,
+            Writer writer,
+            Object columnName,
+            int columnIndex) throws IOException {
+
+        if (columnIndex > 0) {
+            writer.write(" AND ");
+        }
+
+        writer.write(columnName.toString());
+    }
+
+    @Override
+    protected void render(
+            InternalContextAdapter context,
+            Writer writer,
+            ParameterBinding binding) throws IOException {
+
+        if (binding.getValue() != null) {
+            bind(context, binding);
+            writer.write("= ?");
+        }
+        else {
+            writer.write("IS NULL");
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c8709542/cayenne-server/src/main/java/org/apache/cayenne/velocity/BindObjectNotEqualDirective.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/velocity/BindObjectNotEqualDirective.java b/cayenne-server/src/main/java/org/apache/cayenne/velocity/BindObjectNotEqualDirective.java
new file mode 100644
index 0000000..0b610ab
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/velocity/BindObjectNotEqualDirective.java
@@ -0,0 +1,70 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+package org.apache.cayenne.velocity;
+
+import java.io.IOException;
+import java.io.Writer;
+
+import org.apache.cayenne.access.jdbc.ParameterBinding;
+import org.apache.velocity.context.InternalContextAdapter;
+
+/**
+ * A custom Velocity directive to create a set of SQL conditions to check unequality of an
+ * ObjectId of an object. Usage in Velocity template is "WHERE
+ * #bindObjectNotEqual($object)" or "WHERE #bindObjectNotEqual($object $columns
+ * $idValues)".
+ * 
+ * @since 3.0
+ */
+public class BindObjectNotEqualDirective extends BindObjectEqualDirective {
+
+    @Override
+    public String getName() {
+        return "bindObjectNotEqual";
+    }
+
+    @Override
+    protected void renderColumn(
+            InternalContextAdapter context,
+            Writer writer,
+            Object columnName,
+            int columnIndex) throws IOException {
+
+        if (columnIndex > 0) {
+            writer.write(" OR ");
+        }
+
+        writer.write(columnName.toString());
+    }
+
+    @Override
+    protected void render(
+            InternalContextAdapter context,
+            Writer writer,
+            ParameterBinding binding) throws IOException {
+
+        if (binding.getValue() != null) {
+            bind(context, binding);
+            writer.write("<> ?");
+        }
+        else {
+            writer.write("IS NOT NULL");
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c8709542/cayenne-server/src/main/java/org/apache/cayenne/velocity/ChainDirective.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/velocity/ChainDirective.java b/cayenne-server/src/main/java/org/apache/cayenne/velocity/ChainDirective.java
new file mode 100644
index 0000000..b0be445
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/velocity/ChainDirective.java
@@ -0,0 +1,112 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.velocity;
+
+import java.io.IOException;
+import java.io.StringWriter;
+import java.io.Writer;
+
+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.runtime.directive.Directive;
+import org.apache.velocity.runtime.parser.node.ASTDirective;
+import org.apache.velocity.runtime.parser.node.Node;
+
+/**
+ * A custom Velocity directive to conditionally join a number of {@link ChunkDirective chunks}.
+ * Usage of chain is the following:
+ * 
+ * <pre>
+ * #chain(operator) - e.g. #chain(' AND ')
+ * #chain(operator prefix) - e.g. #chain(' AND ' 'WHERE ')</pre>
+ * 
+ * <p><code>operator</code> (e.g. AND, OR, etc.) is used to join chunks that are included
+ * in a chain. <code>prefix</code> is inserted if a chain contains at least one chunk.
+ * </p>
+ * 
+ * @since 1.1
+ */
+public class ChainDirective extends Directive {
+
+    @Override
+    public String getName() {
+        return "chain";
+    }
+
+    @Override
+    public int getType() {
+        return BLOCK;
+    }
+
+    @Override
+    public boolean render(InternalContextAdapter context, Writer writer, Node node)
+        throws
+            IOException,
+            ResourceNotFoundException,
+            ParseErrorException,
+            MethodInvocationException {
+
+        int size = node.jjtGetNumChildren();
+        if (size == 0) {
+            return true;
+        }
+
+        // BLOCK is the last child
+        Node block = node.jjtGetChild(node.jjtGetNumChildren() - 1);
+        String join = (size > 1) ? (String) node.jjtGetChild(0).value(context) : "";
+        String prefix = (size > 2) ? (String) node.jjtGetChild(1).value(context) : "";
+
+        // if there is a conditional prefix, use a separate buffer ofr children
+        StringWriter childWriter = new StringWriter(30);
+
+        int len = block.jjtGetNumChildren();
+        int includedChunks = 0;
+        for (int i = 0; i < len; i++) {
+            Node child = block.jjtGetChild(i);
+
+            // if this is a "chunk", evaluate its expression and prepend join if included...
+            if (child instanceof ASTDirective
+                && "chunk".equals(((ASTDirective) child).getDirectiveName())) {
+
+                if (child.jjtGetNumChildren() < 2
+                    || child.jjtGetChild(0).value(context) != null) {
+
+                    if (includedChunks > 0) {
+                        childWriter.write(join);
+                    }
+
+                    includedChunks++;
+                }
+            }
+
+            child.render(context, childWriter);
+        }
+
+        if (includedChunks > 0) {
+            childWriter.flush();
+            writer.write(prefix);
+            writer.write(childWriter.toString());
+        }
+
+        return true;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c8709542/cayenne-server/src/main/java/org/apache/cayenne/velocity/ChunkDirective.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/velocity/ChunkDirective.java b/cayenne-server/src/main/java/org/apache/cayenne/velocity/ChunkDirective.java
new file mode 100644
index 0000000..5ff0a5e
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/velocity/ChunkDirective.java
@@ -0,0 +1,75 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.velocity;
+
+import java.io.IOException;
+import java.io.Writer;
+
+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.runtime.directive.Directive;
+import org.apache.velocity.runtime.parser.node.Node;
+
+/**
+ * A custom Velocity directive to describe a conditional chunk of a {@link ChainDirective chain}.
+ * Usage of chunk is the following:
+ * 
+ * <pre>
+ * #chunk()...#end - e.g. #chunk()A = 5#end
+ * #chunk($paramKey)...#end - e.g. #chunk($a)A = $a#end
+ * </pre>
+ * <p>
+ * If context contains paramKey and it's value isn't null, chunk is included in the
+ * chain, and if it is not the first chunk, it is prefixed with chain join (OR/AND).
+ * If context doesn't contain paramKey or it's value is null, chunk is skipped.
+ * @since 1.1
+ */
+public class ChunkDirective extends Directive {
+
+    @Override
+    public String getName() {
+        return "chunk";
+    }
+
+    @Override
+    public int getType() {
+        return BLOCK;
+    }
+
+    @Override
+    public boolean render(InternalContextAdapter context, Writer writer, Node node)
+            throws IOException, ResourceNotFoundException, ParseErrorException,
+            MethodInvocationException {
+
+        // first child is an expression, second is BLOCK
+        if (node.jjtGetNumChildren() > 1 && node.jjtGetChild(0).value(context) == null) {
+            // skip this chunk
+            return false;
+        }
+
+        // BLOCK is the last child
+        Node block = node.jjtGetChild(node.jjtGetNumChildren() - 1);
+        block.render(context, writer);
+        return true;
+    }
+
+}