/*
* Copyright (c) 2008, SQL Power Group Inc.
*
* This file is part of SQL Power Library.
*
* SQL Power Library is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* SQL Power Library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package ca.sqlpower.sqlobject;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.apache.log4j.Logger;
import ca.sqlpower.object.SPObject;
import ca.sqlpower.object.annotation.Accessor;
import ca.sqlpower.object.annotation.Constructor;
import ca.sqlpower.object.annotation.ConstructorParameter;
import ca.sqlpower.object.annotation.Mutator;
import ca.sqlpower.object.annotation.NonBound;
import ca.sqlpower.object.annotation.Transient;
/**
* A SQLCatalog is a container for other SQLObjects. If it is in the
* containment hierarchy for a given RDBMS, it will be directly under
* SQLDatabase.
*/
public class SQLCatalog extends SQLObject {
private static Logger logger = Logger.getLogger(SQLCatalog.class);
/**
* Defines an absolute ordering of the child types of this class.
*/
@SuppressWarnings("unchecked")
public static final List<Class<? extends SPObject>> allowedChildTypes =
Collections.unmodifiableList(new ArrayList<Class<? extends SPObject>>(
Arrays.asList(SQLSchema.class, SQLTable.class)));
private List<SQLSchema> schemas = new ArrayList<SQLSchema>();
private List<SQLTable> tables = new ArrayList<SQLTable>();
static List<SQLCatalog> fetchCatalogs(DatabaseMetaData dbmd) throws SQLObjectException {
ResultSet rs = null;
try {
List<SQLCatalog> catalogs = new ArrayList<SQLCatalog>();
rs = dbmd.getCatalogs();
while (rs.next()) {
String catName = rs.getString(1);
if (catName != null) {
SQLCatalog cat = new SQLCatalog(null, catName);
cat.setNativeTerm(dbmd.getCatalogTerm());
logger.debug("Set catalog term to "+cat.getNativeTerm());
catalogs.add(cat);
}
}
return catalogs;
} catch (SQLException ex) {
throw new SQLObjectException("Failed to get catalog names from source database", ex);
} finally {
try {
if (rs != null ) rs.close();
} catch (SQLException e) {
throw new SQLObjectException("Couldn't close result set. Squishing this exception:", e);
}
}
}
/**
* The term used for catalogs in the native database system. In
* SQLServer2000, this is "database".
*/
protected String nativeTerm;
public SQLCatalog() {
this(null, null, false);
}
@Constructor
public SQLCatalog(@ConstructorParameter(propertyName = "parent") SQLDatabase parent,
@ConstructorParameter(propertyName = "name") String name) {
this(parent, name, false);
}
public SQLCatalog(SQLDatabase parent, String name, boolean startPopulated) {
setParent(parent);
setName(name);
this.nativeTerm = "catalog";
this.populated = startPopulated;
}
@Override
public void updateToMatch(SQLObject source) throws SQLObjectException {
SQLCatalog c = (SQLCatalog) source;
setName(c.getName());
setNativeTerm(c.getNativeTerm());
setPhysicalName(c.getPhysicalName());
}
protected SQLTable getTableByName(String tableName) throws SQLObjectException {
populate();
for (SQLObject child : getChildren()) {
if (child instanceof SQLTable) {
SQLTable table = (SQLTable) child;
if (table.getName().equalsIgnoreCase(tableName)) {
return table;
}
} else if (child instanceof SQLSchema) {
SQLTable table = ((SQLSchema) child).findTableByName(tableName);
if (table != null) {
return table;
}
}
}
return null;
}
/**
*
* @return The schema in this catalog with the given name, or null
* if no such schema exists.
*/
public SQLSchema findSchemaByName(String schemaName) throws SQLObjectException {
populate();
if (!isSchemaContainer()) {
return null;
}
for (SQLSchema schema : schemas) {
if (schema.getName().equalsIgnoreCase(schemaName)) {
return schema;
}
}
return null;
}
public String toString() {
return getShortDisplayName();
}
// ---------------------- SQLObject support ------------------------
@Transient @Accessor
public String getShortDisplayName() {
return getName();
}
protected void populateImpl() throws SQLObjectException {
if (populated) return;
logger.debug("SQLCatalog: populate starting");
final List<SQLSchema> fetchedSchemas;
final List<SQLTable> fetchedTables;
synchronized (getParent()) {
Connection con = null;
try {
con = ((SQLDatabase) getParent()).getConnection();
DatabaseMetaData dbmd = con.getMetaData();
// Try to find schemas in this catalog
fetchedSchemas = SQLSchema.fetchSchemas(dbmd, getName());
// No schemas found--fall through and check for tables
if (fetchedSchemas.isEmpty()) {
fetchedTables = SQLTable.fetchTablesForTableContainer(dbmd, getName(), null);
} else {
fetchedTables = null;
}
} catch (SQLException e) {
throw new SQLObjectException("catalog.populate.fail", e);
} finally {
try {
if (con != null) {
con.close();
}
} catch (SQLException e) {
throw new SQLObjectException("Couldn't close connection", e);
}
}
runInForeground(new Runnable() {
public void run() {
synchronized(SQLCatalog.this) {
if (populated) return;
if (!fetchedSchemas.isEmpty()) {
populateCatalogWithList(SQLCatalog.this, fetchedSchemas);
} else if (!fetchedTables.isEmpty()) {
populateCatalogWithList(SQLCatalog.this, fetchedTables);
}
}
}
});
}
logger.debug("SQLCatalog: populate finished");
}
/**
* Populates the SQLCatalog with a given list of children. This must be done
* on the foreground thread. The list of children must be of one type only
* and of type {@link SQLSchema} or {@link SQLTable}.
* <p>
* Package private for use in the {@link SQLObjectUtils}.
*
* @param catalog
* The catalog to populate
* @param children
* The list of children to add as children. All objects in this
* list must be of the same type.
*/
static void populateCatalogWithList(SQLCatalog catalog, List<? extends SQLObject> children) {
try {
if (children.isEmpty()) return;
Class<? extends SPObject> childType;
int index;
if (children.get(0) instanceof SQLSchema) {
index = catalog.schemas.size();
childType = SQLSchema.class;
for (SQLObject schema : children) {
catalog.schemas.add((SQLSchema) schema);
schema.setParent(catalog);
}
} else if (children.get(0) instanceof SQLTable) {
index = catalog.tables.size();
childType = SQLTable.class;
for (SQLObject table : children) {
catalog.tables.add((SQLTable) table);
table.setParent(catalog);
}
} else {
throw new IllegalArgumentException("Catalog " + catalog + " cannot have children " +
"of type " + children.get(0).getClass());
}
catalog.populated = true;
catalog.begin("Populating Catalog " + catalog);
for (SQLObject o : children) {
catalog.fireChildAdded(childType, o, index);
index++;
}
catalog.firePropertyChange("populated", false, true);
catalog.commit();
} catch (Exception e) {
catalog.rollback(e.getMessage());
if (children.get(0) instanceof SQLSchema) {
for (SQLObject schema : children) {
catalog.schemas.remove((SQLSchema) schema);
}
} else if (children.get(0) instanceof SQLTable) {
for (SQLObject table : children) {
catalog.tables.remove((SQLTable) table);
}
}
catalog.populated = false;
throw new RuntimeException(e);
}
}
// ----------------- accessors and mutators -------------------
@Accessor
public SQLDatabase getParent() {
return (SQLDatabase) super.getParent();
}
/**
* Because we constrained the return type on getParent there needs to be a
* setter that has the same constraint otherwise the reflection in the undo
* events will not find a setter to match the getter and won't be able to
* undo parent property changes.
*/
@Mutator
public void setParent(SQLDatabase parent) {
super.setParent(parent);
}
/**
* Gets the value of nativeTerm
*
* @return the value of nativeTerm
*/
@Accessor
public String getNativeTerm() {
return this.nativeTerm;
}
/**
* Sets the value of nativeTerm to a lowercase version of argNativeTerm.
*
* @param argNativeTerm Value to assign to this.nativeTerm
*/
@Mutator
public void setNativeTerm(String argNativeTerm) {
if (argNativeTerm != null) argNativeTerm = argNativeTerm.toLowerCase();
String oldValue = nativeTerm;
this.nativeTerm = argNativeTerm;
firePropertyChange("nativeTerm", oldValue, nativeTerm);
}
/**
* Determines whether this SQL object is a container for schemas
*
* @return true (the default) if there are no children; false if
* the first child is not of type SQLSchema.
*/
@NonBound
public boolean isSchemaContainer() throws SQLObjectException {
if (getParent() != null){
populate();
}
// catalog has been populated
if (getChildrenWithoutPopulating().isEmpty()) {
return true;
} else {
return !schemas.isEmpty();
}
}
@Override
protected void addChildImpl(SPObject child, int index) {
if (child instanceof SQLSchema) {
addSchema((SQLSchema) child, index);
} else if (child instanceof SQLTable) {
addTable((SQLTable) child, index);
} else {
throw new IllegalArgumentException("The child " + child.getName() +
" of type " + child.getClass() + " is not a valid child type of " +
getClass() + ".");
}
}
public void addSchema(SQLSchema schema) {
addSchema(schema, schemas.size());
}
public void addSchema(SQLSchema schema, int index) {
schemas.add(index, schema);
schema.setParent(this);
fireChildAdded(SQLSchema.class, schema, index);
}
public void addTable(SQLTable table) {
addTable(table, tables.size());
}
public void addTable(SQLTable table, int index) {
tables.add(index, table);
table.setParent(this);
fireChildAdded(SQLTable.class, table, index);
}
public List<? extends SQLObject> getChildrenWithoutPopulating() {
List<SQLObject> children = new ArrayList<SQLObject>();
children.addAll(schemas);
children.addAll(tables);
return Collections.unmodifiableList(children);
}
@Override
protected boolean removeChildImpl(SPObject child) {
if (child instanceof SQLSchema) {
return removeSchema((SQLSchema) child);
} else if (child instanceof SQLTable) {
return removeTable((SQLTable) child);
}
return false;
}
public boolean removeSchema(SQLSchema schema) {
int index = schemas.indexOf(schema);
if (index != -1) {
schemas.remove(index);
fireChildRemoved(SQLSchema.class, schema, index);
schema.setParent(null);
return true;
}
return false;
}
public boolean removeTable(SQLTable table) {
table.removeNotify();
int index = tables.indexOf(table);
if (index != -1) {
tables.remove(index);
fireChildRemoved(SQLTable.class, table, index);
table.setParent(null);
return true;
}
return false;
}
public List<? extends SPObject> getDependencies() {
return Collections.emptyList();
}
public void removeDependency(SPObject dependency) {
for (SQLObject child : getChildren()) {
child.removeDependency(dependency);
}
}
public List<Class<? extends SPObject>> getAllowedChildTypes() {
return allowedChildTypes;
}
}