001    /*
002     * Sonar, open source software quality management tool.
003     * Copyright (C) 2009 SonarSource SA
004     * mailto:contact AT sonarsource DOT com
005     *
006     * Sonar is free software; you can redistribute it and/or
007     * modify it under the terms of the GNU Lesser General Public
008     * License as published by the Free Software Foundation; either
009     * version 3 of the License, or (at your option) any later version.
010     *
011     * Sonar is distributed in the hope that it will be useful,
012     * but WITHOUT ANY WARRANTY; without even the implied warranty of
013     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014     * Lesser General Public License for more details.
015     *
016     * You should have received a copy of the GNU Lesser General Public
017     * License along with Sonar; if not, write to the Free Software
018     * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
019     */
020    package org.sonar.api.database;
021    
022    import org.apache.commons.configuration.Configuration;
023    import org.hibernate.dialect.DB2Dialect;
024    import org.hibernate.dialect.HSQLDialect;
025    import org.slf4j.Logger;
026    import org.slf4j.LoggerFactory;
027    import org.sonar.api.database.dialect.*;
028    
029    import java.sql.Connection;
030    import java.sql.SQLException;
031    import java.util.Properties;
032    import javax.persistence.EntityManager;
033    import javax.persistence.EntityManagerFactory;
034    import javax.persistence.Persistence;
035    import javax.persistence.PersistenceException;
036    
037    public abstract class AbstractDatabaseConnector implements DatabaseConnector {
038      protected static final Logger LOG_SQL = LoggerFactory.getLogger("org.hibernate.SQL");
039      protected static final Logger LOG_STATISTICS = LoggerFactory.getLogger("org.sonar.DBSTATISTICS");
040    
041      private Configuration configuration = null;
042      private EntityManagerFactory factory = null;
043      private int databaseVersion = SchemaMigration.VERSION_UNKNOWN;
044      private boolean operational = false;
045      private boolean started = false;
046      private boolean startsFailIfSchemaOutdated;
047      private Integer transactionIsolation = null;
048    
049      protected AbstractDatabaseConnector(Configuration configuration, boolean startsFailIfSchemaOutdated) {
050        this.configuration = configuration;
051        this.startsFailIfSchemaOutdated = startsFailIfSchemaOutdated;
052      }
053    
054      protected AbstractDatabaseConnector() {
055      }
056    
057      public Configuration getConfiguration() {
058        return configuration;
059      }
060    
061      public void setConfiguration(Configuration configuration) {
062        this.configuration = configuration;
063      }
064    
065      /**
066       * Indicates if the connector is operational : database connection OK and schema version OK
067       */
068      public boolean isOperational() {
069        return operational;
070      }
071    
072      /**
073       * Indicates if the connector is started : database connection OK and schema version OK or KO
074       */
075      protected boolean isStarted() {
076        return started;
077      }
078    
079      /**
080       * Get the JDBC transaction isolation defined by the configuration
081       *
082       * @return JDBC transaction isolation
083       */
084      public Integer getTransactionIsolation() {
085        return transactionIsolation;
086      }
087    
088      public void start() {
089        if (!started) {
090          transactionIsolation = configuration.getInteger(DatabaseProperties.PROP_ISOLATION, null /* use driver default setting */);
091          testConnection();
092          started = true;
093        }
094        if (!operational) {
095          boolean upToDate = upToDateSchemaVersion();
096          if (!upToDate && startsFailIfSchemaOutdated) {
097            throw new DatabaseException(databaseVersion, SchemaMigration.LAST_VERSION);
098          }
099          if (upToDate) {
100            factory = createEntityManagerFactory();
101            operational = true;
102          }
103        }
104      }
105    
106      public void stop() {
107        if (factory != null && factory.isOpen()) {
108          factory.close();
109          factory = null;
110        }
111        operational = false;
112        started = false;
113      }
114    
115      public abstract void setupEntityManagerFactory(Properties factoryProps);
116    
117      public EntityManagerFactory getEntityManagerFactory() {
118        return factory;
119      }
120    
121      protected void setEntityManagerFactory(EntityManagerFactory factory) {
122        this.factory = factory;
123      }
124    
125      protected EntityManagerFactory createEntityManagerFactory() {
126        // other settings are stored into /META-INF/persistence.xml
127        Properties props = new Properties();
128        if (transactionIsolation != null) {
129          props.put("hibernate.connection.isolation", Integer.toString(transactionIsolation));
130        }
131        props.put("hibernate.hbm2ddl.auto", getConfiguration().getString(DatabaseProperties.PROP_HIBERNATE_HBM2DLL, "validate"));
132        props.put("hibernate.dialect", getDialectClass(getDialect()));
133    
134        props.put("hibernate.generate_statistics", Boolean.valueOf(LOG_STATISTICS.isInfoEnabled()).toString());
135        props.put("hibernate.show_sql", Boolean.valueOf(LOG_SQL.isInfoEnabled()).toString());
136    
137        // custom impl setup
138        setupEntityManagerFactory(props);
139        return Persistence.createEntityManagerFactory("sonar", props);
140      }
141    
142      public EntityManager createEntityManager() {
143        EntityManager manager = factory.createEntityManager();
144        if (LOG_STATISTICS.isInfoEnabled()) {
145          manager = new StatisticsEntityManager(factory, manager, LOG_STATISTICS);
146        }
147        return manager;
148      }
149    
150      private void testConnection() throws DatabaseException {
151        Connection connection = null;
152        try {
153          connection = getConnection();
154        } catch (SQLException e) {
155          throw new DatabaseException("Cannot open connection to database: " + e.getMessage(), e);
156        } finally {
157          silentlyCloseConnection(connection);
158        }
159      }
160    
161      protected int loadVersion() {
162        Connection connection = null;
163        try {
164          connection = getConnection();
165          return SchemaMigration.getCurrentVersion(connection);
166        } catch (SQLException e) {
167          // schema not created
168          return 0;
169        } finally {
170          silentlyCloseConnection(connection);
171        }
172      }
173    
174      private void silentlyCloseConnection(Connection connection) {
175        if (connection != null) {
176          try {
177            connection.close();
178          } catch (SQLException e) {
179          }
180        }
181      }
182    
183      private boolean upToDateSchemaVersion() {
184        if (databaseVersion == SchemaMigration.LAST_VERSION) {
185          return true;
186        }
187        databaseVersion = loadVersion();
188        return databaseVersion == SchemaMigration.LAST_VERSION;
189      }
190    
191      protected int getDatabaseVersion() {
192        return databaseVersion;
193      }
194    
195      public String getDialect() {
196        String dialect = configuration.getString("sonar.jdbc.dialect");
197        if (dialect == null) {
198          Connection connection = null;
199          try {
200            connection = getConnection();
201            dialect = getDialectFromJdbcUrl(connection.getMetaData().getURL());
202    
203          } catch (SQLException e) {
204            throw new PersistenceException("can not autodetect the dialect", e);
205    
206          } finally {
207            silentlyCloseConnection(connection);
208          }
209        }
210        return dialect;
211      }
212    
213      public String getDialectClass(String dialect) {
214        String dialectClass = configuration.getString(DatabaseProperties.PROP_DIALECT_CLASS);
215        if (dialectClass == null) {
216          dialectClass = getHibernateDialectClassName(dialect);
217        }
218        return dialectClass;
219      }
220    
221      private String getDialectFromJdbcUrl(String url) {
222        if (url.toLowerCase().startsWith("jdbc:db2:")) {
223          return DatabaseProperties.DIALECT_DB2;
224        }
225        if (url.toLowerCase().startsWith("jdbc:derby:")) {
226          return DatabaseProperties.DIALECT_DERBY;
227        }
228        if (url.toLowerCase().startsWith("jdbc:hsqldb:")) {
229          return DatabaseProperties.DIALECT_HSQLDB;
230        }
231        if (url.toLowerCase().startsWith("jdbc:microsoft:sqlserver:") ||
232            url.toLowerCase().startsWith("jdbc:jtds:sqlserver:")) {
233          return DatabaseProperties.DIALECT_MSSQL;
234        }
235        if (url.toLowerCase().startsWith("jdbc:mysql:")) {
236          return DatabaseProperties.DIALECT_MYSQL;
237        }
238        if (url.toLowerCase().startsWith("jdbc:oracle:")) {
239          return DatabaseProperties.DIALECT_ORACLE;
240        }
241        if (url.toLowerCase().startsWith("jdbc:postgresql:")) {
242          return DatabaseProperties.DIALECT_POSTGRESQL;
243        }
244        return null;
245      }
246    
247      private String getHibernateDialectClassName(String dialect) {
248        if (DatabaseProperties.DIALECT_DB2.equals(dialect)) {
249          return DB2Dialect.class.getName();
250        }
251        if (DatabaseProperties.DIALECT_DERBY.equals(dialect)) {
252          return DerbyWithDecimalDialect.class.getName();
253        }
254        if (DatabaseProperties.DIALECT_HSQLDB.equals(dialect)) {
255          return HSQLDialect.class.getName();
256        }
257        if (DatabaseProperties.DIALECT_MSSQL.equals(dialect)) {
258          return MsSqlDialect.class.getName();
259        }
260        if (DatabaseProperties.DIALECT_MYSQL.equals(dialect)) {
261          return MySqlWithDecimalDialect.class.getName();
262        }
263        if (DatabaseProperties.DIALECT_ORACLE.equals(dialect)) {
264          return Oracle10gWithDecimalDialect.class.getName();
265        }
266        if (DatabaseProperties.DIALECT_POSTGRESQL.equals(dialect)) {
267          return PostgreSQLWithDecimalDialect.class.getName();
268        }
269        return null;
270      }
271    }