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

/*
 * 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.InvocationTargetException;
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());


    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;
            }
            if (!field.isAccessible()) {
                field.setAccessible(true);
            }
//            @SuppressWarnings("unchecked")
//            List list = (List) field.get(bean);
//            if (list == null) {
//                list = new ArrayList();
//                field.set(bean, list);
//            }
            @SuppressWarnings("unchecked")
            List list = (List) Values.get(field, bean);
            if (list == null) {
                list = new ArrayList();
                Values.set(field, bean, list);
                //field.set(bean, list);
            }
            final Class type
                = (Class) ((ParameterizedType) field.getGenericType())
                .getActualTypeArguments()[0];
            if (ResultSet.class.isInstance(value)) {
                bindAll((ResultSet) value, type, list);
//                Reflections.setParent(type, list, bean);
                Values.setParent(type, list, value);
                return;
            }
            list.add(type
                .getDeclaredMethod("valueOf", Object[].class, Object.class)
                .invoke(null, args, value));
//            Reflections.setParent(type, list, bean);
            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 suppression = suppression(beanClass, field);
                if (suppressed(suppression)) {
                    continue;
                }
                final Label label = field.getAnnotation(Label.class);
                final String labelValue = label.value();
                if (!resultLabels.remove(labelValue)) {
                    logger.log(Level.WARNING,
                               "unmapped column; bean={0}, label={1}",
                               new Object[]{beanClass, label});
                }
                try {
                    final Object value = resultSet.getObject(labelValue);
                    Reflections.setFieldValue(field, beanInstance, value);
                } catch (final SQLException sqle) {
                    logger.log(Level.SEVERE,
                               "failed to get value"
                               + "; label=" + label
                               + ", suppression=" + suppression,
                               sqle);
                    throw sqle;
                } catch (final Exception e) {
                    logger.log(Level.SEVERE,
                               "failed to get value"
                               + "; label=" + label
                               + ", suppression=" + suppression,
                               e);
                    throw new RuntimeException(e);
                }
            }
            if (!resultLabels.isEmpty()) {
                Reflections.setUnknownResults(
                    beanClass, resultLabels, resultSet, beanInstance);
            }
        }

        @SuppressWarnings("unchecked")
        final List fields = Reflections.listFields(beanClass, Invocation.class);
        for (final Field field : fields) {
            final String suppression = suppression(beanClass, field);
            if (suppressed(suppression)) {
                continue;
            }
            final Invocation invocation = field.getAnnotation(Invocation.class);
            final String name = invocation.name();
            getMethodNames().remove(name);
            final Class[] types = invocation.types();
            final Method method = DatabaseMetaData.class.getMethod(name, types);
            for (final InvocationArgs invocationArgs : invocation.argsarr()) {
                final String[] names = invocationArgs.value();
                final Object[] args = Invocations.values(
                    beanClass, beanInstance, types, names);
                try {
                    final Object propertyValue = method.invoke(database, args);
                    setValue(field, beanInstance, propertyValue, args);
                } catch (final InvocationTargetException ite) {
                    logger.log(Level.SEVERE,
                               "failed to invoke"
                               + "; invocation=" + invocation
                               + ", suppressin=" + suppression,
                               ite);
                    throw ite;
                } catch (final Exception e) {
                    logger.log(Level.SEVERE,
                               "failed to invoke"
                               + "; invocation=" + invocation
                               + ", suppressin=" + suppression,
                               e);
                    throw new RuntimeException(e);
                } catch (final AbstractMethodError ame) {
                    logger.log(Level.SEVERE,
                               "failed to invoke"
                               + "; invocation=" + invocation
                               + ", suppressin=" + suppression,
                               ame);
                    throw ame;
                }
            }
        }

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

        return beanInstance;
    }


    private  T bindSingle(final ResultSet results, final Class type)
        throws SQLException, ReflectiveOperationException {

        return bindSingle(results, type, type.newInstance());
    }


    private  List bindAll(final ResultSet results,
                                        final Class type,
                                        final List list)
        throws SQLException, ReflectiveOperationException {

        while (results.next()) {
            list.add(bindSingle(results, type, type.newInstance()));
        }

        return list;
    }


    private  List bindAll(final ResultSet results,
                                        final Class type)
        throws SQLException, ReflectiveOperationException {

        return bindAll(results, type, new ArrayList());
    }


    /**
     * Binds all information.
     *
     * @return a Metadata
     *
     * @throws SQLException if a database occurs.
     * @throws ReflectiveOperationException if a reflection erorr occurs
     */
    public Metadata getMetadata()
        throws SQLException, ReflectiveOperationException {

        final Metadata metadata = bindSingle(null, Metadata.class);

        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);
                    }
                }
            }
        }

        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) {
                if (fromType == toType) {
//                    continue;
                }
                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(); } if (list.isEmpty()) { final Schema schema = new Schema(); schema.setTableSchem(""); list.add(schema); } 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; } private Set getMethodNames() { if (methodNames == null) { methodNames = new HashSet(); for (final Method method : DatabaseMetaData.class.getMethods()) { if (!DatabaseMetaData.class.equals(method.getDeclaringClass())) { 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 Set suppressions; private Set methodNames; }