All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.jinahya.sql.database.metadata.bind.MetadataContext Maven / Gradle / Ivy

There is a newer version: 4.2.9
Show newest version
/*
 * Copyright 2015 Jin Kwon <jinahya_at_gmail.com>.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.github.jinahya.sql.database.metadata.bind;

import static java.beans.Introspector.decapitalize;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeSet;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * A context class for retrieving information from an instance of
 * {@link java.sql.DatabaseMetaData}.
 *
 * @author Jin Kwon <jinahya_at_gmail.com>
 */
public class MetadataContext {

    private static final Logger logger
            = Logger.getLogger(Metadata.class.getName());

    private static String suppression(final Class klass, final Field field) {
        return decapitalize(klass.getSimpleName()) + "/" + field.getName();
    }

    /**
     * Creates a new instance with given {@code DatabaseMetaData}.
     *
     * @param context the {@code DatabaseMetaData} instance to hold.
     */
    public MetadataContext(final DatabaseMetaData context) {
        super();
        if (context == null) {
            throw new NullPointerException("null context");
        }
        this.context = context;
    }

    private boolean suppression(final String suppression) {
        if (suppression == null) {
            throw new NullPointerException("null suppression");
        }
        if (suppressions == null) {
            suppressions = new TreeSet();
        }
        return suppressions.add(suppression);
    }

    /**
     * Add suppression paths.
     *
     * @param suppression the first suppression
     * @param otherSuppressions other suppressions
     *
     * @return this
     */
    public MetadataContext suppressions(
            final String suppression, final String... otherSuppressions) {
        suppression(suppression);
        if (otherSuppressions != null) {
            for (final String otherSuppression : otherSuppressions) {
                suppression(otherSuppression);
            }
        }
        return this;
    }

    private boolean suppressed(final String suppression) {
        if (suppression == null) {
            throw new NullPointerException("null suppression");
        }
        if (suppressions == null) {
            return false;
        }
        return suppressions.contains(suppression);
    }

    private void setValue(final Field field, final Object bean,
                          final Object value, final Object[] args)
            throws ReflectiveOperationException, SQLException {
        final Class fieldType = field.getType();
        if (fieldType == List.class) {
            if (value == null) {
                return;
            }
            @SuppressWarnings("unchecked")
            List list
                    = (List) Utils.propertyValue(field.getName(), bean);
            if (list == null) {
                list = new ArrayList();
                Utils.propertyValue(field.getName(), bean, list);
            }
            final Class elementType
                    = (Class) ((ParameterizedType) field.getGenericType())
                    .getActualTypeArguments()[0];
            if (value instanceof ResultSet) {
                bindAll((ResultSet) value, elementType, list);
                return;
            }
            list.add(elementType
                    .getDeclaredMethod("valueOf", Object[].class, Object.class)
                    .invoke(null, args, value));
            return;
        }
        Utils.propertyValue(field.getName(), bean, value);
    }

    private  T bindSingle(final ResultSet resultSet,
                             final Class bindingType,
                             final T bindingInstance)
            throws SQLException, ReflectiveOperationException {
        if (resultSet != null) { // bind columns to fields
            final Set labels = Utils.columnLabels(resultSet);
            final Map fields
                    = Utils.annotatedFields(bindingType, Label.class);
            for (final Entry entry : fields.entrySet()) {
                final Field field = entry.getKey();
                final String label = entry.getValue().value();
                final String suppression = suppression(bindingType, field);
                final String formatted = String.format(
                        "field=%s, label=%s, suppression=%s", field, label,
                        suppression);
                if (suppressed(suppression)) {
                    logger.log(Level.FINE, "suppressed; {0}", formatted);
                    continue;
                }
                if (!labels.remove(label)) {
                    logger.log(Level.WARNING, "unknown label: {0}", formatted);
                    continue;
                }
                final Object value = resultSet.getObject(label);
                Utils.propertyValue(field.getName(), bindingInstance, value);
            }
            if (!labels.isEmpty()) {
                for (final String label : labels) {
                    final Object object = resultSet.getObject(label);
                    logger.log(Level.FINE, "unknown column; {0}({1}) {2}",
                               new Object[]{label, object, bindingType});
                }
            }
        }
        // invoke
        final Map fields
                = Utils.annotatedFields(bindingType, Invoke.class);
        for (final Entry entry : fields.entrySet()) {
            final Field field = entry.getKey();
            final Invoke invocation = entry.getValue();
            final String suppression = suppression(bindingType, field);
            final String formatted = String.format(
                    "field=%s, invocation=%s, suppression=%s",
                    field, invocation, suppression);
            if (suppressed(suppression)) {
                logger.log(Level.FINE, "suppressed; {0}", formatted);
                continue;
            }
            final String name = invocation.name();
            final Class[] types = invocation.types();
            final Method method;
            try {
                method = DatabaseMetaData.class.getMethod(name, types);
            } catch (final NoSuchMethodException nsme) {
                logger.log(Level.WARNING, "unknown methods; {0}", formatted);
                continue;
            }
            for (final Literals invocationArgs : invocation.args()) {
                final String[] literals = invocationArgs.value();
                final Object[] values = Invocations.invocationValues(
                        bindingType, bindingInstance, types, literals);
                final Object value;
                try {
                    value = method.invoke(context, values);
                    setValue(field, bindingInstance, value, values);
                } catch (final Exception e) {
                    logger.log(Level.SEVERE, "failed to invoke" + formatted, e);
//                    throw new RuntimeException(e);
                } catch (final Error e) {
                    logger.log(Level.SEVERE, "failed to invoke" + formatted, e);
                    throw e;
                }
            }
        }
        if (TableDomain.class.isAssignableFrom(bindingType)) {
            final TableDomain casted = (TableDomain) bindingInstance;
            final List tables = casted.getTables();
            final List references = getCrossReferences(tables);
            casted.getCrossReferences().addAll(references);
        }
        return bindingInstance;
    }

    private  T bindSingle(final ResultSet resultSet,
                             final Class bindingType)
            throws SQLException, ReflectiveOperationException {
        return bindSingle(resultSet, bindingType, bindingType.newInstance());
    }

    private  List bindAll(final ResultSet resultSet,
                                        final Class bindingType,
                                        final List bindingInstances)
            throws SQLException, ReflectiveOperationException {
        while (resultSet.next()) {
            bindingInstances.add(bindSingle(
                    resultSet, bindingType, bindingType.newInstance()));
        }
        return bindingInstances;
    }

    private  List bindAll(final ResultSet resultSet,
                                        final Class bindingType)
            throws SQLException, ReflectiveOperationException {
        return bindAll(resultSet, bindingType, new ArrayList());
    }

    /**
     * Binds all information.
     *
     * @return a Metadata
     * @throws SQLException if a database occurs.
     * @throws ReflectiveOperationException if a reflection error occurs
     */
    public Metadata getMetadata()
            throws SQLException, ReflectiveOperationException {
        final Metadata metadata = bindSingle(null, Metadata.class);
        final List catalogs = metadata.getCatalogs();
        if (catalogs.isEmpty()) {
            final Catalog catalog = new Catalog();
            catalog.setTableCat("");
            logger.log(Level.FINE, "adding an empty catalog: {0}",
                       new Object[]{catalog});
            catalogs.add(catalog);
            bindSingle(null, Catalog.class, catalog);
        }
        for (final Catalog catalog : catalogs) {
            final List schemas = catalog.getSchemas();
            if (schemas.isEmpty()) {
                final Schema schema = new Schema();
                schema.setTableCatalog(catalog.getTableCat());
                schema.setTableSchem("");
                logger.log(Level.FINE, "adding an empty schema: {0}",
                           new Object[]{schema});
                schemas.add(schema);
                bindSingle(null, Schema.class, schema);
            }
        }
        if (!suppressed("metadata/supportsConvert")) {
            final List supportsConvert
                    = new ArrayList();
            metadata.getSupportsConvert().addAll(supportsConvert);
            supportsConvert.add(
                    new SDTSDTBoolean()
                    .fromType(null)
                    .toType(null)
                    .value(context.supportsConvert()));
            final Set sqlTypes = Utils.sqlTypes();
            for (final int fromType : sqlTypes) {
                for (final int toType : sqlTypes) {
                    supportsConvert.add(
                            new SDTSDTBoolean()
                            .fromType(fromType)
                            .toType(toType)
                            .value(context.supportsConvert(fromType, toType)));
                }
            }
        }
//        for (final String methodName : getMethodNames()) {
//            logger.log(Level.INFO, "method not invoked: {0}",
//                       new Object[]{methodName});
//        }
        return metadata;
    }

    public List getAttributes(final String catalog,
                                         final String schemaPattern,
                                         final String typeNamePattern,
                                         final String attributeNamePattern)
            throws SQLException, ReflectiveOperationException {
        final List list = new ArrayList();
        final ResultSet results = context.getAttributes(
                catalog, schemaPattern, typeNamePattern, attributeNamePattern);
        try {
            bindAll(results, Attribute.class, list);
        } finally {
            results.close();
        }
        return list;
    }

    public List getBestRowIdentifier(
            final String catalog, final String schema, final String table,
            final int scope, final boolean nullable)
            throws SQLException, ReflectiveOperationException {
        final List list = new ArrayList();
        final ResultSet results = context.getBestRowIdentifier(
                catalog, schema, table, scope, nullable);
        try {
            bindAll(results, BestRowIdentifier.class, list);
        } finally {
            results.close();
        }
        return list;
    }

    /**
     * Returns catalogs.
     *
     * @return a list of catalogs
     *
     * @throws SQLException if a database error occurs.
     * @throws ReflectiveOperationException if a reflection error occurs.
     *
     * @see DatabaseMetaData#getCatalogs()
     */
    public List getCatalogs()
            throws SQLException, ReflectiveOperationException {
        final List list = new ArrayList();
        final ResultSet results = context.getCatalogs();
        try {
            bindAll(results, Catalog.class, list);
        } finally {
            results.close();
        }
        return list;
    }

    public List getClientInfoProperties()
            throws SQLException, ReflectiveOperationException {
        final List list
                = new ArrayList();
        final ResultSet results = context.getClientInfoProperties();
        try {
            bindAll(results, ClientInfoProperty.class, list);
        } finally {
            results.close();
        }
        return list;
    }

    public List getColumns(final String catalog,
                                   final String schemaPattern,
                                   final String tableNamePattern,
                                   final String columnNamePattern)
            throws SQLException, ReflectiveOperationException {
        final List list = new ArrayList();
        final ResultSet resultSet = context.getColumns(
                catalog, schemaPattern, tableNamePattern, columnNamePattern);
        try {
            bindAll(resultSet, Column.class, list);
        } finally {
            resultSet.close();
        }
        return list;
    }

    public List getColumnPrivileges(
            final String catalog, final String schema, final String table,
            final String columnNamePattern)
            throws SQLException, ReflectiveOperationException {
        final List list = new ArrayList();
        final ResultSet results = context.getColumnPrivileges(
                catalog, schema, table, columnNamePattern);
        try {
            bindAll(results, ColumnPrivilege.class, list);
        } finally {
            results.close();
        }
        return list;
    }

    public List getCrossReferences(
            final String parentCatalog, final String parentSchema,
            final String parentTable,
            final String foreignCatalog, final String foreignSchema,
            final String foreignTable)
            throws SQLException, ReflectiveOperationException {
        final List list = new ArrayList();
        final ResultSet results = context.getCrossReference(
                parentCatalog, parentSchema, parentTable, foreignCatalog,
                foreignSchema, foreignTable);
        try {
            bindAll(results, CrossReference.class, list);
        } finally {
            results.close();
        }
        return list;
    }

    public List getCrossReferences(
            final Table parentTable,
            final Table foreignTable)
            throws SQLException, ReflectiveOperationException {
        return getCrossReferences(
                parentTable.getTableCat(), parentTable.getTableSchem(),
                parentTable.getTableName(),
                foreignTable.getTableCat(), foreignTable.getTableSchem(),
                foreignTable.getTableName());
    }

    List getCrossReferences(final List
tables) throws SQLException, ReflectiveOperationException { final List list = new ArrayList(); for (final Table parentTable : tables) { for (final Table foreignTable : tables) { list.addAll(getCrossReferences(parentTable, foreignTable)); } } return list; } public List getFunctionColumns( final String catalog, final String schemaPattern, final String functionNamePattern, final String columnNamePattern) throws SQLException, ReflectiveOperationException { final List list = new ArrayList(); final ResultSet results = context.getFunctionColumns( catalog, schemaPattern, functionNamePattern, columnNamePattern); try { bindAll(results, FunctionColumn.class, list); } finally { results.close(); } return list; } public List getFunctions(final String catalog, final String schemaPattern, final String functionNamePattern) throws SQLException, ReflectiveOperationException { final List list = new ArrayList(); final ResultSet results = context.getFunctions( catalog, schemaPattern, functionNamePattern); try { bindAll(results, Function.class, list); } finally { results.close(); } return list; } public List getExportedKeys( final String catalog, final String schema, final String table) throws SQLException, ReflectiveOperationException { final List list = new ArrayList(); final ResultSet results = context.getExportedKeys( catalog, schema, table); try { bindAll(results, ExportedKey.class, list); } finally { results.close(); } return list; } public List getImportedKeys( final String catalog, final String schema, final String table) throws SQLException, ReflectiveOperationException { final List list = new ArrayList(); final ResultSet results = context.getImportedKeys( catalog, schema, table); try { bindAll(results, ImportedKey.class, list); } finally { results.close(); } return list; } public List getIndexInfo( final String catalog, final String schema, final String table, final boolean unique, final boolean approximate) throws SQLException, ReflectiveOperationException { final List list = new ArrayList(); final ResultSet results = context.getIndexInfo( catalog, schema, table, unique, approximate); try { bindAll(results, IndexInfo.class, list); } finally { results.close(); } return list; } public List getPrimaryKeys( final String catalog, final String schema, final String table) throws SQLException, ReflectiveOperationException { final List list = new ArrayList(); final ResultSet results = context.getPrimaryKeys( catalog, schema, table); try { bindAll(results, PrimaryKey.class, list); } finally { results.close(); } return list; } public List getProcedureColumns( final String catalog, final String schemaPattern, final String procedureNamePattern, final String columnNamePattern) throws SQLException, ReflectiveOperationException { final List list = new ArrayList(); final ResultSet results = context.getProcedureColumns( catalog, schemaPattern, procedureNamePattern, columnNamePattern); try { bindAll(results, ProcedureColumn.class, list); } finally { results.close(); } return list; } public List getProcedures(final String catalog, final String schemaPattern, final String procedureNamePattern) throws SQLException, ReflectiveOperationException { final List list = new ArrayList(); final ResultSet results = context.getProcedures( catalog, schemaPattern, procedureNamePattern); try { bindAll(results, Procedure.class, list); } finally { results.close(); } return list; } public List getPseudoColumns(final String catalog, final String schemaPattern, final String tableNamePattern, final String columnNamePattern) throws SQLException, ReflectiveOperationException { final List list = new ArrayList(); final ResultSet results = context.getPseudoColumns( catalog, schemaPattern, tableNamePattern, columnNamePattern); try { bindAll(results, PseudoColumn.class, list); } finally { results.close(); } return list; } public List getSchemas() throws SQLException, ReflectiveOperationException { final List list = new ArrayList(); final ResultSet results = context.getSchemas(); try { bindAll(results, SchemaName.class, list); } finally { results.close(); } return list; } public List getSchemas(final String catalog, final String schemaPattern) throws SQLException, ReflectiveOperationException { final List list = new ArrayList(); final ResultSet results = context.getSchemas(catalog, schemaPattern); try { bindAll(results, Schema.class, list); } finally { results.close(); } return list; } public List
getTables(final String catalog, final String schemaPattern, final String tableNamePattern, final String[] types) throws SQLException, ReflectiveOperationException { final List
list = new ArrayList
(); final ResultSet results = context.getTables( catalog, schemaPattern, tableNamePattern, types); try { bindAll(results, Table.class, list); } finally { results.close(); } return list; } public List getTablePrivileges( final String catalog, final String schemaPattern, final String tableNamePattern) throws SQLException, ReflectiveOperationException { final List list = new ArrayList(); final ResultSet results = context.getTablePrivileges( catalog, schemaPattern, tableNamePattern); try { bindAll(results, TablePrivilege.class, list); } finally { results.close(); } return list; } public List getTableTypes() throws SQLException, ReflectiveOperationException { final List list = new ArrayList(); final ResultSet results = context.getTableTypes(); try { bindAll(results, TableType.class, list); } finally { results.close(); } return list; } public List getTypeInfo() throws SQLException, ReflectiveOperationException { final List list = new ArrayList(); final ResultSet results = context.getTypeInfo(); try { bindAll(results, TypeInfo.class, list); } finally { results.close(); } return list; } public List getUDTs(final String catalog, final String schemaPattern, final String typeNamePattern, final int[] types) throws SQLException, ReflectiveOperationException { final List list = new ArrayList(); final ResultSet results = context.getUDTs( catalog, schemaPattern, typeNamePattern, types); try { bindAll(results, UDT.class, list); } finally { results.close(); } return list; } /** * Binds information from * {@link DatabaseMetaData#getVersionColumns(java.lang.String, java.lang.String, java.lang.String)}. * * @param catalog catalog * @param schema schema * @param table table * @return a list of {@link VersionColumn} * @throws SQLException if a database access error occurs. * @throws ReflectiveOperationException if a reflection error occurs */ public List getVersionColumns(final String catalog, final String schema, final String table) throws SQLException, ReflectiveOperationException { final List list = new ArrayList(); final ResultSet results = context.getVersionColumns( catalog, schema, table); try { bindAll(results, VersionColumn.class, list); } finally { results.close(); } return list; } private final DatabaseMetaData context; private Set suppressions; }