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