velocity-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From cbris...@apache.org
Subject svn commit: r1298906 [6/14] - in /velocity/sandbox/velosurf: ./ docs/ examples/ examples/ant-vpp/ examples/ant-vpp/lib/ examples/auth-l10n/ examples/auth-l10n/WEB-INF/ examples/auth-l10n/WEB-INF/lib/ examples/auth-l10n/WEB-INF/src/ examples/auth-l10n/e...
Date Fri, 09 Mar 2012 16:23:32 GMT
Added: velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/model/Action.java
URL: http://svn.apache.org/viewvc/velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/model/Action.java?rev=1298906&view=auto
==============================================================================
--- velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/model/Action.java (added)
+++ velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/model/Action.java Fri Mar  9 16:23:25 2012
@@ -0,0 +1,194 @@
+/*
+ * Copyright 2003 The Apache Software Foundation.
+ *
+ * Licensed 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.velocity.velosurf.model;
+
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import org.apache.velocity.velosurf.sql.Database;
+import org.apache.velocity.velosurf.util.Logger;
+import org.apache.velocity.velosurf.util.StringLists;
+
+/**
+ * This class corresponds to custom update, delete and insert queries.
+ *
+ *  @author <a href=mailto:claude.brisson@gmail.com>Claude Brisson</a>
+ *
+ */
+public class Action
+{
+    /**
+     * Constructor.
+     *
+     * @param name name
+     * @param entity entity
+     */
+    public Action(String name, Entity entity)
+    {
+        this.entity = entity;
+        db = this.entity.getDB();
+        this.name = name;
+    }
+
+    /**
+     * Gets the parent entity
+     * @return parent entity
+     */
+    public Entity getEntity()
+    {
+        return entity;
+    }
+
+    /**
+     * Add a parameter name.
+     * @param paramName
+     */
+    public void addParamName(String paramName)
+    {
+        paramNames.add(paramName);
+    }
+
+    /**
+     * Sets the query.
+     * @param query query
+     */
+    public void setQuery(String query)
+    {
+        this.query = query;
+    }
+
+    /**
+     * Executes this action.
+     *
+     * @param source the object on which apply the action
+     * @exception SQLException an SQL problem occurs
+     * @return number of impacted rows
+     */
+    public int perform(Map<String, Object> source) throws SQLException
+    {
+        List params = buildArrayList(source);
+
+        return db.prepare(query).update(params);
+    }
+
+    /**
+     * Get the list of values for all parameters.
+     *
+     * @param source the ReadOnlyMap
+     * @exception SQLException thrown by the ReadOnlyMap
+     * @return the list of values
+     */
+    public List<Object> buildArrayList(Map<String, Object> source) throws SQLException
+    {
+        List<Object> result = new ArrayList<Object>();
+
+        if(source != null)
+        {
+            for(Iterator i = paramNames.iterator(); i.hasNext(); )
+            {
+                String paramName = (String)i.next();
+                Object value = source.get(paramName);
+
+                if(value == null)
+                {
+                    /* TODO: same problem than in Entity.extractColumnValues... we need a case-insensitive algorithm */
+                    value = source.get(paramName.toUpperCase());
+                    if(value == null)
+                    {
+                        value = source.get(paramName.toLowerCase());
+                    }
+                }
+                if(entity.isObfuscated(paramName))
+                {
+                    value = db.deobfuscate(value);
+                }
+                if(value == null)
+                {
+                    Logger.debug("Action " + getEntity().getName() + "." + name + ": param " + paramName + " is null!");
+                }
+                result.add(value);
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Get the name of the action.
+     *
+     * @return the name
+     */
+    public String getName()
+    {
+        return name;
+    }
+
+    /**
+     * For debugging purposes.
+     *
+     * @return definition string
+     */
+    public String toString()
+    {
+        String result = "";
+
+        if(paramNames.size() > 0)
+        {
+            result += "(" + StringLists.join(paramNames, ",") + ")";
+        }
+        result += ":" + query;
+        return result;
+    }
+
+    /**
+     * Get the database connection.
+     *
+     * @return the database connection
+     */
+    public Database getDB()
+    {
+        return db;
+    }
+
+    /**
+     * The database connection.
+     */
+    protected Database db = null;
+
+    /**
+     * The entity this action belongs to.
+     */
+    private Entity entity = null;
+
+    /**
+     * The name of this action.
+     */
+    private String name = null;
+
+    /**
+     * Parameter names of this action.
+     */
+    protected List<String> paramNames = new ArrayList<String>();
+
+    /**
+     * Query.
+     */
+    private String query = null;
+}

Added: velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/model/Attribute.java
URL: http://svn.apache.org/viewvc/velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/model/Attribute.java?rev=1298906&view=auto
==============================================================================
--- velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/model/Attribute.java (added)
+++ velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/model/Attribute.java Fri Mar  9 16:23:25 2012
@@ -0,0 +1,433 @@
+/*
+ * Copyright 2003 The Apache Software Foundation.
+ *
+ * Licensed 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.velocity.velosurf.model;
+
+import java.sql.SQLException;
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import org.apache.velocity.velosurf.context.RowIterator;
+import org.apache.velocity.velosurf.sql.Database;
+import org.apache.velocity.velosurf.sql.SqlUtil;
+import org.apache.velocity.velosurf.util.Logger;
+import org.apache.velocity.velosurf.util.StringLists;
+
+/**
+ * This class represents an attribute in the object model.
+ *
+ *  @author <a href=mailto:claude.brisson@gmail.com>Claude Brisson</a>
+ *
+ */
+public class Attribute
+{
+    /**
+     * Constant meaning the return type is undefined.
+     */
+    public static final int UNDEFINED = 0;
+
+    /**
+     * Constant meaning the result is a single row.
+     */
+    public static final int ROW = 1;
+
+    /**
+     * Constant meaning the result is a rowset.
+     */
+    public static final int ROWSET = 2;
+
+    /**
+     * Constant meaning the result is a scalar.
+     */
+    public static final int SCALAR = 3;
+
+    /**
+     * Constructor.
+     * @param name name of this attribute
+     * @param entity parent entity
+     */
+    public Attribute(String name, Entity entity)
+    {
+        this.entity = entity;
+        db = entity.getDB();
+        this.name = name;
+    }
+
+    /**
+     * Sets the result type.
+     * @param type
+     */
+    public void setResultType(int type)
+    {
+        this.type = type;
+    }
+
+    /**
+     * Gets the parent entity
+     * @return parent entity
+     */
+    public Entity getEntity()
+    {
+        return entity;
+    }
+
+    /**
+     * Gets the result type.
+     *
+     * @return a string describing the result type.
+     */
+    public String getResultEntity()
+    {
+        return resultEntity;
+    }
+
+    /**
+     * Sets the result entity.
+     * @param entityName the name of the result entity.
+     */
+    public void setResultEntity(String entityName)
+    {
+        resultEntity = entityName;
+    }
+
+    /**
+     * Declares this attribute as a foreign-key and specifies its foreign-key column.
+     * @param col the foreign-key column.
+     * @deprecated since Velosurf 2.0. Use a &lt;imported-key&gt; tag instead.
+     */
+    public void setForeignKeyColumn(String col)
+    {
+        foreignKey = col;
+    }
+
+    /**
+     * Adds a parameter name.
+     * @param name name of a parameter.
+     */
+    public void addParamName(String name)
+    {
+        paramNames.add(name);
+    }
+
+    /**
+     * Sets the query.
+     * @param query this attribute's query
+     */
+    public void setQuery(String query)
+    {
+        this.query = query;
+    }
+
+    /**
+     * Fetch a row.
+     *
+     * @param source source object
+     * @exception SQLException when thrown by the database
+     * @return instance fetched
+     */
+    public Object fetch(Map<String, Object> source) throws SQLException
+    {
+        if(type != ROW)
+        {
+            throw new SQLException("cannot call fetch: result of attribute '" + name + "' is not a row");
+        }
+        return db.prepare(getQuery()).fetch(buildArrayList(source), db.getEntity(resultEntity));
+    }
+
+    /**
+     * Query the resultset for this multivalued attribute.
+     *
+     * @param source the source object
+     * @exception SQLException when thrown from the database
+     * @return the resulting row iterator
+     */
+    public RowIterator query(Map<String, Object> source) throws SQLException
+    {
+        return query(source, null, null);
+    }
+
+    /**
+     * Query the rowset for this attribute.
+     *
+     * @param source source object
+     * @param refineCriteria refine criteria
+     * @param order order clause
+     * @exception SQLException when thrown by the database
+     * @return the resulting row iterator
+     */
+    public RowIterator query(Map<String, Object> source, List refineCriteria, String order) throws SQLException
+    {
+        if(type != ROWSET)
+        {
+            throw new SQLException("cannot call query: result of attribute '" + name + "' is not a rowset");
+        }
+
+        String query = getQuery();
+
+        if(refineCriteria != null)
+        {
+            query = SqlUtil.refineQuery(query, refineCriteria);
+        }
+        if(order != null && order.length() > 0)
+        {
+            query = SqlUtil.orderQuery(query, order);
+        }
+        return db.prepare(query).query(buildArrayList(source), resultEntity == null ? db.getRootEntity() : db.getEntity(resultEntity));
+    }
+
+    // TODO
+
+    /*
+     * public long getCount() {
+     *   return getCount(null,null);
+     * }
+     *
+     * String countQuery = null;
+     * Pattern pattern = Pattern.compile("\\s*select\\s.*\\sfrom\\s",Pattern.CASE_INSENSITIVE);
+     *
+     * public long getCount(List refineCriteria,String order) {
+     *   if(countQuery == null) {
+     *       synchronized(this) {
+     *           if(countQuery == null) {
+     *               String query = getQuery();
+     *               Matcher matcher = pattern.matcher(countQuery);
+     *               if(matcher.lookingAt()) {
+     *                   countQuery = matcher.replaceFirst().... pas bon
+     *               }
+     *           }
+     *       }
+     *   }
+     *
+     *   db.getEntity(resultEntity);
+     *   if (refineCriteria!=null) query = SqlUtil.refineQuery(query,refineCriteria);
+     *   if (order!=null && order.length()>0) query = SqlUtil.orderQuery(query,order);
+     *   return (Long)db.evaluate(query);
+     * }
+     */
+
+    /**
+     * Evaluate this scalar attribute.
+     *
+     * @param source source object
+     * @exception SQLException when thrown from the database
+     * @return the resulting scalar
+     */
+    public Object evaluate(Map<String, Object> source) throws SQLException
+    {
+        if(type != SCALAR)
+        {
+            throw new SQLException("cannot call evaluate: result of attribute '" + name + "' is not a scalar");
+        }
+        return db.prepare(getQuery()).evaluate(buildArrayList(source));
+    }
+
+    /**
+     * Get the type of this attribute.
+     *
+     * @return this attribute's type
+     */
+    public int getType()
+    {
+        return type;
+    }
+
+    /**
+     * Builds the list of parameter values.
+     *
+     * @param source source object
+     * @exception SQLException thrown by the database engine
+     * @return the built list
+     */
+    private List<Object> buildArrayList(Map<String, Object> source) throws SQLException
+    {
+        List<Object> result = new ArrayList<Object>();
+
+        if(source != null)
+        {
+            for(Iterator i = paramNames.iterator(); i.hasNext(); )
+            {
+                String paramName = (String)i.next();
+                Object value = null;
+                int dot = paramName.indexOf('.');
+
+                if(dot > 0 && dot < paramName.length() - 1)
+                {
+                    String parentKey = paramName.substring(0, dot);
+
+                    value = source.get(parentKey);
+                    if(value == null)
+                    {
+                        value = source.get(entity.resolveName(parentKey));
+                    }
+                    if(value != null && value instanceof Map)
+                    {
+                        String subKey = paramName.substring(dot + 1);
+
+                        value = ((Map)value).get(subKey);
+                    }
+                }
+                if(value == null)
+                {
+                    value = source.get(paramName);
+                }
+                if(entity.isObfuscated(paramName))
+                {
+                    value = db.deobfuscate(value);
+                }
+                if(value == null)
+                {
+                    Logger.debug("Attribute " + getEntity().getName() + "." + name + ": param " + paramName
+                                + " is null!");
+                }
+                value = db.filterParam(value);
+                result.add(value);
+            }
+        }
+        if(Logger.getLogLevel() >= Logger.TRACE_ID)
+        {
+            Logger.trace("    with parameters " + result);
+        }
+        return result;
+    }
+
+    /**
+     * Gets the name of this attribute.
+     *
+     * @return name of the attribute
+     */
+    public String getName()
+    {
+        return name;
+    }
+
+    /**
+     * Debug method.
+     *
+     * @return the definition string of this attribute
+     */
+    public String toString()
+    {
+        String result = "";
+
+        switch(type)
+        {
+            case SCALAR :
+                result += "!";
+                break;
+            case ROWSET :
+                result += "*";
+                break;
+        }
+        if(resultEntity != null)
+        {
+            result += resultEntity;
+        }
+        if(foreignKey != null)
+        {
+            result += ": foreign key (" + foreignKey + ")";
+        }
+        else
+        {
+            if(paramNames.size() > 0)
+            {
+                result += "(" + StringLists.join(paramNames, ",") + ")";
+            }
+            result += ": " + query;
+        }
+        return result;
+    }
+
+    protected String getQuery()
+    {
+        return query == null ? db.getEntity(resultEntity).getFetchQuery() : query;
+    }
+
+    /**
+     * Gets the database connection.
+     *
+     * @return database connection
+     */
+    public Database getDB()
+    {
+        return db;
+    }
+
+    /**
+     * Gets caching state
+     * @return caching state
+     */
+    public boolean getCaching()
+    {
+        return caching;
+    }
+
+    /**
+     * Sets caching on or off
+     *  @param c caching state
+     */
+    public void setCaching(boolean c)
+    {
+        caching = c;
+    }
+
+    /**
+     * Database connection.
+     */
+    protected Database db = null;
+
+    /**
+     * Name.
+     */
+    private String name = null;
+
+    /**
+     * Parent entity.
+     */
+    protected Entity entity;
+
+    /**
+     * For row and rowset attributes, the resulting entity (if specified).
+     */
+    protected String resultEntity;
+
+    /**
+     * If used, name of the foreign key.
+     * @deprecated
+     */
+    private String foreignKey = null;
+
+    /**
+     * List of the parameter names.
+     */
+    private List<String> paramNames = new ArrayList<String>();
+
+    /**
+     * Attribute query.
+     */
+    protected String query = null;
+
+    /**
+     * Attribute type.
+     */
+    private int type = UNDEFINED;
+
+    /**
+     * Caching
+     */
+    private boolean caching = false;
+}

Added: velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/model/Entity.java
URL: http://svn.apache.org/viewvc/velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/model/Entity.java?rev=1298906&view=auto
==============================================================================
--- velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/model/Entity.java (added)
+++ velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/model/Entity.java Fri Mar  9 16:23:25 2012
@@ -0,0 +1,1442 @@
+/*
+ * Copyright 2003 The Apache Software Foundation.
+ *
+ * Licensed 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.velocity.velosurf.model;
+
+import java.lang.reflect.Constructor;
+import java.sql.SQLException;
+import java.sql.Types;
+import java.util.*;
+import org.apache.velocity.velosurf.cache.Cache;
+import org.apache.velocity.velosurf.context.Instance;
+import org.apache.velocity.velosurf.context.RowIterator;
+import org.apache.velocity.velosurf.context.ExternalObjectWrapper;
+import org.apache.velocity.velosurf.sql.Database;
+import org.apache.velocity.velosurf.sql.PooledPreparedStatement;
+import org.apache.velocity.velosurf.sql.SqlUtil;
+import org.apache.velocity.velosurf.util.Logger;
+import org.apache.velocity.velosurf.util.StringLists;
+import org.apache.velocity.velosurf.util.UserContext;
+import org.apache.velocity.velosurf.validation.FieldConstraint;
+import org.apache.commons.lang.StringEscapeUtils;
+
+/** The Entity class represents an entity in the data model.
+ *
+ *  @author <a href=mailto:claude.brisson@gmail.com>Claude Brisson</a>
+ *
+ */
+public class Entity
+{
+    /**
+     * Constructor reserved for the framework.
+     *
+     * @param db database connection
+     * @param name entity name
+     * @param readOnly access mode (read-write or read-only)
+     * @param cachingMethod caching method to be used
+     */
+    public Entity(Database db,String name,boolean readOnly,int cachingMethod)
+    {
+        this.db = db;
+        this.name = name;
+        table = name; // default mapped table has same name
+        this.readOnly = readOnly;
+        this.cachingMethod = cachingMethod;
+        if (this.cachingMethod != Cache.NO_CACHE)
+        {
+            cache = new Cache(this.cachingMethod);
+        }
+        instanceClass = Instance.class;
+    }
+
+    /**
+     * Add a column at the end of the sequential list of named columns. Called during the reverse engeenering of the database.
+     *
+     * @param colName column name
+     */
+    public void addColumn(String colName,int sqlType)
+    {
+        colName = db.adaptCase(colName);
+        columns.add(colName);
+        types.put(colName,sqlType);
+        /* if (colnames as aliases) */ aliases.put(colName,colName);
+    }
+
+    /**
+     * Add a column alias.
+     * @param alias alias
+     * @param column column
+     */
+    public void addAlias(String alias, String column)
+    {
+        alias = db.adaptCase(alias);
+        column = db.adaptCase(column);
+        Logger.trace("added alias "+name+"."+alias+" -> "+name+"."+column);
+        aliases.put(alias,column);
+    }
+
+    /**
+     * Translates an alias to its column name.
+     * @param alias alias
+     * @return column name
+     */
+    public String resolveName(String alias)
+    {
+        alias = db.adaptCase(alias);
+        String name = aliases.get(alias);
+        return name == null ? alias : name;
+    }
+
+    /**
+     * Add a key column to the sequential list of the key columns. Called during the reverse-engeenering of the database.
+     *
+     * @param colName name of the key column
+     */
+    public void addPKColumn(String colName)
+    {
+        /* remember the alias */
+        keyCols.add(colName);
+    }
+
+    /**
+     * Add a new attribute.
+     * @param attribute attribute
+     */
+    public void addAttribute(Attribute attribute)
+    {
+        String name = attribute.getName();
+        if(attributeMap.containsKey(name))
+        {
+            Logger.debug("Explicit supersedes implicit for definition of attribute "+getName()+"."+name);
+        }
+        else
+        {
+            attributeMap.put(db.adaptCase(name),attribute);
+            Logger.trace("defined attribute "+this.name+"."+name+" = "+attribute);
+        }
+    }
+
+    /**
+     * Get a named attribute.
+     *
+     * @param property attribute name
+     * @return the attribute
+     */
+    public Attribute getAttribute(String property)
+    {
+        return (Attribute)attributeMap.get(db.adaptCase(property));
+    }
+
+    /**
+     * Add an action.
+     * @param action action
+     */
+    public void addAction(Action action)
+    {
+        String name = action.getName();
+        actionMap.put(db.adaptCase(name),action);
+        Logger.trace("defined action "+this.name+"."+name+" = "+action);
+    }
+
+    /**
+     * Get an action.
+     *
+     * @param property action name
+     * @return the action
+     */
+    public Action getAction(String property)
+    {
+        return (Action)actionMap.get(db.adaptCase(property));
+    }
+
+    /**
+     * Specify a custom class to use when instanciating this entity.
+     *
+     * @param className the java class name
+     */
+    public void setInstanceClass(String className)
+    {
+        try
+        {
+            instanceClass = Class.forName(className);
+        }
+        catch (Exception e)
+        {
+            Logger.log(e);
+        }
+    }
+
+    /**
+     * Specify the caching method. See {@link Cache} for allowed constants.
+     *
+     * @param caching Caching method
+     */
+    public void setCachingMethod(int caching)
+    {
+        if (cachingMethod != caching)
+        {
+            cachingMethod = caching;
+            if (cachingMethod == Cache.NO_CACHE)
+            {
+                cache = null;
+            }
+            else
+            {
+                cache = new Cache(cachingMethod);
+            }
+        }
+    }
+
+    /**
+     * Add a constraint.
+     * @param column column name
+     * @param constraint constraint
+     */
+    public void addConstraint(String column,FieldConstraint constraint)
+    {
+        column = resolveName(column);
+        Logger.trace("adding constraint on column "+Database.adaptContextCase(getName())+"."+column+": "+constraint);
+        List<FieldConstraint> list = constraints.get(column);
+        if (list == null)
+        {
+            list = new ArrayList<FieldConstraint>();
+            constraints.put(column,list);
+        }
+        list.add(constraint);
+    }
+
+    /**
+     * Used by the framework to notify this entity that its reverse enginering is over.
+     */
+    public void reverseEnginered()
+    {
+        if (obfuscate && keyCols.size()>0)
+        {
+            keyColObfuscated = new boolean[keyCols.size()];
+            Iterator key = keyCols.iterator();
+            int i=0;
+            for (;key.hasNext();i++)
+            {
+                keyColObfuscated[i] = obfuscatedColumns.contains(key.next());
+            }
+        }
+        /* fills the cache for the full caching method */
+        if(cachingMethod == Cache.FULL_CACHE)
+        {
+            try
+            {
+                query().getRows();
+            }
+            catch(SQLException sqle)
+            {
+                Logger.error("full caching for entity "+getName()+": could not fill the cache!");
+                Logger.log(sqle);
+            }
+        }
+    }
+
+    /**
+     * Clear the cache (not used for now).
+     */
+    protected void clearCache()
+    {
+        if (cache != null) cache.clear();
+    }
+
+    /**
+     * Create a new realisation of this entity.
+     *
+     * @return the newly created instance
+     */
+    public Instance newInstance()
+    {
+        Instance result = null;
+        try
+        {
+            if( instanceClass.equals(Instance.class))
+            {
+                result = new Instance(this);
+            }
+            else if (Instance.class.isAssignableFrom(instanceClass))
+            {
+                try
+                {
+                    result = (Instance)instanceClass.newInstance();
+                    result.initialize(this);
+                }
+                catch (Exception e)
+                {
+                    Constructor instanceConstructor = instanceClass.getConstructor(new Class[] {Entity.class} );
+                    result = (Instance)instanceConstructor.newInstance(new Object[] { this });
+                }
+            }
+            else
+            {
+                result = new ExternalObjectWrapper(this,instanceClass.newInstance());
+            }
+        }
+        catch (Exception e)
+        {
+            Logger.error("could not create a new instance for entity "+getName());
+            Logger.log(e);
+            result = null;
+        }
+        return result;
+    }
+
+    /**
+     * Build a new instance from a Map object.
+     *
+     * @param values the Map object containing the values
+     * @return the newly created instance
+     */
+    public Instance newInstance(Map<String,Object> values)
+    {
+        return newInstance(values,false);
+    }
+
+    /**
+     * Build a new instance from a Map object.
+     *
+     * @param values the Map object containing the values
+     * @param useSQLnames map keys use SQL column names that must be translated to aliases
+     * @return the newly created instance
+     */
+    public Instance newInstance(Map<String,Object> values,boolean useSQLnames)
+    {
+        try
+        {
+            Instance result = newInstance();
+            extractColumnValues(values,result,useSQLnames);
+            if (cachingMethod != Cache.NO_CACHE)
+            {
+                Object key = buildKey(result);
+                if (key != null)
+                {
+                    cache.put(key,result);
+                }
+            }
+            return result;
+        }
+        catch (SQLException sqle) {
+            Logger.log(sqle);
+            return null;
+        }
+    }
+
+    /**
+     * Invalidate an instance in the cache.
+     * @param instance instance
+     * @throws SQLException
+     */
+    public void invalidateInstance(Map<String,Object> instance) throws SQLException
+    {
+        if (cachingMethod != Cache.NO_CACHE)
+        {
+            Object key = buildKey(instance);
+            if(key != null)
+            {
+                cache.invalidate(key);
+            }
+        }
+    }
+
+    /**
+     * Extract column values from an input Map source and store result in target.
+     *
+     * @param source Map source object
+     * @param target Map target object
+     * @param SQLNames the source uses SQL names
+     */
+    private void extractColumnValues(Map<String,Object> source,Map<String,Object> target,boolean SQLNames) throws SQLException
+    {
+        /* TODO: cache a case-insensitive version of the columns list and iterate on source keys, with equalsIgnoreCase (or more efficient) funtion */
+        /* We use keySet and not entrySet here because if the source map is a ReadOnlyMap, entrySet is not available */
+        for(String key:source.keySet())
+        {
+            /* resove anyway */
+            String col = resolveName(key);
+            /* this is more or less a hack: we do filter columns
+               only when SQLNames is false. The purpose of this
+               is to allow additionnal fields in SQL attributes
+               returning rowsets of entities. */
+
+            if(!SQLNames && !isColumn(col))
+            {
+                continue;
+            }
+
+            Object val = source.get(key);
+	    if(val != null && val.getClass().isArray())
+            {
+		Logger.error("array cell values not supported");
+		val = null;
+	    }
+	    /*
+            if (val == null || (val.getClass().isArray())) {
+                continue;
+            }
+	    */
+            target.put(col,val);
+        }
+    }
+
+    /**
+     * Build the key for the Cache from a Map.
+     *
+     * @param values the Map containing all values (unaliased)
+     * @exception SQLException the getter of the Map throws an
+     *     SQLException
+     * @return an array containing all key values
+     */
+    private Object buildKey(Map<String,Object> values) throws SQLException
+    {
+        if(keyCols.size() == 0) return null;
+        Object [] key = new Object[keyCols.size()];
+        int c=0;
+        for(String keycol:keyCols)
+        {
+            Object v = values.get(keycol);
+            if ( v == null )
+            {
+                return null;
+            }
+
+            // systematically convert Integer values to Long values
+            // TODO CB - a more torough conversion scheme, or a key invariant on class, is to be built (for instance a concatenation of strings)
+            if( v instanceof Integer)
+            {
+                v = ((Integer)v).longValue();
+            }
+
+            key[c++] = v;
+        }
+        return key;
+    }
+
+
+    /**
+     * Build the key for the Cache from a List
+     *
+     * @param values the Map containing all values (unaliased)
+     * @exception SQLException the getter of the Map throws an
+     *     SQLException
+     * @return an array containing all key values
+     */
+    private Object buildKey(List<Object> values) throws SQLException {
+        if(keyCols.size() == 0)
+        {
+            return null;
+        }
+        Object [] key = new Object[keyCols.size()];
+        int c=0;
+        for(Object v:values)
+        {
+            if ( v == null )
+            {
+                return null;
+            }
+
+            // systematically convert Integer values to Long values
+            // TODO CB - a more torough conversion scheme, or a key invariant on class, is to be built (for instance a concatenation of strings)
+            if( v instanceof Integer)
+            {
+                v = ((Integer)v).longValue();
+            }
+
+            key[c++] = v;
+        }
+        return key;
+    }
+
+    /**
+     * Build the key for the Cache from a Number
+     *
+     * @param values the Map containing all values (unaliased)
+     * @exception SQLException the getter of the Map throws an
+     *     SQLException
+     * @return an array containing all key values
+     */
+    private Object buildKey(Number value) throws SQLException
+    {
+            // systematically convert Integer values to Long values
+            // TODO CB - a more torough conversion scheme, or a key invariant on class, is to be built (for instance a concatenation of strings)
+      if(value instanceof Integer)
+      {
+          value = value.longValue();
+      }
+      return new Object[] { value };
+    }
+
+
+    /**
+     * Getter for the name of this entity.
+     *
+     * @return the name of the entity
+     */
+    public String getName()
+    {
+        return name; 
+    }
+
+    /**
+     * Getter for the list of key column names.
+     *
+     * @return the list of key column names
+     */
+    public List<String> getPKCols()
+    {
+        return keyCols;
+    }
+
+    /**
+     * Getter for the list of column names.
+     *
+     * @return the list of column names
+     */
+    public List<String> getColumns()
+    {
+        return columns;
+    }
+
+    public List<String> getUpdatableColumns()
+    {
+        if(updatableCols == null)
+        {
+            updatableCols = new ArrayList(columns);
+            updatableCols.removeAll(keyCols);
+        }
+        return updatableCols;
+    }
+    
+    public boolean isColumn(String name)
+    {
+        return columns.contains(name);
+    }
+
+    public int getColumnIndex(String name)
+    {
+        return columns.indexOf(name);
+    }
+
+    public int getUpdatableColumnIndex(String name)
+    {
+        return updatableCols.indexOf(name);
+    }
+
+    /**
+     * Check if the provided map contains all key columns
+     *
+     * @param values map of values to check
+     * @return true if all key columns are present
+     */
+
+/* unused     
+    public boolean hasKey(Map<String,Object> values) {
+        if(keyCols.size() == 0) {
+            return false; // could be 'true' but indicates that 'fetch' cannot be called
+        }
+        List<String> cols = new ArrayList<String>();
+        for(String key:values.keySet()) {
+            cols.add(resolveName(key));
+        }
+        return (cols.containsAll(keyCols));
+    }
+*/
+
+    /**
+     * Insert a new row based on values of a map.
+     *
+     * @param values the  Map object containing the values
+     * @return success indicator
+     */
+    public boolean insert(Map<String,Object> values) throws SQLException
+    {
+        if (readOnly)
+        {
+            Logger.error("Error: Entity "+getName()+" is read-only!");
+            return false;
+        }
+        Instance instance = newInstance(values);
+        /* if found in cache because it exists, driver will issue a SQLException TODO review this*/
+        boolean success = instance.insert();
+        if (success && keyCols.size() == 1)
+        {
+            /* update last insert id */
+            /* TODO review usecase, this should maybe be forbidden sometimes */
+            /* FIXME for now, we catch some exceptions */
+            try
+            {
+                String pk = keyCols.get(0);
+                values.put(pk,instance.get(pk));
+//            catch(UnsupportedOperationException uoe)
+            }
+            catch(Exception e)
+            {
+                Logger.warn("insert: encountered "+e.getMessage()+" while setting last inserted id value");
+                Logger.warn("insert: values are probably provided using a read-only map");
+                Logger.warn("you can probably just ignore this warning, this is a reminder for the dev community");
+            }
+        }
+        /* try again to put it in the cache since previous attempt may have failed
+           in case there are auto-incremented columns */
+        if (success && cachingMethod != Cache.NO_CACHE)
+        {
+            Object key = buildKey(instance);
+            if (key != null)
+            {
+                cache.put(key,instance);
+            }
+        }
+        return success;
+    }
+
+    /**
+     * Update a row based on a set of values that must contain key values.
+     *
+     * @param values the Map object containing the values
+     * @return success indicator
+     */
+    public boolean update(Map<String,Object> values) throws SQLException
+    {
+        if (readOnly)
+        {
+            Logger.error("Error: Entity "+getName()+" is read-only!");
+            return false;
+        }
+        Instance instance = newInstance(values);
+        return instance.update();
+    }
+
+    /**
+     * Upsert a row based on a set of values (entity's primary key must be one column long - it can be omitted from provided values)
+     *
+     * @param values the Map object containing the values
+     * @return success indicator
+     */
+    public boolean upsert(Map<String,Object> values) throws SQLException
+    {
+        if (readOnly)
+        {
+            Logger.error("Error: Entity "+getName()+" is read-only!");
+            return false;
+        }
+        Instance instance = newInstance(values);
+        return instance.upsert();
+    }
+
+    /**
+     * Delete a row based on (key) values.
+     *
+     * @param values the Map containing the values
+     * @return success indicator
+     */
+    public boolean delete(Map<String,Object> values) throws SQLException
+    {
+        if (readOnly)
+        {
+            Logger.error("Error: Entity "+getName()+" is read-only!");
+            return false;
+        }
+        Instance instance = newInstance(values);
+        return instance.delete();
+    }
+
+    /**
+     * Delete a row based on the unique key string value.
+     *
+     * @param keyValue key value
+     * @return success indicator
+     */
+    public boolean delete(String keyValue) throws SQLException
+    {
+        if (readOnly)
+        {
+            Logger.error("Error: Entity "+getName()+" is read-only!");
+            return false;
+        }
+        if (keyCols.size()!=1)
+        {
+            if (keyCols.size()==0)
+            {
+                throw new SQLException("Entity.delete: Error: Entity '"+name+"' has no primary key!");
+            }
+            else
+            {
+              throw new SQLException("Entity.delete: Error: Entity '"+name+"' has a multi-column primary key!");
+            }
+        }
+        Instance instance = newInstance();
+        instance.put(keyCols.get(0),keyValue);
+        return instance.delete();
+    }
+
+    /**
+     * Delete a row based on the unique key string value.
+     *
+     * @param keyValue key value
+     * @return success indicator
+     */
+    public boolean delete(Number keyValue) throws SQLException
+    {
+        if (readOnly)
+        {
+            Logger.error("Error: Entity "+getName()+" is read-only!");
+            return false;
+        }
+        if (keyCols.size()!=1)
+        {
+            if (keyCols.size()==0)
+            {
+                throw new SQLException("Entity.delete: Error: Entity '"+name+"' has no primary key!");
+            }
+            else
+            {
+                throw new SQLException("Entity.delete: Error: Entity '"+name+"' has a multi-column primary key!");
+            }
+        }
+        Instance instance = newInstance();
+        instance.put(keyCols.get(0),keyValue);
+        return instance.delete();
+    }
+
+
+    /**
+     * Fetch an instance from key values stored in a List in natural order.
+     *
+     * @param values the List containing the key values
+     * @return the fetched instance
+     */
+    public Instance fetch(List<Object> values) throws SQLException
+    {
+        if (values.size() != keyCols.size())
+        {
+            throw new SQLException("Entity.fetch: Error: Wrong number of values for '"+name+"' primary key! Got "+values.size()+", was expecting "+keyCols.size()+" for key list: "+StringLists.join(keyCols,","));
+        }
+        Instance instance = null;
+        // try in cache
+        if (cachingMethod != Cache.NO_CACHE)
+        {
+            instance = (Instance)cache.get(buildKey(values));
+        }
+        if (instance == null)
+        {
+            PooledPreparedStatement statement = db.prepare(getFetchQuery());
+            if (obfuscate)
+            {
+                values = new ArrayList<Object>(values);
+                for(int col=0;col<keyColObfuscated.length;col++)
+                {
+                    if(keyColObfuscated[col])
+                    {
+                        values.set(col,deobfuscate(values.get(col)));
+                    }
+                }
+            }
+            instance = (Instance)statement.fetch(values,this);
+        }
+        if(instance != null)
+        {
+            instance.setClean();
+        }
+        return instance;
+    }
+        
+    /**
+     * Fetch an instance from key values stored in a Map.
+     *
+     * @param values the Map containing the key values
+     * @return the fetched instance
+     */
+    public Instance fetch(Map<String,Object> values) throws SQLException
+    {
+        if(keyCols.size() == 0)
+        {
+            throw new SQLException("entity "+name+": cannot fetch an instance for an entity without key!");
+        }
+        Instance instance = null;
+        /* extract key values */
+        Object key[] = new Object[keyCols.size()];
+        int n = 0;
+        for(Map.Entry<String,Object> entry:values.entrySet())
+        {
+            String col = resolveName(entry.getKey());
+            int i = keyCols.indexOf(col);
+            if (i != -1)
+            {
+                key[i] = entry.getValue();
+                n++;
+            }
+        }
+        if (n != keyCols.size() )
+        {
+            String missing = "";
+            for(int c=0;c<key.length;c++)
+            {
+                if(key[c] == null)
+                {
+                    missing += keyCols.get(c)+" ";
+                }
+            }
+            throw new SQLException("entity "+name+".fetch(): missing key values! Missing values: "+missing);
+        }
+        if (cachingMethod != Cache.NO_CACHE)
+        {
+            // try in cache
+            instance = (Instance)cache.get(key);
+        }
+        if (instance == null)
+        {
+            PooledPreparedStatement statement = db.prepare(getFetchQuery());
+            if (obfuscate)
+            {
+                for(int c=0;c<keyCols.size();c++)
+                {
+                    if (isObfuscated(keyCols.get(c)))
+                    {
+                        key[c] = deobfuscate(key[c]);
+                    }
+                }
+            }
+            instance = (Instance)statement.fetch(Arrays.asList(key),this);
+        }
+        if(instance != null)
+        {
+            instance.setClean();
+        }
+        return instance;
+    }
+
+    /**
+     * Fetch an instance from its key value as a string.
+     *
+     * @param keyValue the key
+     * @return the fetched instance
+     */
+    public Instance fetch(String keyValue) throws SQLException
+    {
+        if (keyCols.size()!=1)
+        {
+            if (keyCols.size()==0)
+            {
+                throw new SQLException("Entity.fetch: Error: Entity '"+name+"' has no primary key!");
+            }
+            else
+            {
+                throw new SQLException("Entity.fetch: Error: Entity '"+name+"' has a multi-column primary key!");
+            }
+        }
+        Instance instance = null;
+
+        // try in cache
+        if (cachingMethod != Cache.NO_CACHE)
+        {
+            instance = (Instance)cache.get(new Object[] { keyValue });
+        }
+
+        if (instance == null)
+        {
+            PooledPreparedStatement statement = db.prepare(getFetchQuery());
+            if (obfuscate && keyColObfuscated[0])
+            {
+                keyValue = deobfuscate(keyValue);
+            }
+            List<String> params = new ArrayList<String>();
+            params.add(keyValue);
+            instance = (Instance)statement.fetch(params,this);
+        }
+        if(instance != null)
+        {
+            instance.setClean();
+        }
+        return instance;
+    }
+
+    /**
+     * Fetch an instance from its key value specified as a Number.
+     *
+     * @param keyValue the key
+     * @return the fetched instance
+     */
+    public Instance fetch(Number keyValue) throws SQLException
+    {
+        if (keyCols.size()!=1)
+        {
+            if (keyCols.size()==0)
+            {
+                throw new SQLException("Entity.fetch: Error: Entity '"+name+"' has no primary key!");
+            }
+            else
+            {
+                throw new SQLException("Entity.fetch: Error: Entity '"+name+"' has a multi-column primary key!");
+            }
+        }
+        Instance instance = null;
+
+        // try in cache
+        if (cachingMethod != Cache.NO_CACHE)
+        {
+          Number cacheKeyValue = keyValue instanceof Integer ? keyValue.longValue() : keyValue;
+            instance = (Instance)cache.get(buildKey(keyValue));
+        }
+
+        if (instance == null)
+        {
+            PooledPreparedStatement statement = db.prepare(getFetchQuery());
+            List<Number> params = new ArrayList<Number>();
+            params.add(keyValue);
+            if(obfuscate && keyColObfuscated[0])
+            {
+                Logger.warn("fetch: column '"+columns.get(0)+"' is obfuscated, please use $db."+name+".fetch($db.obfuscate("+keyValue+"))");
+                return null;
+            }
+            instance = (Instance)statement.fetch(params,this);
+        }
+        if(instance != null)
+        {
+            instance.setClean();
+        }
+        return instance;
+    }
+
+    /**
+     * Get the SQL query string used to fetch one instance of this query.
+     *
+     * @return the SLQ query
+     */
+    public String getFetchQuery()
+    {
+        if (fetchQuery == null)
+        {
+            buildFetchQuery();
+        }
+        return fetchQuery;
+    }
+
+    /**
+     * Build the SQL query used to fetch one instance of this query.
+     */
+    private void buildFetchQuery()
+    {
+        List<String> whereClause = new ArrayList<String>();
+        for(String column:keyCols)
+        {
+            whereClause.add(column+"=?");
+        }
+        fetchQuery = "select * from "+table+" where "+StringLists.join(whereClause," and ");
+    }
+
+    /**
+     * Issue a query to iterate though all instances of this entity.
+     *
+     * @return the resulting RowIterator
+     */
+    public RowIterator query() throws SQLException
+    {
+        return query(null,null);
+    }
+
+    /**
+     * Issue a query to iterate thought instances of this entity, with a facultative refining criteria and a facultative order by clause.
+     *
+     * @param refineCriteria a refining criteria or null to get all instances
+     * @param order an 'order by' clause or null to get instances in their
+     *     natural order
+     * @return the resulting RowIterator
+     */
+    public RowIterator query(List refineCriteria,String order) throws SQLException
+    {
+        String query = "select * from "+ table;
+        if (refineCriteria!=null)
+        {
+            query = SqlUtil.refineQuery(query,refineCriteria);
+        }
+        if (order!=null && order.length()>0)
+        {
+            query = SqlUtil.orderQuery(query,order);
+        }
+        return db.query(query,this);
+    }
+
+    public long getCount()
+    {
+        return getCount(null);
+    }
+
+    public long getCount(List refineCriteria)
+    {
+        String query = "select count(*) from "+ table;
+        if (refineCriteria!=null)
+        {
+            query = SqlUtil.refineQuery(query,refineCriteria);
+        }
+        return (Long)db.evaluate(query);
+    }
+
+    /**
+     * Get the database connection.
+     *
+     * @return the database connection
+     */
+    public Database getDB()
+    {
+        return db;
+    }
+
+    /**
+     * Is this entity read-only or read-write?
+     *
+     * @return whether this entity is read-only or not
+     */
+    public boolean isReadOnly()
+    {
+        return readOnly;
+    }
+
+    /**
+     * Set this entity to be read-only or read-write.
+     *
+     * @param readOnly the mode to switch to : true for read-only, false for
+     *     read-write
+     */
+    public void setReadOnly(boolean readOnly)
+    {
+        this.readOnly = readOnly;
+    }
+
+    /**
+     * Set the name of the table mapped by this entity.
+     *
+     * @param table the table mapped by this entity
+     *     read-write
+     */
+    public void setTableName(String table)
+    {
+        this.table = table;
+    }
+
+    /**
+     * Get the name of the mapped table.
+     *
+     * @return name of the mapped table
+     */
+    public String getTableName()
+    {
+        return table;
+    }
+
+    /**
+     * Indicates a column as being obfuscated.
+     * @param columns list of obfuscated columns
+     */
+    public void setObfuscated(List<String> columns)
+    {
+        obfuscate = true;
+        obfuscatedColumns = columns;
+    }
+
+    /**
+     * Returns whether the given column is obfuscated.
+     * @param column the name of the column
+     * @return a boolean indicating whether this column is obfuscated
+     */
+    public boolean isObfuscated(String column)
+    {
+        return obfuscate && obfuscatedColumns.contains(db.adaptCase(column));
+    }
+
+    /**
+     * Obfuscate given value.
+     * @param value value to obfuscate
+     *
+     * @return obfuscated value
+     */
+    public String obfuscate(Object value)
+    {
+        return db.obfuscate(value);
+    }
+
+    /**
+     * Obfuscate this id value if needed.
+     *  @param id id value
+     *  @return filtered id value (that is, obfuscated if needed)
+     */
+    public Object filterID(Long id)
+    {
+        if (keyCols.size() == 1 && isObfuscated((String)keyCols.get(0)))
+        {
+            return obfuscate(Long.valueOf(id));
+        }
+        return Long.valueOf(id);
+    }
+
+    /**
+     * De-obfuscate given value.
+     * @param value value to de-obfuscate
+     *
+     * @return obfuscated value
+     */
+    public String deobfuscate(Object value)
+    {
+        return db.deobfuscate(value);
+    }
+
+    /**
+     * Indicates a column as being localized.
+     * @param columns list of localized columns
+     */
+    public void setLocalized(List columns)
+    {
+        localizedColumns = columns;
+    }
+
+    /**
+     * Returns whether the given column is obfuscated.
+     * @param column the name of the column
+     * @return a boolean indicating whether this column is obfuscated
+     */
+    public boolean isLocalized(String column)
+    {
+        return localizedColumns != null && localizedColumns.contains(db.adaptCase(column));
+    }
+
+    /**
+     * Does this entity have localized columns?
+     */
+    public boolean hasLocalizedColumns()
+    {
+        return localizedColumns != null && localizedColumns.size() > 0;
+    }
+
+    /**
+     * Truncate validation error messages to a maximum number of characters.
+     */
+    private static final int MAX_DATA_DISPLAY_LENGTH = 40;
+
+    /**
+     * Validate a set of values.
+     */
+    public boolean validate(Map<String,Object> row) throws SQLException
+    {
+        boolean ret = true;
+
+        UserContext userContext = db.getUserContext();
+        /* FIXME Is it a good choice to clear the user context now?
+           We may want to validate several entities
+           before displaying errors to the user.
+           */
+        userContext.clearValidationErrors();
+        List<ValidationError> errors = new ArrayList<ValidationError>();
+        for(Map.Entry<String,Object> entry:row.entrySet())
+        {
+            String col = resolveName(entry.getKey());
+            Object data = entry.getValue();
+            List<FieldConstraint> list = constraints.get(col);
+            if(list != null)
+            {
+                for(FieldConstraint constraint:list)
+                {
+                    if (!constraint.validate(data, userContext.getLocale()))
+                    {
+                        String stringData = data.toString();
+                        String formatted = (data==null || stringData.length() == 0 ? "empty value" : stringData);
+                        if (formatted.length() > MAX_DATA_DISPLAY_LENGTH)
+                        {
+                            formatted = formatted.substring(0,MAX_DATA_DISPLAY_LENGTH)+"...";
+                        }
+                        formatted = StringEscapeUtils.escapeHtml(formatted);
+                        errors.add(new ValidationError(col,userContext.localize(constraint.getMessage(),Database.adaptContextCase(col),formatted)));
+                        ret = false;
+                    }
+                }
+            }
+        }
+        if(errors.size()>0)
+        {
+            /* sort in columns natural order... better than nothing.
+               The ideal ordering would be the order of the form fields,
+               but it is unreachable. */
+            Collections.sort(errors);
+            for(ValidationError error:errors)
+            {
+                Logger.trace("validation: new message: "+error.message);
+                userContext.addValidationError(error.message);
+            }
+        }
+        return ret;
+    }
+
+    public class ColumnOrderComparator implements Comparator<String>
+    {
+        public int compare(String o1, String o2)
+        {
+            String col1 = resolveName(o1);
+            String col2 = resolveName(o2);
+            int i1 = columns.indexOf(col1);
+            int i2 = columns.indexOf(col2);
+	    if(i1 == -1 && i2 == -1)
+            {
+                return o1.compareTo(o2);
+            }
+	    else if (i1 == -1)
+            {
+                return -1;
+            }
+	    else if (i2 == -1)
+            {
+                return 1;
+            }
+            else
+            {
+                return i1 - i2;
+            }
+        }
+    }
+
+    public Comparator<String> getColumnOrderComparator()
+    {
+        return new ColumnOrderComparator();
+    }
+
+    class ValidationError implements Comparable<ValidationError>
+    {
+        ValidationError(String column,String message)
+        {
+            index = columns.indexOf(column);
+            this.message = message;
+        }
+
+        public int compareTo(ValidationError cmp)
+        {
+            return index - cmp.index;
+        }
+
+        String message;
+        int index;
+    }
+    
+
+    /**
+     * Check for the existence of an imported key with the same columns.
+     * @param pkEntity primary key entity
+     * @param fkCols foreign key columns
+     * @return previously defined imported key, if any
+     */
+    public ImportedKey findImportedKey(Entity pkEntity,List<String> fkCols)
+    {
+        for(Map.Entry<String,Attribute> entry:attributeMap.entrySet())
+        {
+            Attribute attribute = entry.getValue();
+            if (!(attribute instanceof ImportedKey))
+            {
+                continue;
+            }
+            ImportedKey imported = (ImportedKey)attribute;
+            if (imported.getResultEntity().equals(pkEntity.getName())
+                && ( imported.getFKCols() == null || imported.getFKCols().equals(fkCols)) )
+            {
+                return imported;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Check for the existence of an exported key with the same columns.
+     * @param fkEntity foreign key entity
+     * @param fkCols foreign key columns
+     * @return previously defined exported key, if any
+     */
+    public ExportedKey findExportedKey(Entity fkEntity,List<String> fkCols)
+    {
+        for(Map.Entry<String,Attribute> entry:attributeMap.entrySet())
+        {
+            Attribute attribute = entry.getValue();
+            if (!(attribute instanceof ExportedKey))
+            {
+                continue;
+            }
+            ExportedKey exported = (ExportedKey)attribute;
+            if (exported.getResultEntity().equals(fkEntity.getName())
+                && ( exported.getFKCols() == null || exported.getFKCols().equals(fkCols) ))
+            {
+                return exported;
+            }
+        }
+        return null;
+    }
+
+    public Object filterIncomingValue(String column,Object value)
+    {
+        if(value == null)
+        {
+            return null;
+        }
+        /* for now, only filter boolean values */
+        Integer type = types.get(column);
+        if (type == null)
+        {
+            return value;
+        }
+        if(type == Types.BOOLEAN || type == Types.BIT)
+        {
+            if(String.class.isAssignableFrom(value.getClass()))
+            {
+                String s = (String)value;
+                if("true".equalsIgnoreCase(s) || "on".equalsIgnoreCase(s) || "1".equalsIgnoreCase(s) || "yes".equalsIgnoreCase(s))
+                {
+                    value = new Boolean(true);
+                }
+                else
+                {
+                    value = new Boolean(false);
+                }
+            }
+        }
+        return value;
+    }
+
+    public Map<String,Attribute> getAttributes()
+    {
+        return attributeMap;
+    }
+
+    public Map<String,Action> getActions()
+    {
+        return actionMap;
+    }
+
+    public boolean isRootEntity()
+    {
+        return "velosurf.root".equals(name);
+    }
+
+    /**
+     * Get the SQL type for the specified column 
+     * @param column column name
+     * @return the sql type
+     */
+    public int getColumnType(String column)
+    {
+        return types.get(column);
+    }
+
+    /**
+     * Name.
+     */
+    private String name = null;
+
+    /**
+     * Table.
+     */
+    private String table = null;
+
+    /**
+     * Column names in natural order.
+     */
+    private List<String> columns = new ArrayList<String>(); // list<String>
+
+    /**
+     * Column types
+     */
+    private Map<String,Integer> types = new HashMap<String,Integer>();
+
+    /**
+     * Key column names in natural order.
+     */
+    private List<String> keyCols = new ArrayList<String>();
+
+    /**
+     * Non-key column names in natural order
+     */
+    private List<String> updatableCols = null;
+
+    /**
+     * Whether to obfuscate something.
+     */
+    private boolean obfuscate = false;
+
+    /**
+     * Names of obfuscated columns.
+     */
+    private List<String> obfuscatedColumns = null;
+
+    /**
+     * Obfuscation status of key columns.
+     */
+    private boolean keyColObfuscated[] = null;
+
+    /**
+     * Localized columns.
+     */
+    private List localizedColumns = null;
+
+    /**
+     * Attributes of this entity.
+     */
+
+    /**
+     * Column by alias map.
+     */
+    private Map<String,String> aliases = new HashMap<String,String>();
+
+    /**
+     * Attribute map.
+     */
+    private Map<String,Attribute> attributeMap = new HashMap<String,Attribute>();
+
+    /**
+     * Action map.
+     */
+    private Map<String,Action> actionMap = new HashMap<String,Action>();
+
+    /**
+     * The java class to use to realize this instance.
+     */
+    private Class instanceClass = null;
+
+    /**
+     * The SQL query used to fetch one instance of this entity.
+     */
+    private String fetchQuery = null;
+
+    /**
+     * Whether this entity is read-only or not.
+     */
+    private boolean readOnly;
+
+    /**
+     * The database connection.
+     */
+    private Database db = null;
+
+    /**
+     * The caching method.
+     */
+    private int cachingMethod = 0;
+
+    /**
+     * The cache.
+     */
+    private Cache cache = null;
+    
+    /**
+     * Constraint by column name map.
+     */
+    private Map<String,List<FieldConstraint>> constraints = new HashMap<String,List<FieldConstraint>>();
+}

Added: velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/model/ExportedKey.java
URL: http://svn.apache.org/viewvc/velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/model/ExportedKey.java?rev=1298906&view=auto
==============================================================================
--- velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/model/ExportedKey.java (added)
+++ velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/model/ExportedKey.java Fri Mar  9 16:23:25 2012
@@ -0,0 +1,121 @@
+/*
+ * Copyright 2003 The Apache Software Foundation.
+ *
+ * Licensed 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.velocity.velosurf.model;
+
+import java.util.List;
+import org.apache.velocity.velosurf.util.Logger;
+import org.apache.velocity.velosurf.util.StringLists;
+
+/**
+ * An exported key (aka primary key used in a foreign key) attribute.
+ *
+ */
+public class ExportedKey extends Attribute
+{
+    /**
+     * List of foreign keys.
+     */
+    private List<String> fkCols = null;
+
+    /**
+     * Order
+     */
+    private String order = null;
+
+    /**
+     * Exported key constructor.
+     * @param name name of this exported key
+     * @param entity parent entity
+     * @param fkEntity foreign key entity
+     * @param fkCols foreign key columns
+     */
+    public ExportedKey(String name, Entity entity, String fkEntity, List<String> fkCols)
+    {
+        super(name, entity);
+        setResultType(Attribute.ROWSET);
+        setResultEntity(fkEntity);
+        this.fkCols = fkCols;    /* may still be null at this stage */
+    }
+
+    /**
+     * Foreign key columns getter.
+     * @return foreign key columns list
+     */
+    public List<String> getFKCols()
+    {
+        if(fkCols == null)
+        {
+            fkCols = entity.getPKCols();
+        }
+        return fkCols;
+    }
+
+    /**
+     * Foreign key columns setter.
+     * @param fkCols foreign key columns list
+     */
+    public void setFKCols(List<String> fkCols)
+    {
+        this.fkCols = fkCols;
+    }
+
+    /**
+     * Set order
+     */
+    public void setOrder(String order)
+    {
+        this.order = order;
+    }
+
+    /**
+     * Query getter.
+     * @return the SQL query
+     */
+    protected synchronized String getQuery()
+    {
+        if(query == null)
+        {
+            Entity fkEntity = db.getEntity(resultEntity);
+
+            for(String param : getEntity().getPKCols())
+            {
+                addParamName(param);
+            }
+            query = "SELECT * FROM " + fkEntity.getTableName() + " WHERE " + StringLists.join(getFKCols(), " = ? AND ")
+                    + " = ?";
+            if(order != null)
+            {
+                query += " ORDER BY " + order;
+            }
+
+//          Logger.debug(getEntity().getName()+"."+getName()+" = "+query+" [ with params "+StringLists.join(getEntity().getPKCols(),",")+" ]" );
+        }
+        return query;
+    }
+
+    /**
+     * Debug method.
+     *
+     * @return the definition string of this attribute
+     */
+    public String toString()
+    {
+        return "exported-key" + (fkCols == null ? "" : " on " + StringLists.join(fkCols, ","));
+    }
+}

Added: velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/model/ImportedKey.java
URL: http://svn.apache.org/viewvc/velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/model/ImportedKey.java?rev=1298906&view=auto
==============================================================================
--- velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/model/ImportedKey.java (added)
+++ velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/model/ImportedKey.java Fri Mar  9 16:23:25 2012
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2003 The Apache Software Foundation.
+ *
+ * Licensed 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.velocity.velosurf.model;
+
+import java.util.List;
+import org.apache.velocity.velosurf.util.Logger;
+import org.apache.velocity.velosurf.util.StringLists;
+
+/**
+ * An imported key (aka foreign key) attribute.
+ *
+ */
+public class ImportedKey extends Attribute
+{
+    /**
+     * Foreign key columns.
+     */
+    private List<String> fkCols = null;
+
+    /**
+     * Imported key constructor.
+     *
+     * @param name name of this exported key
+     * @param entity parent entity
+     * @param pkEntity primary key entity
+     * @param fkCols foreign key columns
+     */
+    public ImportedKey(String name, Entity entity, String pkEntity, List<String> fkCols)
+    {
+        super(name, entity);
+        setResultEntity(pkEntity);
+        this.fkCols = fkCols;    /* may still be null at this stage */
+        setResultType(Attribute.ROW);
+    }
+
+    /**
+     * Query getter.
+     * @return SQL query
+     */
+    protected synchronized String getQuery()
+    {
+        if(query == null)
+        {
+            Entity pkEntity = db.getEntity(resultEntity);
+
+            for(String param : getFKCols())
+            {
+                addParamName(param);
+            }
+            query = "SELECT * FROM " + pkEntity.getTableName() + " WHERE "
+                    + StringLists.join(pkEntity.getPKCols(), " = ? AND ") + " = ?";
+
+//          Logger.debug(getEntity().getName()+"."+getName()+" = "+query+" [ with params "+StringLists.join(fkCols,",")+" ]" );
+        }
+        return query;
+    }
+
+    /**
+     * Foreign key columns getter.
+     * @return foreign key columns list
+     */
+    public List<String> getFKCols()
+    {
+        if(fkCols == null)
+        {
+            fkCols = entity.getDB().getEntity(getResultEntity()).getPKCols();
+        }
+        return fkCols;
+    }
+
+    /**
+     * Foreign key columns setter.
+     *
+     * @param fkCols foreign key columns list
+     */
+    public void setFKCols(List<String> fkCols)
+    {
+        this.fkCols = fkCols;
+    }
+
+    /**
+     * Debug method.
+     *
+     * @return the definition string of this attribute
+     */
+    public String toString()
+    {
+        return "imported-key" + (fkCols == null ? "" : " on " + StringLists.join(fkCols, ","));
+    }
+}

Added: velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/model/Transaction.java
URL: http://svn.apache.org/viewvc/velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/model/Transaction.java?rev=1298906&view=auto
==============================================================================
--- velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/model/Transaction.java (added)
+++ velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/model/Transaction.java Fri Mar  9 16:23:25 2012
@@ -0,0 +1,151 @@
+/*
+ * Copyright 2003 The Apache Software Foundation.
+ *
+ * Licensed 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.velocity.velosurf.model;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.List;
+import java.util.Map;
+import org.apache.velocity.velosurf.sql.ConnectionWrapper;
+import org.apache.velocity.velosurf.sql.PooledPreparedStatement;
+import org.apache.velocity.velosurf.util.StringLists;
+
+/**
+ * This class is an action that gather several consecutive update/delete/insert queries.
+ *
+ *  @author <a href=mailto:claude.brisson@gmail.com>Claude Brisson</a>
+ *
+ */
+public class Transaction extends Action
+{
+    /**
+     * Builds a new transaction.
+     *
+     * @param name transaction name
+     * @param entity entity
+     */
+    public Transaction(String name, Entity entity)
+    {
+        super(name, entity);
+    }
+
+    /**
+     * Set the list of queries.
+     * @param queries list of SQL queries
+     */
+    public void setQueries(List<String> queries)
+    {
+        this.queries = queries;
+    }
+
+    /**
+     * Set the list of list of parameters.
+     *
+     * @param paramLists list of list of parameters
+     */
+    public void setParamNamesLists(List<List<String>> paramLists)
+    {
+        paramNamesList = paramLists;
+    }
+
+    /**
+     * Performs this action.
+     *
+     * @param source ReadOnlyMap containing parameter values
+     * @exception SQLException thrown from the database
+     * @return number of affected rows (addition of all the partial counts)
+     */
+    public int perform(Map<String, Object> source) throws SQLException
+    {
+        ConnectionWrapper conn = db.getTransactionConnection();
+
+        try
+        {
+            int nb = queries.size();
+            int ret = 0;
+
+            for(int i = 0; i < nb; i++)
+            {
+                // fool the buildArrayList method by using
+                // the super member paramNames
+                paramNames = (List<String>)paramNamesList.get(i);
+
+                List params = buildArrayList(source);
+
+                /* TODO: pool transaction statements */
+                PooledPreparedStatement statement = new PooledPreparedStatement(conn,
+                                                        conn.prepareStatement(queries.get(i),
+                                                            ResultSet.TYPE_SCROLL_INSENSITIVE,
+                                                            ResultSet.CONCUR_READ_ONLY));
+
+                ret += statement.update(params);
+                statement.close();
+            }
+            conn.commit();
+            return ret;
+        }
+        catch(SQLException sqle)
+        {
+            conn.rollback();
+            throw sqle;
+        }
+        finally
+        {
+            conn.leaveBusyState();
+        }
+    }
+
+    /**
+     * Debug method.
+     *
+     * @return the definition string of the transaction
+     */
+    public String toString()
+    {
+        StringBuffer result = new StringBuffer();
+        int nb = queries.size();
+
+        for(int i = 0; i < nb; i++)
+        {
+            List paramNames = (List)paramNamesList.get(i);
+
+            if(paramNames.size() > 0)
+            {
+                result.append("(");
+                result.append(StringLists.join(paramNames, ",") + ")");
+            }
+            result.append(":" + queries.get(i));
+            if(i < nb - 1)
+            {
+                result.append('\n');
+            }
+        }
+        return result.toString();
+    }
+
+    /**
+     * All the queries.
+     */
+    private List<String> queries;    // = null; WARNING : this init code is executed AFER Action constructor
+
+    /**
+     * List of lists of parameter names.
+     */
+    private List<List<String>> paramNamesList;    // = null;
+}

Added: velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/model/package.html
URL: http://svn.apache.org/viewvc/velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/model/package.html?rev=1298906&view=auto
==============================================================================
--- velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/model/package.html (added)
+++ velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/model/package.html Fri Mar  9 16:23:25 2012
@@ -0,0 +1,23 @@
+<html>
+<body bgcolor="white">
+
+Classes linked to the data model : entities, attributes and actions
+
+<h2>Package Specification</h2>
+
+<!-- ANY SPECS NEEDED BY JAVA COMPATIBILITY KIT GO THERE 
+<ul>
+  <li><a href="">##### REFER TO ANY FRAMEMAKER SPECIFICATION HERE #####</a>
+</ul>
+
+<h2>Related Documentation</h2>
+
+For overviews, tutorials, examples, guides, and tool documentation, please see:
+<ul>
+  <li><a href="">##### REFER TO NON-SPEC DOCUMENTATION HERE #####</a>
+</ul>
+
+<!-- Put @see and @since tags down here. -->
+
+</body>
+</html>

Added: velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/package.html
URL: http://svn.apache.org/viewvc/velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/package.html?rev=1298906&view=auto
==============================================================================
--- velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/package.html (added)
+++ velocity/sandbox/velosurf/src/java/org/apache/velocity/velosurf/package.html Fri Mar  9 16:23:25 2012
@@ -0,0 +1,23 @@
+<html>
+<body bgcolor="white">
+
+Contains the Velosurf tool.
+
+<h2>Package Specification</h2>
+
+<!-- ANY SPECS NEEDED BY JAVA COMPATIBILITY KIT GO THERE
+<ul>
+  <li><a href="">##### REFER TO ANY FRAMEMAKER SPECIFICATION HERE #####</a>
+</ul>
+
+<h2>Related Documentation</h2>
+
+For overviews, tutorials, examples, guides, and tool documentation, please see:
+<ul>
+  <li><a href="">##### REFER TO NON-SPEC DOCUMENTATION HERE #####</a>
+</ul>
+
+<!-- Put @see and @since tags down here. -->
+
+</body>
+</html>



Mime
View raw message