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