001: /**
002: * com.mckoi.database.ProcedureManager 27 Feb 2003
003: *
004: * Mckoi SQL Database ( http://www.mckoi.com/database )
005: * Copyright (C) 2000, 2001, 2002 Diehl and Associates, Inc.
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License
009: * Version 2 as published by the Free Software Foundation.
010: *
011: * This program is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
014: * GNU General Public License Version 2 for more details.
015: *
016: * You should have received a copy of the GNU General Public License
017: * Version 2 along with this program; if not, write to the Free Software
018: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
019: *
020: * Change Log:
021: *
022: *
023: */package com.mckoi.database;
024:
025: import java.lang.reflect.*;
026: import com.mckoi.database.global.BlobAccessor;
027: import com.mckoi.database.global.StringAccessor;
028: import com.mckoi.util.BigNumber;
029: import java.io.IOException;
030: import java.io.InputStream;
031: import java.util.StringTokenizer;
032: import java.util.ArrayList;
033:
034: /**
035: * A DatabaseConnection procedure manager. This controls adding, updating,
036: * deleting and querying/calling stored procedures.
037: *
038: * @author Tobias Downer
039: */
040:
041: public class ProcedureManager {
042:
043: /**
044: * The DatabaseConnection.
045: */
046: private DatabaseConnection connection;
047:
048: /**
049: * The context.
050: */
051: private DatabaseQueryContext context;
052:
053: /**
054: * Constructs the ProcedureManager for a DatabaseConnection.
055: */
056: ProcedureManager(DatabaseConnection connection) {
057: this .connection = connection;
058: this .context = new DatabaseQueryContext(connection);
059: }
060:
061: /**
062: * Given the SYS_FUNCTION table, this returns a new table that contains the
063: * entry with the given procedure name, or an empty result if nothing found.
064: * Generates an error if more than 1 entry found.
065: */
066: private Table findProcedureEntry(DataTable table,
067: ProcedureName procedure_name) {
068:
069: Operator EQUALS = Operator.get("=");
070:
071: Variable schemav = table.getResolvedVariable(0);
072: Variable namev = table.getResolvedVariable(1);
073:
074: Table t = table.simpleSelect(context, namev, EQUALS,
075: new Expression(TObject.stringVal(procedure_name
076: .getName())));
077: t = t.exhaustiveSelect(context, Expression.simple(schemav,
078: EQUALS, TObject.stringVal(procedure_name.getSchema())));
079:
080: // This should be at most 1 row in size
081: if (t.getRowCount() > 1) {
082: throw new RuntimeException(
083: "Assert failed: multiple procedure names for "
084: + procedure_name);
085: }
086:
087: // Return the entries found.
088: return t;
089:
090: }
091:
092: /**
093: * Formats a string that gives information about the procedure, return
094: * type and param types.
095: */
096: private static String procedureInfoString(ProcedureName name,
097: TType ret, TType[] params) {
098: StringBuffer buf = new StringBuffer();
099: if (ret != null) {
100: buf.append(ret.asSQLString());
101: buf.append(" ");
102: }
103: buf.append(name.getName());
104: buf.append("(");
105: for (int i = 0; i < params.length; ++i) {
106: buf.append(params[i].asSQLString());
107: if (i < params.length - 1) {
108: buf.append(", ");
109: }
110: }
111: buf.append(")");
112: return new String(buf);
113: }
114:
115: /**
116: * Given a location string as defined for a Java stored procedure, this
117: * parses the string into the various parts. For example, given the
118: * string 'com.mycompany.storedprocedures.MyFunctions.minFunction()' this
119: * will parse the string out to the class called
120: * 'com.mycompany.storedprocedures.MyFunctions' and the method 'minFunction'
121: * with no arguments. This function will work event if the method name is
122: * not given, or the method name does not have an arguments specification.
123: */
124: public static String[] parseJavaLocationString(final String str) {
125: // Look for the first parenthese
126: int parenthese_delim = str.indexOf("(");
127: String class_method;
128:
129: if (parenthese_delim != -1) {
130: // This represents class/method
131: class_method = str.substring(0, parenthese_delim);
132: // This will be deliminated by a '.'
133: int method_delim = class_method.lastIndexOf(".");
134: if (method_delim == -1) {
135: throw new StatementException(
136: "Incorrectly formatted Java method string: "
137: + str);
138: }
139: String class_str = class_method.substring(0, method_delim);
140: String method_str = class_method
141: .substring(method_delim + 1);
142: // Next parse the argument list
143: int end_parenthese_delim = str.lastIndexOf(")");
144: if (end_parenthese_delim == -1) {
145: throw new StatementException(
146: "Incorrectly formatted Java method string: "
147: + str);
148: }
149: String arg_list_str = str.substring(parenthese_delim + 1,
150: end_parenthese_delim);
151: // Now parse the list of arguments
152: ArrayList arg_list = new ArrayList();
153: StringTokenizer tok = new StringTokenizer(arg_list_str, ",");
154: while (tok.hasMoreTokens()) {
155: String arg = tok.nextToken();
156: arg_list.add(arg);
157: }
158:
159: // Form the parsed array and return it
160: int sz = arg_list.size();
161: String[] return_array = new String[2 + sz];
162: return_array[0] = class_str;
163: return_array[1] = method_str;
164: for (int i = 0; i < sz; ++i) {
165: return_array[i + 2] = (String) arg_list.get(i);
166: }
167: return return_array;
168:
169: } else {
170: // No parenthese so we assume this is a java class
171: return new String[] { str };
172: }
173:
174: }
175:
176: /**
177: * Returns true if the procedure with the given name exists.
178: */
179: public boolean procedureExists(ProcedureName procedure_name) {
180:
181: DataTable table = connection.getTable(Database.SYS_FUNCTION);
182: return findProcedureEntry(table, procedure_name).getRowCount() == 1;
183:
184: }
185:
186: /**
187: * Returns true if the procedure with the given table name exists.
188: */
189: public boolean procedureExists(TableName procedure_name) {
190: return procedureExists(new ProcedureName(procedure_name));
191: }
192:
193: /**
194: * Defines a Java stored procedure. If the procedure with the name has not
195: * been defined it is defined. If the procedure has been defined then it is
196: * overwritten with this information.
197: * <p>
198: * If 'return_type' is null then the procedure does not return a value.
199: */
200: public void defineJavaProcedure(ProcedureName procedure_name,
201: String java_specification, TType return_type,
202: TType[] param_types, String username)
203: throws DatabaseException {
204:
205: TableName proc_table_name = new TableName(procedure_name
206: .getSchema(), procedure_name.getName());
207:
208: // Check this name is not reserved
209: DatabaseConnection.checkAllowCreate(proc_table_name);
210:
211: DataTable table = connection.getTable(Database.SYS_FUNCTION);
212:
213: // The new row to insert/update
214: RowData row_data = new RowData(table);
215: row_data.setColumnDataFromObject(0, procedure_name.getSchema());
216: row_data.setColumnDataFromObject(1, procedure_name.getName());
217: row_data.setColumnDataFromObject(2, "Java-1");
218: row_data.setColumnDataFromObject(3, java_specification);
219: if (return_type != null) {
220: row_data.setColumnDataFromObject(4, TType
221: .asEncodedString(return_type));
222: }
223: row_data.setColumnDataFromObject(5, TType
224: .asEncodedString(param_types));
225: row_data.setColumnDataFromObject(6, username);
226:
227: // Find the entry from the procedure table that equal this name
228: Table t = findProcedureEntry(table, procedure_name);
229:
230: // Delete the entry if it already exists.
231: if (t.getRowCount() == 1) {
232: table.delete(t);
233: }
234:
235: // Insert the new entry,
236: table.add(row_data);
237:
238: // Notify that this database object has been successfully created.
239: connection.databaseObjectCreated(proc_table_name);
240:
241: }
242:
243: /**
244: * Deletes the procedure with the given name, or generates an error if the
245: * procedure doesn't exist.
246: */
247: public void deleteProcedure(ProcedureName procedure_name)
248: throws DatabaseException {
249:
250: DataTable table = connection.getTable(Database.SYS_FUNCTION);
251:
252: // Find the entry from the procedure table that equal this name
253: Table t = findProcedureEntry(table, procedure_name);
254:
255: // If no entries then generate error.
256: if (t.getRowCount() == 0) {
257: throw new StatementException("Procedure " + procedure_name
258: + " doesn't exist.");
259: }
260:
261: table.delete(t);
262:
263: // Notify that this database object has been successfully dropped.
264: connection.databaseObjectDropped(new TableName(procedure_name
265: .getSchema(), procedure_name.getName()));
266:
267: }
268:
269: /**
270: * Returns an InternalTableInfo object used to model the list of procedures
271: * that are accessible within the given Transaction object. This is used to
272: * model all procedures that have been defined as tables.
273: */
274: static InternalTableInfo createInternalTableInfo(
275: Transaction transaction) {
276: return new ProcedureInternalTableInfo(transaction);
277: }
278:
279: /**
280: * Invokes the procedure with the given name and the given parameters and
281: * returns the procedure return value.
282: */
283: public TObject invokeProcedure(ProcedureName procedure_name,
284: TObject[] params) {
285:
286: DataTable table = connection.getTable(Database.SYS_FUNCTION);
287:
288: // Find the entry from the procedure table that equals this name
289: Table t = findProcedureEntry(table, procedure_name);
290: if (t.getRowCount() == 0) {
291: throw new StatementException("Procedure " + procedure_name
292: + " doesn't exist.");
293: }
294:
295: int row_index = t.rowEnumeration().nextRowIndex();
296: TObject type_ob = t.getCellContents(2, row_index);
297: TObject location_ob = t.getCellContents(3, row_index);
298: TObject return_type_ob = t.getCellContents(4, row_index);
299: TObject param_types_ob = t.getCellContents(5, row_index);
300: TObject owner_ob = t.getCellContents(6, row_index);
301:
302: String type = type_ob.getObject().toString();
303: String location = location_ob.getObject().toString();
304: TType return_type = null;
305: if (!return_type_ob.isNull()) {
306: return_type = TType.decodeString(return_type_ob.getObject()
307: .toString());
308: }
309: TType[] param_types = TType.decodeTypes(param_types_ob
310: .getObject().toString());
311: String owner = owner_ob.getObject().toString();
312:
313: // Check the number of parameters given match the function parameters length
314: if (params.length != param_types.length) {
315: throw new StatementException(
316: "Parameters given do not match the parameters of the procedure: "
317: + procedureInfoString(procedure_name,
318: return_type, param_types));
319: }
320:
321: // The different procedure types,
322: if (type.equals("Java-1")) {
323: return invokeJavaV1Procedure(procedure_name, location,
324: return_type, param_types, owner, params);
325: } else {
326: throw new RuntimeException("Unknown procedure type: "
327: + type);
328: }
329:
330: }
331:
332: /**
333: * Resolves a Java class specification string to a Java Class object. For
334: * example, "String" becomes 'java.lang.String.class' and "boolean[]" becomes
335: * 'boolean[].class', etc.
336: */
337: private static Class resolveToClass(String java_spec) {
338: // Trim the string
339: java_spec = java_spec.trim();
340: // Is this an array? Count the number of array dimensions.
341: int dimensions = -1;
342: int last_index = java_spec.length();
343: while (last_index > 0) {
344: ++dimensions;
345: last_index = java_spec.lastIndexOf("[]", last_index) - 1;
346: }
347: // Remove the array part
348: int array_end = java_spec.length() - (dimensions * 2);
349: String class_part = java_spec.substring(0, array_end);
350: // Check there's no array parts in the class part
351: if (class_part.indexOf("[]") != -1) {
352: throw new RuntimeException(
353: "Java class specification incorrectly formatted: "
354: + java_spec);
355: }
356:
357: // Convert the java specification to a Java class. For example,
358: // String is converted to java.lang.String.class, etc.
359: Class cl;
360: // Is there a '.' in the class specification?
361: if (class_part.indexOf(".") != -1) {
362: // Must be a specification such as 'java.net.URL' or 'java.util.List'.
363: try {
364: cl = Class.forName(class_part);
365: } catch (ClassNotFoundException i) {
366: throw new RuntimeException("Java class not found: "
367: + class_part);
368: }
369: }
370:
371: // Try for a primitive types
372: else if (class_part.equals("boolean")) {
373: cl = boolean.class;
374: } else if (class_part.equals("byte")) {
375: cl = byte.class;
376: } else if (class_part.equals("short")) {
377: cl = short.class;
378: } else if (class_part.equals("char")) {
379: cl = char.class;
380: } else if (class_part.equals("int")) {
381: cl = int.class;
382: } else if (class_part.equals("long")) {
383: cl = long.class;
384: } else if (class_part.equals("float")) {
385: cl = float.class;
386: } else if (class_part.equals("double")) {
387: cl = double.class;
388: } else {
389: // Not a primitive type so try resolving against java.lang.* or some
390: // key classes in com.mckoi.database.*
391: if (class_part.equals("ProcedureConnection")) {
392: cl = ProcedureConnection.class;
393: } else {
394: try {
395: cl = Class.forName("java.lang." + class_part);
396: } catch (ClassNotFoundException i) {
397: // No luck so give up,
398: throw new RuntimeException("Java class not found: "
399: + class_part);
400: }
401: }
402: }
403:
404: // Finally make into a dimension if necessary
405: if (dimensions > 0) {
406: // This is a little untidy way of doing this. Perhaps a better approach
407: // would be to make an array encoded string (eg. "[[Ljava.langString;").
408: cl = java.lang.reflect.Array.newInstance(cl,
409: new int[dimensions]).getClass();
410: }
411:
412: return cl;
413:
414: }
415:
416: /**
417: * Given a Java location_str and a list of parameter types, returns an
418: * immutable 'Method' object that can be used to invoke a Java stored
419: * procedure. The returned object can be cached if necessary. Note that
420: * this method will generate an error for the following situations:
421: * a) The invokation class or method was not found, b) there is not an
422: * invokation method with the required number of arguments or that matches
423: * the method specification.
424: * <p>
425: * Returns null if the invokation method could not be found.
426: */
427: public static Method javaProcedureMethod(String location_str,
428: TType[] param_types) {
429: // Parse the location string
430: String[] loc_parts = parseJavaLocationString(location_str);
431:
432: // The name of the class
433: String class_name;
434: // The name of the invokation method in the class.
435: String method_name;
436: // The object specification that must be matched. If any entry is 'null'
437: // then the argument parameter is discovered.
438: Class[] object_specification;
439: boolean firstProcedureConnectionIgnore;
440:
441: if (loc_parts.length == 1) {
442: // This means the location_str only specifies a class name, so we use
443: // 'invoke' as the static method to call, and discover the arguments.
444: class_name = loc_parts[0];
445: method_name = "invoke";
446: // All null which means we discover the arg types dynamically
447: object_specification = new Class[param_types.length];
448: // ignore ProcedureConnection is first argument
449: firstProcedureConnectionIgnore = true;
450: } else {
451: // This means we specify a class and method name and argument
452: // specification.
453: class_name = loc_parts[0];
454: method_name = loc_parts[1];
455: object_specification = new Class[loc_parts.length - 2];
456:
457: for (int i = 0; i < loc_parts.length - 2; ++i) {
458: String java_spec = loc_parts[i + 2];
459: object_specification[i] = resolveToClass(java_spec);
460: }
461:
462: firstProcedureConnectionIgnore = false;
463: }
464:
465: Class procedure_class;
466: try {
467: // Reference the procedure's class.
468: procedure_class = Class.forName(class_name);
469: } catch (ClassNotFoundException e) {
470: throw new RuntimeException("Procedure class not found: "
471: + class_name);
472: }
473:
474: // Get all the methods in this class
475: Method[] methods = procedure_class.getMethods();
476: Method invoke_method = null;
477: // Search for the invoke method
478: for (int i = 0; i < methods.length; ++i) {
479: Method method = methods[i];
480: int modifier = method.getModifiers();
481:
482: if (Modifier.isStatic(modifier)
483: && Modifier.isPublic(modifier)
484: && method.getName().equals(method_name)) {
485:
486: boolean params_match;
487:
488: // Get the parameters for this method
489: Class[] method_args = method.getParameterTypes();
490: // If no methods, and object_specification has no args then this is a
491: // match.
492: if (method_args.length == 0
493: && object_specification.length == 0) {
494: params_match = true;
495: } else {
496: int search_start = 0;
497: // Is the first arugments a ProcedureConnection implementation?
498: if (firstProcedureConnectionIgnore
499: && ProcedureConnection.class
500: .isAssignableFrom(method_args[0])) {
501: search_start = 1;
502: }
503: // Do the number of arguments match
504: if (object_specification.length == method_args.length
505: - search_start) {
506: // Do they match the specification?
507: boolean match_spec = true;
508: for (int n = 0; n < object_specification.length
509: && match_spec == true; ++n) {
510: Class ob_spec = object_specification[n];
511: if (ob_spec != null
512: && ob_spec != method_args[n
513: + search_start]) {
514: match_spec = false;
515: }
516: }
517: params_match = match_spec;
518: } else {
519: params_match = false;
520: }
521: }
522:
523: if (params_match) {
524: if (invoke_method == null) {
525: invoke_method = method;
526: } else {
527: throw new RuntimeException(
528: "Ambiguous public static "
529: + method_name
530: + " methods in stored procedure class '"
531: + class_name + "'");
532: }
533: }
534:
535: }
536:
537: }
538:
539: // Return the invoke method we found
540: return invoke_method;
541:
542: }
543:
544: // ---------- Various procedure type invokation methods ----------
545:
546: /**
547: * Invokes a Java (type 1) procedure. A type 1 procedure is represented by
548: * a single class with a static invokation method (called invoke). The
549: * parameters of the static 'invoke' method must be compatible class
550: * parameters defined for the procedure, and the return class must also be
551: * compatible with the procedure return type.
552: * <p>
553: * If the invoke method does not contain arguments that are compatible with
554: * the parameters given an exception is generated.
555: * <p>
556: * The class must only have a single public static 'invoke' method. If there
557: * are multiple 'invoke' methods a runtime exception is generated.
558: */
559: private TObject invokeJavaV1Procedure(ProcedureName procedure_name,
560: String location_str, TType return_type,
561: TType[] param_types, String owner, TObject[] param_values) {
562:
563: // Search for the invokation method for this stored procedure
564: Method invoke_method = javaProcedureMethod(location_str,
565: param_types);
566:
567: // Did we find an invoke method?
568: if (invoke_method == null) {
569: throw new RuntimeException(
570: "Could not find the invokation method for "
571: + "the Java location string '"
572: + location_str + "'");
573: }
574:
575: // Go through each argument of this class and work out how we are going
576: // cast from the database engine object to the Java object.
577: Class[] java_param_types = invoke_method.getParameterTypes();
578:
579: // Is the first param a ProcedureConnection implementation?
580: int start_param;
581: Object[] java_values;
582: if (java_param_types.length > 0
583: && ProcedureConnection.class
584: .isAssignableFrom(java_param_types[0])) {
585: start_param = 1;
586: java_values = new Object[param_types.length + 1];
587: } else {
588: start_param = 0;
589: java_values = new Object[param_types.length];
590: }
591:
592: // For each type
593: for (int i = 0; i < param_types.length; ++i) {
594: TObject value = param_values[i];
595: TType proc_type = param_types[i];
596: Class java_type = java_param_types[i + start_param];
597: String java_type_str = java_type.getName();
598:
599: // First null check,
600: if (value.isNull()) {
601: java_values[i + start_param] = null;
602: } else {
603: TType value_type = value.getTType();
604: // If not null, is the value and the procedure type compatible
605: if (proc_type.comparableTypes(value_type)) {
606:
607: boolean error_cast = false;
608: Object cast_value = null;
609:
610: // Compatible types,
611: // Now we need to convert the parameter value into a Java object,
612: if (value_type instanceof TStringType) {
613: // A String type can be represented in Java as a java.lang.String,
614: // or as a java.io.Reader.
615: StringAccessor accessor = (StringAccessor) value
616: .getObject();
617: if (java_type == java.lang.String.class) {
618: cast_value = accessor.toString();
619: } else if (java_type == java.io.Reader.class) {
620: cast_value = accessor.getReader();
621: } else {
622: error_cast = true;
623: }
624: } else if (value_type instanceof TBooleanType) {
625: // A boolean in Java is either java.lang.Boolean or primitive
626: // boolean.
627: if (java_type == java.lang.Boolean.class
628: || java_type == boolean.class) {
629: cast_value = value.getObject();
630: } else {
631: error_cast = true;
632: }
633: } else if (value_type instanceof TDateType) {
634: // A date translates to either java.util.Date, java.sql.Date,
635: // java.sql.Timestamp, java.sql.Time.
636: java.util.Date d = (java.util.Date) value
637: .getObject();
638: if (java_type == java.util.Date.class) {
639: cast_value = d;
640: } else if (java_type == java.sql.Date.class) {
641: cast_value = new java.sql.Date(d.getTime());
642: } else if (java_type == java.sql.Time.class) {
643: cast_value = new java.sql.Time(d.getTime());
644: } else if (java_type == java.sql.Timestamp.class) {
645: cast_value = new java.sql.Timestamp(d
646: .getTime());
647: } else {
648: error_cast = true;
649: }
650: } else if (value_type instanceof TNumericType) {
651: // Number can be cast to any one of the Java numeric types
652: BigNumber num = (BigNumber) value.getObject();
653: if (java_type == BigNumber.class) {
654: cast_value = num;
655: } else if (java_type == java.lang.Byte.class
656: || java_type == byte.class) {
657: cast_value = new Byte(num.byteValue());
658: } else if (java_type == java.lang.Short.class
659: || java_type == short.class) {
660: cast_value = new Short(num.shortValue());
661: } else if (java_type == java.lang.Integer.class
662: || java_type == int.class) {
663: cast_value = new Integer(num.intValue());
664: } else if (java_type == java.lang.Long.class
665: || java_type == long.class) {
666: cast_value = new Long(num.longValue());
667: } else if (java_type == java.lang.Float.class
668: || java_type == float.class) {
669: cast_value = new Float(num.floatValue());
670: } else if (java_type == java.lang.Double.class
671: || java_type == double.class) {
672: cast_value = new Double(num.doubleValue());
673: } else if (java_type == java.math.BigDecimal.class) {
674: cast_value = num.asBigDecimal();
675: } else {
676: error_cast = true;
677: }
678: } else if (value_type instanceof TBinaryType) {
679: // A binary type can translate to a java.io.InputStream or a
680: // byte[] array.
681: BlobAccessor blob = (BlobAccessor) value
682: .getObject();
683: if (java_type == java.io.InputStream.class) {
684: cast_value = blob.getInputStream();
685: } else if (java_type == byte[].class) {
686: byte[] buf = new byte[blob.length()];
687: try {
688: InputStream in = blob.getInputStream();
689: int n = 0;
690: int len = blob.length();
691: while (len > 0) {
692: int count = in.read(buf, n, len);
693: if (count == -1) {
694: throw new IOException(
695: "End of stream.");
696: }
697: n += count;
698: len -= count;
699: }
700: } catch (IOException e) {
701: throw new RuntimeException("IO Error: "
702: + e.getMessage());
703: }
704: cast_value = buf;
705: } else {
706: error_cast = true;
707: }
708:
709: }
710:
711: // If the cast of the parameter was not possible, report the error.
712: if (error_cast) {
713: throw new StatementException(
714: "Unable to cast argument "
715: + i
716: + " ... "
717: + value_type.asSQLString()
718: + " to "
719: + java_type_str
720: + " for procedure: "
721: + procedureInfoString(
722: procedure_name,
723: return_type,
724: param_types));
725: }
726:
727: // Set the java value for this parameter
728: java_values[i + start_param] = cast_value;
729:
730: } else {
731: // The parameter is not compatible -
732: throw new StatementException("Parameter ("
733: + i
734: + ") not compatible "
735: + value.getTType().asSQLString()
736: + " -> "
737: + proc_type.asSQLString()
738: + " for procedure: "
739: + procedureInfoString(procedure_name,
740: return_type, param_types));
741: }
742:
743: } // if not null
744:
745: } // for each parameter
746:
747: // Create the user that has the privs of this procedure.
748: User priv_user = new User(owner, connection.getDatabase(),
749: "/Internal/Procedure/", System.currentTimeMillis());
750:
751: // Create the ProcedureConnection object.
752: ProcedureConnection proc_connection = connection
753: .createProcedureConnection(priv_user);
754: Object result;
755: try {
756: // Now the 'connection' will be set to the owner's user privs.
757:
758: // Set the ProcedureConnection object as an argument if necessary.
759: if (start_param > 0) {
760: java_values[0] = proc_connection;
761: }
762:
763: // The java_values array should now contain the parameter values formatted
764: // as Java objects.
765:
766: // Invoke the method
767: try {
768: result = invoke_method.invoke(null, java_values);
769: } catch (IllegalAccessException e) {
770: connection.Debug().writeException(e);
771: throw new StatementException(
772: "Illegal access exception when invoking "
773: + "stored procedure: " + e.getMessage());
774: } catch (InvocationTargetException e) {
775: Throwable real_e = e.getTargetException();
776: connection.Debug().writeException(real_e);
777: throw new StatementException("Procedure Exception: "
778: + real_e.getMessage());
779: }
780:
781: } finally {
782: connection.disposeProcedureConnection(proc_connection);
783: }
784:
785: // If return_type is null, there is no result from this procedure (void)
786: if (return_type == null) {
787: return null;
788: } else {
789: // Cast to a valid return object and return.
790: return TObject.createAndCastFromObject(return_type, result);
791: }
792:
793: }
794:
795: // ---------- Inner classes ----------
796:
797: /**
798: * An object that models the list of procedures as table objects in a
799: * transaction.
800: */
801: private static class ProcedureInternalTableInfo extends
802: AbstractInternalTableInfo2 {
803:
804: ProcedureInternalTableInfo(Transaction transaction) {
805: super (transaction, Database.SYS_FUNCTION);
806: }
807:
808: private static DataTableDef createDataTableDef(String schema,
809: String name) {
810: // Create the DataTableDef that describes this entry
811: DataTableDef def = new DataTableDef();
812: def.setTableName(new TableName(schema, name));
813:
814: // Add column definitions
815: def
816: .addColumn(DataTableColumnDef
817: .createStringColumn("type"));
818: def.addColumn(DataTableColumnDef
819: .createStringColumn("location"));
820: def.addColumn(DataTableColumnDef
821: .createStringColumn("return_type"));
822: def.addColumn(DataTableColumnDef
823: .createStringColumn("param_args"));
824: def.addColumn(DataTableColumnDef
825: .createStringColumn("owner"));
826:
827: // Set to immutable
828: def.setImmutable();
829:
830: // Return the data table def
831: return def;
832: }
833:
834: public String getTableType(int i) {
835: return "FUNCTION";
836: }
837:
838: public DataTableDef getDataTableDef(int i) {
839: TableName table_name = getTableName(i);
840: return createDataTableDef(table_name.getSchema(),
841: table_name.getName());
842: }
843:
844: public MutableTableDataSource createInternalTable(int index) {
845: MutableTableDataSource table = transaction
846: .getTable(Database.SYS_FUNCTION);
847: RowEnumeration row_e = table.rowEnumeration();
848: int p = 0;
849: int i;
850: int row_i = -1;
851: while (row_e.hasMoreRows()) {
852: i = row_e.nextRowIndex();
853: if (p == index) {
854: row_i = i;
855: } else {
856: ++p;
857: }
858: }
859: if (p == index) {
860: String schema = table.getCellContents(0, row_i)
861: .getObject().toString();
862: String name = table.getCellContents(1, row_i)
863: .getObject().toString();
864:
865: final DataTableDef table_def = createDataTableDef(
866: schema, name);
867: final TObject type = table.getCellContents(2, row_i);
868: final TObject location = table
869: .getCellContents(3, row_i);
870: final TObject return_type = table.getCellContents(4,
871: row_i);
872: final TObject param_types = table.getCellContents(5,
873: row_i);
874: final TObject owner = table.getCellContents(6, row_i);
875:
876: // Implementation of MutableTableDataSource that describes this
877: // procedure.
878: return new GTDataSource(transaction.getSystem()) {
879: public DataTableDef getDataTableDef() {
880: return table_def;
881: }
882:
883: public int getRowCount() {
884: return 1;
885: }
886:
887: public TObject getCellContents(int col, int row) {
888: switch (col) {
889: case 0:
890: return type;
891: case 1:
892: return location;
893: case 2:
894: return return_type;
895: case 3:
896: return param_types;
897: case 4:
898: return owner;
899: default:
900: throw new RuntimeException(
901: "Column out of bounds.");
902: }
903: }
904: };
905:
906: } else {
907: throw new RuntimeException("Index out of bounds.");
908: }
909:
910: }
911:
912: }
913:
914: }
|