001: package com.ibatis.sqlmap.engine.builder.xml;
002:
003: import com.ibatis.common.beans.Probe;
004: import com.ibatis.common.beans.ProbeFactory;
005: import com.ibatis.common.resources.Resources;
006: import com.ibatis.common.xml.NodeletUtils;
007: import com.ibatis.sqlmap.client.SqlMapException;
008: import com.ibatis.sqlmap.engine.cache.CacheModel;
009: import com.ibatis.sqlmap.engine.mapping.parameter.BasicParameterMap;
010: import com.ibatis.sqlmap.engine.mapping.parameter.InlineParameterMapParser;
011: import com.ibatis.sqlmap.engine.mapping.parameter.ParameterMap;
012: import com.ibatis.sqlmap.engine.mapping.result.AutoResultMap;
013: import com.ibatis.sqlmap.engine.mapping.result.BasicResultMap;
014: import com.ibatis.sqlmap.engine.mapping.sql.Sql;
015: import com.ibatis.sqlmap.engine.mapping.sql.SqlText;
016: import com.ibatis.sqlmap.engine.mapping.sql.dynamic.DynamicSql;
017: import com.ibatis.sqlmap.engine.mapping.sql.dynamic.elements.*;
018: import com.ibatis.sqlmap.engine.mapping.sql.simple.SimpleDynamicSql;
019: import com.ibatis.sqlmap.engine.mapping.sql.stat.StaticSql;
020: import com.ibatis.sqlmap.engine.mapping.statement.*;
021: import org.w3c.dom.CharacterData;
022: import org.w3c.dom.Node;
023: import org.w3c.dom.NodeList;
024:
025: import java.sql.ResultSet;
026: import java.util.*;
027:
028: public class SqlStatementParser extends BaseParser {
029:
030: private static final Probe PROBE = ProbeFactory.getProbe();
031:
032: private static final InlineParameterMapParser PARAM_PARSER = new InlineParameterMapParser();
033:
034: public SqlStatementParser(Variables vars) {
035: super (vars);
036: }
037:
038: public MappedStatement parseGeneralStatement(Node node,
039: GeneralStatement statement) {
040: vars.errorCtx.setActivity("parsing a mapped statement");
041:
042: // get attributes
043: Properties attributes = NodeletUtils.parseAttributes(node,
044: vars.currentProperties);
045: String id = attributes.getProperty("id");
046:
047: if (vars.useStatementNamespaces) {
048: id = applyNamespace(id);
049: }
050:
051: String parameterMapName = applyNamespace(attributes
052: .getProperty("parameterMap"));
053: String parameterClassName = attributes
054: .getProperty("parameterClass");
055: String resultMapName = attributes.getProperty("resultMap");
056: String resultClassName = attributes.getProperty("resultClass");
057: String cacheModelName = applyNamespace(attributes
058: .getProperty("cacheModel"));
059: String xmlResultName = attributes.getProperty("xmlResultName");
060: String resultSetType = attributes.getProperty("resultSetType");
061: String fetchSize = attributes.getProperty("fetchSize");
062: String allowRemapping = attributes.getProperty("remapResults");
063: String timeout = attributes.getProperty("timeout");
064:
065: String[] additionalResultMapNames;
066:
067: vars.errorCtx.setObjectId(id + " statement");
068:
069: // get parameter and result maps
070:
071: vars.errorCtx.setMoreInfo("Check the result map name.");
072: //BasicResultMap resultMap = null;
073: if (resultMapName != null) {
074: additionalResultMapNames = getAllButFirstToken(resultMapName);
075: resultMapName = getFirstToken(resultMapName);
076: statement.setResultMap((BasicResultMap) vars.client
077: .getDelegate().getResultMap(
078: applyNamespace(resultMapName)));
079: for (int i = 0; i < additionalResultMapNames.length; i++) {
080: statement
081: .addResultMap((BasicResultMap) vars.client
082: .getDelegate()
083: .getResultMap(
084: applyNamespace(additionalResultMapNames[i])));
085: }
086: }
087:
088: vars.errorCtx.setMoreInfo("Check the parameter map name.");
089:
090: if (parameterMapName != null) {
091: statement.setParameterMap((BasicParameterMap) vars.client
092: .getDelegate().getParameterMap(parameterMapName));
093: }
094:
095: statement.setId(id);
096: statement.setResource(vars.errorCtx.getResource());
097:
098: if (resultSetType != null) {
099: if ("FORWARD_ONLY".equals(resultSetType)) {
100: statement.setResultSetType(new Integer(
101: ResultSet.TYPE_FORWARD_ONLY));
102: } else if ("SCROLL_INSENSITIVE".equals(resultSetType)) {
103: statement.setResultSetType(new Integer(
104: ResultSet.TYPE_SCROLL_INSENSITIVE));
105: } else if ("SCROLL_SENSITIVE".equals(resultSetType)) {
106: statement.setResultSetType(new Integer(
107: ResultSet.TYPE_SCROLL_SENSITIVE));
108: }
109: }
110:
111: if (fetchSize != null) {
112: statement.setFetchSize(new Integer(fetchSize));
113: }
114:
115: // set parameter class either from attribute or from map (make sure to match)
116: ParameterMap parameterMap = statement.getParameterMap();
117: if (parameterMap == null) {
118: try {
119: if (parameterClassName != null) {
120: vars.errorCtx
121: .setMoreInfo("Check the parameter class.");
122: parameterClassName = vars.typeHandlerFactory
123: .resolveAlias(parameterClassName);
124: Class parameterClass = Resources
125: .classForName(parameterClassName);
126: statement.setParameterClass(parameterClass);
127: }
128: } catch (ClassNotFoundException e) {
129: throw new SqlMapException(
130: "Error. Could not set parameter class. Cause: "
131: + e, e);
132: }
133: } else {
134: statement.setParameterClass(parameterMap
135: .getParameterClass());
136: }
137:
138: // process SQL statement, including inline parameter maps
139: vars.errorCtx.setMoreInfo("Check the SQL statement.");
140: processSqlStatement(node, statement);
141:
142: // set up either null result map or automatic result mapping
143: BasicResultMap resultMap = (BasicResultMap) statement
144: .getResultMap();
145: if (resultMap == null && resultClassName == null) {
146: statement.setResultMap(null);
147: } else if (resultMap == null) {
148: String firstResultClass = getFirstToken(resultClassName);
149: resultMap = buildAutoResultMap(allowRemapping, statement,
150: firstResultClass, xmlResultName);
151: statement.setResultMap(resultMap);
152: String[] additionalResultClasses = getAllButFirstToken(resultClassName);
153: for (int i = 0; i < additionalResultClasses.length; i++) {
154: statement.addResultMap(buildAutoResultMap(
155: allowRemapping, statement,
156: additionalResultClasses[i], xmlResultName));
157: }
158:
159: }
160:
161: statement.setTimeout(vars.defaultStatementTimeout);
162: if (timeout != null) {
163: try {
164: statement.setTimeout(Integer.valueOf(timeout));
165: } catch (NumberFormatException e) {
166: throw new SqlMapException(
167: "Specified timeout value for statement "
168: + statement.getId()
169: + " is not a valid integer");
170: }
171: }
172:
173: vars.errorCtx.setMoreInfo(null);
174: vars.errorCtx.setObjectId(null);
175:
176: statement.setSqlMapClient(vars.client);
177: if (cacheModelName != null && cacheModelName.length() > 0
178: && vars.client.getDelegate().isCacheModelsEnabled()) {
179: CacheModel cacheModel = vars.client.getDelegate()
180: .getCacheModel(cacheModelName);
181: return new CachingStatement(statement, cacheModel);
182: } else {
183: return statement;
184: }
185:
186: }
187:
188: private BasicResultMap buildAutoResultMap(String allowRemapping,
189: GeneralStatement statement, String firstResultClass,
190: String xmlResultName) {
191: BasicResultMap resultMap;
192: resultMap = new AutoResultMap(vars.client.getDelegate(), "true"
193: .equals(allowRemapping));
194: resultMap.setId(statement.getId() + "-AutoResultMap");
195: resultMap.setResultClass(resolveClass(firstResultClass));
196: resultMap.setXmlName(xmlResultName);
197: resultMap.setResource(statement.getResource());
198: return resultMap;
199: }
200:
201: private Class resolveClass(String resultClassName) {
202: try {
203: if (resultClassName != null) {
204: vars.errorCtx.setMoreInfo("Check the result class.");
205: return Resources.classForName(vars.typeHandlerFactory
206: .resolveAlias(resultClassName));
207: } else {
208: return null;
209: }
210: } catch (ClassNotFoundException e) {
211: throw new SqlMapException(
212: "Error. Could not set result class. Cause: " + e,
213: e);
214: }
215: }
216:
217: private String getFirstToken(String s) {
218: return new StringTokenizer(s, ", ", false).nextToken();
219: }
220:
221: private String[] getAllButFirstToken(String s) {
222: List strings = new ArrayList();
223: StringTokenizer parser = new StringTokenizer(s, ", ", false);
224: parser.nextToken();
225: while (parser.hasMoreTokens()) {
226: strings.add(parser.nextToken());
227: }
228: return (String[]) strings.toArray(new String[strings.size()]);
229: }
230:
231: private void processSqlStatement(Node n, GeneralStatement statement) {
232: vars.errorCtx.setActivity("processing an SQL statement");
233:
234: boolean isDynamic = false;
235: DynamicSql dynamic = new DynamicSql(vars.client.getDelegate());
236: StringBuffer sqlBuffer = new StringBuffer();
237:
238: isDynamic = parseDynamicTags(n, dynamic, sqlBuffer, isDynamic,
239: false);
240: if (statement instanceof InsertStatement) {
241: InsertStatement insertStatement = ((InsertStatement) statement);
242: SelectKeyStatement selectKeyStatement = findAndParseSelectKeyStatement(
243: n, statement);
244: insertStatement.setSelectKeyStatement(selectKeyStatement);
245: }
246:
247: String sqlStatement = sqlBuffer.toString();
248: if (isDynamic) {
249: statement.setSql(dynamic);
250: } else {
251: applyInlineParameterMap(statement, sqlStatement);
252: }
253:
254: }
255:
256: private boolean parseDynamicTags(Node node, DynamicParent dynamic,
257: StringBuffer sqlBuffer, boolean isDynamic,
258: boolean postParseRequired) {
259: vars.errorCtx.setActivity("parsing dynamic SQL tags");
260:
261: NodeList children = node.getChildNodes();
262: for (int i = 0; i < children.getLength(); i++) {
263: Node child = children.item(i);
264: String nodeName = child.getNodeName();
265: if (child.getNodeType() == Node.CDATA_SECTION_NODE
266: || child.getNodeType() == Node.TEXT_NODE) {
267:
268: String data = ((CharacterData) child).getData();
269: data = NodeletUtils.parsePropertyTokens(data,
270: vars.properties);
271:
272: SqlText sqlText;
273:
274: if (postParseRequired) {
275: sqlText = new SqlText();
276: sqlText.setPostParseRequired(postParseRequired);
277: sqlText.setText(data);
278: } else {
279: sqlText = PARAM_PARSER.parseInlineParameterMap(
280: vars.client.getDelegate()
281: .getTypeHandlerFactory(), data,
282: null);
283: sqlText.setPostParseRequired(postParseRequired);
284: }
285:
286: dynamic.addChild(sqlText);
287:
288: sqlBuffer.append(data);
289: } else if ("include".equals(nodeName)) {
290: Properties attributes = NodeletUtils.parseAttributes(
291: child, vars.properties);
292: String refid = (String) attributes.get("refid");
293: Node includeNode = (Node) vars.sqlIncludes.get(refid);
294: if (includeNode == null) {
295: String nsrefid = applyNamespace(refid);
296: includeNode = (Node) vars.sqlIncludes.get(nsrefid);
297: if (includeNode == null) {
298: throw new RuntimeException(
299: "Could not find SQL statement to include with refid '"
300: + refid + "'");
301: }
302: }
303: isDynamic = parseDynamicTags(includeNode, dynamic,
304: sqlBuffer, isDynamic, false);
305: } else {
306: vars.errorCtx.setMoreInfo("Check the dynamic tags.");
307:
308: SqlTagHandler handler = SqlTagHandlerFactory
309: .getSqlTagHandler(nodeName);
310: if (handler != null) {
311: isDynamic = true;
312:
313: SqlTag tag = new SqlTag();
314: tag.setName(nodeName);
315: tag.setHandler(handler);
316:
317: Properties attributes = NodeletUtils
318: .parseAttributes(child, vars.properties);
319:
320: tag.setPrependAttr(attributes
321: .getProperty("prepend"));
322: tag.setPropertyAttr(attributes
323: .getProperty("property"));
324: tag.setRemoveFirstPrepend(attributes
325: .getProperty("removeFirstPrepend"));
326:
327: tag.setOpenAttr(attributes.getProperty("open"));
328: tag.setCloseAttr(attributes.getProperty("close"));
329:
330: tag.setComparePropertyAttr(attributes
331: .getProperty("compareProperty"));
332: tag.setCompareValueAttr(attributes
333: .getProperty("compareValue"));
334: tag.setConjunctionAttr(attributes
335: .getProperty("conjunction"));
336:
337: // an iterate ancestor requires a post parse
338:
339: if (dynamic instanceof SqlTag) {
340: SqlTag parentSqlTag = (SqlTag) dynamic;
341: if (parentSqlTag.isPostParseRequired()
342: || tag.getHandler() instanceof IterateTagHandler) {
343: tag.setPostParseRequired(true);
344: }
345: } else if (dynamic instanceof DynamicSql) {
346: if (tag.getHandler() instanceof IterateTagHandler) {
347: tag.setPostParseRequired(true);
348: }
349: }
350:
351: dynamic.addChild(tag);
352:
353: if (child.hasChildNodes()) {
354: isDynamic = parseDynamicTags(child, tag,
355: sqlBuffer, isDynamic, tag
356: .isPostParseRequired());
357: }
358: }
359: }
360: }
361: vars.errorCtx.setMoreInfo(null);
362: return isDynamic;
363: }
364:
365: private SelectKeyStatement findAndParseSelectKeyStatement(Node n,
366: GeneralStatement insertStatement) {
367: vars.errorCtx.setActivity("parsing select key tags");
368:
369: SelectKeyStatement selectKeyStatement = null;
370:
371: boolean foundTextFirst = false;
372: boolean hasType = false;
373:
374: NodeList children = n.getChildNodes();
375: for (int i = 0; i < children.getLength(); i++) {
376: Node child = children.item(i);
377: if (child.getNodeType() == Node.CDATA_SECTION_NODE
378: || child.getNodeType() == Node.TEXT_NODE) {
379: String data = ((CharacterData) child).getData();
380: if (data.trim().length() > 0) {
381: foundTextFirst = true;
382: }
383: } else if (child.getNodeType() == Node.ELEMENT_NODE
384: && "selectKey".equals(child.getNodeName())) {
385: selectKeyStatement = new SelectKeyStatement();
386: hasType = parseSelectKey(child, insertStatement,
387: selectKeyStatement);
388: break;
389: }
390: }
391: if (selectKeyStatement != null && !hasType) {
392: selectKeyStatement.setAfter(foundTextFirst);
393: }
394: vars.errorCtx.setMoreInfo(null);
395: return selectKeyStatement;
396: }
397:
398: /**
399: *
400: * @param node
401: * @param insertStatement
402: * @param selectKeyStatement
403: * @return true is the type (pre or post) was set from the configuration
404: * false if the type (pre or post) should be inferred from the position
405: * of the element in the text (the legacy behavior)
406: */
407: private boolean parseSelectKey(Node node,
408: GeneralStatement insertStatement,
409: SelectKeyStatement selectKeyStatement) {
410: vars.errorCtx.setActivity("parsing a select key");
411:
412: // get attributes
413: Properties attributes = NodeletUtils.parseAttributes(node,
414: vars.properties);
415: String keyPropName = attributes.getProperty("keyProperty");
416: String resultClassName = attributes.getProperty("resultClass");
417: resultClassName = vars.typeHandlerFactory
418: .resolveAlias(resultClassName);
419: Class resultClass = null;
420:
421: // get parameter and result maps
422: selectKeyStatement.setSqlMapClient(vars.client);
423:
424: selectKeyStatement
425: .setId(insertStatement.getId() + "-SelectKey");
426: selectKeyStatement.setResource(vars.errorCtx.getResource());
427: selectKeyStatement.setKeyProperty(keyPropName);
428:
429: // process the type (pre or post) attribute
430: boolean hasType;
431: String type = attributes.getProperty("type");
432: if (type == null) {
433: hasType = false;
434: } else {
435: hasType = true;
436: selectKeyStatement.setAfter("post".equals(type));
437: }
438:
439: try {
440: if (resultClassName != null) {
441: vars.errorCtx
442: .setMoreInfo("Check the select key result class.");
443: resultClass = Resources.classForName(resultClassName);
444: } else {
445: Class parameterClass = insertStatement
446: .getParameterClass();
447: if (keyPropName != null && parameterClass != null) {
448: resultClass = PROBE.getPropertyTypeForSetter(
449: parameterClass, selectKeyStatement
450: .getKeyProperty());
451: }
452: }
453: } catch (ClassNotFoundException e) {
454: throw new SqlMapException(
455: "Error. Could not set result class. Cause: " + e,
456: e);
457: }
458:
459: if (resultClass == null) {
460: resultClass = Object.class;
461: }
462:
463: // process SQL statement, including inline parameter maps
464: vars.errorCtx
465: .setMoreInfo("Check the select key SQL statement.");
466: processSqlStatement(node, selectKeyStatement);
467:
468: BasicResultMap resultMap;
469: resultMap = new AutoResultMap(vars.client.getDelegate(), false);
470: resultMap.setId(selectKeyStatement.getId() + "-AutoResultMap");
471: resultMap.setResultClass(resultClass);
472: resultMap.setResource(selectKeyStatement.getResource());
473: selectKeyStatement.setResultMap(resultMap);
474:
475: vars.errorCtx.setMoreInfo(null);
476: return hasType;
477: }
478:
479: private void applyInlineParameterMap(GeneralStatement statement,
480: String sqlStatement) {
481: String newSql = sqlStatement;
482:
483: vars.errorCtx.setActivity("building an inline parameter map");
484:
485: ParameterMap parameterMap = statement.getParameterMap();
486:
487: vars.errorCtx.setMoreInfo("Check the inline parameters.");
488: if (parameterMap == null) {
489:
490: BasicParameterMap map;
491: map = new BasicParameterMap(vars.client.getDelegate());
492:
493: map.setId(statement.getId() + "-InlineParameterMap");
494: map.setParameterClass(statement.getParameterClass());
495: map.setResource(statement.getResource());
496: statement.setParameterMap(map);
497:
498: SqlText sqlText = PARAM_PARSER.parseInlineParameterMap(
499: vars.client.getDelegate().getTypeHandlerFactory(),
500: newSql, statement.getParameterClass());
501: newSql = sqlText.getText();
502: List mappingList = Arrays.asList(sqlText
503: .getParameterMappings());
504:
505: map.setParameterMappingList(mappingList);
506:
507: }
508:
509: Sql sql = null;
510: if (SimpleDynamicSql.isSimpleDynamicSql(newSql)) {
511: sql = new SimpleDynamicSql(vars.client.getDelegate(),
512: newSql);
513: } else {
514: sql = new StaticSql(newSql);
515: }
516: statement.setSql(sql);
517:
518: }
519:
520: }
|