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.Modifier;
import java.lang.reflect.ParameterizedType;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.logging.Level;
import java.util.logging.Logger;
import static java.util.logging.Logger.getLogger;


/**
 * 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 = getLogger(Metadata.class.getName());


    public static final String DMDB_SUPPRESS_UNKNOWN_COLUMNS
        = "dmdb.suppressUnknownColumns";


    public static final String DMDB_SUPPRESS_UNKNOWN_METHODS
        = "dmdb.suppressUnknownMethods";


    public static final String DMDB_EMPTY_CATALOG_IF_NONE
        = "dmdb.emptyCatalogIfNone";


    public static final String DMDB_EMPTY_SCHEMA_IF_NONE
        = "dmdb.emptySchemaIfNone";


    private static String suppression(
        final Class beanClass, final Field beanField) {

        return decapitalize(beanClass.getSimpleName()) + "/"
               + beanField.getName();
    }


    /**
     * Creates a new instance with given {@code DatabaseMetaData}.
     *
     * @param database the {@code DatabaseMetaData} instance to hold.
     */
    public MetadataContext(final DatabaseMetaData database) {

        super();

        if (database == null) {
            throw new NullPointerException("null database");
        }

        this.database = database;
    }


    private boolean addSuppression(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 addSuppressions(
        final String suppression, final String... otherSuppressions) {

        addSuppression(suppression);

        if (otherSuppressions != null) {
            for (final String otherSuppression : otherSuppressions) {
                addSuppression(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) Values.get(field, bean);
            if (list == null) {
                list = new ArrayList();
                Values.set(field, bean, list);
            }
            final Class type
                = (Class) ((ParameterizedType) field.getGenericType())
                .getActualTypeArguments()[0];
            if (ResultSet.class.isInstance(value)) {
                bindAll((ResultSet) value, type, list);
                Values.setParent(type, list, value);
                return;
            }
            list.add(type
                .getDeclaredMethod("valueOf", Object[].class, Object.class)
                .invoke(null, args, value));
            Values.setParent(type, list, value);
            return;
        }

        Reflections.setFieldValue(field, bean, value);
    }


    private  T bindSingle(final ResultSet resultSet,
                             final Class beanClass, final T beanInstance)
        throws SQLException, ReflectiveOperationException {

        if (resultSet != null) {
            final Set resultLabels
                = ResultSets.getColumnLabels(resultSet);
            @SuppressWarnings("unchecked")
            final List fields
                = Reflections.listFields(beanClass, Label.class);
            for (final Field field : fields) {
                final String label = field.getAnnotation(Label.class).value();
                final String suppression = suppression(beanClass, field);
                final String info = String.format(
                    "field=%s, label=%s, suppression=%s", field, label,
                    suppression);
                if (suppressed(suppression)) {
                    logger.log(Level.FINE, "suppressed; {0}", info);
                    continue;
                }
                if (!resultLabels.remove(label)) {
                    final String message = "unknown column; " + info;
                    if (!suppressUnknownColumns) {
                        throw new RuntimeException(message);
                    }
                    logger.warning(message);
                    continue;
                }
                final Object value;
                try {
                    value = resultSet.getObject(label);
                } catch (final Exception e) {
                    final String message = "failed to get value; " + info;
                    logger.severe(message);
                    if (e instanceof SQLException) {
                        throw (SQLException) e;
                    }
                    throw new RuntimeException(e);
                }
                Reflections.setFieldValue(field, beanInstance, value);
            }
            if (!resultLabels.isEmpty()) {
                for (final String resultLabel : resultLabels) {
                    final Object resultValue = resultSet.getObject(resultLabel);
                    logger.log(Level.WARNING, "unknown result; {0}({1})",
                               new Object[]{resultLabel, resultValue});
                }
            }
        }

        @SuppressWarnings("unchecked")
        final List fields
            = Reflections.listFields(beanClass, Invocation.class);
        for (final Field field : fields) {
            final Invocation invocation = field.getAnnotation(Invocation.class);
            final String suppression = suppression(beanClass, field);
            final String info = String.format(
                "field=%s, invocation=%s, suppression=%s",
                field, invocation, suppression);
            if (suppressed(suppression)) {
                logger.log(Level.FINE, "suppressed; {0}", new Object[]{info});
                continue;
            }
            final String name = invocation.name();
            getMethodNames().remove(name);
            final Class[] types = invocation.types();
            final Method method;
            try {
                method = DatabaseMetaData.class.getMethod(name, types);
            } catch (final NoSuchMethodException nsme) {
                final String message = "unknown methods; " + info;
                if (!suppressUnknownMethods) {
                    throw new RuntimeException(message);
                }
                logger.warning(message);
                continue;
            }
            for (final InvocationArgs invocationArgs : invocation.argsarr()) {
                final String[] names = invocationArgs.value();
                final Object[] args = Invocations.values(
                    beanClass, beanInstance, types, names);
                final Object value;
                try {
                    value = method.invoke(database, args);
                } catch (final Exception e) {
                    logger.log(Level.SEVERE, "failed to invoke" + info, e);
                    throw new RuntimeException(e);
                } catch (final AbstractMethodError ame) {
                    logger.log(Level.SEVERE, "failed by abstract" + info, ame);
                    throw ame;
                }
                setValue(field, beanInstance, value, args);
            }
        }

        if (TableDomain.class.isAssignableFrom(beanClass)) {
            getMethodNames().remove("getCrossReference");
            final List tables = ((TableDomain) beanInstance).getTables();
            final List crossReferences
                = getCrossReferences(tables);
            ((TableDomain) beanInstance).setCrossReferences(crossReferences);
        }

        return beanInstance;
    }


    private  T bindSingle(final ResultSet resultSet,
                             final Class beanClass)
        throws SQLException, ReflectiveOperationException {

        return bindSingle(resultSet, beanClass, beanClass.newInstance());
    }


    private  List bindAll(final ResultSet resultSet,
                                        final Class beanClass,
                                        final List beanList)
        throws SQLException, ReflectiveOperationException {

        while (resultSet.next()) {
            beanList.add(bindSingle(
                resultSet, beanClass, beanClass.newInstance()));
        }

        return beanList;
    }


    private  List bindAll(final ResultSet resultSet,
                                        final Class beanType)
        throws SQLException, ReflectiveOperationException {

        return bindAll(resultSet, beanType, 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() && emptyCatalogIfNone) {
            final Catalog catalog = new Catalog();
            catalog.setTableCat("");
            catalog.setParent(metadata);
            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() && emptySchemaIfNone) {
                final Schema schema = new Schema();
                schema.setTableCatalog(catalog.getTableCat());
                schema.setTableSchem("");
                schema.setParent(catalog);
                logger.log(Level.FINE, "adding an empty schema: {0}",
                           new Object[]{schema});
                schemas.add(schema);
                bindSingle(null, Schema.class, schema);
            }
        }

//        if (!suppressed("metadata/catalogs")) {
//            final List catalogs = metadata.getCatalogs();
//            if (catalogs.isEmpty()) {
//                final Catalog catalog = new Catalog();
//                catalog.setTableCat("");
//                catalog.setParent(metadata);
//                logger.log(Level.INFO, "adding an empty catalog: {0}",
//                           new Object[]{catalog});
//                catalogs.add(catalog);
//                bindSingle(null, Catalog.class, catalog);
//            }
//            if (!suppressed("category/schemas")) {
//                for (final Catalog catalog : catalogs) {
//                    final List schemas = catalog.getSchemas();
//                    if (schemas.isEmpty()) {
//                        final Schema schema = new Schema();
//                        schema.setTableCatalog(catalog.getTableCat());
//                        schema.setTableSchem("");
//                        schema.setParent(catalog);
//                        logger.log(Level.INFO, "adding an empty schema: {0}",
//                                   new Object[]{schema});
//                        schemas.add(schema);
//                        bindSingle(null, Schema.class, schema);
//                    }
//                }
//            }
//        }
        if (!suppressed("metadata/supportsConvert")) {
            getMethodNames().remove("supportsConvert");
            final List supportsConvert
                = new ArrayList();
            metadata.setSupportsConvert(supportsConvert);
            supportsConvert.add(
                new SDTSDTBoolean()
                .fromType(null)
                .toType(null)
                .value(database.supportsConvert()));
            final Set sqlTypes = Reflections.getSqlTypes();
            for (final int fromType : sqlTypes) {
                for (final int toType : sqlTypes) {
                    supportsConvert.add(
                        new SDTSDTBoolean()
                        .fromType(fromType)
                        .toType(toType)
                        .value(database.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 = database.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 = database.getBestRowIdentifier(
            catalog, schema, table, scope, nullable);

        try {
            bindAll(results, BestRowIdentifier.class, list);
        } finally {
            results.close();
        }

        return list;
    }


    public List getCatalogs()
        throws SQLException, ReflectiveOperationException {

        final List list = new ArrayList();

        final ResultSet results = database.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 = database.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 = database.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 = database.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 = database.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 = database.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 = database.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 = database.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 = database.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 = database.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 = database.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 = database.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 = database.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 = database.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 = database.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 = database.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 = database.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 = database.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 = database.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 = database.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 = database.getUDTs( catalog, schemaPattern, typeNamePattern, types); try { bindAll(results, UDT.class, list); } finally { results.close(); } return list; } public List getVersionColumns(final String catalog, final String schema, final String table) throws SQLException, ReflectiveOperationException { final List list = new ArrayList(); final ResultSet results = database.getVersionColumns( catalog, schema, table); try { bindAll(results, VersionColumn.class, list); } finally { results.close(); } return list; } public MetadataContext suppressUnknownColumns( final boolean suppressUnknownColumns) { this.suppressUnknownColumns = suppressUnknownColumns; return this; } public MetadataContext suppressUnknownMethods( final boolean suppressUnknownMethods) { this.suppressUnknownMethods = suppressUnknownMethods; return this; } public MetadataContext emptyCatalogIfNone( final boolean emptyCatalogIfNone) { this.emptyCatalogIfNone = emptyCatalogIfNone; return this; } public MetadataContext emptySchemaIfNone(final boolean emptySchemaIfNone) { this.emptySchemaIfNone = emptySchemaIfNone; return this; } private Set getMethodNames() { if (methodNames == null) { methodNames = new HashSet(); for (final Method method : DatabaseMetaData.class.getMethods()) { if (method.getDeclaringClass() != DatabaseMetaData.class) { continue; } final int modifier = method.getModifiers(); if (Modifier.isStatic(modifier)) { continue; } if (!Modifier.isPublic(modifier)) { continue; } methodNames.add(method.getName()); } } return methodNames; } private final DatabaseMetaData database; private boolean suppressUnknownColumns; private boolean suppressUnknownMethods; private boolean emptyCatalogIfNone = true; private boolean emptySchemaIfNone = true; private Set suppressions; private transient Set methodNames; }