Package com.espertech.esper.epl.db

Source Code of com.espertech.esper.epl.db.DatabasePollingViewableFactory

/**************************************************************************************
* Copyright (C) 2008 EsperTech, Inc. All rights reserved.                            *
* http://esper.codehaus.org                                                          *
* http://www.espertech.com                                                           *
* ---------------------------------------------------------------------------------- *
* The software in this package is published under the terms of the GPL license       *
* a copy of which has been included with this distribution in the license.txt file.  *
**************************************************************************************/
package com.espertech.esper.epl.db;

import com.espertech.esper.antlr.NoCaseSensitiveStream;
import com.espertech.esper.client.ConfigurationDBRef;
import com.espertech.esper.client.EventType;
import com.espertech.esper.client.hook.SQLColumnTypeContext;
import com.espertech.esper.client.hook.SQLColumnTypeConversion;
import com.espertech.esper.client.hook.SQLOutputRowConversion;
import com.espertech.esper.client.hook.SQLOutputRowTypeContext;
import com.espertech.esper.core.context.util.EPStatementAgentInstanceHandle;
import com.espertech.esper.epl.expression.ExprValidationException;
import com.espertech.esper.epl.generated.EsperEPL2GrammarLexer;
import com.espertech.esper.epl.spec.DBStatementStreamSpec;
import com.espertech.esper.event.EventAdapterService;
import com.espertech.esper.util.*;
import com.espertech.esper.view.HistoricalEventViewable;
import org.antlr.runtime.CharStream;
import org.antlr.runtime.CommonTokenStream;
import org.antlr.runtime.Token;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.sql.*;
import java.util.*;

/**
* Factory for a view onto historical data via SQL statement.
*/
public class DatabasePollingViewableFactory
{
    /**
     * Placeholder name for SQL-where clause substitution.
     */
    public static final String SAMPLE_WHERECLAUSE_PLACEHOLDER = "$ESPER-SAMPLE-WHERE";

    /**
     * Creates the viewable for polling via database SQL query.
     * @param streamNumber is the stream number of the view
     * @param databaseStreamSpec provides the SQL statement, database name and additional info
     * @param databaseConfigService for getting database connection and settings
     * @param eventAdapterService for generating event beans from database information
     * @param epStatementAgentInstanceHandle is the statements-own handle for use in registering callbacks with services
     * @param columnTypeConversionHook hook for statement-specific column conversion
     * @param outputRowConversionHook hook for statement-specific row conversion
     * @param enableJDBCLogging indicator to enable JDBC logging
     * @return viewable providing poll functionality
     * @throws ExprValidationException if the validation failed
     */
    public static HistoricalEventViewable createDBStatementView( String statementId,
                                                                 int streamNumber,
                                                                 DBStatementStreamSpec databaseStreamSpec,
                                                                 DatabaseConfigService databaseConfigService,
                                                                 EventAdapterService eventAdapterService,
                                                                 EPStatementAgentInstanceHandle epStatementAgentInstanceHandle,
                                                                 SQLColumnTypeConversion columnTypeConversionHook,
                                                                 SQLOutputRowConversion outputRowConversionHook,
                                                                 boolean enableJDBCLogging)
            throws ExprValidationException
    {
        // Parse the SQL for placeholders and text fragments
        List<PlaceholderParser.Fragment> sqlFragments;
        try
        {
            sqlFragments = PlaceholderParser.parsePlaceholder(databaseStreamSpec.getSqlWithSubsParams());
        }
        catch (PlaceholderParseException ex)
        {
            String text = "Error parsing SQL";
            throw new ExprValidationException(text + ", reason: " + ex.getMessage());
        }

        // Assemble a PreparedStatement and parameter list
        String preparedStatementText = createPreparedStatement(sqlFragments);
        SQLParameterDesc parameterDesc = getParameters(sqlFragments);
        if (log.isDebugEnabled())
        {
            log.debug(".createDBEventStream preparedStatementText=" + preparedStatementText +
                " parameterDesc=" + parameterDesc);
        }

        // Get a database connection
        String databaseName = databaseStreamSpec.getDatabaseName();
        DatabaseConnectionFactory databaseConnectionFactory;
        ColumnSettings metadataSetting;
        try
        {
            databaseConnectionFactory = databaseConfigService.getConnectionFactory(databaseName);
            metadataSetting = databaseConfigService.getQuerySetting(databaseName);
        }
        catch (DatabaseConfigException ex)
        {
            String text = "Error connecting to database '" + databaseName + '\'';
            log.error(text, ex);
            throw new ExprValidationException(text + ", reason: " + ex.getMessage());
        }

        Connection connection;
        try
        {
            connection = databaseConnectionFactory.getConnection();
        }
        catch (DatabaseConfigException ex)
        {
            String text = "Error connecting to database '" + databaseName + '\'';
            log.error(text, ex);
            throw new ExprValidationException(text + ", reason: " + ex.getMessage());
        }

        // On default setting, if we detect Oracle in the connection then don't query metadata from prepared statement
        ConfigurationDBRef.MetadataOriginEnum metaOriginPolicy = metadataSetting.getMetadataRetrievalEnum();
        if (metaOriginPolicy == ConfigurationDBRef.MetadataOriginEnum.DEFAULT)
        {
            String connectionClass = connection.getClass().getName();
            if ((connectionClass.toLowerCase().contains("oracle") || (connectionClass.toLowerCase().contains("timesten"))))
            {
                // switch to sample statement if we are dealing with an oracle connection
                metaOriginPolicy = ConfigurationDBRef.MetadataOriginEnum.SAMPLE;
            }
        }

        QueryMetaData queryMetaData;
        try
        {
            if ((metaOriginPolicy == ConfigurationDBRef.MetadataOriginEnum.METADATA) || (metaOriginPolicy == ConfigurationDBRef.MetadataOriginEnum.DEFAULT))
            {
                queryMetaData = getPreparedStmtMetadata(connection, parameterDesc.getParameters(), preparedStatementText, metadataSetting);
            }
            else
            {
                String sampleSQL;
                boolean isGivenMetadataSQL = true;
                if (databaseStreamSpec.getMetadataSQL() != null)
                {
                    sampleSQL = databaseStreamSpec.getMetadataSQL();
                    isGivenMetadataSQL = true;
                    if (log.isInfoEnabled())
                    {
                        log.info(".createDBStatementView Using provided sample SQL '" + sampleSQL + "'");
                    }
                }
                else
                {
                    // Create the sample SQL by replacing placeholders with null and
                    // SAMPLE_WHERECLAUSE_PLACEHOLDER with a "where 1=0" clause
                    sampleSQL = createSamplePlaceholderStatement(sqlFragments);

                    if (log.isInfoEnabled())
                    {
                        log.info(".createDBStatementView Using un-lexed sample SQL '" + sampleSQL + "'");
                    }

                    // If there is no SAMPLE_WHERECLAUSE_PLACEHOLDER, lexical analyse the SQL
                    // adding a "where 1=0" clause.
                    if (parameterDesc.getBuiltinIdentifiers().length != 1)
                    {
                        sampleSQL = lexSampleSQL(sampleSQL);
                        if (log.isInfoEnabled())
                        {
                            log.info(".createDBStatementView Using lexed sample SQL '" + sampleSQL + "'");
                        }
                    }
                }

                // finally get the metadata by firing the sample SQL
                queryMetaData = getExampleQueryMetaData(connection, parameterDesc.getParameters(), sampleSQL, metadataSetting, isGivenMetadataSQL);
            }
        }
        catch (ExprValidationException ex)
        {
            try
            {
                connection.close();
            }
            catch (SQLException e)
            {
                // don't handle
            }
            throw ex;
        }

        // Close connection
        try
        {
            connection.close();
        }
        catch (SQLException e)
        {
            String text = "Error closing connection";
            log.error(text, e);
            throw new ExprValidationException(text + ", reason: " + e.getMessage());
        }

        // Create event type
        // Construct an event type from SQL query result metadata
        Map<String, Object> eventTypeFields = new HashMap<String, Object>();
        int columnNum = 1;
        for (Map.Entry<String, DBOutputTypeDesc> entry : queryMetaData.getOutputParameters().entrySet())
        {
            String name = entry.getKey();
            DBOutputTypeDesc dbOutputDesc = entry.getValue();

            Class clazz;
            if (dbOutputDesc.getOptionalBinding() != null)
            {
                clazz = dbOutputDesc.getOptionalBinding().getType();
            }
            else
            {
                clazz = SQLTypeMapUtil.sqlTypeToClass(dbOutputDesc.getSqlType(), dbOutputDesc.getClassName());
            }

            if (columnTypeConversionHook != null) {

                Class newValue = columnTypeConversionHook.getColumnType(new SQLColumnTypeContext(databaseStreamSpec.getDatabaseName(), databaseStreamSpec.getSqlWithSubsParams(), name, clazz, dbOutputDesc.getSqlType(), columnNum));
                if (newValue != null) {
                    clazz = newValue;
                }

            }
            eventTypeFields.put(name, clazz);
            columnNum++;
        }

        EventType eventType;
        if (outputRowConversionHook == null) {
            String outputEventType = statementId + "_dbpoll_" + streamNumber;
            eventType = eventAdapterService.createAnonymousMapType(outputEventType, eventTypeFields);
        }
        else {
            Class carrierClass = outputRowConversionHook.getOutputRowType(new SQLOutputRowTypeContext(databaseStreamSpec.getDatabaseName(), databaseStreamSpec.getSqlWithSubsParams(), eventTypeFields));
            if (carrierClass == null) {
                throw new ExprValidationException("Output row conversion hook returned no type");
            }
            eventType = eventAdapterService.addBeanType(carrierClass.getName(), carrierClass, false, false, false);
        }

        // Get a proper connection and data cache
        ConnectionCache connectionCache;
        DataCache dataCache;
        try
        {
            connectionCache = databaseConfigService.getConnectionCache(databaseName, preparedStatementText);
            dataCache = databaseConfigService.getDataCache(databaseName, epStatementAgentInstanceHandle);
        }
        catch (DatabaseConfigException e)
        {
            String text = "Error obtaining cache configuration";
            log.error(text, e);
            throw new ExprValidationException(text + ", reason: " + e.getMessage());
        }

        PollExecStrategyDBQuery dbPollStrategy = new PollExecStrategyDBQuery(eventAdapterService,
                eventType, connectionCache, preparedStatementText, queryMetaData.getOutputParameters(), columnTypeConversionHook, outputRowConversionHook, enableJDBCLogging);

        return new DatabasePollingViewable(streamNumber, queryMetaData.getInputParameters(), dbPollStrategy, dataCache, eventType);
    }

    private static QueryMetaData getExampleQueryMetaData(Connection connection, String[] parameters, String sampleSQL, ColumnSettings metadataSetting, boolean isUsingMetadataSQL)
            throws ExprValidationException
    {
        // Simply add up all input parameters
        List<String> inputParameters = new LinkedList<String>();
        inputParameters.addAll(Arrays.asList(parameters));

        Statement statement;
        try
        {
            statement = connection.createStatement();
        }
        catch (SQLException ex)
        {
            String text = "Error creating statement";
            log.error(text, ex);
            throw new ExprValidationException(text + ", reason: " + ex.getMessage());
        }

        ResultSet result;
        try
        {
            result = statement.executeQuery(sampleSQL);
        }
        catch (SQLException ex)
        {
            String text;
            if (isUsingMetadataSQL)
            {
                text = "Error compiling metadata SQL to retrieve statement metadata, using sql text '" + sampleSQL + "'";
            }
            else
            {
                text = "Error compiling metadata SQL to retrieve statement metadata, consider using the 'metadatasql' syntax, using sql text '" + sampleSQL + "'";
            }

            try {
                statement.close();
            } catch (SQLException e) {
                log.info("Error closing statement: " + e.getMessage(), e);
            }

            log.error(text, ex);
            throw new ExprValidationException(text + ", reason: " + ex.getMessage());
        }

        Map<String, DBOutputTypeDesc> outputProperties;
        try
        {
            outputProperties = compileResultMetaData(result.getMetaData(), metadataSetting);
        }
        catch (SQLException ex)
        {
            try
            {
                result.close();
            }
            catch (SQLException e)
            {
                // don't handle
            }
            try
            {
                statement.close();
            }
            catch (SQLException e)
            {
                // don't handle
            }
            String text = "Error in statement '" + sampleSQL + "', failed to obtain result metadata";
            log.error(text, ex);
            throw new ExprValidationException(text + ", please check the statement, reason: " + ex.getMessage());
        }

        return new QueryMetaData(inputParameters, outputProperties);
    }

    /**
     * Lexes the sample SQL and inserts a "where 1=0" where-clause.
     * @param querySQL to inspect using lexer
     * @return sample SQL with where-clause inserted
     * @throws ExprValidationException to indicate a lexer problem
     */
    protected static String lexSampleSQL(String querySQL)
                throws ExprValidationException
    {
        querySQL = querySQL.replaceAll("\\s\\s+|\\n|\\r", " ");
        StringReader reader = new StringReader(querySQL);
        CharStream input;
        try
        {
            input = new NoCaseSensitiveStream(reader);
        }
        catch (IOException ex)
        {
            throw new ExprValidationException("IOException lexing query SQL '" + querySQL + '\'', ex);
        }

        int whereIndex = -1;
        int groupbyIndex = -1;
        int havingIndex = -1;
        int orderByIndex = -1;
        List<Integer> unionIndexes = new ArrayList<Integer>();

        EsperEPL2GrammarLexer lex = new EsperEPL2GrammarLexer(input);
        CommonTokenStream tokens = new CommonTokenStream(lex);
        List tokenList = tokens.getTokens();

        for (int i = 0; i < tokenList.size(); i++)
        {
            Token token = (Token) tokenList.get(i);
            if ((token == null) || token.getText() == null)
            {
                break;
            }
            String text = token.getText().toLowerCase().trim();
            if (text.equals("where"))
            {
                whereIndex = token.getCharPositionInLine() + 1;
            }
            if (text.equals("group"))
            {
                groupbyIndex = token.getCharPositionInLine() + 1;
            }
            if (text.equals("having"))
            {
                havingIndex = token.getCharPositionInLine() + 1;
            }
            if (text.equals("order"))
            {
                orderByIndex = token.getCharPositionInLine() + 1;
            }
            if (text.equals("union"))
            {
                unionIndexes.add(token.getCharPositionInLine() + 1);
            }
        }

        // If we have a union, break string into subselects and process each
        if (unionIndexes.size() != 0)
        {
            StringWriter changedSQL = new StringWriter();
            int lastIndex = 0;
            for (int i = 0; i < unionIndexes.size(); i++)
            {
                int index = unionIndexes.get(i);
                String fragment;
                if (i > 0)
                {
                    fragment = querySQL.substring(lastIndex + 5, index - 1);
                }
                else
                {
                    fragment = querySQL.substring(lastIndex, index - 1);
                }
                String lexedFragment = lexSampleSQL(fragment);

                if (i > 0)
                {
                    changedSQL.append("union ");
                }
                changedSQL.append(lexedFragment);
                lastIndex = index - 1;
            }

            // last part after last union
            String fragment = querySQL.substring(lastIndex + 5, querySQL.length());
            String lexedFragment = lexSampleSQL(fragment);
            changedSQL.append("union ");
            changedSQL.append(lexedFragment);

            return changedSQL.toString();
        }

        // Found a where clause, simplest cases
        if (whereIndex != -1)
        {
            StringWriter changedSQL = new StringWriter();
            String prefix = querySQL.substring(0, whereIndex + 5);
            String suffix = querySQL.substring(whereIndex + 5, querySQL.length());
            changedSQL.write(prefix);
            changedSQL.write("1=0 and ");
            changedSQL.write(suffix);
            return changedSQL.toString();
        }

        // No where clause, find group-by
        int insertIndex;
        if (groupbyIndex != -1)
        {
            insertIndex = groupbyIndex;
        }
        else if (havingIndex != -1)
        {
            insertIndex = havingIndex;
        }
        else if (orderByIndex != -1)
        {
            insertIndex = orderByIndex;
        }
        else
        {
            StringWriter changedSQL = new StringWriter();
            changedSQL.write(querySQL);
            changedSQL.write(" where 1=0 ");
            return changedSQL.toString();
        }

        try
        {
            StringWriter changedSQL = new StringWriter();
            String prefix = querySQL.substring(0, insertIndex - 1);
            changedSQL.write(prefix);
            changedSQL.write("where 1=0 ");
            String suffix = querySQL.substring(insertIndex - 1, querySQL.length());
            changedSQL.write(suffix);
            return changedSQL.toString();
        }
        catch (Exception ex)
        {
            String text = "Error constructing sample SQL to retrieve metadata for JDBC-drivers that don't support metadata, consider using the " + SAMPLE_WHERECLAUSE_PLACEHOLDER + " placeholder or providing a sample SQL";
            log.error(text, ex);
            throw new ExprValidationException(text, ex);
        }
    }

    private static QueryMetaData getPreparedStmtMetadata(Connection connection,
                                                         String[] parameters,
                                                         String preparedStatementText,
                                                         ColumnSettings metadataSetting)
            throws ExprValidationException
    {
        PreparedStatement prepared;
        try
        {
            if (log.isInfoEnabled())
            {
                log.info(".getPreparedStmtMetadata Preparing statement '" + preparedStatementText + "'");
            }
            prepared = connection.prepareStatement(preparedStatementText);
        }
        catch (SQLException ex)
        {
            String text = "Error preparing statement '" + preparedStatementText + '\'';
            log.error(text, ex);
            throw new ExprValidationException(text + ", reason: " + ex.getMessage());
        }

        // Interrogate prepared statement - parameters and result
        List<String> inputParameters = new LinkedList<String>();
        try
        {
            ParameterMetaData parameterMetaData = prepared.getParameterMetaData();
            inputParameters.addAll(Arrays.asList(parameters).subList(0, parameterMetaData.getParameterCount()));
        }
        catch (Exception ex)
        {
            try
            {
                prepared.close();
            }
            catch (SQLException e)
            {
                // don't handle
            }
            String text = "Error obtaining parameter metadata from prepared statement, consider turning off metadata interrogation via configuration, for statement '" + preparedStatementText + '\'';
            log.error(text, ex);
            throw new ExprValidationException(text + ", please check the statement, reason: " + ex.getMessage());
        }

        Map<String, DBOutputTypeDesc> outputProperties;
        try
        {
            outputProperties = compileResultMetaData(prepared.getMetaData(), metadataSetting);
        }
        catch (SQLException ex)
        {
            try
            {
                prepared.close();
            }
            catch (SQLException e)
            {
                // don't handle
            }
            String text = "Error in statement '" + preparedStatementText + "', failed to obtain result metadata, consider turning off metadata interrogation via configuration";
            log.error(text, ex);
            throw new ExprValidationException(text + ", please check the statement, reason: " + ex.getMessage());
        }

        if (log.isDebugEnabled())
        {
            log.debug(".createDBEventStream in=" + inputParameters.toString() +
                " out=" + outputProperties.toString());
        }

        // Close statement
        try
        {
            prepared.close();
        }
        catch (SQLException e)
        {
            String text = "Error closing prepared statement";
            log.error(text, e);
            throw new ExprValidationException(text + ", reason: " + e.getMessage());
        }

        return new QueryMetaData(inputParameters, outputProperties);
    }

    private static String createPreparedStatement(List<PlaceholderParser.Fragment> parseFragements)
    {
        StringBuilder buffer = new StringBuilder();
        for (PlaceholderParser.Fragment fragment : parseFragements)
        {
            if (!fragment.isParameter())
            {
                buffer.append(fragment.getValue());
            }
            else
            {
                if (fragment.getValue().equals(SAMPLE_WHERECLAUSE_PLACEHOLDER))
                {
                    continue;
                }
                buffer.append('?');
            }
        }
        return buffer.toString();
    }

    private static String createSamplePlaceholderStatement(List<PlaceholderParser.Fragment> parseFragements)
    {
        StringBuilder buffer = new StringBuilder();
        for (PlaceholderParser.Fragment fragment : parseFragements)
        {
            if (!fragment.isParameter())
            {
                buffer.append(fragment.getValue());
            }
            else
            {
                if (fragment.getValue().equals(SAMPLE_WHERECLAUSE_PLACEHOLDER))
                {
                    buffer.append(" where 1=0 ");
                    break;
                }
                else
                {
                    buffer.append("null");
                }
            }
        }
        return buffer.toString();
    }

    private static SQLParameterDesc getParameters(List<PlaceholderParser.Fragment> parseFragements)
    {
        List<String> eventPropertyParams = new LinkedList<String>();
        for (PlaceholderParser.Fragment fragment : parseFragements)
        {
            if (fragment.isParameter())
            {
                if (!fragment.getValue().equals(SAMPLE_WHERECLAUSE_PLACEHOLDER))
                {
                    eventPropertyParams.add(fragment.getValue());
                }
            }
        }
        String[] params = eventPropertyParams.toArray(new String[eventPropertyParams.size()]);
        String[] builtin = eventPropertyParams.toArray(new String[eventPropertyParams.size()]);
        return new SQLParameterDesc(params, builtin);
    }

    private static Map<String, DBOutputTypeDesc> compileResultMetaData(ResultSetMetaData resultMetaData,
                                                                       ColumnSettings columnSettings
                                                                       )
            throws SQLException
    {
        Map<String, DBOutputTypeDesc> outputProperties = new HashMap<String, DBOutputTypeDesc>();
        for (int i = 0; i < resultMetaData.getColumnCount(); i++)
        {
            String columnName = resultMetaData.getColumnLabel(i + 1);
            if (columnName == null) {
                columnName = resultMetaData.getColumnName(i + 1);
            }
            int columnType = resultMetaData.getColumnType(i + 1);
            String javaClass = resultMetaData.getColumnTypeName(i + 1);

            ConfigurationDBRef.ColumnChangeCaseEnum caseEnum = columnSettings.getColumnCaseConversionEnum();
            if ((caseEnum != null) && (caseEnum == ConfigurationDBRef.ColumnChangeCaseEnum.LOWERCASE))
            {
                columnName = columnName.toLowerCase();
            }
            if ((caseEnum != null) && (caseEnum == ConfigurationDBRef.ColumnChangeCaseEnum.UPPERCASE))
            {
                columnName = columnName.toUpperCase();
            }

            DatabaseTypeBinding binding = null;
            String javaTypeBinding = null;
            if (columnSettings.getJavaSqlTypeBinding() != null)
            {
                javaTypeBinding = columnSettings.getJavaSqlTypeBinding().get(columnType);
            }
            if (javaTypeBinding != null)
            {
                binding = DatabaseTypeEnum.getEnum(javaTypeBinding).getBinding();
            }
            DBOutputTypeDesc outputType = new DBOutputTypeDesc(columnType, javaClass, binding);
            outputProperties.put(columnName, outputType);
        }
        return outputProperties;
    }

    private static final Log log = LogFactory.getLog(DatabasePollingViewableFactory.class);
}
TOP

Related Classes of com.espertech.esper.epl.db.DatabasePollingViewableFactory

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.