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 <imported-key> 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>
|