001: /*
002: * DbObjectCache.java
003: *
004: * This file is part of SQL Workbench/J, http://www.sql-workbench.net
005: *
006: * Copyright 2002-2008, Thomas Kellerer
007: * No part of this code maybe reused without the permission of the author
008: *
009: * To contact the author please send an email to: support@sql-workbench.net
010: *
011: */
012: package workbench.db;
013:
014: import java.beans.PropertyChangeEvent;
015: import java.beans.PropertyChangeListener;
016: import java.util.Collections;
017: import java.util.HashSet;
018: import java.util.Iterator;
019: import java.util.List;
020: import java.util.Set;
021: import java.util.SortedMap;
022: import java.util.SortedSet;
023: import java.util.TreeMap;
024: import java.util.TreeSet;
025: import workbench.log.LogMgr;
026: import workbench.resource.Settings;
027:
028: /**
029: * A cache for database objects to support Auto-completion in the editor
030: * @author support@sql-workbench.net
031: */
032: public class DbObjectCache implements PropertyChangeListener {
033: private WbConnection dbConnection;
034: private static final String NULL_SCHEMA = "$$wb-null-schema$$";
035: private boolean retrieveOraclePublicSynonyms = false;
036:
037: private Set<String> schemasInCache;
038: private SortedMap<TableIdentifier, List<ColumnIdentifier>> objects;
039:
040: DbObjectCache(WbConnection conn) {
041: this .dbConnection = conn;
042: this .createCache();
043: retrieveOraclePublicSynonyms = conn.getMetadata().isOracle()
044: && Settings
045: .getInstance()
046: .getBoolProperty(
047: "workbench.editor.autocompletion.oracle.public_synonyms",
048: false);
049: conn.addChangeListener(this );
050: }
051:
052: private void createCache() {
053: schemasInCache = new HashSet<String>();
054: objects = new TreeMap<TableIdentifier, List<ColumnIdentifier>>();
055: }
056:
057: /**
058: * Add this list of tables to the current cache.
059: */
060: private void setTables(List<TableIdentifier> tables) {
061: for (TableIdentifier tbl : tables) {
062: if (!this .objects.containsKey(tbl)) {
063: this .objects.put(tbl, null);
064: }
065: }
066: }
067:
068: public Set<TableIdentifier> getTables() {
069: return getTables(null, null);
070: }
071:
072: public Set<TableIdentifier> getTables(String schema) {
073: return getTables(schema, null);
074: }
075:
076: private String getSchemaToUse(String schema) {
077: DbMetadata meta = this .dbConnection.getMetadata();
078: return meta.adjustSchemaNameCase(schema);
079: }
080:
081: /**
082: * Get the tables (and views) the are currently in the cache
083: */
084: public Set<TableIdentifier> getTables(String schema, String type) {
085: String schemaToUse = getSchemaToUse(schema);
086: if (this .objects.size() == 0
087: || (!schemasInCache
088: .contains(schemaToUse == null ? NULL_SCHEMA
089: : schemaToUse))) {
090: try {
091: DbMetadata meta = this .dbConnection.getMetadata();
092: List<TableIdentifier> tables = meta
093: .getSelectableObjectsList(schemaToUse);
094: this .setTables(tables);
095: this .schemasInCache.add(schema == null ? NULL_SCHEMA
096: : schemaToUse);
097: } catch (Exception e) {
098: LogMgr.logError("DbObjectCache.getTables()",
099: "Could not retrieve table list", e);
100: }
101: }
102: if (type != null)
103: return filterTablesByType(schemaToUse, type);
104: else
105: return filterTablesBySchema(schemaToUse);
106: }
107:
108: private Set<TableIdentifier> filterTablesByType(String schema,
109: String type) {
110: this .getTables(schema);
111: String schemaToUse = getSchemaToUse(schema);
112: SortedSet<TableIdentifier> result = new TreeSet<TableIdentifier>(
113: new TableNameComparator());
114: for (TableIdentifier tbl : objects.keySet()) {
115: String ttype = tbl.getType();
116: String tSchema = tbl.getSchema();
117: if (type.equalsIgnoreCase(ttype)
118: && ((schemaToUse == null
119: || schemaToUse.equalsIgnoreCase(tSchema)
120: || tSchema == null || "public"
121: .equalsIgnoreCase(tSchema)))) {
122: TableIdentifier copy = tbl.createCopy();
123: if (tSchema != null && tSchema.equals(tbl.getSchema()))
124: copy.setSchema(null);
125: result.add(copy);
126: }
127: }
128: return result;
129: }
130:
131: private Set<TableIdentifier> filterTablesBySchema(String schema) {
132: SortedSet<TableIdentifier> result = new TreeSet<TableIdentifier>(
133: new TableNameComparator());
134: DbMetadata meta = this .dbConnection.getMetadata();
135: String schemaToUse = getSchemaToUse(schema);
136: for (TableIdentifier tbl : objects.keySet()) {
137: String tSchema = tbl.getSchema();
138: if (schemaToUse == null || meta.ignoreSchema(tSchema)
139: || schemaToUse.equalsIgnoreCase(tSchema)) {
140: TableIdentifier copy = tbl.createCopy();
141: copy.setSchema(null);
142: String cat = copy.getCatalog();
143: if (meta.ignoreCatalog(cat))
144: copy.setCatalog(null);
145: result.add(copy);
146: }
147: }
148: return result;
149: }
150:
151: /**
152: * Return the columns for the given table
153: * @return a List with {@link workbench.db.ColumnIdentifier} objects
154: */
155: public List<ColumnIdentifier> getColumns(TableIdentifier tbl) {
156: String schema = getSchemaToUse(tbl.getSchema());
157:
158: if (this .objects.size() == 0
159: || !schemasInCache
160: .contains(schema == null ? NULL_SCHEMA : schema)) {
161: this .getTables(schema);
162: }
163:
164: List<ColumnIdentifier> cols = this .objects.get(tbl);
165:
166: TableIdentifier t2 = null;
167:
168: // if we didn't find an entry with the schema in the table, try
169: // to find a table with that name but without the schema
170: // (this is to support oracle public synonyms/objects)
171: if (tbl.getSchema() != null && cols == null) {
172: if (!this .objects.containsKey(tbl)) {
173: t2 = tbl.createCopy();
174: t2.setSchema(null);
175: t2.setType(null);
176: cols = this .objects.get(t2);
177: if (cols == null && retrieveOraclePublicSynonyms) {
178: // retrieve Oracle PUBLIC synonyms
179: this .getTables("PUBLIC");
180: cols = this .objects.get(t2);
181: }
182: }
183: }
184:
185: if (cols == null || cols == Collections.EMPTY_LIST) {
186: TableIdentifier tblToUse = null;
187:
188: // use the stored key because that might carry the correct type attribute
189: // TabelIdentifier.equals() doesn't compare the type, only the expression
190: // so we'll get a containsKey() == true even if the type is different
191: // (which is necessary because the TableIdentifier passed to this
192: // method will never contain a type!)
193: if (objects.containsKey(tbl)) {
194: tblToUse = findKey(tbl);
195: } else if (t2 != null && objects.containsKey(t2)) {
196: tblToUse = findKey(t2);
197: } else {
198: tblToUse = this .dbConnection.getMetadata().findTable(
199: tbl);
200: }
201:
202: try {
203: cols = this .dbConnection.getMetadata().getTableColumns(
204: tblToUse);
205: Collections.sort(cols);
206: } catch (Throwable e) {
207: LogMgr.logError("DbObjectCache.getColumns",
208: "Error retrieving columns for " + tblToUse, e);
209: cols = null;
210: }
211: this .objects.put(tblToUse, cols);
212:
213: }
214: return Collections.unmodifiableList(cols);
215: }
216:
217: /**
218: * Return the stored key according to the passed
219: * TableIdentifier. The stored key might carry additional
220: * properties that the passed key does not have (even
221: * though they are equal)
222: */
223: private TableIdentifier findKey(TableIdentifier key) {
224: if (key == null)
225: return null;
226: Iterator itr = this .objects.keySet().iterator();
227: while (itr.hasNext()) {
228: TableIdentifier tbl = (TableIdentifier) itr.next();
229: if (key.equals(tbl))
230: return tbl;
231: }
232: return null;
233: }
234:
235: /**
236: * Disposes any db objects held in the cache
237: */
238: public void clear() {
239: if (this .objects != null)
240: this .objects.clear();
241: this .schemasInCache.clear();
242: }
243:
244: /**
245: * Notification about the state of the connection. If the connection
246: * is closed, we can dispose the object cache
247: */
248: public void propertyChange(PropertyChangeEvent evt) {
249: if (WbConnection.PROP_CONNECTION_STATE.equals(evt
250: .getPropertyName())
251: && WbConnection.CONNECTION_CLOSED.equals(evt
252: .getNewValue())) {
253: this.clear();
254: }
255: }
256:
257: }
|