Author: cbrisson
Date: Thu Apr 18 15:04:54 2019
New Revision: 1857755

URL: http://svn.apache.org/viewvc?rev=1857755&view=rev
Log:
[tools/model] Initial import: Model, Entity, Attribute and Instance objects and 
their dependencies

Added:
    
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/Action.java
    
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/Attribute.java
    
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/Entity.java
    
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/Instance.java
    
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/Model.java
    
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/RowAttribute.java
    
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/RowsetAttribute.java
    
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/ScalarAttribute.java
    
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/Transaction.java
    
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/WrappingInstance.java
    
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/impl/
    
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/impl/AttributeHolder.java
    
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/impl/BaseAttribute.java
    
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/impl/BaseEntity.java
    
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/impl/BaseModel.java
    
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/impl/InstanceProducer.java
    
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/impl/ReverseEngineer.java
    
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/impl/RowIterator.java
    
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/impl/UpdateAction.java

Added: 
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/Action.java
URL: 
http://svn.apache.org/viewvc/velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/Action.java?rev=1857755&view=auto
==============================================================================
--- 
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/Action.java
 (added)
+++ 
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/Action.java
 Thu Apr 18 15:04:54 2019
@@ -0,0 +1,59 @@
+package org.apache.velocity.tools.model;
+
+import org.apache.velocity.tools.model.impl.AttributeHolder;
+import org.apache.velocity.tools.model.sql.PooledStatement;
+
+import java.io.Serializable;
+import java.sql.SQLException;
+import java.util.Map;
+
+public class Action extends Attribute
+{
+    public Action(String name, AttributeHolder parent)
+    {
+        super(name, parent);
+
+    }
+
+    public int perform(Serializable... params) throws SQLException
+    {
+        return performImpl(getParamValues(params));
+    }
+
+    public int perform(Map source) throws SQLException
+    {
+        return performImpl(getParamValues(source));
+    }
+
+    public int perform(Map source, Serializable... params) throws SQLException
+    {
+        return performImpl(getParamValues(source, params));
+    }
+
+    protected int performImpl(Serializable... paramValues) throws SQLException
+    {
+        int changed = 0;
+        PooledStatement statement = null;
+        try
+        {
+            statement = getModel().prepareUpdate(getQuery());
+            statement.getConnection().enterBusyState();
+            changed = statement.executeUpdate(paramValues);
+        }
+        finally
+        {
+            if (statement != null)
+            {
+                statement.notifyOver();
+                statement.getConnection().leaveBusyState();
+            }
+        }
+        return changed;
+    }
+
+    @Override
+    protected String getQueryMethodName()
+    {
+        return "perform";
+    }
+}

Added: 
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/Attribute.java
URL: 
http://svn.apache.org/viewvc/velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/Attribute.java?rev=1857755&view=auto
==============================================================================
--- 
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/Attribute.java
 (added)
+++ 
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/Attribute.java
 Thu Apr 18 15:04:54 2019
@@ -0,0 +1,33 @@
+package org.apache.velocity.tools.model;
+
+/*
+ * 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.
+ */
+
+import org.apache.velocity.tools.model.impl.AttributeHolder;
+import org.apache.velocity.tools.model.impl.BaseAttribute;
+
+public abstract class Attribute extends BaseAttribute
+{
+    public Attribute(String name, AttributeHolder parent)
+    {
+        super(name, parent);
+    }
+
+    protected abstract String getQueryMethodName();
+}

Added: 
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/Entity.java
URL: 
http://svn.apache.org/viewvc/velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/Entity.java?rev=1857755&view=auto
==============================================================================
--- 
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/Entity.java
 (added)
+++ 
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/Entity.java
 Thu Apr 18 15:04:54 2019
@@ -0,0 +1,93 @@
+package org.apache.velocity.tools.model;
+
+/*
+ * 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.
+ */
+
+import org.apache.velocity.exception.VelocityException;
+import org.apache.velocity.tools.model.impl.BaseEntity;
+import org.apache.velocity.tools.model.util.TypeUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.Serializable;
+import java.lang.reflect.Method;
+import java.sql.SQLException;
+import java.util.Iterator;
+import java.util.Map;
+
+public class Entity extends BaseEntity implements Iterable<Instance>
+{
+    protected static Logger logger = LoggerFactory.getLogger(Entity.class);
+
+    public Entity(String name, Model model)
+    {
+        super(name, model);
+        setInstanceBuilder(() -> new Instance(this));
+    }
+
+    public Logger getLogger()
+    {
+        return logger;
+    }
+
+    public Iterator<Instance> iterate() throws SQLException
+    {
+        return getIterateAttribute().query();
+    }
+
+    public Iterator<Instance> iterator()
+    {
+        try
+        {
+            return iterate();
+        }
+        catch (SQLException sqle)
+        {
+            throw new VelocityException("cannot iterate on instances of " + 
getName(), sqle);
+        }
+    }
+
+    public long getCount() throws SQLException
+    {
+        return TypeUtils.toLong(getCountAttribute().evaluate());
+    }
+
+    public Instance fetch(Serializable... key) throws SQLException
+    {
+        return getFetchAttribute().retrieve(key);
+    }
+
+    public Instance fetch(Map key) throws SQLException
+    {
+        return getFetchAttribute().retrieve(key);
+    }
+
+    @Override
+    protected Map<String, Method> getWrappedInstanceGetters()
+    {
+        return super.getWrappedInstanceGetters();
+    }
+
+    protected Map<String, Method> getWrappedInstanceSetters()
+    {
+        return super.getWrappedInstanceSetters();
+    }
+
+
+}

Added: 
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/Instance.java
URL: 
http://svn.apache.org/viewvc/velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/Instance.java?rev=1857755&view=auto
==============================================================================
--- 
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/Instance.java
 (added)
+++ 
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/Instance.java
 Thu Apr 18 15:04:54 2019
@@ -0,0 +1,333 @@
+package org.apache.velocity.tools.model;
+
+/*
+ * 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.
+ */
+
+import org.apache.velocity.tools.model.filter.ValueFilterHandler;
+import org.apache.velocity.tools.model.sql.RowValues;
+import org.apache.velocity.tools.model.util.ChainedMap;
+import org.apache.velocity.tools.model.filter.Filter;
+import org.apache.velocity.tools.model.util.SlotTreeMap;
+import org.apache.velocity.tools.model.util.TypeUtils;
+
+import java.io.Serializable;
+import java.sql.SQLException;
+import java.util.Arrays;
+import java.util.BitSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+public class Instance extends SlotTreeMap
+{
+    public Instance(Model model)
+    {
+        this.model = model;
+        this.dirtyFlags = new BitSet();
+        this.canWrite = model.getWriteAccess() != Model.WriteAccess.NONE;
+    }
+
+    protected Instance(Entity entity)
+    {
+        this(entity.getModel());
+        this.entity = entity;
+    }
+
+    public void setInitialValues(RowValues values) throws SQLException
+    {
+        Filter<String> defaultNameMapper = 
getModel().getIdentifiers().getDefaultColumnLeaf();
+        ValueFilterHandler readFilters = 
getModel().getFilters().getReadFilters();
+        for (String key : values.keySet())
+        {
+            Serializable value = values.get(key);
+            String colName = entity == null ? null : 
entity.translateColumnName(key);
+            if (colName == null)
+            {
+                colName = defaultNameMapper.apply(key);
+            }
+            else
+            {
+                value = entity.getColumn(colName).read(value);
+            }
+            if (value != null)
+            {
+                Filter<Serializable> filter = 
readFilters.getTypeEntry(value.getClass());
+                if (filter != null)
+                {
+                    value = filter.apply(value);
+                }
+            }
+            setInitialValue(colName, value);
+        }
+        setClean();
+        persisted = entity != null && entity.getPrimaryKey().size() > 0;
+    }
+
+    public void setInitialValue(String columnName, Serializable value)
+    {
+        super.put(columnName, value);
+    }
+
+    public Serializable evaluate(String name, Map params) throws SQLException
+    {
+        return entity.evaluate(name, params == null ? this : new 
ChainedMap(this, params));
+    }
+
+    public Serializable evaluate(String name, Serializable... params) throws 
SQLException
+    {
+        return entity.evaluate(name, this, params);
+    }
+
+    public Instance retrieve(String name, Map params) throws SQLException
+    {
+        return entity.retrieve(name, params == null ? this : new 
ChainedMap(this, params));
+    }
+
+    public Instance retrieve(String name, Serializable... params) throws 
SQLException
+    {
+        return entity.retrieve(name, this, params);
+    }
+
+    public Iterator<Instance> query(String name, Map params) throws 
SQLException
+    {
+        return entity.query(name, params == null ? this : new ChainedMap(this, 
params));
+    }
+
+    public Iterator<Instance> query(String name, Serializable... params) 
throws SQLException
+    {
+        return entity.query(name, this, params);
+    }
+
+    public int perform(String name, Map params) throws SQLException
+    {
+        if (!canWrite)
+        {
+            throw new SQLException("instance is read-only");
+        }
+        return entity.perform(name, params == null ? this : new 
ChainedMap(this, params));
+    }
+
+    public int perform(String name, Serializable... params) throws SQLException
+    {
+        if (!canWrite)
+        {
+            throw new SQLException("instance is read-only");
+        }
+        return entity.perform(name, this, params);
+    }
+
+    public String getString(String name)
+    {
+        return TypeUtils.toString(get(name));
+    }
+
+    public Boolean getBoolean(String name)
+    {
+        return TypeUtils.toBoolean(get(name));
+    }
+
+    public Short getShort(String name)
+    {
+        return TypeUtils.toShort(get(name));
+    }
+
+    public Integer getInteger(String name)
+    {
+        return TypeUtils.toInteger(get(name));
+    }
+
+    public Long getLong(String name)
+    {
+        return TypeUtils.toLong(get(name));
+    }
+
+    public Float getFloat(String name)
+    {
+        return TypeUtils.toFloat(get(name));
+    }
+
+    public Double getDouble(String name)
+    {
+        return TypeUtils.toDouble(get(name));
+    }
+
+    public final Model getModel()
+    {
+        return model;
+    }
+
+    public final Entity getEntity()
+    {
+        return entity;
+    }
+
+    protected void setClean()
+    {
+        dirtyFlags.clear();
+    }
+
+    public Serializable[] getPrimaryKey()
+    {
+        List<Entity.Column> pk = entity.getPrimaryKey();
+        if (pk == null)
+        {
+            return null;
+        }
+        Serializable[] ret = new Serializable[pk.size()];
+        int col = 0;
+        for (Entity.Column column : pk)
+        {
+            ret[col++] = get(column.name);
+        }
+        return ret;
+    }
+
+    public BitSet getDirtyFlags()
+    {
+        return dirtyFlags;
+    }
+
+    public boolean isDirty()
+    {
+        return dirtyFlags.cardinality() > 0;
+    }
+
+    public void refresh() throws SQLException
+    {
+        ensurePersisted();
+        Instance myself = getEntity().fetch(getPrimaryKey());
+        super.putAll(myself);
+    }
+
+    public void delete() throws SQLException
+    {
+        if (!canWrite)
+        {
+            throw new SQLException("instance is read-only");
+        }
+        ensurePersisted();
+        entity.delete(this);
+        persisted = false;
+    }
+
+    public void insert() throws SQLException
+    {
+        if (!canWrite)
+        {
+            throw new SQLException("instance is read-only");
+        }
+        ensureNotPersisted();
+        entity.insert(this);
+        persisted = entity != null && entity.getPrimaryKey().size() > 0;
+    }
+
+    public void update() throws SQLException
+    {
+        if (!canWrite)
+        {
+            throw new SQLException("instance is read-only");
+        }
+        ensurePersisted();
+        if (!isDirty())
+        {
+            return;
+        }
+        entity.update(this);
+    }
+
+    @Override
+    public void putAll(Map<? extends String, ? extends Serializable> map)
+    {
+        Serializable[] pk = null;
+        if (persisted)
+        {
+            pk = getPrimaryKey();
+        }
+        super.putAll(map);
+        if (persisted)
+        {
+            Serializable[] newpk = getPrimaryKey();
+            if (Arrays.equals(pk, newpk))
+            {
+                entity.getNonKeyMask().stream().filter(col -> 
map.containsKey(entity.getColumn(col).name)).forEach(col -> 
dirtyFlags.set(col));
+            }
+            else
+            {
+                persisted = false;
+            }
+
+        }
+    }
+
+    protected Serializable putImpl(String key, Serializable value)
+    {
+        return super.put(key, value);
+    }
+
+    @Override
+    public final Serializable put(String key, Serializable value)
+    {
+        Serializable ret = putImpl(key, value);
+        if (persisted)
+        {
+            Entity.Column column = entity.getColumn(key);
+            if (column != null)
+            {
+                if (entity.getPrimaryKeyMask().get(column.getIndex()))
+                {
+                    if (!ret.equals(value))
+                    {
+                        persisted = false;
+                    }
+                }
+                else
+                {
+                    dirtyFlags.set(column.getIndex());
+                }
+            }
+        }
+        return ret;
+    }
+
+    private void ensurePersisted()
+    {
+        if (!persisted)
+        {
+            throw new IllegalStateException("instance must be persisted");
+        }
+    }
+
+    private void ensureNotPersisted()
+    {
+        if (persisted)
+        {
+            throw new IllegalStateException("instance must not be persisted");
+        }
+    }
+
+    private Model model = null;
+
+    private Entity entity = null;
+
+    private BitSet dirtyFlags = null;
+
+    private boolean canWrite = false;
+
+    private boolean persisted = false;
+}

Added: 
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/Model.java
URL: 
http://svn.apache.org/viewvc/velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/Model.java?rev=1857755&view=auto
==============================================================================
--- 
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/Model.java
 (added)
+++ 
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/Model.java
 Thu Apr 18 15:04:54 2019
@@ -0,0 +1,69 @@
+package org.apache.velocity.tools.model;
+
+/*
+ * 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.
+ */
+
+import org.apache.velocity.tools.model.impl.BaseModel;
+import org.apache.velocity.tools.model.sql.ConnectionWrapper;
+import org.apache.velocity.tools.model.sql.PooledStatement;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.sql.SQLException;
+
+public class Model extends BaseModel
+{
+    protected static Logger logger = LoggerFactory.getLogger(Model.class);
+
+    public Model()
+    {
+    }
+
+    public Logger getLogger()
+    {
+        return logger;
+    }
+
+    public Model getModel()
+    {
+        return this;
+    }
+
+    /**
+     * Prepare a query.
+     *
+     * @param query an sql query
+     * @return the pooled prepared statement corresponding to the query
+     */
+    protected PooledStatement prepareQuery(String query) throws SQLException
+    {
+        return getStatementPool().prepareQuery(query);
+    }
+
+    protected PooledStatement prepareUpdate(String query) throws SQLException
+    {
+        return getStatementPool().prepareUpdate(query);
+    }
+
+    @Override
+    protected ConnectionWrapper getTransactionConnection() throws SQLException
+    {
+        return super.getTransactionConnection();
+    }
+}

Added: 
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/RowAttribute.java
URL: 
http://svn.apache.org/viewvc/velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/RowAttribute.java?rev=1857755&view=auto
==============================================================================
--- 
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/RowAttribute.java
 (added)
+++ 
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/RowAttribute.java
 Thu Apr 18 15:04:54 2019
@@ -0,0 +1,83 @@
+package org.apache.velocity.tools.model;
+
+/*
+ * 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.
+ */
+
+import org.apache.velocity.tools.model.impl.AttributeHolder;
+import org.apache.velocity.tools.model.sql.PooledStatement;
+
+import java.io.Serializable;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Map;
+
+public class RowAttribute extends Attribute
+{
+    public RowAttribute(String name, AttributeHolder parent)
+    {
+        super(name, parent);
+    }
+
+    public Instance retrieve(Serializable... paramValues) throws SQLException
+    {
+        return retrieveImpl(getParamValues(paramValues));
+    }
+
+    public Instance retrieve(Map source) throws SQLException
+    {
+        return retrieveImpl(getParamValues(source));
+    }
+
+    public Instance retrieve(Map source, Serializable... params) throws 
SQLException
+    {
+        return retrieveImpl(getParamValues(source, params));
+    }
+
+    protected Instance retrieveImpl(Serializable... paramValues) throws 
SQLException
+    {
+        Instance instance = null;
+        PooledStatement statement = null;
+        try
+        {
+            statement = getModel().prepareQuery(getQuery());
+            statement.getConnection().enterBusyState();
+            ResultSet result = statement.executeQuery(paramValues);
+            if (result.next())
+            {
+                instance = newResultInstance();
+                instance.setInitialValues(statement);
+            }
+        }
+        finally
+        {
+            if (statement != null)
+            {
+                statement.notifyOver();
+                statement.getConnection().leaveBusyState();
+            }
+        }
+        return instance;
+    }
+
+    protected String getQueryMethodName()
+    {
+        return "retrieve";
+    }
+
+}

Added: 
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/RowsetAttribute.java
URL: 
http://svn.apache.org/viewvc/velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/RowsetAttribute.java?rev=1857755&view=auto
==============================================================================
--- 
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/RowsetAttribute.java
 (added)
+++ 
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/RowsetAttribute.java
 Thu Apr 18 15:04:54 2019
@@ -0,0 +1,84 @@
+package org.apache.velocity.tools.model;
+
+/*
+ * 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.
+ */
+
+import org.apache.velocity.tools.model.impl.AttributeHolder;
+import org.apache.velocity.tools.model.impl.RowIterator;
+import org.apache.velocity.tools.model.sql.PooledStatement;
+
+import java.io.Serializable;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Iterator;
+import java.util.Map;
+
+public class RowsetAttribute extends Attribute
+{
+    public RowsetAttribute(String name, AttributeHolder parent)
+    {
+        super(name, parent);
+    }
+
+    public Iterator<Instance> query(Serializable... params) throws SQLException
+    {
+        return queryImpl(getParamValues(params));
+    }
+
+    public Iterator<Instance> query(Map source) throws SQLException
+    {
+        return queryImpl(getParamValues(source));
+    }
+
+    public Iterator<Instance> query(Map source, Serializable... params) throws 
SQLException
+    {
+        return queryImpl(getParamValues(source, params));
+    }
+
+    protected Iterator<Instance> queryImpl(Serializable... params) throws 
SQLException
+    {
+        Iterator<Instance> iterator = null;
+        PooledStatement statement = null;
+        ResultSet result = null;
+        try
+        {
+            statement = getModel().prepareQuery(getQuery());
+            statement.getConnection().enterBusyState();
+            result = statement.executeQuery(params);
+            iterator = new RowIterator(getParent(), statement, result, 
getResultEntity());
+        }
+        finally
+        {
+            if (statement != null)
+            {
+                if (result == null)
+                {
+                    statement.notifyOver();
+                }
+                statement.getConnection().leaveBusyState();
+            }
+        }
+        return iterator;
+    }
+
+    protected String getQueryMethodName()
+    {
+        return "query";
+    }
+}

Added: 
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/ScalarAttribute.java
URL: 
http://svn.apache.org/viewvc/velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/ScalarAttribute.java?rev=1857755&view=auto
==============================================================================
--- 
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/ScalarAttribute.java
 (added)
+++ 
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/ScalarAttribute.java
 Thu Apr 18 15:04:54 2019
@@ -0,0 +1,156 @@
+package org.apache.velocity.tools.model;
+
+/*
+ * 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.
+ */
+
+import org.apache.velocity.tools.model.impl.AttributeHolder;
+import org.apache.velocity.tools.model.sql.PooledStatement;
+import org.apache.velocity.tools.model.util.TypeUtils;
+
+import java.io.Serializable;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Map;
+
+public class ScalarAttribute extends Attribute
+{
+    public ScalarAttribute(String name, AttributeHolder parent)
+    {
+        super(name, parent);
+    }
+
+    public Serializable evaluate(Serializable... paramValues) throws 
SQLException
+    {
+        return evaluateImpl(getParamValues(paramValues));
+    }
+
+    public Serializable evaluate(Map source) throws SQLException
+    {
+        return evaluateImpl(getParamValues(source));
+    }
+
+    public Serializable evaluate(Map source, Serializable... params) throws 
SQLException
+    {
+        return evaluateImpl(getParamValues(source, params));
+    }
+
+    protected Serializable evaluateImpl(Serializable... paramValues) throws 
SQLException
+    {
+        Serializable value = null;
+        PooledStatement statement = null;
+        try
+        {
+            statement = getModel().prepareQuery(getQuery());
+            statement.getConnection().enterBusyState();
+            ResultSet result = statement.executeQuery(paramValues);
+            if (result.next())
+            {
+                value = (Serializable)result.getObject(1);
+                if (result.wasNull())
+                {
+                    value = null;
+                }
+            }
+        }
+        finally
+        {
+            if (statement != null)
+            {
+                statement.notifyOver();
+                statement.getConnection().leaveBusyState();
+            }
+        }
+        return value;
+    }
+
+    protected String getQueryMethodName()
+    {
+        return "evaluate";
+    }
+
+    public String getString(Serializable... paramValues) throws SQLException
+    {
+        return TypeUtils.toString(evaluate(paramValues));
+    }
+
+    public String getString(Map source) throws SQLException
+    {
+        return TypeUtils.toString(evaluate(source));
+    }
+
+    public Boolean getBoolean(Serializable... paramValues) throws SQLException
+    {
+        return TypeUtils.toBoolean(evaluate(paramValues));
+    }
+
+    public Boolean getBoolean(Map source) throws SQLException
+    {
+        return TypeUtils.toBoolean(evaluate(source));
+    }
+
+    public Short getShort(Serializable... paramValues) throws SQLException
+    {
+        return TypeUtils.toShort(evaluate(paramValues));
+    }
+
+    public Short getShort(Map source) throws SQLException
+    {
+        return TypeUtils.toShort(evaluate(source));
+    }
+
+    public Integer getInteger(Serializable... paramValues) throws SQLException
+    {
+        return TypeUtils.toInteger(evaluate(paramValues));
+    }
+
+    public Integer getInteger(Map source) throws SQLException
+    {
+        return TypeUtils.toInteger(evaluate(source));
+    }
+
+    public Long getLong(Serializable... paramValues) throws SQLException
+    {
+        return TypeUtils.toLong(evaluate(paramValues));
+    }
+
+    public Long getLong(Map source) throws SQLException
+    {
+        return TypeUtils.toLong(evaluate(source));
+    }
+
+    public Float getFloat(Serializable... paramValues) throws SQLException
+    {
+        return TypeUtils.toFloat(evaluate(paramValues));
+    }
+
+    public Float getFloat(Map source) throws SQLException
+    {
+        return TypeUtils.toFloat(evaluate(source));
+    }
+
+    public Double getDouble(Serializable... paramValues) throws SQLException
+    {
+        return TypeUtils.toDouble(evaluate(paramValues));
+    }
+
+    public Double getDouble(Map source) throws SQLException
+    {
+        return TypeUtils.toDouble(evaluate(source));
+    }
+}

Added: 
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/Transaction.java
URL: 
http://svn.apache.org/viewvc/velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/Transaction.java?rev=1857755&view=auto
==============================================================================
--- 
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/Transaction.java
 (added)
+++ 
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/Transaction.java
 Thu Apr 18 15:04:54 2019
@@ -0,0 +1,73 @@
+package org.apache.velocity.tools.model;
+
+import org.apache.velocity.tools.model.impl.AttributeHolder;
+import org.apache.velocity.tools.model.sql.ConnectionWrapper;
+
+import java.io.Serializable;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.List;
+
+public class Transaction extends Action
+{
+    public Transaction(String name, AttributeHolder parent)
+    {
+        super(name, parent);
+    }
+
+    @Override
+    public int performImpl(Serializable... paramValues) throws SQLException
+    {
+        ConnectionWrapper connection = null;
+        try
+        {
+            int changed = 0;
+            connection = getModel().getTransactionConnection();
+            connection.enterBusyState();
+            int param = 0;
+            for (String individualStatement : getStatements())
+            {
+                PreparedStatement statement = 
connection.prepareStatement(individualStatement);
+                int paramCount = 
statement.getParameterMetaData().getParameterCount();
+                for (int i = 1; i <= paramCount; ++i)
+                {
+                    statement.setObject(i, paramValues[param++]);
+                }
+                changed += statement.executeUpdate();
+            }
+            connection.commit();
+            return changed;
+        }
+        catch (SQLException sqle)
+        {
+            if (connection != null)
+            {
+                connection.rollback();
+            }
+            throw sqle;
+        }
+        finally
+        {
+            if (connection != null)
+            {
+                connection.leaveBusyState();
+            }
+        }
+    }
+
+    private List<String> getStatements() throws SQLException
+    {
+        List<String> statements = new ArrayList<>();
+        String[] elements = getQuery().split(";");
+        for (String element : elements)
+        {
+            element = element.trim();
+            if (element.length() > 0)
+            {
+                statements.add(element);
+            }
+        }
+        return statements;
+    }
+}

Added: 
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/WrappingInstance.java
URL: 
http://svn.apache.org/viewvc/velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/WrappingInstance.java?rev=1857755&view=auto
==============================================================================
--- 
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/WrappingInstance.java
 (added)
+++ 
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/WrappingInstance.java
 Thu Apr 18 15:04:54 2019
@@ -0,0 +1,107 @@
+package org.apache.velocity.tools.model;
+
+import java.io.Serializable;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.sql.SQLException;
+import java.sql.Wrapper;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+
+public class WrappingInstance extends Instance implements Wrapper
+{
+    public WrappingInstance(Entity entity, Object pojo)
+    {
+        super(entity);
+        this.pojo = pojo;
+        this.getters = 
Optional.ofNullable(entity.getWrappedInstanceGetters()).orElse(new HashMap<>());
+        this.setters = 
Optional.ofNullable(entity.getWrappedInstanceSetters()).orElse(new HashMap<>());
+    }
+
+    @Override
+    public void setInitialValue(String columnName, Serializable value)
+    {
+        Method setter = setters.get(columnName);
+        if (setter == null)
+        {
+            super.setInitialValue(columnName, value);
+        }
+        else
+        {
+            try
+            {
+                setter.invoke(pojo, value);
+            }
+            catch (IllegalAccessException | InvocationTargetException e)
+            {
+                throw new RuntimeException("could not set property " + 
columnName, e);
+            }
+        }
+    }
+
+    @Override
+    public Serializable get(Object key)
+    {
+        Method getter = getters.get(key);
+        if (getter == null)
+        {
+            return super.get(key);
+        }
+        else
+        {
+            try
+            {
+                return (Serializable)getter.invoke(pojo);
+            }
+            catch (IllegalAccessException | InvocationTargetException e)
+            {
+                throw new RuntimeException("could not get property " + key, e);
+            }
+        }
+    }
+
+    @Override
+    protected Serializable putImpl(String key, Serializable value)
+    {
+        Method setter = setters.get(key);
+        if (setter == null)
+        {
+            return super.putImpl(key, value);
+        }
+        else
+        {
+            try
+            {
+                return (Serializable)setter.invoke(pojo, value);
+            }
+            catch (IllegalAccessException | InvocationTargetException e)
+            {
+                throw new RuntimeException("could not set property " + key, e);
+            }
+        }
+    }
+
+    private Object pojo;
+    private Map<String, Method> getters = null;
+    private Map<String, Method> setters = null;
+
+    @Override
+    public <T> T unwrap(Class<T> iface) throws SQLException
+    {
+        if (isWrapperFor(iface))
+        {
+            return (T)pojo;
+        }
+        else
+        {
+            throw new SQLException("cannot unwrap towards " + iface.getName());
+        }
+    }
+
+    @Override
+    public boolean isWrapperFor(Class<?> iface) throws SQLException
+    {
+        return iface.isAssignableFrom(pojo.getClass());
+    }
+}

Added: 
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/impl/AttributeHolder.java
URL: 
http://svn.apache.org/viewvc/velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/impl/AttributeHolder.java?rev=1857755&view=auto
==============================================================================
--- 
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/impl/AttributeHolder.java
 (added)
+++ 
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/impl/AttributeHolder.java
 Thu Apr 18 15:04:54 2019
@@ -0,0 +1,243 @@
+package org.apache.velocity.tools.model.impl;
+
+/*
+ * 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.
+ */
+
+import org.apache.velocity.tools.model.Action;
+import org.apache.velocity.tools.model.Attribute;
+import org.apache.velocity.tools.model.Entity;
+import org.apache.velocity.tools.model.Instance;
+import org.apache.velocity.tools.model.Model;
+import org.apache.velocity.tools.model.RowAttribute;
+import org.apache.velocity.tools.model.RowsetAttribute;
+import org.apache.velocity.tools.model.ScalarAttribute;
+import org.slf4j.Logger;
+
+import java.io.Serializable;
+import java.sql.SQLException;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.NavigableMap;
+import java.util.TreeMap;
+
+public abstract class AttributeHolder implements Serializable
+{
+    protected abstract Model getModel();
+
+    protected abstract Logger getLogger();
+
+    protected void initializeAttributes()
+    {
+        for (Attribute attribute : attributesMap.values())
+        {
+            attribute.initialize();
+        }
+    }
+
+    public Attribute getAttribute(String name)
+    {
+        return attributesMap.get(name); // TODO resolveCase?
+    }
+
+    public Serializable evaluate(String name, Serializable... params) throws 
SQLException
+    {
+       Attribute attribute = getAttribute(name);
+       if (attribute == null)
+       {
+           throw new IllegalArgumentException("unknown baseAttribute: " + 
name);
+       }
+       if (!(attribute instanceof ScalarAttribute))
+       {
+           throw new IllegalArgumentException("not a scalar baseAttribute: " + 
name);
+       }
+       return ((ScalarAttribute)attribute).evaluate(params);
+    }
+
+    public Serializable evaluate(String name, Map source) throws SQLException
+    {
+        Attribute attribute = getAttribute(name);
+        if (attribute == null)
+        {
+            throw new IllegalArgumentException("unknown baseAttribute: " + 
name);
+        }
+        if (!(attribute instanceof ScalarAttribute))
+        {
+            throw new IllegalArgumentException("not a scalar baseAttribute: " 
+ name);
+        }
+        return ((ScalarAttribute)attribute).evaluate(source);
+    }
+
+    protected Serializable evaluate(String name, Map source, Serializable... 
params) throws SQLException
+    {
+        Attribute attribute = getAttribute(name);
+        if (attribute == null)
+        {
+            throw new IllegalArgumentException("unknown baseAttribute: " + 
name);
+        }
+        if (!(attribute instanceof ScalarAttribute))
+        {
+            throw new IllegalArgumentException("not a scalar baseAttribute: " 
+ name);
+        }
+        return ((ScalarAttribute)attribute).evaluate(source, params);
+    }
+
+    public Instance retrieve(String name, Serializable... params) throws 
SQLException
+    {
+        Attribute attribute = getAttribute(name);
+        if (attribute == null)
+        {
+            throw new IllegalArgumentException("unknown baseAttribute: " + 
name);
+        }
+        if (!(attribute instanceof RowAttribute))
+        {
+            throw new IllegalArgumentException("not a scalar baseAttribute: " 
+ name);
+        }
+        return ((RowAttribute)attribute).retrieve(params);
+    }
+
+    public Instance retrieve(String name, Map source) throws SQLException
+    {
+        Attribute attribute = getAttribute(name);
+        if (attribute == null)
+        {
+            throw new IllegalArgumentException("unknown baseAttribute: " + 
name);
+        }
+        if (!(attribute instanceof RowAttribute))
+        {
+            throw new IllegalArgumentException("not a scalar baseAttribute: " 
+ name);
+        }
+        return ((RowAttribute)attribute).retrieve(source);
+    }
+
+    public Instance retrieve(String name, Map source, Serializable... params) 
throws SQLException
+    {
+        Attribute attribute = getAttribute(name);
+        if (attribute == null)
+        {
+            throw new IllegalArgumentException("unknown baseAttribute: " + 
name);
+        }
+        if (!(attribute instanceof RowAttribute))
+        {
+            throw new IllegalArgumentException("not a scalar baseAttribute: " 
+ name);
+        }
+        return ((RowAttribute)attribute).retrieve(source, params);
+    }
+
+    public Iterator<Instance> query(String name, Serializable... params) 
throws SQLException
+    {
+        Attribute attribute = getAttribute(name);
+        if (attribute == null)
+        {
+            throw new IllegalArgumentException("unknown attribute: " + name);
+        }
+        if (!(attribute instanceof RowsetAttribute))
+        {
+            throw new IllegalArgumentException("not a scalar attribute: " + 
name);
+        }
+        return ((RowsetAttribute)attribute).query(params);
+    }
+
+    public Iterator<Instance> query(String name, Map source) throws 
SQLException
+    {
+        Attribute attribute = getAttribute(name);
+        if (attribute == null)
+        {
+            throw new IllegalArgumentException("unknown attribute: " + name);
+        }
+        if (!(attribute instanceof RowsetAttribute))
+        {
+            throw new IllegalArgumentException("not a scalar attribute: " + 
name);
+        }
+        return ((RowsetAttribute)attribute).query(source);
+    }
+
+    public Iterator<Instance> query(String name, Map source, Serializable... 
params) throws SQLException
+    {
+        Attribute attribute = getAttribute(name);
+        if (attribute == null)
+        {
+            throw new IllegalArgumentException("unknown attribute: " + name);
+        }
+        if (!(attribute instanceof RowsetAttribute))
+        {
+            throw new IllegalArgumentException("not a scalar attribute: " + 
name);
+        }
+        return ((RowsetAttribute)attribute).query(source, params);
+    }
+
+    public int perform(String name, Serializable... params) throws SQLException
+    {
+        Attribute attribute = getAttribute(name);
+        if (attribute == null)
+        {
+            throw new IllegalArgumentException("unknown attribute: " + name);
+        }
+        if (!(attribute instanceof Action))
+        {
+            throw new IllegalArgumentException("not an action attribute: " + 
name);
+        }
+        return ((Action)attribute).perform(params);
+    }
+
+    public int perform(String name, Map source) throws SQLException
+    {
+        Attribute attribute = getAttribute(name);
+        if (attribute == null)
+        {
+            throw new IllegalArgumentException("unknown attribute: " + name);
+        }
+        if (!(attribute instanceof Action))
+        {
+            throw new IllegalArgumentException("not an action attribute: " + 
name);
+        }
+        return ((Action)attribute).perform(source);
+    }
+
+    public int perform(String name, Map source, Serializable... params) throws 
SQLException
+    {
+        Attribute attribute = getAttribute(name);
+        if (attribute == null)
+        {
+            throw new IllegalArgumentException("unknown attribute: " + name);
+        }
+        if (!(attribute instanceof Action))
+        {
+            throw new IllegalArgumentException("not an action attribute: " + 
name);
+        }
+        return ((Action)attribute).perform(source, params);
+    }
+
+    protected Entity resolveEntity(String name)
+    {
+        return getModel().getEntity(name);
+    }
+
+    protected void addAttribute(Attribute attribute)
+    {
+        attributesMap.put(attribute.getName(), attribute);
+    }
+
+    public NavigableMap<String, Attribute> getAttributes()
+    {
+        return Collections.unmodifiableNavigableMap(attributesMap);
+    }
+
+    private NavigableMap<String, Attribute> attributesMap = new TreeMap<>();
+}

Added: 
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/impl/BaseAttribute.java
URL: 
http://svn.apache.org/viewvc/velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/impl/BaseAttribute.java?rev=1857755&view=auto
==============================================================================
--- 
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/impl/BaseAttribute.java
 (added)
+++ 
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/impl/BaseAttribute.java
 Thu Apr 18 15:04:54 2019
@@ -0,0 +1,206 @@
+package org.apache.velocity.tools.model.impl;
+
+/*
+ * 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.
+ */
+
+import org.apache.velocity.tools.model.Entity;
+import org.apache.velocity.tools.model.Instance;
+import org.apache.velocity.tools.model.sql.RowValues;
+
+import java.io.Serializable;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+/**
+ * <p>BaseAttribute interface</p>
+ * @author Claude Brisson
+ * @version $Revision: $
+ * @since 3.1
+ */
+
+public abstract class BaseAttribute extends InstanceProducer implements 
Serializable
+{
+    public BaseAttribute(String name, AttributeHolder parent)
+    {
+        super(parent.getModel());
+        this.parent = parent;
+        this.attributeName = name;
+    }
+
+    protected void initialize()
+    {
+        if (query != null)
+        {
+            query = query.trim();
+        }
+    }
+
+    public String getName()
+    {
+        return attributeName;
+    }
+
+    public AttributeHolder getParent()
+    {
+        return parent;
+    }
+
+    protected void setResultEntity(String entityName)
+    {
+        setResultEntity(parent.resolveEntity(entityName));
+    }
+
+    public List<String> getParameterNames()
+    {
+        return Collections.unmodifiableList(parameterNames);
+    }
+
+    protected void addQueryPart(String queryPart)
+    {
+        query = query + queryPart;
+    }
+
+    protected void addParameter(String paramName)
+    {
+        parameterNames.add(paramName);
+        query = query + "?";
+    }
+
+    protected boolean getCaching()
+    {
+        return caching;
+    }
+
+    protected Serializable[] getParamValues(RowValues source) throws 
SQLException
+    {
+        Serializable[] paramValues = new Serializable[parameterNames.size()];
+        for (int i = 0; i < paramValues.length; ++i)
+        {
+            paramValues[i] = source.get(parameterNames.get(i));
+        }
+        return paramValues;
+    }
+
+    protected Serializable[] getParamValues(Serializable[] paramValues) throws 
SQLException
+    {
+        Entity entity = (Entity)Optional.ofNullable(getParent()).filter(x -> x 
instanceof Entity).orElse(null);
+        if (entity != null)
+        {
+            for (int i = 0; i < paramValues.length; ++i)
+            {
+                String paramName = parameterNames.get(i);
+                paramValues[i] = entity.filterValue(paramName, paramValues[i]);
+            }
+        }
+        return paramValues;
+    }
+
+    protected Serializable[] getParamValues(Map source) throws SQLException
+    {
+        Serializable[] paramValues = new Serializable[parameterNames.size()];
+        Entity parentEntity = 
(Entity)Optional.ofNullable(getParent()).filter(x -> x instanceof 
Entity).orElse(null);
+        Entity sourceEntity = Optional.ofNullable(source).filter(x -> x 
instanceof Instance).map(i -> ((Instance)i).getEntity()).orElse(null);
+        if (parentEntity == null && sourceEntity == null)
+        {
+            for (int i = 0; i < paramValues.length; ++i)
+            {
+                paramValues[i] = 
(Serializable)source.get(parameterNames.get(i));
+            }
+        }
+        else if (parentEntity == null || sourceEntity == null || parentEntity 
== sourceEntity)
+        {
+            Entity entity = parentEntity == null ? sourceEntity : parentEntity;
+            for (int i = 0; i < paramValues.length; ++i)
+            {
+                String paramName = parameterNames.get(i);
+                paramValues[i] = entity.filterValue(paramName, 
(Serializable)source.get(paramName));
+            }
+        }
+        else
+        {
+            for (int i = 0; i < paramValues.length; ++i)
+            {
+                String paramName = parameterNames.get(i);
+                Serializable value = (Serializable)source.get(paramName);
+                paramValues[i] = parentEntity.hasColumn(paramName) ?
+                    parentEntity.filterValue(paramName, value) :
+                    sourceEntity.filterValue(paramName, value);
+            }
+        }
+        return paramValues;
+    }
+
+    protected Serializable[] getParamValues(Map source, Serializable[] 
additionalParams) throws SQLException
+    {
+        Serializable[] paramValues = new Serializable[parameterNames.size()];
+        Entity parentEntity = 
(Entity)Optional.ofNullable(getParent()).filter(x -> x instanceof 
Entity).orElse(null);
+        Entity sourceEntity = Optional.ofNullable(source).filter(x -> x 
instanceof Instance).map(i -> ((Instance)i).getEntity()).orElse(null);
+        int additionalParamIndex = 0;
+        if (parentEntity == null && sourceEntity == null)
+        {
+            for (int i = 0; i < paramValues.length; ++i)
+            {
+                String paramName = parameterNames.get(i);
+                paramValues[i] = source.containsKey(paramName) ? 
(Serializable)source.get(paramName) : additionalParams[additionalParamIndex++];
+            }
+        }
+        else if (parentEntity == null || sourceEntity == null || parentEntity 
== sourceEntity)
+        {
+            Entity entity = parentEntity == null ? sourceEntity : parentEntity;
+            for (int i = 0; i < paramValues.length; ++i)
+            {
+                String paramName = parameterNames.get(i);
+                Serializable value = source.containsKey(paramName) ? 
(Serializable)source.get(paramName) : additionalParams[additionalParamIndex++];
+                paramValues[i] = entity.filterValue(paramName, value);
+            }
+        }
+        else
+        {
+            for (int i = 0; i < paramValues.length; ++i)
+            {
+                String paramName = parameterNames.get(i);
+                Serializable value = source.containsKey(paramName) ? 
(Serializable)source.get(paramName) : additionalParams[additionalParamIndex++];
+                paramValues[i] = parentEntity.hasColumn(paramName) ?
+                    parentEntity.filterValue(paramName, value) :
+                    sourceEntity.filterValue(paramName, value);
+            }
+        }
+        if (additionalParamIndex != additionalParams.length)
+        {
+            throw new SQLException("too many parameters provided");
+        }
+        return paramValues;
+    }
+
+    protected String getQuery() throws SQLException
+    {
+        return query;
+    }
+
+    private boolean caching = false;
+    private AttributeHolder parent = null;
+    private Entity resultEntity = null;
+    private String attributeName = null;
+    private String query = "";
+    protected List<String> parameterNames = new ArrayList<>();
+}

Added: 
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/impl/BaseEntity.java
URL: 
http://svn.apache.org/viewvc/velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/impl/BaseEntity.java?rev=1857755&view=auto
==============================================================================
--- 
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/impl/BaseEntity.java
 (added)
+++ 
velocity/tools/branches/model/velocity-tools-model/src/main/java/org/apache/velocity/tools/model/impl/BaseEntity.java
 Thu Apr 18 15:04:54 2019
@@ -0,0 +1,502 @@
+package org.apache.velocity.tools.model.impl;
+
+/*
+ * 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.
+ */
+
+import org.apache.velocity.tools.config.ConfigurationException;
+import org.apache.velocity.tools.model.Action;
+import org.apache.velocity.tools.model.Attribute;
+import org.apache.velocity.tools.model.Entity;
+import org.apache.velocity.tools.model.Instance;
+import org.apache.velocity.tools.model.Model;
+import org.apache.velocity.tools.model.RowAttribute;
+import org.apache.velocity.tools.model.RowsetAttribute;
+import org.apache.velocity.tools.model.ScalarAttribute;
+import org.apache.velocity.tools.model.filter.Filter;
+
+import java.beans.PropertyDescriptor;
+import java.io.Serializable;
+import java.lang.reflect.Method;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.BitSet;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+public abstract class BaseEntity extends AttributeHolder
+{
+    public BaseEntity(String name, Model model)
+    {
+        this.model = model;
+        this.name = name;
+    }
+
+    public String getName()
+    {
+        return name;
+    }
+
+    public String getTable() { return sqlName; }
+
+    public void setTable(String table)
+    {
+        this.sqlName = table;
+    }
+
+    protected void addColumn(String name, String sqlName,  int type, Integer 
size, boolean generated)
+    {
+        addColumn(new Entity.Column(name, sqlName, type, size, generated));
+    }
+
+    protected void addColumn(Entity.Column column)
+    {
+        Entity.Column previous = columns.put(column.name, column);
+        if (previous != null)
+        {
+            throw new ConfigurationException("column name collision: " + 
getName() + "." + column.name + " mapped on " + getTable() + "." + 
previous.sqlName + " and on " + getTable() + "." + column.sqlName);
+        }
+        column.setIndex(columns.size() - 1);
+        
Optional.ofNullable(getModel().getFilters().getReadFilters().getColumnEntry(sqlName,
 column.sqlName)).ifPresent(filter -> column.setReadFilter(filter));
+        
Optional.ofNullable(getModel().getFilters().getWriteFilters().getColumnEntry(sqlName,
 column.sqlName)).ifPresent(filter -> column.setWriteFilter(filter));
+        columnsMapping.put(column.sqlName, column.name);
+    }
+
+    public List<Entity.Column> getPrimaryKey()
+    {
+        return primaryKey;
+    }
+
+    public BitSet getPrimaryKeyMask()
+    {
+        return (BitSet)primaryKeyMask.clone();
+    }
+
+    public BitSet getNonKeyMask()
+    {
+        BitSet ret = (BitSet)primaryKeyMask.clone();
+        ret.flip(0, columns.size());
+        return ret;
+    }
+
+    protected List<String> getSqlPrimaryKey()
+    {
+        return sqlPrimaryKey != null ? 
Collections.unmodifiableList(sqlPrimaryKey) : null;
+    }
+
+    protected void setSqlPrimaryKey(String ... sqlPrimaryKey) // receives sql 
names
+    {
+        this.sqlPrimaryKey = Arrays.asList(sqlPrimaryKey);
+
+        // now we can initialize entity internal attributes
+        initialize();
+    }
+
+    public Collection<Entity.Column> getColumns()
+    {
+        return Collections.unmodifiableCollection(columns.values());
+    }
+
+    public List<String> getColumnNames()
+    {
+        return Collections.unmodifiableList(columnNames);
+    }
+
+    public Entity.Column getColumn(int index)
+    {
+        return columns.get(columnNames.get(index));
+    }
+
+    public Entity.Column getColumn(String columnName)
+    {
+        return columns.get(columnName);
+    }
+
+    public String getColumnName(int index)
+    {
+        return columnNames.get(index);
+    }
+
+    public String translateColumnName(String sqlColumnName)
+    {
+        String ret = columnsMapping.get(sqlColumnName);
+        if (ret == null)
+        {
+            try
+            {
+                ret = 
getModel().getIdentifiers().getDefaultColumnLeaf().apply(sqlColumnName);
+            }
+            catch (Exception e)
+            {
+                throw new RuntimeException(e);
+            }
+        }
+        return ret;
+    }
+
+    public void delete(Map source) throws SQLException
+    {
+        delete.perform(source);
+    }
+
+    public void insert(Map source) throws SQLException
+    {
+        insert.perform(source);
+    }
+
+    public void update(Map source) throws SQLException
+    {
+        update.perform(source);
+    }
+
+    public Model getModel()
+    {
+        return model;
+    }
+
+    public Instance newInstance()
+    {
+        return instanceBuilder.create();
+    }
+
+    protected Map<String, Method> getWrappedInstanceGetters()
+    {
+        return wrappedInstanceGetters;
+    }
+
+    protected Map<String, Method> getWrappedInstanceSetters()
+    {
+        return wrappedInstanceSetters;
+    }
+
+    /**
+     * initialization
+     */
+
+    protected void initialize()
+    {
+        initializeAttributes();
+        // TODO - lazy initialization
+        if (sqlName == null)
+        {
+            sqlName = name;
+        }
+        columnNames = columns.values().stream().map(x -> 
x.name).collect(Collectors.toList());
+
+        String tableIdentifier = quoteIdentifier(getTable());
+
+        iterateAttribute = new RowsetAttribute("iterate", this);
+        iterateAttribute.setResultEntity((Entity)this);
+        iterateAttribute.addQueryPart("SELECT * FROM " + tableIdentifier);
+
+        countAttribute = new ScalarAttribute("getCount", this);
+        countAttribute.addQueryPart("SELECT COUNT(*) FROM " + tableIdentifier);
+
+        if (sqlPrimaryKey != null && sqlPrimaryKey.size() > 0)
+        {
+            primaryKey = getSqlPrimaryKey().stream().map(sql -> 
columns.get(columnsMapping.get(sql))).collect(Collectors.toList());
+            primaryKeyMask = new BitSet();
+            primaryKey.stream().forEach(col -> { col.setKeyColumn(); 
primaryKeyMask.set(col.getIndex()); });
+
+            fetchAttribute = new RowAttribute("retrieve", this);
+            fetchAttribute.setResultEntity((Entity)this);
+            fetchAttribute.addQueryPart("SELECT * FROM " + tableIdentifier + " 
WHERE ");
+            addKeyMapToAttribute(fetchAttribute);
+
+            delete = new Action("delete", this);
+            delete.addQueryPart("DELETE FROM " + tableIdentifier + " WHERE ");
+            addKeyMapToAttribute(delete);
+
+            update = new UpdateAction(this);
+            update.addQueryPart("UPDATE " + tableIdentifier + " SET ");
+            update.addParameter(UpdateAction.DYNAMIC_PART);
+            update.addQueryPart(" WHERE ");
+            addKeyMapToAttribute(update);
+
+            insert = new Action("insert", this);
+            insert.addQueryPart("INSERT INTO " + tableIdentifier + "(");
+            int nonGeneratedColumns = 0;
+            List<String> params = new ArrayList<>();
+            for (Entity.Column column : columns.values())
+            {
+                if (!column.generated)
+                {
+                    if (nonGeneratedColumns++ > 0)
+                    {
+                        insert.addQueryPart(", ");
+                    }
+                    insert.addQueryPart(quoteIdentifier(column.sqlName));
+                    params.add(column.name);
+                }
+            }
+            insert.addQueryPart(") VALUES (");
+            int col = 0;
+            for (String param : params)
+            {
+                if (col++ > 0)
+                {
+                    insert.addQueryPart(", ");
+                }
+                insert.addParameter(param);
+            }
+            insert.addQueryPart(")");
+        }
+    }
+
+    private void addKeyMapToAttribute(Attribute attribute)
+    {
+        for (int i = 0; i < sqlPrimaryKey.size(); ++i)
+        {
+            if (i > 0)
+            {
+                attribute.addQueryPart(" AND ");
+            }
+            attribute.addQueryPart(quoteIdentifier(sqlPrimaryKey.get(i)) + " = 
");
+            attribute.addParameter(getColumn(i).name);
+        }
+    }
+
+    protected void declareUpstreamJoin(String upstreamAttributeName, Entity 
pkEntity, List<String> fkColumns)
+    {
+        Attribute upstreamAttribute = new RowAttribute(upstreamAttributeName, 
this);
+        upstreamAttribute.setResultEntity(pkEntity);
+        List<String> pkColumns = pkEntity.getSqlPrimaryKey();
+        upstreamAttribute.addQueryPart("SELECT * FROM " + 
quoteIdentifier(pkEntity.getTable()) + " WHERE ");
+        for (int col = 0; col < pkColumns.size(); ++ col)
+        {
+            if (col > 0)
+            {
+                upstreamAttribute.addQueryPart(" AND ");
+            }
+            upstreamAttribute.addQueryPart(quoteIdentifier(pkColumns.get(col)) 
+ " = ");
+            
upstreamAttribute.addParameter(translateColumnName(fkColumns.get(col)));
+        }
+        addAttribute(upstreamAttribute);
+    }
+
+    public void declareDownstreamJoin(String downstreamAttributeName, Entity 
fkEntity, List<String> fkColumns)
+    {
+        Attribute downstreamAttribute = new 
RowsetAttribute(downstreamAttributeName, this);
+        downstreamAttribute.setResultEntity(fkEntity);
+        downstreamAttribute.addQueryPart("SELECT * FROM " + 
quoteIdentifier(fkEntity.getTable()) + " WHERE ");
+        for (int col = 0; col < sqlPrimaryKey.size(); ++ col)
+        {
+            if (col > 0)
+            {
+                downstreamAttribute.addQueryPart(" AND ");
+            }
+            
downstreamAttribute.addQueryPart(quoteIdentifier(fkColumns.get(col)) + " = ");
+            
downstreamAttribute.addParameter(translateColumnName(sqlPrimaryKey.get(col)));
+        }
+        addAttribute(downstreamAttribute);
+    }
+
+    public void declareExtendedJoin(String joinAttributeName, List<String> 
leftFKCols, Entity joinEntity, List<String> rightFKCols, Entity rightEntity)
+    {
+        List<String> rightPK = rightEntity.getSqlPrimaryKey();
+        Attribute extendedJoin = new RowsetAttribute(joinAttributeName, this);
+        extendedJoin.setResultEntity(rightEntity);
+        extendedJoin.addQueryPart("SELECT " + 
quoteIdentifier(rightEntity.getTable()) + ".* FROM " +
+            quoteIdentifier(joinEntity.getTable()) + "JOIN " + 
quoteIdentifier(rightEntity.getTable()) + " ON ");
+        for (int col = 0; col < rightPK.size(); ++col)
+        {
+            if (col > 0)
+            {
+                extendedJoin.addQueryPart(" AND ");
+            }
+            extendedJoin.addQueryPart(quoteIdentifier(rightEntity.getTable()) 
+ "." + quoteIdentifier(rightPK.get(col)) + " = " + 
quoteIdentifier(joinEntity.getTable()) + "." + 
quoteIdentifier(rightFKCols.get(col)));
+        }
+        extendedJoin.addQueryPart(" WHERE ");
+        for (int col = 0; col < sqlPrimaryKey.size(); ++col)
+        {
+            if (col > 0)
+            {
+                extendedJoin.addQueryPart(" AND ");
+            }
+            extendedJoin.addQueryPart(quoteIdentifier(leftFKCols.get(col)) + " 
= ");
+            
extendedJoin.addParameter(translateColumnName(sqlPrimaryKey.get(col)));
+        }
+        addAttribute(extendedJoin);
+    }
+
+    protected final String quoteIdentifier(String id)
+    {
+        return getModel().quoteIdentifier(id);
+    }
+
+    protected final Serializable filterValue(String columnName, Serializable 
value) throws SQLException
+    {
+        if (value != null)
+        {
+            Column column = getColumn(columnName);
+            if (column != null)
+            {
+                value = column.write(value);
+            }
+            Filter<Serializable> typeFilter = 
getModel().getFilters().getWriteFilters().getTypeEntry(value.getClass());
+            if (typeFilter != null)
+            {
+                value = typeFilter.apply(value);
+            }
+        }
+        return value;
+    }
+
+    public boolean hasColumn(String key)
+    {
+        return columns.containsKey(key);
+    }
+
+    protected void setInstanceBuilder(InstanceBuilder builder)
+    {
+        setInstanceBuilder(builder, null);
+    }
+
+    protected void setInstanceBuilder(InstanceBuilder builder, 
PropertyDescriptor[] properties)
+    {
+        this.instanceBuilder = builder;
+        if (properties != null)
+        {
+            wrappedInstanceGetters = new HashMap<String, Method>();
+            wrappedInstanceSetters = new HashMap<String, Method>();
+            for (PropertyDescriptor descriptor : properties)
+            {
+                String name = descriptor.getName();
+                
Optional.ofNullable(descriptor.getReadMethod()).ifPresent(getter -> 
wrappedInstanceGetters.put(name, getter));
+                
Optional.ofNullable(descriptor.getWriteMethod()).ifPresent(setter -> 
wrappedInstanceSetters.put(name, setter));
+            }
+        }
+    }
+
+    protected ScalarAttribute getCountAttribute()
+    {
+        return countAttribute;
+    }
+
+    protected RowAttribute getFetchAttribute()
+    {
+        return fetchAttribute;
+    }
+    protected RowsetAttribute getIterateAttribute()
+    {
+        return iterateAttribute;
+    }
+    private String name = null;
+    private String sqlName = null;
+    private Model model = null;
+    private Map<String, String> columnsMapping = new HashMap<>();
+    private LinkedHashMap<String, Entity.Column> columns = new 
LinkedHashMap<>();
+    private List<String> columnNames = null; // redundant with 'columns' 
field, but needed for random access
+    private List<String> sqlPrimaryKey = null;
+
+    private List<Entity.Column> primaryKey = null;
+    private BitSet primaryKeyMask = null;
+    private ScalarAttribute countAttribute = null;
+    private RowAttribute fetchAttribute = null;
+    private RowsetAttribute iterateAttribute = null;
+    private Action delete = null;
+
+    private Action insert = null;
+
+    private Action update = null;
+
+    private InstanceBuilder instanceBuilder = null;
+
+    private Map<String, Method> wrappedInstanceGetters = null;
+
+    private Map<String, Method> wrappedInstanceSetters = null;
+
+    @FunctionalInterface
+    protected interface InstanceBuilder
+    {
+        Instance create();
+    }
+
+    public static class Column
+    {
+        public Column(String name, String sqlName, int type, Integer size, 
boolean generated)
+        {
+            this.name = name;
+            this.sqlName = sqlName;
+            this.type = type;
+            this.size = size;
+            this.generated = generated;
+        }
+
+        public int getIndex()
+        {
+            return index;
+        }
+
+        protected void setIndex(int index)
+        {
+            this.index = index;
+        }
+
+        /**
+         * Whether column is the primary key, or part of the primary key
+         * @return
+         */
+        public boolean isKeyColumn()
+        {
+            return keyColumn;
+        }
+
+        protected void setKeyColumn()
+        {
+            keyColumn = true;
+        }
+
+        protected void setReadFilter(Filter<Serializable> readFilter)
+        {
+            this.readFilter = readFilter;
+        }
+
+        protected void setWriteFilter(Filter<Serializable> writeFilter)
+        {
+            this.writeFilter = writeFilter;
+        }
+
+        public final Serializable read(Serializable value) throws SQLException
+        {
+            return readFilter.apply(value);
+        }
+
+        public final Serializable write(Serializable value) throws SQLException
+        {
+            return writeFilter.apply(value);
+        }
+
+        public final String name;
+        public final String sqlName;
+        public final int type;
+        public final Integer size;
+        public final boolean generated;
+        private int index = -1;
+        private boolean keyColumn = false;
+        private Filter<Serializable> readFilter = Filter.identity();
+        private Filter<Serializable> writeFilter = Filter.identity();
+    }
+}


Reply via email to