· 9 years ago · Mar 29, 2017, 02:56 PM
1/*
2* Copyright (c) 2016 Tecom LLC
3* All rights reserved
4*
5* ИÑключительное право (c) 2016 принадлежит ООО Теком
6* Ð’Ñе права защищены
7*/
8package com.tecomgroup.energetics.server.database.migration;
9
10import java.io.IOException;
11import java.io.InputStream;
12import java.io.InputStreamReader;
13import java.net.MalformedURLException;
14import java.sql.Connection;
15import java.sql.DriverManager;
16import java.sql.PreparedStatement;
17import java.sql.ResultSet;
18import java.sql.SQLException;
19import java.sql.Statement;
20import java.util.ArrayList;
21import java.util.Arrays;
22import java.util.Collections;
23import java.util.Date;
24import java.util.HashMap;
25import java.util.List;
26import java.util.Map;
27import java.util.NoSuchElementException;
28import java.util.Properties;
29
30import org.apache.commons.io.IOUtils;
31import org.flywaydb.core.Flyway;
32import org.flywaydb.core.api.FlywayException;
33import org.flywaydb.core.api.MigrationInfo;
34import org.flywaydb.core.api.MigrationInfoService;
35import org.hibernate.tool.hbm2ddl.ImportSqlCommandExtractor;
36import org.slf4j.Logger;
37import org.slf4j.LoggerFactory;
38
39import com.tecomgroup.energetics.server.database.domain.TEM_Agent;
40import com.tecomgroup.energetics.server.database.utils.AbstractPlaceholderResolver;
41import com.tecomgroup.energetics.server.database.utils.AgentProperties;
42import com.tecomgroup.energetics.server.database.utils.Utils;
43import com.tecomgroup.energetics.server.system.AgentClassLoader;
44import com.tecomgroup.energetics.server.system.Constants;
45
46/**
47 * Allows to manage the database, i.e. create schema,
48 * migrate to the latest version, install agent, get
49 * the actual version, validate and so on.
50 *
51 * @author ariskin.a
52 */
53public class Database {
54
55 private static class AgentDirectoryPlaceholderResolver extends AbstractPlaceholderResolver {
56
57 private static String homeDir;
58
59 static {
60 homeDir = System.getenv(Constants.NMS_HOME);
61 }
62
63 @Override
64 protected String resolvePlaceholder(String placeholderName) {
65 if (Constants.NMS_HOME.equals(placeholderName)) {
66 return homeDir;
67 }
68 else {
69 return null;
70 }
71 }
72
73 }
74
75 private static class AgentPlaceholderResolver extends AbstractPlaceholderResolver {
76
77 private final static String AGENT_ID_PLACEHOLDER = "agent_id";
78 private final static String AGENT_NAME_PLACEHOLDER = "agent_name";
79
80 private TEM_Agent agent;
81
82 public AgentPlaceholderResolver(TEM_Agent agent) {
83 this.agent = agent;
84 }
85
86 @Override
87 protected String resolvePlaceholder(String placeholderName) {
88 if (AGENT_ID_PLACEHOLDER.equals(placeholderName)) {
89 return agent.getId().toString();
90 }
91 else if (AGENT_NAME_PLACEHOLDER.equals(placeholderName)) {
92 return agent.getName().toString();
93 }
94 else {
95 return null;
96 }
97 }
98
99 }
100
101 private static final Logger LOGGER = LoggerFactory.getLogger(Database.class);
102
103 private static final String[] CREATE_SCRIPTS = new String[] {
104 "schema_create_pre.sql",
105 "schema_create.sql",
106 "schema_create_post.sql"
107 };
108
109 private static final String[] DROP_SCRIPTS = new String[] {
110 "schema_drop_pre.sql",
111 "schema_drop.sql",
112 "schema_drop_post.sql"
113 };
114
115 private static final String IMPORT_SCRIPT = "import.sql";
116
117 private static final String RESOURCE_FOLDER_SQL_CORE = "sql";
118
119 private static final String CORE_SQL_MIGRATIONS_PREFIX = "migration";
120 private static final String CORE_JAVA_MIGRATIONS_PACKAGE = "com.tecomgroup.energetics.server.database.migration";
121 private static final String COMMON_SQL_MIGRATIONS_FOLDER = "common";
122 private static final String PLACEHOLDER_PREFIX = "$${";
123 //private static final String SCHEMA_VERSION_CORE_TABLE = "schema_version";
124 private static final String SCHEMA_VERSION_AGENT_TABLE_TEMPLATE = "schema_version_%s";
125
126 private final DatabaseConfiguration databaseConfig;
127
128 public Database(DatabaseConfiguration databaseConfig) {
129 this.databaseConfig = databaseConfig;
130 }
131
132 public static void createSchema(DatabaseConfiguration databaseConfig, String[] agentJarPaths) throws Exception {
133 Database database = new Database(databaseConfig);
134 database.createSchema(agentJarPaths);
135 }
136
137 public void createSchema(String[] agentJarPaths) throws Exception {
138 Connection connection = getConnection();
139 try {
140 AgentClassLoader classLoader = getAgentClassLoaderByJars(agentJarPaths);
141 try {
142 createSchema(classLoader, connection);
143 }
144 finally {
145 classLoader.close();
146 }
147 }
148 finally {
149 connection.close();
150 }
151 }
152
153 private void createSchema(AgentClassLoader classLoader, Connection connection) throws Exception {
154 LOGGER.info("Creating database schema...");
155
156 String dialectFolder = Utils.getFolderByDialect(databaseConfig.getDialect());
157 LOGGER.debug("Dialect folder: {}", dialectFolder);
158
159 Class<? extends ImportSqlCommandExtractor> commandExtractorClass = getCommandExtractorClassOrDefault();
160 LOGGER.debug("Command extractor class: {}", commandExtractorClass.getCanonicalName());
161 ImportSqlCommandExtractor commandExtractor = commandExtractorClass.newInstance();
162
163 String encoding = getEncodingOrDefault();
164 LOGGER.debug("Encoding: {}", encoding);
165
166 createSchemaCore(classLoader, connection, dialectFolder, commandExtractor, encoding);
167
168 Flyway flywayCore = createFlywayCore(classLoader);
169 internalBaselineToLatestVersion(flywayCore);
170
171 for (String agentName : classLoader.getAgentNames()) {
172 installAgent(classLoader, connection, agentName, dialectFolder, commandExtractor, encoding);
173 }
174
175 LOGGER.info("Database schema created");
176 }
177
178 private void createSchemaCore(AgentClassLoader classLoader, Connection connection, String dialectFolder, ImportSqlCommandExtractor commandExtractor, String encoding) throws Exception {
179 LOGGER.info("Creating schema for core...");
180 String resourceFolder = RESOURCE_FOLDER_SQL_CORE;
181 internalCreateSchema(classLoader, connection, resourceFolder, dialectFolder, commandExtractor, encoding);
182 }
183
184 private void createSchemaAgent(AgentClassLoader classLoader, Connection connection, TEM_Agent agent, String dialectFolder, ImportSqlCommandExtractor commandExtractor, String encoding) throws Exception {
185 LOGGER.info("Creating schema for agent {}...", agent.getName());
186 String resourceFolder = getAgentSqlResourceFolder(classLoader, agent.getName());
187 internalCreateSchema(classLoader, connection, resourceFolder, dialectFolder, commandExtractor, encoding, new AgentPlaceholderResolver(agent));
188 }
189
190 private void internalCreateSchema(AgentClassLoader classLoader, Connection connection, String resourceFolder, String dialectFolder,
191 ImportSqlCommandExtractor commandExtractor, String encoding) throws Exception {
192 internalCreateSchema(classLoader, connection, resourceFolder, dialectFolder, commandExtractor, encoding, null);
193 }
194
195 private void internalCreateSchema(AgentClassLoader classLoader, Connection connection, String resourceFolder, String dialectFolder,
196 ImportSqlCommandExtractor commandExtractor, String encoding, AbstractPlaceholderResolver placeholderResolver) throws Exception {
197 LOGGER.debug("Creating schema, resource folder: {}, dialect folder: {}", resourceFolder, dialectFolder);
198
199 LOGGER.debug("Loading create commands...");
200 String[] createCommands = loadCommands(classLoader, commandExtractor, resourceFolder, dialectFolder, CREATE_SCRIPTS, encoding);
201 if (placeholderResolver != null) {
202 LOGGER.debug("Using placeholder resolver {}...", placeholderResolver.getClass().getCanonicalName());
203 for (int i = 0; i < createCommands.length; ++i) {
204 String createCommand = createCommands[i];
205 String replacedCreateCommand = placeholderResolver.replacePlaceholders(createCommand);
206 if (!replacedCreateCommand.equals(createCommand)) {
207 LOGGER.trace("Create command: '{}' -> '{}'", createCommand, replacedCreateCommand);
208 }
209 createCommands[i] = replacedCreateCommand;
210 }
211 }
212 if (LOGGER.isTraceEnabled()) {
213 LOGGER.trace("Create commands:");
214 for (String createCommand : createCommands) {
215 LOGGER.trace("{}", createCommand);
216 }
217 }
218
219 LOGGER.info("Executing create commands...");
220 List<Exception> createExceptions = executeCommands(connection, createCommands);
221 if (createExceptions.size() > 0) {
222 LOGGER.error("Exceptions during execution of create scripts:");
223 for (Exception ex : createExceptions) {
224 LOGGER.error("Exception", ex);
225 }
226 throw new Exception("Failed to execute schema create scripts");
227 }
228 LOGGER.info("Create commands executed");
229
230 LOGGER.debug("Loading import script...");
231 String importScript = loadScriptAsString(classLoader, resourceFolder, dialectFolder, IMPORT_SCRIPT, encoding);
232 if (importScript != null) {
233 if (placeholderResolver != null) {
234 LOGGER.debug("Using placeholder resolver {}...", placeholderResolver.getClass().getCanonicalName());
235 String replacedImportScript = placeholderResolver.replacePlaceholders(importScript);
236 if (!replacedImportScript.equals(importScript)) {
237 LOGGER.trace("Import script: '{}' -> '{}", importScript, replacedImportScript);
238 }
239 importScript = replacedImportScript;
240 }
241 LOGGER.trace("Import script: {}", importScript);
242
243 LOGGER.info("Executing import script...");
244 try {
245 if (!importScript.trim().isEmpty()) {
246 executeScript(connection, importScript);
247 }
248 else {
249 LOGGER.debug("Import script is empty");
250 }
251 }
252 catch (SQLException ex) {
253 throw new Exception("Failed to execute import script", ex);
254 }
255 LOGGER.info("Import script executed");
256 }
257 }
258
259 public static void dropSchema(DatabaseConfiguration databaseConfig) throws Exception {
260 Database database = new Database(databaseConfig);
261 database.dropSchema();
262 }
263
264 public void dropSchema() throws Exception {
265 Connection connection = getConnection();
266 try {
267 AgentClassLoader classLoader = getAgentClassLoaderByInstalledAgents(connection);
268 try {
269 dropSchema(classLoader, connection);
270 }
271 finally {
272 classLoader.close();
273 }
274 }
275 finally {
276 connection.close();
277 }
278 }
279
280 private void dropSchema(AgentClassLoader classLoader, Connection connection) throws Exception {
281 LOGGER.info("Dropping database schema...");
282
283 String dialectFolder = Utils.getFolderByDialect(databaseConfig.getDialect());
284 LOGGER.debug("Dialect folder: {}", dialectFolder);
285
286 Class<? extends ImportSqlCommandExtractor> commandExtractorClass = getCommandExtractorClassOrDefault();
287 LOGGER.debug("Command extractor class: {}", commandExtractorClass.getCanonicalName());
288 ImportSqlCommandExtractor commandExtractor = commandExtractorClass.newInstance();
289
290 String encoding = getEncodingOrDefault();
291 LOGGER.debug("Encoding: {}", encoding);
292
293 List<TEM_Agent> installedAgents = getInstalledAgents(connection);
294 for (TEM_Agent installedAgent : installedAgents) {
295 uninstallAgent(classLoader, connection, installedAgent, dialectFolder, commandExtractor, encoding);
296 }
297
298 dropSchemaCore(classLoader, connection, dialectFolder, commandExtractor, encoding);
299
300 LOGGER.info("Database schema dropped");
301 }
302
303 private void dropSchemaCore(AgentClassLoader classLoader, Connection connection, String dialectFolder, ImportSqlCommandExtractor commandExtractor, String encoding) throws Exception {
304 LOGGER.info("Dropping schema for core...");
305 String resourceFolder = RESOURCE_FOLDER_SQL_CORE;
306 internalDropSchema(classLoader, connection, resourceFolder, dialectFolder, commandExtractor, encoding);
307 }
308
309 private void dropSchemaAgent(AgentClassLoader classLoader, Connection connection, final TEM_Agent agent, String dialectFolder, ImportSqlCommandExtractor commandExtractor, String encoding) throws Exception {
310 LOGGER.info("Dropping schema for agent {}...", agent.getName());
311 String resourceFolder = getAgentSqlResourceFolder(classLoader, agent.getName());
312 internalDropSchema(classLoader, connection, resourceFolder, dialectFolder, commandExtractor, encoding, new AgentPlaceholderResolver(agent));
313 }
314
315 private void internalDropSchema(AgentClassLoader classLoader, Connection connection, String resourceFolder, String dialectFolder,
316 ImportSqlCommandExtractor commandExtractor, String encoding) throws Exception {
317 internalDropSchema(classLoader, connection, resourceFolder, dialectFolder, commandExtractor, encoding, null);
318 }
319
320 private void internalDropSchema(AgentClassLoader classLoader, Connection connection, String resourceFolder, String dialectFolder,
321 ImportSqlCommandExtractor commandExtractor, String encoding, AbstractPlaceholderResolver placeholderResolver) throws Exception {
322 LOGGER.debug("Dropping schema, resource folder: {}, dialect folder: {}", resourceFolder, dialectFolder);
323
324 LOGGER.debug("Loading drop commands...");
325 String[] dropCommands = loadCommands(classLoader, commandExtractor, resourceFolder, dialectFolder, DROP_SCRIPTS, encoding);
326 if (placeholderResolver != null) {
327 LOGGER.debug("Using placeholder resolver {}...", placeholderResolver.getClass().getCanonicalName());
328 for (int i = 0; i < dropCommands.length; ++i) {
329 String dropCommand = dropCommands[i];
330 String replacedDropCommand = placeholderResolver.replacePlaceholders(dropCommand);
331 if (!replacedDropCommand.equals(dropCommand)) {
332 LOGGER.trace("Create command: '{}' -> '{}'", dropCommand, replacedDropCommand);
333 }
334 dropCommands[i] = replacedDropCommand;
335 }
336 }
337 if (LOGGER.isTraceEnabled()) {
338 LOGGER.trace("Drop commands:");
339 for (String dropCommand : dropCommands) {
340 LOGGER.trace("{}", dropCommand);
341 }
342 }
343
344 LOGGER.info("Executing drop commands...");
345 List<Exception> dropExceptions = executeCommands(connection, dropCommands);
346 LOGGER.info("Drop commands executed");
347 if (LOGGER.isDebugEnabled()) {
348 for (Exception dropException : dropExceptions) {
349 LOGGER.debug("Drop exception", dropException);
350 }
351 }
352
353 /*
354 try {
355 dropCoreSchemaVersionTable(connection);
356 }
357 catch (SQLException ex) {
358 LOGGER.debug("Failed to drop core schema version table", ex);
359 }
360 */
361 }
362
363 public static void upgradeSchema(DatabaseConfiguration databaseConfig, String[] agentJarPaths) throws Exception {
364 Database database = new Database(databaseConfig);
365 database.upgradeSchema(agentJarPaths);
366 }
367
368 public void upgradeSchema(String[] agentJarPaths) throws Exception {
369 Connection connection = getConnection();
370 try {
371 AgentClassLoader classLoader = getAgentClassLoaderByJars(agentJarPaths);
372 try {
373 upgradeSchema(classLoader, connection);
374 }
375 finally {
376 classLoader.close();
377 }
378 }
379 finally {
380 connection.close();
381 }
382 }
383
384 private void upgradeSchema(AgentClassLoader classLoader, Connection connection) throws Exception {
385 LOGGER.info("Upgrading database schema...");
386
387 String dialectFolder = Utils.getFolderByDialect(databaseConfig.getDialect());
388 LOGGER.debug("Dialect folder: {}", dialectFolder);
389
390 Class<? extends ImportSqlCommandExtractor> commandExtractorClass = getCommandExtractorClassOrDefault();
391 LOGGER.debug("Command extractor class: {}", commandExtractorClass.getCanonicalName());
392 ImportSqlCommandExtractor commandExtractor = commandExtractorClass.newInstance();
393
394 String encoding = getEncodingOrDefault();
395 LOGGER.debug("Encoding: {}", encoding);
396
397 Flyway flywayCore = createFlywayCore(classLoader);
398 migrateSchema(flywayCore);
399
400 for (String agentName : classLoader.getAgentNames()) {
401 installOrUpgradeAgent(classLoader, connection, agentName, dialectFolder, commandExtractor, encoding);
402 }
403
404 LOGGER.info("Database schema upgraded");
405 }
406
407 public static void installAgent(DatabaseConfiguration databaseConfig, String agentJarPath) throws Exception {
408 Database database = new Database(databaseConfig);
409 database.installAgent(agentJarPath);
410 }
411
412 public void installAgent(String agentJarPath) throws Exception {
413 Connection connection = getConnection();
414 try {
415 AgentClassLoader classLoader = getAgentClassLoaderByJar(agentJarPath);
416 try {
417 String agentName;
418 try {
419 agentName = classLoader.getAgentNames().iterator().next();
420 }
421 catch (NoSuchElementException ex) {
422 String errorMsg = String.format("Failed to get agent name from JAR {}. Probably, you specified not an agent JAR.", agentJarPath);
423 LOGGER.error("{}", errorMsg);
424 throw new Exception(errorMsg);
425 }
426
427 installAgent(classLoader, connection, agentName);
428 }
429 finally {
430 classLoader.close();
431 }
432 }
433 finally {
434 connection.close();
435 }
436 }
437
438 private void installAgent(AgentClassLoader classLoader, Connection connection, String agentName) throws Exception {
439 LOGGER.info("Installing agent {}...", agentName);
440
441 String dialectFolder = Utils.getFolderByDialect(databaseConfig.getDialect());
442 LOGGER.debug("Dialect folder: {}", dialectFolder);
443
444 Class<? extends ImportSqlCommandExtractor> commandExtractorClass = getCommandExtractorClassOrDefault();
445 LOGGER.debug("Command extractor class: {}", commandExtractorClass.getCanonicalName());
446 ImportSqlCommandExtractor commandExtractor = commandExtractorClass.newInstance();
447
448 String encoding = getEncodingOrDefault();
449 LOGGER.debug("Encoding: {}", encoding);
450
451 installAgent(classLoader, connection, agentName, dialectFolder, commandExtractor, encoding);
452
453 LOGGER.info("Agent {} installed", agentName);
454 }
455
456 private void installAgent(AgentClassLoader classLoader, Connection connection, String agentName, String dialectFolder, ImportSqlCommandExtractor commandExtractor, String encoding) throws Exception {
457 LOGGER.debug("Installing agent {}...", agentName);
458
459 if (!isAgentsTableExists(connection)) {
460 throw new Exception("Agents table not exists");
461 }
462
463 TEM_Agent agent = getInstalledAgentByName(connection, agentName);
464 if (agent != null) {
465 throw new Exception(String.format("Agent %s is already installed", agentName));
466 }
467
468 agent = insertAgentIntoAgentsTable(classLoader, connection, agentName, dialectFolder);
469
470 createSchemaAgent(classLoader, connection, agent, dialectFolder, commandExtractor, encoding);
471
472 createAgentSchemaVersionTable(classLoader, connection, agent.getName());
473 }
474
475 private TEM_Agent insertAgentIntoAgentsTable(AgentClassLoader classLoader, Connection connection, String agentName, String dialectFolder) throws Exception {
476 LOGGER.debug("Inserting agent {} into agents table...", agentName);
477
478 Properties agentProperties = getAgentProperties(classLoader, agentName);
479 String agentVersion = agentProperties.getProperty(AgentProperties.AGENT_VERSION_PROPERTY);
480 LOGGER.debug("Agent version: {}", agentVersion);
481 String agentDescription = agentProperties.getProperty(AgentProperties.AGENT_DESCRIPTION_PROPERTY);
482 LOGGER.debug("Agent version: {}", agentVersion);
483 Class<?> deviceClass = classLoader.getDeviceClassByAgentName(agentName);
484 LOGGER.debug("Device class: {}", deviceClass.getCanonicalName());
485 String agentDirectory = String.format("${NMS_HOME}/agents/%s", agentName); // assume this directory name is used for an agent
486 LOGGER.debug("Agent directory: {}", agentDirectory);
487 String agentDisplayName = agentProperties.getProperty(AgentProperties.AGENT_DISPLAY_NAME_PROPERTY);
488 LOGGER.debug("Agent display name: {}", agentDisplayName);
489 Class<?> agentClass = classLoader.getAgentClassByAgentName(agentName);
490 LOGGER.debug("Agent class: {}", agentClass.getCanonicalName());
491
492 String insertQuery = getInsertAgentQuery(dialectFolder);
493 LOGGER.debug("Insert query: {}", insertQuery);
494 PreparedStatement insertAgentStatement = connection.prepareStatement(insertQuery, Statement.RETURN_GENERATED_KEYS);
495 insertAgentStatement.setString(1, agentVersion);
496 insertAgentStatement.setString(2, agentDescription);
497 insertAgentStatement.setString(3, deviceClass.getCanonicalName());
498 insertAgentStatement.setString(4, agentDirectory);
499 insertAgentStatement.setString(5, agentDisplayName);
500 insertAgentStatement.setDate(6, new java.sql.Date(new Date().getTime()));
501 insertAgentStatement.setString(7, agentName);
502 insertAgentStatement.setString(8, agentClass.getCanonicalName());
503
504 int insertedRows = insertAgentStatement.executeUpdate();
505 LOGGER.debug("Inserted {} rows", insertedRows);
506 if (insertedRows <= 0) {
507 throw new Exception("Failed to insert data into agents table");
508 }
509
510 ResultSet generatedKeys = insertAgentStatement.getGeneratedKeys();
511 if (generatedKeys.next()) {
512 Long insertedId = generatedKeys.getLong(1);
513 LOGGER.debug("Inserted ID: {}", insertedId);
514
515 TEM_Agent insertedAgent = getInstalledAgentById(connection, insertedId);
516 return insertedAgent;
517 }
518 else {
519 // For unknown reason, we could't obtain ID of the inserted record.
520 // So, we try to get agent by its name. This won't work if we implement
521 // feature with different agent versions.
522 // For now, just warn.
523 LOGGER.warn("Failed to get inserted ID.");
524
525 TEM_Agent insertedAgent = getInstalledAgentByName(connection, agentName);
526 return insertedAgent;
527 }
528 }
529
530 private void createAgentSchemaVersionTable(AgentClassLoader classLoader, Connection connection, String agentName) throws Exception {
531 LOGGER.info("Creating schema version table for agent {}...", agentName);
532 Flyway flywayAgent = createFlywayAgent(classLoader, agentName);
533 internalBaselineToLatestVersion(flywayAgent);
534 }
535
536 public static void uninstallAgent(DatabaseConfiguration databaseConfig, Long agentId) throws Exception {
537 Database database = new Database(databaseConfig);
538 database.uninstallAgent(agentId);
539 }
540
541 public void uninstallAgent(Long agentId) throws Exception {
542 Connection connection = getConnection();
543 try {
544 if (!isAgentsTableExists(connection)) {
545 throw new Exception("Agents table not exists");
546 }
547
548 TEM_Agent agent = getInstalledAgentById(connection, agentId);
549 if (agent == null) {
550 String errorMsg = String.format("Failed to find agent with ID %d", agentId);
551 LOGGER.error(errorMsg);
552 throw new Exception(errorMsg);
553 }
554
555 AgentClassLoader classLoader = getAgentClassLoaderByDirectory(agent.getDirectory());
556 try {
557 uninstallAgent(classLoader, connection, agent);
558 }
559 finally {
560 classLoader.close();
561 }
562 }
563 finally {
564 connection.close();
565 }
566 }
567
568 public static void uninstallAgent(DatabaseConfiguration databaseConfig, String agentName) throws Exception {
569 Database database = new Database(databaseConfig);
570 database.uninstallAgent(agentName);
571 }
572
573 public void uninstallAgent(String agentName) throws Exception {
574 Connection connection = getConnection();
575 try {
576 if (!isAgentsTableExists(connection)) {
577 throw new Exception("Agents table not exists");
578 }
579
580 TEM_Agent agent = getInstalledAgentByName(connection, agentName);
581 if (agent == null) {
582 String errorMsg = String.format("Failed to find agent with name %s", agentName);
583 LOGGER.error(errorMsg);
584 throw new Exception(errorMsg);
585 }
586
587 AgentClassLoader classLoader = getAgentClassLoaderByDirectory(agent.getDirectory());
588 try {
589 uninstallAgent(classLoader, connection, agent);
590 }
591 finally {
592 classLoader.close();
593 }
594 }
595 finally {
596 connection.close();
597 }
598 }
599
600 private void uninstallAgent(AgentClassLoader classLoader, Connection connection, TEM_Agent agent) throws Exception {
601 LOGGER.info("Uninstalling agent {} (ID {})...", agent.getName(), agent.getId());
602
603 String dialectFolder = Utils.getFolderByDialect(databaseConfig.getDialect());
604 LOGGER.debug("Dialect folder: {}", dialectFolder);
605
606 Class<? extends ImportSqlCommandExtractor> commandExtractorClass = getCommandExtractorClassOrDefault();
607 LOGGER.debug("Command extractor class: {}", commandExtractorClass.getCanonicalName());
608 ImportSqlCommandExtractor commandExtractor = commandExtractorClass.newInstance();
609
610 String encoding = getEncodingOrDefault();
611 LOGGER.debug("Encoding: {}", encoding);
612
613 uninstallAgent(classLoader, connection, agent, dialectFolder, commandExtractor, encoding);
614
615 LOGGER.debug("Agent {} (ID {}) uninstalled", agent.getName(), agent.getId());
616 }
617
618 private void uninstallAgent(AgentClassLoader classLoader, Connection connection, TEM_Agent agent, String dialectFolder, ImportSqlCommandExtractor commandExtractor, String encoding) throws Exception {
619 LOGGER.debug("Uninstalling agent {} (ID {})...", agent.getName(), agent.getId());
620
621 try {
622 dropAgentSchemaVersionTable(connection, agent.getName());
623 }
624 catch (SQLException ex) {
625 LOGGER.error("Failed to drop schema version table for agent {}", agent.getName(), ex);
626 }
627
628 dropSchemaAgent(classLoader, connection, agent, dialectFolder, commandExtractor, encoding);
629
630 if (isAgentsTableExists(connection)) {
631 try {
632 deleteAgentFromAgentsTable(connection, agent.getId());
633 }
634 catch (SQLException ex) {
635 LOGGER.error("Failed to delete agent from agents table", ex);
636 }
637 }
638 }
639
640 /*
641 private void dropCoreSchemaVersionTable(Connection connection) throws SQLException {
642 LOGGER.debug("Dropping schema version table for core...");
643 Statement dropCoreSchemaVersionTableStatement = connection.createStatement();
644 String queryDropCoreSchemaVersionTable = String.format("DROP TABLE \"%s\"", SCHEMA_VERSION_CORE_TABLE);
645 LOGGER.debug("Drop table SQL statement: {}", queryDropCoreSchemaVersionTable);
646 dropCoreSchemaVersionTableStatement.execute(queryDropCoreSchemaVersionTable);
647 }
648 */
649
650 private void dropAgentSchemaVersionTable(Connection connection, String agentName) throws SQLException {
651 LOGGER.debug("Dropping schema version table for agent {}...", agentName);
652 String agentSchemaVersionTableName = getAgentSchemaVersionTableName(agentName);
653 LOGGER.debug("Schema version table name: {}", agentSchemaVersionTableName);
654 Statement dropAgentSchemaVersionTableStatement = connection.createStatement();
655 String queryDropAgentSchemaVersionTable = String.format("DROP TABLE \"%s\"", agentSchemaVersionTableName);
656 LOGGER.debug("Drop table SQL statement: {}", queryDropAgentSchemaVersionTable);
657 dropAgentSchemaVersionTableStatement.execute(queryDropAgentSchemaVersionTable);
658 }
659
660 private static final String queryDeleteAgent = "DELETE FROM tem_agent WHERE id=?";
661
662 private void deleteAgentFromAgentsTable(Connection connection, Long agentId) throws SQLException {
663 LOGGER.debug("Delete agent with ID {} from agents table...", agentId);
664 PreparedStatement deleteAgentStatement = connection.prepareStatement(queryDeleteAgent);
665 deleteAgentStatement.setLong(1, agentId);
666 deleteAgentStatement.execute();
667 }
668
669 public static void upgradeAgent(DatabaseConfiguration databaseConfig, String agentJarPath) throws Exception {
670 Database database = new Database(databaseConfig);
671 database.upgradeAgent(agentJarPath);
672 }
673
674 public void upgradeAgent(String agentJarPath) throws Exception {
675 Connection connection = getConnection();
676 try {
677 AgentClassLoader classLoader = getAgentClassLoaderByJar(agentJarPath);
678 try {
679 String agentName;
680 try {
681 agentName = classLoader.getAgentNames().iterator().next();
682 }
683 catch (NoSuchElementException ex) {
684 String errorMsg = String.format("Failed to get agent name from JAR {}. Probably, you specified not an agent JAR.", agentJarPath);
685 LOGGER.error("{}", errorMsg);
686 throw new Exception(errorMsg);
687 }
688
689 upgradeAgent(classLoader, connection, agentName);
690 }
691 finally {
692 classLoader.close();
693 }
694 }
695 finally {
696 connection.close();
697 }
698 }
699
700 private void upgradeAgent(AgentClassLoader classLoader, Connection connection, String agentName) throws Exception {
701 LOGGER.debug("Upgrading agent {}...", agentName);
702
703 if (!isAgentsTableExists(connection)) {
704 throw new Exception("Agents table not exists");
705 }
706
707 TEM_Agent agent = getInstalledAgentByName(connection, agentName);
708 if (agent == null) {
709 throw new Exception(String.format("Agent %s is not installed", agentName));
710 }
711
712 internalUpgradeAgent(classLoader, connection, agentName);
713 }
714
715 private void internalUpgradeAgent(AgentClassLoader classLoader, Connection connection, String agentName) throws Exception {
716 updateAgentInAgentsTable(classLoader, connection, agentName);
717
718 upgradeAgentSchema(classLoader, agentName);
719
720 // assume that no changed required in schema version table for agent
721 }
722
723 private void upgradeAgentSchema(AgentClassLoader classLoader, String agentName) throws Exception {
724 LOGGER.debug("Upgrading schema for agent {}...", agentName);
725
726 Flyway flywayAgent = createFlywayAgent(classLoader, agentName);
727 migrateSchema(flywayAgent);
728 }
729
730 private static final String queryUpdateAgentByName = "UPDATE tem_agent " +
731 "SET agentversion=?, description=?, deviceclass=?, displayname=?, agentclass=? WHERE name=?";
732
733 private void updateAgentInAgentsTable(AgentClassLoader classLoader, Connection connection, String agentName) throws Exception {
734 LOGGER.debug("Updating agent {} in agents table...", agentName);
735
736 Properties agentProperties = getAgentProperties(classLoader, agentName);
737 String agentVersion = agentProperties.getProperty(AgentProperties.AGENT_VERSION_PROPERTY);
738 LOGGER.debug("Agent version: {}", agentVersion);
739 String agentDescription = agentProperties.getProperty(AgentProperties.AGENT_DESCRIPTION_PROPERTY);
740 LOGGER.debug("Agent version: {}", agentVersion);
741 Class<?> deviceClass = classLoader.getDeviceClassByAgentName(agentName);
742 LOGGER.debug("Device class: {}", deviceClass.getCanonicalName());
743 String agentDisplayName = agentProperties.getProperty(AgentProperties.AGENT_DISPLAY_NAME_PROPERTY);
744 LOGGER.debug("Agent display name: {}", agentDisplayName);
745 Class<?> agentClass = classLoader.getAgentClassByAgentName(agentName);
746 LOGGER.debug("Agent class: {}", agentClass.getCanonicalName());
747
748 PreparedStatement updateAgentStatement = connection.prepareStatement(queryUpdateAgentByName);
749 updateAgentStatement.setString(1, agentVersion);
750 updateAgentStatement.setString(2, agentDescription);
751 updateAgentStatement.setString(3, deviceClass.getCanonicalName());
752 updateAgentStatement.setString(4, agentDisplayName);
753 updateAgentStatement.setString(5, agentClass.getCanonicalName());
754 updateAgentStatement.setString(6, agentName);
755
756 int updatedRows = updateAgentStatement.executeUpdate();
757 LOGGER.debug("Updated {} rows", updatedRows);
758 if (updatedRows <= 0) {
759 throw new Exception("Failed to update data in agents table");
760 }
761 else if (updatedRows > 1) {
762 LOGGER.warn("Updated more than one ({}) rows in agents table", updatedRows);
763 }
764 }
765
766 public static void installOrUpgradeAgent(DatabaseConfiguration databaseConfig, String agentJarPath) throws Exception {
767 Database database = new Database(databaseConfig);
768 database.installOrUpgradeAgent(agentJarPath);
769 }
770
771 public void installOrUpgradeAgent(String agentJarPath) throws Exception {
772 Connection connection = getConnection();
773 try {
774 AgentClassLoader classLoader = getAgentClassLoaderByJar(agentJarPath);
775 try {
776 String agentName;
777 try {
778 agentName = classLoader.getAgentNames().iterator().next();
779 }
780 catch (NoSuchElementException ex) {
781 String errorMsg = String.format("Failed to get agent name from JAR {}. Probably, you specified not an agent JAR.", agentJarPath);
782 LOGGER.error("{}", errorMsg);
783 throw new Exception(errorMsg);
784 }
785
786 installOrUpgradeAgent(classLoader, connection, agentName);
787 }
788 finally {
789 classLoader.close();
790 }
791 }
792 finally {
793 connection.close();
794 }
795 }
796
797 private void installOrUpgradeAgent(AgentClassLoader classLoader, Connection connection, String agentName) throws Exception {
798 LOGGER.debug("Installing/upgrading agent {}...", agentName);
799
800 String dialectFolder = Utils.getFolderByDialect(databaseConfig.getDialect());
801 LOGGER.debug("Dialect folder: {}", dialectFolder);
802
803 Class<? extends ImportSqlCommandExtractor> commandExtractorClass = getCommandExtractorClassOrDefault();
804 LOGGER.debug("Command extractor class: {}", commandExtractorClass.getCanonicalName());
805 ImportSqlCommandExtractor commandExtractor = commandExtractorClass.newInstance();
806
807 String encoding = getEncodingOrDefault();
808 LOGGER.debug("Encoding: {}", encoding);
809
810 installOrUpgradeAgent(classLoader, connection, agentName, dialectFolder, commandExtractor, encoding);
811
812 LOGGER.debug("Agent {} installed/upgraded", agentName);
813 }
814
815 private void installOrUpgradeAgent(AgentClassLoader classLoader, Connection connection, String agentName, String dialectFolder,
816 ImportSqlCommandExtractor commandExtractor, String encoding) throws Exception {
817 if (!isAgentsTableExists(connection)) {
818 throw new Exception("Agents table not exists");
819 }
820
821 TEM_Agent installedAgent = getInstalledAgentByName(connection, agentName);
822 if (installedAgent != null) {
823 LOGGER.debug("Agent {} is already installed, upgrading it...");
824 internalUpgradeAgent(classLoader, connection, agentName);
825 }
826 else {
827 LOGGER.debug("Agent {} is not installed, installing it...");
828 installAgent(classLoader, connection, agentName, dialectFolder, commandExtractor, encoding);
829 }
830 }
831
832 public static DatabaseVersion getVersion(DatabaseConfiguration databaseConfig) throws Exception {
833 Database database = new Database(databaseConfig);
834 DatabaseVersion version = database.getVersion();
835 return version;
836 }
837
838 /**
839 * Get the database version.
840 * Retrieves the actual and the latest versions for core and agents.
841 *
842 * @return version
843 * @throws MigrationException
844 */
845 public DatabaseVersion getVersion() throws Exception {
846 Connection connection = getConnection();
847 try {
848 AgentClassLoader classLoader = getAgentClassLoaderByInstalledAgents(connection);
849 try {
850 DatabaseVersion version = getVersion(classLoader);
851 return version;
852 }
853 finally {
854 classLoader.close();
855 }
856 }
857 finally {
858 connection.close();
859 }
860 }
861
862 public DatabaseVersion getVersion(AgentClassLoader classLoader) throws Exception {
863 LOGGER.info("Getting database version...");
864
865 Flyway flywayCore = createFlywayCore(classLoader);
866 Map<String, Flyway> flywayAgents = createFlywaysAgents(classLoader);
867
868 String coreActualVersion = getMigrationsVersion(flywayCore.info().applied());
869 String coreLatestVersion;
870
871 MigrationInfo[] corePendingMigrations = flywayCore.info().pending();
872 if (corePendingMigrations == null || corePendingMigrations.length == 0) {
873 coreLatestVersion = coreActualVersion;
874 }
875 else {
876 coreLatestVersion = getMigrationsVersion(corePendingMigrations);
877 }
878 ComponentVersions coreVersions = new ComponentVersions(coreActualVersion, coreLatestVersion);
879 LOGGER.debug("Core actual version: {}", coreActualVersion != null ? coreActualVersion.toString() : null);
880 LOGGER.debug("Core latest version: {}", coreLatestVersion != null ? coreLatestVersion.toString() : null);
881
882 Map<String, ComponentVersions> allAgentVersions = new HashMap<>();
883 for (Map.Entry<String, Flyway> flywayAgentEntry : flywayAgents.entrySet()) {
884 String agentName = flywayAgentEntry.getKey();
885 Flyway flywayAgent = flywayAgentEntry.getValue();
886
887 String agentActualVersion = getMigrationsVersion(flywayAgent.info().applied());
888 String agentLatestVersion;
889
890 MigrationInfo[] agentPendingMigrations = flywayAgent.info().pending();
891 if (agentPendingMigrations == null || agentPendingMigrations.length == 0) {
892 agentLatestVersion = agentActualVersion;
893 }
894 else {
895 agentLatestVersion = getMigrationsVersion(agentPendingMigrations);
896 }
897 ComponentVersions agentVersions = new ComponentVersions(agentActualVersion, agentLatestVersion);
898 LOGGER.debug("Agent {} actual version: {}", agentName, agentActualVersion != null ? agentActualVersion.toString() : null);
899 LOGGER.debug("Agent {} latest version: {}", agentName, agentLatestVersion != null ? agentLatestVersion.toString() : null);
900
901 allAgentVersions.put(agentName, agentVersions);
902 }
903
904 return new DatabaseVersion(coreVersions, allAgentVersions);
905 }
906
907 public static DatabaseMigrations getMigrations(DatabaseConfiguration databaseConfig) throws Exception {
908 Database database = new Database(databaseConfig);
909 DatabaseMigrations migrations = database.getMigrations();
910 return migrations;
911 }
912
913 /**
914 * Return all migrations.
915 *
916 * @return migrations
917 * @throws MigrationException
918 */
919 public DatabaseMigrations getMigrations() throws Exception {
920 Connection connection = getConnection();
921 try {
922 AgentClassLoader classLoader = getAgentClassLoaderByInstalledAgents(connection);
923 try {
924 DatabaseMigrations migrations = getMigrations(classLoader);
925 return migrations;
926 }
927 finally {
928 classLoader.close();
929 }
930 }
931 finally {
932 connection.close();
933 }
934 }
935
936 public DatabaseMigrations getMigrations(AgentClassLoader classLoader) throws Exception {
937 LOGGER.info("Getting database migrations...");
938
939 Flyway flywayCore = createFlywayCore(classLoader);
940 Map<String, Flyway> flywayAgents = createFlywaysAgents(classLoader);
941
942 List<Migration> coreMigrations = getMigrations(flywayCore);
943 if (LOGGER.isDebugEnabled()) {
944 LOGGER.debug("Core migrations:");
945 for (Migration coreMigration : coreMigrations) {
946 LOGGER.debug("{}", coreMigration.toString());
947 }
948 }
949
950 Map<String, List<Migration>> allAgentMigrations = new HashMap<>();
951 for (Map.Entry<String, Flyway> flywayAgentEntry : flywayAgents.entrySet()) {
952 String agentName = flywayAgentEntry.getKey();
953 Flyway flywayAgent = flywayAgentEntry.getValue();
954
955 List<Migration> agentMigrations = getMigrations(flywayAgent);
956 if (LOGGER.isDebugEnabled()) {
957 LOGGER.debug("Agent {} migrations:", agentName);
958 for (Migration agentMigration : agentMigrations) {
959 LOGGER.debug("{}", agentMigration.toString());
960 }
961 }
962
963 allAgentMigrations.put(agentName, agentMigrations);
964 }
965
966 return new DatabaseMigrations(coreMigrations, allAgentMigrations);
967 }
968
969 public static void clean(DatabaseConfiguration databaseConfig) throws Exception {
970 Database database = new Database(databaseConfig);
971 database.clean();
972 }
973
974 /**
975 * Cleans the database.
976 * !!! REMOVES ALL THE TABLES FROM THE DATABASE !!!
977 *
978 * @throws MigrationException
979 */
980 public void clean() throws Exception {
981 Connection connection = getConnection();
982 try {
983 AgentClassLoader classLoader = getAgentClassLoaderByInstalledAgents(connection);
984 try {
985 clean(classLoader);
986 }
987 finally {
988 classLoader.close();
989 }
990 }
991 finally {
992 connection.close();
993 }
994 }
995
996 public void clean(AgentClassLoader classLoader) throws Exception {
997 LOGGER.info("Cleaning the database...");
998
999 Flyway flywayCore = createFlywayCore(classLoader);
1000 Map<String, Flyway> flywayAgents = createFlywaysAgents(classLoader);
1001
1002 try {
1003 for (Map.Entry<String, Flyway> flywayAgentEntry : flywayAgents.entrySet()) {
1004 String agentName = flywayAgentEntry.getKey();
1005 Flyway flywayAgent = flywayAgentEntry.getValue();
1006
1007 LOGGER.info("Cleaning database for agent {}...", agentName);
1008 flywayAgent.clean();
1009 }
1010
1011 LOGGER.info("Cleaning database for core...");
1012 flywayCore.clean();
1013
1014 LOGGER.info("Database successfully cleaned");
1015 }
1016 catch (FlywayException ex) {
1017 LOGGER.error("Failed to clean database", ex);
1018 throw new MigrationException("Failed to clean database", ex);
1019 }
1020 }
1021
1022 public static void baseline(DatabaseConfiguration databaseConfig, String baselineVersion, String baselineDescription) throws Exception {
1023 Database database = new Database(databaseConfig);
1024 database.baseline(baselineVersion, baselineDescription);
1025 }
1026
1027 /**
1028 * Baseline the database.
1029 * This method creates initial migration based on the current database tables.
1030 *
1031 * @param baselineVersion initial database version
1032 * @param baselineDescription initial description
1033 * @throws MigrationException
1034 */
1035 public void baseline(String baselineVersion, String baselineDescription) throws Exception {
1036 Connection connection = getConnection();
1037 try {
1038 AgentClassLoader classLoader = getAgentClassLoaderByInstalledAgents(connection);
1039 try {
1040 baseline(classLoader, baselineVersion, baselineDescription);
1041 }
1042 finally {
1043 classLoader.close();
1044 }
1045 }
1046 finally {
1047 connection.close();
1048 }
1049 }
1050
1051 public void baseline(AgentClassLoader classLoader, String baselineVersion, String baselineDescription) throws Exception {
1052 LOGGER.info("Baselining the database to version {}...", baselineVersion);
1053
1054 Flyway flywayCore = createFlywayCore(classLoader);
1055 Map<String, Flyway> flywayAgents = createFlywaysAgents(classLoader);
1056
1057 try {
1058 LOGGER.info("Baselining core to version {}...", baselineVersion);
1059 internalBaseline(flywayCore, baselineVersion, baselineDescription);
1060
1061 for (Map.Entry<String, Flyway> flywayAgentEntry : flywayAgents.entrySet()) {
1062 String agentName = flywayAgentEntry.getKey();
1063 Flyway flywayAgent = flywayAgentEntry.getValue();
1064
1065 LOGGER.info("Baselining agent {} to version {}...", agentName, baselineVersion);
1066 internalBaseline(flywayAgent, baselineVersion, baselineDescription);
1067 }
1068
1069 LOGGER.info("Database successfully baselined");
1070 }
1071 catch (FlywayException ex) {
1072 LOGGER.error("Failed to baseline database", ex);
1073 throw new MigrationException("Failed to baseline database", ex);
1074 }
1075 }
1076
1077 public static void baselineToLatestVersion(DatabaseConfiguration databaseConfig) throws Exception {
1078 Database database = new Database(databaseConfig);
1079 database.baselineToLatestVersion();
1080 }
1081
1082 public void baselineToLatestVersion() throws Exception {
1083 Connection connection = getConnection();
1084 try {
1085 AgentClassLoader classLoader = getAgentClassLoaderByInstalledAgents(connection);
1086 try {
1087 baselineToLatestVersion(classLoader);
1088 }
1089 finally {
1090 classLoader.close();
1091 }
1092 }
1093 finally {
1094 connection.close();
1095 }
1096 }
1097
1098 /**
1099 * Baseline the database to the latest available version.
1100 *
1101 * @throws MigrationException
1102 */
1103 public void baselineToLatestVersion(AgentClassLoader classLoader) throws Exception {
1104 LOGGER.info("Baselining the database to the latest version...");
1105
1106 Flyway flywayCore = createFlywayCore(classLoader);
1107 Map<String, Flyway> flywayAgents = createFlywaysAgents(classLoader);
1108
1109 try {
1110 LOGGER.info("Baselining core to latest version...");
1111 internalBaselineToLatestVersion(flywayCore);
1112
1113 for (Map.Entry<String, Flyway> flywayAgentEntry : flywayAgents.entrySet()) {
1114 String agentName = flywayAgentEntry.getKey();
1115 Flyway flywayAgent = flywayAgentEntry.getValue();
1116
1117 LOGGER.info("Baselining agent {} to latest version...", agentName);
1118 internalBaselineToLatestVersion(flywayAgent);
1119 }
1120
1121 LOGGER.info("Database successfully baselined to the latest version");
1122 }
1123 catch (FlywayException ex) {
1124 LOGGER.error("Failed to baseline database", ex);
1125 throw new MigrationException("Failed to baseline database", ex);
1126 }
1127 }
1128
1129 private void internalBaseline(Flyway flyway, String baselineVersion, String baselineDescription) {
1130 LOGGER.debug("Baselining to version {}...", baselineVersion);
1131 flyway.setBaselineVersionAsString(baselineVersion);
1132 flyway.setBaselineDescription(baselineDescription);
1133 flyway.baseline();
1134 }
1135
1136 private void internalBaselineToLatestVersion(Flyway flyway) {
1137 String latestVersion = getMigrationsVersion(flyway.info().pending());
1138 LOGGER.debug("Baselining to latest version {}...", latestVersion);
1139 flyway.setBaselineVersionAsString(latestVersion);
1140 flyway.baseline();
1141 }
1142
1143 public static boolean validate(DatabaseConfiguration databaseConfig) throws Exception {
1144 Database database = new Database(databaseConfig);
1145 boolean isValid = database.validate();
1146 return isValid;
1147 }
1148
1149 /**
1150 * Validate the database.
1151 *
1152 * @return true if database is valid, false otherwise
1153 * @throws MigrationException
1154 */
1155 public boolean validate() throws Exception {
1156 Connection connection = getConnection();
1157 try {
1158 AgentClassLoader classLoader = getAgentClassLoaderByInstalledAgents(connection);
1159 try {
1160 boolean isValid = validate(classLoader);
1161 return isValid;
1162 }
1163 finally {
1164 classLoader.close();
1165 }
1166 }
1167 finally {
1168 connection.close();
1169 }
1170 }
1171
1172 public boolean validate(AgentClassLoader classLoader) throws Exception {
1173 LOGGER.debug("Validating the database");
1174
1175 Flyway flywayCore = createFlywayCore(classLoader);
1176 Map<String, Flyway> flywayAgents = createFlywaysAgents(classLoader);
1177
1178 try {
1179 LOGGER.debug("Validating core...");
1180 flywayCore.validate();
1181
1182 for (Map.Entry<String, Flyway> flywayAgentEntry : flywayAgents.entrySet()) {
1183 String agentName = flywayAgentEntry.getKey();
1184 Flyway flywayAgent = flywayAgentEntry.getValue();
1185
1186 LOGGER.info("Validating agent {}...", agentName);
1187 flywayAgent.validate();
1188 }
1189
1190 LOGGER.debug("Database successfully validated");
1191 return true;
1192 }
1193 catch (FlywayException ex) {
1194 LOGGER.error("Validate failed", ex);
1195 return false;
1196 }
1197 }
1198
1199 public static void repair(DatabaseConfiguration databaseConfig) throws Exception {
1200 Database database = new Database(databaseConfig);
1201 database.repair();
1202 }
1203
1204 /**
1205 * Repair the database. This will perform the following actions:
1206 * <ul>
1207 * <li>Remove any failed migrations on databases without DDL transactions (User objects left behind must still be cleaned up manually)</li>
1208 * <li>Correct wrong checksums</li>
1209 * </ul>
1210 *
1211 * @throws MigrationException
1212 */
1213 public void repair() throws Exception {
1214 Connection connection = getConnection();
1215 try {
1216 AgentClassLoader classLoader = getAgentClassLoaderByInstalledAgents(connection);
1217 try {
1218 repair(classLoader);
1219 }
1220 finally {
1221 classLoader.close();
1222 }
1223 }
1224 finally {
1225 connection.close();
1226 }
1227 }
1228
1229 public void repair(AgentClassLoader classLoader) throws Exception {
1230 LOGGER.info("Repairing the database");
1231
1232 Flyway flywayCore = createFlywayCore(classLoader);
1233 Map<String, Flyway> flywayAgents = createFlywaysAgents(classLoader);
1234
1235 try {
1236 LOGGER.debug("Repairing core...");
1237 flywayCore.repair();
1238
1239 for (Map.Entry<String, Flyway> flywayAgentEntry : flywayAgents.entrySet()) {
1240 String agentName = flywayAgentEntry.getKey();
1241 Flyway flywayAgent = flywayAgentEntry.getValue();
1242
1243 LOGGER.info("Repairing agent {}...", agentName);
1244 flywayAgent.repair();
1245 }
1246
1247 LOGGER.info("Database successfully repaired");
1248 }
1249 catch (FlywayException ex) {
1250 LOGGER.error("Repair failed", ex);
1251 throw new MigrationException("Failed to repair database", ex);
1252 }
1253 }
1254
1255 private void migrateSchema(Flyway flyway) throws MigrationException {
1256 LOGGER.debug("Repairing and migrating schema...");
1257 try {
1258 flyway.repair();
1259 flyway.migrate();
1260 }
1261 catch (FlywayException ex) {
1262 LOGGER.error("Failed to migrate to the latest version", ex);
1263 throw new MigrationException("Failed to migrate to the latest version", ex);
1264 }
1265 }
1266
1267 /**
1268 * Return the locations for the database migration files.
1269 * This method returns the following migration locations:
1270 * - common SQL migration location (which
1271 * contains migrations for all supported databases);
1272 * - SQL migration location that are specific for DB used (for example,
1273 * postgres);
1274 * - Java migration package.
1275 *
1276 * @return migration locations
1277 * @throws MigrationException
1278 */
1279 private String[] getMigrationLocations(LocationConfiguration locationConfiguration) throws MigrationException {
1280 if (locationConfiguration == null) {
1281 return null;
1282 }
1283
1284 try {
1285 String dialectFolder = Utils.getFolderByDialect(databaseConfig.getDialect());
1286 return new String[] {
1287 String.format("%s.%s", locationConfiguration.getSqlMigrationsPrefix(), COMMON_SQL_MIGRATIONS_FOLDER),
1288 String.format("%s.%s", locationConfiguration.getSqlMigrationsPrefix(), dialectFolder),
1289 locationConfiguration.getJavaMigrationsPackage()
1290 };
1291 }
1292 catch (ClassNotFoundException ex) {
1293 throw new MigrationException(String.format("Dialect class %s not found", databaseConfig.getDialect()), ex);
1294 }
1295 }
1296
1297 private Migration getMigration(MigrationInfo migrationInfo) throws MigrationException {
1298 Migration migration = new Migration();
1299 migration.setDescription(migrationInfo.getDescription());
1300 migration.setInstallationDate(migrationInfo.getInstalledOn());
1301 migration.setState(getMigrationState(migrationInfo.getState()));
1302 migration.setType(getMigrationType(migrationInfo.getType()));
1303 migration.setVersion(migrationInfo.getVersion().getVersion());
1304 return migration;
1305 }
1306
1307 private MigrationState getMigrationState(org.flywaydb.core.api.MigrationState state) throws MigrationException {
1308 switch (state) {
1309 case PENDING:
1310 return MigrationState.PENDING;
1311 case ABOVE_TARGET:
1312 case BELOW_BASELINE:
1313 case IGNORED:
1314 return MigrationState.IGNORED;
1315 case BASELINE:
1316 case MISSING_SUCCESS:
1317 case SUCCESS:
1318 case OUT_OF_ORDER:
1319 case FUTURE_SUCCESS:
1320 return MigrationState.SUCCESS;
1321 case MISSING_FAILED:
1322 case FAILED:
1323 case FUTURE_FAILED:
1324 return MigrationState.FAILED;
1325 case OUTDATED:
1326 case SUPERSEEDED:
1327 return MigrationState.OUTDATED;
1328 default:
1329 throw new MigrationException("Unknown migration state: " + state);
1330 }
1331 }
1332
1333 private MigrationType getMigrationType(org.flywaydb.core.api.MigrationType type) throws MigrationException {
1334 switch (type) {
1335 case BASELINE:
1336 case SCHEMA:
1337 return MigrationType.INITIAL;
1338 case JDBC:
1339 case SPRING_JDBC:
1340 return MigrationType.JAVA;
1341 case CUSTOM:
1342 return MigrationType.CUSTOM;
1343 case SQL:
1344 return MigrationType.SQL;
1345 default:
1346 throw new MigrationException("Unknown migration type: " + type);
1347 }
1348 }
1349
1350 private String getMigrationsVersion(MigrationInfo[] migrations) {
1351 MigrationInfo latestMigration = getLatestMigration(migrations);
1352 if (latestMigration == null) {
1353 LOGGER.debug("No migrations found.");
1354 return null;
1355 }
1356
1357 String latestMigrationVersion = latestMigration.getVersion().getVersion();
1358 LOGGER.debug("The latest migration has the version {}", latestMigrationVersion);
1359
1360 return latestMigrationVersion;
1361 }
1362
1363 private MigrationInfo getLatestMigration(MigrationInfo[] migrations) {
1364 if (migrations == null || migrations.length == 0) {
1365 return null;
1366 }
1367 LOGGER.debug("{} migrations found", migrations.length);
1368 List<MigrationInfo> migrationsList = Arrays.asList(migrations);
1369 Collections.sort(migrationsList);
1370 return migrationsList.get(migrationsList.size() - 1);
1371 }
1372
1373 private List<Migration> getMigrations(Flyway flyway) throws MigrationException {
1374 LOGGER.trace("getMigrations()");
1375 try {
1376 MigrationInfoService infoService = flyway.info();
1377 MigrationInfo[] migrationInfos = infoService.all();
1378 List<Migration> migrations = new ArrayList<>();
1379 LOGGER.debug("{} migrations", migrationInfos.length);
1380 for (MigrationInfo migrationInfo : migrationInfos) {
1381 Migration migration = getMigration(migrationInfo);
1382 LOGGER.debug("{}", migration.toString());
1383 migrations.add(migration);
1384 }
1385 return migrations;
1386 }
1387 catch (FlywayException ex) {
1388 throw new MigrationException("Failed to get migrations", ex);
1389 }
1390 }
1391
1392 private Flyway createFlywayCore(AgentClassLoader classLoader) throws MigrationException {
1393 LOGGER.debug("Creating flyway object for core...");
1394 Flyway flywayCore = createFlyway(classLoader, null, null);
1395 return flywayCore;
1396 }
1397
1398 private Flyway createFlywayAgent(AgentClassLoader classLoader, String agentName) throws Exception {
1399 LOGGER.debug("Creating flyway object for agent {}...", agentName);
1400 AgentConfiguration agentConfig = getAgentConfiguration(classLoader, agentName);
1401 Flyway flywayAgent = createFlyway(classLoader, agentName, agentConfig);
1402 return flywayAgent;
1403 }
1404
1405 private Map<String, Flyway> createFlywaysAgents(AgentClassLoader classLoader) throws Exception {
1406 Map<String, Flyway> flywayAgents = new HashMap<String, Flyway>();
1407 for (String agentName : classLoader.getAgentNames()) {
1408 Flyway flywayAgent = createFlywayAgent(classLoader, agentName);
1409 flywayAgents.put(agentName, flywayAgent);
1410 }
1411 return flywayAgents;
1412 }
1413
1414 /**
1415 * Create flyway instance for core or agent
1416 *
1417 * @param agentConfiguration agent configuration if creating flyway for agent, or null
1418 * if creating for core
1419 * @return flyway
1420 * @throws MigrationException
1421 */
1422 private Flyway createFlyway(AgentClassLoader classLoader, String agentName, AgentConfiguration agentConfiguration) throws MigrationException {
1423 Flyway flyway = new Flyway();
1424
1425 flyway.setClassLoader(classLoader);
1426
1427 flyway.setDataSource(databaseConfig.getConnectionString(), databaseConfig.getUsername(), databaseConfig.getPassword());
1428
1429 /*
1430 * Change the default placeholder prefix from the default ${ to $${
1431 * This is required because some our import scripts have ${...} (for
1432 * example, ${NMS_HOME}) that should not be expanded, and Flyway
1433 * complains that the placeholder cannot be resolved.
1434 */
1435 flyway.setPlaceholderPrefix(PLACEHOLDER_PREFIX);
1436
1437 String encoding = getEncodingOrDefault();
1438 flyway.setEncoding(encoding);
1439
1440 String[] locations;
1441 if (agentConfiguration != null) {
1442 locations = getMigrationLocations(agentConfiguration.getLocationConfiguration());
1443 }
1444 else {
1445 locations = getMigrationLocations(new LocationConfiguration(CORE_SQL_MIGRATIONS_PREFIX, CORE_JAVA_MIGRATIONS_PACKAGE));
1446 }
1447 flyway.setLocations(locations);
1448
1449 String schema = databaseConfig.getSchema();
1450 if (schema != null) {
1451 flyway.setSchemas(schema);
1452 }
1453
1454 if (agentName != null) {
1455 String tableName = getAgentSchemaVersionTableName(agentName);
1456 flyway.setTable(tableName);
1457 }
1458
1459 boolean outOfOrder = getOutOfOrderOrDefault();
1460 flyway.setOutOfOrder(outOfOrder);
1461
1462 boolean validateOnMigrate = getValidateOnMigrateOrDefault();
1463 flyway.setValidateOnMigrate(validateOnMigrate);
1464
1465 /*
1466 * Without this property set to true, initial migrate will fail for agent
1467 * because schema is not empty.
1468 */
1469 flyway.setBaselineOnMigrate(true);
1470
1471 return flyway;
1472 }
1473
1474 public static String getAgentSchemaVersionTableName(String agentName) {
1475 String tableName = String.format(SCHEMA_VERSION_AGENT_TABLE_TEMPLATE, agentName);
1476 return tableName;
1477 }
1478
1479 private Connection getConnection() throws SQLException {
1480 return getConnection(databaseConfig.getConnectionString(), databaseConfig.getUsername(), databaseConfig.getPassword());
1481 }
1482
1483 private Connection getConnection(String connectionString, String username, String password) throws SQLException {
1484 Connection connection = DriverManager.getConnection(connectionString, username, password);
1485 return connection;
1486 }
1487
1488 private AgentClassLoader getAgentClassLoaderByJar(String agentJarPath) throws MalformedURLException {
1489 AgentClassLoader classLoader = new AgentClassLoader();
1490 classLoader.addAgentFile(agentJarPath);
1491 return classLoader;
1492 }
1493
1494 private AgentClassLoader getAgentClassLoaderByJars(String[] agentJarPaths) throws MalformedURLException {
1495 AgentClassLoader classLoader = new AgentClassLoader();
1496 classLoader.addAgentFiles(agentJarPaths);
1497 return classLoader;
1498 }
1499
1500 private AgentClassLoader getAgentClassLoaderByDirectory(String agentDirectory) {
1501 AgentClassLoader classLoader = new AgentClassLoader();
1502 AgentDirectoryPlaceholderResolver placeholderResolver = new AgentDirectoryPlaceholderResolver();
1503 String resolvedAgentDirectory = placeholderResolver.replacePlaceholders(agentDirectory);
1504 LOGGER.debug("Resovled agent directory: {} -> {}", agentDirectory, resolvedAgentDirectory);
1505 classLoader.addAgentFolder(resolvedAgentDirectory);
1506 return classLoader;
1507 }
1508
1509 private AgentClassLoader getAgentClassLoaderByInstalledAgents(Connection connection) throws SQLException {
1510 AgentClassLoader classLoader = new AgentClassLoader();
1511 AgentDirectoryPlaceholderResolver placeholderResolver = new AgentDirectoryPlaceholderResolver();
1512 List<TEM_Agent> installedAgents = getInstalledAgents(connection);
1513 for (TEM_Agent installedAgent : installedAgents) {
1514 String agentDirectory = installedAgent.getDirectory();
1515 String resolvedAgentDirectory = placeholderResolver.replacePlaceholders(agentDirectory);
1516 LOGGER.debug("Resolved agent directory: {} -> {}", agentDirectory, resolvedAgentDirectory);
1517 classLoader.addAgentFolder(resolvedAgentDirectory);
1518 }
1519 return classLoader;
1520 }
1521
1522 private static final String queryGetInstalledAgents = "SELECT id, agentversion, description, deviceclass, directory, displayname, installationdate, name, agentclass FROM tem_agent";
1523
1524 private List<TEM_Agent> getInstalledAgents(Connection connection) throws SQLException {
1525 if (!isAgentsTableExists(connection)) {
1526 return new ArrayList<TEM_Agent>();
1527 }
1528
1529 Statement statement = connection.createStatement();
1530 ResultSet agentsResultSet = statement.executeQuery(queryGetInstalledAgents);
1531 List<TEM_Agent> result = new ArrayList<TEM_Agent>();
1532 while (agentsResultSet.next()) {
1533 TEM_Agent agent = getAgentFromResultSet(agentsResultSet);
1534 result.add(agent);
1535 }
1536 return result;
1537 }
1538
1539 private static final String queryGetInstalledAgentById = "SELECT id, agentversion, description, deviceclass, directory, displayname, installationdate, name, agentclass FROM tem_agent WHERE id=?";
1540
1541 private TEM_Agent getInstalledAgentById(Connection connection, Long agentId) throws SQLException {
1542 PreparedStatement statement = connection.prepareStatement(queryGetInstalledAgentById);
1543 statement.setLong(1, agentId);
1544 ResultSet agentResultSet = statement.executeQuery();
1545 if (agentResultSet.next()) {
1546 TEM_Agent agent = getAgentFromResultSet(agentResultSet);
1547 return agent;
1548 }
1549 else {
1550 return null;
1551 }
1552 }
1553
1554 private static final String queryGetInstalledAgentByName = "SELECT id, agentversion, description, deviceclass, directory, displayname, installationdate, name, agentclass FROM tem_agent WHERE name=?";
1555
1556 private TEM_Agent getInstalledAgentByName(Connection connection, String agentName) throws SQLException {
1557 PreparedStatement statement = connection.prepareStatement(queryGetInstalledAgentByName);
1558 statement.setString(1, agentName);
1559 ResultSet agentResultSet = statement.executeQuery();
1560 if (agentResultSet.next()) {
1561 TEM_Agent agent = getAgentFromResultSet(agentResultSet);
1562 return agent;
1563 }
1564 else {
1565 return null;
1566 }
1567 }
1568
1569 private TEM_Agent getAgentFromResultSet(ResultSet resultSet) throws SQLException {
1570 Long id = resultSet.getLong("id");
1571 String agentVersion = resultSet.getString("agentversion");
1572 String description = resultSet.getString("description");
1573 String deviceClass = resultSet.getString("deviceclass");
1574 String directory = resultSet.getString("directory");
1575 String displayName = resultSet.getString("displayname");
1576 Date installationDate = resultSet.getDate("installationdate");
1577 String name = resultSet.getString("name");
1578 String agentClass = resultSet.getString("agentclass");
1579
1580 TEM_Agent agent = new TEM_Agent();
1581 agent.setId(id);
1582 agent.setAgentVersion(agentVersion);
1583 agent.setDescription(description);
1584 agent.setDeviceClass(deviceClass);
1585 agent.setDirectory(directory);
1586 agent.setDisplayName(displayName);
1587 agent.setInstallationDate(installationDate);
1588 agent.setName(name);
1589 agent.setAgentClass(agentClass);
1590
1591 return agent;
1592 }
1593
1594 private static final String queryInsertAgent_postgres = "INSERT INTO tem_agent(id, version, agentversion, description, deviceclass, directory, displayname, installationdate, name, agentclass) " +
1595 "VALUES(nextval('id_sequence'), 0, ?, ?, ?, ?, ?, ?, ?, ?)";
1596
1597 private static final String queryInsertAgent_hsqldb = "INSERT INTO tem_agent(id, version, agentversion, description, deviceclass, directory, displayname, installationdate, name, agentclass) " +
1598 "VALUES(nextval('id_sequence'), 0, ?, ?, ?, ?, ?, ?, ?, ?)";
1599
1600 private String getInsertAgentQuery(String dialectFolder) throws Exception {
1601 if (Utils.FOLDER_POSTGRES.equals(dialectFolder)) {
1602 return queryInsertAgent_postgres;
1603 }
1604 else if (Utils.FOLDER_HSQLDB.equals(dialectFolder)) {
1605 return queryInsertAgent_hsqldb;
1606 }
1607 else if (Utils.FOLDER_ORACLE.equals(dialectFolder)) {
1608 throw new Exception(String.format("Inserting into agents table is not implemented for dialect %s", dialectFolder));
1609 }
1610 else {
1611 throw new Exception(String.format("Dialect %s not supported", dialectFolder));
1612 }
1613 }
1614
1615 private Properties getAgentProperties(AgentClassLoader classLoader, String agentName) throws Exception {
1616 Properties properties = classLoader.getPropertiesByAgentName(agentName);
1617 if (properties == null) {
1618 throw new Exception(String.format("Properties for agent %s not found", agentName));
1619 }
1620 return properties;
1621 }
1622
1623 private AgentConfiguration getAgentConfiguration(AgentClassLoader classLoader, String agentName) throws Exception {
1624 Properties agentProperties = getAgentProperties(classLoader, agentName);
1625
1626 String sqlMigrationsPrefix = agentProperties.getProperty(AgentProperties.AGENT_SQL_MIGRATIONS_PREFIX_PROPERTY);
1627 LOGGER.debug("Agent {} SQL migrations prefix: {}", agentName, sqlMigrationsPrefix);
1628 String javaMigrationsPackage = agentProperties.getProperty(AgentProperties.AGENT_JAVA_MIGRATIONS_PACKAGE_PROPERTY);
1629 LOGGER.debug("Agent {} Java migrations package: {}", agentName, javaMigrationsPackage);
1630
1631 AgentConfiguration agentConfig = new AgentConfiguration(new LocationConfiguration(sqlMigrationsPrefix, javaMigrationsPackage));
1632 return agentConfig;
1633 }
1634
1635 private String getAgentSqlResourceFolder(AgentClassLoader classLoader, String agentName) throws Exception {
1636 Properties agentProperties = getAgentProperties(classLoader, agentName);
1637 String resourceFolder = agentProperties.getProperty(AgentProperties.AGENT_SQL_FOLDER_PROPERTY);
1638 LOGGER.debug("Agent {} SQL folder: {}", agentName, resourceFolder);
1639 return resourceFolder;
1640 }
1641
1642 private String[] loadCommands(ClassLoader classLoader, ImportSqlCommandExtractor commandExtractor,
1643 String resourceFolder, String dialectFolder, String[] scriptFileNames, String encoding) throws IOException {
1644 List<String> allScriptCommands = new ArrayList<String>();
1645 for (String scriptFileName : scriptFileNames) {
1646 String[] scriptCommands = loadCommands(classLoader, commandExtractor, resourceFolder, dialectFolder, scriptFileName, encoding);
1647 if (scriptCommands != null) {
1648 allScriptCommands.addAll(Arrays.asList(scriptCommands));
1649 }
1650 }
1651 return allScriptCommands.toArray(new String[allScriptCommands.size()]);
1652 }
1653
1654 private String[] loadCommands(ClassLoader classLoader, ImportSqlCommandExtractor commandExtractor,
1655 String resourceFolder, String dialectFolder, String scriptFileName, String encoding) throws IOException {
1656 LOGGER.debug("Loading commands from file {}", scriptFileName);
1657
1658 String scriptFilePath = getScriptFilePath(resourceFolder, dialectFolder, scriptFileName);
1659 LOGGER.debug("Loading commands from resource {}...", scriptFilePath);
1660
1661 try (InputStream is = classLoader.getResourceAsStream(scriptFilePath)) {
1662 if (is == null) {
1663 LOGGER.info("Resource {} not found", scriptFilePath);
1664 return null;
1665 }
1666
1667 LOGGER.info("Resource {} found", scriptFilePath);
1668
1669 InputStreamReader reader = new InputStreamReader(is, encoding);
1670 LOGGER.debug("Extracting commands from resource {}", scriptFilePath);
1671 String[] commands = commandExtractor.extractCommands(reader);
1672 LOGGER.debug("Extracted {} commands from resource {}", commands.length, scriptFilePath);
1673 return commands;
1674 }
1675 }
1676
1677 private String loadScriptAsString(ClassLoader classLoader, String resourceFolder, String dialectFolder,
1678 String scriptFileName, String encoding) throws IOException {
1679 LOGGER.debug("Loading script from file {}", scriptFileName);
1680
1681 String scriptFilePath = getScriptFilePath(resourceFolder, dialectFolder, scriptFileName);
1682 LOGGER.debug("Loading script from resource {}...", scriptFilePath);
1683
1684 try (InputStream is = classLoader.getResourceAsStream(scriptFilePath)) {
1685 if (is == null) {
1686 LOGGER.info("Resource {} not found", scriptFilePath);
1687 return null;
1688 }
1689
1690 LOGGER.info("Resource {} found", scriptFilePath);
1691
1692 String script = IOUtils.toString(is, encoding);
1693 return script;
1694 }
1695 }
1696
1697 private String getScriptFilePath(String resourceFolder, String dialectFolder, String scriptFileName) {
1698 return String.format("%s/%s/%s", resourceFolder, dialectFolder, scriptFileName);
1699 }
1700
1701 private List<Exception> executeCommands(Connection connection, String[] commands) throws SQLException {
1702 List<Exception> exceptions = new ArrayList<Exception>();
1703 if (commands != null) {
1704 Statement statement = connection.createStatement();
1705 for (String command : commands) {
1706 try {
1707 LOGGER.debug("Executing command {}", command);
1708 statement.execute(command);
1709 }
1710 catch (SQLException ex) {
1711 exceptions.add(ex);
1712 }
1713 }
1714 }
1715 return exceptions;
1716 }
1717
1718 private void executeScript(Connection connection, String script) throws SQLException {
1719 Statement statement = connection.createStatement();
1720 statement.execute(script);
1721 }
1722
1723 private Class<? extends ImportSqlCommandExtractor> getCommandExtractorClassOrDefault() {
1724 Class<? extends ImportSqlCommandExtractor> commandExtractorClass = databaseConfig.getCommandExtractorClass();
1725 if (commandExtractorClass == null) {
1726 commandExtractorClass = DatabaseConfiguration.DEFAULT_COMMAND_EXTRACTOR_CLASS;
1727 }
1728 return commandExtractorClass;
1729 }
1730
1731 private String getEncodingOrDefault() {
1732 String encoding = databaseConfig.getEncoding();
1733 if (encoding == null) {
1734 encoding = DatabaseConfiguration.DEFAULT_ENCODING;
1735 }
1736 return encoding;
1737 }
1738
1739 private boolean getOutOfOrderOrDefault() {
1740 Boolean outOfOrder = databaseConfig.isOutOfOrder();
1741 if (outOfOrder != null) {
1742 return outOfOrder.booleanValue();
1743 }
1744 else {
1745 return DatabaseConfiguration.DEFAULT_OUT_OF_ORDER;
1746 }
1747 }
1748
1749 private boolean getValidateOnMigrateOrDefault() {
1750 Boolean validateOnMigrate = databaseConfig.isValidateOnMigrate();
1751 if (validateOnMigrate != null) {
1752 return validateOnMigrate.booleanValue();
1753 }
1754 else {
1755 return DatabaseConfiguration.DEFAULT_VALIDATE_ON_MIGRATE;
1756 }
1757 }
1758
1759 /**
1760 * Checks if table exists.
1761 * We just try to select from agents table, and if unsuccessful, return false.
1762 * First version of this method tried to get connection metadata, but we must
1763 * know the schema name for that, and getting current schema name is not easy
1764 * using JDBC.
1765 *
1766 * @param connection
1767 * @return
1768 */
1769 private boolean isAgentsTableExists(Connection connection) {
1770 try {
1771 Statement statement = connection.createStatement();
1772 statement.executeQuery(queryGetInstalledAgents);
1773 LOGGER.debug("Agents table exists");
1774 return true;
1775 }
1776 catch (SQLException ex) {
1777 LOGGER.debug("Failed to get installed agents", ex);
1778 return false;
1779 }
1780 }
1781
1782}