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 AmicoSoft, Inc. dba Emic Networks
006: * Copyright (C) 2005-2006 Continuent, Inc.
007: * Contact: sequoia@continuent.org
008: *
009: * Licensed under the Apache License, Version 2.0 (the "License");
010: * you may not use this file except in compliance with the License.
011: * You may obtain a copy of the License at
012: *
013: * http://www.apache.org/licenses/LICENSE-2.0
014: *
015: * Unless required by applicable law or agreed to in writing, software
016: * distributed under the License is distributed on an "AS IS" BASIS,
017: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
018: * See the License for the specific language governing permissions and
019: * limitations under the License.
020: *
021: * Initial developer(s): Emmanuel Cecchet.
022: * Contributor(s): Heiko Schramm.
023: */package org.continuent.sequoia.common.sql.schema;
024:
025: import java.io.Serializable;
026: import java.sql.SQLException;
027: import java.util.ArrayList;
028: import java.util.ConcurrentModificationException;
029: import java.util.HashMap;
030: import java.util.Iterator;
031: import java.util.List;
032:
033: import org.continuent.sequoia.common.locks.TransactionLogicalLock;
034: import org.continuent.sequoia.common.xml.DatabasesXmlTags;
035: import org.continuent.sequoia.controller.requests.AbstractRequest;
036:
037: /**
038: * A <code>DatabaseSchema</code> describes all the tables and columns of a
039: * database.
040: *
041: * @author <a href="mailto:Emmanuel.Cecchet@inria.fr">Emmanuel Cecchet </a>
042: * @author <a href="mailto:Nicolas.Modrzyk@inrialpes.fr">Nicolas Modrzyk </a>
043: * @version 1.0
044: */
045: public class DatabaseSchema implements Serializable {
046: private static final long serialVersionUID = 1105453274994163661L;
047:
048: /**
049: * Virtual database name with a dot at the end (e.g. 'mydb.') that can be used
050: * as a prefix in the table names
051: */
052: private String vdbNameWithDot;
053: /** <code>HashMap</code> of <code>DatabaseTables</code>. */
054: private HashMap tables;
055: /** <code>HashMap</code> of <code>DatabaseProcedures</code>. */
056: private HashMap procedures;
057:
058: /** Lock for this schema */
059: private transient TransactionLogicalLock lock = new TransactionLogicalLock();
060:
061: /**
062: * Creates a new <code>DatabaseSchema</code> instance.
063: *
064: * @param vdbName the virtual database name this schema represents
065: */
066: public DatabaseSchema(String vdbName) {
067: this .vdbNameWithDot = vdbName + ".";
068: tables = new HashMap();
069: procedures = new HashMap();
070: }
071:
072: /**
073: * Creates a new <code>DatabaseSchema</code> instance with a specified
074: * number of tables.
075: *
076: * @param vdbName the virtual database name this schema represents
077: * @param nbOfTables an <code>int</code> value
078: */
079: public DatabaseSchema(String vdbName, int nbOfTables) {
080: this .vdbNameWithDot = vdbName + ".";
081: tables = new HashMap(nbOfTables);
082: procedures = new HashMap();
083: }
084:
085: /**
086: * Creates a new <code>DatabaseSchema</code> instance from an existing
087: * database schema (the schema is cloned).
088: *
089: * @param schema the existing database schema
090: */
091: public DatabaseSchema(DatabaseSchema schema) {
092: if (schema == null)
093: throw new IllegalArgumentException(
094: "Illegal null database schema in DatabaseSchema(DatabaseSchema) constructor");
095:
096: vdbNameWithDot = schema.getVirtualDatabaseName();
097: tables = new HashMap(schema.getTables());
098: procedures = new HashMap(schema.getProcedures());
099: }
100:
101: /**
102: * Adds a <code>DatabaseTable</code> describing a table of the database.
103: *
104: * @param table the table to add
105: */
106: public synchronized void addTable(DatabaseTable table) {
107: if (table == null)
108: throw new IllegalArgumentException(
109: "Illegal null database table in addTable(DatabaseTable) method");
110: tables.put(table.getName(), table);
111: if (table.getSchema() != null)
112: tables
113: .put(table.getSchema() + "." + table.getName(),
114: table);
115: }
116:
117: /**
118: * Adds a <code>DatabaseProcedure</code> describing a procedure of the
119: * database.
120: *
121: * @param procedure the procedure to add
122: */
123: public synchronized void addProcedure(DatabaseProcedure procedure) {
124: if (procedure == null)
125: throw new IllegalArgumentException(
126: "Illegal null database table in addTable(DatabaseTable) method");
127: String key = procedure.getKey();
128: int semicolon = key.indexOf(';');
129: if (semicolon > 0) { // The procedure includes a version number (like in Sybase). If key is
130: // myproc;1.4(2), also add myproc(2) in the list
131: String keyWithoutVersionNumber = key
132: .substring(0, semicolon)
133: + key.substring(procedure.getName().length());
134: procedures.put(keyWithoutVersionNumber, procedure);
135: }
136: procedures.put(key, procedure);
137: }
138:
139: /**
140: * Returns true if all tables are not locked by anyone.
141: *
142: * @param request request trying to execute (to indicate if it is in
143: * autocommit mode and the transaction id)
144: * @return true if all tables are unlocked.
145: */
146: public boolean allTablesAreUnlockedOrLockedByTransaction(
147: AbstractRequest request) {
148: // Optimistic approach where we use an iterator and if the tables are
149: // modified concurrently, iter.next() will fail and we will restart the
150: // search.
151: boolean retry;
152: do {
153: retry = false;
154: try {
155: for (Iterator iter = tables.values().iterator(); iter
156: .hasNext();) {
157: TransactionLogicalLock l = ((DatabaseTable) iter
158: .next()).getLock();
159: if (l.isLocked()) {
160: // If the lock is held by another transaction then this is not ok
161: if (request.getTransactionId() != l.getLocker())
162: return false;
163: }
164: }
165: } catch (ConcurrentModificationException e) {
166: retry = true;
167: }
168: } while (retry);
169: return true;
170: }
171:
172: /**
173: * Lock all tables that are not already locked by this transaction (assumes
174: * that locks are free).
175: *
176: * @param request request trying to execute (to indicate if it is in
177: * autocommit mode and the transaction id)
178: * @return list of locks acquired (excluding locks already acquired before
179: * this method was called)
180: */
181: public List lockAllTables(AbstractRequest request) {
182: // Optimistic approach where we use an iterator and if the tables are
183: // modified concurrently, iter.next() will fail and we will restart the
184: // search.
185: boolean retry;
186: List acquiredLocks = new ArrayList();
187: do {
188: retry = false;
189: try {
190: for (Iterator iter = tables.values().iterator(); iter
191: .hasNext();) {
192: TransactionLogicalLock l = ((DatabaseTable) iter
193: .next()).getLock();
194: if (!l.isLocked()) {
195: l.acquire(request);
196: acquiredLocks.add(l);
197: }
198: }
199: } catch (ConcurrentModificationException e) {
200: retry = true;
201: }
202: } while (retry);
203: return acquiredLocks;
204: }
205:
206: /**
207: * Returns the lock for this table.
208: *
209: * @return a <code>TransactionLogicalLock</code> instance
210: */
211: public TransactionLogicalLock getLock() {
212: return lock;
213: }
214:
215: /**
216: * When the database schema is reloaded, the locks held by active transactions
217: * must be retained.
218: *
219: * @param oldSchema the previous version of the schema.
220: */
221: public void setLocks(DatabaseSchema oldSchema) {
222: lock = oldSchema.lock;
223: for (Iterator iter = tables.values().iterator(); iter.hasNext();) {
224: DatabaseTable table = (DatabaseTable) iter.next();
225: DatabaseTable oldTable = oldSchema.getTable(
226: table.getName(), true);
227: if (oldTable != null)
228: table.setLock(oldTable);
229: }
230: }
231:
232: /**
233: * Returns the <code>DatabaseProcedure</code> object matching the given
234: * procedure name or <code>null</code> if not found.
235: *
236: * @param procedureKey the procedure key to look for
237: * @return a <code>DatabaseProcedure</code> value or null
238: */
239: public DatabaseProcedure getProcedure(String procedureKey) {
240: DatabaseProcedure proc = null;
241: if (procedureKey == null)
242: return null;
243:
244: synchronized (procedures) {
245: proc = (DatabaseProcedure) procedures.get(procedureKey);
246: // If we don't find the stored procedure in the procedures hashmap using
247: // case sensitive matching, we try to find it in lower case.
248: if (proc == null)
249: proc = (DatabaseProcedure) procedures.get(procedureKey
250: .toLowerCase());
251: return proc;
252: }
253: }
254:
255: /**
256: * Returns the <code>DatabaseProcedure</code> object matching the given
257: * procedure or <code>null</code> if not found.
258: *
259: * @param procedure the procedure to look for
260: * @return a <code>DatabaseProcedure</code> value or null
261: */
262: public DatabaseProcedure getProcedure(DatabaseProcedure procedure) {
263: if (procedure == null)
264: return null;
265:
266: // Optimistic approach where we use an iterator and if the tables are
267: // modified concurrently, iter.next() will fail and we will restart the
268: // search.
269: boolean retry;
270: do {
271: retry = false;
272: try {
273: for (Iterator iter = procedures.values().iterator(); iter
274: .hasNext();) {
275: DatabaseProcedure p = (DatabaseProcedure) iter
276: .next();
277: if (procedure.equals(p))
278: return p;
279: }
280: } catch (ConcurrentModificationException e) {
281: retry = true;
282: }
283: } while (retry);
284: return null;
285: }
286:
287: /**
288: * Returns an <code>HashMap</code> of <code>DatabaseProcedure</code>
289: * objects describing the database. The key entry is given by
290: * DatabaseProcedure.getKey().
291: *
292: * @return an <code>HashMap</code> of <code>DatabaseProcedure</code>
293: */
294: public HashMap getProcedures() {
295: return procedures;
296: }
297:
298: /**
299: * Returns the <code>DatabaseTable</code> object matching the given table
300: * name or <code>null</code> if not found.
301: *
302: * @param tableName the table name to look for
303: * @return a <code>DatabaseTable</code> value or null
304: */
305: public DatabaseTable getTable(String tableName) {
306: if ((tableName == null) || (tableName.length() == 0))
307: return null;
308:
309: synchronized (tables) {
310: DatabaseTable t = (DatabaseTable) tables.get(tableName);
311: if (t == null) {
312: // Check if we have a fully qualified table name prefixed by the
313: // database name (e.g. mydb.table1).
314: if (tableName.startsWith(vdbNameWithDot)) { // Strip the (virtual) database prefix if any
315: t = (DatabaseTable) tables.get(tableName
316: .substring(vdbNameWithDot.length()));
317: }
318: // SEQUOIA-518: (Database specific) use of quoted tableNames:
319: // PostreSQL accepts '"', MySQL accepts '`'
320: // so we need to trim them
321: else {
322: char firstChar = tableName.charAt(0);
323: if (firstChar == '\"' || firstChar == '`'
324: || firstChar == '\'') {
325: String trimedTableName = tableName.substring(1,
326: tableName.length() - 1);
327: t = (DatabaseTable) tables.get(trimedTableName);
328: }
329: }
330: }
331: return t;
332: }
333: }
334:
335: private DatabaseTable getTable(DatabaseTable other) {
336: if (other.getSchema() != null)
337: return getTable(other.getSchema() + "." + other.getName());
338: else
339: return getTable(other.getName());
340: }
341:
342: /**
343: * Returns the <code>DatabaseTable</code> object matching the given table
344: * name or <code>null</code> if not found. An extra boolean indicates if
345: * table name matching is case sensitive or not.
346: *
347: * @param tableName the table name to look for
348: * @param isCaseSensitive true if name matching must be case sensitive
349: * @return a <code>DatabaseTable</code> value or null
350: */
351: public DatabaseTable getTable(String tableName,
352: boolean isCaseSensitive) {
353: if ((tableName == null) || (tableName.length() == 0))
354: return null;
355:
356: DatabaseTable t = getTable(tableName);
357: if (isCaseSensitive || (t != null))
358: return t;
359:
360: // Not found with the case sensitive approach, let's try a case insensitive
361: // way
362:
363: // Strip the (virtual) database prefix if any for fully qualified table
364: // names
365: if (tableName.startsWith(vdbNameWithDot))
366: tableName = tableName.substring(vdbNameWithDot.length());
367:
368: // Optimistic approach where we use an iterator and if the tables are
369: // modified concurrently, iter.next() will fail and we will restart the
370: // search.
371: boolean retry;
372: do {
373: retry = false;
374: try {
375: for (Iterator iter = tables.values().iterator(); iter
376: .hasNext();) {
377: t = (DatabaseTable) iter.next();
378: if (tableName.equalsIgnoreCase(t.getName()))
379: return t;
380: }
381: } catch (ConcurrentModificationException e) {
382: retry = true;
383: }
384: } while (retry);
385: return null;
386: }
387:
388: /**
389: * Returns an <code>HashMap</code> of <code>DatabaseTable</code> objects
390: * describing the database. The key is the table name.
391: *
392: * @return an <code>HashMap</code> of <code>DatabaseTable</code>
393: */
394: public synchronized HashMap getTables() {
395: return new HashMap(tables);
396: }
397:
398: /**
399: * Returns the virtual database name value.
400: *
401: * @return Returns the virtual database name.
402: */
403: public final String getVirtualDatabaseName() {
404: return vdbNameWithDot;
405: }
406:
407: /**
408: * Returns <code>true</code> if the given <code>ProcedureName</code> is
409: * found in this schema.
410: *
411: * @param procedureName the name of the procedure you are looking for
412: * @param nbOfParameters number of parameters of the stored procecdure
413: * @return <code>true</code> if the procedure has been found
414: */
415: public boolean hasProcedure(String procedureName, int nbOfParameters) {
416: return procedures.containsKey(DatabaseProcedure.buildKey(
417: procedureName, nbOfParameters));
418: }
419:
420: /**
421: * Returns true if the given transaction locks (or wait for a lock) on any of
422: * the table of this schema.
423: *
424: * @param transactionId the transaction identifier
425: * @return true if the transaction locks or wait for a lock on at least one
426: * table
427: */
428: public boolean hasATableLockedByTransaction(long transactionId) {
429: // Optimistic approach where we use an iterator and if the tables are
430: // modified concurrently, iter.next() will fail and we will restart the
431: // search.
432: boolean retry;
433: do {
434: retry = false;
435: try {
436: for (Iterator iter = tables.values().iterator(); iter
437: .hasNext();) {
438: TransactionLogicalLock l = ((DatabaseTable) iter
439: .next()).getLock();
440: if (l.isLocked()
441: && ((l.getLocker() == transactionId) || (l
442: .isWaiting(transactionId))))
443: return true;
444: }
445: } catch (ConcurrentModificationException e) {
446: retry = true;
447: }
448: } while (retry);
449: return false;
450: }
451:
452: /**
453: * Returns <code>true</code> if the given <code>TableName</code> is found
454: * in this schema.
455: *
456: * @param tableName the name of the table you are looking for
457: * @return <code>true</code> if the table has been found
458: */
459: public boolean hasTable(String tableName) {
460: // Optimistic approach, if the tables are modified concurrently we will fail
461: // and we will restart the search.
462: do {
463: try {
464: return tables.containsKey(tableName);
465: } catch (ConcurrentModificationException e) {
466: }
467: } while (true);
468: }
469:
470: /**
471: * Checks if this <code>DatabaseSchema</code> is a compatible subset of a
472: * given schema. It means that all tables in this schema must be present with
473: * the same definition in the other schema.
474: *
475: * @param other the object to compare with
476: * @return <code>true</code> if the two schemas are compatible
477: */
478: public boolean isCompatibleSubset(DatabaseSchema other) {
479: if (other == null)
480: return false;
481:
482: DatabaseTable table, otherTable;
483: for (Iterator iter = tables.values().iterator(); iter.hasNext();) { // Parse all tables
484: table = (DatabaseTable) iter.next();
485: otherTable = other.getTable(table);
486: if (otherTable == null)
487: return false; // Not present
488: else if (!table.equalsIgnoreType(otherTable))
489: return false; // Not compatible
490: }
491: DatabaseProcedure procedure, otherProcedure;
492: for (Iterator iter = procedures.values().iterator(); iter
493: .hasNext();) { // Parse all procedures
494: procedure = (DatabaseProcedure) iter.next();
495: otherProcedure = other.getProcedure(procedure.getName());
496: if (otherProcedure == null)
497: return false; // Not present
498: else if (!procedure.equals(otherProcedure))
499: return false; // Not compatible
500: }
501: return true; // Ok, all tables passed the test
502: }
503:
504: /**
505: * Checks if this <code>DatabaseSchema</code> is compatible with the given
506: * schema. It means that all tables in this schema that are common with the
507: * other schema must be identical.
508: *
509: * @param other the object to compare with
510: * @return <code>true</code> if the two schemas are compatible
511: */
512: public boolean isCompatibleWith(DatabaseSchema other) {
513: DatabaseTable table, otherTable;
514: for (Iterator iter = tables.values().iterator(); iter.hasNext();) { // Parse all tables
515: table = (DatabaseTable) iter.next();
516: otherTable = other.getTable(table);
517: if (otherTable == null)
518: continue; // Not present in other schema
519: else if (!table.equalsIgnoreType(otherTable))
520: return false; // Not compatible
521: }
522: DatabaseProcedure procedure, otherProcedure;
523: for (Iterator iter = procedures.values().iterator(); iter
524: .hasNext();) { // Parse all procedures
525: procedure = (DatabaseProcedure) iter.next();
526: otherProcedure = other.getProcedure(procedure.getName());
527: if (otherProcedure == null)
528: continue; // Not present
529: else if (!procedure.equals(otherProcedure))
530: return false; // Not compatible
531: }
532: return true; // Ok, all tables passed the test
533: }
534:
535: /**
536: * Merges the given schema with the current one. All missing tables or columns
537: * are added if no conflict is detected. An exception is thrown if the given
538: * schema definition conflicts with the current one.
539: *
540: * @param databaseSchema the schema to merge
541: * @throws SQLException if the schemas conflict
542: */
543: public void mergeSchema(DatabaseSchema databaseSchema)
544: throws SQLException {
545: if (databaseSchema == null)
546: throw new IllegalArgumentException(
547: "Illegal null database schema in mergeSchema(DatabaseSchema) method");
548:
549: HashMap otherTables = databaseSchema.getTables();
550: if ((otherTables == null) || (otherTables.size() == 0))
551: return;
552:
553: DatabaseTable table, originalTable;
554: for (Iterator iter = otherTables.values().iterator(); iter
555: .hasNext();) { // Parse all tables
556: table = (DatabaseTable) iter.next();
557: originalTable = getTable(table);
558: if (originalTable == null)
559: addTable(table);
560: else
561: originalTable.merge(table);
562: }
563:
564: HashMap otherProcedures = databaseSchema.getProcedures();
565: if ((otherProcedures == null) || (otherProcedures.size() == 0))
566: return;
567:
568: DatabaseProcedure procedure, originalProcedure;
569: for (Iterator iter = otherProcedures.values().iterator(); iter
570: .hasNext();) { // Parse all procedures
571: procedure = (DatabaseProcedure) iter.next();
572: originalProcedure = getProcedure(procedure);
573: if (originalProcedure == null)
574: addProcedure(procedure);
575: else {
576: DatabaseProcedureSemantic originalSemantic = originalProcedure
577: .getSemantic();
578: if ((originalSemantic == null || originalSemantic
579: .isUseDefaultSemantic())
580: && procedure.getSemantic() != null) {
581: addProcedure(procedure);
582: }
583: }
584: }
585: }
586:
587: /**
588: * Release locks held by the given transaction on all tables.
589: *
590: * @param transactionId the transaction identifier
591: */
592: public void releaseLocksOnAllTables(long transactionId) {
593: // Release global lock
594: lock.release(transactionId);
595:
596: // Optimistic approach where we use an iterator and if the tables are
597: // modified concurrently, iter.next() will fail and we will restart the
598: // search.
599: boolean retry;
600: do {
601: retry = false;
602: try {
603: for (Iterator iter = tables.values().iterator(); iter
604: .hasNext();) {
605: TransactionLogicalLock l = ((DatabaseTable) iter
606: .next()).getLock();
607: l.release(transactionId);
608: }
609: } catch (ConcurrentModificationException e) {
610: retry = true;
611: }
612: } while (retry);
613: }
614:
615: /**
616: * removes a <code>DatabaseProcedure</code> describing a procedure of the
617: * database.
618: *
619: * @param procedure to remove
620: * @return true if the procedure was successfully removed
621: */
622: public synchronized boolean removeProcedure(
623: DatabaseProcedure procedure) {
624: if (procedure == null)
625: throw new IllegalArgumentException(
626: "Illegal null database procedure in removeProcedure(DatabaseProcedure) method");
627: return procedures.remove(procedure.getKey()) != null;
628: }
629:
630: /**
631: * Removes a <code>DatabaseTable</code> describing a table of the database.
632: *
633: * @param table the table to remove
634: * @return true if the table was successfully removed
635: */
636: public synchronized boolean removeTable(DatabaseTable table) {
637: if (table == null)
638: throw new IllegalArgumentException(
639: "Illegal null database table in removeTable(DatabaseTable) method");
640: if (table.getSchema() != null)
641: return ((tables.remove(table.getName()) != null) && (tables
642: .remove(table.getSchema() + "." + table.getName()) != null));
643: else
644: return (tables.remove(table.getName()) != null);
645: }
646:
647: /**
648: * Removes a <code>DatabaseTable</code> from the depending tables list of
649: * all tables in the schema.
650: *
651: * @param table the table to remove
652: */
653: public synchronized void removeTableFromDependingTables(
654: DatabaseTable table) {
655: Iterator keys = getTables().keySet().iterator();
656: while (keys.hasNext()) {
657: String dependingTableName = (String) keys.next();
658: DatabaseTable dependingTable = getTable(dependingTableName);
659: if (dependingTable.getDependingTables() != null) {
660: dependingTable.getDependingTables().remove(
661: table.getName());
662: }
663: }
664: }
665:
666: /**
667: * Updates the given schema with the current one. All missing tables or
668: * columns are added and if the given schema definition conflicts with the
669: * current one, the current schema definition is overriden with the one that
670: * is provided.
671: *
672: * @param databaseSchema the schema to merge
673: */
674: public void updateSchema(DatabaseSchema databaseSchema) {
675: if (databaseSchema == null)
676: throw new IllegalArgumentException(
677: "Illegal null database schema in mergeSchema(DatabaseSchema) method");
678:
679: HashMap otherTables = databaseSchema.getTables();
680:
681: // Remove tables that do not exist anymore in new schema
682: for (Iterator iter = tables.values().iterator(); iter.hasNext();) {
683: DatabaseTable t = (DatabaseTable) iter.next();
684: if (!databaseSchema.hasTable(t.getSchema() + "."
685: + t.getName()))
686: iter.remove();
687: }
688:
689: // Add missing tables
690: DatabaseTable table, originalTable;
691: for (Iterator iter = otherTables.values().iterator(); iter
692: .hasNext();) {
693: table = (DatabaseTable) iter.next();
694: originalTable = getTable(table);
695: if (originalTable == null)
696: addTable(table);
697: else
698: originalTable.updateColumns(table);
699: }
700:
701: // Just replace the stored procedures
702: this .procedures = databaseSchema.getProcedures();
703: }
704:
705: /**
706: * Two <code>DatabaseSchema</code> are considered equal if they have the
707: * same tables and the same procedures.
708: *
709: * @param other the object to compare with
710: * @return <code>true</code> if the schemas are equals
711: */
712: public boolean equals(Object other) {
713: boolean equal = true;
714: if ((other == null) || !(other instanceof DatabaseSchema))
715: return false;
716: if (tables == null)
717: equal &= ((DatabaseSchema) other).getTables() == null;
718: else
719: equal &= tables
720: .equals(((DatabaseSchema) other).getTables());
721: if (procedures == null)
722: equal &= ((DatabaseSchema) other).getProcedures() == null;
723: else
724: equal &= procedures.equals(((DatabaseSchema) other)
725: .getProcedures());
726: return equal;
727: }
728:
729: /**
730: * Get xml information about this schema.
731: *
732: * @return xml formatted information on this database schema.
733: */
734: public String getXml() {
735: StringBuffer info = new StringBuffer();
736: info.append("<" + DatabasesXmlTags.ELT_DatabaseStaticSchema
737: + ">");
738: for (Iterator iter = tables.values().iterator(); iter.hasNext();)
739: info.append(((DatabaseTable) iter.next()).getXml());
740: for (Iterator iter = procedures.values().iterator(); iter
741: .hasNext();)
742: info.append(((DatabaseProcedure) iter.next()).getXml());
743: info.append("</" + DatabasesXmlTags.ELT_DatabaseStaticSchema
744: + ">");
745: return info.toString();
746: }
747:
748: }
|