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 }