PageRenderTime 52ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/core/schema/src/main/java/org/opennms/core/schema/Migrator.java

https://bitbucket.org/peternixon/opennms-mirror
Java | 527 lines | 320 code | 49 blank | 158 comment | 44 complexity | 55a0140f15fe3e07d0d7cfeb33d43ee2 MD5 | raw file
Possible License(s): Apache-2.0, BSD-3-Clause, GPL-2.0
  1. /*******************************************************************************
  2. * This file is part of OpenNMS(R).
  3. *
  4. * Copyright (C) 2009-2012 The OpenNMS Group, Inc.
  5. * OpenNMS(R) is Copyright (C) 1999-2012 The OpenNMS Group, Inc.
  6. *
  7. * OpenNMS(R) is a registered trademark of The OpenNMS Group, Inc.
  8. *
  9. * OpenNMS(R) is free software: you can redistribute it and/or modify
  10. * it under the terms of the GNU General Public License as published
  11. * by the Free Software Foundation, either version 3 of the License,
  12. * or (at your option) any later version.
  13. *
  14. * OpenNMS(R) is distributed in the hope that it will be useful,
  15. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. * GNU General Public License for more details.
  18. *
  19. * You should have received a copy of the GNU General Public License
  20. * along with OpenNMS(R). If not, see:
  21. * http://www.gnu.org/licenses/
  22. *
  23. * For more information contact:
  24. * OpenNMS(R) Licensing <license@opennms.org>
  25. * http://www.opennms.org/
  26. * http://www.opennms.com/
  27. *******************************************************************************/
  28. package org.opennms.core.schema;
  29. import java.io.File;
  30. import java.net.MalformedURLException;
  31. import java.net.URL;
  32. import java.net.URLClassLoader;
  33. import java.sql.Connection;
  34. import java.sql.ResultSet;
  35. import java.sql.SQLException;
  36. import java.sql.Statement;
  37. import java.util.ArrayList;
  38. import java.util.List;
  39. import java.util.regex.Matcher;
  40. import java.util.regex.Pattern;
  41. import javax.sql.DataSource;
  42. import liquibase.Liquibase;
  43. import liquibase.database.DatabaseConnection;
  44. import liquibase.database.jvm.JdbcConnection;
  45. import liquibase.logging.LogFactory;
  46. import liquibase.logging.LogLevel;
  47. import liquibase.resource.ResourceAccessor;
  48. import org.opennms.core.utils.LogUtils;
  49. import org.springframework.core.io.DefaultResourceLoader;
  50. import org.springframework.core.io.ResourceLoader;
  51. /**
  52. * <p>Migrator class.</p>
  53. *
  54. * @author ranger
  55. * @version $Id: $
  56. */
  57. public class Migrator {
  58. private static final Pattern POSTGRESQL_VERSION_PATTERN = Pattern.compile("^(?:PostgreSQL|EnterpriseDB) (\\d+\\.\\d+)");
  59. public static final float POSTGRES_MIN_VERSION = 7.4f;
  60. public static final float POSTGRES_MAX_VERSION_PLUS_ONE = 9.9f;
  61. private DataSource m_dataSource;
  62. private DataSource m_adminDataSource;
  63. private Float m_databaseVersion;
  64. private boolean m_validateDatabaseVersion = true;
  65. private boolean m_createUser = true;
  66. private boolean m_createDatabase = true;
  67. /**
  68. * <p>Constructor for Migrator.</p>
  69. */
  70. public Migrator() {
  71. initLogging();
  72. }
  73. private void initLogging() {
  74. LogFactory.getLogger().setLogLevel(LogLevel.INFO);
  75. }
  76. public void enableDebug() {
  77. LogFactory.getLogger().setLogLevel(LogLevel.DEBUG);
  78. }
  79. /**
  80. * <p>getDataSource</p>
  81. *
  82. * @return a {@link javax.sql.DataSource} object.
  83. */
  84. public DataSource getDataSource() {
  85. return m_dataSource;
  86. }
  87. /**
  88. * <p>setDataSource</p>
  89. *
  90. * @param dataSource a {@link javax.sql.DataSource} object.
  91. */
  92. public void setDataSource(final DataSource dataSource) {
  93. m_dataSource = dataSource;
  94. }
  95. /**
  96. * <p>getAdminDataSource</p>
  97. *
  98. * @return a {@link javax.sql.DataSource} object.
  99. */
  100. public DataSource getAdminDataSource() {
  101. return m_adminDataSource;
  102. }
  103. /**
  104. * <p>setAdminDataSource</p>
  105. *
  106. * @param dataSource a {@link javax.sql.DataSource} object.
  107. */
  108. public void setAdminDataSource(final DataSource dataSource) {
  109. m_adminDataSource = dataSource;
  110. }
  111. /**
  112. * <p>setValidateDatabaseVersion</p>
  113. *
  114. * @param validate a boolean.
  115. */
  116. public void setValidateDatabaseVersion(final boolean validate) {
  117. m_validateDatabaseVersion = validate;
  118. }
  119. /**
  120. * <p>setCreateUser</p>
  121. *
  122. * @param create a boolean.
  123. */
  124. public void setCreateUser(final boolean create) {
  125. m_createUser = create;
  126. }
  127. /**
  128. * <p>setCreateDatabase</p>
  129. *
  130. * @param create a boolean.
  131. */
  132. public void setCreateDatabase(final boolean create) {
  133. m_createDatabase = create;
  134. }
  135. /**
  136. * <p>getDatabaseVersion</p>
  137. *
  138. * @return a {@link java.lang.Float} object.
  139. * @throws org.opennms.core.schema.MigrationException if any.
  140. */
  141. public Float getDatabaseVersion() throws MigrationException {
  142. if (m_databaseVersion == null) {
  143. String versionString = null;
  144. Statement st = null;
  145. ResultSet rs = null;
  146. Connection c = null;
  147. try {
  148. c = m_adminDataSource.getConnection();
  149. st = c.createStatement();
  150. rs = st.executeQuery("SELECT version()");
  151. if (!rs.next()) {
  152. throw new MigrationException("Database didn't return any rows for 'SELECT version()'");
  153. }
  154. versionString = rs.getString(1);
  155. rs.close();
  156. st.close();
  157. } catch (final SQLException e) {
  158. throw new MigrationException("an error occurred getting the version from the database", e);
  159. } finally {
  160. cleanUpDatabase(c, st, rs);
  161. }
  162. final Matcher m = POSTGRESQL_VERSION_PATTERN.matcher(versionString);
  163. if (!m.find()) {
  164. throw new MigrationException("Could not parse version number out of version string: " + versionString);
  165. }
  166. m_databaseVersion = Float.parseFloat(m.group(1));
  167. }
  168. return m_databaseVersion;
  169. }
  170. /**
  171. * <p>validateDatabaseVersion</p>
  172. *
  173. * @throws org.opennms.core.schema.MigrationException if any.
  174. */
  175. public void validateDatabaseVersion() throws MigrationException {
  176. if (!m_validateDatabaseVersion) {
  177. LogUtils.infof(this, "skipping database version validation");
  178. return;
  179. }
  180. LogUtils.infof(this, "validating database version");
  181. final Float dbv = getDatabaseVersion();
  182. if (dbv == null) {
  183. throw new MigrationException("unable to determine database version");
  184. }
  185. final String message = String.format(
  186. "Unsupported database version \"%f\" -- you need at least %f and less than %f. "
  187. + "Use the \"-Q\" option to disable this check if you feel brave and are willing "
  188. + "to find and fix bugs found yourself.",
  189. dbv.floatValue(), POSTGRES_MIN_VERSION, POSTGRES_MAX_VERSION_PLUS_ONE
  190. );
  191. if (dbv < POSTGRES_MIN_VERSION || dbv >= POSTGRES_MAX_VERSION_PLUS_ONE) {
  192. throw new MigrationException(message);
  193. }
  194. }
  195. /**
  196. * Get the expected extension for this platform.
  197. * @return
  198. */
  199. private String getExtension(final boolean jni) {
  200. final String osName = System.getProperty("os.name").toLowerCase();
  201. if (osName.startsWith("windows")) {
  202. return "dll";
  203. } else if (osName.startsWith("mac")) {
  204. if (jni) {
  205. return "jnilib";
  206. } else {
  207. return "so";
  208. }
  209. }
  210. return "so";
  211. }
  212. /**
  213. * <p>createLangPlPgsql</p>
  214. *
  215. * @throws org.opennms.core.schema.MigrationException if any.
  216. */
  217. public void createLangPlPgsql() throws MigrationException {
  218. LogUtils.infof(this, "adding PL/PgSQL support to the database, if necessary");
  219. Statement st = null;
  220. ResultSet rs = null;
  221. Connection c = null;
  222. try {
  223. c = m_dataSource.getConnection();
  224. st = c.createStatement();
  225. rs = st.executeQuery("SELECT oid FROM pg_proc WHERE " + "proname='plpgsql_call_handler' AND " + "proargtypes = ''");
  226. if (rs.next()) {
  227. LogUtils.infof(this, "PL/PgSQL call handler exists");
  228. } else {
  229. LogUtils.infof(this, "adding PL/PgSQL call handler");
  230. st.execute("CREATE FUNCTION plpgsql_call_handler () " + "RETURNS OPAQUE AS '$libdir/plpgsql." + getExtension(false) + "' LANGUAGE 'c'");
  231. }
  232. rs.close();
  233. rs = st.executeQuery("SELECT pg_language.oid "
  234. + "FROM pg_language, pg_proc WHERE "
  235. + "pg_proc.proname='plpgsql_call_handler' AND "
  236. + "pg_proc.proargtypes = '' AND "
  237. + "pg_proc.oid = pg_language.lanplcallfoid AND "
  238. + "pg_language.lanname = 'plpgsql'");
  239. if (rs.next()) {
  240. LogUtils.infof(this, "PL/PgSQL language exists");
  241. } else {
  242. LogUtils.infof(this, "adding PL/PgSQL language");
  243. st.execute("CREATE TRUSTED PROCEDURAL LANGUAGE 'plpgsql' "
  244. + "HANDLER plpgsql_call_handler LANCOMPILER 'PL/pgSQL'");
  245. }
  246. } catch (final SQLException e) {
  247. throw new MigrationException("an error occurred getting the version from the database", e);
  248. } finally {
  249. cleanUpDatabase(c, st, rs);
  250. }
  251. }
  252. /**
  253. * <p>databaseUserExists</p>
  254. *
  255. * @param migration a {@link org.opennms.core.schema.Migration} object.
  256. * @return a boolean.
  257. * @throws org.opennms.core.schema.MigrationException if any.
  258. */
  259. public boolean databaseUserExists(final Migration migration) throws MigrationException {
  260. Statement st = null;
  261. ResultSet rs = null;
  262. Connection c = null;
  263. try {
  264. c = m_adminDataSource.getConnection();
  265. st = c.createStatement();
  266. rs = st.executeQuery("SELECT usename FROM pg_user WHERE usename = '" + migration.getDatabaseUser() + "'");
  267. if (rs.next()) {
  268. final String datname = rs.getString("usename");
  269. if (datname != null && datname.equalsIgnoreCase(migration.getDatabaseUser())) {
  270. return true;
  271. } else {
  272. return false;
  273. }
  274. }
  275. return rs.next();
  276. } catch (final SQLException e) {
  277. throw new MigrationException("an error occurred determining whether the OpenNMS user exists", e);
  278. } finally {
  279. cleanUpDatabase(c, st, rs);
  280. }
  281. }
  282. /**
  283. * <p>createUser</p>
  284. *
  285. * @param migration a {@link org.opennms.core.schema.Migration} object.
  286. * @throws org.opennms.core.schema.MigrationException if any.
  287. */
  288. public void createUser(final Migration migration) throws MigrationException {
  289. if (!m_createUser || databaseUserExists(migration)) {
  290. return;
  291. }
  292. LogUtils.infof(this, "creating OpenNMS user, if necessary");
  293. Statement st = null;
  294. ResultSet rs = null;
  295. Connection c = null;
  296. try {
  297. c = m_adminDataSource.getConnection();
  298. st = c.createStatement();
  299. st.execute("CREATE USER " + migration.getDatabaseUser() + " WITH PASSWORD '" + migration.getDatabasePassword() + "' CREATEDB CREATEUSER");
  300. } catch (final SQLException e) {
  301. throw new MigrationException("an error occurred creating the OpenNMS user", e);
  302. } finally {
  303. cleanUpDatabase(c, st, rs);
  304. }
  305. }
  306. /**
  307. * <p>databaseExists</p>
  308. *
  309. * @param migration a {@link org.opennms.core.schema.Migration} object.
  310. * @return a boolean.
  311. * @throws org.opennms.core.schema.MigrationException if any.
  312. */
  313. public boolean databaseExists(final Migration migration) throws MigrationException {
  314. Statement st = null;
  315. ResultSet rs = null;
  316. Connection c = null;
  317. try {
  318. c = m_adminDataSource.getConnection();
  319. st = c.createStatement();
  320. rs = st.executeQuery("SELECT datname from pg_database WHERE datname = '" + migration.getDatabaseName() + "'");
  321. if (rs.next()) {
  322. final String datname = rs.getString("datname");
  323. if (datname != null && datname.equalsIgnoreCase(migration.getDatabaseName())) {
  324. return true;
  325. } else {
  326. return false;
  327. }
  328. }
  329. return rs.next();
  330. } catch (final SQLException e) {
  331. throw new MigrationException("an error occurred determining whether the OpenNMS user exists", e);
  332. } finally {
  333. cleanUpDatabase(c, st, rs);
  334. }
  335. }
  336. public void createSchema(final Migration migration) throws MigrationException {
  337. if (!m_createDatabase || schemaExists(migration)) {
  338. return;
  339. }
  340. }
  341. public boolean schemaExists(final Migration migration) throws MigrationException {
  342. /* FIXME: not sure how to ask postgresql for a schema
  343. Statement st = null;
  344. ResultSet rs = null;
  345. Connection c = null;
  346. try {
  347. c = m_adminDataSource.getConnection();
  348. st = c.createStatement();
  349. rs = st.executeQuery("SELECT datname from pg_database WHERE datname = '" + migration.getDatabaseName() + "'");
  350. if (rs.next()) {
  351. final String datname = rs.getString("datname");
  352. if (datname != null && datname.equalsIgnoreCase(migration.getDatabaseName())) {
  353. return true;
  354. } else {
  355. return false;
  356. }
  357. }
  358. return rs.next();
  359. } catch (final SQLException e) {
  360. throw new MigrationException("an error occurred determining whether the OpenNMS user exists", e);
  361. } finally {
  362. cleanUpDatabase(c, st, rs);
  363. }
  364. */
  365. return true;
  366. }
  367. /**
  368. * <p>createDatabase</p>
  369. *
  370. * @param migration a {@link org.opennms.core.schema.Migration} object.
  371. * @throws org.opennms.core.schema.MigrationException if any.
  372. */
  373. public void createDatabase(final Migration migration) throws MigrationException {
  374. if (!m_createDatabase || databaseExists(migration)) {
  375. return;
  376. }
  377. LogUtils.infof(this, "creating OpenNMS database, if necessary");
  378. if (!databaseUserExists(migration)) {
  379. throw new MigrationException(String.format("database will not be created: unable to grant access (user %s does not exist)", migration.getDatabaseUser()));
  380. }
  381. Statement st = null;
  382. ResultSet rs = null;
  383. Connection c = null;
  384. try {
  385. c = m_adminDataSource.getConnection();
  386. st = c.createStatement();
  387. st.execute("CREATE DATABASE \"" + migration.getDatabaseName() + "\" WITH ENCODING='UNICODE'");
  388. st.execute("GRANT ALL ON DATABASE \"" + migration.getDatabaseName() + "\" TO \"" + migration.getDatabaseUser() + "\"");
  389. } catch (final SQLException e) {
  390. throw new MigrationException("an error occurred creating the OpenNMS database", e);
  391. } finally {
  392. cleanUpDatabase(c, st, rs);
  393. }
  394. }
  395. /**
  396. * <p>prepareDatabase</p>
  397. *
  398. * @param migration a {@link org.opennms.core.schema.Migration} object.
  399. * @throws org.opennms.core.schema.MigrationException if any.
  400. */
  401. public void prepareDatabase(final Migration migration) throws MigrationException {
  402. validateDatabaseVersion();
  403. createUser(migration);
  404. createSchema(migration);
  405. createDatabase(migration);
  406. createLangPlPgsql();
  407. }
  408. /**
  409. * <p>migrate</p>
  410. *
  411. * @param migration a {@link org.opennms.core.schema.Migration} object.
  412. * @throws org.opennms.core.schema.MigrationException if any.
  413. */
  414. public void migrate(final Migration migration) throws MigrationException {
  415. Connection connection = null;
  416. try {
  417. connection = m_dataSource.getConnection();
  418. final DatabaseConnection dbConnection = new JdbcConnection(connection);
  419. ResourceAccessor accessor = migration.getAccessor();
  420. if (accessor == null) accessor = new SpringResourceAccessor();
  421. final Liquibase liquibase = new Liquibase( migration.getChangeLog(), accessor, dbConnection );
  422. liquibase.setChangeLogParameter("install.database.admin.user", migration.getAdminUser());
  423. liquibase.setChangeLogParameter("install.database.admin.password", migration.getAdminPassword());
  424. liquibase.setChangeLogParameter("install.database.user", migration.getDatabaseUser());
  425. liquibase.getDatabase().setDefaultSchemaName(migration.getSchemaName());
  426. final String contexts = System.getProperty("opennms.contexts", "production");
  427. liquibase.update(contexts);
  428. } catch (final Throwable e) {
  429. throw new MigrationException("unable to migrate the database", e);
  430. } finally {
  431. cleanUpDatabase(connection, null, null);
  432. }
  433. }
  434. public void generateChangelog() {
  435. }
  436. /**
  437. * <p>getMigrationResourceLoader</p>
  438. *
  439. * @param migration a {@link org.opennms.core.schema.Migration} object.
  440. * @return a {@link org.springframework.core.io.ResourceLoader} object.
  441. */
  442. protected ResourceLoader getMigrationResourceLoader(final Migration migration) {
  443. final File changeLog = new File(migration.getChangeLog());
  444. final List<URL> urls = new ArrayList<URL>();
  445. try {
  446. if (changeLog.exists()) {
  447. urls.add(changeLog.getParentFile().toURI().toURL());
  448. }
  449. } catch (final MalformedURLException e) {
  450. LogUtils.infof(this, "unable to figure out URL for " + migration.getChangeLog(), e);
  451. }
  452. final ClassLoader cl = new URLClassLoader(urls.toArray(new URL[0]), this.getClass().getClassLoader());
  453. return new DefaultResourceLoader(cl);
  454. }
  455. private void cleanUpDatabase(final Connection c, final Statement st, final ResultSet rs) {
  456. if (rs != null) {
  457. try {
  458. rs.close();
  459. } catch (final SQLException e) {
  460. LogUtils.warnf(this, "unable to close version-check result set", e);
  461. }
  462. }
  463. if (st != null) {
  464. try {
  465. st.close();
  466. } catch (final SQLException e) {
  467. LogUtils.warnf(this, "unable to close version-check statement", e);
  468. }
  469. }
  470. if (c != null) {
  471. try {
  472. c.close();
  473. } catch (final SQLException e) {
  474. LogUtils.warnf(this, "unable to close version-check connection", e);
  475. }
  476. }
  477. }
  478. }