View Javadoc
1   /*
2    *
3    * The DbUnit Database Testing Framework
4    * Copyright (C)2002-2004, DbUnit.org
5    *
6    * This library is free software; you can redistribute it and/or
7    * modify it under the terms of the GNU Lesser General Public
8    * License as published by the Free Software Foundation; either
9    * version 2.1 of the License, or (at your option) any later version.
10   *
11   * This library is distributed in the hope that it will be useful,
12   * but WITHOUT ANY WARRANTY; without even the implied warranty of
13   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14   * Lesser General Public License for more details.
15   *
16   * You should have received a copy of the GNU Lesser General Public
17   * License along with this library; if not, write to the Free Software
18   * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19   *
20   */
21  
22  package org.dbunit.ant;
23  
24  import java.sql.SQLException;
25  import java.util.ArrayList;
26  import java.util.Iterator;
27  import java.util.List;
28  
29  import org.apache.tools.ant.BuildException;
30  import org.apache.tools.ant.ProjectComponent;
31  import org.apache.tools.ant.types.FilterSet;
32  import org.dbunit.database.AmbiguousTableNameException;
33  import org.dbunit.database.IDatabaseConnection;
34  import org.dbunit.database.QueryDataSet;
35  import org.slf4j.Logger;
36  import org.slf4j.LoggerFactory;
37  
38  /**
39   * This element is a container for Queries. It facilitates reuse
40   * through references. Using Ant 1.6 and greater, references can be
41   * defined in a single build file and <i>import</i>ed into many others.
42   * An example of where this is useful follows:
43   * <p>
44   * In our database
45   * we have INDIVIDUALS which must have an associated NAME_INFO and
46   * at least one IND_ADDRESS. The developer creating a dataset for
47   * his/her tests probably won't know all the details of what relationships are
48   * expected, and if he did, its an error prone and repetitive task
49   * to create the correct SQL for entities in each dataset.
50   * Missing a related table, not only creates invalid data for your tests,
51   * but also is likely to cause DBUnit setUp() failures from foreign key
52   * constraint violation errors.
53   * (example: If a previous test had inserted INDIVIDUALS
54   * and NAME_INFO and my test tries to delete only the INDIVIDUALS, the
55   * NAME_INFO.IND_ID constraint would be violated)
56   * <p>
57   * <p>
58   * Each queryset is internally converted to a <code>QueryDataSet</code> and then
59   * combined using a <code>CompositeDataSet</code>. This means that you can use
60   * more than one <code>query</code> element for any given table provided they
61   * are nested within separate <code>queryset</code>s.
62   * <p>
63   * Usage:
64   *
65   * <pre>
66   * &lt;!-- ======== Define the reusable reference ========== --&gt;
67   *
68   * &lt;queryset id="individuals"&gt;
69   *    &lt;query name="INDIVIDUALS" sql="
70   *      SELECT * FROM INDIVIDUALS WHERE IND_ID IN (@subQuery@)"/&gt;
71   *
72   *    &lt;query name="NAME_INFO" sql="
73   *      SELECT B.* FROM INDIVIDUALS A, NAME_INFO B
74   *      WHERE A.IND_ID IN (@subQuery@)
75   *      AND B.IND_ID = A.IND_ID"/&gt;
76   *
77   *    &lt;query name="IND_ADDRESSES" sql="
78   *      SELECT B.* FROM INDIVIDUALS A, IND_ADDRESSES B
79   *      WHERE A.IND_ID IN (@subQuery@)
80   *      AND B.IND_ID = A.IND_ID"/&gt;
81   * &lt;/queryset&gt;
82   *
83   * &lt;!-- ========= Use the reference ====================== --&gt;
84   *
85   * &lt;dbunit driver="${jdbcDriver}"
86   *     url="${jdbcURL}" userid="${jdbcUser}" password="${jdbcPassword}"&gt;
87   *   &lt;export dest="${dest}"&gt;
88   *   &lt;queryset refid="individuals"&gt;
89   *      &lt;filterset&gt;
90   *        &lt;filter token="subQuery" value="
91   *          SELECT IND_ID FROM INDIVIDUALS WHERE USER_NAME = 'UNKNOWN'"/&gt;
92   *      &lt;/filterset&gt;
93   *   &lt;/queryset&gt;
94   *
95   *   &lt;/export&gt;
96   * &lt;/dbunit&gt;
97   *
98   * </pre>
99   *
100  * @author Lenny Marks lenny@aps.org
101  * @author Last changed by: $Author$
102  * @version $Revision$ $Date$
103  * @since 2.2.0 (Sep. 13 2004)
104  */
105 public class QuerySet extends ProjectComponent
106 {
107 
108     /**
109      * Logger for this class
110      */
111     private static final Logger logger = LoggerFactory.getLogger(QuerySet.class);
112 
113     private String id;
114     private String refid;
115     private List queries = new ArrayList();
116     private List filterSets = new ArrayList();
117 
118     private static String ERR_MSG =
119             "Cannot specify 'id' and 'refid' attributes together in queryset.";
120 
121     public QuerySet() {
122         super();
123     }
124 
125     public void addQuery(final Query query) {
126         logger.debug("addQuery(query={}) - start", query);
127 
128         queries.add(query);
129     }
130 
131     public void addFilterSet(final FilterSet filterSet) {
132         logger.debug("addFilterSet(filterSet={}) - start", filterSet);
133 
134         filterSets.add(filterSet);
135     }
136 
137     public String getId() {
138         return id;
139     }
140 
141     public String getRefid() {
142         return refid;
143     }
144 
145     public void setId(final String string) throws BuildException {
146         logger.debug("setId(string={}) - start", string);
147 
148         if(refid != null) throw new BuildException(ERR_MSG);
149         id = string;
150     }
151 
152     public void setRefid(final String string) throws BuildException {
153         logger.debug("setRefid(string={}) - start", string);
154 
155         if(id != null) throw new BuildException(ERR_MSG);
156         refid = string;
157     }
158 
159     public List getQueries() {
160         logger.debug("getQueries() - start");
161 
162         final Iterator i = queries.iterator();
163         while(i.hasNext()) {
164             final Query query = (Query)i.next();
165             replaceTokens(query);
166         }
167 
168         return queries;
169 
170     }
171 
172     private void replaceTokens(final Query query) {
173         logger.debug("replaceTokens(query={}) - start", query);
174 
175         final Iterator i = filterSets.iterator();
176         while(i.hasNext()) {
177             final FilterSet filterSet = (FilterSet)i.next();
178             query.setSql(filterSet.replaceTokens(query.getSql()));
179         }
180     }
181 
182 
183     public void copyQueriesFrom(final QuerySet referenced) {
184         logger.debug("copyQueriesFrom(referenced={}) - start", referenced);
185 
186         final Iterator i = referenced.queries.iterator();
187         while(i.hasNext()) {
188             addQuery((Query)i.next());
189         }
190     }
191 
192     public QueryDataSet getQueryDataSet(final IDatabaseConnection connection)
193             throws SQLException, AmbiguousTableNameException
194     {
195         logger.debug("getQueryDataSet(connection={}) - start", connection);
196 
197         //incorporate queries from referenced query-set
198         final String refid = getRefid();
199         if(refid != null) {
200             final QuerySet referenced = (QuerySet)getProject().getReference(refid);
201             copyQueriesFrom(referenced);
202         }
203 
204         final QueryDataSet partialDataSet = new QueryDataSet(connection);
205 
206         final Iterator queriesIter = getQueries().iterator();
207         while(queriesIter.hasNext()) {
208             final Query query = (Query)queriesIter.next();
209             partialDataSet.addTable(query.getName(), query.getSql());
210         }
211 
212         return partialDataSet;
213 
214     }
215 
216 }