1 package com.ibatis.sqlmap.engine.builder.xml; 2 3 import com.ibatis.common.resources.Resources; 4 import com.ibatis.common.xml.Nodelet; 5 import com.ibatis.common.xml.NodeletException; 6 import com.ibatis.common.xml.NodeletParser; 7 import com.ibatis.common.xml.NodeletUtils; 8 import com.ibatis.common.exception.NestedRuntimeException; 9 import com.ibatis.sqlmap.client.extensions.TypeHandlerCallback; 10 import com.ibatis.sqlmap.engine.cache.CacheModel; 11 import com.ibatis.sqlmap.engine.mapping.parameter.BasicParameterMap; 12 import com.ibatis.sqlmap.engine.mapping.parameter.BasicParameterMapping; 13 import com.ibatis.sqlmap.engine.mapping.result; 14 import com.ibatis.sqlmap.engine.mapping.statement; 15 import com.ibatis.sqlmap.engine.type.CustomTypeHandler; 16 import com.ibatis.sqlmap.engine.type.TypeHandler; 17 import org.w3c.dom.Node; 18 19 import java.io.Reader; 20 import java.util.ArrayList; 21 import java.util.Properties; 22 import java.util.StringTokenizer; 23 import java.util.Iterator; 24 25 public class SqlMapParser extends BaseParser { 26 27 private final NodeletParser parser = new NodeletParser(); 28 29 public SqlMapParser(Variables vars) { 30 super(vars); 31 parser.setValidation(true); 32 parser.setEntityResolver(new SqlMapClasspathEntityResolver()); 33 34 addSqlMapNodelets(); 35 addSqlNodelets(); 36 addTypeAliasNodelets(); 37 addCacheModelNodelets(); 38 addParameterMapNodelets(); 39 addResultMapNodelets(); 40 addStatementNodelets(); 41 42 } 43 44 public void parse(Reader reader) throws NodeletException { 45 parser.parse(reader); 46 } 47 48 private void addSqlMapNodelets() { 49 parser.addNodelet("/sqlMap", new Nodelet() { 50 public void process(Node node) throws Exception { 51 Properties attributes = NodeletUtils.parseAttributes(node, vars.properties); 52 vars.currentNamespace = attributes.getProperty("namespace"); 53 } 54 }); 55 parser.addNodelet("/sqlMap/end()", new Nodelet() { 56 public void process(Node node) throws Exception { 57 Iterator names = vars.delegate.getResultMapNames(); 58 while (names.hasNext()) { 59 String name = (String)names.next(); 60 ResultMap rm = vars.delegate.getResultMap(name); 61 Discriminator disc = rm.getDiscriminator(); 62 if (disc != null) { 63 disc.bindSubMaps(); 64 } 65 } 66 } 67 }); 68 } 69 70 private void addSqlNodelets() { 71 parser.addNodelet("/sqlMap/sql", new Nodelet() { 72 public void process(Node node) throws Exception { 73 Properties attributes = NodeletUtils.parseAttributes(node, vars.properties); 74 String id = attributes.getProperty("id"); 75 if (vars.useStatementNamespaces) { 76 id = applyNamespace(id); 77 } 78 vars.sqlIncludes.put(id, node); 79 } 80 }); 81 } 82 83 private void addTypeAliasNodelets() { 84 parser.addNodelet("/sqlMap/typeAlias", new Nodelet() { 85 public void process(Node node) throws Exception { 86 Properties prop = NodeletUtils.parseAttributes(node, vars.properties); 87 String alias = prop.getProperty("alias"); 88 String type = prop.getProperty("type"); 89 vars.typeHandlerFactory.putTypeAlias(alias, type); 90 } 91 }); 92 } 93 94 private void addCacheModelNodelets() { 95 parser.addNodelet("/sqlMap/cacheModel", new Nodelet() { 96 public void process(Node node) throws Exception { 97 vars.currentCacheModel = new CacheModel(); 98 vars.currentProperties = new Properties(); 99 } 100 }); 101 parser.addNodelet("/sqlMap/cacheModel/end()", new Nodelet() { 102 public void process(Node node) throws Exception { 103 vars.errorCtx.setActivity("building a cache model"); 104 105 Properties attributes = NodeletUtils.parseAttributes(node, vars.properties); 106 String id = applyNamespace(attributes.getProperty("id")); 107 String type = attributes.getProperty("type"); 108 type = vars.typeHandlerFactory.resolveAlias(type); 109 110 String readOnly = attributes.getProperty("readOnly"); 111 if (readOnly != null && readOnly.length() > 0) { 112 vars.currentCacheModel.setReadOnly("true".equals(readOnly)); 113 } else { 114 vars.currentCacheModel.setReadOnly(true); 115 } 116 117 String serialize = attributes.getProperty("serialize"); 118 if (serialize != null && serialize.length() > 0) { 119 vars.currentCacheModel.setSerialize("true".equals(serialize)); 120 } else { 121 vars.currentCacheModel.setSerialize(false); 122 } 123 124 vars.errorCtx.setObjectId(id + " cache model"); 125 126 vars.errorCtx.setMoreInfo("Check the cache model type."); 127 vars.currentCacheModel.setId(id); 128 vars.currentCacheModel.setResource(vars.errorCtx.getResource()); 129 130 try { 131 vars.currentCacheModel.setControllerClassName(type); 132 } catch (Exception e) { 133 throw new NestedRuntimeException("Error setting Cache Controller Class. Cause: " + e, e); 134 } 135 136 vars.errorCtx.setMoreInfo("Check the cache model configuration."); 137 vars.currentCacheModel.configure(vars.currentProperties); 138 139 if (vars.client.getDelegate().isCacheModelsEnabled()) { 140 vars.client.getDelegate().addCacheModel(vars.currentCacheModel); 141 } 142 143 vars.errorCtx.setMoreInfo(null); 144 vars.errorCtx.setObjectId(null); 145 vars.currentProperties = null; 146 vars.currentCacheModel = null; 147 } 148 }); 149 parser.addNodelet("/sqlMap/cacheModel/property", new Nodelet() { 150 public void process(Node node) throws Exception { 151 vars.errorCtx.setMoreInfo("Check the cache model properties."); 152 Properties attributes = NodeletUtils.parseAttributes(node, vars.properties); 153 String name = attributes.getProperty("name"); 154 String value = NodeletUtils.parsePropertyTokens(attributes.getProperty("value"), vars.properties); 155 vars.currentProperties.put(name, value); 156 } 157 }); 158 parser.addNodelet("/sqlMap/cacheModel/flushOnExecute", new Nodelet() { 159 public void process(Node node) throws Exception { 160 vars.errorCtx.setMoreInfo("Check the cache model flush on statement elements."); 161 Properties childAttributes = NodeletUtils.parseAttributes(node, vars.properties); 162 vars.currentCacheModel.addFlushTriggerStatement(childAttributes.getProperty("statement")); 163 } 164 }); 165 parser.addNodelet("/sqlMap/cacheModel/flushInterval", new Nodelet() { 166 public void process(Node node) throws Exception { 167 Properties childAttributes = NodeletUtils.parseAttributes(node, vars.properties); 168 long t = 0; 169 try { 170 vars.errorCtx.setMoreInfo("Check the cache model flush interval."); 171 String milliseconds = childAttributes.getProperty("milliseconds"); 172 String seconds = childAttributes.getProperty("seconds"); 173 String minutes = childAttributes.getProperty("minutes"); 174 String hours = childAttributes.getProperty("hours"); 175 if (milliseconds != null) t += Integer.parseInt(milliseconds); 176 if (seconds != null) t += Integer.parseInt(seconds) * 1000; 177 if (minutes != null) t += Integer.parseInt(minutes) * 60 * 1000; 178 if (hours != null) t += Integer.parseInt(hours) * 60 * 60 * 1000; 179 if (t < 1) throw new NestedRuntimeException("A flush interval must specify one or more of milliseconds, seconds, minutes or hours."); 180 vars.currentCacheModel.setFlushInterval(t); 181 } catch (NumberFormatException e) { 182 throw new NestedRuntimeException("Error building cache '" + vars.currentCacheModel.getId() + "' in '" + "resourceNAME" + "'. Flush interval milliseconds must be a valid long integer value. Cause: " + e, e); 183 } 184 } 185 }); 186 } 187 188 private void addParameterMapNodelets() { 189 parser.addNodelet("/sqlMap/parameterMap/end()", new Nodelet() { 190 public void process(Node node) throws Exception { 191 192 vars.currentParameterMap.setParameterMappingList(vars.parameterMappingList); 193 194 vars.client.getDelegate().addParameterMap(vars.currentParameterMap); 195 196 vars.errorCtx.setMoreInfo(null); 197 vars.errorCtx.setObjectId(null); 198 } 199 }); 200 parser.addNodelet("/sqlMap/parameterMap", new Nodelet() { 201 public void process(Node node) throws Exception { 202 vars.errorCtx.setActivity("building a parameter map"); 203 204 vars.currentParameterMap = new BasicParameterMap(vars.client.getDelegate()); 205 206 Properties attributes = NodeletUtils.parseAttributes(node, vars.properties); 207 String id = applyNamespace(attributes.getProperty("id")); 208 String parameterClassName = attributes.getProperty("class"); 209 parameterClassName = vars.typeHandlerFactory.resolveAlias(parameterClassName); 210 211 vars.currentParameterMap.setId(id); 212 vars.currentParameterMap.setResource(vars.errorCtx.getResource()); 213 214 vars.errorCtx.setObjectId(id + " parameter map"); 215 216 Class parameterClass = null; 217 try { 218 vars.errorCtx.setMoreInfo("Check the parameter class."); 219 parameterClass = Resources.classForName(parameterClassName); 220 } catch (Exception e) { 221 //TODO: Why is this commented out? 222 //throw new SqlMapException("Error configuring ParameterMap. Could not set ParameterClass. Cause: " + e, e); 223 } 224 225 vars.currentParameterMap.setParameterClass(parameterClass); 226 227 vars.parameterMappingList = new ArrayList(); 228 229 vars.errorCtx.setMoreInfo("Check the parameter mappings."); 230 } 231 }); 232 parser.addNodelet("/sqlMap/parameterMap/parameter", new Nodelet() { 233 public void process(Node node) throws Exception { 234 Properties childAttributes = NodeletUtils.parseAttributes(node, vars.properties); 235 String propertyName = childAttributes.getProperty("property"); 236 String jdbcType = childAttributes.getProperty("jdbcType"); 237 String javaType = childAttributes.getProperty("javaType"); 238 String nullValue = childAttributes.getProperty("nullValue"); 239 String mode = childAttributes.getProperty("mode"); 240 String callback = childAttributes.getProperty("typeHandler"); 241 242 callback = vars.typeHandlerFactory.resolveAlias(callback); 243 javaType = vars.typeHandlerFactory.resolveAlias(javaType); 244 245 vars.errorCtx.setObjectId(propertyName + " mapping of the " + vars.currentParameterMap.getId() + " parameter map"); 246 247 TypeHandler handler = null; 248 if (callback != null) { 249 vars.errorCtx.setMoreInfo("Check the parameter mapping typeHandler attribute '" + callback + "' (must be a TypeHandlerCallback implementation)."); 250 try { 251 TypeHandlerCallback typeHandlerCallback = (TypeHandlerCallback) Resources.classForName(callback).newInstance(); 252 handler = new CustomTypeHandler(typeHandlerCallback); 253 } catch (Exception e) { 254 throw new NestedRuntimeException("Error occurred during custom type handler configuration. Cause: " + e, e); 255 } 256 } else { 257 vars.errorCtx.setMoreInfo("Check the parameter mapping property type or name."); 258 handler = resolveTypeHandler(vars.client.getDelegate().getTypeHandlerFactory(), vars.currentParameterMap.getParameterClass(), propertyName, javaType, jdbcType); 259 } 260 261 BasicParameterMapping mapping = new BasicParameterMapping(); 262 mapping.setPropertyName(propertyName); 263 mapping.setJdbcTypeName(jdbcType); 264 mapping.setNullValue(nullValue); 265 if (mode != null && mode.length() > 0) { 266 mapping.setMode(mode); 267 } 268 mapping.setTypeHandler(handler); 269 try { 270 if (javaType != null && javaType.length() > 0) { 271 mapping.setJavaType(Class.forName(javaType)); 272 } 273 } catch (ClassNotFoundException e) { 274 throw new NestedRuntimeException("Error setting javaType on parameter mapping. Cause: " + e); 275 } 276 277 vars.parameterMappingList.add(mapping); 278 279 } 280 }); 281 } 282 283 private void addResultMapNodelets() { 284 parser.addNodelet("/sqlMap/resultMap/end()", new Nodelet() { 285 public void process(Node node) throws Exception { 286 vars.currentResultMap.setResultMappingList(vars.resultMappingList); 287 288 vars.currentResultMap.setDiscriminator(vars.discriminator); 289 290 vars.client.getDelegate().addResultMap(vars.currentResultMap); 291 292 vars.errorCtx.setMoreInfo(null); 293 294 vars.errorCtx.setObjectId(null); 295 } 296 }); 297 parser.addNodelet("/sqlMap/resultMap", new Nodelet() { 298 public void process(Node node) throws Exception { 299 vars.errorCtx.setActivity("building a result map"); 300 301 vars.currentResultMap = new BasicResultMap(vars.client.getDelegate()); 302 303 Properties attributes = NodeletUtils.parseAttributes(node, vars.properties); 304 String id = applyNamespace(attributes.getProperty("id")); 305 String resultClassName = attributes.getProperty("class"); 306 String extended = applyNamespace(attributes.getProperty("extends")); 307 String xmlName = attributes.getProperty("xmlName"); 308 String groupBy = attributes.getProperty("groupBy"); 309 resultClassName = vars.typeHandlerFactory.resolveAlias(resultClassName); 310 311 vars.errorCtx.setObjectId(id + " result map"); 312 313 vars.currentResultMap.setId(id); 314 vars.currentResultMap.setXmlName(xmlName); 315 vars.currentResultMap.setResource(vars.errorCtx.getResource()); 316 317 if (groupBy != null && groupBy.length() > 0) { 318 StringTokenizer parser = new StringTokenizer(groupBy, ", ", false); 319 while (parser.hasMoreTokens()) { 320 vars.currentResultMap.addGroupByProperty(parser.nextToken()); 321 } 322 } 323 324 Class resultClass = null; 325 try { 326 vars.errorCtx.setMoreInfo("Check the result class."); 327 resultClass = Resources.classForName(resultClassName); 328 } catch (Exception e) { 329 throw new NestedRuntimeException("Error configuring Result. Could not set ResultClass. Cause: " + e, e); 330 331 } 332 333 vars.currentResultMap.setResultClass(resultClass); 334 335 vars.resultMappingList = new ArrayList(); 336 337 vars.errorCtx.setMoreInfo("Check the extended result map."); 338 if (extended != null) { 339 BasicResultMap extendedResultMap = (BasicResultMap) vars.client.getDelegate().getResultMap(extended); 340 ResultMapping[] resultMappings = extendedResultMap.getResultMappings(); 341 for (int i = 0; i < resultMappings.length; i++) { 342 vars.resultMappingList.add(resultMappings[i]); 343 } 344 } 345 346 vars.errorCtx.setMoreInfo("Check the result mappings."); 347 vars.resultMappingIndex = vars.resultMappingList.size(); 348 349 } 350 }); 351 parser.addNodelet("/sqlMap/resultMap/result", new Nodelet() { 352 public void process(Node node) throws Exception { 353 Properties childAttributes = NodeletUtils.parseAttributes(node, vars.properties); 354 String propertyName = childAttributes.getProperty("property"); 355 String nullValue = childAttributes.getProperty("nullValue"); 356 String jdbcType = childAttributes.getProperty("jdbcType"); 357 String javaType = childAttributes.getProperty("javaType"); 358 String columnName = childAttributes.getProperty("column"); 359 String columnIndex = childAttributes.getProperty("columnIndex"); 360 String statementName = childAttributes.getProperty("select"); 361 String resultMapName = childAttributes.getProperty("resultMap"); 362 String callback = childAttributes.getProperty("typeHandler"); 363 364 callback = vars.typeHandlerFactory.resolveAlias(callback); 365 javaType = vars.typeHandlerFactory.resolveAlias(javaType); 366 367 vars.errorCtx.setObjectId(propertyName + " mapping of the " + vars.currentResultMap.getId() + " result map"); 368 369 TypeHandler handler = null; 370 if (callback != null) { 371 vars.errorCtx.setMoreInfo("Check the result mapping typeHandler attribute '" + callback + "' (must be a TypeHandlerCallback implementation)."); 372 try { 373 Object impl = Resources.classForName(callback).newInstance(); 374 if (impl instanceof TypeHandlerCallback) { 375 handler = new CustomTypeHandler((TypeHandlerCallback) impl); 376 } else if (impl instanceof TypeHandler) { 377 handler = (TypeHandler) impl; 378 } else { 379 throw new NestedRuntimeException ("The class '' is not a valid implementation of TypeHandler or TypeHandlerCallback"); 380 } 381 } catch (Exception e) { 382 throw new NestedRuntimeException("Error occurred during custom type handler configuration. Cause: " + e, e); 383 } 384 } else { 385 vars.errorCtx.setMoreInfo("Check the result mapping property type or name."); 386 handler = resolveTypeHandler(vars.client.getDelegate().getTypeHandlerFactory(), vars.currentResultMap.getResultClass(), propertyName, javaType, jdbcType, true); 387 } 388 389 390 BasicResultMapping mapping = new BasicResultMapping(); 391 mapping.setPropertyName(propertyName); 392 mapping.setColumnName(columnName); 393 mapping.setJdbcTypeName(jdbcType); 394 mapping.setTypeHandler(handler); 395 mapping.setNullValue(nullValue); 396 mapping.setStatementName(statementName); 397 mapping.setNestedResultMapName(resultMapName); 398 399 if (resultMapName != null && resultMapName.length() > 0) { 400 vars.currentResultMap.addNestedResultMappings(mapping); 401 } 402 403 try { 404 if (javaType != null && javaType.length() > 0) { 405 mapping.setJavaType(Class.forName(javaType)); 406 } 407 } catch (ClassNotFoundException e) { 408 throw new NestedRuntimeException("Error setting javaType on result mapping. Cause: " + e); 409 } 410 411 if (columnIndex != null && columnIndex.length() > 0) { 412 mapping.setColumnIndex(Integer.parseInt(columnIndex)); 413 } else { 414 vars.resultMappingIndex++; 415 mapping.setColumnIndex(vars.resultMappingIndex); 416 } 417 418 vars.resultMappingList.add(mapping); 419 } 420 }); 421 422 parser.addNodelet("/sqlMap/resultMap/discriminator/subMap", new Nodelet() { 423 public void process(Node node) throws Exception { 424 if (vars.discriminator == null) { 425 throw new NestedRuntimeException ("The discriminator is null, but somehow a subMap was reached. This is a bug."); 426 } 427 Properties childAttributes = NodeletUtils.parseAttributes(node, vars.properties); 428 String value = childAttributes.getProperty("value"); 429 String resultMap = childAttributes.getProperty("resultMap"); 430 vars.discriminator.addSubMap(value, applyNamespace(resultMap)); 431 } 432 }); 433 434 parser.addNodelet("/sqlMap/resultMap/discriminator", new Nodelet() { 435 public void process(Node node) throws Exception { 436 Properties childAttributes = NodeletUtils.parseAttributes(node, vars.properties); 437 String nullValue = childAttributes.getProperty("nullValue"); 438 String jdbcType = childAttributes.getProperty("jdbcType"); 439 String javaType = childAttributes.getProperty("javaType"); 440 String columnName = childAttributes.getProperty("column"); 441 String columnIndex = childAttributes.getProperty("columnIndex"); 442 String callback = childAttributes.getProperty("typeHandler"); 443 444 callback = vars.typeHandlerFactory.resolveAlias(callback); 445 javaType = vars.typeHandlerFactory.resolveAlias(javaType); 446 447 TypeHandler handler = null; 448 if (callback != null) { 449 vars.errorCtx.setMoreInfo("Check the result mapping typeHandler attribute '" + callback + "' (must be a TypeHandlerCallback implementation)."); 450 try { 451 Object impl = Resources.classForName(callback).newInstance(); 452 if (impl instanceof TypeHandlerCallback) { 453 handler = new CustomTypeHandler((TypeHandlerCallback) impl); 454 } else if (impl instanceof TypeHandler) { 455 handler = (TypeHandler) impl; 456 } else { 457 throw new NestedRuntimeException ("The class '' is not a valid implementation of TypeHandler or TypeHandlerCallback"); 458 } 459 } catch (Exception e) { 460 throw new NestedRuntimeException("Error occurred during custom type handler configuration. Cause: " + e, e); 461 } 462 } else { 463 vars.errorCtx.setMoreInfo("Check the result mapping property type or name."); 464 handler = resolveTypeHandler(vars.client.getDelegate().getTypeHandlerFactory(), vars.currentResultMap.getResultClass(), "", javaType, jdbcType, true); 465 } 466 467 BasicResultMapping mapping = new BasicResultMapping(); 468 mapping.setColumnName(columnName); 469 mapping.setJdbcTypeName(jdbcType); 470 mapping.setTypeHandler(handler); 471 mapping.setNullValue(nullValue); 472 473 try { 474 if (javaType != null && javaType.length() > 0) { 475 mapping.setJavaType(Class.forName(javaType)); 476 } 477 } catch (ClassNotFoundException e) { 478 throw new NestedRuntimeException("Error setting javaType on result mapping. Cause: " + e); 479 } 480 481 if (columnIndex != null && columnIndex.length() > 0) { 482 mapping.setColumnIndex(Integer.parseInt(columnIndex)); 483 } 484 485 vars.discriminator = new Discriminator (vars.delegate, mapping); 486 } 487 }); 488 } 489 490 private void addStatementNodelets() { 491 parser.addNodelet("/sqlMap/statement", new Nodelet() { 492 public void process(Node node) throws Exception { 493 vars.currentStatement = new SqlStatementParser(vars).parseGeneralStatement(node, new GeneralStatement()); 494 vars.delegate.addMappedStatement(vars.currentStatement); 495 } 496 }); 497 parser.addNodelet("/sqlMap/insert", new Nodelet() { 498 public void process(Node node) throws Exception { 499 vars.currentStatement = new SqlStatementParser(vars).parseGeneralStatement(node, new InsertStatement()); 500 vars.delegate.addMappedStatement(vars.currentStatement); 501 } 502 }); 503 parser.addNodelet("/sqlMap/update", new Nodelet() { 504 public void process(Node node) throws Exception { 505 vars.currentStatement = new SqlStatementParser(vars).parseGeneralStatement(node, new UpdateStatement()); 506 vars.delegate.addMappedStatement(vars.currentStatement); 507 } 508 }); 509 parser.addNodelet("/sqlMap/delete", new Nodelet() { 510 public void process(Node node) throws Exception { 511 vars.currentStatement = new SqlStatementParser(vars).parseGeneralStatement(node, new DeleteStatement()); 512 vars.delegate.addMappedStatement(vars.currentStatement); 513 } 514 }); 515 parser.addNodelet("/sqlMap/select", new Nodelet() { 516 public void process(Node node) throws Exception { 517 vars.currentStatement = new SqlStatementParser(vars).parseGeneralStatement(node, new SelectStatement()); 518 vars.delegate.addMappedStatement(vars.currentStatement); 519 } 520 }); 521 parser.addNodelet("/sqlMap/procedure", new Nodelet() { 522 public void process(Node node) throws Exception { 523 vars.currentStatement = new SqlStatementParser(vars).parseGeneralStatement(node, new ProcedureStatement()); 524 vars.delegate.addMappedStatement(vars.currentStatement); 525 } 526 }); 527 } 528 529 530 }