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 }