001: /* Copyright (c) 2001-2005, The HSQL Development Group
002: * All rights reserved.
003: *
004: * Redistribution and use in source and binary forms, with or without
005: * modification, are permitted provided that the following conditions are met:
006: *
007: * Redistributions of source code must retain the above copyright notice, this
008: * list of conditions and the following disclaimer.
009: *
010: * Redistributions in binary form must reproduce the above copyright notice,
011: * this list of conditions and the following disclaimer in the documentation
012: * and/or other materials provided with the distribution.
013: *
014: * Neither the name of the HSQL Development Group nor the names of its
015: * contributors may be used to endorse or promote products derived from this
016: * software without specific prior written permission.
017: *
018: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
019: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
020: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
021: * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
022: * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
023: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
024: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
025: * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
026: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
027: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
028: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
029: */
030:
031: package org.hsqldb;
032:
033: import java.lang.reflect.Method;
034: import java.lang.reflect.Modifier;
035:
036: import org.hsqldb.lib.HashMap;
037: import org.hsqldb.lib.HashSet;
038: import org.hsqldb.lib.HsqlArrayList;
039: import org.hsqldb.lib.Iterator;
040: import org.hsqldb.lib.WrapperIterator;
041:
042: // boucherb@users - 2004xxxx - patch 1.7.2
043: // -- canonical database uri for catalog name reporting
044: // -- enumXXX methods to iterateXXX
045: // -- simple support for SEQUENCE schema reporting
046: // -- report built-in procedures/procedure columns without dependency on user grants;
047:
048: /**
049: * Provides catalog and schema related definitions and functionality. <p>
050: *
051: * Additional features include accessibility tests, class loading, filtered
052: * iteration and inverted alias mapping functionality regarding Java Classes
053: * and Methods defined within the context of this database name space support
054: * object. <p>
055: *
056: * @author boucherb@users
057: * @version 1.8.0
058: * @since 1.7.2
059: */
060: final class DINameSpace {
061:
062: /** The Database for which the name space functionality is provided */
063: private Database database;
064:
065: /** The catalog name reported by this namespace */
066: private String catalogName;
067:
068: /**
069: * Set { <code>Class</code> FQN <code>String</code> objects }. <p>
070: *
071: * The Set contains the names of the classes providing the public static
072: * methods that are automatically made accessible to the PUBLIC user in
073: * support of the expected SQL CLI scalar functions and other core
074: * HSQLDB SQL functions and stored procedures. <p>
075: */
076: private static HashSet builtin = new HashSet();
077:
078: // procedure columns
079: // make temporary ad-hoc spec a little more "official"
080: // until better system in place
081: static {
082: builtin.add("org.hsqldb.Library");
083: builtin.add("java.lang.Math");
084: }
085:
086: /**
087: * Constructs a new name space support object for the
088: * specified Database object. <p>
089: *
090: * @param database The Database object for which to provide name
091: * space support
092: * @throws HsqlException if a database access error occurs
093: */
094: public DINameSpace(Database database) throws HsqlException {
095:
096: try {
097: this .database = database;
098: this .catalogName = database.getURI();
099: } catch (Exception e) {
100: Trace.throwerror(Trace.GENERAL_ERROR, e.toString());
101: }
102: }
103:
104: /**
105: * Retrieves the declaring <code>Class</code> object for the specified
106: * fully qualified method name, using (if possible) the classLoader
107: * attribute of this object's database. <p>
108: *
109: * @param fqn the fully qualified name of the method for which to
110: * retrieve the declaring <code>Class</code> object.
111: * @return the declaring <code>Class</code> object for the
112: * specified fully qualified method name
113: */
114: Class classForMethodFQN(String fqn) {
115:
116: try {
117: return classForName(fqn.substring(0, fqn.lastIndexOf('.')));
118: } catch (Exception e) {
119: return null;
120: }
121: }
122:
123: /**
124: * Retrieves the <code>Class</code> object specified by the
125: * <code>name</code> argument, using, if possible, the
126: * classLoader attribute of the database. <p>
127: *
128: * @param name the fully qualified name of the <code>Class</code>
129: * object to retrieve.
130: * @throws ClassNotFoundException if the specified class object
131: * cannot be found in the context of this name space
132: * @return the <code>Class</code> object specified by the
133: * <code>name</code> argument
134: */
135: Class classForName(String name) throws ClassNotFoundException {
136:
137: try {
138: if (database.classLoader == null) {
139: return Class.forName(name);
140: } else {
141: if (name != null) {
142: return database.classLoader.loadClass(name);
143: } else {
144: throw new ClassNotFoundException();
145: }
146: }
147: } catch (NoClassDefFoundError err) {
148: throw new ClassNotFoundException(err.toString());
149: }
150: }
151:
152: /**
153: * Retrieves an <code>Iterator</code> whose elements form the set of
154: * distinct names of all visible catalogs, relative to this object's
155: * database. <p>
156: *
157: * If catalog reporting is turned off, then the empty Iterator is
158: * returned. <p>
159: *
160: * <b>Note:</b> in the present implementation, if catalog reporting is
161: * turned on, then the iteration consists of a single element that is the
162: * uri of this object's database; HSQLDB currently does not support the
163: * concept a single engine hosting multiple catalogs. <p>
164: *
165: * @return An Iterator whose elements are <code>String</code> objects
166: * naming all visible catalogs, relative to this object's database.
167: * @throws HsqlException never (reserved for future use)
168: */
169: Iterator iterateCatalogNames() throws HsqlException {
170: return isReportCatalogs() ? new WrapperIterator(catalogName)
171: : new WrapperIterator();
172: }
173:
174: /**
175: * Retrieves the name of the catalog corresponding to the indicated
176: * object. <p>
177: *
178: * <B>Note:</B> the uri of this object's database is returned whenever
179: * catalog reporting is turned on. <p>
180: *
181: * This a stub that will be used until such time (if ever) that the
182: * engine actually supports the concept of multiple hosted
183: * catalogs. <p>
184: *
185: * @return the name of specified object's qualifying catalog, or null if
186: * catalog reporting is turned off.
187: * @param o the object for which the name of its qualifying catalog
188: * is to be retrieved
189: */
190: String getCatalogName(Object o) {
191: return isReportCatalogs() ? catalogName : null;
192: }
193:
194: /**
195: * Retrieves a map from each distinct value of this object's database
196: * SQL routine CALL alias map to the list of keys in the input map
197: * mapping to that value. <p>
198: *
199: * @return The requested map
200: */
201: HashMap getInverseAliasMap() {
202:
203: HashMap mapIn;
204: HashMap mapOut;
205: Iterator keys;
206: Object key;
207: Object value;
208: HsqlArrayList keyList;
209:
210: // TODO:
211: // update Database to dynamically maintain its own
212: // inverse alias map. This will make things *much*
213: // faster for our purposes here, without appreciably
214: // slowing down Database
215: mapIn = database.getAliasMap();
216: mapOut = new HashMap();
217: keys = mapIn.keySet().iterator();
218:
219: while (keys.hasNext()) {
220: key = keys.next();
221: value = mapIn.get(key);
222: keyList = (HsqlArrayList) mapOut.get(value);
223:
224: if (keyList == null) {
225: keyList = new HsqlArrayList();
226:
227: mapOut.put(value, keyList);
228: }
229:
230: keyList.add(key);
231: }
232:
233: return mapOut;
234: }
235:
236: /**
237: * Retrieves the fully qualified name of the given Method object. <p>
238: *
239: * @param m The Method object for which to retreive the fully
240: * qualified name
241: * @return the fully qualified name of the specified Method object.
242: */
243: static String getMethodFQN(Method m) {
244:
245: return m == null ? null : m.getDeclaringClass().getName() + '.'
246: + m.getName();
247: }
248:
249: /**
250: * Retrieves the specific name of the given Method object. <p>
251: *
252: * @param m The Method object for which to retreive the specific name
253: * @return the specific name of the specified Method object.
254: */
255: static String getMethodSpecificName(Method m) {
256:
257: return m == null ? null : m.getDeclaringClass().getName() + '.'
258: + getSignature(m);
259: }
260:
261: static String getSignature(Method method) {
262:
263: StringBuffer sb;
264: String signature;
265: Class[] parmTypes;
266: int len;
267: int last;
268:
269: sb = new StringBuffer();
270: parmTypes = method.getParameterTypes();
271: len = parmTypes.length;
272: last = len - 1;
273:
274: sb.append(method.getName()).append('(');
275:
276: for (int i = 0; i < len; i++) {
277: sb.append(parmTypes[i].getName());
278:
279: if (i < last) {
280: sb.append(',');
281: }
282: }
283:
284: sb.append(')');
285:
286: signature = sb.toString();
287:
288: return signature;
289: }
290:
291: /**
292: * Deprecated
293: */
294: String getSchemaName(Object o) {
295: return database.schemaManager.PUBLIC_SCHEMA;
296: }
297:
298: /**
299: * Adds to the given Set the fully qualified names of the Class objects
300: * internally granted to PUBLIC in support of core operation.
301: *
302: * @param the HashSet to which to add the fully qualified names of
303: * the Class objects internally granted to PUBLIC in support of
304: * core operation.
305: */
306: void addBuiltinToSet(HashSet set) {
307: set.addAll(builtin.toArray(new String[builtin.size()]));
308: }
309:
310: /**
311: * Retrieves whether the indicated Class object is systematically
312: * granted to PUBLIC in support of core operation. <p>
313: *
314: * @return whether the indicated Class object is systematically
315: * granted to PUBLIC in support of core operation
316: * @param clazz The Class object for which to make the determination
317: */
318: boolean isBuiltin(Class clazz) {
319: return clazz == null ? false : builtin
320: .contains(clazz.getName());
321: }
322:
323: /**
324: * Retrieves whether the Class object indicated by the fully qualified
325: * class name is systematically granted to PUBLIC in support of
326: * core operation. <p>
327: *
328: * @return true if system makes grant, else false
329: * @param name fully qualified name of a Class
330: */
331: boolean isBuiltin(String name) {
332: return (name == null) ? false : builtin.contains(name);
333: }
334:
335: /**
336: * Retrieves an <code>Iterator</code> object describing the Java
337: * <code>Method</code> objects that are both the entry points
338: * to executable SQL database objects (such as SQL functions and
339: * stored procedures) within the context of this name space. <p>
340: *
341: * Each element of the <code>Iterator</code> is an Object[3] array
342: * whose elements are: <p>
343: *
344: * <ol>
345: * <li>a <code>Method</code> object.
346: * <li>an <code>HsqlArrayList</code> object whose elements are the SQL call
347: * aliases for the method.
348: * <li>the <code>String</code> "ROUTINE"
349: * </ol>
350: *
351: * <b>Note:</b> Admin users are actually free to invoke *any* public
352: * static non-abstract Java Method that can be found through the database
353: * class loading process, either as a SQL stored procedure or SQL function,
354: * as long as its parameters and return type are compatible with the
355: * engine's supported SQL type / Java <code>Class</code> mappings. <p>
356: *
357: * @return An <code>Iterator</code> object whose elements form the set
358: * of distinct <code>Method</code> objects accessible as
359: * executable as SQL routines within the current execution
360: * context.<p>
361: *
362: * Elements are <code>Object[3]</code> instances, with [0] being a
363: * <code>Method</code> object, [1] being an alias list object and
364: * [2] being the <code>String</code> "ROUTINE"<p>
365: *
366: * If the <code>Method</code> object at index [0] has aliases,
367: * and the <code>andAliases</code> parameter is specified
368: * as <code>true</code>, then there is an HsqlArrayList
369: * at index [1] whose elements are <code>String</code> objects
370: * whose values are the SQL call aliases for the method.
371: * Otherwise, the value of index [1] is <code>null</code>.
372: * @param className The fully qualified name of the class for which to
373: * retrieve the iteration
374: * @param andAliases if <code>true</code>, alias lists for qualifying
375: * methods are additionally retrieved.
376: * @throws HsqlException if a database access error occurs
377: *
378: */
379: Iterator iterateRoutineMethods(String className, boolean andAliases)
380: throws HsqlException {
381:
382: Class clazz;
383: Method[] methods;
384: Method method;
385: int mods;
386: Object[] info;
387: HsqlArrayList aliasList;
388: HsqlArrayList methodList;
389: HashMap invAliasMap;
390:
391: try {
392: clazz = classForName(className);
393: } catch (ClassNotFoundException e) {
394: return new WrapperIterator();
395: }
396:
397: invAliasMap = andAliases ? getInverseAliasMap() : null;
398:
399: // we are interested in inherited methods too,
400: // so we use getDeclaredMethods() first.
401: // However, under Applet execution or
402: // under restrictive SecurityManager policies
403: // this may fail, so we use getMethods()
404: // if getDeclaredMethods() fails.
405: try {
406: methods = clazz.getDeclaredMethods();
407: } catch (Exception e) {
408: methods = clazz.getMethods();
409: }
410:
411: methodList = new HsqlArrayList(methods.length);
412:
413: // add all public static methods to the set
414: for (int i = 0; i < methods.length; i++) {
415: method = methods[i];
416: mods = method.getModifiers();
417:
418: if (!(Modifier.isPublic(mods) && Modifier.isStatic(mods))) {
419: continue;
420: }
421:
422: info = new Object[] { method, null, "ROUTINE" };
423:
424: if (andAliases) {
425: info[1] = invAliasMap.get(getMethodFQN(method));
426: }
427:
428: methodList.add(info);
429: }
430:
431: // return the iterator
432: return methodList.iterator();
433: }
434:
435: /**
436: * Retrieves an <code>Iterator</code> object describing the
437: * fully qualified names of all Java <code>Class</code> objects
438: * that are both trigger body implementations and that are accessible
439: * (whose fire method can potentially be invoked) by actions upon this
440: * object's database by the specified <code>User</code>. <p>
441: *
442: * @param user the <code>User</code> for which to retrieve the
443: * <code>Iterator</code>
444: * @throws HsqlException if a database access error occurs
445: * @return an <code>Iterator</code> object describing the
446: * fully qualified names of all Java <code>Class</code>
447: * objects that are both trigger body implementations
448: * and that are accessible (whose fire method can
449: * potentially be invoked) by actions upon this object's database
450: * by the specified <code>User</code>.
451: */
452: Iterator iterateAccessibleTriggerClassNames(User user)
453: throws HsqlException {
454:
455: Table table;
456: Class clazz;
457: HashSet classSet;
458: TriggerDef triggerDef;
459: HsqlArrayList[] triggerLists;
460: HsqlArrayList triggerList;
461: HsqlArrayList tableList;
462: int listSize;
463:
464: classSet = new HashSet();
465:
466: Iterator schemas = database.schemaManager
467: .userSchemaNameIterator();
468:
469: while (schemas.hasNext()) {
470: String schema = (String) schemas.next();
471: Iterator tables = database.schemaManager
472: .tablesIterator(schema);
473:
474: while (tables.hasNext()) {
475: table = (Table) tables.next();
476:
477: if (!user.isAccessible(table.getName())) {
478: continue;
479: }
480:
481: triggerLists = table.triggerLists;
482:
483: if (triggerLists == null) {
484: continue;
485: }
486:
487: for (int j = 0; j < triggerLists.length; j++) {
488: triggerList = triggerLists[j];
489:
490: if (triggerList == null) {
491: continue;
492: }
493:
494: listSize = triggerList.size();
495:
496: for (int k = 0; k < listSize; k++) {
497: triggerDef = (TriggerDef) triggerList.get(k);
498:
499: if (triggerDef == null
500: || !triggerDef.valid
501: || triggerDef.trigger == null
502: || !user.isAccessible(table.getName(),
503: TriggerDef.indexToRight(k))) {
504: continue;
505: }
506:
507: classSet.add(triggerDef.trigger.getClass()
508: .getName());
509: }
510: }
511: }
512: }
513:
514: return classSet.iterator();
515: }
516:
517: /**
518: * Retrieves a composite <code>Iterator</code> consisting of the elements
519: * from {@link #iterateRoutineMethods} for each Class granted to the
520: * specified session. <p>
521: *
522: * @return a composite <code>Iterator</code> consisting of the elements
523: * from {@link #iterateRoutineMethods} and
524: * {@link #iterateAccessibleTriggerMethods}
525: * @param session The context in which to produce the iterator
526: * @param andAliases true if the alias lists for the "ROUTINE" type method
527: * elements are to be generated.
528: * @throws HsqlException if a database access error occurs
529: */
530: Iterator iterateAllAccessibleMethods(Session session,
531: boolean andAliases) throws HsqlException {
532:
533: Iterator out;
534: HashSet classNameSet;
535: Iterator classNames;
536: Iterator methods;
537: String className;
538:
539: out = new WrapperIterator();
540: classNameSet = session.getUser().getGrantedClassNames(true);
541:
542: addBuiltinToSet(classNameSet);
543:
544: classNames = classNameSet.iterator();
545:
546: while (classNames.hasNext()) {
547: className = (String) classNames.next();
548: methods = iterateRoutineMethods(className, andAliases);
549: out = new WrapperIterator(out, methods);
550: }
551:
552: return out;
553: }
554:
555: /**
556: * Retrieves the set of distinct, visible sessions connected to this
557: * object's database, as a list. <p>
558: *
559: * @param session The context in which to produce the list
560: * @return the set of distinct, visible sessions connected
561: * to this object's database, as a list.
562: */
563: Session[] listVisibleSessions(Session session) {
564: return database.sessionManager.getVisibleSessions(session);
565: }
566:
567: /**
568: * Retrieves whether this object is reporting catalog qualifiers.
569: * @return true if this object is reporting catalog qualifiers, else false.
570: */
571: boolean isReportCatalogs() {
572: return database.getProperties().isPropertyTrue(
573: "hsqldb.catalogs");
574: }
575: }
|