PageRenderTime 26ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/jira-project/jira-components/jira-core/src/main/java/com/atlassian/jira/startup/DatabaseLauncher.java

https://bitbucket.org/ahmed_bilal_360factors/jira7-core
Java | 280 lines | 214 code | 26 blank | 40 comment | 28 complexity | 7d4a827610acf83f455f0cff7d15df20 MD5 | raw file
Possible License(s): Apache-2.0
  1. package com.atlassian.jira.startup;
  2. import com.atlassian.core.ofbiz.util.CoreTransactionUtil;
  3. import com.atlassian.jira.component.ComponentAccessor;
  4. import com.atlassian.jira.component.ComponentReference;
  5. import com.atlassian.jira.config.database.DatabaseConfig;
  6. import com.atlassian.jira.config.database.DatabaseConfigurationManager;
  7. import com.atlassian.jira.config.properties.JiraProperties;
  8. import com.atlassian.jira.database.DatabaseUtil;
  9. import com.atlassian.jira.exception.DataAccessException;
  10. import com.atlassian.jira.instrumentation.InstrumentationListenerManager;
  11. import com.atlassian.jira.instrumentation.jdbc.JdbcCollector;
  12. import com.atlassian.jira.ofbiz.DefaultOfBizConnectionFactory;
  13. import com.atlassian.jira.ofbiz.FieldMap;
  14. import com.atlassian.jira.ofbiz.OfBizDelegator;
  15. import com.atlassian.jira.upgrade.ConnectionKeeper;
  16. import org.apache.commons.lang.StringUtils;
  17. import org.ofbiz.core.entity.DelegatorInterface;
  18. import org.ofbiz.core.entity.GenericEntityException;
  19. import org.ofbiz.core.entity.GenericHelper;
  20. import org.ofbiz.core.entity.config.DatasourceInfo;
  21. import org.ofbiz.core.entity.config.EntityConfigUtil;
  22. import org.ofbiz.core.entity.model.ModelEntity;
  23. import org.slf4j.Logger;
  24. import org.slf4j.LoggerFactory;
  25. import java.sql.Connection;
  26. import java.sql.ResultSet;
  27. import java.sql.SQLException;
  28. import java.sql.Statement;
  29. import java.util.Map;
  30. /**
  31. * Configures the JIRA database by configuring transactions and setting up HSQL connection hacks.
  32. *
  33. * @since v4.4
  34. */
  35. public class DatabaseLauncher implements JiraLauncher {
  36. private static final Logger log = LoggerFactory.getLogger(DatabaseLauncher.class);
  37. // should be removed in 8.0; only required during migration of 6.x HSQL installation to 7.x H2
  38. private static final String HSQLDB = "hsql";
  39. private static final int CK_CONNECTIONS = 1;
  40. private static final int CK_SLEEPTIME = 300000;
  41. private static final String TRANSACTION_ISOLATION_PROPERTY = "jira.transaction.isolation";
  42. private static final String TRANSACTION_DISABLE_PROPERTY = "jira.transaction.disable";
  43. private static final String JIRA_INSTRUMENTATION_JDBC = "jira.instrumentation.jdbc";
  44. private static final String JIRA_INSTRUMENTATION_JDBC_EXECUTION_TIMES = "jira.instrumentation.jdbc.execution.times";
  45. private final JiraProperties jiraSystemProperties;
  46. private volatile ConnectionKeeper connectionKeeper;
  47. private final ComponentReference<DatabaseConfigurationManager> configManagerRef =
  48. ComponentAccessor.getComponentReference(DatabaseConfigurationManager.class);
  49. public DatabaseLauncher(final JiraProperties jiraSystemProperties) {
  50. this.jiraSystemProperties = jiraSystemProperties;
  51. }
  52. @Override
  53. public void start() {
  54. DatabaseConfig databaseConfig = configManagerRef.get().getDatabaseConfiguration();
  55. if (databaseConfig == null) {
  56. log.error("No database config found");
  57. return;
  58. }
  59. // Add the datasource and delegator
  60. final DatasourceInfo datasourceInfo = databaseConfig.getDatasourceInfo();
  61. if (datasourceInfo == null) {
  62. log.error("No datasource info found");
  63. return;
  64. }
  65. showEmbeddedDbWarning(databaseConfig);
  66. setupHsqlHacks(datasourceInfo);
  67. initDatabaseTransactions(datasourceInfo);
  68. // JRADEV-23357 clean up table name case sensitivity problems
  69. cleanupDatabaseTableNames();
  70. new JiraStartupLogger().printStartingMessageDatabaseOK();
  71. // Register for instrumentation....
  72. if (jiraSystemProperties.getBoolean(JIRA_INSTRUMENTATION_JDBC)) {
  73. ComponentAccessor.getComponentSafely(InstrumentationListenerManager.class)
  74. .ifPresent(instrumentationListenerManager -> {
  75. try {
  76. // Check to see if the collector is on the classpath
  77. Class.forName("com.atlassian.instrumentation.driver.JdbcThreadLocalCollector");
  78. instrumentationListenerManager.addRequestListener(new JdbcCollector(jiraSystemProperties.getBoolean(JIRA_INSTRUMENTATION_JDBC_EXECUTION_TIMES)));
  79. } catch (ClassNotFoundException e) {
  80. // Do nothing. The JDBC proxy was not in the classpath so the collector does not get added to the processing loop.
  81. }
  82. });
  83. }
  84. }
  85. @Override
  86. public void stop() {
  87. shutdownHsqlHacks();
  88. final DatabaseConfig config = configManagerRef.get().getDatabaseConfiguration();
  89. if (config != null) {
  90. // shutting down even though database was not yet set up
  91. String name = config.getDatasourceName();
  92. final EntityConfigUtil entityConfigUtil = EntityConfigUtil.getInstance();
  93. // check if delegator was ever configured
  94. if (entityConfigUtil.getDelegatorInfo(name) != null) {
  95. entityConfigUtil.removeDelegator(name);
  96. }
  97. // check if datasource was ever configured
  98. if (entityConfigUtil.getDatasourceInfo(name) != null) {
  99. entityConfigUtil.removeDatasource(name);
  100. }
  101. }
  102. }
  103. /**
  104. * Print some logspew to warn that we are running in an embedded database.
  105. */
  106. private void showEmbeddedDbWarning(final DatabaseConfig databaseConfig) {
  107. if (databaseConfig.isEmbeddedDatabase()) {
  108. final String message1 = databaseConfig.getDatabaseType() + " is an in-memory database, and susceptible to corruption when abnormally terminated.";
  109. final String message2 = "DO NOT USE IN PRODUCTION, please switch to a regular database.";
  110. final String line = StringUtils.repeat("*", Math.max(message1.length(), message2.length()));
  111. final String newLine = jiraSystemProperties.getProperty("line.separator");
  112. log.warn(newLine + newLine + line + newLine + message1 + newLine + message2 + newLine + line + newLine);
  113. }
  114. }
  115. /**
  116. * Sets up hacks to ensure HSQL connections stay alive
  117. *
  118. * @param datasourceInfo The datasource info
  119. * @deprecated since 7.0; only required during migration of 6.x HSQL installation to 7.x H2
  120. */
  121. private void setupHsqlHacks(DatasourceInfo datasourceInfo) {
  122. if (HSQLDB.equals(datasourceInfo.getFieldTypeName())) {
  123. if (log.isDebugEnabled()) {
  124. log.debug("Will open " + CK_CONNECTIONS + " connections to keep the database alive.");
  125. log.debug("Starting ConnectionKeeper with datasource name '" + datasourceInfo.getName() +
  126. "', connections to open '" + CK_CONNECTIONS + "' and sleep time '" + CK_SLEEPTIME + "' milliseconds.");
  127. }
  128. connectionKeeper = new ConnectionKeeper(datasourceInfo.getName(), CK_CONNECTIONS, CK_SLEEPTIME);
  129. connectionKeeper.start();
  130. }
  131. }
  132. /**
  133. * @deprecated since 7.0; only required during migration of 6.x HSQL installation to 7.x H2
  134. */
  135. private void shutdownHsqlHacks() {
  136. if (connectionKeeper != null) {
  137. connectionKeeper.shutdown();
  138. }
  139. }
  140. private void initDatabaseTransactions(final DatasourceInfo datasourceInfo) {
  141. boolean startTransaction = true;
  142. Integer isolationLevel = null;
  143. // should be removed in 8.0; only required during migration of 6.x HSQL installation to 7.x H2
  144. // Test for null datasource as it is null under JBoss
  145. if (datasourceInfo != null) {
  146. // HSQLDB does not support any transaction isolation except for Connection.
  147. if (HSQLDB.equals(datasourceInfo.getFieldTypeName())) {
  148. log.info("Setting isolation level to '" + Connection.TRANSACTION_READ_UNCOMMITTED + "' as this is the only isolation level '" + HSQLDB + "' supports.");
  149. isolationLevel = Connection.TRANSACTION_READ_UNCOMMITTED;
  150. }
  151. } else {
  152. log.info("Cannot get datasource information from server. Probably using JBoss. If using HSQLDB please set '" + TRANSACTION_ISOLATION_PROPERTY + "' to '1'. Other databases should not need this property.");
  153. }
  154. try {
  155. if (jiraSystemProperties.getBoolean(TRANSACTION_DISABLE_PROPERTY)) {
  156. log.info("System property + '" + TRANSACTION_DISABLE_PROPERTY + "' set to true.");
  157. startTransaction = false;
  158. }
  159. final String isolationProperty = jiraSystemProperties.getProperty(TRANSACTION_ISOLATION_PROPERTY);
  160. if (isolationProperty != null) {
  161. try {
  162. log.info("System property + '" + TRANSACTION_ISOLATION_PROPERTY + "' set to '" + isolationProperty + "'. Overriding default.");
  163. isolationLevel = Integer.valueOf(isolationProperty);
  164. } catch (final NumberFormatException e) {
  165. log.error("The '" + TRANSACTION_ISOLATION_PROPERTY + "' is set to a non-numeric value '" + isolationProperty + "'.");
  166. }
  167. }
  168. } catch (final SecurityException e) {
  169. log.warn(
  170. "There was a security problem trying to read transaction configuration system properties. This usually occurs if you are " + "running JIRA with a security manager. As these system properties are not required to be set (unless you are trying to solve another problem) " + "JIRA should function properly.",
  171. e);
  172. }
  173. log.info("Database transactions enabled: " + startTransaction);
  174. CoreTransactionUtil.setUseTransactions(startTransaction);
  175. if (isolationLevel != null) {
  176. log.info("Database transaction isolation level: " + isolationLevel);
  177. CoreTransactionUtil.setIsolationLevel(isolationLevel);
  178. } else {
  179. log.info("Using JIRA's default for database transaction isolation level: " + CoreTransactionUtil.getIsolationLevel());
  180. }
  181. }
  182. private void cleanupDatabaseTableNames() {
  183. // This is only a problem for people upgrading from an early 6.0.x release to 6.0.5 or higher.
  184. // We can eventually get rid of this method once upgrades are not supported from 6.0
  185. OfBizDelegator ofBizDelegator = ComponentAccessor.getOfBizDelegator();
  186. boolean needsTablesRecreated = false;
  187. // Test MovedIssueKey
  188. try {
  189. ofBizDelegator.findByAnd("MovedIssueKey", FieldMap.build("oldIssueKey", "bogus"));
  190. // Sweet - it worked
  191. } catch (DataAccessException ex) {
  192. log.warn("JRADEV-23357: unable to select from the 'MovedIssueKey' entity.");
  193. cleanupDatabaseTableName("MOVED_ISSUE_KEY");
  194. needsTablesRecreated = true;
  195. }
  196. // Test ProjectKey
  197. try {
  198. ofBizDelegator.findByAnd("ProjectKey", FieldMap.build("projectKey", "bogus"));
  199. // Sweet - it worked
  200. } catch (DataAccessException ex) {
  201. log.warn("JRADEV-23357: unable to select from the 'ProjectKey' entity.");
  202. cleanupDatabaseTableName("PROJECT_KEY");
  203. needsTablesRecreated = true;
  204. }
  205. if (needsTablesRecreated) {
  206. kickOfbizInTheGuts();
  207. }
  208. }
  209. @edu.umd.cs.findbugs.annotations.SuppressWarnings(value =
  210. "SQL_NONCONSTANT_STRING_PASSED_TO_EXECUTE", justification = "Dynamic SQL does not come from user input so no SQL injection is possible.")
  211. private void cleanupDatabaseTableName(String tableName) {
  212. Connection con = null;
  213. Statement stmt = null;
  214. ResultSet rs = null;
  215. try {
  216. // JRADEV-23357
  217. // So the table presumably exists but in upper-case and we need lower-case. Lets confirm that...
  218. con = DefaultOfBizConnectionFactory.getInstance().getConnection();
  219. stmt = con.createStatement();
  220. rs = stmt.executeQuery("SELECT COUNT(*) FROM " + tableName);
  221. rs.next();
  222. int count = rs.getInt(1);
  223. rs.close();
  224. rs = null;
  225. stmt.close();
  226. stmt = null;
  227. if (count > 0) {
  228. throw new IllegalStateException("Need to rename the " + tableName + " table, but there is existing data in it. Please contact Atlassian Support.");
  229. }
  230. // Yep, table exists and has no data - drop and recreate
  231. log.info("We need to change the case of table '" + tableName + "'... will drop table and then recreate.");
  232. stmt = con.createStatement();
  233. stmt.execute("DROP TABLE " + tableName);
  234. // In a sec we will force OfBiz to rerun its startup check and add back the missing tables.
  235. } catch (SQLException e) {
  236. throw new DataAccessException(e);
  237. } finally {
  238. DatabaseUtil.closeQuietly(rs);
  239. DatabaseUtil.closeQuietly(stmt);
  240. DatabaseUtil.closeQuietly(con);
  241. }
  242. }
  243. private void kickOfbizInTheGuts() {
  244. DelegatorInterface delegatorInterface = ComponentAccessor.getComponent(DelegatorInterface.class);
  245. Map<String, ModelEntity> modelEntities = delegatorInterface.getModelEntityMapByGroup("default");
  246. try {
  247. GenericHelper helper = delegatorInterface.getEntityHelper("ProjectKey");
  248. // This will loop over all entities and add any that are missing - just like we do on startup.
  249. helper.checkDataSource(modelEntities, null, true);
  250. } catch (GenericEntityException ex) {
  251. throw new DataAccessException(ex);
  252. }
  253. }
  254. }