001: /*
002: * $Id: ForeachHandler.java,v 1.9 2005/05/04 05:05:58 spal Exp $
003: * $Source: /cvsroot/sqlunit/sqlunit/src/net/sourceforge/sqlunit/handlers/ForeachHandler.java,v $
004: * SQLUnit - a test harness for unit testing database stored procedures.
005: * Copyright (C) 2003 The SQLUnit Team
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License
009: * as published by the Free Software Foundation; either version 2
010: * of the License, or (at your option) any later version.
011: *
012: * This program is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
015: * GNU General Public License for more details.
016: *
017: * You should have received a copy of the GNU General Public License
018: * along with this program; if not, write to the Free Software
019: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
020: */
021: package net.sourceforge.sqlunit.handlers;
022:
023: import net.sourceforge.sqlunit.HandlerFactory;
024: import net.sourceforge.sqlunit.IErrorCodes;
025: import net.sourceforge.sqlunit.IHandler;
026: import net.sourceforge.sqlunit.SQLUnitException;
027: import net.sourceforge.sqlunit.SymbolTable;
028: import net.sourceforge.sqlunit.utils.TypeUtils;
029: import net.sourceforge.sqlunit.utils.XMLUtils;
030:
031: import org.apache.log4j.Logger;
032: import org.jdom.Element;
033:
034: import java.util.ArrayList;
035: import java.util.Iterator;
036: import java.util.List;
037:
038: /**
039: * This class implements the handling of the foreach element. The foreach
040: * element is used to specify loops in the setup, prepare and teardown
041: * elements.
042: * @author Sujit Pal (spal@users.sourceforge.net)
043: * @version $Revision: 1.9 $
044: * @sqlunit.parent name="setup" ref="setup"
045: * @sqlunit.parent name="prepare" ref="prepare"
046: * @sqlunit.parent name="teardown" ref="teardown"
047: * @sqlunit.element name="foreach"
048: * description="The foreach tag allows for simple bulk substitution by
049: * replacing a named parameter in a SQL statement with values from a
050: * supplied list or values generated by incrementing a counter. The
051: * syntax is similar to FOR and FOREACH statements found in most
052: * programming languages."
053: * syntax="((sql)*)"
054: * @sqlunit.attrib name="param"
055: * description="The name of the parameter that will be substituted in the
056: * SQL statement. For example, if the param is id, then all occurences of
057: * ${id} will be substituted by the current value of id."
058: * required="Yes"
059: * @sqlunit.attrib name="values"
060: * description="A comma-separated list of values for the id. A value set
061: * can be specified within each element in the list. The elements of the
062: * set are separated with semi-colons. They are referred to later in the
063: * scope of the foreach tag as value_of_param.index. The index is 0-based."
064: * required="No, either this or (start,stop,(step)) or (start,count,(step))
065: * may be specified"
066: * @sqlunit.attrib name="start"
067: * description="Specifies the starting value of the sequence of values
068: * for the param"
069: * required="No, values can be specified instead. If neither is specified,
070: * then start defaults to 0."
071: * @sqlunit.attrib name="stop"
072: * description="The ending value of the sequence of values for param"
073: * required="No, count can be specified instead."
074: * @sqlunit.attrib name="step"
075: * description="The increment if a sequence is specified with start and stop"
076: * required="No, defaults to 1."
077: * @sqlunit.attrib name="count"
078: * description="The number of entries in the sequence specified by start
079: * and stop."
080: * required="No, stop can be specified instead."
081: * @sqlunit.child name="sql"
082: * description="Contains a single SQL statement with placeholders for param
083: * which will be executed in a loop specified by the foreach attributes. The
084: * replaceable placeholder(s) should be specified as ${id}."
085: * required="Yes"
086: * ref="sql"
087: * @sqlunit.example name="A foreach used to prepare test data"
088: * description="
089: * <prepare>{\n}
090: * {\t}<sql><stmt>delete from foreachtest where 1=1</stmt></sql>{\n}
091: * {\t}<foreach param=\"id\" start=\"0\" stop=\"10\" step=\"1\">{\n}
092: * {\t}{\t}<sql>
093: * {\t}{\t}{\t}<stmt>insert into foreachtest (id,name) values (${id},'name${id}')</stmt>{\n}
094: * {\t}{\t}</sql>{\n}
095: * {\t}</foreach>{\n}
096: * </prepare>{\n}
097: * "
098: */
099: public class ForeachHandler implements IHandler {
100:
101: private static final Logger LOG = Logger
102: .getLogger(ForeachHandler.class);
103:
104: /**
105: * Processes the content of the foreach element, returns a null.
106: * @param elForeach the foreach element to parse.
107: * @return a null object.
108: * @throws Exception if one is thrown by the underlying methods.
109: */
110: public final Object process(final Element elForeach)
111: throws Exception {
112: LOG.debug(">> process(elForeach)");
113: if (elForeach == null) {
114: throw new SQLUnitException(IErrorCodes.ELEMENT_IS_NULL,
115: new String[] { "foreach" });
116: }
117: String param = XMLUtils.getAttributeValue(elForeach, "param");
118: String values = XMLUtils.getAttributeValue(elForeach, "values");
119: boolean isListForm = (values != null);
120: List valuesList = new ArrayList();
121: // build the list of values for our replaceable parameter
122: if (!isListForm) {
123: String start = XMLUtils.getAttributeValue(elForeach,
124: "start");
125: String stop = XMLUtils.getAttributeValue(elForeach, "stop");
126: String step = XMLUtils.getAttributeValue(elForeach, "step");
127: String count = XMLUtils.getAttributeValue(elForeach,
128: "count");
129: // we can have (start,stop|count,(interval))
130: if (start == null) {
131: throw new SQLUnitException(IErrorCodes.ELEMENT_IS_NULL,
132: new String[] { "foreach@start" });
133: }
134: boolean isCountForm = false;
135: if (stop == null && count == null) {
136: return null;
137: } else {
138: isCountForm = (count != null);
139: }
140: int startIndex = TypeUtils.convertStringToInt(start, 0);
141: int stepIndex = TypeUtils.convertStringToInt(step, 1);
142: int stopIndex = 0;
143: if (isCountForm) {
144: int countInt = TypeUtils.convertStringToInt(count, 0);
145: stopIndex = startIndex + ((countInt - 1) * stepIndex);
146: } else {
147: stopIndex = TypeUtils.convertStringToInt(stop,
148: startIndex);
149: }
150: for (int i = startIndex; i <= stopIndex; i += stepIndex) {
151: valuesList.add((new Integer(i)).toString());
152: }
153: } else {
154: String[] tokens = values.split("\\s*,\\s*");
155: for (int i = 0; i < tokens.length; i++) {
156: String[] subtokens;
157: if (tokens[i].indexOf(';') > -1) {
158: // if we have semi-colon separated strings, build array
159: // and stuff into the valuesList
160: subtokens = tokens[i].split("\\s*;\\s*");
161: valuesList.add(subtokens);
162: } else {
163: // else stuff a string
164: valuesList.add(tokens[i].trim());
165: }
166: }
167: }
168: // get list of sql tags and pass to the SqlHandler
169: IHandler handler = HandlerFactory.getInstance("sql");
170: List elSqlList = elForeach.getChildren("sql");
171: Iterator elSqlIter = elSqlList.iterator();
172: while (elSqlIter.hasNext()) {
173: Element elSql = (Element) elSqlIter.next();
174: String sql = XMLUtils.getText(elSql.getChild("stmt"));
175: int numQueries = valuesList.size();
176: for (int sqlIndex = 0; sqlIndex < numQueries; sqlIndex++) {
177: String sqlToRun = sql;
178: // save the param (array) into the symbol table
179: Object valueObj = valuesList.get(sqlIndex);
180: if (valueObj instanceof String[]) {
181: String[] valueArray = (String[]) valueObj;
182: for (int i = 0; i < valueArray.length; i++) {
183: SymbolTable.setValue("${" + param + "." + i
184: + "}", valueArray[i]);
185: sqlToRun = sqlToRun
186: .replaceAll("\\$\\{" + param + "." + i
187: + "\\}", valueArray[i].trim());
188: }
189: } else {
190: SymbolTable.setValue("${" + param + "}",
191: (String) valuesList.get(sqlIndex));
192: sqlToRun = sql.replaceAll("\\$\\{" + param + "\\}",
193: (String) valuesList.get(sqlIndex));
194: }
195: elSql.getChild("stmt").setText(sqlToRun);
196: // delegate to the sql handler
197: handler.process(elSql);
198: // remove the param (array) from the symbol table
199: if (valueObj instanceof String[]) {
200: String[] valueArray = (String[]) valueObj;
201: for (int i = 0; i < valueArray.length; i++) {
202: SymbolTable.removeSymbol("${" + param + "." + i
203: + "}");
204: }
205: } else {
206: SymbolTable.removeSymbol("${" + param + "}");
207: }
208: }
209: }
210: // return null
211: return null;
212: }
213: }
|