001 /* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the "License"); 007 * you may not use this file except in compliance with the License. 008 * You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018 /* 019 * $Id: SQLQueryParser.java 468638 2006-10-28 06:52:06Z minchau $ 020 */ 021 022 /** 023 * This is used by the SQLDocumentHandler for processing JDBC queries. 024 * This prepares JDBC PreparedStatement or CallableStatements and the 025 * input/output of parameters from/to variables. 026 * 027 */ 028 029 package org.apache.xalan.lib.sql; 030 031 import java.util.*; 032 import java.sql.*; 033 import org.apache.xpath.objects.*; 034 import org.apache.xalan.extensions.ExpressionContext; 035 import org.apache.xml.utils.QName; 036 import javax.xml.transform.TransformerException; 037 038 039 040 public class SQLQueryParser 041 { 042 /** 043 * If the parser used inline parser to pull out variables then 044 * this will be true. The default is not to use the Inline Parser. 045 */ 046 private boolean m_InlineVariables = false; 047 048 /** 049 * 050 */ 051 private boolean m_IsCallable = false; 052 053 /** 054 * 055 */ 056 private String m_OrigQuery = null; 057 058 /** 059 * 060 */ 061 private StringBuffer m_ParsedQuery = null; 062 063 /** 064 * 065 */ 066 private Vector m_Parameters = null; 067 068 /** 069 * 070 */ 071 private boolean m_hasOutput = false; 072 073 /** 074 * 075 */ 076 private boolean m_HasParameters; 077 078 public static final int NO_OVERRIDE = 0; 079 public static final int NO_INLINE_PARSER = 1; 080 public static final int INLINE_PARSER = 2; 081 082 /** 083 * The SQLStatement Parser will be created as a psuedo SINGLETON per 084 * XConnection. Since we are only caching the Query and its parsed results 085 * we may be able to use this as a real SINGLETON. It all depends on how 086 * Statement Caching will play out. 087 */ 088 public SQLQueryParser() 089 { 090 init(); 091 } 092 093 /** 094 * Constructor, used to create a new parser entry 095 */ 096 private SQLQueryParser(String query) 097 { 098 m_OrigQuery = query; 099 } 100 101 /** 102 * On a per Xconnection basis, we will create a SQLStatemenetParser, from 103 * this parser, individual parsers will be created. The Init method is defined 104 * to initialize all the internal structures that maintains the pool of parsers. 105 */ 106 private void init() 107 { 108 // Do nothing for now. 109 } 110 111 /** 112 * Produce an SQL Statement Parser based on the incomming query. 113 * 114 * For now we will just create a new object, in the future we may have this 115 * interface cache the queries so that we can take advantage of a preparsed 116 * String. 117 * 118 * If the Inline Parser is not enabled in the Options, no action will be 119 * taken on the parser. This option can be set by the Stylesheet. If the 120 * option is not set or cleared, a default value will be set determined 121 * by the way variables were passed into the system. 122 */ 123 public SQLQueryParser parse(XConnection xconn, String query, int override) 124 { 125 SQLQueryParser parser = new SQLQueryParser(query); 126 127 // Try to implement caching here, if we found a parser in the cache 128 // then just return the instance otherwise 129 parser.parse(xconn, override); 130 131 return parser; 132 } 133 134 135 136 /** 137 * Produce an SQL Statement Parser based on the incomming query. 138 * 139 * For now we will just create a new object, in the future we may have this 140 * interface cache the queries so that we can take advantage of a preparsed 141 * String. 142 * 143 * If the Inline Parser is not enabled in the Options, no action will be 144 * taken on the parser. This option can be set by the Stylesheet. If the 145 * option is not set or cleared, a default value will be set determined 146 * by the way variables were passed into the system. 147 */ 148 private void parse(XConnection xconn, int override) 149 { 150 // Grab the Feature here. We could maintain it from the Parent Parser 151 // but that may cause problems if a single XConnection wants to maintain 152 // both Inline Variable Statemens along with NON inline variable statements. 153 154 m_InlineVariables = "true".equals(xconn.getFeature("inline-variables")); 155 if (override == NO_INLINE_PARSER) m_InlineVariables = false; 156 else if (override == INLINE_PARSER) m_InlineVariables = true; 157 158 if (m_InlineVariables) inlineParser(); 159 160 } 161 162 /** 163 * If a SQL Statement does not have any parameters, then it can be executed 164 * directly. Most SQL Servers use this as a performance advantage since no 165 * parameters need to be parsed then bound. 166 */ 167 public boolean hasParameters() 168 { 169 return m_HasParameters; 170 } 171 172 /** 173 * If the Inline Parser is used, the parser will note if this stastement is 174 * a plain SQL Statement or a Called Procedure. Called Procudures generally 175 * have output parameters and require special handling. 176 * 177 * Called Procudures that are not processed with the Inline Parser will 178 * still be executed but under the context of a PreparedStatement and 179 * not a CallableStatement. Called Procudures that have output parameters 180 * MUST be handled with the Inline Parser. 181 */ 182 public boolean isCallable() 183 { 184 return m_IsCallable; 185 } 186 187 188 /** 189 * 190 */ 191 public Vector getParameters() 192 { 193 return m_Parameters; 194 } 195 196 /** 197 * The XConnection will use this method to store the Parameters 198 * that were supplied by the style sheet in the case where the 199 * inline parser was not used 200 */ 201 public void setParameters(Vector p) 202 { 203 m_HasParameters = true; 204 m_Parameters = p; 205 } 206 207 /** 208 * Return a copy of the parsed SQL query that will be set to the 209 * Database system to execute. If the inline parser was not used, 210 * then the original query will be returned. 211 */ 212 public String getSQLQuery() 213 { 214 if (m_InlineVariables) return m_ParsedQuery.toString(); 215 else return m_OrigQuery; 216 } 217 218 219 /** 220 * The SQL Statement Parser, when an Inline Parser is used, tracks the XSL 221 * variables used to populate a statement. The data use to popoulate a 222 * can also be provided. If the data is provided, it will overide the 223 * populastion using XSL variables. When the Inline PArser is not used, then 224 * the Data will always be provided. 225 * 226 */ 227 public void populateStatement(PreparedStatement stmt, ExpressionContext ctx) 228 { 229 // Set input parameters from variables. 230 // for ( int indx = returnParm ? 1 : 0 ; indx < m_Parameters.size() ; indx++ ) 231 232 for ( int indx = 0 ; indx < m_Parameters.size() ; indx++ ) 233 { 234 QueryParameter parm = (QueryParameter) m_Parameters.elementAt(indx); 235 236 try 237 { 238 239 if (m_InlineVariables) 240 { 241 XObject value = (XObject)ctx.getVariableOrParam(new QName(parm.getName())); 242 243 if (value != null) 244 { 245 stmt.setObject( 246 indx + 1, 247 value.object(), 248 parm.getType(), 4); // Currently defaulting scale to 4 - should read this! 249 } 250 else 251 { 252 stmt.setNull(indx + 1, parm.getType()); 253 } 254 } 255 else 256 { 257 String value = parm.getValue(); 258 259 if (value != null) 260 { 261 stmt.setObject( 262 indx + 1, 263 value, 264 parm.getType(), 4); // Currently defaulting scale to 4 - should read this! 265 } 266 else 267 { 268 stmt.setNull(indx + 1, parm.getType()); 269 } 270 } 271 } 272 catch (Exception tx) 273 { 274 // if ( ! parm.isOutput() ) throw new SQLException(tx.toString()); 275 } 276 } 277 278 } 279 280 public void registerOutputParameters(CallableStatement cstmt) throws SQLException 281 { 282 // Register output parameters if call. 283 if ( m_IsCallable && m_hasOutput ) 284 { 285 for ( int indx = 0 ; indx < m_Parameters.size() ; indx++ ) 286 { 287 QueryParameter parm = (QueryParameter) m_Parameters.elementAt(indx); 288 if ( parm.isOutput() ) 289 { 290 //System.out.println("chrysalisSQLStatement() Registering output parameter for parm " + indx); 291 cstmt.registerOutParameter(indx + 1, parm.getType()); 292 } 293 } 294 } 295 } 296 297 /** 298 * 299 */ 300 protected void inlineParser() 301 { 302 QueryParameter curParm = null; 303 int state = 0; 304 StringBuffer tok = new StringBuffer(); 305 boolean firstword = true; 306 307 if (m_Parameters == null) m_Parameters = new Vector(); 308 309 if (m_ParsedQuery == null) m_ParsedQuery = new StringBuffer(); 310 311 for ( int idx = 0 ; idx < m_OrigQuery.length() ; idx++ ) 312 { 313 char ch = m_OrigQuery.charAt(idx); 314 switch ( state ) 315 { 316 317 case 0: // Normal 318 if ( ch == '\'' ) state = 1; 319 else if ( ch == '?' ) state = 4; 320 else if ( firstword && (Character.isLetterOrDigit(ch) || ch == '#') ) 321 { 322 tok.append(ch); 323 state = 3; 324 } 325 m_ParsedQuery.append(ch); 326 break; 327 328 case 1: // In String 329 if ( ch == '\'' ) state = 0; 330 else if ( ch == '\\' ) state = 2; 331 m_ParsedQuery.append(ch); 332 break; 333 334 case 2: // In escape 335 state = 1; 336 m_ParsedQuery.append(ch); 337 break; 338 339 case 3: // First word 340 if ( Character.isLetterOrDigit(ch) || ch == '#' || ch == '_' ) tok.append(ch); 341 else 342 { 343 if ( tok.toString().equalsIgnoreCase("call") ) 344 { 345 m_IsCallable = true; 346 if ( curParm != null ) 347 { 348 // returnParm = true; 349 curParm.setIsOutput(true); 350 // hasOutput = true; 351 } 352 } 353 firstword = false; 354 tok = new StringBuffer(); 355 if ( ch == '\'' ) state = 1; 356 else if ( ch == '?' ) state = 4; 357 else state = 0; 358 } 359 360 m_ParsedQuery.append(ch); 361 break; 362 363 case 4: // Get variable definition 364 if ( ch == '[' ) state = 5; 365 break; 366 367 case 5: // Read variable type. 368 if ( !Character.isWhitespace(ch) && ch != '=' ) 369 { 370 tok.append(Character.toUpperCase(ch)); 371 } 372 else if ( tok.length() > 0 ) 373 { 374 // OK we have at least one parameter. 375 m_HasParameters = true; 376 377 curParm = new QueryParameter(); 378 379 curParm.setTypeName(tok.toString()); 380 // curParm.type = map_type(curParm.typeName); 381 m_Parameters.addElement(curParm); 382 tok = new StringBuffer(); 383 if ( ch == '=' ) state = 7; 384 else state = 6; 385 } 386 break; 387 388 case 6: // Look for '=' 389 if ( ch == '=' ) state = 7; 390 break; 391 392 case 7: // Read variable name. 393 if ( !Character.isWhitespace(ch) && ch != ']' ) tok.append(ch); 394 else if ( tok.length() > 0 ) 395 { 396 curParm.setName(tok.toString()); 397 tok = new StringBuffer(); 398 if ( ch == ']' ) 399 { 400 //param_output.addElement(new Boolean(false)); 401 state = 0; 402 } 403 else state = 8; 404 } 405 break; 406 407 case 8: // Look for "OUTput. 408 if ( !Character.isWhitespace(ch) && ch != ']' ) 409 { 410 tok.append(ch); 411 } 412 else if ( tok.length() > 0 ) 413 { 414 tok.setLength(3); 415 if ( tok.toString().equalsIgnoreCase("OUT") ) 416 { 417 curParm.setIsOutput(true); 418 m_hasOutput = true; 419 } 420 421 tok = new StringBuffer(); 422 if ( ch == ']' ) 423 { 424 state = 0; 425 } 426 } 427 break; 428 } 429 } 430 431 432 // Prepare statement or call. 433 if ( m_IsCallable ) 434 { 435 m_ParsedQuery.insert(0, '{'); 436 m_ParsedQuery.append('}'); 437 } 438 439 } 440 441 } 442