001: /*
002: * ============================================================================
003: * GNU Lesser General Public License
004: * ============================================================================
005: *
006: * JasperReports - Free Java report-generating library.
007: * Copyright (C) 2001-2006 JasperSoft Corporation http://www.jaspersoft.com
008: *
009: * This library is free software; you can redistribute it and/or
010: * modify it under the terms of the GNU Lesser General Public
011: * License as published by the Free Software Foundation; either
012: * version 2.1 of the License, or (at your option) any later version.
013: *
014: * This library is distributed in the hope that it will be useful,
015: * but WITHOUT ANY WARRANTY; without even the implied warranty of
016: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
017: * Lesser General Public License for more details.
018: *
019: * You should have received a copy of the GNU Lesser General Public
020: * License along with this library; if not, write to the Free Software
021: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
022: *
023: * JasperSoft Corporation
024: * 303 Second Street, Suite 450 North
025: * San Francisco, CA 94107
026: * http://www.jaspersoft.com
027: */
028: package net.sf.jasperreports.engine.query;
029:
030: import java.util.ArrayList;
031: import java.util.HashMap;
032: import java.util.HashSet;
033: import java.util.Iterator;
034: import java.util.List;
035: import java.util.Map;
036: import java.util.Set;
037:
038: import net.sf.jasperreports.engine.JRDataset;
039: import net.sf.jasperreports.engine.JRParameter;
040: import net.sf.jasperreports.engine.JRQuery;
041: import net.sf.jasperreports.engine.JRQueryChunk;
042: import net.sf.jasperreports.engine.JRRuntimeException;
043: import net.sf.jasperreports.engine.JRValueParameter;
044: import net.sf.jasperreports.engine.fill.JRFillParameter;
045: import net.sf.jasperreports.engine.util.JRQueryChunkHandler;
046: import net.sf.jasperreports.engine.util.JRQueryParser;
047:
048: /**
049: * Base abstract query executer.
050: *
051: * @author Lucian Chirita (lucianc@users.sourceforge.net)
052: * @version $Id: JRAbstractQueryExecuter.java 1737 2007-06-04 15:18:39Z teodord $
053: */
054: public abstract class JRAbstractQueryExecuter implements
055: JRQueryExecuter {
056:
057: protected static final int CLAUSE_POSITION_ID = 0;
058:
059: /**
060: * A parameter present in the query.
061: */
062: protected static class QueryParameter {
063: protected static final int COUNT_SINGLE = -1;
064:
065: private final String name;
066: private final int count;
067:
068: public QueryParameter(String name) {
069: this (name, COUNT_SINGLE);
070: }
071:
072: public QueryParameter(String name, int count) {
073: this .name = name;
074: this .count = count;
075: }
076:
077: /**
078: * Decides whether the parameter has multiple values.
079: *
080: * @return whether the parameter has multiple values.
081: */
082: public boolean isMulti() {
083: return count != COUNT_SINGLE;
084: }
085:
086: /**
087: * Returns the number of parameter values.
088: *
089: * @return the number of parameter values
090: * @see #isMulti()
091: */
092: public int getCount() {
093: return count;
094: }
095:
096: /**
097: * Returns the name of the report parameter.
098: *
099: * @return the name of the report parameter
100: */
101: public String getName() {
102: return name;
103: }
104: }
105:
106: /**
107: * Clause function registry.
108: */
109: protected final Map clauseFunctions = new HashMap();
110:
111: protected final JRDataset dataset;
112: private final Map parametersMap;
113:
114: private String queryString;
115:
116: /**
117: * List of {@link QueryParameter query parameters}.
118: */
119: private List queryParameters;
120:
121: private Set parameterClauseStack;
122:
123: protected JRAbstractQueryExecuter(JRDataset dataset,
124: Map parametersMap) {
125: this .dataset = dataset;
126: this .parametersMap = parametersMap;
127:
128: queryString = "";
129: queryParameters = new ArrayList();
130: }
131:
132: /**
133: * Registers a clause function.
134: *
135: * @param id the function ID
136: * @param function the function
137: */
138: protected void registerClauseFunction(String id,
139: JRClauseFunction function) {
140: clauseFunctions.put(id, function);
141: }
142:
143: /**
144: * Unregisters a clause function.
145: *
146: * @param id the function ID
147: */
148: protected void unregisterClauseFunction(String id) {
149: clauseFunctions.remove(id);
150: }
151:
152: /**
153: * Resolves a clause function ID to a function instance.
154: *
155: * @param id the function ID
156: * @return the clause function registered for the ID
157: * @throws JRRuntimeException if no function for the ID is found
158: */
159: protected JRClauseFunction resolveFunction(String id) {
160: JRClauseFunction function = (JRClauseFunction) clauseFunctions
161: .get(id);
162: if (function == null) {
163: throw new JRRuntimeException("No clause function for id "
164: + id + " found");
165: }
166: return function;
167: }
168:
169: /**
170: * Parses the query and replaces the parameter clauses by the paramter values and
171: * the parameters by the return value of {@link #getParameterReplacement(String) getParameterReplacement}.
172: *
173: */
174: protected void parseQuery() {
175: parameterClauseStack = new HashSet();
176:
177: JRQuery query = dataset.getQuery();
178:
179: if (query != null) {
180: JRQueryChunk[] chunks = query.getChunks();
181: if (chunks != null && chunks.length > 0) {
182: StringBuffer sbuffer = new StringBuffer();
183: for (int i = 0; i < chunks.length; i++) {
184: JRQueryChunk chunk = chunks[i];
185: appendQueryChunk(sbuffer, chunk);
186: }
187:
188: queryString = sbuffer.toString();
189: }
190: }
191: }
192:
193: protected void appendQueryChunk(StringBuffer sbuffer,
194: JRQueryChunk chunk) {
195: switch (chunk.getType()) {
196: case JRQueryChunk.TYPE_PARAMETER_CLAUSE: {
197: appendParameterClauseChunk(sbuffer, chunk.getText());
198: break;
199: }
200: case JRQueryChunk.TYPE_PARAMETER: {
201: appendParameterChunk(sbuffer, chunk.getText());
202: break;
203: }
204: case JRQueryChunk.TYPE_CLAUSE_TOKENS: {
205: appendClauseChunk(sbuffer, chunk.getTokens());
206: break;
207: }
208: case JRQueryChunk.TYPE_TEXT:
209: default: {
210: appendTextChunk(sbuffer, chunk.getText());
211: break;
212: }
213: }
214: }
215:
216: protected void appendTextChunk(StringBuffer sbuffer, String text) {
217: sbuffer.append(text);
218: }
219:
220: protected void appendParameterChunk(StringBuffer sbuffer,
221: String chunkText) {
222: String parameterName = chunkText;
223: checkParameter(parameterName);
224:
225: sbuffer.append(getParameterReplacement(parameterName));
226: addQueryParameter(chunkText);
227: }
228:
229: /**
230: * Records a query parameter.
231: *
232: * @param parameterName the parameter name
233: * @see #getCollectedParameters()
234: */
235: protected void addQueryParameter(String parameterName) {
236: QueryParameter param = new QueryParameter(parameterName);
237: queryParameters.add(param);
238: }
239:
240: /**
241: * Records a multi-valued query parameter.
242: *
243: * @param parameterName the parameter name
244: * @param count the value count
245: * @see #getCollectedParameters()
246: * @see QueryParameter#isMulti()
247: */
248: protected void addQueryMultiParameters(String parameterName,
249: int count) {
250: QueryParameter param = new QueryParameter(parameterName, count);
251: queryParameters.add(param);
252: }
253:
254: protected void appendParameterClauseChunk(
255: final StringBuffer sbuffer, String chunkText) {
256: String parameterName = chunkText;
257: checkParameter(parameterName);
258:
259: if (!parameterClauseStack.add(parameterName)) {
260: throw new JRRuntimeException(
261: "The query contains circularly nested parameter clauses starting with "
262: + parameterName);
263: }
264:
265: try {
266: Object parameterValue = getParameterValue(parameterName);
267: String clauseText = String.valueOf(parameterValue);
268: JRQueryChunkHandler nestedChunkHandler = new JRQueryChunkHandler() {
269: public void handleParameterChunk(String text) {
270: appendParameterChunk(sbuffer, text);
271: }
272:
273: public void handleParameterClauseChunk(String text) {
274: appendParameterClauseChunk(sbuffer, text);
275: }
276:
277: public void handleTextChunk(String text) {
278: appendTextChunk(sbuffer, text);
279: }
280:
281: public void handleClauseChunk(String[] tokens) {
282: appendClauseChunk(sbuffer, tokens);
283: }
284: };
285: JRQueryParser.instance().parse(clauseText,
286: nestedChunkHandler);
287: } finally {
288: parameterClauseStack.remove(parameterName);
289: }
290: }
291:
292: /**
293: * Handles a {@link JRQueryChunk#TYPE_CLAUSE_TOKENS clause query chunk}.
294: * <p>
295: * The default implementation considers the first token as a
296: * {@link JRClauseFunction clause function} ID and delegates the call to the
297: * function.
298: * </p>
299: * <p>
300: * Extending query executers can override this to implement custom query clause handling.
301: * </p>
302: *
303: * @param sbuffer the query text buffer
304: * @param clauseTokens clause tokens
305: * @see #registerClauseFunction(String, JRClauseFunction)
306: * @throws JRRuntimeException if there is no first token or no clause function is found for the ID
307: */
308: protected void appendClauseChunk(final StringBuffer sbuffer,
309: String[] clauseTokens) {
310: JRClauseTokens tokens = new JRClauseTokens(clauseTokens);
311: String id = tokens.getToken(CLAUSE_POSITION_ID);
312: if (id == null) {
313: throw new JRRuntimeException(
314: "Query clause ID/first token missing");
315: }
316:
317: JRClauseFunction function = resolveFunction(id);
318: applyClause(function, tokens, sbuffer);
319: }
320:
321: protected void applyClause(JRClauseFunction function,
322: JRClauseTokens tokens, final StringBuffer sbuffer) {
323: function.apply(tokens, new JRQueryClauseContext() {
324: public void addQueryMultiParameters(String parameterName,
325: int count) {
326: JRAbstractQueryExecuter.this .addQueryMultiParameters(
327: parameterName, count);
328: }
329:
330: public void addQueryParameter(String parameterName) {
331: JRAbstractQueryExecuter.this
332: .addQueryParameter(parameterName);
333: }
334:
335: public JRValueParameter getValueParameter(
336: String parameterName) {
337: return JRAbstractQueryExecuter.this
338: .getValueParameter(parameterName);
339: }
340:
341: public StringBuffer queryBuffer() {
342: return sbuffer;
343: }
344: });
345: }
346:
347: /**
348: * Returns the parsed query string with the paramter clauses replaced by the paramter values and
349: * the parameters replaced by {@link #getParameterReplacement(String) getParameterReplacement}.
350: *
351: * @return the parsed query string
352: */
353: protected String getQueryString() {
354: return queryString;
355: }
356:
357: /**
358: * Returns the list of parameter names in the order in which they appear in the query.
359: *
360: * @return the list of parameter names
361: */
362: protected List getCollectedParameterNames() {
363: List parameterNames = new ArrayList(queryParameters.size());
364: for (Iterator it = queryParameters.iterator(); it.hasNext();) {
365: QueryParameter param = (QueryParameter) it.next();
366: parameterNames.add(param.getName());
367: }
368: return parameterNames;
369: }
370:
371: /**
372: * Returns the list of {@link QueryParameter query parameters} in the order in which they appear in the query.
373: *
374: * @return the list of query parameters
375: */
376: protected List getCollectedParameters() {
377: return queryParameters;
378: }
379:
380: /**
381: * Returns the value of a fill paramter.
382: * @param parameterName the paramter name
383: * @param ignoreMissing if <code>true</code>, the method will return null for non existing parameters;
384: * otherwise, an exception will be thrown if the parameter does not exist
385: * @return the parameter value
386: */
387: protected Object getParameterValue(String parameterName,
388: boolean ignoreMissing) {
389: if (ignoreMissing) {
390: JRValueParameter parameter = getValueParameter(
391: JRParameter.REPORT_PARAMETERS_MAP, false);
392: return ((Map) parameter.getValue()).get(parameterName);
393: }
394:
395: JRValueParameter parameter = getValueParameter(parameterName,
396: ignoreMissing);
397: return parameter == null ? null : parameter.getValue();
398: }
399:
400: /**
401: * Returns the value of a fill paramter.
402: * @param parameterName the paramter name
403: * @return the parameter value
404: */
405: protected Object getParameterValue(String parameterName) {
406: return getParameterValue(parameterName, false);
407: }
408:
409: /**
410: * Return a fill parameter from the paramter map.
411: *
412: * @param parameterName the paramter name
413: * @return the parameter
414: * @deprecated {@link #getValueParameter(String) getValueParameter(String)} should be used instead
415: */
416: protected JRFillParameter getParameter(String parameterName) {
417: JRFillParameter parameter = (JRFillParameter) parametersMap
418: .get(parameterName);
419:
420: if (parameter == null) {
421: throw new JRRuntimeException("Parameter \"" + parameterName
422: + "\" does not exist.");
423: }
424:
425: return parameter;
426: }
427:
428: protected void checkParameter(String parameterName) {
429: if (!parametersMap.containsKey(parameterName)) {
430: throw new JRRuntimeException("Parameter \"" + parameterName
431: + "\" does not exist.");
432: }
433: }
434:
435: /**
436: * Return a value parameter from the paramters map.
437: *
438: * @param parameterName the paramter name
439: * @param ignoreMissing if <code>true</code>, the method will return null for non existing parameters;
440: * otherwise, an exception will be thrown if the parameter does not exist
441: * @return the parameter
442: */
443: protected JRValueParameter getValueParameter(String parameterName,
444: boolean ignoreMissing) {
445: JRValueParameter parameter = (JRValueParameter) parametersMap
446: .get(parameterName);
447:
448: if (parameter == null && !ignoreMissing) {
449: throw new JRRuntimeException("Parameter \"" + parameterName
450: + "\" does not exist.");
451: }
452:
453: return parameter;
454: }
455:
456: /**
457: * Return a value parameter from the parameters map.
458: *
459: * @param parameterName the parameter name
460: * @return the parameter
461: */
462: protected JRValueParameter getValueParameter(String parameterName) {
463: return getValueParameter(parameterName, false);
464: }
465:
466: /**
467: * Returns the replacement text for a query paramter.
468: *
469: * @param parameterName the paramter name
470: * @return the replacement text
471: * @see JRQueryChunk#TYPE_PARAMETER
472: */
473: protected abstract String getParameterReplacement(
474: String parameterName);
475: }
|