001: /**
002: * Sequoia: Database clustering technology.
003: * Copyright (C) 2002-2004 French National Institute For Research In Computer
004: * Science And Control (INRIA).
005: * Copyright (C) 2005-2006 Continuent, Inc.
006: * Contact: sequoia@continuent.org
007: *
008: * Licensed under the Apache License, Version 2.0 (the "License");
009: * you may not use this file except in compliance with the License.
010: * You may obtain a copy of the License at
011: *
012: * http://www.apache.org/licenses/LICENSE-2.0
013: *
014: * Unless required by applicable law or agreed to in writing, software
015: * distributed under the License is distributed on an "AS IS" BASIS,
016: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017: * See the License for the specific language governing permissions and
018: * limitations under the License.
019: *
020: * Initial developer(s): Emmanuel Cecchet.
021: * Contributor(s): ______________________.
022: */package org.continuent.sequoia.controller.requests;
023:
024: import java.sql.SQLException;
025: import java.util.ArrayList;
026: import java.util.HashMap;
027: import java.util.List;
028: import java.util.SortedMap;
029: import java.util.TreeMap;
030: import java.util.regex.Matcher;
031: import java.util.regex.Pattern;
032:
033: import org.continuent.sequoia.common.i18n.Translate;
034: import org.continuent.sequoia.common.sql.schema.DatabaseProcedure;
035: import org.continuent.sequoia.common.sql.schema.DatabaseProcedureSemantic;
036: import org.continuent.sequoia.common.sql.schema.DatabaseSchema;
037:
038: /**
039: * <code>StoredProcedure</code> is encodes a stored procedure call.
040: * <p>
041: * It can have the following syntax:
042: *
043: * <pre>
044: * {call <procedure-name>[<arg1>,<arg2>, ...]}
045: * {?=call <procedure-name>[<arg1>,<arg2>, ...]}
046: * </pre>
047: *
048: * For more information
049: *
050: * @see org.continuent.sequoia.driver.CallableStatement
051: * @author <a href="mailto:Emmanuel.Cecchet@inria.fr">Emmanuel Cecchet </a>
052: * @version 1.0
053: */
054: public class StoredProcedure extends AbstractRequest {
055: private static final long serialVersionUID = 5360592917986126504L;
056:
057: /** <code>true</code> if this request might block. */
058: private transient boolean blocking = true;
059:
060: /** Number of parameters of the stored procedure */
061: protected int nbOfParameters;
062:
063: /**
064: * Map of named parameter values <String(name),String(type)>
065: */
066: private HashMap namedParameterValues = null;
067:
068: /**
069: * List of named parameters names
070: */
071: private List namedParameterNames = null;
072:
073: /**
074: * Map of out parameter values (parameter index corresponds to index in list)
075: */
076: private SortedMap outParameterValues = null;
077:
078: /**
079: * List of out parameter types (parameter index corresponds to index in list)
080: */
081: private List outParameterIndexes = null;
082:
083: protected transient DatabaseProcedureSemantic semantic = null;
084:
085: protected String procedureKey = null;
086:
087: private String procedureName = "";
088:
089: // Be conservative, if we cannot parse the query, assumes it invalidates
090: // everything
091: protected boolean altersAggregateList = true;
092: protected boolean altersDatabaseCatalog = true;
093: protected boolean altersDatabaseSchema = true;
094: protected boolean altersMetadataCache = true;
095: protected boolean altersQueryResultCache = true;
096: protected boolean altersSomething = true;
097: protected boolean altersStoredProcedureList = true;
098: protected boolean altersUserDefinedTypes = true;
099: protected boolean altersUsers = true;
100:
101: // Regular expression for parsing {call ...} statements
102: private static final String STORED_PROCEDURE_PATTERN_STRING = "^((execute\\s+([^(\\s|\\()]+)(.*)?)|(\\{(\\s*\\?\\s*=)?\\s*call\\s+([^(\\s|\\()]+)(.*)?\\})|(\\s*call\\s+([^(\\s|\\()]+)(.*)?))";
103:
104: private static final Pattern STORED_PROCEDURE_PATTERN = Pattern
105: .compile(STORED_PROCEDURE_PATTERN_STRING,
106: Pattern.CASE_INSENSITIVE | Pattern.DOTALL);
107:
108: private static int posSyntax1 = 2;
109: private static int posSyntax2 = 5;
110: private static int posSyntax3 = 10;
111:
112: /**
113: * Creates a new <code>StoredProcedure</code> instance.
114: *
115: * @param sqlQuery the SQL request
116: * @param escapeProcessing should the driver to escape processing before
117: * sending to the database ?
118: * @param timeout an <code>int</code> value
119: * @param lineSeparator the line separator used in the query
120: */
121: public StoredProcedure(String sqlQuery, boolean escapeProcessing,
122: int timeout, String lineSeparator) {
123: super (sqlQuery, escapeProcessing, timeout, lineSeparator,
124: RequestType.STORED_PROCEDURE);
125: }
126:
127: /**
128: * @see org.continuent.sequoia.controller.requests.AbstractRequest#altersAggregateList()
129: */
130: public boolean altersAggregateList() {
131: return altersAggregateList;
132: }
133:
134: /**
135: * @see org.continuent.sequoia.controller.requests.AbstractRequest#altersDatabaseCatalog()
136: */
137: public boolean altersDatabaseCatalog() {
138: return altersDatabaseCatalog;
139: }
140:
141: /**
142: * @see org.continuent.sequoia.controller.requests.AbstractRequest#altersDatabaseSchema()
143: */
144: public boolean altersDatabaseSchema() {
145: return altersDatabaseSchema;
146: }
147:
148: /**
149: * @see org.continuent.sequoia.controller.requests.AbstractRequest#altersMetadataCache()
150: */
151: public boolean altersMetadataCache() {
152: return altersMetadataCache;
153: }
154:
155: /**
156: * @see org.continuent.sequoia.controller.requests.AbstractRequest#altersQueryResultCache()
157: */
158: public boolean altersQueryResultCache() {
159: return altersQueryResultCache;
160: }
161:
162: /**
163: * @see org.continuent.sequoia.controller.requests.AbstractRequest#altersSomething()
164: */
165: public boolean altersSomething() {
166: return altersSomething;
167: }
168:
169: /**
170: * @see org.continuent.sequoia.controller.requests.AbstractRequest#altersStoredProcedureList()
171: */
172: public boolean altersStoredProcedureList() {
173: return altersStoredProcedureList;
174: }
175:
176: /**
177: * @see org.continuent.sequoia.controller.requests.AbstractRequest#altersUserDefinedTypes()
178: */
179: public boolean altersUserDefinedTypes() {
180: return altersUserDefinedTypes;
181: }
182:
183: /**
184: * @see org.continuent.sequoia.controller.requests.AbstractRequest#altersUsers()
185: */
186: public boolean altersUsers() {
187: return altersUsers;
188: }
189:
190: /**
191: * @return <code>false</code>
192: * @see org.continuent.sequoia.controller.requests.AbstractRequest#needsMacroProcessing()
193: */
194: public boolean needsMacroProcessing() {
195: return true;
196: }
197:
198: /**
199: * Returns the the value of a named parameter.
200: *
201: * @param name parameter name
202: * @return Returns the named parameter value.
203: */
204: public Object getNamedParameterValue(String name) {
205: return namedParameterValues.get(name);
206: }
207:
208: /**
209: * Returns the named parameter names or none if this stored procedure call has
210: * no named parameter.
211: *
212: * @return Returns the namedParameterNames.
213: */
214: public List getNamedParameterNames() {
215: return namedParameterNames;
216: }
217:
218: /**
219: * Returns the nbOfParameters value.
220: *
221: * @return Returns the nbOfParameters.
222: */
223: public final int getNbOfParameters() {
224: return nbOfParameters;
225: }
226:
227: /**
228: * Returns the the value of an out parameter.
229: *
230: * @param idx parameter index
231: * @return Returns the out parameter value.
232: */
233: public Object getOutParameterValue(Object idx) {
234: return outParameterValues.get(idx);
235: }
236:
237: /**
238: * Returns the list of out parameter indexes (null if none).
239: *
240: * @return Returns the outParameterIndexes.
241: */
242: public List getOutParameterIndexes() {
243: return outParameterIndexes;
244: }
245:
246: /**
247: * Get the stored procedure key
248: *
249: * @return the stored procedure key
250: * @see DatabaseProcedure#buildKey(String, int)
251: */
252: public String getProcedureKey() {
253: if (procedureKey == null)
254: try {
255: parse(null, 0, true);
256: } catch (SQLException e) {
257: return null;
258: }
259: return procedureKey;
260: }
261:
262: /**
263: * Returns the stored procedure name.
264: *
265: * @return the stored procedure name
266: */
267: public String getProcedureName() {
268: return procedureName;
269: }
270:
271: /**
272: * Returns the semantic value.
273: *
274: * @return Returns the semantic.
275: */
276: public DatabaseProcedureSemantic getSemantic() {
277: return semantic;
278: }
279:
280: /**
281: * Tests if this request might block.
282: *
283: * @return <code>true</code> if this request might block
284: */
285: public boolean mightBlock() {
286: return blocking;
287: }
288:
289: /**
290: * Sets if this request might block.
291: *
292: * @param blocking a <code>boolean</code> value
293: */
294: public void setBlocking(boolean blocking) {
295: this .blocking = blocking;
296: }
297:
298: /**
299: * Set a named parameter on this stored procedure call.
300: *
301: * @param paramName name of the parameter
302: * @param val named parameter value
303: */
304: public synchronized void setNamedParameterValue(String paramName,
305: Object val) {
306: if (namedParameterValues == null)
307: namedParameterValues = new HashMap();
308: namedParameterValues.put(paramName, val);
309: }
310:
311: /**
312: * Set a named parameter name on this stored procedure call.
313: *
314: * @param paramName name of the parameter
315: */
316: public synchronized void setNamedParameterName(String paramName) {
317: if (namedParameterNames == null)
318: namedParameterNames = new ArrayList();
319: namedParameterNames.add(paramName);
320: }
321:
322: /**
323: * Set an out parameter value on this stored procedure call.
324: *
325: * @param paramIdx index of the parameter
326: * @param val value of the parameter
327: */
328: public synchronized void setOutParameterValue(Object paramIdx,
329: Object val) {
330: if (outParameterValues == null)
331: outParameterValues = new TreeMap();
332: outParameterValues.put(paramIdx, val);
333: }
334:
335: /**
336: * Set an out parameter index on this stored procedure call.
337: *
338: * @param paramIdx index of the parameter
339: */
340: public synchronized void setOutParameterIndex(int paramIdx) {
341: if (outParameterIndexes == null)
342: outParameterIndexes = new ArrayList();
343: outParameterIndexes.add(new Integer(paramIdx));
344: }
345:
346: /**
347: * Just get the stored procedure name.
348: *
349: * @see org.continuent.sequoia.controller.requests.AbstractRequest#parse(org.continuent.sequoia.common.sql.schema.DatabaseSchema,
350: * int, boolean)
351: */
352: public void parse(DatabaseSchema schema, int granularity,
353: boolean isCaseSensitive) throws SQLException {
354: Matcher matcher;
355: sqlQueryOrTemplate = sqlQueryOrTemplate.trim();
356:
357: matcher = STORED_PROCEDURE_PATTERN.matcher(sqlQueryOrTemplate);
358: if (!matcher.matches())
359: return;
360:
361: String parameterList = null;
362:
363: if (matcher.group(posSyntax1) != null) {
364: procedureName = matcher.group(posSyntax1 + 1);
365: parameterList = matcher.group(posSyntax1 + 2);
366: } else if (matcher.group(posSyntax2) != null) {
367: procedureName = matcher.group(posSyntax2 + 2);
368: parameterList = matcher.group(posSyntax2 + 3);
369: } else {
370: procedureName = matcher.group(posSyntax3);
371: parameterList = matcher.group(posSyntax3 + 1);
372: }
373:
374: int parenthesis = parameterList.indexOf('(');
375:
376: if (parenthesis == -1) {
377: // sometimes, parameters are not inside ()
378: // we will check if we find ',' after the name of the stored procedure
379: // to check for parameters
380: int commaIdx = parameterList.indexOf(',');
381: if (commaIdx == -1) {
382: if (parameterList.trim().length() != 0)
383: nbOfParameters = 1;
384: else
385: nbOfParameters = 0;
386: } else {
387: nbOfParameters = 1;
388: while (commaIdx != -1) {
389: nbOfParameters++;
390: commaIdx = parameterList.indexOf(',', commaIdx + 1);
391: }
392: }
393: } else {
394: // Note that indexOf is tolerant and accept values greater than the String
395: // size so there is no need to check for the limit.
396: int commaIdx = parameterList.indexOf(',', parenthesis + 1);
397:
398: // Here we just count the number of commas to find the number of
399: // parameters (which is equal to nb of commas plus one if there is at
400: // least one comma).
401:
402: if (commaIdx == -1) { // Check if we have 0 or 1 parameter
403: int closingParenthesis = parameterList.indexOf(')',
404: parenthesis + 1);
405: try {
406: if (parameterList.substring(parenthesis + 1,
407: closingParenthesis).trim().length() == 0)
408: nbOfParameters = 0;
409: else
410: nbOfParameters = 1;
411: } catch (RuntimeException e) { // Malformed query, just use -1 as the number of parameters
412: nbOfParameters = -1;
413: }
414: } else {
415: nbOfParameters = 1;
416: while (commaIdx != -1) {
417: nbOfParameters++;
418: commaIdx = parameterList.indexOf(',', commaIdx + 1);
419: }
420: }
421: }
422:
423: // Build the key and retrieve the associated semantic
424: procedureKey = DatabaseProcedure.buildKey(procedureName,
425: nbOfParameters);
426:
427: if (schema != null) {
428: DatabaseProcedure dbProc = schema
429: .getProcedure(procedureKey);
430: if (dbProc == null)
431: semantic = null;
432: else {
433: semantic = dbProc.getSemantic();
434: if (semantic != null) {
435: writeLockedTables = semantic.getWriteTables();
436: addDependingTables(schema, writeLockedTables);
437: altersDatabaseSchema = semantic.hasDDLWrite();
438: altersSomething = !semantic.isReadOnly();
439: }
440: }
441: }
442: isParsed = true;
443: }
444:
445: /**
446: * @see org.continuent.sequoia.controller.requests.AbstractRequest#cloneParsing(org.continuent.sequoia.controller.requests.AbstractRequest)
447: */
448: public void cloneParsing(AbstractRequest request) {
449: if (!request.isParsed)
450: return;
451: StoredProcedure other = (StoredProcedure) request;
452: procedureKey = other.getProcedureKey();
453: nbOfParameters = other.getNbOfParameters();
454: semantic = other.getSemantic();
455: writeLockedTables = other.writeLockedTables;
456: }
457:
458: /**
459: * Copy (simple assignment, no object cloning so be sure to not modify the
460: * original object else you expose yourself to nasty side effects) named
461: * parameter values and names ; and out parameter values and indexes of this
462: * stored procedure call.
463: *
464: * @param otherProc the other stored procedure that has the result to copy
465: * into this object
466: */
467: public void copyNamedAndOutParameters(StoredProcedure otherProc) {
468: this .outParameterValues = otherProc.outParameterValues;
469: this .outParameterIndexes = otherProc.outParameterIndexes;
470: this .namedParameterValues = otherProc.namedParameterValues;
471: this .namedParameterNames = otherProc.namedParameterNames;
472: }
473:
474: /**
475: * @see org.continuent.sequoia.controller.requests.AbstractRequest#getParsingResultsAsString()
476: */
477: public String getParsingResultsAsString() {
478: StringBuffer sb = new StringBuffer(super
479: .getParsingResultsAsString());
480: sb.append(Translate.get("request.storedproc.name",
481: procedureName));
482: sb.append(Translate.get("request.blocking", blocking));
483: sb.append(Translate.get("request.storedproc.parameters.number",
484: nbOfParameters));
485: if (namedParameterNames != null
486: && namedParameterNames.size() > 0) {
487: sb.append(Translate
488: .get("request.storedproc.named.parameters.names"));
489: for (int i = 0; i < namedParameterNames.size(); i++) {
490: sb.append(Translate.get(
491: "request.storedproc.named.parameters.names",
492: namedParameterNames.get(i)));
493: }
494: }
495: if (namedParameterValues != null
496: && namedParameterValues.size() > 0) {
497: sb.append(Translate
498: .get("request.storedproc.named.parameters.values"));
499: for (int i = 0; i < namedParameterValues.size(); i++) {
500: sb
501: .append(Translate
502: .get(
503: "request.storedproc.named.parameters.value",
504: new String[] {
505: namedParameterValues
506: .keySet()
507: .toArray()[i]
508: .toString(),
509: namedParameterValues
510: .get(
511: namedParameterValues
512: .keySet()
513: .toArray()[i])
514: .toString() }));
515: }
516: }
517: if (outParameterIndexes != null
518: && outParameterIndexes.size() > 0) {
519: sb.append(Translate
520: .get("request.storedproc.out.parameters.indexes"));
521: for (int i = 0; i < outParameterIndexes.size(); i++) {
522: sb.append(Translate.get(
523: "request.storedproc.out.parameters.index",
524: outParameterIndexes.get(i)));
525: }
526: }
527: if (outParameterValues != null && outParameterValues.size() > 0) {
528: sb.append(Translate
529: .get("request.storedproc.out.parameters.values"));
530: for (int i = 0; i < outParameterValues.size(); i++) {
531: sb.append(Translate
532: .get("request.storedproc.out.parameters.value",
533: new String[] {
534: outParameterValues.keySet()
535: .toArray()[i]
536: .toString(),
537: outParameterValues.get(
538: outParameterValues
539: .keySet()
540: .toArray()[i])
541: .toString() }));
542: }
543: }
544: sb.append(Translate
545: .get("request.storedproc.semantic", semantic));
546: sb.append(Translate.get("request.alters", new String[] {
547: String.valueOf(altersAggregateList()),
548: String.valueOf(altersDatabaseCatalog()),
549: String.valueOf(altersDatabaseSchema()),
550: String.valueOf(altersMetadataCache()),
551: String.valueOf(altersQueryResultCache()),
552: String.valueOf(altersSomething()),
553: String.valueOf(altersStoredProcedureList()),
554: String.valueOf(altersUserDefinedTypes()),
555: String.valueOf(altersUsers()) }));
556: return sb.toString();
557: }
558:
559: }
|