001package org.eclipse.birt.report.data.oda.jdbc;
002
003import java.sql.DatabaseMetaData;
004import java.sql.SQLException;
005import java.util.ArrayList;
006import java.util.Collection;
007import java.util.Enumeration;
008import java.util.HashMap;
009import java.util.Map;
010import java.util.Properties;
011import java.util.logging.Level;
012import java.util.logging.Logger;
013
014import org.eclipse.birt.report.data.bidi.utils.core.BidiConstants;
015import org.eclipse.birt.report.data.bidi.utils.core.BidiFormat;
016import org.eclipse.birt.report.data.bidi.utils.core.BidiTransform;
017import org.eclipse.birt.report.data.oda.i18n.ResourceConstants;
018import org.eclipse.birt.report.data.oda.jdbc.bidi.BidiCallStatement;
019import org.eclipse.birt.report.data.oda.jdbc.bidi.BidiStatement;
020import org.eclipse.datatools.connectivity.oda.IConnection;
021import org.eclipse.datatools.connectivity.oda.IDataSetMetaData;
022import org.eclipse.datatools.connectivity.oda.IQuery;
023import org.eclipse.datatools.connectivity.oda.OdaException;
024
025import com.ibm.icu.util.ULocale;
026import org.kuali.ole.config.OLEReportDBConfig;
027
028/**
029 * Connection implements IConnection interface of ODA. It is a wrapper of JDBC
030 * Connection.
031 *
032 */
033public class Connection implements IConnection
034{
035    /** The JDBC Connection instance. */
036    protected java.sql.Connection jdbcConn = null;
037
038    /** logger */
039    private static Logger logger = Logger.getLogger( Connection.class.getName( ) );
040
041    // TODO: externalize
042    private static final String advancedDataType = "org.eclipse.birt.report.data.oda.jdbc.SPSelectDataSet";
043
044    protected Map appContext;
045
046    private Boolean autoCommit;
047    private int isolationMode = Constants.TRANSCATION_ISOLATION_DEFAULT;
048
049    OLEReportDBConfig oleReportDBConfig = new OLEReportDBConfig();
050    /*
051     * @see org.eclipse.datatools.connectivity.oda.IConnection#isOpen()
052     */
053    public boolean isOpen( ) throws OdaException
054    {
055        return ( jdbcConn != null );
056    }
057
058    /*
059     * @see org.eclipse.datatools.connectivity.oda.IConnection#open(java.util.Properties)
060     */
061    public void open( Properties connProperties ) throws OdaException
062    {
063        if ( this.appContext != null )
064        {
065            Object value = this.appContext.get( IConnectionFactory.PASS_IN_CONNECTION );
066            if ( value != null && ( value instanceof java.sql.Connection ) )
067            {
068                jdbcConn = (java.sql.Connection) value;
069                logger.logp( Level.FINER,
070                        Connection.class.getName( ),
071                        "open",
072                        jdbcConn.toString( ) );
073                return;
074            }
075        }
076        if ( connProperties == null )
077        {
078            IllegalArgumentException e = new IllegalArgumentException( "connProperties cannot be null" );
079            logger.logp( Level.FINE,
080                    Connection.class.getName( ),
081                    "open",
082                    e.getMessage( ),
083                    e );
084            throw e;
085        }
086        // Log connection information
087        if ( logger.isLoggable( Level.FINE ) )
088        {
089            StringBuffer logMsg = new StringBuffer( "Connection.open(Properties). connProperties = " ); //$NON-NLS-1$
090            for ( Enumeration enumeration = connProperties.propertyNames( ); enumeration.hasMoreElements( ); )
091            {
092                String propName = (String) enumeration.nextElement( );
093                // Don't log value of any property that looks like a password
094                String lcPropName = propName.toLowerCase( );
095                String propVal;
096                if ( lcPropName.indexOf( "password" ) >= 0 //$NON-NLS-1$
097                        || lcPropName.indexOf( "pwd" ) >= 0 ) //$NON-NLS-1$
098                    propVal = "***"; //$NON-NLS-1$
099                else
100                {
101                    propVal = connProperties.getProperty( propName );
102                    if ( lcPropName.equals( "odaurl" ) ) //$NON-NLS-1$
103                    {
104                        propVal = LogUtil.encryptURL( propVal );
105                    }
106                }
107
108                logMsg.append( propName )
109                        .append( "=" ).append( propVal ).append( ";" ); //$NON-NLS-1$//$NON-NLS-2$
110            }
111            logger.logp( Level.FINE,
112                    Connection.class.getName( ),
113                    "open", //$NON-NLS-1$
114                    logMsg.toString( ) );
115        }
116
117        close( );
118
119        String dataSource = connProperties.getProperty( Constants.ODADataSource );
120        if ( dataSource != null )
121        {
122            // TODO connect by DataSource
123            UnsupportedOperationException e = new UnsupportedOperationException( "Oda-jdbc:connect by data source" ); //$NON-NLS-1$
124            logger.logp( Level.FINE,
125                    Connection.class.getName( ),
126                    "open", //$NON-NLS-1$
127                    e.getMessage( ),
128                    e );
129            throw e;
130        }
131        else
132        {
133            if (hasBidiProperties (connProperties)){
134                connProperties = bidiTransform (connProperties);
135            }
136            String url = oleReportDBConfig.getPropertyByKey(Constants.REPORT_DBA_URL);
137            String jndiName = connProperties.getProperty( Constants.ODAJndiName );
138
139            String autoCommit = connProperties.getProperty( Constants.CONNECTION_AUTO_COMMIT );
140            if ( autoCommit != null )
141            {
142                this.autoCommit = Boolean.valueOf( autoCommit );
143            }
144
145            String isolationMode = connProperties.getProperty( Constants.CONNECTION_ISOLATION_MODE );
146            this.isolationMode = Constants.getIsolationMode( isolationMode );
147
148            if ( (url == null || url.length( ) == 0) && (jndiName == null ||
149                    jndiName.length() == 0) )
150            {
151                throw new JDBCException( ResourceConstants.DRIVER_MISSING_PROPERTIES,
152                        ResourceConstants.ERROR_MISSING_PROPERTIES );
153            }
154            connectByUrl( url, connProperties );
155        }
156        logger.log(Level.FINER, "JDBC connection: " + jdbcConn + " is opened");
157        updateAppContext (connProperties);
158    }
159
160    private boolean hasBidiProperties(Properties connProperties) {
161        if ((connProperties.containsKey(BidiConstants.CONTENT_FORMAT_PROP_NAME)) ||
162                (connProperties.containsKey(BidiConstants.METADATA_FORMAT_PROP_NAME)))
163            return true;
164        return false;
165    }
166
167    private void updateAppContext(Properties connProperties) {
168        if (appContext == null)
169            appContext = new HashMap();
170        appContext.put(Constants.CONNECTION_PROPERTIES_STR, connProperties);
171    }
172
173    /**
174     * Opens a JDBC connection using the specified url and connection properties
175     * @param connProperties
176     */
177    protected void connectByUrl( String url, Properties connProperties )
178            throws OdaException
179    {
180        assert connProperties != null;
181        assert url != null;
182
183        // Copy connProperties to props; skip property starting with
184        // "oda"; those are properties read by this driver
185        Properties props = new Properties( );
186        for ( Enumeration enumeration = connProperties.propertyNames( ); enumeration.hasMoreElements( ); )
187        {
188            String propName = (String) enumeration.nextElement( );
189            if ( ! propName.startsWith( "oda" ) && ! propName.startsWith( "Oda" ) )
190            {
191                props.setProperty( propName,
192                        connProperties.getProperty( propName ) );
193            }
194        }
195
196        // Read user name and password
197        String user = oleReportDBConfig.getPropertyByKey(Constants.REPORT_DBA_USERNAME);
198        String pwd = oleReportDBConfig.getPropertyByKey(Constants.REPORT_DBA_PASSWORD);
199        props = JDBCDriverManager.addUserAuthenticationProperties(
200                props, user, pwd );
201
202        String driverClass = oleReportDBConfig.getPropertyByKey(Constants.REPORT_DBA_DRIVER);
203        String jndiNameUrl = connProperties.getProperty( Constants.ODAJndiName );
204
205        try
206        {
207            if ( ( jndiNameUrl == null || jndiNameUrl.trim( ).length( ) == 0 )
208                    && ConnectionPoolFactory.getInstance( ) != null )
209            {
210                jdbcConn = ConnectionPoolFactory.getInstance( )
211                        .getConnection( driverClass,
212                                url,
213                                props,
214                                getDriverClassPath( ),
215                                this.appContext );
216                populateConnectionProp( );
217                logger.log(Level.FINE, "JDBC connection success : " + jdbcConn );
218            }
219        }
220        catch ( Exception e )
221        {
222            if( e instanceof SQLException )
223            {
224                SQLException e1 = (SQLException)e;
225                logger.log( Level.SEVERE,
226                        "JDBC connection throws exception: Error Code "
227                                + e1.getErrorCode( ) + " Message:"
228                                + e1.getLocalizedMessage( ) );
229                //First try to identify the authorization info. 28000 is xOpen standard for login failure
230                if( "28000".equals( e1.getSQLState( )))
231                    throw new JDBCException( ResourceConstants.CONN_CANNOT_GET, e1 );
232            }
233            else
234            {
235                logger.log( Level.SEVERE, "JDBC connection throws exception: " + e.getLocalizedMessage( ) );
236            }
237        }
238        try
239        {
240
241            if ( jdbcConn == null )
242            {
243                jdbcConn = JDBCDriverManager.getInstance( )
244                        .getConnection( driverClass,
245                                url,
246                                jndiNameUrl,
247                                props,
248                                getDriverClassPath( ) );
249
250                populateConnectionProp( );
251            }
252        }
253        catch ( SQLException e )
254        {
255            throw new JDBCException( ResourceConstants.CONN_CANNOT_GET, e );
256        }
257    }
258
259    private void populateConnectionProp( ) throws SQLException
260    {
261        if( jdbcConn!= null )
262        {
263            if( this.autoCommit != null )
264                jdbcConn.setAutoCommit( this.autoCommit );
265            else
266            {
267                if  (DBConfig.getInstance().qualifyPolicy(
268                        jdbcConn.getMetaData().getDriverName(),
269                        DBConfig.SET_COMMIT_TO_FALSE) ) {
270                    this.autoCommit = false;
271                    jdbcConn.setAutoCommit( false );
272                }
273            }
274            if( this.isolationMode!= Constants.TRANSCATION_ISOLATION_DEFAULT)
275                jdbcConn.setTransactionIsolation( this.isolationMode );
276        }
277    }
278
279    @SuppressWarnings("unchecked")
280    protected Collection<String> getDriverClassPath( )
281    {
282        if ( this.appContext == null )
283            return null;
284
285        if ( this.appContext.get( IConnectionFactory.DRIVER_CLASSPATH ) == null )
286            return null;
287
288        Object classPath = this.appContext.get( IConnectionFactory.DRIVER_CLASSPATH );
289
290        if ( classPath instanceof String )
291        {
292            ArrayList<String> result = new ArrayList<String>();
293            result.add( classPath.toString( ) );
294            return result;
295        }
296        else if ( classPath instanceof Collection )
297        {
298            ArrayList<String> result = new ArrayList<String>();
299            for( Object aClassPath: (Collection)classPath )
300            {
301                if( aClassPath!= null )
302                    result.add( aClassPath.toString( ) );
303            }
304            return result;
305        }
306
307        return null;
308    }
309
310    /*
311     * @see org.eclipse.datatools.connectivity.oda.IConnection#getMetaData(java.lang.String)
312     */
313    public IDataSetMetaData getMetaData( String dataSetType )
314            throws OdaException
315    {
316        logger.logp( Level.FINEST,
317                Connection.class.getName( ),
318                "getMetaData",
319                "Connection.getMetaData(" + dataSetType + ")" );
320        // Only one data source type, ignoring the argument.
321        DatabaseMetaData dbMetadata = null;
322        if ( jdbcConn != null )
323        {
324            try
325            {
326                dbMetadata = jdbcConn.getMetaData( );
327            }
328            catch ( SQLException e )
329            {
330                throw new JDBCException( ResourceConstants.CONN_CANNOT_GET_METADATA,
331                        e );
332            }
333        }
334        return new DataSourceMetaData( this, dbMetadata );
335    }
336
337    /*
338     * @see org.eclipse.datatools.connectivity.oda.IConnection#newQuery(java.lang.String)
339     */
340    public IQuery newQuery( String dataSourceType ) throws OdaException
341    {
342        logger.logp( Level.FINER,
343                Connection.class.getName( ),
344                "createStatement",
345                "Connection.createStatement(" + dataSourceType + ")" );
346
347        // only one data source type, ignoring the argument.
348        assertOpened( );
349        if ( dataSourceType != null
350                && dataSourceType.equalsIgnoreCase( advancedDataType ) )
351            return createCallStatement( jdbcConn );
352        else
353            return createStatement( jdbcConn );
354    }
355
356    private IQuery createCallStatement(java.sql.Connection jdbcConn2) throws OdaException {
357        if ((appContext != null) &&
358                (appContext.get(Constants.CONNECTION_PROPERTIES_STR) != null)){
359            Properties props = (Properties)appContext.get(Constants.CONNECTION_PROPERTIES_STR);
360            if (hasBidiProperties(props))
361                return new BidiCallStatement(jdbcConn, props);
362        }
363        return new CallStatement( jdbcConn );
364    }
365
366    protected IQuery createStatement(java.sql.Connection jdbcConn) throws OdaException {
367        if ((appContext != null) &&
368                (appContext.get(Constants.CONNECTION_PROPERTIES_STR) != null)){
369            Properties props = (Properties)appContext.get(Constants.CONNECTION_PROPERTIES_STR);
370            if (hasBidiProperties(props))
371                return new BidiStatement(jdbcConn, props);
372        }
373        return new Statement( jdbcConn );
374    }
375
376    /*
377     * @see org.eclipse.datatools.connectivity.oda.IConnection#commit()
378     */
379    public void commit( ) throws OdaException
380    {
381        logger.logp( Level.FINEST,
382                Connection.class.getName( ),
383                "commit",
384                "Connection.commit()" );
385        assertOpened( );
386        try
387        {
388            jdbcConn.commit( );
389        }
390        catch ( SQLException e )
391        {
392            throw new JDBCException( ResourceConstants.CONN_COMMIT_ERROR, e );
393        }
394    }
395
396    /*
397     * @see org.eclipse.datatools.connectivity.oda.IConnection#rollback()
398     */
399    public void rollback( ) throws OdaException
400    {
401        logger.logp( Level.FINEST,
402                Connection.class.getName( ),
403                "rollback",
404                "Connection.rollback()" );
405        assertOpened( );
406        try
407        {
408            jdbcConn.rollback( );
409        }
410        catch ( SQLException e )
411        {
412            throw new JDBCException( ResourceConstants.CONN_ROLLBACK_ERROR, e );
413        }
414    }
415
416    /*
417     * @see org.eclipse.datatools.connectivity.oda.IConnection#getMaxQueries()
418     */
419    public int getMaxQueries( ) throws OdaException
420    {
421        if ( jdbcConn != null )
422        {
423            try
424            {
425                DatabaseMetaData dbMetadata = jdbcConn.getMetaData( );
426                int maxstmts = dbMetadata.getMaxStatements( );
427                int maxconns = dbMetadata.getMaxConnections( );
428                if ( maxstmts == 0 && maxconns == 0 )
429                    return 0;
430                else if ( maxconns == 0 || maxstmts < maxconns )
431                    return 1;
432                else
433                    return maxstmts / maxconns;
434            }
435            catch ( SQLException e )
436            {
437                return 1;
438            }
439        }
440
441        return 0;
442    }
443
444    /*
445     * @see org.eclipse.datatools.connectivity.oda.IConnection#close()
446     */
447    public void close( ) throws OdaException
448    {
449        logger.logp( Level.FINEST,
450                Connection.class.getName( ),
451                "close", //$NON-NLS-1$
452                "Connection closed." ); //$NON-NLS-1$
453        if ( jdbcConn == null )
454        {
455            return;
456        }
457        try
458        {
459            if ( this.appContext != null && jdbcConn != null )
460            {
461                Object option = this.appContext.get( IConnectionFactory.CLOSE_PASS_IN_CONNECTION );
462                boolean closePassInConnection = ( option instanceof Boolean )
463                        ? ( (Boolean) option ).booleanValue( ) : true;
464                if ( !closePassInConnection )
465                    return;
466            }
467
468            if ( jdbcConn.isClosed( ) == false )
469            {
470                jdbcConn.close( );
471                logger.log(Level.FINE, "JDBC connection: " + jdbcConn + " is closed");
472            }
473            else
474            {
475                logger.log(Level.FINER, "JDBC connection: " + jdbcConn + " is already closed outside of JDBC ODA driver");
476            }
477        }
478        catch ( SQLException e )
479        {
480            try
481            {
482                if (DBConfig.getInstance().qualifyPolicy(
483                        jdbcConn.getMetaData().getDriverName(),
484                        DBConfig.IGNORE_UNIMPORTANT_EXCEPTION))
485                    return;
486                if ( this.autoCommit == Boolean.FALSE && DBConfig.getInstance().qualifyPolicy( jdbcConn.getMetaData().getDriverName(), DBConfig.TRY_COMMIT_THEN_CLOSE ))
487                {
488                    jdbcConn.commit();
489                    jdbcConn.close();
490                    return;
491                }
492            }
493            catch (SQLException e1) {
494
495            }
496            throw new JDBCException( ResourceConstants.CONN_CANNOT_CLOSE, e );
497        }
498        jdbcConn = null;
499    }
500
501    /*
502     * @see org.eclipse.datatools.connectivity.oda.IConnection#setAppContext(java.lang.Object)
503     */
504    public void setAppContext( Object context ) throws OdaException
505    {
506        if( context instanceof Map )
507            this.appContext = (Map) context;
508    }
509
510    /**
511     * Returns the application context Map set by {@link #setAppContext(Object)}.
512     * @return the application context Map; may be null if none was set
513     * @since 3.7.2
514     */
515    protected Map<?,?> getAppContextMap()
516    {
517        return this.appContext;
518    }
519
520    /**
521     * Assert the connection is opened.
522     *
523     * @throws JDBCException
524     */
525    private void assertOpened( ) throws OdaException
526    {
527        if ( jdbcConn == null )
528        {
529            throw new JDBCException( ResourceConstants.DRIVER_NO_CONNECTION,
530                    ResourceConstants.ERROR_NO_CONNECTION );
531        }
532    }
533
534    /* (non-Javadoc)
535     * @see org.eclipse.datatools.connectivity.oda.IConnection#setLocale(com.ibm.icu.util.ULocale)
536     */
537    public void setLocale( ULocale locale ) throws OdaException
538    {
539        // TODO Auto-generated method stub
540        throw new UnsupportedOperationException();
541    }
542
543    private Properties bidiTransform( Properties connectionProperties )
544    {
545        if ( connectionProperties == null )
546        {
547            return null;
548        }
549        Properties p = new Properties( );
550        String metadataBidiFormatStr = connectionProperties.getProperty(BidiConstants.METADATA_FORMAT_PROP_NAME);
551        if (!BidiFormat.isValidBidiFormat(metadataBidiFormatStr))
552            return connectionProperties;
553
554        for ( Enumeration enumeration = connectionProperties.propertyNames( ); enumeration.hasMoreElements( ); )
555        {
556            String propName = (String) enumeration.nextElement( );
557            String propValue = connectionProperties.getProperty( propName );
558            if ( (Constants.ODAUser.equals( propName ) || Constants.ODAPassword.equals( propName ))
559                    && propValue != null )
560            {
561                p.put( propName, BidiTransform.transform( propValue, BidiConstants.DEFAULT_BIDI_FORMAT_STR, metadataBidiFormatStr) );
562            }
563            else if (Constants.ODAURL.equals( propName ))
564            {
565                p.put( propName, BidiTransform.transformURL( propValue, BidiConstants.DEFAULT_BIDI_FORMAT_STR, metadataBidiFormatStr) );
566            }
567            else
568            {
569                p.put( propName, propValue );
570            }
571        }
572        return p;
573    }
574
575    /**
576     *  define constants  ODAURL, ODAPassword, ODAUser, ODADriverClass, ODADataSource
577     */
578    public static class Constants
579    {
580        public static final String REPORT_DBA_USERNAME = "report.dba.username";
581        public static final String REPORT_DBA_PASSWORD = "report.dba.password";
582        public static final String REPORT_DBA_DRIVER = "report.dba.driver";
583        public static final String REPORT_DBA_URL = "report.dba.url";
584        public static final String ODAURL = "odaURL";
585        public static final String ODAPassword = "odaPassword";
586        public static final String ODAUser = "odaUser";
587        public static final String ODADriverClass = "odaDriverClass";
588        public static final String ODADataSource = "odaDataSource";
589        public static final String ODAJndiName = "odaJndiName";
590        public static final String CONNECTION_AUTO_COMMIT = "odaAutoCommit";
591        public static final String CONNECTION_ISOLATION_MODE = "odaIsolationMode";
592        public static final int TRANSCATION_ISOLATION_DEFAULT = -1;
593        public static final String TRANSACTION_READ_COMMITTED = "read-committed";
594        public static final String TRANSACTION_READ_UNCOMMITTED = "read-uncommitted";
595        public static final String TRANSACTION_REPEATABLE_READ = "repeatable-read";
596        public static final String TRANSACTION_SERIALIZABLE = "serializable";
597        public static final String CONNECTION_PROPERTIES_STR = "connectionProperties";
598        public static final String ODAResourceIdentiers = "odaResourceIdentifiers";
599
600        public static int getIsolationMode( String value )
601        {
602            if( value == null )
603                return TRANSCATION_ISOLATION_DEFAULT;
604            if( TRANSACTION_READ_COMMITTED.equals( value ))
605                return java.sql.Connection.TRANSACTION_READ_COMMITTED;
606            if( TRANSACTION_READ_UNCOMMITTED.equals( value ))
607                return java.sql.Connection.TRANSACTION_READ_UNCOMMITTED;
608            if( TRANSACTION_REPEATABLE_READ.equals( value ))
609                return java.sql.Connection.TRANSACTION_REPEATABLE_READ;
610            if( TRANSACTION_SERIALIZABLE.equals( value ))
611                return java.sql.Connection.TRANSACTION_SERIALIZABLE;
612            return Integer.parseInt( value );
613        }
614    }
615
616}