001    /*
002     * Sonar, open source software quality management tool.
003     * Copyright (C) 2008-2011 SonarSource
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.jpa.session;
021    
022    import org.apache.commons.configuration.Configuration;
023    import org.apache.commons.lang.StringUtils;
024    import org.slf4j.Logger;
025    import org.slf4j.LoggerFactory;
026    import org.sonar.api.database.DatabaseProperties;
027    import org.sonar.jpa.dialect.Dialect;
028    import org.sonar.jpa.dialect.DialectRepository;
029    import org.sonar.jpa.entity.SchemaMigration;
030    
031    import javax.persistence.EntityManager;
032    import javax.persistence.EntityManagerFactory;
033    import javax.persistence.Persistence;
034    import java.sql.Connection;
035    import java.sql.SQLException;
036    import java.util.Iterator;
037    import java.util.Map;
038    import java.util.Properties;
039    
040    public abstract class AbstractDatabaseConnector implements DatabaseConnector {
041      protected static final Logger LOG_SQL = LoggerFactory.getLogger("org.hibernate.SQL");
042      protected static final Logger LOG = LoggerFactory.getLogger(AbstractDatabaseConnector.class);
043    
044      private Configuration configuration = null;
045      private EntityManagerFactory factory = null;
046      private int databaseVersion = SchemaMigration.VERSION_UNKNOWN;
047      private boolean operational = false;
048      private boolean started = false;
049      private boolean startsFailIfSchemaOutdated;
050      private Integer transactionIsolation = null;
051      private Dialect dialect = null;
052    
053      protected AbstractDatabaseConnector(Configuration configuration, boolean startsFailIfSchemaOutdated) {
054        this.configuration = configuration;
055        this.startsFailIfSchemaOutdated = startsFailIfSchemaOutdated;
056      }
057    
058      protected AbstractDatabaseConnector() {
059      }
060    
061      public Configuration getConfiguration() {
062        return configuration;
063      }
064    
065      public void setConfiguration(Configuration configuration) {
066        this.configuration = configuration;
067      }
068    
069      public String getDialectId() {
070        return dialect.getId();
071      }
072      /**
073       * Indicates if the connector is operational : database connection OK and schema version OK
074       */
075      public boolean isOperational() {
076        return operational;
077      }
078    
079      /**
080       * Indicates if the connector is started : database connection OK and schema version OK or KO
081       */
082      protected boolean isStarted() {
083        return started;
084      }
085    
086      /**
087       * Get the JDBC transaction isolation defined by the configuration
088       *
089       * @return JDBC transaction isolation
090       */
091      public final Integer getTransactionIsolation() {
092        return transactionIsolation;
093      }
094    
095      public void start() {
096        if (!started) {
097          transactionIsolation = configuration.getInteger(DatabaseProperties.PROP_ISOLATION, null /* use driver default setting */);
098          String jdbcConnectionUrl = testConnection();
099          dialect = DialectRepository.find(configuration.getString("sonar.jdbc.dialect"), jdbcConnectionUrl);
100          LoggerFactory.getLogger("org.sonar.INFO").info("Database dialect class " + dialect.getClass().getName());
101          started = true;
102        }
103        if (!operational) {
104          boolean upToDate = upToDateSchemaVersion();
105          if (!upToDate && startsFailIfSchemaOutdated) {
106            throw new DatabaseException(databaseVersion, SchemaMigration.LAST_VERSION);
107          }
108          if (upToDate) {
109            factory = createEntityManagerFactory();
110            operational = true;
111          }
112        }
113      }
114    
115      public void stop() {
116        if (factory != null && factory.isOpen()) {
117          factory.close();
118          factory = null;
119        }
120        operational = false;
121        started = false;
122      }
123    
124      public abstract void setupEntityManagerFactory(Properties factoryProps);
125    
126      public EntityManagerFactory getEntityManagerFactory() {
127        return factory;
128      }
129    
130      protected void setEntityManagerFactory(EntityManagerFactory factory) {
131        this.factory = factory;
132      }
133    
134      protected EntityManagerFactory createEntityManagerFactory() {
135        // other settings are stored into /META-INF/persistence.xml
136        Properties props = getHibernateProperties();
137        logHibernateSettings(props);
138        return Persistence.createEntityManagerFactory("sonar", props);
139      }
140    
141      private void logHibernateSettings(Properties props) {
142        if (LOG.isDebugEnabled()) {
143          for (Map.Entry<Object, Object> entry : props.entrySet()) {
144            LOG.debug(entry.getKey() + ": " + entry.getValue());
145          }
146        }
147      }
148    
149      protected Properties getHibernateProperties() {
150        Properties props = new Properties();
151        if (transactionIsolation != null) {
152          props.put("hibernate.connection.isolation", Integer.toString(transactionIsolation));
153        }
154        props.put("hibernate.hbm2ddl.auto", getConfiguration().getString(DatabaseProperties.PROP_HIBERNATE_HBM2DLL, "validate"));
155        props.put("hibernate.dialect", getDialectClass());
156    
157        props.put("hibernate.generate_statistics", getConfiguration().getBoolean(DatabaseProperties.PROP_HIBERNATE_GENERATE_STATISTICS, false));
158        props.put("hibernate.show_sql", Boolean.valueOf(LOG_SQL.isInfoEnabled()).toString());
159    
160        Configuration subset = getConfiguration().subset("sonar.hibernate");
161        for (Iterator keys = subset.getKeys(); keys.hasNext();) {
162          String key = (String) keys.next();
163          if (StringUtils.isNotBlank((String)subset.getProperty(key))) {
164            props.put("hibernate." + key, subset.getProperty(key));
165          }
166        }
167    
168        // custom impl setup
169        setupEntityManagerFactory(props);
170    
171    
172        return props;
173      }
174    
175      public EntityManager createEntityManager() {
176        return factory.createEntityManager();
177      }
178    
179      private String testConnection() throws DatabaseException {
180        Connection connection = null;
181        try {
182          connection = getConnection();
183          return connection.getMetaData().getURL();
184    
185        } catch (SQLException e) {
186          throw new DatabaseException("Cannot open connection to database: " + e.getMessage(), e);
187    
188        } finally {
189          close(connection);
190        }
191      }
192    
193      protected int loadVersion() {
194        Connection connection = null;
195        try {
196          connection = getConnection();
197          return SchemaMigration.getCurrentVersion(connection);
198    
199        } catch (SQLException e) {
200          // schema not created
201          return 0;
202        } finally {
203          close(connection);
204        }
205      }
206    
207      private void close(Connection connection) {
208        if (connection != null) {
209          try {
210            connection.close();
211          } catch (SQLException e) {
212            // why does close() throw a checked-exception ???
213          }
214        }
215      }
216    
217      protected boolean upToDateSchemaVersion() {
218        if (databaseVersion == SchemaMigration.LAST_VERSION) {
219          return true;
220        }
221        databaseVersion = loadVersion();
222        return databaseVersion == SchemaMigration.LAST_VERSION;
223      }
224    
225      public final int getDatabaseVersion() {
226        return databaseVersion;
227      }
228    
229      public final Dialect getDialect() {
230        return dialect;
231      }
232    
233      public final String getDialectClass() {
234        String dialectClass = configuration.getString(DatabaseProperties.PROP_DIALECT_CLASS);
235        if (dialectClass == null && dialect != null) {
236          dialectClass = dialect.getHibernateDialectClass().getName();
237        }
238        return dialectClass;
239      }
240    
241    }