Index: 3rdParty_sources/tacitknowledge/autopatch/.mailmap =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/.mailmap (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/.mailmap (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,17 @@ +Mike Hardy mikehardy +Mike Hardy mike +Scott Askew Scott Askew +Scott Askew scott +Scott Askew saskew +Alex Soto alex +Alex Soto asoto +Marques Lee marquesfarques +Artie Pesh-Imam apeshimam +Vladimir Pertu vpertu +Vladimir Pertu woffca +Chris Andrasick chrisa +Ian Mortimer imorti +Paddy Atherstone atherstone +Stephanie Blair sblair +Vladislav Gangan vgangantk +John R. Jackson jrjackso Index: 3rdParty_sources/tacitknowledge/autopatch/CHANGELOG =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/CHANGELOG (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/CHANGELOG (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,186 @@ +Changelog for AutoPatch +======================= + +Version 1.4.2 +---------------------------------- +- Merged pull request 31 from Brian Jaress : "Fix for abstract method errors" + +Version 1.4.1 +---------------------------------- +- Added PicoContainer support. + +Version 1.4.0 +---------------------------------- +- Fixed #30 - MigrationTaskSupport now has a default value for the 'name' property. + +Version 1.4.0 +---------------------------------- +- Bumped JDK requirement to 1.6. It's about time. +- Fixed ResultSet closing bug, thanks to github/misnomer. + +Version 1.3.3 +---------------------------------- +- Adjusted lock.obtain query to overcome mysql's 1093 error + +Version 1.3.2 +---------------------------------- +- Issue #29 -- Corrected table name in label.table.exists to tk_patches. + +Version 1.3.1 +---------------------------------- +- Two bug fixes: + Issue #28. Fixes multi-server race condition + Issue #27: Modifying property files to use system_name and patch_level combination to be the primary key. + +Thanks to Jeff Kolesky for the fixes! + +Version 1.3 +---------------------------------- +- Support for branching. There is a new property "migration.strategy" that can + be used to indicate how AutoPatch should determine which patches need to be + run (ordered or missing patches). See more here: + https://github.com/tacitknowledge/autopatch/wiki/How-to-Migrate-to-AutoPatch-1.3 + +Version 1.2.0-b3 +--------------------------------- +- Altered logging and comments for autocommit+commit/rollback to more closely + correlate with severity and the needed fix + +Version 1.2.0-b2 +--------------------------------- +- Included bugfix in discovery jar for duplicate patch detection + +Version 1.2.0-b1 +---------------------------------- +- Added ability for migrations to be rolled back. This change maintains + 100% backwards compatibility for existing implementations of MigrationTask. + Initations of rollback migrations are available through the + StandaloneMigrationLauncher which now can accept a flag "-rollback." An + additional integer parameter is required to indicate the level that the + system should rollback to. Added a new interface called + RollbackableMigrationTask which defines three methods: isRollbackSupported, + up and down. Deprecated MigrationTask. All new tasks should now implement + RollbackableMigrationTask. If a task is not intended + to be rolledback, then return false for isRollbackSupported. + +- Example configuration for mssql / ant from Simon Kingaby. Thanks Simon! + +- Exposed the MigrationListener framework to allow user defined + MigrationListeners. Define a property named .listeners in your + migration.properties. It supports a comma separated list of fully qualified + java classnames that implement the MigrationListener interface. + Thanks to Alex Soto + +- Added the detection of new database nodes in a DistributedMigration. + Default behaviour is to exit the patch process if a new node is detected. + The node can be forcibly synced to the current patch level by defining a + system property named 'forcesync'. See javadoc on DistributedMigrationProcess + for more details. + Thanks to Alex Soto + +- Added support to DistributedStandaloneMigrationLauncher for specifying an + alternate migration.properties filename via the system property + 'migration.settings'. This brings the distributed migration feature + in line with StandaloneMigrationLauncher which already supported this. + Thanks to Natalia Usmanova @ KodakGallery dot Com + +- Added the ability to override the properties specified in the + databasetype.properties (mysql.properties, hsqldb.properties, ...). + Although technically possible to override any property in those files, + the feature was added to tweak the *supportsMultipleStatements* property, + and is probably the most common one to override. + See the javadoc on com.tacitknowledge.util.migration.jdbc.DatabaseType + for details on how to specify the override. + Thanks to Alex Soto + +- Added support for raw .sql patches in the PHP AutoPatch + Thanks to Mike Hardy @ Tacit Knowledge + +- Fixed a bug in the handling of the readOnly flag that caused it not to work! + Bug found and correct patch proposed by Natalia Usmanova. Thanks Natalia! + +Version 1.0.3 +------------- +- Added "lockPollRetries" property, allows a configuration where AutoPatch + will wait a maximum amount of time then override a lock. Also shored up + testing / coverage in this area + Thanks to Mike Hardy @ Tacit Knowledge + +Version 1.0.2 +------------- +- For standalone migration information and launcher added a possibility + to supply a migration.settings system property (or command line argument) + that will specify the name of the file to use for migration settings. If + not supplied then the default 'migration.properties' will be used. + Thanks to Vladislav Gangan @ Tacit Knowledge + +Version 1.0.1 +------------- +- All development (bug tracking, feature requests, CVS, mailing lists) + are hosted on sourceforge now, not just the releases. Check it out: + http://autopatch.sourceforge.net + +- Added support for Microsoft SQL Server dialect. + Support for Microsoft SQL Server should be considered beta + and some features may depend on specific JDBC drivers to + work. The free MS-SQL JDBC driver may have issues. + Basic DDL patches appear to work. + https://sourceforge.net/support/tracker.php?aid=1658440 + Thank you Shirish Kulkarni + +- Added support for Sybase dialect + Support for Sybase should be considered beta at the moment. + It has not received a great deal of testing yet. + https://sourceforge.net/support/tracker.php?aid=1662010 + Thank you Alex Soto + +- List of unapplied patches displayed prior to patching + Mike Hardy + https://sourceforge.net/support/tracker.php?aid=1658446 + +- Read-only mode implemented, now you can specify that the + system should be read-only, and it will do everything + *except* actually apply the patches. If the level isn't correct + it will throw an exception, you can use this to enforce consistency + while making sure that systems aren't patched automatically + (useful in production configurations) + Mike Hardy + https://sourceforge.net/support/tracker.php?aid=1658445 + +- Added an example project that demonstrates how most of the AutoPatch + features work, including the distributed feature + Thank you Alex Soto + +- Found and fixed improper database connection usage + https://sourceforge.net/support/tracker.php?aid=1665584 + Thank you Alex Soto + +- Implemented serial application of same patch across multiple + databases, and extended documentation and example to cover this. + This is referred to as "multi-node" patch application in the code and docs + Mike Hardy + https://sourceforge.net/support/tracker.php?aid=1661985 + +- Added integration tests utilizing HSQLDB to further automate correctness + Mike Hardy + +- Implemented forcible table unlocking (useful if JVMs crash) + Mike Hardy + https://sourceforge.net/support/tracker.php?aid=1658547 + +- Big Checkstyle cleanup + +- Added XML file loader (useful to import DbUnit files) + Alex Soto + +- More friendly distribution packaging - a zip file, + and unpacks into a sub-directory instead of the current directory + Mike Hardy + +- Don't allow commit or rollback statements through to the connection + if autocommit is true, JBoss will throw an exception otherwise + Reported by Kendall Jackman @ ATG (thanks Kendall) + +Version 1.0.0 +------------- +Versions prior to 1.0.0 did not maintain a changelog. Index: 3rdParty_sources/tacitknowledge/autopatch/README.md =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/README.md (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/README.md (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,58 @@ +AutoPatch +========= + +AutoPatch automates the application of changes to persistent storage. + +AutoPatch was born from the needs of using an agile development process +while working on systems that have persistent storage. Without +AutoPatch, developers usually can't afford the maintenance headache of +their own database, and DBAs are required just to apply changes to all +of the various environments a serious development effort requires. + +The very application of database changes becomes an inefficient, +error-prone, expensive process, all conspiring to discourage any +refactoring that touches the model, or being a bottleneck when model +changes are made. + +AutoPatch solves this problem, completely. + +With AutoPatch, an agile development process that requires a database +change looks like this: + +* Developer alters the model, which requires a change to the database +* Developer possibly consults a DBA, and develops a SQL patch against + their personal database that implements the alteration +* Developer commits the patch to source control at the same time as they + commit their dependent code +* Other developers' and environments' databases are automatically updated + by AutoPatch the next time the new source is run + +This represents streamlined environment maintenance, allowing developers +to cheaply have their own databases and all databases to stay in synch +with massively lower costs and no environment skew. + +Requirements +------------ + +* Java 6. That's it. + + +Where do I get AutoPatch? +------------------------- +AutoPatch is open source and is hosted at [Github](http://github.com/tacitknowledge/autopatch). + +The documentation for AutoPatch is on the [AutoPatch Wiki](https://github.com/tacitknowledge/autopatch/wiki) + +You can include AutoPath in your Maven project via: + + + com.tacitknowledge + autopatch + 1.4.2 + + +Help +==== + +If you have a question about AutoPatch feel free to post it up to our +new [AutoPatch Google Group](http://groups.google.com/group/autopatch-users/). Index: 3rdParty_sources/tacitknowledge/autopatch/examples/MSSQL-example.txt =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/examples/MSSQL-example.txt (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/examples/MSSQL-example.txt (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,92 @@ + +Simon Kingaby got a working setup with autopatch talking to MS-SQL using ant. +He documented his configuration in the hopes it would help future folks. + +Here's his configuration: + +Project Structure: +------------------ + +trunk +-->HelloWorldDatabase + (build.xml) +---->src +------>main +-------->conf + (log4j.properties) + (migration.properties) +-------->db +---------->patch + (patch0001_create_table.sql) +-->vendor +---->build +------>sqljdbc_1.1 +-------->enu + (sqljdbc.jar and other sqljdbc files and folders) +------>tk-autopatch + (tk-autopatch-1.0.2.jar and other autopatch files and folders) + + +Here is the migration.properties file: +-------------------------------------- +orchestration.context=zabbadoo +orchestration.controlled.systems=zabbadoo +zabbadoo.jdbc.database.type=sqlserver +zabbadoo.jdbc.driver=com.microsoft.sqlserver.jdbc.SQLServerDriver +zabbadoo.jdbc.url=jdbc:sqlserver://kobol\\dev;database=zabbadoo +zabbadoo.jdbc.username=zabbadoo +zabbadoo.jdbc.password=password +zabbadoo.patch.path=db.patch + + +Here is the ant build.xml: +-------------------------- + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file Index: 3rdParty_sources/tacitknowledge/autopatch/examples/distributed-sample/build.xml =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/examples/distributed-sample/build.xml (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/examples/distributed-sample/build.xml (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/tacitknowledge/autopatch/examples/distributed-sample/conf/log4j.properties =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/examples/distributed-sample/conf/log4j.properties (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/examples/distributed-sample/conf/log4j.properties (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,8 @@ +# Set root logger level to DEBUG for lots of output +log4j.rootLogger=DEBUG, CONSOLE + +# If you would like to have output simply be on the console, set the +# rootLogger to CONSOLE +log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender +log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout +log4j.appender.CONSOLE.layout.ConversionPattern=%d %-4r [%t] %-5p %c{1} %x - %m%n Index: 3rdParty_sources/tacitknowledge/autopatch/examples/distributed-sample/conf/migration.properties =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/examples/distributed-sample/conf/migration.properties (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/examples/distributed-sample/conf/migration.properties (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,37 @@ +distributed-sample.context=orchestrator +distributed-sample.controlled.systems=app1,app2,app3 + +orchestrator.jdbc.database.type=hsqldb +orchestrator.jdbc.driver=org.hsqldb.jdbcDriver +orchestrator.jdbc.url=jdbc:hsqldb:file:./var/orchestrator +orchestrator.jdbc.username=sa +orchestrator.jdbc.password= +orchestrator.patch.path=orchestrator:example.orchestrator.db.patch + + +app1.jdbc.database.type=hsqldb +app1.jdbc.driver=org.hsqldb.jdbcDriver +app1.jdbc.url=jdbc:hsqldb:file:./var/app1 +app1.jdbc.username=sa +app1.jdbc.password= +app1.patch.path=app1:example.app1.db.patch + +app2.jdbc.database.type=hsqldb +app2.jdbc.driver=org.hsqldb.jdbcDriver +app2.jdbc.url=jdbc:hsqldb:file:./var/app2 +app2.jdbc.username=sa +app2.jdbc.password= +app2.patch.path=app2:example.app2.db.patch + +app3.jdbc.systems=jdbc-system1,jdbc-system2 +app3.jdbc-system1.database.type=hsqldb +app3.jdbc-system1.driver=org.hsqldb.jdbcDriver +app3.jdbc-system1.url=jdbc:hsqldb:file:./var/app3/system1 +app3.jdbc-system1.username=sa +app3.jdbc-system1.password= +app3.jdbc-system2.database.type=hsqldb +app3.jdbc-system2.driver=org.hsqldb.jdbcDriver +app3.jdbc-system2.url=jdbc:hsqldb:file:./var/app3/system2 +app3.jdbc-system2.username=sa +app3.jdbc-system2.password= +app3.patch.path=app3:example.app3.db.patch \ No newline at end of file Index: 3rdParty_sources/tacitknowledge/autopatch/examples/distributed-sample/db/data/app1/patch00005_initial_data.xml =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/examples/distributed-sample/db/data/app1/patch00005_initial_data.xml (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/examples/distributed-sample/db/data/app1/patch00005_initial_data.xml (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,7 @@ + + + + + + + Index: 3rdParty_sources/tacitknowledge/autopatch/examples/distributed-sample/db/sql/app1/patch00002.sql =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/examples/distributed-sample/db/sql/app1/patch00002.sql (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/examples/distributed-sample/db/sql/app1/patch00002.sql (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,4 @@ +CREATE TABLE baz ( + id INT NOT NULL PRIMARY KEY, + value VARCHAR(256) +); \ No newline at end of file Index: 3rdParty_sources/tacitknowledge/autopatch/examples/distributed-sample/db/sql/app2/patch00001.sql =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/examples/distributed-sample/db/sql/app2/patch00001.sql (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/examples/distributed-sample/db/sql/app2/patch00001.sql (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,4 @@ +CREATE TABLE foo ( + id INT NOT NULL PRIMARY KEY, + value VARCHAR(256) +); \ No newline at end of file Index: 3rdParty_sources/tacitknowledge/autopatch/examples/distributed-sample/db/sql/app2/patch00003.sql =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/examples/distributed-sample/db/sql/app2/patch00003.sql (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/examples/distributed-sample/db/sql/app2/patch00003.sql (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,6 @@ +CREATE TABLE bar ( + id INT NOT NULL PRIMARY KEY, + value VARCHAR(256) +); + +INSERT INTO bar(id, value) VALUES (1, 'foo'); \ No newline at end of file Index: 3rdParty_sources/tacitknowledge/autopatch/examples/distributed-sample/db/sql/app3/patch00004.sql =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/examples/distributed-sample/db/sql/app3/patch00004.sql (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/examples/distributed-sample/db/sql/app3/patch00004.sql (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,6 @@ +CREATE TABLE baz ( + id INT NOT NULL PRIMARY KEY, + value VARCHAR(256) +); + +INSERT INTO baz(id, value) VALUES (1, 'foo'); \ No newline at end of file Index: 3rdParty_sources/tacitknowledge/autopatch/examples/distributed-sample/lib/hsqldb.jar =================================================================== diff -u Binary files differ Index: 3rdParty_sources/tacitknowledge/autopatch/examples/distributed-sample/var/app1.properties =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/examples/distributed-sample/var/app1.properties (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/examples/distributed-sample/var/app1.properties (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,17 @@ +#HSQL Database Engine 1.8.0.7 +#Wed Feb 21 16:08:47 PST 2007 +hsqldb.script_format=0 +runtime.gc_interval=0 +sql.enforce_strict_size=false +hsqldb.cache_size_scale=8 +readonly=false +hsqldb.nio_data_file=true +hsqldb.cache_scale=14 +version=1.8.0 +hsqldb.default_table_type=memory +hsqldb.cache_file_scale=1 +hsqldb.log_size=200 +modified=yes +hsqldb.cache_version=1.7.0 +hsqldb.original_version=1.8.0 +hsqldb.compatible_version=1.8.0 Index: 3rdParty_sources/tacitknowledge/autopatch/examples/distributed-sample/var/app1.script =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/examples/distributed-sample/var/app1.script (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/examples/distributed-sample/var/app1.script (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,4 @@ +CREATE SCHEMA PUBLIC AUTHORIZATION DBA +CREATE USER SA PASSWORD "" +GRANT DBA TO SA +SET WRITE_DELAY 10 Index: 3rdParty_sources/tacitknowledge/autopatch/examples/distributed-sample/var/app2.properties =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/examples/distributed-sample/var/app2.properties (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/examples/distributed-sample/var/app2.properties (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,17 @@ +#HSQL Database Engine 1.8.0.7 +#Wed Feb 21 16:08:47 PST 2007 +hsqldb.script_format=0 +runtime.gc_interval=0 +sql.enforce_strict_size=false +hsqldb.cache_size_scale=8 +readonly=false +hsqldb.nio_data_file=true +hsqldb.cache_scale=14 +version=1.8.0 +hsqldb.default_table_type=memory +hsqldb.cache_file_scale=1 +hsqldb.log_size=200 +modified=yes +hsqldb.cache_version=1.7.0 +hsqldb.original_version=1.8.0 +hsqldb.compatible_version=1.8.0 Index: 3rdParty_sources/tacitknowledge/autopatch/examples/distributed-sample/var/app2.script =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/examples/distributed-sample/var/app2.script (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/examples/distributed-sample/var/app2.script (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,4 @@ +CREATE SCHEMA PUBLIC AUTHORIZATION DBA +CREATE USER SA PASSWORD "" +GRANT DBA TO SA +SET WRITE_DELAY 10 Index: 3rdParty_sources/tacitknowledge/autopatch/examples/distributed-sample/var/app3/system1.properties =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/examples/distributed-sample/var/app3/system1.properties (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/examples/distributed-sample/var/app3/system1.properties (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,17 @@ +#HSQL Database Engine 1.8.0.7 +#Wed Feb 21 16:08:47 PST 2007 +hsqldb.script_format=0 +runtime.gc_interval=0 +sql.enforce_strict_size=false +hsqldb.cache_size_scale=8 +readonly=false +hsqldb.nio_data_file=true +hsqldb.cache_scale=14 +version=1.8.0 +hsqldb.default_table_type=memory +hsqldb.cache_file_scale=1 +hsqldb.log_size=200 +modified=yes +hsqldb.cache_version=1.7.0 +hsqldb.original_version=1.8.0 +hsqldb.compatible_version=1.8.0 Index: 3rdParty_sources/tacitknowledge/autopatch/examples/distributed-sample/var/app3/system1.script =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/examples/distributed-sample/var/app3/system1.script (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/examples/distributed-sample/var/app3/system1.script (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,4 @@ +CREATE SCHEMA PUBLIC AUTHORIZATION DBA +CREATE USER SA PASSWORD "" +GRANT DBA TO SA +SET WRITE_DELAY 10 Index: 3rdParty_sources/tacitknowledge/autopatch/examples/distributed-sample/var/app3/system2.properties =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/examples/distributed-sample/var/app3/system2.properties (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/examples/distributed-sample/var/app3/system2.properties (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,17 @@ +#HSQL Database Engine 1.8.0.7 +#Wed Feb 21 16:08:47 PST 2007 +hsqldb.script_format=0 +runtime.gc_interval=0 +sql.enforce_strict_size=false +hsqldb.cache_size_scale=8 +readonly=false +hsqldb.nio_data_file=true +hsqldb.cache_scale=14 +version=1.8.0 +hsqldb.default_table_type=memory +hsqldb.cache_file_scale=1 +hsqldb.log_size=200 +modified=yes +hsqldb.cache_version=1.7.0 +hsqldb.original_version=1.8.0 +hsqldb.compatible_version=1.8.0 Index: 3rdParty_sources/tacitknowledge/autopatch/examples/distributed-sample/var/app3/system2.script =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/examples/distributed-sample/var/app3/system2.script (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/examples/distributed-sample/var/app3/system2.script (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,4 @@ +CREATE SCHEMA PUBLIC AUTHORIZATION DBA +CREATE USER SA PASSWORD "" +GRANT DBA TO SA +SET WRITE_DELAY 10 Index: 3rdParty_sources/tacitknowledge/autopatch/examples/distributed-sample/var/orchestrator.properties =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/examples/distributed-sample/var/orchestrator.properties (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/examples/distributed-sample/var/orchestrator.properties (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,17 @@ +#HSQL Database Engine 1.8.0.7 +#Wed Mar 07 11:47:15 PST 2007 +hsqldb.script_format=0 +runtime.gc_interval=0 +sql.enforce_strict_size=false +hsqldb.cache_size_scale=8 +readonly=false +hsqldb.nio_data_file=true +hsqldb.cache_scale=14 +version=1.8.0 +hsqldb.default_table_type=memory +hsqldb.cache_file_scale=1 +hsqldb.log_size=200 +modified=yes +hsqldb.cache_version=1.7.0 +hsqldb.original_version=1.8.0 +hsqldb.compatible_version=1.8.0 Index: 3rdParty_sources/tacitknowledge/autopatch/examples/distributed-sample/var/orchestrator.script =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/examples/distributed-sample/var/orchestrator.script (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/examples/distributed-sample/var/orchestrator.script (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,4 @@ +CREATE SCHEMA PUBLIC AUTHORIZATION DBA +CREATE USER SA PASSWORD "" +GRANT DBA TO SA +SET WRITE_DELAY 10 Index: 3rdParty_sources/tacitknowledge/autopatch/maven-eclipse.xml =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/maven-eclipse.xml (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/maven-eclipse.xml (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file Index: 3rdParty_sources/tacitknowledge/autopatch/migration.properties =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/migration.properties (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/migration.properties (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,42 @@ +# +# Which context do we use for the orchestration patch store? +# +orchestration.context=core +orchestration.controlled.systems=core,orders,catalog +orchestration.lockPollRetries=10 +# +# Configure a context named "core" +# +core.jdbc.database.type=postgres +core.jdbc.driver=org.postgresql.Driver +core.jdbc.url=jdbc:postgresql://localhost/core +core.jdbc.username=core +core.jdbc.password=password +core.patch.path=patches.core +# +# Configure a context named "orders" +# +orders.jdbc.database.type=postgres +orders.jdbc.driver=org.postgresql.Driver +orders.jdbc.url=jdbc:postgresql://localhost/orders +orders.jdbc.username=orders +orders.jdbc.password=password +orders.patch.path=patches.orders +# +# Configure a context named catalog +# +catalog.jdbc.database.type=postgres +catalog.jdbc.driver=org.postgresql.Driver +catalog.jdbc.url=jdbc:postgresql://localhost/catalog +catalog.jdbc.username=catalog +catalog.jdbc.password=password +catalog.patch.path=patches.catalog +# +# Configure a spikebook context +# +spikebook.jdbc.database.type=postgres +spikebook.jdbc.driver=org.postgresql.Driver +spikebook.jdbc.url=jdbc:postgresql://localhost/spikebook +spikebook.jdbc.username=spikebook +spikebook.jdbc.password=dbspikeb00k +spikebook.patch.path=patches:com.tacitknowledge.spikebook.patches Index: 3rdParty_sources/tacitknowledge/autopatch/pom.xml =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/pom.xml (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/pom.xml (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,427 @@ + + + 4.0.0 + com.tacitknowledge + autopatch + 1.4.2 + jar + + + com.tacitknowledge + oss-parent + 1 + + + AutoPatch + An automated Java patching system + https://github.com/tacitknowledge/autopatch + + + Tacit Knowledge + http://www.tacitknowledge.com/ + + + 2004 + + + 1.6 + 1.6 + **/*Test.java + + **/MigrationUnlockTest.java,**/MultiNodeAutoPatchTest.java,**/MissingPatchMigrationRunnerStrategyIntegrationTest.java,**/MultiServerRaceConditionTest.java + + + + + + Apache 2 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + Github Issue Tracker + https://github.com/tacitknowledge/autopatch/issues + + + + scm:git:https://github.com/tacitknowledge/autopatch.git + scm:git:git@github.com:tacitknowledge/autopatch.git + https://github.com/tacitknowledge/autopatch + + + + + scottfromsf + Scott Askew + scott at tacitknowledge.com + Tacit Knowledge + + + mikehardy + Mike Hardy + mikehardy at tacitknowledge.com + Tacit Knowledge + + + + + + Alexandru Croitor + + + Alex Soto + + + David Martinez + + + Ian Mortimer + + + Marek Gilbert + + + Vladislav Gangan + + + Vladimir Pertu + + + Marques Lee + + + Ulises Pulido + + + Hemri Herrera + + + + + + + + ${basedir}/src/test/resources + + *.properties + **/*.sql + + + + + ${basedir}/src/integration-test/resources + + *.properties + **/*.sql + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 2.5 + + ${source.jdk} + ${target.jdk} + + + + + org.apache.maven.plugins + maven-checkstyle-plugin + 2.8 + + tacit_checkstyle.config + + + + + org.codehaus.mojo + cobertura-maven-plugin + 2.5.1 + + + + 0 + 0 + true + 64 + 67 + 30 + 42 + + xml + + + + + check + + + + + + + org.codehaus.mojo + build-helper-maven-plugin + 1.5 + + + add-test-source + generate-sources + + add-test-source + + + + ${basedir}/src/integration-test/java + + + + + + + + + maven-surefire-plugin + 2.3 + + true + + ${unit.tests.pattern} + + + ${integration.tests.pattern} + + false + + + + + + integration-test + integration-test + + test + + + + ${basedir}/src/integration-test/java + + none + + + ${integration.tests.pattern} + + false + + + + + + + org.apache.maven.plugins + maven-jar-plugin + 2.1 + + + + true + true + + + + + + org.apache.maven.plugins + maven-shade-plugin + 1.4 + + + package + + shade + + + true + shaded + + + com.tacitknowledge.util.migration.jdbc.StandaloneMigrationLauncher + + + + + + + + + + + + + + com.tacitknowledge + discovery + [1.0,) + compile + + + + commons-lang + commons-lang + [2.0,) + compile + + + + commons-logging + commons-logging + 1.1 + compile + + + + commons-collections + commons-collections + 3.0 + compile + + + + junit + junit + 3.8.1 + test + + + + junit-addons + junit-addons + 1.4 + test + + + + log4j + log4j + 1.2.9 + compile + + + + org.dbunit + dbunit + [2.2,) + compile + + + + poi + poi + 2.5.1-final-20040804 + compile + + + + commons-beanutils + commons-beanutils + 1.6.1 + test + + + + org.mockejb + mockejb + 0.6-beta2 + test + + + + com.mockrunner + mockrunner + 0.3.1 + test + + + + + struts + struts + 1.2.2 + test + + + + + commons-digester + commons-digester + 1.5 + test + + + + javax.transaction + jta + 1.1 + test + + + + geronimo-spec + geronimo-spec-jms + 1.1-rc4 + test + + + geronimo-spec + geronimo-spec-ejb + 2.1-rc4 + test + + + + org.easymock + easymockclassextension + 2.4 + test + + + + org.hsqldb + hsqldb + 2.2.8 + test + + + + org.picocontainer + picocontainer + 2.14.1 + + + + + + + + org.apache.maven.plugins + maven-checkstyle-plugin + 2.8 + + tacit_checkstyle.config + + + + + + + Index: 3rdParty_sources/tacitknowledge/autopatch/src/integration-test/java/com/tacitknowledge/util/migration/AutoPatchIntegrationTestBase.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/integration-test/java/com/tacitknowledge/util/migration/AutoPatchIntegrationTestBase.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/integration-test/java/com/tacitknowledge/util/migration/AutoPatchIntegrationTestBase.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,190 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration; + +import java.sql.*; + +import com.tacitknowledge.util.migration.jdbc.DistributedJdbcMigrationLauncher; +import com.tacitknowledge.util.migration.jdbc.DistributedJdbcMigrationLauncherFactory; +import com.tacitknowledge.util.migration.jdbc.JdbcMigrationLauncher; +import com.tacitknowledge.util.migration.jdbc.JdbcMigrationLauncherFactory; + +import com.tacitknowledge.util.migration.jdbc.util.SqlUtil; +import junit.framework.TestCase; + +/** + * Superclass for other integration test cases. Sets up a test database etc. + * + * @author Mike Hardy (mike@tacitknowledge.com) + */ +public abstract class AutoPatchIntegrationTestBase extends TestCase +{ + /** The DistributedLauncher we're testing */ + private DistributedJdbcMigrationLauncher distributedLauncher = null; + + /** A regular launcher we can test */ + private JdbcMigrationLauncher launcher = null; + + /** A multi-node launcher we can test */ + private JdbcMigrationLauncher multiNodeLauncher = null; + + /** + * Constructor + * + * @param name the name of the test to run + */ + public AutoPatchIntegrationTestBase(String name) + { + super(name); + } + + /** + * Sets up a test database + * + * @exception Exception if anything goes wrong + */ + public void setUp() throws Exception + { + super.setUp(); + DistributedJdbcMigrationLauncherFactory dlFactory = + new DistributedJdbcMigrationLauncherFactory(); + distributedLauncher = + (DistributedJdbcMigrationLauncher) + dlFactory.createMigrationLauncher("integration_test", + "inttest-migration.properties"); + + JdbcMigrationLauncherFactory lFactory = new JdbcMigrationLauncherFactory(); + launcher = lFactory.createMigrationLauncher("orders", "inttest-migration.properties"); + multiNodeLauncher = lFactory.createMigrationLauncher("catalog", + "inttest-migration.properties"); + } + + /** + * Tears down the test database + * + * @exception Exception if anything goes wrong + */ + public void tearDown() throws Exception + { + super.tearDown(); + destroyDatabase("core"); + destroyDatabase("orders"); + destroyDatabase("catalog1"); + destroyDatabase("catalog2"); + destroyDatabase("catalog3"); + destroyDatabase("catalog4"); + destroyDatabase("catalog5"); + destroyDatabase("nodes"); + destroyDatabase("node1"); + destroyDatabase("node2"); + destroyDatabase("node3"); + } + + /** + * Destroys a database so a future test can use it + * + * @param database the name of the database to destroy + * @exception Exception if anything goes wrong + */ + protected void destroyDatabase(String database) throws Exception + { + Connection conn = DriverManager.getConnection("jdbc:hsqldb:mem:" + database, "sa", ""); + Statement stmt = conn.createStatement(); + stmt.execute("SHUTDOWN"); + } + + /** + * Get the DistributedLauncher under test + * + * @return DistributedJdbcMigrationLauncher + */ + public DistributedJdbcMigrationLauncher getDistributedLauncher() + { + return distributedLauncher; + } + + /** + * Set the DistributedJdbcMigrationLauncher to test + * + * @param distributedLauncher the launcher to test for distributed functionality + */ + public void setDistributedLauncher(DistributedJdbcMigrationLauncher distributedLauncher) + { + this.distributedLauncher = distributedLauncher; + } + + /** + * Get the JdbcMigrationLauncher to test + * + * @return JdbcMigrationLauncher to test + */ + public JdbcMigrationLauncher getLauncher() + { + return launcher; + } + + /** + * Set the JdbcMigrationLauncher to test + * + * @param launcher the launcher to test for core functionality + */ + public void setLauncher(JdbcMigrationLauncher launcher) + { + this.launcher = launcher; + } + + /** + * Get the JdbcMigrationLauncher to test for multi-node functionality + * + * @return JdbcMigrationLauncher configured for multi-node + */ + public JdbcMigrationLauncher getMultiNodeLauncher() + { + return multiNodeLauncher; + } + + /** + * Set the JdbcMigrationLauncher to use for multi-node functional testing + * + * @param multiNodeLauncher the launcher to test for multi-node functionality + */ + public void setMultiNodeLauncher(JdbcMigrationLauncher multiNodeLauncher) + { + this.multiNodeLauncher = multiNodeLauncher; + } + + /** + * Get the patch level for a given database + * + * @param conn the database connection to use + * @param system + * @return int representing the patch level + * @exception Exception if getting the patch level fails + */ + protected int getPatchLevel(Connection conn, String system) throws Exception + { + Statement stmt = conn.createStatement(); + ResultSet rs = stmt.executeQuery("SELECT MAX(patch_level) AS patch_level FROM patches WHERE system_name='" + system + "'"); + rs.next(); + int patchLevel = rs.getInt("patch_level"); + SqlUtil.close(null, stmt, rs); + return patchLevel; + } + + protected Connection getOrderConnection() throws SQLException { + return DriverManager.getConnection("jdbc:hsqldb:mem:orders", "sa", ""); + } +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/integration-test/java/com/tacitknowledge/util/migration/MigrationUnlockTest.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/integration-test/java/com/tacitknowledge/util/migration/MigrationUnlockTest.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/integration-test/java/com/tacitknowledge/util/migration/MigrationUnlockTest.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,109 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration; + +import java.sql.*; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import com.tacitknowledge.util.migration.jdbc.MigrationTableUnlock; +import com.tacitknowledge.util.migration.jdbc.util.SqlUtil; + +/** + * Test AutoPatch MultiNode functionality + * + * @author Mike Hardy (mike@tacitknowledge.com) + */ +public class MigrationUnlockTest extends AutoPatchIntegrationTestBase +{ + /** Class logger */ + private static Log log = LogFactory.getLog(MigrationUnlockTest.class); + + /** + * Constructor + * + * @param name the name of the test to run + */ + public MigrationUnlockTest(String name) + { + super(name); + } + + /** + * Test that all the tables were created successfully in all of the databases + * + * @exception Exception if anything goes wrong + */ + public void testMigrationUnlock() throws Exception + { + log.debug("Testing forcible migration table unlocking"); + try + { + getLauncher().doMigrations(); + } + catch (Exception e) + { + log.error("Unexpected error", e); + fail("shouldn't have thrown any exceptions"); + } + + // now we should have a patch table. Let's lock it. + lockPatchTable("orders"); + + // now lets try to unlock it + MigrationTableUnlock unlocker = new MigrationTableUnlock(); + unlocker.tableUnlock("orders"); + + // Now let's see if it worked + verifyPatchTableNotLocked("orders"); + } + + /** + * Lock the patch table for a database + * + * @param database the database name to lock + * @exception Exception if getting the patch level fails + */ + private void lockPatchTable(String database) throws Exception + { + Connection conn = DriverManager.getConnection("jdbc:hsqldb:mem:" + database, "sa", ""); + + PreparedStatement stmt = conn.prepareStatement("UPDATE patches SET patch_in_progress = 'T' WHERE patch_level in ( SELECT MAX(patch_level) FROM patches WHERE system_name = ?)"); + stmt.setString(1, database); + int rowCount = stmt.executeUpdate(); + assertEquals(1, rowCount); + SqlUtil.close(conn, stmt, null); + } + + /** + * Verify that there is no lock in the patch table for a database + * + * @param database the database name to verify + * @exception Exception if getting the patch level fails + */ + private void verifyPatchTableNotLocked(String database) throws Exception + { + Connection conn = DriverManager.getConnection("jdbc:hsqldb:mem:" + database, "sa", ""); + PreparedStatement stmt = conn.prepareStatement("SELECT patch_in_progress FROM patches WHERE system_name = ? AND ( patch_in_progress <> 'F' OR patch_level in ( SELECT MAX(patch_level) FROM patches WHERE system_name = ? ))"); + stmt.setString(1, database); + stmt.setString(2, database); + ResultSet rs = stmt.executeQuery(); + rs.next(); + assertEquals("F", rs.getString("patch_in_progress")); + SqlUtil.close(conn, stmt, rs); + } +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/integration-test/java/com/tacitknowledge/util/migration/MissingPatchMigrationRunnerStrategyIntegrationTest.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/integration-test/java/com/tacitknowledge/util/migration/MissingPatchMigrationRunnerStrategyIntegrationTest.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/integration-test/java/com/tacitknowledge/util/migration/MissingPatchMigrationRunnerStrategyIntegrationTest.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,269 @@ +/* Copyright 2011 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tacitknowledge.util.migration; + +import com.tacitknowledge.util.migration.jdbc.DistributedJdbcMigrationLauncher; +import com.tacitknowledge.util.migration.jdbc.DistributedJdbcMigrationLauncherFactory; +import com.tacitknowledge.util.migration.jdbc.JdbcMigrationLauncher; +import com.tacitknowledge.util.migration.jdbc.JdbcMigrationLauncherFactory; +import com.tacitknowledge.util.migration.jdbc.util.SqlUtil; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import java.sql.*; + +/* + * @author Hemri Herrera (hemri@tacitknowledge.com) + * @author Ulises Pulido (upulido@tacitknowledge.com) + */ +public class MissingPatchMigrationRunnerStrategyIntegrationTest extends AutoPatchIntegrationTestBase { + + /** + * Class logger + */ + private static Log log = LogFactory.getLog(MissingPatchMigrationRunnerStrategyIntegrationTest.class); + private static final String ORDERS = "orders"; + + /** + * Constructor + * + * @param name the name of the test to run + */ + public MissingPatchMigrationRunnerStrategyIntegrationTest(String name) { + super(name); + } + + public void setUp() throws Exception { + } + + public void testMigrationRunsFromBatches() throws Exception { + + try { + JdbcMigrationLauncherFactory lFactory = new JdbcMigrationLauncherFactory(); + log.debug("Testing migration from batch 1"); + JdbcMigrationLauncher launcherBatch1 = lFactory.createMigrationLauncher(ORDERS, "missingpatchstrategybatch1-inttest-migration.properties"); + launcherBatch1.doMigrations(); + + assertEquals(4, getPatchLevel(getOrderConnection(), ORDERS)); + assertTrue(isPatchApplied(getOrderConnection(), 1, ORDERS)); + assertTrue(isPatchApplied(getOrderConnection(), 4, ORDERS)); + assertFalse(isPatchApplied(getOrderConnection(), 2, ORDERS)); + assertFalse(isPatchApplied(getOrderConnection(), 3, ORDERS)); + + log.debug("Testing migration from batch 2"); + JdbcMigrationLauncher launcherBatch2 = lFactory.createMigrationLauncher(ORDERS, "missingpatchstrategybatch2-inttest-migration.properties"); + launcherBatch2.doMigrations(); + currentPatches(ORDERS); + + assertEquals(4, getPatchLevel(getOrderConnection(), ORDERS)); + assertTrue(isPatchApplied(getOrderConnection(), 1, ORDERS)); + assertTrue(isPatchApplied(getOrderConnection(), 3, ORDERS)); + assertTrue(isPatchApplied(getOrderConnection(), 4, ORDERS)); + assertTrue(isPatchApplied(getOrderConnection(), 2, ORDERS)); + + } catch (Exception e) { + log.error("Unexpected error", e); + fail("shouldn't have thrown any exceptions"); + } + + } + + public void testMigrationRunsFromTwoBatchesOnMultipleNodes() throws Exception, SQLException { + DistributedJdbcMigrationLauncherFactory dlFactory = + new DistributedJdbcMigrationLauncherFactory(); + + DistributedJdbcMigrationLauncher distributedLauncher1 = (DistributedJdbcMigrationLauncher) + dlFactory.createMigrationLauncher("nodes", + "missingpatchstrategybatch1-inttest-migration.properties"); + + distributedLauncher1.doMigrations(); + + Connection node1Conn = DriverManager.getConnection("jdbc:hsqldb:mem:node1", "sa", ""); + Connection node2Conn = DriverManager.getConnection("jdbc:hsqldb:mem:node2", "sa", ""); + + assertEquals(4, getPatchLevel(node1Conn, "nodes")); + assertTrue(isPatchApplied(node1Conn, 1, "nodes")); + assertTrue(isPatchApplied(node1Conn, 4, "nodes")); + assertFalse(isPatchApplied(node1Conn, 2, "nodes")); + assertFalse(isPatchApplied(node1Conn, 3, "nodes")); + + + assertEquals(4, getPatchLevel(node2Conn, "nodes")); + assertTrue(isPatchApplied(node2Conn, 1, "nodes")); + assertTrue(isPatchApplied(node2Conn, 4, "nodes")); + assertFalse(isPatchApplied(node2Conn, 2, "nodes")); + assertFalse(isPatchApplied(node2Conn, 3, "nodes")); + + + DistributedJdbcMigrationLauncher distributedLauncher2 = (DistributedJdbcMigrationLauncher) + dlFactory.createMigrationLauncher("nodes", + "missingpatchstrategybatch2-inttest-migration.properties"); + + distributedLauncher2.doMigrations(); + + assertEquals(4, getPatchLevel(node1Conn, "nodes")); + assertTrue(isPatchApplied(node1Conn, 1, "nodes")); + assertTrue(isPatchApplied(node1Conn, 4, "nodes")); + assertTrue(isPatchApplied(node1Conn, 2, "nodes")); + assertTrue(isPatchApplied(node1Conn, 3, "nodes")); + + + assertEquals(4, getPatchLevel(node2Conn, "nodes")); + assertTrue(isPatchApplied(node2Conn, 1, "nodes")); + assertTrue(isPatchApplied(node2Conn, 4, "nodes")); + assertTrue(isPatchApplied(node2Conn, 2, "nodes")); + assertTrue(isPatchApplied(node2Conn, 3, "nodes")); + + + SqlUtil.close(node1Conn, null, null); + SqlUtil.close(node2Conn, null, null); + + + } + + public void testMigrationRunsFromMultipleBatchesOnMultipleNodesWithForcedSync() throws Exception, SQLException { + + + testMigrationRunsFromTwoBatchesOnMultipleNodes( ); + + DistributedJdbcMigrationLauncherFactory dlFactory = + new DistributedJdbcMigrationLauncherFactory(); + + DistributedJdbcMigrationLauncher distributedLauncher3 = (DistributedJdbcMigrationLauncher) + dlFactory.createMigrationLauncher("nodes", + "missingpatchstrategybatch3-inttest-migration.properties"); + DistributedMigrationProcess distributedMigrationProcess3 = (DistributedMigrationProcess) distributedLauncher3.getMigrationProcess(); + distributedMigrationProcess3.setForceSync(true); + distributedLauncher3.doMigrations(); + + Connection node1Conn = DriverManager.getConnection("jdbc:hsqldb:mem:node1", "sa", ""); + Connection node2Conn = DriverManager.getConnection("jdbc:hsqldb:mem:node2", "sa", ""); + Connection node3Conn = DriverManager.getConnection("jdbc:hsqldb:mem:node3", "sa", ""); + + + assertEquals(4, getPatchLevel(node1Conn, "nodes")); + assertTrue(isPatchApplied(node1Conn, 1, "nodes")); + assertTrue(isPatchApplied(node1Conn, 4, "nodes")); + assertTrue(isPatchApplied(node1Conn, 2, "nodes")); + assertTrue(isPatchApplied(node1Conn, 3, "nodes")); + + + assertEquals(4, getPatchLevel(node2Conn, "nodes")); + assertTrue(isPatchApplied(node2Conn, 1, "nodes")); + assertTrue(isPatchApplied(node2Conn, 4, "nodes")); + assertTrue(isPatchApplied(node2Conn, 2, "nodes")); + assertTrue(isPatchApplied(node2Conn, 3, "nodes")); + + assertEquals(4, getPatchLevel(node3Conn, "nodes")); + assertTrue(isPatchApplied(node3Conn, 1, "nodes")); + assertTrue(isPatchApplied(node3Conn, 4, "nodes")); + assertFalse(isPatchApplied(node3Conn, 2, "nodes")); + assertFalse(isPatchApplied(node3Conn, 3, "nodes")); + + DistributedJdbcMigrationLauncher distributedLauncher4 = (DistributedJdbcMigrationLauncher) + dlFactory.createMigrationLauncher("nodes", + "missingpatchstrategybatch4-inttest-migration.properties"); + + DistributedMigrationProcess distributedMigrationProcess4 = (DistributedMigrationProcess) distributedLauncher4.getMigrationProcess(); + distributedMigrationProcess4.setForceSync(true); + distributedLauncher4.doMigrations(); + + + assertEquals(4, getPatchLevel(node1Conn, "nodes")); + assertTrue(isPatchApplied(node1Conn, 1, "nodes")); + assertTrue(isPatchApplied(node1Conn, 4, "nodes")); + assertTrue(isPatchApplied(node1Conn, 2, "nodes")); + assertTrue(isPatchApplied(node1Conn, 3, "nodes")); + + + assertEquals(4, getPatchLevel(node2Conn, "nodes")); + assertTrue(isPatchApplied(node2Conn, 1, "nodes")); + assertTrue(isPatchApplied(node2Conn, 4, "nodes")); + assertTrue(isPatchApplied(node2Conn, 2, "nodes")); + assertTrue(isPatchApplied(node2Conn, 3, "nodes")); + + assertEquals(4, getPatchLevel(node3Conn, "nodes")); + assertTrue(isPatchApplied(node3Conn, 1, "nodes")); + assertTrue(isPatchApplied(node3Conn, 4, "nodes")); + assertTrue(isPatchApplied(node3Conn, 2, "nodes")); + assertTrue(isPatchApplied(node3Conn, 3, "nodes")); + + + SqlUtil.close(node1Conn, null, null); + SqlUtil.close(node2Conn, null, null); + SqlUtil.close(node3Conn, null, null); + + + } + + private void currentPatches(String database) throws Exception { + Connection conn = DriverManager.getConnection("jdbc:hsqldb:mem:orders", "sa", ""); + Statement stmt = conn.createStatement(); + ResultSet resultSet = stmt.executeQuery("SELECT * FROM patches"); + while (resultSet.next()) { + log.info("Result " + resultSet.getInt(2)); + } + SqlUtil.close(conn, stmt, null); + + } + + private boolean isPatchApplied(Connection conn, int patch_level, String system) throws Exception { + + Statement stmt = conn.createStatement(); + String sql = "SELECT patch_level FROM patches WHERE system_name= '" + system + "' and patch_level=" + patch_level + ""; + ResultSet rs = stmt.executeQuery(sql); + return rs.next(); + } + + public void testRollbacksRunOnMultipleNode() throws Exception { + + //Applying migrations to node1 and node2 + testMigrationRunsFromTwoBatchesOnMultipleNodes(); + + DistributedJdbcMigrationLauncherFactory dlFactory = + new DistributedJdbcMigrationLauncherFactory(); + + DistributedJdbcMigrationLauncher distributedLauncher2 = (DistributedJdbcMigrationLauncher) + dlFactory.createMigrationLauncher("nodes", + "missingpatchstrategybatch2-inttest-migration.properties"); + + int[] rollbackLevels = new int[]{ 2, 3 }; + + distributedLauncher2.doRollbacks(rollbackLevels); + + Connection node1Conn = DriverManager.getConnection("jdbc:hsqldb:mem:node1", "sa", ""); + Connection node2Conn = DriverManager.getConnection("jdbc:hsqldb:mem:node2", "sa", ""); + + + assertEquals(4, getPatchLevel(node1Conn, "nodes")); + assertTrue(isPatchApplied(node1Conn, 1, "nodes")); + assertTrue(isPatchApplied(node1Conn, 4, "nodes")); + assertFalse(isPatchApplied(node1Conn, 2, "nodes")); + assertFalse(isPatchApplied(node1Conn, 3, "nodes")); + + + assertEquals(4, getPatchLevel(node2Conn, "nodes")); + assertTrue(isPatchApplied(node2Conn, 1, "nodes")); + assertTrue(isPatchApplied(node2Conn, 4, "nodes")); + assertFalse(isPatchApplied(node2Conn, 2, "nodes")); + assertFalse(isPatchApplied(node2Conn, 3, "nodes")); + + + SqlUtil.close(node1Conn, null, null); + SqlUtil.close(node2Conn, null, null); + + } + +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/integration-test/java/com/tacitknowledge/util/migration/MultiNodeAutoPatchTest.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/integration-test/java/com/tacitknowledge/util/migration/MultiNodeAutoPatchTest.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/integration-test/java/com/tacitknowledge/util/migration/MultiNodeAutoPatchTest.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,289 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration; + +import java.sql.*; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import com.tacitknowledge.util.migration.jdbc.DistributedJdbcMigrationLauncher; +import com.tacitknowledge.util.migration.jdbc.DistributedJdbcMigrationLauncherFactory; +import com.tacitknowledge.util.migration.jdbc.util.SqlUtil; + +/** + * Test AutoPatch MultiNode functionality + * + * @author Mike Hardy (mike@tacitknowledge.com) + */ +public class MultiNodeAutoPatchTest extends AutoPatchIntegrationTestBase +{ + /** Class logger */ + private static Log log = LogFactory.getLog(MultiNodeAutoPatchTest.class); + private static final String CATALOG = "catalog"; + private static final String ORDERS = "orders"; + private static final String CORE = "core"; + + /** + * Constructor + * + * @param name the name of the test to run + */ + public MultiNodeAutoPatchTest(String name) + { + super(name); + } + /** + * Test that all the tables were created successfully in all of the databases + * + * @exception Exception if anything goes wrong + */ + public void testMultiNodePatch() throws Exception + { + log.debug("Testing multi node patching"); + try + { + getDistributedLauncher().doMigrations(); + } + catch (Exception e) + { + log.error("Unexpected error", e); + fail("shouldn't have thrown any exceptions"); + } + + // Make sure everything worked out okay + Connection core = DriverManager.getConnection("jdbc:hsqldb:mem:core", "sa", ""); + Connection orders = getOrderConnection(); + Connection catalog1 = DriverManager.getConnection("jdbc:hsqldb:mem:catalog1", "sa", ""); + Connection catalog2 = DriverManager.getConnection("jdbc:hsqldb:mem:catalog2", "sa", ""); + Connection catalog3 = DriverManager.getConnection("jdbc:hsqldb:mem:catalog3", "sa", ""); + + // 4 patches should have executed + assertEquals(4, getPatchLevel(core, CORE)); + assertEquals(4, getPatchLevel(orders, ORDERS)); + assertEquals(4, getPatchLevel(catalog1, CATALOG)); + assertEquals(4, getPatchLevel(catalog2, CATALOG)); + assertEquals(4, getPatchLevel(catalog3, CATALOG)); + + + // we should have test values in each table + verifyTestTable(core, "core_table_1"); + verifyTestTable(orders, "order_table_1"); + verifyTestTable(orders, "order_table_2"); + verifyTestTable(catalog1, "catalog_table_1"); + verifyTestTable(catalog2, "catalog_table_1"); + verifyTestTable(catalog3, "catalog_table_1"); + + SqlUtil.close(core, null, null); + SqlUtil.close(orders, null, null); + SqlUtil.close(catalog1, null, null); + SqlUtil.close(catalog2, null, null); + SqlUtil.close(catalog3, null, null); + } + + /** + * Test that all the tables were created successfully in all of the databases and then test + * the rollback of the final task. + * + * @exception Exception if anything goes wrong + */ + public void testMultiNodeRollback() throws Exception + { + log.debug("Testing multi node rollback"); + try + { + getDistributedLauncher().doMigrations(); + } + catch (Exception e) + { + log.error("Unexpected error", e); + fail("shouldn't have thrown any exceptions"); + } + + // Make sure everything worked out okay + Connection core = DriverManager.getConnection("jdbc:hsqldb:mem:core", "sa", ""); + Connection orders = getOrderConnection(); + Connection catalog1 = DriverManager.getConnection("jdbc:hsqldb:mem:catalog1", "sa", ""); + Connection catalog2 = DriverManager.getConnection("jdbc:hsqldb:mem:catalog2", "sa", ""); + Connection catalog3 = DriverManager.getConnection("jdbc:hsqldb:mem:catalog3", "sa", ""); + + // 4 patches should have executed + assertEquals(4, getPatchLevel(core, CORE)); + assertEquals(4, getPatchLevel(orders, ORDERS)); + assertEquals(4, getPatchLevel(catalog1, CATALOG)); + assertEquals(4, getPatchLevel(catalog2, CATALOG)); + assertEquals(4, getPatchLevel(catalog3, CATALOG)); + + + // we should have test values in each table + verifyTestTable(core, "core_table_1"); + verifyTestTable(orders, "order_table_1"); + verifyTestTable(orders, "order_table_2"); + verifyTestTable(catalog1, "catalog_table_1"); + verifyTestTable(catalog2, "catalog_table_1"); + verifyTestTable(catalog3, "catalog_table_1"); + + try + { + getDistributedLauncher().doRollbacks(3); + } catch(Exception e) + { + log.error("Unexpected error", e); + fail("should not have received any exceptions"); + } + + // 4 patches should have executed + assertEquals(3, getPatchLevel(core, CORE)); + assertEquals(3, getPatchLevel(orders, ORDERS)); + assertEquals(3, getPatchLevel(catalog1, CATALOG)); + assertEquals(3, getPatchLevel(catalog2, CATALOG)); + assertEquals(3, getPatchLevel(catalog3, CATALOG)); + + SqlUtil.close(core, null, null); + SqlUtil.close(orders, null, null); + SqlUtil.close(catalog1, null, null); + SqlUtil.close(catalog2, null, null); + SqlUtil.close(catalog3, null, null); + } + + /** + * Tests that a when there is a node that is out of sync with the patch level, autopatch + * detects the node and fails with an error reporting the problem. + * @throws Exception + */ + public void testMultiNodePatchDetectsOutOfSyncNode() throws Exception + { + // run the base multinode patch to bring the databases up to date + getDistributedLauncher().doMigrations(); + + Connection core = DriverManager.getConnection("jdbc:hsqldb:mem:core", "sa", ""); + Connection orders = getOrderConnection(); + Connection catalog1 = DriverManager.getConnection("jdbc:hsqldb:mem:catalog1", "sa", ""); + Connection catalog2 = DriverManager.getConnection("jdbc:hsqldb:mem:catalog2", "sa", ""); + Connection catalog3 = DriverManager.getConnection("jdbc:hsqldb:mem:catalog3", "sa", ""); + Connection catalog4 = DriverManager.getConnection("jdbc:hsqldb:mem:catalog4", "sa", ""); + + // make sure databases are in the state we expect + assertEquals(4, getPatchLevel(core, CORE)); + assertEquals(4, getPatchLevel(orders, ORDERS)); + assertEquals(4, getPatchLevel(catalog1, CATALOG)); + assertEquals(4, getPatchLevel(catalog2, CATALOG)); + assertEquals(4, getPatchLevel(catalog3, CATALOG)); + try + { + getPatchLevel(catalog4, CATALOG); + fail("patches table should not exist at this point"); + } + catch (Exception e) + { + // expected + } + + // create a new migration launcher with a migration.properties that + // specifies a new node + DistributedJdbcMigrationLauncherFactory dlFactory = new DistributedJdbcMigrationLauncherFactory(); + DistributedJdbcMigrationLauncher nodeAddedDistributedLauncher = + (DistributedJdbcMigrationLauncher) + dlFactory.createMigrationLauncher("integration_test", + "node-added-inttest-migration.properties"); + + // run the migrations and expect a MigrationException to be raised. + try + { + nodeAddedDistributedLauncher.doMigrations(); + + fail("should have thrown an exception"); + } + catch (MigrationException e) + { + // expected + } + + // no new patches should have executed + assertEquals(4, getPatchLevel(core, CORE)); + assertEquals(4, getPatchLevel(orders, ORDERS)); + assertEquals(4, getPatchLevel(catalog1, CATALOG)); + assertEquals(4, getPatchLevel(catalog2, CATALOG)); + assertEquals(4, getPatchLevel(catalog3, CATALOG)); + assertEquals(0, getPatchLevel(catalog4, CATALOG)); + + SqlUtil.close(core, null, null); + SqlUtil.close(orders, null, null); + SqlUtil.close(catalog1, null, null); + SqlUtil.close(catalog2, null, null); + SqlUtil.close(catalog3, null, null); + SqlUtil.close(catalog4, null, null); + + } + + /** + * Tests that when there is a node that is out of sync with the system's patch level, + * autopatch will detect and patch the node if the flag to force syncing of nodes is set. + */ + public void testMultiNodePatchesOutOfSyncNode() throws Exception + { + // run the base multinode patch to bring the databases up to date + testMultiNodePatch(); + + // create a new migration launcher with a migration.properties that + // specifies a new node + DistributedJdbcMigrationLauncherFactory dlFactory = + new DistributedJdbcMigrationLauncherFactory(); + DistributedJdbcMigrationLauncher nodeAddedDistributedLauncher = + (DistributedJdbcMigrationLauncher) + dlFactory.createMigrationLauncher("integration_test", + "node-added-inttest-migration.properties"); + DistributedMigrationProcess migrationProcess = + (DistributedMigrationProcess) nodeAddedDistributedLauncher.getMigrationProcess(); + migrationProcess.setForceSync(true); + + // run the migrations and no MigrationException should be raised. + nodeAddedDistributedLauncher.doMigrations(); + + // Make sure everything worked out okay + Connection catalog4 = DriverManager.getConnection("jdbc:hsqldb:mem:catalog4", "sa", ""); + Connection catalog5 = DriverManager.getConnection("jdbc:hsqldb:mem:catalog5", "sa", ""); + + // 4 patches should have executed + assertEquals(4, getPatchLevel(catalog4, CATALOG)); + assertEquals(4, getPatchLevel(catalog5, CATALOG)); + + + // we should have test values in each table + verifyTestTable(catalog4, "catalog_table_1"); + verifyTestTable(catalog5, "catalog_table_1"); + + SqlUtil.close(catalog4, null, null); + SqlUtil.close(catalog5, null, null); + } + + + /** + * Verify that a given table exists and that it contains one row + * with a value equal to the name of the table (this matches the inttest patches) + * + * @param conn the Connection to use + * @param tableName the table name (and row value) to look for + * @exception Exception if anything goes wrong + */ + private void verifyTestTable(Connection conn, String tableName) throws Exception + { + Statement stmt = conn.createStatement(); + ResultSet rs = stmt.executeQuery("SELECT value FROM " + tableName); + rs.next(); + assertEquals(tableName, rs.getString("value")); + } +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/integration-test/java/com/tacitknowledge/util/migration/MultiServerRaceConditionTest.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/integration-test/java/com/tacitknowledge/util/migration/MultiServerRaceConditionTest.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/integration-test/java/com/tacitknowledge/util/migration/MultiServerRaceConditionTest.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,111 @@ +package com.tacitknowledge.util.migration; + +import java.sql.*; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import junit.framework.TestCase; + +import com.tacitknowledge.util.migration.MigrationContext; +import com.tacitknowledge.util.migration.jdbc.JdbcMigrationLauncher; +import com.tacitknowledge.util.migration.jdbc.JdbcMigrationLauncherFactory; + +/** + * Pin-points a race condition when the AutoPatcher is run from multiple servers on the same schema. + * + * @author Jeff Kolesky (jeff@kolesky.com) + */ +public class MultiServerRaceConditionTest extends TestCase +{ + private static Log log = LogFactory.getLog(MultiServerRaceConditionTest.class); + + /** + * Constructor + * + * @param name the name of the test to run + */ + public MultiServerRaceConditionTest(String name) + { + super(name); + } + + public void tearDown() throws Exception + { + super.tearDown(); + Connection conn = DriverManager.getConnection("jdbc:hsqldb:mem:race", "sa", ""); + Statement stmt = conn.createStatement(); + stmt.execute("SHUTDOWN"); + } + + /** + * Tests that two servers both booting up the AutoPatcher will not both try to apply the same patches + * + * @exception Exception if anything goes wrong + */ + public void testMultiServerRaceCondition() throws Exception + { + log.debug("Testing multi server race condition"); + MigrationThread a = new MigrationThread("A"); + MigrationThread b = new MigrationThread("B"); + + a.start(); + b.start(); + + boolean success = true; + + success &= finish(a); + success &= finish(b); + + if (!success) + { + fail("shouldn't have thrown any exceptions"); + } + } + + private boolean finish(MigrationThread t) throws Exception + { + t.join(); + Exception error = t.getError(); + if (error != null) + { + return false; + } + return true; + } + + private static class MigrationThread extends Thread + { + private JdbcMigrationLauncherFactory factory; + private JdbcMigrationLauncher launcher; + private Exception error; + + private MigrationThread(String name) throws Exception + { + super(name); + this.factory = new JdbcMigrationLauncherFactory(); + this.launcher = this.factory.createMigrationLauncher("race", "multiserver-inttest-migration.properties"); + // initialize the patch table *not* in parallel + this.launcher.getDatabasePatchLevel((MigrationContext)this.launcher.getContexts().keySet().iterator().next()); + } + + public Exception getError() + { + return this.error; + } + + public void run() + { + try + { + this.launcher.doMigrations(); + } + catch (Exception e) + { + this.error = e; + log.error("Unexpected error", this.error); + } + } + } +} + Index: 3rdParty_sources/tacitknowledge/autopatch/src/integration-test/java/com/tacitknowledge/util/migration/listeners/WhinyMigrationListener.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/integration-test/java/com/tacitknowledge/util/migration/listeners/WhinyMigrationListener.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/integration-test/java/com/tacitknowledge/util/migration/listeners/WhinyMigrationListener.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,59 @@ +package com.tacitknowledge.util.migration.listeners; + + +import com.tacitknowledge.util.migration.MigrationContext; +import com.tacitknowledge.util.migration.MigrationException; +import com.tacitknowledge.util.migration.MigrationListener; +import com.tacitknowledge.util.migration.MigrationTask; +import com.tacitknowledge.util.migration.jdbc.JdbcMigrationContext; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import java.util.Properties; + + +/** + * @author Alex Soto (apsoto@gmail.com) + */ +public class WhinyMigrationListener implements MigrationListener +{ + /** + * Class logger + */ + private static Log log = LogFactory.getLog(WhinyMigrationListener.class); + + protected String getTaskInfo(MigrationTask task, MigrationContext context) + { + String ctxInfo = ""; + if (context instanceof JdbcMigrationContext) + { + JdbcMigrationContext ctx = (JdbcMigrationContext) context; + ctxInfo += ctx.getSystemName() + " : " + ctx.getDatabaseName(); + } + + return "Task => (" + task.toString() + "), Context => (" + ctxInfo + ")"; + } + + public void migrationFailed(MigrationTask task, MigrationContext context, MigrationException e) throws MigrationException + { + log.debug("MIGRATION FAILED, " + getTaskInfo(task, context) + " WAHHH!!!"); + } + + public void migrationStarted(MigrationTask task, MigrationContext context) throws MigrationException + { + log.debug("MIGRATION STARTED, " + getTaskInfo(task, context) + " WAHHH!!!"); + } + + public void migrationSuccessful(MigrationTask task, MigrationContext context) throws MigrationException + { + log.debug("MIGRATION SUCCEEDED, " + getTaskInfo(task, context) + " WAHHH!!!"); + } + + /** + * @see com.tacitknowledge.util.migration.MigrationListener#initialize(Properties) + */ + public void initialize(String systemName, Properties properties) throws MigrationException + { + log.debug("MIGRATION LISTENER INTIALIZED FOR " + systemName + " SYSTEM, WAHHH!!!"); + } +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/integration-test/resources/com/tacitknowledge/util/migration/inttest-tasks/catalog/patch00004-rollback_patch04.sql =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/integration-test/resources/com/tacitknowledge/util/migration/inttest-tasks/catalog/patch00004-rollback_patch04.sql (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/integration-test/resources/com/tacitknowledge/util/migration/inttest-tasks/catalog/patch00004-rollback_patch04.sql (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1 @@ +DELETE FROM catalog_table_1 where id = 1; \ No newline at end of file Index: 3rdParty_sources/tacitknowledge/autopatch/src/integration-test/resources/com/tacitknowledge/util/migration/inttest-tasks/catalog/patch00004_patch04.sql =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/integration-test/resources/com/tacitknowledge/util/migration/inttest-tasks/catalog/patch00004_patch04.sql (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/integration-test/resources/com/tacitknowledge/util/migration/inttest-tasks/catalog/patch00004_patch04.sql (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,6 @@ +CREATE TABLE catalog_table_1 ( + id INT NOT NULL PRIMARY KEY, + value VARCHAR(256) +); + +INSERT INTO catalog_table_1 (id, value) VALUES (1, 'catalog_table_1'); \ No newline at end of file Index: 3rdParty_sources/tacitknowledge/autopatch/src/integration-test/resources/com/tacitknowledge/util/migration/inttest-tasks/core/patch00002_patch02.sql =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/integration-test/resources/com/tacitknowledge/util/migration/inttest-tasks/core/patch00002_patch02.sql (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/integration-test/resources/com/tacitknowledge/util/migration/inttest-tasks/core/patch00002_patch02.sql (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,7 @@ +CREATE TABLE core_table_1 ( + id INT NOT NULL PRIMARY KEY, + value VARCHAR(256) +); + + +INSERT INTO core_table_1 (id, value) VALUES (1, 'core_table_1'); \ No newline at end of file Index: 3rdParty_sources/tacitknowledge/autopatch/src/integration-test/resources/com/tacitknowledge/util/migration/inttest-tasks/missingpatchstrategy/batch1/patch00001_patch01.sql =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/integration-test/resources/com/tacitknowledge/util/migration/inttest-tasks/missingpatchstrategy/batch1/patch00001_patch01.sql (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/integration-test/resources/com/tacitknowledge/util/migration/inttest-tasks/missingpatchstrategy/batch1/patch00001_patch01.sql (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,6 @@ +CREATE TABLE order_table_1 ( + id INT NOT NULL PRIMARY KEY, + value VARCHAR(256) +); + +INSERT INTO order_table_1 (id, value) VALUES (1, 'order_table_1'); \ No newline at end of file Index: 3rdParty_sources/tacitknowledge/autopatch/src/integration-test/resources/com/tacitknowledge/util/migration/inttest-tasks/missingpatchstrategy/batch1/patch00004_patch04.sql =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/integration-test/resources/com/tacitknowledge/util/migration/inttest-tasks/missingpatchstrategy/batch1/patch00004_patch04.sql (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/integration-test/resources/com/tacitknowledge/util/migration/inttest-tasks/missingpatchstrategy/batch1/patch00004_patch04.sql (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,6 @@ +CREATE TABLE catalog_table_1 ( + id INT NOT NULL PRIMARY KEY, + value VARCHAR(256) +); + +INSERT INTO catalog_table_1 (id, value) VALUES (1, 'catalog_table_1'); \ No newline at end of file Index: 3rdParty_sources/tacitknowledge/autopatch/src/integration-test/resources/com/tacitknowledge/util/migration/inttest-tasks/missingpatchstrategy/batch2/patch00002-rollback_patch02.sql =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/integration-test/resources/com/tacitknowledge/util/migration/inttest-tasks/missingpatchstrategy/batch2/patch00002-rollback_patch02.sql (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/integration-test/resources/com/tacitknowledge/util/migration/inttest-tasks/missingpatchstrategy/batch2/patch00002-rollback_patch02.sql (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1 @@ +DROP TABLE core_table_1; Index: 3rdParty_sources/tacitknowledge/autopatch/src/integration-test/resources/com/tacitknowledge/util/migration/inttest-tasks/missingpatchstrategy/batch2/patch00002_patch02.sql =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/integration-test/resources/com/tacitknowledge/util/migration/inttest-tasks/missingpatchstrategy/batch2/patch00002_patch02.sql (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/integration-test/resources/com/tacitknowledge/util/migration/inttest-tasks/missingpatchstrategy/batch2/patch00002_patch02.sql (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,7 @@ +CREATE TABLE core_table_1 ( + id INT NOT NULL PRIMARY KEY, + value VARCHAR(256) +); + + +INSERT INTO core_table_1 (id, value) VALUES (1, 'core_table_1'); \ No newline at end of file Index: 3rdParty_sources/tacitknowledge/autopatch/src/integration-test/resources/com/tacitknowledge/util/migration/inttest-tasks/missingpatchstrategy/batch2/patch00003-rollback_patch03.sql =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/integration-test/resources/com/tacitknowledge/util/migration/inttest-tasks/missingpatchstrategy/batch2/patch00003-rollback_patch03.sql (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/integration-test/resources/com/tacitknowledge/util/migration/inttest-tasks/missingpatchstrategy/batch2/patch00003-rollback_patch03.sql (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1 @@ +DROP TABLE order_table_2; Index: 3rdParty_sources/tacitknowledge/autopatch/src/integration-test/resources/com/tacitknowledge/util/migration/inttest-tasks/missingpatchstrategy/batch2/patch00003_patch03.sql =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/integration-test/resources/com/tacitknowledge/util/migration/inttest-tasks/missingpatchstrategy/batch2/patch00003_patch03.sql (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/integration-test/resources/com/tacitknowledge/util/migration/inttest-tasks/missingpatchstrategy/batch2/patch00003_patch03.sql (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,6 @@ +CREATE TABLE order_table_2 ( + id INT NOT NULL PRIMARY KEY, + value VARCHAR(256) +); + +INSERT INTO order_table_2 (id, value) VALUES (1, 'order_table_2'); \ No newline at end of file Index: 3rdParty_sources/tacitknowledge/autopatch/src/integration-test/resources/com/tacitknowledge/util/migration/inttest-tasks/order/patch00001_patch01.sql =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/integration-test/resources/com/tacitknowledge/util/migration/inttest-tasks/order/patch00001_patch01.sql (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/integration-test/resources/com/tacitknowledge/util/migration/inttest-tasks/order/patch00001_patch01.sql (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,6 @@ +CREATE TABLE order_table_1 ( + id INT NOT NULL PRIMARY KEY, + value VARCHAR(256) +); + +INSERT INTO order_table_1 (id, value) VALUES (1, 'order_table_1'); \ No newline at end of file Index: 3rdParty_sources/tacitknowledge/autopatch/src/integration-test/resources/com/tacitknowledge/util/migration/inttest-tasks/order/patch00003_patch03.sql =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/integration-test/resources/com/tacitknowledge/util/migration/inttest-tasks/order/patch00003_patch03.sql (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/integration-test/resources/com/tacitknowledge/util/migration/inttest-tasks/order/patch00003_patch03.sql (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,6 @@ +CREATE TABLE order_table_2 ( + id INT NOT NULL PRIMARY KEY, + value VARCHAR(256) +); + +INSERT INTO order_table_2 (id, value) VALUES (1, 'order_table_2'); \ No newline at end of file Index: 3rdParty_sources/tacitknowledge/autopatch/src/integration-test/resources/com/tacitknowledge/util/migration/inttest-tasks/race/patch00001_create_race_table.sql =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/integration-test/resources/com/tacitknowledge/util/migration/inttest-tasks/race/patch00001_create_race_table.sql (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/integration-test/resources/com/tacitknowledge/util/migration/inttest-tasks/race/patch00001_create_race_table.sql (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,5 @@ +create table race ( + key_name varchar(20) not null, + field_value varchar(40) not null, + constraint race_uk unique (key_name, field_value) +); Index: 3rdParty_sources/tacitknowledge/autopatch/src/integration-test/resources/com/tacitknowledge/util/migration/inttest-tasks/race/patch00002_insert_into_race.sql =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/integration-test/resources/com/tacitknowledge/util/migration/inttest-tasks/race/patch00002_insert_into_race.sql (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/integration-test/resources/com/tacitknowledge/util/migration/inttest-tasks/race/patch00002_insert_into_race.sql (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1 @@ +insert into race (key_name, field_value) values ('count', 'One'); Index: 3rdParty_sources/tacitknowledge/autopatch/src/integration-test/resources/com/tacitknowledge/util/migration/inttest-tasks/race/patch00003_insert_into_race_2.sql =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/integration-test/resources/com/tacitknowledge/util/migration/inttest-tasks/race/patch00003_insert_into_race_2.sql (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/integration-test/resources/com/tacitknowledge/util/migration/inttest-tasks/race/patch00003_insert_into_race_2.sql (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1 @@ +insert into race (key_name, field_value) values ('count', 'Two'); Index: 3rdParty_sources/tacitknowledge/autopatch/src/integration-test/resources/com/tacitknowledge/util/migration/inttest-tasks/race/patch00004_insert_into_race_3.sql =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/integration-test/resources/com/tacitknowledge/util/migration/inttest-tasks/race/patch00004_insert_into_race_3.sql (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/integration-test/resources/com/tacitknowledge/util/migration/inttest-tasks/race/patch00004_insert_into_race_3.sql (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1 @@ +insert into race (key_name, field_value) values ('count', 'Three'); Index: 3rdParty_sources/tacitknowledge/autopatch/src/integration-test/resources/inttest-migration.properties =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/integration-test/resources/inttest-migration.properties (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/integration-test/resources/inttest-migration.properties (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,45 @@ +# +# Which context do we use for the orchestration patch store? Core. +# +integration_test.context=core +integration_test.controlled.systems=core,orders,catalog +integration_test.listeners=com.tacitknowledge.util.migration.listeners.WhinyMigrationListener + +# Configure a context named "core" +# +core.jdbc.database.type=hsqldb +core.jdbc.driver=org.hsqldb.jdbcDriver +core.jdbc.url=jdbc:hsqldb:mem:core +core.jdbc.username=sa +core.jdbc.password= +core.patch.path=com.tacitknowledge.util.migration.inttest-tasks.core +# +# Configure a context named "orders" +# +orders.jdbc.database.type=hsqldb +orders.jdbc.driver=org.hsqldb.jdbcDriver +orders.jdbc.url=jdbc:hsqldb:mem:orders +orders.jdbc.username=sa +orders.jdbc.password= +orders.patch.path=com.tacitknowledge.util.migration.inttest-tasks.order + +# +# Configure a context named catalog, and make it a multi-node context +# +catalog.jdbc.systems=jdbccatalog1,jdbccatalog2,jdbccatalog3 +catalog.jdbccatalog1.database.type=hsqldb +catalog.jdbccatalog1.driver=org.hsqldb.jdbcDriver +catalog.jdbccatalog1.url=jdbc:hsqldb:mem:catalog1 +catalog.jdbccatalog1.username=sa +catalog.jdbccatalog1.password= +catalog.jdbccatalog2.database.type=hsqldb +catalog.jdbccatalog2.driver=org.hsqldb.jdbcDriver +catalog.jdbccatalog2.url=jdbc:hsqldb:mem:catalog2 +catalog.jdbccatalog2.username=sa +catalog.jdbccatalog2.password= +catalog.jdbccatalog3.database.type=hsqldb +catalog.jdbccatalog3.driver=org.hsqldb.jdbcDriver +catalog.jdbccatalog3.url=jdbc:hsqldb:mem:catalog3 +catalog.jdbccatalog3.username=sa +catalog.jdbccatalog3.password= +catalog.patch.path=com.tacitknowledge.util.migration.inttest-tasks.catalog \ No newline at end of file Index: 3rdParty_sources/tacitknowledge/autopatch/src/integration-test/resources/log4j.properties =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/integration-test/resources/log4j.properties (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/integration-test/resources/log4j.properties (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,8 @@ +# Set root logger level to DEBUG for lots of output +log4j.rootLogger=INFO, CONSOLE + +# If you would like to have output simply be on the console, set the +# rootLogger to CONSOLE +log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender +log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout +log4j.appender.CONSOLE.layout.ConversionPattern=%d %-4r [%t] %-5p %c{1} %x - %m%n Index: 3rdParty_sources/tacitknowledge/autopatch/src/integration-test/resources/missingpatchstrategybatch1-inttest-migration.properties =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/integration-test/resources/missingpatchstrategybatch1-inttest-migration.properties (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/integration-test/resources/missingpatchstrategybatch1-inttest-migration.properties (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,38 @@ +# +# Which context do we use for the orchestration patch store? Orders. +# +migration.strategy=com.tacitknowledge.util.migration.MissingPatchMigrationRunnerStrategy + +# +# Configure a context named "orders" +# +orders.jdbc.database.type=hsqldb +orders.jdbc.driver=org.hsqldb.jdbcDriver +orders.jdbc.url=jdbc:hsqldb:mem:orders +orders.jdbc.username=sa +orders.jdbc.password= +orders.patch.path=com.tacitknowledge.util.migration.inttest-tasks.missingpatchstrategy.batch1 + +# +# Configure a context named nodes, and make it a multi-node context +# +nodes.context=nodes +nodes.controlled.systems=nodes +nodes.jdbc.database.type=hsqldb +nodes.jdbc.driver=org.hsqldb.jdbcDriver +nodes.jdbc.url=jdbc:hsqldb:mem:nodes +nodes.jdbc.username=sa +nodes.jdbc.password= +nodes.patch.path=com.tacitknowledge.util.migration.inttest-tasks.missingpatchstrategy.batch1 + +nodes.jdbc.systems=jdbcnode1,jdbcnode2 +nodes.jdbcnode1.database.type=hsqldb +nodes.jdbcnode1.driver=org.hsqldb.jdbcDriver +nodes.jdbcnode1.url=jdbc:hsqldb:mem:node1 +nodes.jdbcnode1.username=sa +nodes.jdbcnode1.password= +nodes.jdbcnode2.database.type=hsqldb +nodes.jdbcnode2.driver=org.hsqldb.jdbcDriver +nodes.jdbcnode2.url=jdbc:hsqldb:mem:node2 +nodes.jdbcnode2.username=sa +nodes.jdbcnode2.password= Index: 3rdParty_sources/tacitknowledge/autopatch/src/integration-test/resources/missingpatchstrategybatch2-inttest-migration.properties =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/integration-test/resources/missingpatchstrategybatch2-inttest-migration.properties (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/integration-test/resources/missingpatchstrategybatch2-inttest-migration.properties (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,38 @@ +# +# Which context do we use for the orchestration patch store? Orders. +# +migration.strategy=com.tacitknowledge.util.migration.MissingPatchMigrationRunnerStrategy + +# +# Configure a context named "orders" +# +orders.jdbc.database.type=hsqldb +orders.jdbc.driver=org.hsqldb.jdbcDriver +orders.jdbc.url=jdbc:hsqldb:mem:orders +orders.jdbc.username=sa +orders.jdbc.password= +orders.patch.path=com.tacitknowledge.util.migration.inttest-tasks.missingpatchstrategy.batch2 + +# +# Configure a context named nodes, and make it a multi-node context +# +nodes.context=nodes +nodes.controlled.systems=nodes +nodes.jdbc.database.type=hsqldb +nodes.jdbc.driver=org.hsqldb.jdbcDriver +nodes.jdbc.url=jdbc:hsqldb:mem:nodes +nodes.jdbc.username=sa +nodes.jdbc.password= +nodes.patch.path=com.tacitknowledge.util.migration.inttest-tasks.missingpatchstrategy.batch2 + +nodes.jdbc.systems=jdbcnode1,jdbcnode2 +nodes.jdbcnode1.database.type=hsqldb +nodes.jdbcnode1.driver=org.hsqldb.jdbcDriver +nodes.jdbcnode1.url=jdbc:hsqldb:mem:node1 +nodes.jdbcnode1.username=sa +nodes.jdbcnode1.password= +nodes.jdbcnode2.database.type=hsqldb +nodes.jdbcnode2.driver=org.hsqldb.jdbcDriver +nodes.jdbcnode2.url=jdbc:hsqldb:mem:node2 +nodes.jdbcnode2.username=sa +nodes.jdbcnode2.password= \ No newline at end of file Index: 3rdParty_sources/tacitknowledge/autopatch/src/integration-test/resources/missingpatchstrategybatch3-inttest-migration.properties =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/integration-test/resources/missingpatchstrategybatch3-inttest-migration.properties (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/integration-test/resources/missingpatchstrategybatch3-inttest-migration.properties (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,24 @@ +# +# Which context do we use for the orchestration patch store? Orders. +# +migration.strategy=com.tacitknowledge.util.migration.MissingPatchMigrationRunnerStrategy + + +# +# Configure a context named nodes, and make it a multi-node context +# +nodes.context=nodes +nodes.controlled.systems=nodes +nodes.jdbc.database.type=hsqldb +nodes.jdbc.driver=org.hsqldb.jdbcDriver +nodes.jdbc.url=jdbc:hsqldb:mem:nodes +nodes.jdbc.username=sa +nodes.jdbc.password= +nodes.patch.path=com.tacitknowledge.util.migration.inttest-tasks.missingpatchstrategy.batch1 + +nodes.jdbc.systems=jdbcnode3 +nodes.jdbcnode3.database.type=hsqldb +nodes.jdbcnode3.driver=org.hsqldb.jdbcDriver +nodes.jdbcnode3.url=jdbc:hsqldb:mem:node3 +nodes.jdbcnode3.username=sa +nodes.jdbcnode3.password= Index: 3rdParty_sources/tacitknowledge/autopatch/src/integration-test/resources/missingpatchstrategybatch4-inttest-migration.properties =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/integration-test/resources/missingpatchstrategybatch4-inttest-migration.properties (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/integration-test/resources/missingpatchstrategybatch4-inttest-migration.properties (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,33 @@ +# +# Which context do we use for the orchestration patch store? Orders. +# +migration.strategy=com.tacitknowledge.util.migration.MissingPatchMigrationRunnerStrategy + +# +# Configure a context named nodes, and make it a multi-node context +# +nodes.context=nodes +nodes.controlled.systems=nodes +nodes.jdbc.database.type=hsqldb +nodes.jdbc.driver=org.hsqldb.jdbcDriver +nodes.jdbc.url=jdbc:hsqldb:mem:nodes +nodes.jdbc.username=sa +nodes.jdbc.password= +nodes.patch.path=com.tacitknowledge.util.migration.inttest-tasks.missingpatchstrategy.batch2 + +nodes.jdbc.systems=jdbcnode1,jdbcnode2,jdbcnode3 +nodes.jdbcnode1.database.type=hsqldb +nodes.jdbcnode1.driver=org.hsqldb.jdbcDriver +nodes.jdbcnode1.url=jdbc:hsqldb:mem:node1 +nodes.jdbcnode1.username=sa +nodes.jdbcnode1.password= +nodes.jdbcnode2.database.type=hsqldb +nodes.jdbcnode2.driver=org.hsqldb.jdbcDriver +nodes.jdbcnode2.url=jdbc:hsqldb:mem:node2 +nodes.jdbcnode2.username=sa +nodes.jdbcnode2.password= +nodes.jdbcnode3.database.type=hsqldb +nodes.jdbcnode3.driver=org.hsqldb.jdbcDriver +nodes.jdbcnode3.url=jdbc:hsqldb:mem:node3 +nodes.jdbcnode3.username=sa +nodes.jdbcnode3.password= \ No newline at end of file Index: 3rdParty_sources/tacitknowledge/autopatch/src/integration-test/resources/multiserver-inttest-migration.properties =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/integration-test/resources/multiserver-inttest-migration.properties (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/integration-test/resources/multiserver-inttest-migration.properties (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,10 @@ +# +# Configure a context named "race" +# +race.jdbc.database.type=hsqldb +race.jdbc.driver=org.hsqldb.jdbcDriver +race.jdbc.url=jdbc:hsqldb:mem:race +race.jdbc.username=sa +race.jdbc.password= +race.patch.path=com.tacitknowledge.util.migration.inttest-tasks.race +race.lockPollMillis=3000 Index: 3rdParty_sources/tacitknowledge/autopatch/src/integration-test/resources/node-added-inttest-migration.properties =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/integration-test/resources/node-added-inttest-migration.properties (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/integration-test/resources/node-added-inttest-migration.properties (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,53 @@ +# +# Which context do we use for the orchestration patch store? Core. +# +integration_test.context=core +integration_test.controlled.systems=core,orders,catalog +# +# Configure a context named "core" +# +core.jdbc.database.type=hsqldb +core.jdbc.driver=org.hsqldb.jdbcDriver +core.jdbc.url=jdbc:hsqldb:mem:core +core.jdbc.username=sa +core.jdbc.password= +core.patch.path=com.tacitknowledge.util.migration.inttest-tasks.core +# +# Configure a context named "orders" +# +orders.jdbc.database.type=hsqldb +orders.jdbc.driver=org.hsqldb.jdbcDriver +orders.jdbc.url=jdbc:hsqldb:mem:orders +orders.jdbc.username=sa +orders.jdbc.password= +orders.patch.path=com.tacitknowledge.util.migration.inttest-tasks.order +# +# Configure a context named catalog, and make it a multi-node context +# +catalog.jdbc.systems=jdbccatalog1,jdbccatalog2,jdbccatalog3,jdbccatalog4,jdbccatalog5 +catalog.jdbccatalog1.database.type=hsqldb +catalog.jdbccatalog1.driver=org.hsqldb.jdbcDriver +catalog.jdbccatalog1.url=jdbc:hsqldb:mem:catalog1 +catalog.jdbccatalog1.username=sa +catalog.jdbccatalog1.password= +catalog.jdbccatalog2.database.type=hsqldb +catalog.jdbccatalog2.driver=org.hsqldb.jdbcDriver +catalog.jdbccatalog2.url=jdbc:hsqldb:mem:catalog2 +catalog.jdbccatalog2.username=sa +catalog.jdbccatalog2.password= +catalog.jdbccatalog3.database.type=hsqldb +catalog.jdbccatalog3.driver=org.hsqldb.jdbcDriver +catalog.jdbccatalog3.url=jdbc:hsqldb:mem:catalog3 +catalog.jdbccatalog3.username=sa +catalog.jdbccatalog3.password= +catalog.jdbccatalog4.database.type=hsqldb +catalog.jdbccatalog4.driver=org.hsqldb.jdbcDriver +catalog.jdbccatalog4.url=jdbc:hsqldb:mem:catalog4 +catalog.jdbccatalog4.username=sa +catalog.jdbccatalog4.password= +catalog.jdbccatalog5.database.type=hsqldb +catalog.jdbccatalog5.driver=org.hsqldb.jdbcDriver +catalog.jdbccatalog5.url=jdbc:hsqldb:mem:catalog5 +catalog.jdbccatalog5.username=sa +catalog.jdbccatalog5.password= +catalog.patch.path=com.tacitknowledge.util.migration.inttest-tasks.catalog \ No newline at end of file Index: 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/AbstractMigrationListener.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/AbstractMigrationListener.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/AbstractMigrationListener.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,51 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration; + +/** + * Abstract base class for MigrationListener authors that aren't interested in + * implementing all the MigrationListener events. + * + * @author Alex Soto (apsoto@gmail.com) + */ +public abstract class AbstractMigrationListener implements MigrationListener +{ + + /** + * @see com.tacitknowledge.util.migration.MigrationListener#migrationFailed(com.tacitknowledge.util.migration.MigrationTask, com.tacitknowledge.util.migration.MigrationContext, com.tacitknowledge.util.migration.MigrationException) + */ + public void migrationFailed(MigrationTask task, MigrationContext context, + MigrationException e) throws MigrationException + { + } + + /** + * @see com.tacitknowledge.util.migration.MigrationListener#migrationStarted(com.tacitknowledge.util.migration.MigrationTask, com.tacitknowledge.util.migration.MigrationContext) + */ + public void migrationStarted(MigrationTask task, MigrationContext context) + throws MigrationException + { + } + + /** + * @see com.tacitknowledge.util.migration.MigrationListener#migrationSuccessful(com.tacitknowledge.util.migration.MigrationTask, com.tacitknowledge.util.migration.MigrationContext) + */ + public void migrationSuccessful(MigrationTask task, MigrationContext context) + throws MigrationException + { + } + +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/AutopatchRegistry.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/AutopatchRegistry.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/AutopatchRegistry.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,53 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package com.tacitknowledge.util.migration; + + +import com.tacitknowledge.util.migration.jdbc.JdbcMigrationLauncherFactoryLoader; +import com.tacitknowledge.util.migration.jdbc.StandaloneMigrationLauncher; +import com.tacitknowledge.util.migration.jdbc.util.MigrationUtil; +import org.picocontainer.DefaultPicoContainer; +import org.picocontainer.MutablePicoContainer; +import org.picocontainer.PicoContainer; +import org.picocontainer.injectors.CompositeInjection; +import org.picocontainer.injectors.ConstructorInjection; +import org.picocontainer.injectors.SetterInjection; + +/** + * AutopatchRegistry using PicoContainer to set all the dependencies of the application, we favor + * constructor injection over other methods of injection. + * + * @author Oscar Gonzalez (oscar@tacitknowledge.com) + */ +public class AutopatchRegistry +{ + MutablePicoContainer pico; + + public PicoContainer configureContainer(){ + pico = new DefaultPicoContainer(); + pico.addComponent(StandaloneMigrationLauncher.class); + pico.addComponent(MigrationUtil.class); + pico.start(); + return pico; + } + + public void destroyContainer(){ + pico.stop(); + pico.dispose(); + } + +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/ClassMigrationTaskSource.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/ClassMigrationTaskSource.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/ClassMigrationTaskSource.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,91 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration; + +import com.tacitknowledge.util.discovery.ClassDiscoveryUtil; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import java.util.ArrayList; +import java.util.List; + +/** + * Returns a list of all public, concrete classes that implement the + * MigrationTask in a specific package. + * + * @author Scott Askew (scott@tacitknowledge.com) + */ +public class ClassMigrationTaskSource implements MigrationTaskSource +{ + /** + * Class logger + */ + private static Log log = LogFactory.getLog(ClassMigrationTaskSource.class); + + /** + * {@inheritDoc} + */ + public List getMigrationTasks(String packageName) throws MigrationException + { + if (packageName == null) + { + throw new MigrationException("You must specify a package to get tasks for"); + } + + Class[] taskClasses = ClassDiscoveryUtil.getClasses(packageName, MigrationTask.class); + log.debug("Found " + taskClasses.length + " patches in " + packageName); + return instantiateTasks(taskClasses); + } + + /** + * Instantiates the given classes + * + * @param taskClasses the classes instantiate + * @return a list of MigrationTasks + * @throws MigrationException if a class could not be instantiated; this + * is most likely due to the abscense of a default constructor + */ + private List instantiateTasks(Class[] taskClasses) throws MigrationException + { + List tasks = new ArrayList(); + for (int i = 0; i < taskClasses.length; i++) + { + Class taskClass = taskClasses[i]; + try + { + Object o = taskClass.newInstance(); + + // It's not legal to have a null name. + MigrationTask task = (MigrationTask) o; + if (task.getName() != null) + { + tasks.add(task); + } + else + { + log.warn("MigrationTask " + taskClass.getName() + + " had no migration name. Is that intentional? Skipping task."); + } + } + catch (Exception e) + { + throw new MigrationException("Could not instantiate MigrationTask " + + taskClass.getName(), e); + } + } + return tasks; + } +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/DistributedMigrationProcess.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/DistributedMigrationProcess.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/DistributedMigrationProcess.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,522 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration; + +import com.tacitknowledge.util.migration.jdbc.JdbcMigrationContext; +import com.tacitknowledge.util.migration.jdbc.JdbcMigrationLauncher; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import java.util.*; +import java.util.Map.Entry; + +/** + * Discovers and executes a sequence of system patches from multiple controlled + * systems, each of which has its own MigrationProcess. + * + * @author Mike Hardy (mike@tacitknowledge.com) + * @author Artie Pesh-Imam (apeshimam@tacitknowledge.com) + * @author Hemri Herrera (hemri@tacitknowledge.com) + * @author Ulises Pulido (upulido@tacitknowledge.com) + * @see com.tacitknowledge.util.migration.MigrationProcess + */ +public class DistributedMigrationProcess extends MigrationProcess +{ + /** + * Class logger + */ + private static Log log = LogFactory.getLog(DistributedMigrationProcess.class); + + /** + * The JdbcMigrationLaunchers we are controlling, keyed by system name + */ + private HashMap controlledSystems = new HashMap(); + + /** + * If true, any nodes of the controlled systems that are not at the system's + * current patch level are patched to bring them in sync with the other + * nodes. This is not enabled by default because in a distributed system, if + * there are cross schema dependencies, then the patching of the node may + * fail since it is being patched 'out of order'. + *

+ * For example: + *

+ * system 1 has one node system 2 has one node + *

+ * patch1 applies to system1 and creates a table patch2 applies to system2 + * and creates a table that references the table in system1 patch3 applies + * to system2, dropping the reference to the table in system1 patch4 applies + * to system1, dropping the table. + *

+ * Later, to add capacity, system2 has a node added. When the second node is + * forcibly 'synced' patches 2 and 3 are applied to it. The patching fails + * when patch2 is applied because the table no longer exists in system1. + *

+ * Therefore, forcing a sync is usually safe for systems that don't contain + * external references, but should not be used for interdependent systems. + *

+ * Instead, it would be better to import the schema from a node already at + * the current patch level using database tools, then the new node can + * participate in the regular patching process. + */ + private boolean forceSync = false; + + /** + * Creates a new Migration instance. + */ + public DistributedMigrationProcess() + { + super(); + } + + /** + * Execute a dry run of the rollback process and return a count of the + * number of tasks which will rollback. + * + * @param rollbacks a List of RollbackableMigrationTasks + * @param rollbacksWithLaunchers a LinkedHashMap of task to launcher + * @return count of the number of rollbacks + */ + protected final int rollbackDryRun(final List rollbacks, + final LinkedHashMap rollbacksWithLaunchers) + { + // take the list of rollbacks + // iterate through the rollbacks + // log the context in which each rollback will execute + + int taskCount = 0; + + if (isPatchSetRollbackable(rollbacks)) + { + for (Iterator i = rollbacks.iterator(); i.hasNext();) + { + RollbackableMigrationTask task = (RollbackableMigrationTask) i.next(); + log.info("Will execute rollback for task '" + task.getName() + "'"); + taskCount++; + JdbcMigrationLauncher launcher = (JdbcMigrationLauncher) rollbacksWithLaunchers + .get(task); + for (Iterator contextIterator = launcher.getContexts().keySet().iterator(); contextIterator + .hasNext();) + { + + MigrationContext context = (MigrationContext) contextIterator.next(); + log.debug("Task will execute in context '" + context + "'"); + + } + } + } + + return taskCount; + } + + /** + * Execute a dry run of the patch process and return a count of the number + * of patches we would have executed. + * + * @param currentPatchInfoStore The current patch info store + * @param migrationsWithLaunchers a map of migration task to launcher + * @return count of the number of patches + */ + protected final int patchDryRun(final PatchInfoStore currentPatchInfoStore, + final LinkedHashMap migrationsWithLaunchers) throws MigrationException + { + int taskCount = 0; + + for (Iterator i = migrationsWithLaunchers.entrySet().iterator(); i.hasNext();) + { + Entry entry = (Entry) i.next(); + MigrationTask task = (MigrationTask) entry.getKey(); + JdbcMigrationLauncher launcher = (JdbcMigrationLauncher) entry.getValue(); + + + if (getMigrationRunnerStrategy().shouldMigrationRun(task.getLevel(), currentPatchInfoStore)) + { + log.info("Will execute patch task '" + getTaskLabel(task) + "'"); + if (log.isDebugEnabled()) + { + // Get all the contexts the task will execute in + for (Iterator j = launcher.getContexts().keySet().iterator(); j.hasNext();) + { + MigrationContext launcherContext = (MigrationContext) j.next(); + log.debug("Task will execute in context '" + launcherContext + "'"); + } + } + taskCount++; + } + + } + + return taskCount; + } + + /** + * Applies the necessary rollbacks to the system. + * + * @param currentPatchInfoStore + * @param rollbackLevels the level that the system should rollback to + * @param context information and resources that are available to the migration tasks + * @return the number of RollbackableMigrationTasks which have been rolled back + * @throws MigrationException if a rollback fails + * @Override + */ + public final int doRollbacks(final PatchInfoStore currentPatchInfoStore, final int[] rollbackLevels, final MigrationContext context, + boolean forceRollback) throws MigrationException + { + log.debug("Starting doRollbacks"); + // get all of the allTasks, with launchers, then get the list of just + // allTasks + LinkedHashMap rollbacksWithLaunchers = getMigrationTasksWithLaunchers(); + List allTasks = new ArrayList(); + allTasks.addAll(rollbacksWithLaunchers.keySet()); + + List rollbackCandidates = getMigrationRunnerStrategy().getRollbackCandidates(allTasks, rollbackLevels, currentPatchInfoStore); + + + validateControlledSystems(currentPatchInfoStore); + rollbackDryRun(rollbackCandidates, rollbacksWithLaunchers); + + if (rollbackCandidates.size() > 0) + { + log.info("A total of " + rollbackCandidates.size() + " rollback patch tasks will execute."); + } + else + { + log.info("System up-to-date. No patch tasks will rollback."); + } + + if (isPatchSetRollbackable(rollbackCandidates) || forceRollback) + { + if (isReadOnly()) + { + throw new MigrationException("Unapplied rollbacks exist, but read-only flag is set"); + } + + for (Iterator rollbackIterator = rollbackCandidates.iterator(); rollbackIterator.hasNext();) + { + RollbackableMigrationTask task = (RollbackableMigrationTask) rollbackIterator + .next(); + // Execute the task in the context it was loaded from + JdbcMigrationLauncher launcher = (JdbcMigrationLauncher) rollbacksWithLaunchers + .get(task); + + // iterate through all the contexts + for (Iterator j = launcher.getContexts().keySet().iterator(); j.hasNext();) + { + MigrationContext launcherContext = (MigrationContext) j.next(); + applyRollback(launcherContext, task, true); + } + } + + } + else + { + log + .info("Could not complete rollback because one or more of the tasks is not rollbackable."); + } + + List rollbacksNotApplied = getMigrationRunnerStrategy().getRollbackCandidates(rollbackCandidates, + rollbackLevels, currentPatchInfoStore); + + if (rollbacksNotApplied.isEmpty()) + { + log.info("Rollback complete (" + rollbackCandidates.size() + " patch tasks rolledback)"); + } + else + { + log.info("The system could not rollback the tasks"); + } + return rollbackCandidates.size() - rollbacksNotApplied.size(); + } + + /** + * Applies necessary patches to the system. + * + * @param patchInfoStore of the system to run + * @param context information and resources that are available to the migration tasks + * @return the number of MigrationTasks that have executed + * @throws MigrationException if a migration fails + * @Override + */ + public final int doMigrations(final PatchInfoStore patchInfoStore, + final MigrationContext context) throws MigrationException + { + log.debug("Starting doMigrations"); + // Get all the migrations, with their launchers, then get the list of + // just the migrations + LinkedHashMap migrationsWithLaunchers = getMigrationTasksWithLaunchers(); + List migrations = new ArrayList(); + migrations.addAll(migrationsWithLaunchers.keySet()); + + // make sure the migrations are okay, then sort them + validateTasks(migrations); + Collections.sort(migrations); + + validateControlledSystems(patchInfoStore); + + // determine how many tasks we're going to execute + int taskCount = patchDryRun(patchInfoStore, migrationsWithLaunchers); + + if (taskCount > 0) + { + log.info("A total of " + taskCount + " patch tasks will execute."); + } + else + { + log.info("System up-to-date. No patch tasks will execute."); + } + + // See if we should execute + if (isReadOnly()) + { + if (taskCount > 0) + { + throw new MigrationException("Unapplied patches exist, but read-only flag is set"); + } + + log.info("In read-only mode - skipping patch application"); + return 0; + } + + // Roll through each migration, applying it if necessary + taskCount = 0; + for (Iterator i = migrations.iterator(); i.hasNext();) + { + MigrationTask task = (MigrationTask) i.next(); + int migrationLevel = task.getLevel().intValue(); + boolean shouldApplyPatch = getMigrationRunnerStrategy().shouldMigrationRun(migrationLevel, patchInfoStore); + + if (shouldApplyPatch && !forceSync) + { + // Execute the task in the context it was loaded from + JdbcMigrationLauncher launcher = (JdbcMigrationLauncher) migrationsWithLaunchers + .get(task); + // Get all the contexts the task will execute in + for (Iterator j = launcher.getContexts().keySet().iterator(); j.hasNext();) + { + MigrationContext launcherContext = (MigrationContext) j.next(); + applyPatch(launcherContext, task, true); + } + taskCount++; + } + else if (forceSync)// if a sync is forced, need to check all + // the contexts to identify the ones out of + // sync + { + boolean patchesApplied = false; + ArrayList outOfSyncContexts = new ArrayList(); + + // first need to iterate over all the contexts and determined + // which one's are out of sync. + // can't sync yet because if there are multiple contexts that + // are out of sync, after the + // first one is synced, the remaining one's have their patch + // level updated via the + // MigrationListener.migrationSuccessful event. + JdbcMigrationLauncher launcher = (JdbcMigrationLauncher) migrationsWithLaunchers + .get(task); + for (Iterator j = launcher.getContexts().keySet().iterator(); j.hasNext();) + { + MigrationContext launcherContext = (MigrationContext) j.next(); + PatchInfoStore patchInfoStoreOfContext = + (PatchInfoStore) launcher.getContexts().get( + launcherContext); + + if (!getMigrationRunnerStrategy().isSynchronized(patchInfoStore, patchInfoStoreOfContext)) + { + outOfSyncContexts.add(launcherContext); + } + } + + // next patch the contexts that have been determined to be out + // of sync + for (Iterator iter = outOfSyncContexts.iterator(); iter.hasNext();) + { + MigrationContext launcherContext = (MigrationContext) iter.next(); + applyPatch(launcherContext, task, true); + patchesApplied = true; + } + + if (patchesApplied) + { + taskCount++; + } + } // else if forceSync + } + + if (taskCount > 0) + { + log.info("Patching complete (" + taskCount + " patch tasks executed)"); + } + else + { + log.info("System up-to-date. No patch tasks have been run."); + } + + return taskCount; + } + + /** + * Validates that the controlled systems are all at the current patch level. + * + * @param currentPatchInfoStore + * @throws MigrationException if all the controlled systems are not at the current patch level. + */ + protected final void validateControlledSystems(final PatchInfoStore currentPatchInfoStore) throws MigrationException + { + for (Iterator it = getControlledSystems().keySet().iterator(); it.hasNext();) + { + String systemName = (String) it.next(); + JdbcMigrationLauncher launcher = (JdbcMigrationLauncher) getControlledSystems().get( + systemName); + for (Iterator contextIt = launcher.getContexts().keySet().iterator(); contextIt + .hasNext();) + { + JdbcMigrationContext ctx = (JdbcMigrationContext) contextIt.next(); + PatchInfoStore patchInfoStore = (PatchInfoStore) launcher.getContexts().get(ctx); + if (!getMigrationRunnerStrategy().isSynchronized(currentPatchInfoStore, patchInfoStore)) + { + String message = "Database " + ctx.getDatabaseName() + + " is out of sync with system: " + systemName + ". " + + ctx.getDatabaseName() + " is at patch level " + + Integer.toString(patchInfoStore.getPatchLevel()) + " and the System is at patch level " + + Integer.toString(currentPatchInfoStore.getPatchLevel()) + "."; + if (getForceSync()) + { + log.info(message + " Continuing since 'forcesync' was specified."); + } + else + { + throw new MigrationException(message); + } + } + } + } + } + + /** + * Returns a LinkedHashMap of task/launcher pairings, regardless of patch + * level. + * + * @return LinkedHashMap containing MigrationTask / JdbcMigrationLauncher + * pairings + * @throws MigrationException if one or more migration tasks could not be created + */ + public final LinkedHashMap getMigrationTasksWithLaunchers() throws MigrationException + { + LinkedHashMap tasks = new LinkedHashMap(); + + // Roll through all our controlled system names + for (Iterator controlledSystemIter = getControlledSystems().keySet().iterator(); controlledSystemIter + .hasNext();) + { + // Get the sub launcher that runs patches for the current name + String controlledSystemName = (String) controlledSystemIter.next(); + JdbcMigrationLauncher subLauncher = (JdbcMigrationLauncher) getControlledSystems().get( + controlledSystemName); + + // Get all the tasks for that sub launcher + List subTasks = subLauncher.getMigrationProcess().getMigrationTasks(); + log.info("Found " + subTasks.size() + " for system " + controlledSystemName); + for (Iterator subTaskIter = subTasks.iterator(); subTaskIter.hasNext();) + { + MigrationTask task = (MigrationTask) subTaskIter.next(); + if (log.isDebugEnabled()) + { + Iterator launchers = subLauncher.getContexts().keySet().iterator(); + String systemName = ((JdbcMigrationContext) launchers.next()).getSystemName(); + log.debug("\tMigration+Launcher binder found subtask " + task.getName() + + " for launcher context " + systemName); + } + + // store the task, related to its launcher + tasks.put(task, subLauncher); + } + } + + return tasks; + } + + /** + * Returns a List of MigrationTasks, regardless of patch level. + * + * @return List containing MigrationTask objects + * @throws MigrationException if one or more migration tasks could not be created + */ + public final List getMigrationTasks() throws MigrationException + { + List tasks = new ArrayList(); + + for (Iterator controlledSystemIter = getControlledSystems().keySet().iterator(); controlledSystemIter + .hasNext();) + { + String controlledSystemName = (String) controlledSystemIter.next(); + JdbcMigrationLauncher launcher = (JdbcMigrationLauncher) getControlledSystems().get( + controlledSystemName); + List subTasks = launcher.getMigrationProcess().getMigrationTasks(); + log.info("Found " + subTasks.size() + " for system " + controlledSystemName); + if (log.isDebugEnabled()) + { + for (Iterator subTaskIter = subTasks.iterator(); subTaskIter.hasNext();) + { + log.debug("\tFound subtask " + ((MigrationTask) subTaskIter.next()).getName()); + } + } + tasks.addAll(subTasks); + } + + // Its difficult to tell what's going on when you don't see any patches. + // This will help people realize they don't have patches, and perhaps + // help them discover why. + if (tasks.size() == 0) + { + log.info("No patch tasks were discovered in your classpath. " + + "Run with DEBUG logging enabled for patch search details."); + } + + return tasks; + } + + /** + * Get the list of systems we are controlling + * + * @return HashMap of JdbcMigrationLauncher objects keyed by String system + * names + */ + public final HashMap getControlledSystems() + { + return controlledSystems; + } + + /** + * Set the list of systems to control + * + * @param controlledSystems HashMap of system name / JdbcMigrationLauncher pairs + */ + public final void setControlledSystems(final HashMap controlledSystems) + { + this.controlledSystems = controlledSystems; + } + + public final boolean getForceSync() + { + return forceSync; + } + + public final void setForceSync(final boolean forceSync) + { + this.forceSync = forceSync; + } +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/MigrationBroadcaster.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/MigrationBroadcaster.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/MigrationBroadcaster.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,143 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * Manages the MessageListener that are associated with a + * Migration instance. + * + * @author Scott Askew (scott@tacitknowledge.com) + */ +class MigrationBroadcaster +{ + /** + * Used by notifyListeners to indicate that listeners should + * be informed that a task is about to start. + */ + public static final int TASK_START = 1; + + /** + * Used by notifyListeners to indicate that listeners should + * be informed that a task has successfully completed. + */ + public static final int TASK_SUCCESS = 2; + + /** + * Used by notifyListeners to indicate that listeners should + * be informed that a task failed. + */ + public static final int TASK_FAILED = 3; + + /** + * The listeners interested in being notified of migration task events. + */ + private List listeners = new ArrayList(); + + /** + * Notifies all registered listeners of a migration task event. + * + * @param task the task that is being or that has been executed + * @param context the context in which the task was executed + * @param eventType TASK_START, TASK_SUCCESS, or TASK_FAIL + * @param e the exception thrown by the task if the task failed + * @throws MigrationException if one of the listeners threw an exception + */ + public void notifyListeners(MigrationTask task, MigrationContext context, + MigrationException e, int eventType) throws MigrationException + { + for (Iterator i = listeners.iterator(); i.hasNext();) + { + MigrationListener listener = (MigrationListener) i.next(); + switch (eventType) + { + case TASK_START: + listener.migrationStarted(task, context); + break; + + case TASK_SUCCESS: + listener.migrationSuccessful(task, context); + break; + + case TASK_FAILED: + listener.migrationFailed(task, context, e); + break; + + default: + throw new IllegalArgumentException("Unknown event type"); + } + } + } + + /** + * Notifies all registered listeners of a migration task event. + * + * @param task the task that is being or that has been executed + * @param context the context in which the task was executed + * @param eventType TASK_START, TASK_SUCCESS, or TASK_FAIL + * @throws MigrationException if one of the listeners threw an exception + */ + public void notifyListeners(MigrationTask task, MigrationContext context, + int eventType) throws MigrationException + { + notifyListeners(task, context, null, eventType); + } + + /** + * Registers the given MigrationListener as being interested + * in migration task events. + * + * @param listener the listener to add; may not be null + */ + public void addListener(MigrationListener listener) + { + if (listener == null) + { + throw new IllegalArgumentException("listener cannot be null"); + } + listeners.add(listener); + } + + /** + * Removes the given MigrationListener from the list of listeners + * associated with the Migration instance. + * + * @param listener the listener to add; may not be null + * @return true if the listener was located and removed, + * otherwise false. + */ + public boolean removeListener(MigrationListener listener) + { + if (listener == null) + { + throw new IllegalArgumentException("listener cannot be null"); + } + return listeners.remove(listener); + } + + /** + * Get the list of listeners + * + * @return List of MigrationListener objects + */ + public List getListeners() + { + return listeners; + } +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/MigrationContext.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/MigrationContext.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/MigrationContext.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,46 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration; + + +/** + * Provides system resources to migration tasks. + * + * @author Scott Askew (scott@tacitknowledge.com) + */ +public interface MigrationContext +{ + /** + * The name of the migration configuration file + */ + public static final String MIGRATION_CONFIG_FILE = "migration.properties"; + + /** + * Commits the current migration transaction. + * + * @throws MigrationException if there was an unrecoverable error committing + * the transaction + */ + public void commit() throws MigrationException; + + /** + * Rolls back the current migration transaction. + * + * @throws MigrationException if there was an unrecoverable error committing + * the transaction + */ + public void rollback() throws MigrationException; +} \ No newline at end of file Index: 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/MigrationException.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/MigrationException.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/MigrationException.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,45 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration; + +/** + * An exception we can use to type things as we send problems up + * + * @author Scott Askew (scott@tacitknowledge.com) + */ +public class MigrationException extends Exception +{ + /** + * Make a new MigrationException with the given message + * + * @param message the message to include in the exception + */ + public MigrationException(String message) + { + super(message); + } + + /** + * Make a new MigrationException with the given message and cause + * + * @param message the message to include in the exception + * @param cause the cause of the problem + */ + public MigrationException(String message, Throwable cause) + { + super(message, cause); + } +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/MigrationListener.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/MigrationListener.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/MigrationListener.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,67 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration; + +import java.util.Properties; + +/** + * Receives notifications regarding migration task migrations. + * + * @author Scott Askew (scott@tacitknowledge.com) + */ +public interface MigrationListener +{ + /** + * Initialize the migration listener. This provides an opportunity + * for the MigrationListener to initialize itself before patching + * begins. + * + * @param properties The properties loaded from migration.properties + */ + public void initialize(String systemName, Properties properties) throws MigrationException; + + /** + * Notifies the listener that the given task is about to start execution. + * + * @param task the recently finished task + * @param context the migration context + * @throws MigrationException if an unrecoverable error occurs + */ + public void migrationStarted(MigrationTask task, MigrationContext context) + throws MigrationException; + + /** + * Notifies the listener that the given task has completed execution. + * + * @param task the recently finished task + * @param context the migration context + * @throws MigrationException if an unrecoverable error occurs + */ + public void migrationSuccessful(MigrationTask task, MigrationContext context) + throws MigrationException; + + /** + * Notifies the listener that the given task has completed execution. + * + * @param task the recently finished task + * @param context the migration context + * @param e the MigrationException thrown by the task + * @throws MigrationException if an unrecoverable error occurs + */ + public void migrationFailed(MigrationTask task, + MigrationContext context, MigrationException e) throws MigrationException; + +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/MigrationProcess.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/MigrationProcess.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/MigrationProcess.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,819 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import java.util.*; + +/** + * Discovers and executes a sequence of system patches. Patches take the form of + * MigrationTask instances, each of which performs an atomic + * migration or patch transaction. MigrationTasks are executed + * sequentially based on the result of MigrationTask.getOrder. + * No two tasks can return the same result for getOrder, and + * this class will throw a MigrationException should such a + * situation occur. + *

+ * One useful pre-defined MigrationTask is + * SqlScriptMigrationTask, which wraps a .SQL file and executes + * all statements inside it. Any file in the migration task search path that + * matches the pattern "^patch(\\d++)(?!-rollback)_?(.+)?\\.sql" will be wrapped with the + * SqlScriptMigrationTask. Also, you rollback scripts are specified by + * matching the pattern "^patch(\\d+)-rollback_(.+)\\.sql." The rollback scripts + * are meant to reverse a patch and will reduce the level of the system to the + * previous patch level. The execution order for these tasks + * is defined by the number immediately following the "patch" part of the SQL + * script file name. + *

+ * Example: + *

+ *

+ *    // Find the patches
+ *    migrationRunner.addResourcePackage("com.example.myapp.migration");
+ *    migrationRunner.addResourceDirectory("db/sql");
+ *
+ *    try
+ *    {
+ *        <i>... figure out the current patch level...</i>
+ *        migrationRunner.doMigration(currentLevel, context);
+ *        <i>... update patch level</i>
+ *        <i>... commit MigrationContext ...</i>
+ *    }
+ *    catch (MigrationException e)
+ *    {
+ *        <i>... rollback MigrationContext ...</i>
+ *    }
+ * 
+ * + * @author Scott Askew (scott@tacitknowledge.com) + * @author Hemri Herrera (hemri@tacitknowledge.com) + * @author Ulises Pulido (upulido@tacitknowledge.com) + */ +public class MigrationProcess +{ + /** + * Class logger + */ + private static Log log = LogFactory.getLog(MigrationProcess.class); + + /** + * The list of package names containing the MigrationTasks + * and SQL scripts to execute as patches + */ + private List patchResourcePackages = new ArrayList(); + + /** + * The list of package names containing MigrationTasks and + * SQL scripts to execute after patch execution + */ + private List postPatchResourcePackages = new ArrayList(); + + /** + * Migration task providers + */ + private List migrationTaskSources = new ArrayList(); + + /** + * Used to broadcast migration task notifications + */ + private MigrationBroadcaster broadcaster = null; + + /** + * Used to broadcast rollback task notifications + */ + private RollbackBroadcaster rollbackBroadcaster = new RollbackBroadcaster(); + + /** + * Holds the strategy used to work with the different patches + */ + private MigrationRunnerStrategy migrationRunnerStrategy = null; + + + /** + * Whether we actually want to apply patches, or just look + */ + private boolean readOnly = false; + + /** + * Creates a new Migration instance. + */ + public MigrationProcess() + { + addMigrationTaskSource(new ClassMigrationTaskSource()); + setMigrationBroadcaster(new MigrationBroadcaster()); + } + + + public void setMigrationRunnerStrategy(MigrationRunnerStrategy migrationRunnerStrategy) + { + this.migrationRunnerStrategy = migrationRunnerStrategy; + } + + + /** + * Sets the MigrationBroadcaster for the current instance. + * + * @param migrationBroadcaster with the MigrationBroadcaster to be set + */ + protected void setMigrationBroadcaster(MigrationBroadcaster migrationBroadcaster) + { + this.broadcaster = migrationBroadcaster; + } + + /** + * Adds the given package to the migration task search path. + * + * @param packageName the name of the package to add to the search path + */ + public void addPatchResourcePackage(String packageName) + { + patchResourcePackages.add(packageName); + } + + /** + * Adds the given classpath-relative directory to the migration task search + * path. + * + * @param dir the name of the directory to add to the post-patch search path + */ + public void addPatchResourceDirectory(String dir) + { + // Make the path package-name-like so that + // ClassLoader.getResourceAsStream + // will work correctly + String packageName = dir.replace('/', '.').replace('\\', '.'); + addPatchResourcePackage(packageName); + } + + /** + * Adds the given package to the post-patch migration task search path. + * + * @param packageName the name of the package to add to the search path + */ + public void addPostPatchResourcePackage(String packageName) + { + postPatchResourcePackages.add(packageName); + } + + /** + * Adds the given classpath-relative directory to the post-patch migration + * task search path. + * + * @param dir the name of the directory to add to the post-patch search path + */ + public void addPostPatchResourceDirectory(String dir) + { + // Make the path package-name-like so that + // ClassLoader.getResourceAsStream + // will work correctly + String packageName = dir.replace('/', '.').replace('\\', '.'); + addPostPatchResourcePackage(packageName); + } + + /** + * Adds a MigrationTaskSource to the list of sources that + * provide this instance with MigrationTasks. + * + * @param source the MigrationTaskSource to add; may not be + * null + */ + public void addMigrationTaskSource(MigrationTaskSource source) + { + if (source == null) + { + throw new IllegalArgumentException("source cannot be null."); + } + else + { + migrationTaskSources.add(source); + } + } + + /** + * Applies rollbacks to move the patch level from the current level to the + * rollback level. + * + * @param currentPatchInfoStore + * @param rollbackLevels an integer indicating the level the desired patch level + * @param context information and resources that are available to the migration tasks + * @return the count of patches which were rolled back + * @throws MigrationException + */ + public int doRollbacks(PatchInfoStore currentPatchInfoStore, int[] rollbackLevels, MigrationContext context, + boolean forceRollback) throws MigrationException + { + log.trace("Starting doRollbacks"); + List allTasks = getMigrationTasks(); + validateTasks(allTasks); + + List rollbackCandidates = getMigrationRunnerStrategy() + .getRollbackCandidates(allTasks, rollbackLevels, currentPatchInfoStore); + + + boolean isPatchSetRollbackable = isPatchSetRollbackable(rollbackCandidates); + rollbackDryRun(rollbackCandidates, context); + if (isPatchSetRollbackable || forceRollback) + { + // See if we should execute + if (isReadOnly()) + { + throw new MigrationException("Unapplied rollbacks exist, but read-only flag is set"); + } + + // the list of patches is rollbackable now actually perform the + // rollback + log.info("A total of " + rollbackCandidates.size() + " rollbacks will execute."); + for (MigrationTask rollbackTask : rollbackCandidates) + { + RollbackableMigrationTask task = (RollbackableMigrationTask) rollbackTask; + + log.info("Will rollback patch task '" + getTaskLabel(task) + "'"); + log.debug("Task will rollback in context '" + context + "'"); + + applyRollback(context, task, true); + } + } + else + { + // Can I list the tasks which are not rollbackable? + log.info("Could not complete rollback because one or more of the tasks " + + "is not rollbackable.The system is still at patch level " + currentPatchInfoStore + "."); + } + + List rollbacksNotApplied = getMigrationRunnerStrategy() + .getRollbackCandidates(rollbackCandidates, rollbackLevels, currentPatchInfoStore); + if (rollbacksNotApplied.isEmpty()) + { + log.info("Rollback complete. The system is now at the desired patch level."); + } + else + { + log.info("The system was not able to rollback the patches."); + } + log.trace("Ending doRollbacks"); + return rollbackCandidates.size() - rollbacksNotApplied.size(); + } + + /** + * Helper method to determine if the set of migration tasks is rollbackable + * + * @param migrations a List of MigrationTasks + * @return a boolean value indicating if all of the tasks can be rolledback + */ + protected boolean isPatchSetRollbackable(List migrations) + { + boolean isRollbackable = true; + + for (Iterator i = migrations.iterator(); i.hasNext() && isRollbackable;) + { + // for each migration check if the task is able to be rolledback + // think about type + RollbackableMigrationTask task = null; + try + { + task = (RollbackableMigrationTask) i.next(); + } + catch (ClassCastException cce) + { + isRollbackable = false; + log.info("The task " + task.getName() + " is not rollbackable."); + } + if (!task.isRollbackSupported()) + { + isRollbackable = false; + log.info("The task " + task.getName() + " is not rollbackable."); + } + + } + return isRollbackable; + } + + /** + * Applies necessary patches to the system. + * + * @param patchInfoStore used to execute migrations + * @param context information and resources that are available to the migration tasks + * @return the number of MigrationTasks that have executed + * @throws MigrationException if a migration fails + */ + public int doMigrations(PatchInfoStore patchInfoStore, + MigrationContext context) throws MigrationException + { + + log.trace("Starting doMigrations"); + List migrations = getMigrationTasks(); + validateTasks(migrations); + Collections.sort(migrations); + int taskCount = dryRun(patchInfoStore, context, migrations); + + // See if we should execute + if (isReadOnly()) + { + if (taskCount > 0) + { + throw new MigrationException("Unapplied patches exist, but read-only flag is set"); + } + + log.info("In read-only mode - skipping patch application"); + return 0; + } + + // Now apply them + taskCount = 0; + for (MigrationTask task : migrations) + { + if (migrationRunnerStrategy.shouldMigrationRun(task.getLevel(), patchInfoStore)) + { + applyPatch(context, task, true); + taskCount++; + } + } + + if (taskCount > 0) + { + log.info("Patching complete (" + taskCount + " patch tasks executed)"); + } + else + { + log.info("System up-to-date. No patch tasks executed."); + } + + return taskCount; + } + + /** + * Performs a dry run of rollbacks. This method determines which tasks will rollback + * and logs this information. + * + * @param migrations a List of migrations + * @param context the MigrationContext where rollbacks would occer + * @return the count of tasks which would rollback + */ + private int rollbackDryRun(List migrations, MigrationContext context) + { + int taskCount = 0; + // Roll through once, just printing out what we'll do + for (MigrationTask migrationTask : migrations) + { + RollbackableMigrationTask task = (RollbackableMigrationTask) migrationTask; + + log.info("Will execute rollback for task '" + getTaskLabel(task) + "'"); + log.debug("Task will execute in context '" + context + "'"); + taskCount++; + + } + if (taskCount > 0) + { + log.info("A total of " + taskCount + " patch tasks will rollback."); + } + else + { + log.info("System up-to-date. No patch tasks will execute."); + } + return taskCount; + } + + /** + * Run post-migration tasks + * + * @param context the context to use for the post-patch migrations + * @return the number of MigrationTasks that executed + * @throws MigrationException if a post-patch task fails + */ + public int doPostPatchMigrations(MigrationContext context) throws MigrationException + { + log.info("Running post-patch tasks..."); + List postMigrationTasks = getPostPatchMigrationTasks(); + validateTasks(postMigrationTasks); + Collections.sort(postMigrationTasks); + + if (postMigrationTasks.size() == 0) + { + log.info("No post-patch tasks found."); + return 0; + } + + // Roll through once, just printing out what we'll do + int taskCount = 0; + for (Iterator i = postMigrationTasks.iterator(); i.hasNext(); taskCount++) + { + MigrationTask task = (MigrationTask) i.next(); + log.info("Will execute post-patch task '" + getTaskLabel(task) + "'"); + } + log.info("A total of " + taskCount + " post-patch tasks will execute."); + + // See if we should execute + // FIXME test read-only mode with no patches skipping post-patch tasks + if (isReadOnly()) + { + log.info("In read-only mode - skipping post-patch task execution"); + return 0; + } + + // Now execute them + taskCount = 0; + for (Iterator i = postMigrationTasks.iterator(); i.hasNext(); taskCount++) + { + MigrationTask task = (MigrationTask) i.next(); + applyPatch(context, task, false); + } + log.info("Post-patch tasks complete (" + taskCount + " tasks executed)"); + + return taskCount; + } + + /** + * This method applies a single Rollback to the system. + * + * @param context the MigrationContext in which the task is rolled back + * @param task the RollbackableMigrationTask which is to be rolled back + * @param broadcast a boolean indicating if the execution of this rollback should be broadcast to listeners + * @throws MigrationException is thrown in case of encountering an exception + */ + public void applyRollback(MigrationContext context, RollbackableMigrationTask task, + boolean broadcast) throws MigrationException + { + String label = getTaskLabel(task); + int rollbackLevel = task.getLevel().intValue(); + + if (broadcast) + { + rollbackBroadcaster.notifyListeners(task, context, MigrationBroadcaster.TASK_START, + rollbackLevel); + log + .debug("broadcaster has " + rollbackBroadcaster.getListeners().size() + + " listeners"); + } + log.info("Executing patch task \"" + label + "\"..."); + + try + { + long startTime = System.currentTimeMillis(); + task.down(context); + long duration = System.currentTimeMillis() - startTime; + log.info("Finished patch task \"" + label + "\" (" + duration + " millis.)"); + if (broadcast) + { + rollbackBroadcaster.notifyListeners(task, context, + MigrationBroadcaster.TASK_SUCCESS, rollbackLevel); + } + context.commit(); + } + catch (MigrationException e) + { + if (broadcast) + { + rollbackBroadcaster.notifyListeners(task, context, e, + MigrationBroadcaster.TASK_FAILED, rollbackLevel); + } + try + { + context.rollback(); + log.info("Patch task failed; rollback successful"); + } + catch (MigrationException me) + { + log.info("Patch task failed; COULD NOT ROLL BACK TRANSACTION", me); + } + throw e; + } + } + + /** + * Apply a single patch + * + * @param context the context the patch will need during application + * @param task the application task to carry out + * @param broadcast whether to broadcast to listeners that the patch applied + * @throws MigrationException if the patch application fails + */ + public void applyPatch(MigrationContext context, MigrationTask task, boolean broadcast) + throws MigrationException + { + String label = getTaskLabel(task); + if (broadcast) + { + broadcaster.notifyListeners(task, context, MigrationBroadcaster.TASK_START); + log.debug("broadcaster has " + broadcaster.getListeners().size() + " listeners"); + } + log.info("Executing patch task \"" + label + "\"..."); + + try + { + long startTime = System.currentTimeMillis(); + task.migrate(context); + long duration = System.currentTimeMillis() - startTime; + log.info("Finished patch task \"" + label + "\" (" + duration + " millis.)"); + if (broadcast) + { + broadcaster.notifyListeners(task, context, MigrationBroadcaster.TASK_SUCCESS); + } + context.commit(); + } + catch (MigrationException e) + { + if (broadcast) + { + broadcaster.notifyListeners(task, context, e, MigrationBroadcaster.TASK_FAILED); + } + try + { + context.rollback(); + log.info("Patch task failed; rollback successful"); + } + catch (MigrationException me) + { + log.info("Patch task failed; COULD NOT ROLL BACK TRANSACTION", me); + } + throw e; + } + } + + /** + * Returns a list of all migration tasks, regardless of patch level. + * + * @return a list of all migration tasks + * @throws MigrationException if one or more migration tasks could not be created + */ + public List getMigrationTasks() throws MigrationException + { + return getTasksFromPackages(patchResourcePackages); + } + + /** + * Returns a list of all post-patch migration tasks + * + * @return a list of all post-patch migration tasks + * @throws MigrationException if one or more post-patch migration tasks could not be created + */ + public List getPostPatchMigrationTasks() throws MigrationException + { + return getTasksFromPackages(postPatchResourcePackages); + } + + /** + * Instantiate all the MigrationTask objects in the given resource packages + * + * @param resourcePackages a List of Strings specifying package names to look for tasks in + * @return List of MigrationTask objects instantiated from the given packages + * @throws MigrationException if one or more post-patch migration tasks could not be + * created + */ + private List getTasksFromPackages(List resourcePackages) throws MigrationException + { + List tasks = new ArrayList(); + for (String packageName : resourcePackages) + { + log.debug("Searching for patch tasks in package " + packageName); + + for (MigrationTaskSource source : migrationTaskSources) + { + List sourceTasks = source.getMigrationTasks(packageName); + if (sourceTasks.size() > 0) + { + log.debug("Source [" + source + "] found " + sourceTasks.size() + + " patch tasks: " + sourceTasks); + } + else + { + log.debug("Source [" + source + "] returned 0 patch tasks."); + } + + tasks.addAll(sourceTasks); + } + } + + // Its difficult to tell what's going on when you don't see any patches. + // This will help people realize they don't have patches, and perhaps + // help them discover why. + if (tasks.size() == 0) + { + log.info("No patch tasks were discovered in your classpath. " + + "Run with DEBUG logging enabled for patch task search details."); + } + + return tasks; + } + + /** + * Returns the patch level which is previous to the current level + * + * @param currentLevel the current patch level + * @return the level of the patch that was applied previous to the current patch l + * @throws MigrationException if there is an error retrieving the previous patch level + */ + public int getPreviousPatchLevel(int currentLevel) throws MigrationException + { + boolean isCurrentPatchFound = false; + List tasks = getMigrationTasks(); + int previousTaskLevel = 0; + + Collections.sort(tasks); + + for (ListIterator patchIterator = tasks.listIterator(); patchIterator.hasNext() + && !isCurrentPatchFound;) + { + int previousIndex = patchIterator.previousIndex(); + + //get the current patch + MigrationTask task = (MigrationTask) patchIterator.next(); + + if (currentLevel == task.getLevel().intValue()) + { + //the current patch level is found + isCurrentPatchFound = true; + + if (previousIndex != -1) + { + MigrationTask previousTask = (MigrationTask) tasks.get(previousIndex); + previousTaskLevel = previousTask.getLevel().intValue(); + } + } + } + return previousTaskLevel; + } + + /** + * Returns the number to use when creating the next patch. + * + * @return the number to use when creating the next patch + * @throws MigrationException if the existing tasks are invalid + */ + public int getNextPatchLevel() throws MigrationException + { + List tasks = getMigrationTasks(); + + if (tasks.size() == 0) + { + return 1; + } + + Collections.sort(tasks); + validateTasks(tasks); + MigrationTask lastTask = (MigrationTask) tasks.get(tasks.size() - 1); + + return lastTask.getLevel().intValue() + 1; + } + + /** + * Registers the given MigrationListener as being interested + * in migration task events. + * + * @param listener the listener to add; may not be null + */ + public void addListener(MigrationListener listener) + { + broadcaster.addListener(listener); + if (listener instanceof RollbackListener) + rollbackBroadcaster.addListener((RollbackListener) listener); + } + + /** + * Removes the given MigrationListener from the list of + * listeners associated with this Migration instance. + * + * @param listener the listener to add; may not be null + * @return true if the listener was located and removed, + * otherwise false. + */ + public boolean removeListener(MigrationListener listener) + { + return broadcaster.removeListener(listener); + } + + /** + * Get all of the MigrationListeners + * + * @return List of MigrationListeners + */ + public List getListeners() + { + return broadcaster.getListeners(); + } + + /** + * Returns a user-friendly label for the specified task. + * + * @param task the task to create a label for + * @return a user-friendly label for the specified task + */ + protected String getTaskLabel(MigrationTask task) + { + return task.getName() + " [" + task.getClass().getName() + "]"; + } + + /** + * Ensures that no two MigrationTasks have the same ordering. + * + * @param migrations the list of defined migration tasks + * @throws MigrationException if the migration tasks are not correctly defined + */ + public void validateTasks(List migrations) throws MigrationException + { + Map usedOrderNumbers = new HashMap(); + for (Iterator i = migrations.iterator(); i.hasNext();) + { + MigrationTask task = (MigrationTask) i.next(); + + Integer level = task.getLevel(); + if (level == null) + { + throw new MigrationException("Patch task '" + getTaskLabel(task) + + "' does not have a patch level defined."); + } + + if (usedOrderNumbers.containsKey(level)) + { + MigrationTask otherTask = (MigrationTask) usedOrderNumbers.get(level); + throw new MigrationException("Patch task " + getTaskLabel(task) + + " has a conflicting patch level with " + getTaskLabel(otherTask) + + "; both are configured for patch level " + level); + } + + usedOrderNumbers.put(level, task); + } + } + + /** + * See if we are actually applying patches, or if it is just readonly + * + * @return boolean true if we will skip application + */ + public boolean isReadOnly() + { + return readOnly; + } + + /** + * Set whether or not to actually apply patches + * + * @param readOnly boolean true if we should skip application + */ + public void setReadOnly(boolean readOnly) + { + this.readOnly = readOnly; + } + + /** + * Registers the given MigrationListeners as being interested + * in migration task events. + * + * @param listeners the listeners to add; + */ + public void addListeners(List listeners) + { + for (Iterator it = listeners.iterator(); it.hasNext();) + { + MigrationListener listener = (MigrationListener) it.next(); + broadcaster.addListener(listener); + if (listener instanceof RollbackListener) + rollbackBroadcaster.addListener((RollbackListener) listener); + } + } + + public int dryRun(PatchInfoStore patchInfoStore, MigrationContext migrationContext, List migrations) throws MigrationException + { + int taskCount = 0; + // Roll through once, just printing out what we'll do + for (Iterator i = migrations.iterator(); i.hasNext();) + { + MigrationTask task = (MigrationTask) i.next(); + if (migrationRunnerStrategy + .shouldMigrationRun(task.getLevel().intValue(), patchInfoStore)) + { + log.info("Will execute patch task '" + getTaskLabel(task) + "'"); + log.debug("Task will execute in context '" + migrationContext + "'"); + taskCount++; + } + } + if (taskCount > 0) + { + log.info("A total of " + taskCount + " patch tasks will execute."); + } + else + { + log.info("System up-to-date. No patch tasks will execute."); + } + return taskCount; + } + + public MigrationRunnerStrategy getMigrationRunnerStrategy() + { + return migrationRunnerStrategy; + } +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/MigrationRunnerFactory.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/MigrationRunnerFactory.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/MigrationRunnerFactory.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,60 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration; + +import org.apache.commons.lang.StringUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Returns the strategy we should we apply to decide if a patch needs to be applied + * or we should rollback + * + * @author Oscar Gonzalez (oscar@tacitknowledge.com) + */ + +public class MigrationRunnerFactory +{ + + private static Log log = LogFactory.getLog(MigrationRunnerFactory.class); + public static final String DEFAULT_MIGRATION_STRATEGY = "com.tacitknowledge.util.migration.OrderedMigrationRunnerStrategy"; + + public static MigrationRunnerStrategy getMigrationRunnerStrategy(String strategy) + { + + log.info("Strategy received '" + strategy + "'"); + + if (StringUtils.isBlank(strategy)) + { + return new OrderedMigrationRunnerStrategy(); + + } + + try + { + Class c = Class.forName(strategy.trim()); + MigrationRunnerStrategy runnerStrategy = (MigrationRunnerStrategy) c.newInstance(); + return runnerStrategy; + } + catch (Exception e) + { + throw new IllegalArgumentException("Strategy selected " + strategy + " cannot be instantiated ", e); + } + + + } + +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/MigrationRunnerStrategy.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/MigrationRunnerStrategy.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/MigrationRunnerStrategy.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,60 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration; + +import java.util.List; + +/** + * Dictates the methods that different algorithms should run when we + * try to apply patches or get information about the patches that need to be run. + * + * @author Oscar Gonzalez (oscar@tacitknowledge.com) + * @author Hemri Herrera (hemri@tacitknowledge.com) + * @author Ulises Pulido (upulido@tacitknowledge.com) + */ + +public interface MigrationRunnerStrategy +{ + /** + * Determines if a MigrationTask is able to run. + * + * @param migrationLevel of the MigrationTask to be check as in int + * @param patchInfoStore object representing patch level information + * @return boolean value telling us if we should run the migration or not. + */ + public boolean shouldMigrationRun(int migrationLevel, PatchInfoStore patchInfoStore) throws MigrationException; + + /** + * Determines if two stores are synchronized to each other. + * + * @param currentPatchInfoStore + * @param patchInfoStore + * @return + * @throws MigrationException + */ + public boolean isSynchronized(PatchInfoStore currentPatchInfoStore, PatchInfoStore patchInfoStore) throws MigrationException; + + /** + * Retrieves all tasks that are candidates for rollback. + * + * @param allMigrationTasks + * @param rollbackLevels + * @param currentPatchInfoStore + * @return + * @throws MigrationException + */ + public List getRollbackCandidates(List allMigrationTasks, int[] rollbackLevels, PatchInfoStore currentPatchInfoStore) throws MigrationException; +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/MigrationTask.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/MigrationTask.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/MigrationTask.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,47 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration; + +/** + * A single, idempotent migration task. + * + * @author Scott Askew (scott@tacitknowledge.com) + */ +public interface MigrationTask extends Comparable +{ + /** + * Performs a migration + * + * @param context the MigrationContext for this run + * @throws MigrationException if an unexpected error occurred + */ + public void migrate(MigrationContext context) throws MigrationException; + + /** + * Returns the name of this migration task. + * + * @return the name of this migration task + */ + public String getName(); + + /** + * Returns the relative order in which this migration should occur. + * + * @return the relative order in which this migration should occur; may never + * return null + */ + public Integer getLevel(); +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/MigrationTaskSource.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/MigrationTaskSource.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/MigrationTaskSource.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,37 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration; + +import java.util.List; + +/** + * A source of MigrationTasks. + * + * @author Scott Askew (scott@tacitknowledge.com) + */ +public interface MigrationTaskSource +{ + /** + * Returns a list of MigrationTasks that are in the given + * package. + * + * @param packageName to package to search for migration tasks + * @return a list of migration tasks; if not tasks were found, then an empty + * list must be returned. + * @throws MigrationException if an unrecoverable error occurs + */ + public List getMigrationTasks(String packageName) throws MigrationException; +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/MigrationTaskSupport.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/MigrationTaskSupport.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/MigrationTaskSupport.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,130 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration; + +/** + * Convenience base class for migration tasks. + * + * @author Scott Askew (scott@tacitknowledge.com) + * @author Artie Pesh-Imam (apeshimam@tacitknowledge.com) + */ +public abstract class MigrationTaskSupport implements RollbackableMigrationTask +{ + protected boolean isRollbackSupported = false; + + /** + * The name of this migration task + */ + private String name = this.getClass().getName(); + + /** + * The relative order in which this test should run + */ + private Integer level; + + /** + * {@inheritDoc} + */ + public String getName() + { + return name; + } + + /** + * Sets the name of this migration task. + * + * @param name the name of this migration task + */ + public void setName(String name) + { + this.name = name; + } + + /** + * {@inheritDoc} + */ + public Integer getLevel() + { + return level; + } + + /** + * Sets the relative order in which this test should run + * + * @param lvl the relative order in which this test should run + */ + public void setLevel(Integer lvl) + { + this.level = lvl; + } + + /** + * {@inheritDoc} + */ + public int compareTo(Object o) + { + MigrationTask task = (MigrationTask) o; + if (task.getLevel() == null) + { + return 1; + } + return getLevel().compareTo(task.getLevel()); + } + + /** + * By default, this method is not supported. + */ + public void down(MigrationContext context) throws MigrationException + { + throw new UnsupportedOperationException("This method is not supported by this task."); + } + + /** + * @return a boolean indicating if rollback is supported + */ + public boolean isRollbackSupported() + { + return isRollbackSupported; + } + + /** + * Sets the isRollbackSupported attribute + * + * @param isRollbackSupported + */ + public void setRollbackSupported(boolean isRollbackSupported) + { + this.isRollbackSupported = isRollbackSupported; + } + + /** + * By default, this method delegates to the up method. + */ + public void migrate(MigrationContext context) throws MigrationException + { + up(context); + } + + /** + * By default, this method is a no-op. If a legacy task extends + * MigrationTaskSupport but does not implement up, it would cause a + * compilation error. This no-op method resolves that issue. + */ + public void up(MigrationContext context) throws MigrationException + { + // no op + } +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/MissingPatchMigrationRunnerStrategy.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/MissingPatchMigrationRunnerStrategy.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/MissingPatchMigrationRunnerStrategy.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,90 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tacitknowledge.util.migration; + + +import org.apache.commons.lang.ArrayUtils; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Set; + +/* + * @author Hemri Herrera (hemri@tacitknowledge.com) + * @author Ulises Pulido (upulido@tacitknowledge.com) + */ + +public class MissingPatchMigrationRunnerStrategy implements MigrationRunnerStrategy +{ + + public boolean shouldMigrationRun(int migrationLevel, PatchInfoStore patchInfoStore) throws MigrationException + { + + if (patchInfoStore == null) + { + throw new IllegalArgumentException("Patch Info Store should not be null"); + } + + return !patchInfoStore.isPatchApplied(migrationLevel); + } + + public boolean isSynchronized(PatchInfoStore currentPatchInfoStore, PatchInfoStore patchInfoStore) throws MigrationException + { + + if (currentPatchInfoStore == null || patchInfoStore == null) + { + throw new IllegalArgumentException("currentPatchInfoStore and patchInfoStore should not be null"); + } + Set currentPatchInfoStorePatchesApplied = currentPatchInfoStore.getPatchesApplied(); + Set patchInfoStorePatchesApplied = patchInfoStore.getPatchesApplied(); + + return currentPatchInfoStorePatchesApplied.equals(patchInfoStorePatchesApplied); + } + + public List getRollbackCandidates(List allMigrationTasks, int[] rollbackLevels, PatchInfoStore currentPatchInfoStore) throws MigrationException + { + + validateRollbackLevels(rollbackLevels); + + List rollbacksLevelList = Arrays.asList(ArrayUtils.toObject(rollbackLevels)); + List rollbackCandidates = new ArrayList(); + + + for (MigrationTask migrationTask : allMigrationTasks) + { + if (rollbacksLevelList.contains(migrationTask.getLevel()) + && currentPatchInfoStore.isPatchApplied(migrationTask.getLevel())) + { + rollbackCandidates.add(migrationTask); + } + } + + return rollbackCandidates; + } + + private void validateRollbackLevels(int[] rollbackLevels) throws MigrationException + { + if (rollbackLevels == null) + { + throw new MigrationException("rollbackLevels should not be null"); + } + + if (rollbackLevels.length == 0) + { + throw new MigrationException("rollbackLevels should not be empty"); + } + } +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/OrderedMigrationRunnerStrategy.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/OrderedMigrationRunnerStrategy.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/OrderedMigrationRunnerStrategy.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,94 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tacitknowledge.util.migration; + +import org.apache.commons.collections.CollectionUtils; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Helps to get information about how the migrations should run based in an ordered stategy, i.e. + * If the level of the MigrationTask is greater than the current level and + * the MigrationTaskhas not yet been applied. Then the information of that migration + * is considered missing. + * + * @author Oscar Gonzalez (oscar@tacitknowledge.com) + * @author Hemri Herrera (hemri@tacitknowledge.com) + * @author Ulises Pulido (upulido@tacitknowledge.com) + */ +public class OrderedMigrationRunnerStrategy implements MigrationRunnerStrategy +{ + public boolean shouldMigrationRun(int migrationLevel, PatchInfoStore patchInfoStore) throws MigrationException + { + return migrationLevel > patchInfoStore.getPatchLevel(); + } + + public boolean isSynchronized(PatchInfoStore currentPatchInfoStore, PatchInfoStore patchInfoStore) throws MigrationException + { + + if (currentPatchInfoStore == null || patchInfoStore == null) + { + throw new IllegalArgumentException("currentPatchInfoStore and patchInfoStore should not be null"); + } + + return currentPatchInfoStore.getPatchLevel() == patchInfoStore.getPatchLevel(); + } + + public List getRollbackCandidates(List allMigrationTasks, int[] rollbackLevels, PatchInfoStore currentPatchInfoStore) throws MigrationException + { + validateRollbackLevel(rollbackLevels); + + int rollbackLevel = rollbackLevels[0]; + int currentPatchLevel = currentPatchInfoStore.getPatchLevel(); + + if (currentPatchLevel < rollbackLevel) + { + throw new MigrationException( + "The rollback patch level cannot be greater than the current patch level"); + } + + PatchRollbackPredicate rollbackPredicate = new PatchRollbackPredicate(currentPatchLevel, + rollbackLevel); + List migrationCandidates = new ArrayList(); + migrationCandidates.addAll(allMigrationTasks); + CollectionUtils.filter(migrationCandidates, rollbackPredicate); + Collections.sort(migrationCandidates); + // need to reverse the list do we apply the rollbacks in descending + // order + Collections.reverse(migrationCandidates); + return migrationCandidates; + + } + + private void validateRollbackLevel(int[] rollbackLevels) throws MigrationException + { + if (rollbackLevels == null) + { + throw new MigrationException("rollbackLevels should not be null"); + } + + if (rollbackLevels.length == 0) + { + throw new MigrationException("rollbackLevels should not be empty"); + } + + if (rollbackLevels.length > 1) + { + throw new MigrationException("OrderedMigrationRunnerStrategy only supports one rollbackLevel"); + } + } +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/PatchInfoStore.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/PatchInfoStore.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/PatchInfoStore.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,98 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration; + +import java.util.Set; + +/** + * Interface for the persistence of information related to the patch level + * of the system, as well as whether patches are currently being applied + * + * @author Mike Hardy (mike@tacitknowledge.com) + * @author Hemri Herrera (hemri@tacitknowledge.com) + * @author Ulises Pulido (upulido@tacitknowledge.com) + */ +public interface PatchInfoStore +{ + /** + * Creates the patch storage area if it has not been done before + * + * @throws MigrationException if creation is unsuccessful + */ + public void createPatchStoreIfNeeded() throws MigrationException; + + /** + * Returns the current patch level of the system + * + * @return the current patch level of the system + * @throws MigrationException if it is not possible to get the patch level + */ + public int getPatchLevel() throws MigrationException; + + /** + * Updates the system patch level to the specified value + * + * @param level the new system patch level + * @throws MigrationException if updating the patch level failed + */ + public void updatePatchLevel(int level) throws MigrationException; + + /** + * Determines if the patch store is already locked + * + * @return true if the patch store is already locked + * @throws MigrationException if checking for the lock fails + */ + public boolean isPatchStoreLocked() throws MigrationException; + + /** + * Places a lock for this system on the patch store + * + * @throws MigrationException if locking the store fails + * @throws IllegalStateException if a lock already exists + */ + public void lockPatchStore() throws MigrationException, IllegalStateException; + + /** + * Removes any locks for this system on the patch store + * + * @throws MigrationException if unlocking the store fails + */ + public void unlockPatchStore() throws MigrationException; + + /** + * Determines if a given patch has been applied in the system + * + * @throws MigrationException if unlocking the store fails + */ + public boolean isPatchApplied(int patchLevel) throws MigrationException; + + /** + * Updates the system patch level to the specified value after rollback + * + * @param rollbackLevel the new system patch level + * @throws MigrationException if updating the patch level failed + */ + public void updatePatchLevelAfterRollBack(int rollbackLevel) throws MigrationException; + + /** + * Obtains all patches applied in the system. + * + * @return a set containing all patches number applied in the system. + * @throws MigrationException if retrieving patches fails. + */ + public Set getPatchesApplied() throws MigrationException; +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/PatchRollbackPredicate.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/PatchRollbackPredicate.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/PatchRollbackPredicate.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,73 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration; + +import org.apache.commons.collections.Predicate; + +/** + * This class defines a predicate for CollectionUtils which evaluates if a + * given RollbackableMigrationTask should remain in the collection + * + * @author Artie Pesh-Imam (apeshimam@tacitknowledge.com) + * @see Predicate + */ +public class PatchRollbackPredicate implements Predicate +{ + /* initialize both to max value */ + private int rollbackPatchLevel = Integer.MAX_VALUE; + private int currentPatchLevel = Integer.MAX_VALUE; + + /** + * Constructor for this predicate. The current patch level and the rollback + * patch level are set in this constructor. + * + * @param currentPatchLevel + * @param rollbackPatchLevel + */ + public PatchRollbackPredicate(int currentPatchLevel, int rollbackPatchLevel) + { + this.rollbackPatchLevel = rollbackPatchLevel; + this.currentPatchLevel = currentPatchLevel; + } + + /** + * The evaluate method returns false if the passed object is: + *
    + *
  • null
  • + *
  • not an instance of RollbackableMigrationTask
  • + *
  • the level associated with the task is less than the rollback patch level
  • + *
  • the level associated with the task is greater than the current patch level
  • + *
+ * + * @param obj the Object to be evaluated + * @boolean a boolean indicating if the object falls within the valid range for + * this rollback + */ + public boolean evaluate(Object obj) + { + + if (obj == null) + return false; + + if (!(obj instanceof RollbackableMigrationTask)) + return false; + + RollbackableMigrationTask task = (RollbackableMigrationTask) obj; + final int level = task.getLevel().intValue(); + + return ((level > rollbackPatchLevel) && (level <= currentPatchLevel)); + } +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/RollbackBroadcaster.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/RollbackBroadcaster.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/RollbackBroadcaster.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,145 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * Manages the MessageListener that are associated with a + * Rollback instance. + * + * @author Artie Pesh-Imam (apeshimam@tacitknowledge.com) + */ +class RollbackBroadcaster +{ + /** + * Used by notifyListeners to indicate that listeners should + * be informed that a task is about to start. + */ + public static final int TASK_START = 1; + + /** + * Used by notifyListeners to indicate that listeners should + * be informed that a task has successfully completed. + */ + public static final int TASK_SUCCESS = 2; + + /** + * Used by notifyListeners to indicate that listeners should + * be informed that a task failed. + */ + public static final int TASK_FAILED = 3; + + /** + * The listeners interested in being notified of migration task events. + */ + private List listeners = new ArrayList(); + + /** + * Notifies all registered listeners of a migration task event. + * + * @param task the task that is being or that has been executed + * @param context the context in which the task was executed + * @param eventType TASK_START, TASK_SUCCESS, or TASK_FAIL + * @param rollbackLevel + * @param e the exception thrown by the task if the task failed + * @throws MigrationException if one of the listeners threw an exception + */ + public void notifyListeners(RollbackableMigrationTask task, + MigrationContext context, MigrationException e, int eventType, int rollbackLevel) + throws MigrationException + { + for (Iterator i = listeners.iterator(); i.hasNext();) + { + RollbackListener listener = (RollbackListener) i.next(); + switch (eventType) + { + case TASK_START: + listener.rollbackStarted(task, context); + break; + + case TASK_SUCCESS: + listener.rollbackSuccessful(task, rollbackLevel, context); + break; + + case TASK_FAILED: + listener.rollbackFailed(task, context, e); + break; + + default: + throw new IllegalArgumentException("Unknown event type"); + } + } + } + + /** + * Notifies all registered listeners of a migration task event. + * + * @param task the task that is being or that has been executed + * @param context the context in which the task was executed + * @param eventType TASK_START, TASK_SUCCESS, or TASK_FAIL + * @throws MigrationException if one of the listeners threw an exception + */ + public void notifyListeners(RollbackableMigrationTask task, + MigrationContext context, int eventType, int rollbackLevel) throws MigrationException + { + notifyListeners(task, context, null, eventType, rollbackLevel); + } + + /** + * Registers the given MigrationListener as being interested + * in migration task events. + * + * @param listener the listener to add; may not be null + */ + public void addListener(RollbackListener listener) + { + if (listener == null) + { + throw new IllegalArgumentException("listener cannot be null"); + } + listeners.add(listener); + } + + /** + * Removes the given MigrationListener from the list of + * listeners associated with the Migration instance. + * + * @param listener the listener to add; may not be null + * @return true if the listener was located and removed, + * otherwise false. + */ + public boolean removeListener(RollbackListener listener) + { + if (listener == null) + { + throw new IllegalArgumentException("listener cannot be null"); + } + return listeners.remove(listener); + } + + /** + * Get the list of listeners + * + * @return List of MigrationListener objects + */ + public List getListeners() + { + return listeners; + } +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/RollbackListener.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/RollbackListener.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/RollbackListener.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,69 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration; + +import java.util.Properties; + +/** + * Receives notifications regarding task rollbacks. + * + * @author Artie Pesh-Imam (apeshimam@tacitknowledge.com) + */ +public interface RollbackListener extends MigrationListener +{ + /** + * Initialize the rollback listener. This provides an opportunity + * for the RollbackListener to initialize itself before patching + * begins. + * + * @param properties The properties loaded from migration.properties + */ + public void initialize(String systemName, Properties properties) throws MigrationException; + + /** + * Notifies the listener that the given rollback is about to start execution. + * + * @param task the recently finished task + * @param context the migration context + * @throws MigrationException if an unrecoverable error occurs + */ + public void rollbackStarted(RollbackableMigrationTask task, MigrationContext context) + throws MigrationException; + + /** + * Notifies the listener that the given roolback has completed execution. + * + * @param task the recently finished task + * @param context the migration context + * @throws MigrationException if an unrecoverable error occurs + */ + public void rollbackSuccessful(RollbackableMigrationTask task, int rollbackLevel, + MigrationContext context) + throws MigrationException; + + /** + * Notifies the listener that the given rollback has completed execution. + * + * @param task the recently finished task + * @param context the migration context + * @param e the MigrationException thrown by the task + * @throws MigrationException if an unrecoverable error occurs + */ + public void rollbackFailed(RollbackableMigrationTask task, + MigrationContext context, MigrationException e) throws MigrationException; + +} + Index: 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/RollbackableMigrationTask.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/RollbackableMigrationTask.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/RollbackableMigrationTask.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,63 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration; + +/** + * A single, idempotent and migration task, which also supports rollbacks. + * + * @author Artie Pesh-Imam (apeshimam@tacitknowledge.com) + */ +public interface RollbackableMigrationTask extends MigrationTask +{ + + /** + * Performs a migration + * + * @param context the MigrationContext for this run. + * @throws MigrationException if an unexpected error occurs + */ + public void up(MigrationContext context) throws MigrationException; + + /** + * Performs a rollback + * + * @param context the MigrationContext for this run. + * @throws MigrationException if an unexpected error occurrs + */ + public void down(MigrationContext context) throws MigrationException; + + /** + * Returns a boolean indicating if this task can be rolled back. + * + * @return a boolean indicating if the task can be rolled back. + */ + public boolean isRollbackSupported(); + + /** + * Returns the name of this migration task. + * + * @return the name of this migration task + */ + public String getName(); + + /** + * Returns the relative order in which this migration should occur. + * + * @return the relative order in which this migration should occur; may never + * return null + */ + public Integer getLevel(); +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/AutoPatchService.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/AutoPatchService.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/AutoPatchService.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,296 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration.jdbc; + +import com.tacitknowledge.util.migration.MigrationException; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import javax.sql.DataSource; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * Creates an AutoPatch environment using a configuration supplied by dependency + * injection. Exports a hook that can be called to execute AutoPatch after configuration + * + * @author Scott Askew (scott@tacitknowledge.com) + */ +public class AutoPatchService extends JdbcMigrationLauncherFactory +{ + /** + * Class logger + */ + private static Log log = LogFactory.getLog(AutoPatchService.class); + + /** + * The name of the schema to patch + */ + private String systemName = null; + + /** + * The data source used to patch the schema + */ + private DataSource dataSource = null; + + /** + * The type of database + */ + private String databaseType = null; + + /** + * The path to the SQL patches + */ + private String patchPath = null; + + /** + * The patch to the post-patch tasks + */ + private String postPatchPath = null; + + /** + * Whether we really want to apply the patches, or just look + */ + private boolean readOnly = false; + + /** + * The number of times to wait for the lock before overriding it. -1 is infinite + */ + private int lockPollRetries = -1; + + /** + * A set of contexts, in case you want multi-node patches + */ + private List contexts = new ArrayList(); + + /** + * Patches the database, if necessary. + * + * @throws MigrationException if an unexpected error occurs + */ + public void patch() throws MigrationException + { + JdbcMigrationLauncher launcher = getLauncher(); + + try + { + log.info("Applying patches...."); + int patchesApplied = launcher.doMigrations(); + log.info("Applied " + patchesApplied + " " + + (patchesApplied == 1 ? "patch" : "patches") + "."); + } + catch (MigrationException e) + { + throw new MigrationException("Error applying patches", e); + } + } + + /** + * Configure and return a JdbcMigrationLauncher to use for patching + * + * @return JdbcMigrationLauncher configured from injected properties + */ + public JdbcMigrationLauncher getLauncher() + { + JdbcMigrationLauncher launcher = getJdbcMigrationLauncher(); + + // If no one has added a collection of contexts to the service, + // then take the single-context property configuration and set it in + if (contexts.size() == 0) + { + launcher.addContext(getContext()); + } + // otherwise, add the collection of contexts in there + else + { + for (Iterator i = contexts.iterator(); i.hasNext();) + { + launcher.addContext((JdbcMigrationContext) i.next()); + } + } + launcher.setPatchPath(getPatchPath()); + launcher.setPostPatchPath(getPostPatchPath()); + launcher.setReadOnly(isReadOnly()); + launcher.setLockPollRetries(getLockPollRetries()); + return launcher; + } + + /** + * Configure and return a DataSourceMigrationContext from this object's + * injected properties + * + * @return DataSourceMigrationContext configured from injected properties + */ + protected DataSourceMigrationContext getContext() + { + DataSourceMigrationContext context = getDataSourceMigrationContext(); + context.setSystemName(getSystemName()); + context.setDatabaseType(new DatabaseType(getDatabaseType())); + context.setDataSource(getDataSource()); + return context; + } + + /** + * @return Returns the dataSource. + */ + public DataSource getDataSource() + { + return dataSource; + } + + /** + * @param dataSource The dataSource to set. + */ + public void setDataSource(DataSource dataSource) + { + this.dataSource = dataSource; + } + + /** + * @return Returns the systemName. + */ + public String getSystemName() + { + return systemName; + } + + /** + * @param systemName The systemName to set. + */ + public void setSystemName(String systemName) + { + this.systemName = systemName; + } + + /** + * @return Returns the databaseType. + */ + public String getDatabaseType() + { + return databaseType; + } + + /** + * @param dialect The databaseType to set. + */ + public void setDatabaseType(String dialect) + { + this.databaseType = dialect; + } + + /** + * @return Returns the patchPath. + */ + public String getPatchPath() + { + return patchPath; + } + + /** + * @param patchPath The patchPath to set. + */ + public void setPatchPath(String patchPath) + { + this.patchPath = patchPath; + } + + /** + * @return Returns the postPatchPath. + */ + public String getPostPatchPath() + { + return postPatchPath; + } + + /** + * @param postPatchPath The postPatchPath to set. + */ + public void setPostPatchPath(String postPatchPath) + { + this.postPatchPath = postPatchPath; + } + + /** + * See if we are actually applying patches, or if it is just readonly + * + * @return boolean true if we will skip application + */ + public boolean isReadOnly() + { + return readOnly; + } + + /** + * Set whether or not to actually apply patches + * + * @param readOnly boolean true if we should skip application + */ + public void setReadOnly(boolean readOnly) + { + this.readOnly = readOnly; + } + + /** + * Return the number of times to poll the lock before overriding it. -1 is infinite + * + * @return int either -1 for infinite or number of times to poll before override + */ + public int getLockPollRetries() + { + return lockPollRetries; + } + + /** + * Set the number of times to poll the lock before overriding it. -1 is infinite + * + * @param lockPollRetries either -1 for infinite or number of times to poll before override + */ + public void setLockPollRetries(int lockPollRetries) + { + this.lockPollRetries = lockPollRetries; + } + + /** + * Get the list of database contexts for multi-node configuration + * + * @return List of JdbcMigrationContext objects for multi-node, or empty List + */ + public List getContexts() + { + return contexts; + } + + /** + * Set the list of database contexts for multi-node configuration + * + * @param contexts List of JdbcMigrationContext objects for multi-node, or empty list + */ + public void setContexts(List contexts) + { + this.contexts = contexts; + } + + /** + * Add a database context to the list of database contexts for multi-node configuration + * + * @param context the JdbcMigrationContext to use for multi-node patch application + */ + public void addContext(JdbcMigrationContext context) + { + contexts.add(context); + } +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/AutoPatchSupport.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/AutoPatchSupport.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/AutoPatchSupport.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,127 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration.jdbc; + +import com.tacitknowledge.util.migration.MigrationException; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +/** + * Support using AutoPatch via injection and allows you to directly set the patch level. + * Jacques Morel contributed this originally. + * + * @author Jacques Morel + */ +public class AutoPatchSupport +{ + /** + * Class logger + */ + private static Log log = LogFactory.getLog(AutoPatchSupport.class); + + /** + * The launcher we'll use + */ + private JdbcMigrationLauncher launcher; + + /** + * Create a new support component for the given system name. + * This will use create a factory for you. + * + * @param systemName the name of the system to be patched + * @throws MigrationException if there is a problem + * @see JdbcMigrationLauncherFactoryLoader + */ + public AutoPatchSupport(String systemName) throws MigrationException + { + this(new JdbcMigrationLauncherFactoryLoader().createFactory(), systemName); + } + + /** + * Create support component with the given factory and system name + * + * @param launcherFactory the factory to use for migrations + * @param systemName the system to patch + * @throws MigrationException if there is any problem + */ + public AutoPatchSupport(JdbcMigrationLauncherFactory launcherFactory, + String systemName) throws MigrationException + { + this(launcherFactory.createMigrationLauncher(systemName)); + } + + /** + * Create a support component with the given configured launcher + * + * @param launcher the launcher to use for the migrations + */ + public AutoPatchSupport(JdbcMigrationLauncher launcher) + { + this.launcher = launcher; + } + + /** + * Set the patch level to the specified level + * + * @param patchLevel the level to set the patch table to + * @throws MigrationException if the store can't be locked + */ + public void setPatchLevel(int patchLevel) throws MigrationException + { + Map contextMap = launcher.getContexts(); + Set contexts = contextMap.keySet(); + // FIXME test that setting the patch level works + for (Iterator i = contexts.iterator(); i.hasNext();) + { + JdbcMigrationContext migrationContext = (JdbcMigrationContext) i.next(); + PatchTable patchTable = (PatchTable) contextMap.get(migrationContext); + patchTable.lockPatchStore(); + patchTable.updatePatchLevel(patchLevel); + log.info("Set the patch level to " + patchLevel + " for context " + migrationContext); + patchTable.unlockPatchStore(); + } + } + + /** + * Get the current patch level + * + * @return int with the patch level in the patch database + * @throws MigrationException if there is a problem getting the patch level + */ + public int getPatchLevel() throws MigrationException + { + Map contextMap = launcher.getContexts(); + // Any of the patch tables for any of the contexts should be fine, get the first + PatchTable firstPatchTable = (PatchTable) contextMap.values().iterator().next(); + // FIXME test that getting the patch level works + return firstPatchTable.getPatchLevel(); + } + + /** + * Get the highest patch level of all the configured patches + * + * @return int with the highest patch level + * @throws MigrationException if there is problem getting the patch level + */ + public int getHighestPatchLevel() throws MigrationException + { + return launcher.getNextPatchLevel() - 1; + } +} \ No newline at end of file Index: 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/DataSourceMigrationContext.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/DataSourceMigrationContext.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/DataSourceMigrationContext.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,224 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration.jdbc; + +import com.tacitknowledge.util.migration.MigrationException; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.SQLException; + +/** + * Provides JDBC resources to migration tasks. + * + * @author Scott Askew (scott@tacitknowledge.com) + */ +public class DataSourceMigrationContext implements JdbcMigrationContext +{ + /** + * Class logger + */ + private static Log log = LogFactory.getLog(JdbcMigrationContext.class); + + /** + * The database connection to use + */ + private Connection connection = null; + + /** + * The DataSource to use + */ + private DataSource dataSource = null; + + /** + * The name of the system being patched + */ + private String systemName = null; + + /** + * The name of the system being patched + */ + private DatabaseType databaseType = null; + + /** + * The databasename + */ + private String databaseName = ""; + + /** + * Returns the database connection to use + * + * @return the database connection to use + * @throws SQLException if an unexpected error occurs + */ + public Connection getConnection() throws SQLException + { + if ((connection == null) || connection.isClosed()) + { + DataSource ds = getDataSource(); + if (ds != null) + { + connection = ds.getConnection(); + } + else + { + throw new SQLException("Datasource is null"); + } + } + return connection; + } + + /** + * {@inheritDoc} + */ + public void commit() throws MigrationException + { + try + { + if (getConnection().getAutoCommit()) + { + // FIXME we need to set autocommit to false on connections for AutoPatch, then commit/rollback and re-set the autocommit state + // correctly before handing them back, then this will go away + log.debug("AutoPatch issue - commit called on connection with autoCommit==true - would break on JBoss if it went through"); + return; + } + getConnection().commit(); + } + catch (SQLException e) + { + throw new MigrationException("Error committing SQL transaction", e); + } + } + + /** + * {@inheritDoc} + */ + public void rollback() throws MigrationException + { + try + { + if (getConnection().getAutoCommit()) + { + // FIXME we need to set autocommit to false on connections for AutoPatch, then commit/rollback and re-set the autocommit state + // correctly before handing them back, then this will go away + log.warn("AutoPatch issue - rollback called on connection with autoCommit==true - would break in JBoss if it went through"); + return; + } + getConnection().rollback(); + } + catch (SQLException e) + { + throw new MigrationException("Could not rollback SQL transaction", e); + } + } + + /** + * Returns the type of database being patched. + * + * @return the type of database being patched + */ + public DatabaseType getDatabaseType() + { + return databaseType; + } + + /** + * Returns the type of database being patched. + * + * @param type the type of database being patched + */ + public void setDatabaseType(DatabaseType type) + { + this.databaseType = type; + } + + /** + * @return Returns the dataSource. + */ + public DataSource getDataSource() + { + return dataSource; + } + + /** + * @param dataSource The dataSource to set. + */ + public void setDataSource(DataSource dataSource) + { + this.dataSource = dataSource; + } + + /** + * @return Returns the systemName. + */ + public String getSystemName() + { + return systemName; + } + + /** + * Sets the system name. + * + * @param name the name of the system to patch + */ + public void setSystemName(String name) + { + if (name == null) + { + throw new IllegalArgumentException("systemName cannot be null"); + } + if (name.length() > MAX_SYSTEMNAME_LENGTH) + { + throw new IllegalArgumentException("systemName cannot be longer than " + + MAX_SYSTEMNAME_LENGTH + " characters"); + } + this.systemName = name; + } + + /** + * Useful for debugging + * + * @return String with state information + */ + public String toString() + { + return + "DataSourceMigrationContext[" + + getDatabaseType() + "/" + + getSystemName() + "/" + + getDataSource() + "]"; + } + + /** + * @override {@link JdbcMigrationContext#getDatabaseName()} + */ + public String getDatabaseName() + { + return this.databaseName; + } + + /** + * Set the database name. + * + * @param databaseName the name + */ + public void setDatabaseName(String databaseName) + { + this.databaseName = databaseName; + } +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/DatabaseType.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/DatabaseType.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/DatabaseType.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,187 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration.jdbc; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; + +/** + * Defines a type of database (e.g. oracle or postgres. This + * is used to help define the SQL that is used to update the patch table and as a hint + * used while parsing SQL patch files. + *

+ * Each type of database that this AutoPatch supports (currently PostgreSQL and Oracle) has its + * own database-type.properties file containing the database-specific SQL and DDL. The + * required keys are: + *

    + *
  • patches.create - DDL that creates the patches table
  • + *
  • level.create - SQL that inserts a new patch level record for the system
  • + *
  • level.read - SQL that selects the current patch level of the system
  • + *
  • level.update - SQL that updates the current patch level of the system
  • + *
  • lock.read - Returns 'T' if the system patch lock is in use, 'F' otherwise
  • + *
  • lock.obtain - SQL that selects the patch lock for the system
  • + *
  • lock.release - SQL that releases the patch lock for the system
  • + *
+ *

+ * Use postgres.properties or oracle.properties as a baseline for adding + * additional database types. + *

+ * You can override the values in the the database.properties file by placing properties in + * your migration.properties. The property names should remain the same and they should be + * prepended with the database type and a period. + *

+ * For example, the property, supportsMultipleStatements would be overridden + * for mysql using the property name mysql.supportsMultipleStatements. + * + * @author Scott Askew (scott@tacitknowledge.com) + */ +public class DatabaseType +{ + /** + * The SQL statements and properties that are unique to this database flavor. + */ + private Properties databaseProperties; + + /** + * The migration.properties that may override databaseProperties + */ + private Properties migrationProperties; + + /** + * The database type + */ + private String databaseType = ""; + + /** + * Creates a new DatabaseType. + * + * @param databaseType the type of database + */ + public DatabaseType(String databaseType) + { + // required to keep old behavior where it expects the properties file in the same location + // as the DatabaseType class because it was loaded via Class.getResourceAsStream() + String className = this.getClass().getName(); + int index = className.lastIndexOf("."); + String databasePropertiesFilename = className.substring(0, index).replace(".", "/") + "/" + databaseType + ".properties"; + databaseProperties = loadProperties(databasePropertiesFilename, this.getClass().getClassLoader()); + migrationProperties = new Properties(); + try + { + migrationProperties = loadProperties("migration.properties", Thread.currentThread().getContextClassLoader()); + } + catch (IllegalArgumentException iae) + { + // this is okay, in this class, migration.properties is only used to override SQL + } + this.databaseType = databaseType; + } + + protected Properties loadProperties(String propertiesFilename, ClassLoader loader) + { + Properties properties = new Properties(); + + InputStream is = loader.getResourceAsStream(propertiesFilename); + if (is == null) + { + is = this.getClass().getResourceAsStream(propertiesFilename); + } + + if (is == null) + { + throw new IllegalArgumentException("Could not find properties " + + " file " + propertiesFilename + "."); + } + try + { + properties.load(is); + } + catch (IOException e) + { + throw new IllegalArgumentException("Could not read " + propertiesFilename); + } + finally + { + try + { + is.close(); + } + catch (IOException e1) + { + // not important + } + } + + return properties; + } + + /** + * Return the name of the database type + * + * @return String containing the name of the database type + */ + public String getDatabaseType() + { + return databaseType; + } + + /** + * Returns the named property. + * + * @param propertyName the property to retrieve + * @return the requested property, or null if it doesn't exist + */ + public String getProperty(String propertyName) + { + String value = null; + + if (migrationProperties.containsKey(databaseType + "." + propertyName)) + { + value = migrationProperties.getProperty(databaseType + "." + propertyName); + } + else + { + value = databaseProperties.getProperty(propertyName); + } + + return value; + } + + /** + * Determines if the database supports multiple SQL and DDL statements in a single + * Statement.execute call. + * + * @return if the database supports multiple SQL and DDL statements in a single + * Statement.execute call. + */ + public boolean isMultipleStatementsSupported() + { + String value = this.getProperty("supportsMultipleStatements"); + String multiStatement = (value != null) ? value : "false"; + return Boolean.valueOf(multiStatement).booleanValue(); + } + + /** + * Useful for debugging + * + * @return String containing state information + */ + public String toString() + { + return "DatabaseType " + getDatabaseType(); + } +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/DistributedAutoPatchService.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/DistributedAutoPatchService.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/DistributedAutoPatchService.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,270 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration.jdbc; + +import com.tacitknowledge.util.migration.DistributedMigrationProcess; +import com.tacitknowledge.util.migration.MigrationException; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import javax.sql.DataSource; +import java.util.HashMap; +import java.util.Map; + +/** + * Creates an DistributedAutoPatch environment using a configuration supplied by dependency + * injection. Exports a hook that can be called to execute AutoPatch after configuration. + * + * @author Mike Hardy (mike@tacitknowledge.com) + */ +public class DistributedAutoPatchService extends DistributedJdbcMigrationLauncherFactory +{ + /** + * Class logger + */ + private static Log log = LogFactory.getLog(AutoPatchService.class); + + /** + * The name of the schema to patch + */ + private String systemName = null; + + /** + * The data source used to store data about all the systems being patched + */ + private DataSource dataSource = null; + + /** + * The type of database + */ + private String databaseType = null; + + /** + * The AutoPatchServices this object should control + */ + private AutoPatchService[] controlledSystems = null; + + /** + * The patch to the post-patch tasks + */ + private String postPatchPath = null; + + /** + * Whether we actually want to apply patches, or just look + */ + private boolean readOnly = false; + + /** + * The number of times to wait for the lock before overriding it. -1 is infinite + */ + private int lockPollRetries = -1; + + /** + * Patches all of the databases in your distributed system, if necessary. + * + * @throws MigrationException if an unexpected error occurs + */ + public void patch() throws MigrationException + { + DistributedJdbcMigrationLauncher launcher = getLauncher(); + + try + { + log.info("Applying patches...."); + int patchesApplied = launcher.doMigrations(); + log.info("Applied " + patchesApplied + " " + + (patchesApplied == 1 ? "patch" : "patches") + "."); + } + catch (MigrationException e) + { + throw new MigrationException("Error applying patches", e); + } + } + + /** + * Configure and return a DistributedJdbcMigrationLauncher to use for patching + * + * @return DistributedJdbcMigrationLauncher configured from injected properties + */ + public DistributedJdbcMigrationLauncher getLauncher() + { + DistributedJdbcMigrationLauncher launcher = getDistributedJdbcMigrationLauncher(); + launcher.addContext(getContext()); + + // Grab the controlled systems and subjugate them + HashMap controlledLaunchers = new HashMap(); + for (int i = 0; i < controlledSystems.length; i++) + { + AutoPatchService controlledSystem = controlledSystems[i]; + JdbcMigrationLauncher subLauncher = controlledSystem.getLauncher(); + + // We need the system name, all of the contexts should be the same, take the first + // FIXME should the system name be on the launcher instead of the context? + Map subContextMap = subLauncher.getContexts(); + String subSystemName = + ((JdbcMigrationContext) subContextMap.keySet().iterator().next()).getSystemName(); + controlledLaunchers.put(subSystemName, subLauncher); + + // Make sure the controlled migration process gets migration events + launcher.getMigrationProcess().addListener(subLauncher); + } + + ((DistributedMigrationProcess) launcher.getMigrationProcess()) + .setControlledSystems(controlledLaunchers); + launcher.setPostPatchPath(getPostPatchPath()); + launcher.setReadOnly(isReadOnly()); + launcher.setLockPollRetries(getLockPollRetries()); + + return launcher; + } + + /** + * Configure and return a DataSourceMigrationContext from this object's + * injected properties + * + * @return DataSourceMigrationContext configured from injected properties + */ + private DataSourceMigrationContext getContext() + { + DataSourceMigrationContext context = getDataSourceMigrationContext(); + context.setSystemName(getSystemName()); + context.setDatabaseType(new DatabaseType(getDatabaseType())); + context.setDataSource(getDataSource()); + return context; + } + + /** + * @return Returns the dataSource. + */ + public DataSource getDataSource() + { + return dataSource; + } + + /** + * @param dataSource The dataSource to set. + */ + public void setDataSource(DataSource dataSource) + { + this.dataSource = dataSource; + } + + /** + * @return Returns the systemName. + */ + public String getSystemName() + { + return systemName; + } + + /** + * @param systemName The systemName to set. + */ + public void setSystemName(String systemName) + { + this.systemName = systemName; + } + + /** + * @return Returns the databaseType. + */ + public String getDatabaseType() + { + return databaseType; + } + + /** + * @param dialect The databaseType to set. + */ + public void setDatabaseType(String dialect) + { + this.databaseType = dialect; + } + + /** + * @return the controlled AutoPatchService objects + */ + public AutoPatchService[] getControlledSystems() + { + return controlledSystems; + } + + /** + * Takes an Array of AutoPatchService objects to control when patching + * + * @param controlledSystems the AutoPatchService objects to control + */ + public void setControlledSystems(AutoPatchService[] controlledSystems) + { + this.controlledSystems = controlledSystems; + } + + /** + * @return Returns the postPatchPath. + */ + public String getPostPatchPath() + { + return postPatchPath; + } + + /** + * @param postPatchPath The postPatchPath to set. + */ + public void setPostPatchPath(String postPatchPath) + { + this.postPatchPath = postPatchPath; + } + + /** + * See if we are actually applying patches, or if it is just readonly + * + * @return boolean true if we will skip application + */ + public boolean isReadOnly() + { + return readOnly; + } + + /** + * Set whether or not to actually apply patches + * + * @param readOnly boolean true if we should skip application + */ + public void setReadOnly(boolean readOnly) + { + this.readOnly = readOnly; + } + + /** + * Return the number of times to poll the lock before overriding it. -1 is infinite + * + * @return int either -1 for infinite or number of times to poll before override + */ + public int getLockPollRetries() + { + return lockPollRetries; + } + + /** + * Set the number of times to poll the lock before overriding it. -1 is infinite + * + * @param lockPollRetries either -1 for infinite or number of times to poll before override + */ + public void setLockPollRetries(int lockPollRetries) + { + this.lockPollRetries = lockPollRetries; + } +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/DistributedJdbcMigrationLauncher.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/DistributedJdbcMigrationLauncher.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/DistributedJdbcMigrationLauncher.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,101 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration.jdbc; + +import com.tacitknowledge.util.migration.DistributedMigrationProcess; +import com.tacitknowledge.util.migration.MigrationException; +import com.tacitknowledge.util.migration.MigrationProcess; +import com.tacitknowledge.util.migration.MigrationRunnerFactory; + +/** + * Core starting point for a distributed database migration run. + * This class obtains a connection to the orchestration database, + * checks its patch level, delegates the actual execution of the + * migration tasks to a MigrationProcess instance, + * and then commits and cleans everything up at the end. + *

+ * This class is NOT threadsafe. + * + * @author Mike Hardy (mike@tacitknowledge.com) + */ +public class DistributedJdbcMigrationLauncher extends JdbcMigrationLauncher +{ + /** + * Create a new MigrationProcess and add a SqlScriptMigrationTaskSource + */ + public DistributedJdbcMigrationLauncher() + { + super(); + } + + /** + * Create a new MigrationLancher. + * + * @param context the JdbcMigrationContext to use. + */ + public DistributedJdbcMigrationLauncher(JdbcMigrationContext context) + { + super(context); + } + + /** + * Override the sub-class so we get a DistributedMigrationProcess instead of the + * normal one + * + * @return DistributedMigrationProcess + */ + public MigrationProcess getNewMigrationProcess() + { + DistributedMigrationProcess migrationProcess = new DistributedMigrationProcess(); + migrationProcess.setMigrationRunnerStrategy + (MigrationRunnerFactory.getMigrationRunnerStrategy(getMigrationStrategy())); + return migrationProcess; + } + + /** + * Starts the application migration process across all configured contexts + * + * @return the number of patches applied + * @throws MigrationException if an unrecoverable error occurs during + * the migration + */ + public int doMigrations() throws MigrationException + { + if (getContexts().size() == 0) + { + throw new MigrationException("You must configure a migration context"); + } + + return super.doMigrations(); + } + + /** + * Starts the application migration process across all configured contexts + * + * @return the number of patches applied + * @throws MigrationException if an unrecoverable error occurs during + * the migration + */ + public int doRollbacks(int rollbackLevel) throws MigrationException + { + if (getContexts().size() == 0) + { + throw new MigrationException("You must configure a migration context"); + } + + return super.doRollbacks(new int[]{rollbackLevel}); + } +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/DistributedJdbcMigrationLauncherFactory.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/DistributedJdbcMigrationLauncherFactory.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/DistributedJdbcMigrationLauncherFactory.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,257 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration.jdbc; + +import com.tacitknowledge.util.migration.DistributedMigrationProcess; +import com.tacitknowledge.util.migration.MigrationContext; +import com.tacitknowledge.util.migration.MigrationException; +import com.tacitknowledge.util.migration.jdbc.util.ConfigurationUtil; +import com.tacitknowledge.util.migration.jdbc.util.NonPooledDataSource; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.List; +import java.util.Properties; + +/** + * Creates and configures a new DistributedJdbcMigrationContext based on the values + * in the migration.properties file for the given system. This is a convenience + * class for systems that need to initialize the autopatch framework but do not to or can not + * configure the framework themselves. + *

+ * This factory expects a file named migration.properties to be in the + * root of the class path. This file must contain these properties (where systemName + * is the name of the system being patched): + * + * + * + * + *
Keydescription
systemName.contextThe context to use for orchestration
systemName.controlled.systemscomma-delimited systems to manage
+ *

+ * For each system in the controlled systems list, the properties file should contain + * information as directed in the documenation for JdbcMigrationLauncher. + *

+ *

+ * The systemName.listeners property only applies to the top level system name which manages + * all the sub-systems. + *

+ *

+ * If a new database node is detected in the migration.properties, the default behaviour is to + * stop the patch process. To force the new node to be 'brought up to date' with the other + * nodes, then set a system property named 'forcesync'. The value is not important, merely the + * existance is sufficient. + * + * @author Mike Hardy (mike@tacitknowledge.com) + * @author Alex Soto (apsoto@gmail.com) + * @see com.tacitknowledge.util.migration.jdbc.JdbcMigrationLauncher + */ +public class DistributedJdbcMigrationLauncherFactory extends JdbcMigrationLauncherFactory +{ + /** + * Class logger + */ + private static Log log = LogFactory.getLog(DistributedJdbcMigrationLauncherFactory.class); + + /** + * Creates and configures a new JdbcMigrationLauncher based on the + * values in the migration.properties file for the given system. + * + * @param systemName the system to patch + * @param propFile name of the properties file in the classpath + * @return a fully configured DistributedJdbcMigrationLauncher. + * @throws MigrationException if an unexpected error occurs + */ + public JdbcMigrationLauncher createMigrationLauncher(String systemName, String propFile) + throws MigrationException + { + log.info("Creating DistributedJdbcMigrationLauncher for system " + systemName); + DistributedJdbcMigrationLauncher launcher = getDistributedJdbcMigrationLauncher(); + configureFromMigrationProperties(launcher, systemName, propFile); + return launcher; + } + + /** + * Creates and configures a new JdbcMigrationLauncher based on the + * values in the migration.properties file for the given system. + * + * @param systemName the system to patch + * @return a fully configured DistributedJdbcMigrationLauncher. + * @throws MigrationException if an unexpected error occurs + */ + public JdbcMigrationLauncher createMigrationLauncher(String systemName) + throws MigrationException + { + log.info("Creating DistributedJdbcMigrationLauncher for system " + systemName); + DistributedJdbcMigrationLauncher launcher = getDistributedJdbcMigrationLauncher(); + configureFromMigrationProperties(launcher, systemName, + MigrationContext.MIGRATION_CONFIG_FILE); + return launcher; + } + + /** + * Get a new DistributedJdbcMigrationLauncher + * + * @return DistributedJdbcMigrationLauncher + */ + public DistributedJdbcMigrationLauncher getDistributedJdbcMigrationLauncher() + { + return new DistributedJdbcMigrationLauncher(); + } + + /** + * Loads the configuration from the migration config properties file. + * + * @param launcher the launcher to configure + * @param systemName the name of the system + * @param propFile the name of the properties file on the classpath + * @throws MigrationException if an unexpected error occurs + */ + private void configureFromMigrationProperties(DistributedJdbcMigrationLauncher launcher, + String systemName, + String propFile) + throws MigrationException + { + ClassLoader cl = Thread.currentThread().getContextClassLoader(); + InputStream is = cl.getResourceAsStream(propFile); + if (is != null) + { + try + { + Properties props = new Properties(); + props.load(is); + + configureFromMigrationProperties(launcher, systemName, props, propFile); + } + catch (IOException e) + { + throw new MigrationException("Error reading in migration properties file", e); + } + finally + { + try + { + is.close(); + } + catch (IOException ioe) + { + throw new MigrationException("Error closing migration properties file", ioe); + } + } + } + else + { + throw new MigrationException("Unable to find migration properties file '" + + propFile + "'"); + } + } + + /** + * Configure the launcher from the provided properties, system name + * + * @param launcher The launcher to configure + * @param systemName The name of the system we're configuring + * @param props The Properties object with our configuration information + * @param propFileName the property file name for configuration + * @throws IllegalArgumentException if a required parameter is missing + * @throws MigrationException if there is problem setting the context into the launcher + */ + void configureFromMigrationProperties(DistributedJdbcMigrationLauncher launcher, + String systemName, Properties props, + String propFileName) + throws IllegalArgumentException, MigrationException + { + // Get the name of the context to use for our patch information + String patchContext = + ConfigurationUtil.getRequiredParam(props, systemName + ".context"); + + // Set up the data source + NonPooledDataSource ds = new NonPooledDataSource(); + ds.setDriverClass(ConfigurationUtil.getRequiredParam(props, patchContext + ".jdbc.driver")); + ds.setDatabaseUrl(ConfigurationUtil.getRequiredParam(props, patchContext + ".jdbc.url")); + ds.setUsername(ConfigurationUtil.getRequiredParam(props, patchContext + ".jdbc.username")); + ds.setPassword(ConfigurationUtil.getRequiredParam(props, patchContext + ".jdbc.password")); + + launcher.setMigrationStrategy(props.getProperty("migration.strategy")); + // Get any post-patch task paths + launcher.setPostPatchPath(props.getProperty(patchContext + ".postpatch.path")); + + // See if they want to run in read-only mode + launcher.setReadOnly(false); + if ("true".equals(props.getProperty(systemName + ".readonly"))) + { + launcher.setReadOnly(true); + } + + // See if they want to override the lock after a certain amount of time + String lockPollRetries = props.getProperty(systemName + ".lockPollRetries"); + if (lockPollRetries != null) + { + launcher.setLockPollRetries(Integer.parseInt(lockPollRetries)); + } + + // see if forcesync specified. Value doesn't matter, just presence of system property enables syncing + String forceSync = ConfigurationUtil.getOptionalParam("forcesync", System.getProperties(), null, 0); + if (forceSync != null) + { + ((DistributedMigrationProcess) launcher.getMigrationProcess()).setForceSync(true); + } + + // Set up the JDBC migration context; accepts one of two property names + DataSourceMigrationContext context = getDataSourceMigrationContext(); + String databaseType = ConfigurationUtil.getRequiredParam(props, + patchContext + ".jdbc.database.type", patchContext + ".jdbc.dialect"); + context.setDatabaseType(new DatabaseType(databaseType)); + + // Finish setting up the context + context.setSystemName(systemName); + context.setDataSource(ds); + + // setup the user-defined listeners + List userDefinedListeners = loadMigrationListeners(systemName, props); + launcher.getMigrationProcess().addListeners(userDefinedListeners); + + // done reading in config, set launcher's context + // FIXME only using one context here, would a distributed one ever go into multiple nodes? + launcher.addContext(context); + + // Get our controlled systems, and instantiate their launchers + HashMap controlledSystems = new HashMap(); + String[] controlledSystemNames = + ConfigurationUtil.getRequiredParam(props, + systemName + ".controlled.systems").split(","); + for (int i = 0; i < controlledSystemNames.length; i++) + { + log.info("Creating controlled patch executor for system " + controlledSystemNames[i]); + //TODO should be injected + JdbcMigrationLauncherFactory factory = + new JdbcMigrationLauncherFactoryLoader().createFactory(); + JdbcMigrationLauncher subLauncher = + factory.createMigrationLauncher(controlledSystemNames[i], propFileName); + + controlledSystems.put(controlledSystemNames[i], subLauncher); + + // Make sure the controlled migration process gets migration events + launcher.getMigrationProcess().addListener(subLauncher); + } + + // communicate our new-found controlled systems to the migration process + ((DistributedMigrationProcess) launcher.getMigrationProcess()) + .setControlledSystems(controlledSystems); + } +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/DistributedMigrationInformation.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/DistributedMigrationInformation.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/DistributedMigrationInformation.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,151 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration.jdbc; + +import com.tacitknowledge.util.migration.jdbc.util.ConfigurationUtil; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import java.util.Map; + +/** + * Launches the migration process as a standalone application. + *

+ * This class expects the following Java environment parameters: + *

    + *
  • migration.systemname - the name of the logical system being migrated
  • + *
+ *

+ * Alternatively, you can pass the migration system name on the command line as the + * first argument. + *

+ * Below is an example of how this class can be configured in an Ant build.xml file: + *

+ *   ...
+ *  <target name="patch.information" description="Prints out information about patch levels">
+ *   <java
+ *       fork="true"
+ *       classpathref="patch.classpath"
+ *       failonerror="true"
+ *       classname="com.tacitknowledge.util.migration.jdbc.DistributedMigrationInformation">
+ *     <sysproperty key="migration.systemname" value="${application.name}"/>
+ *   </java>
+ * </target>
+ *   ...
+ * 
+ * + * @author Mike Hardy (mike@tacitknowledge.com) + * @see com.tacitknowledge.util.migration.DistributedMigrationProcess + * @see com.tacitknowledge.util.migration.jdbc.DistributedJdbcMigrationLauncherFactory + */ +public class DistributedMigrationInformation +{ + /** + * Class logger + */ + private static Log log = LogFactory.getLog(DistributedMigrationInformation.class); + + /** + * Get the migration level information for the given system name + * + * @param arguments the command line arguments, if any (none are used) + * @throws Exception if anything goes wrong + */ + public static void main(String[] arguments) throws Exception + { + DistributedMigrationInformation info = new DistributedMigrationInformation(); + String migrationName = System.getProperty("migration.systemname"); + String migrationSettings = ConfigurationUtil.getOptionalParam("migration.settings", + System.getProperties(), arguments, 1); + + if (migrationName == null) + { + if ((arguments != null) && (arguments.length > 0)) + { + migrationName = arguments[0].trim(); + } + else + { + throw new IllegalArgumentException("The migration.systemname " + + "system property is required"); + } + } + // info.getMigrationInformation(migrationName); + info.getMigrationInformation(migrationName, migrationSettings); + } + + /** + * Get the migration level information for the given system name + * + * @param systemName the name of the system + * @return returns the current highest source code patch number + * @throws Exception if anything goes wrong + */ + public int getMigrationInformation(String systemName) throws Exception + { + return getMigrationInformation(systemName, null); + } + + /** + * Get the migration level information for the given system name + * + * @param systemName the name of the system + * @param migrationSettings name of alternate migration.properties file to use + * @return returns the current highest source code patch number + * @throws Exception if anything goes wrong + */ + public int getMigrationInformation(String systemName, String migrationSettings) throws Exception + { + // The MigrationLauncher is responsible for handling the interaction + // between the PatchTable and the underlying MigrationTasks; as each + // task is executed, the patch level is incremented, etc. + try + { + DistributedJdbcMigrationLauncherFactory factory = + new DistributedJdbcMigrationLauncherFactory(); + DistributedJdbcMigrationLauncher launcher = null; + + if (migrationSettings == null) + { + log.info("Using migration.properties (default)"); + launcher = (DistributedJdbcMigrationLauncher) factory.createMigrationLauncher(systemName); + } + else + { + log.info("Using " + migrationSettings); + launcher = (DistributedJdbcMigrationLauncher) factory.createMigrationLauncher(systemName, migrationSettings); + } + // FIXME test that the migration information is correct + Map contextMap = launcher.getContexts(); + + JdbcMigrationContext context = + (JdbcMigrationContext) contextMap.keySet().iterator().next(); + + int currentLevel = launcher.getDatabasePatchLevel(context); + int nextPatchLevel = launcher.getNextPatchLevel(); + log.info("Current Database patch level is : " + currentLevel); + int unappliedPatches = nextPatchLevel - launcher.getDatabasePatchLevel(context) - 1; + log.info("Current number of unapplied patches is : " + unappliedPatches); + log.info("The next patch to author should be : " + nextPatchLevel); + return (nextPatchLevel - 1); + } + catch (Exception e) + { + log.error(e); + throw e; + } + } +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/DistributedMigrationTableUnlock.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/DistributedMigrationTableUnlock.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/DistributedMigrationTableUnlock.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,104 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration.jdbc; + +import com.tacitknowledge.util.migration.MigrationContext; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import java.util.Map; + +/** + * Allows you to force-unlock a migration table with an orphaned lock. Should + * be used in the same way that DistributedMigrationInformation is used + * + * @author Mike Hardy (mike@tacitknowledge.com) + * @see com.tacitknowledge.util.migration.jdbc.DistributedMigrationInformation + */ +public class DistributedMigrationTableUnlock +{ + /** + * Class logger + */ + private static Log log = LogFactory.getLog(DistributedMigrationTableUnlock.class); + + /** + * Get the migration level information for the given system name + * + * @param arguments the command line arguments, if any (none are used) + * @throws Exception if anything goes wrong + */ + public static void main(String[] arguments) throws Exception + { + DistributedMigrationTableUnlock unlock = new DistributedMigrationTableUnlock(); + String migrationName = System.getProperty("migration.systemname"); + if (migrationName == null) + { + if ((arguments != null) && (arguments.length > 0)) + { + migrationName = arguments[0].trim(); + } + else + { + throw new IllegalArgumentException("The migration.systemname " + + "system property is required"); + } + } + unlock.tableUnlock(migrationName); + } + + /** + * unlock the patch table for the given system name + * + * @param systemName the name of the system + * @throws Exception if anything goes wrong + */ + public void tableUnlock(String systemName) throws Exception + { + tableUnlock(systemName, MigrationContext.MIGRATION_CONFIG_FILE); + } + + /** + * unlock the patch table for the given system name + * + * @param systemName the name of the system + * @param migrationSettings migration settings file + * @throws Exception if anything goes wrong + */ + public void tableUnlock(String systemName, String migrationSettings) throws Exception + { + // The MigrationLauncher is responsible for handling the interaction + // between the PatchTable and the underlying MigrationTasks; as each + // task is executed, the patch level is incremented, etc. + try + { + DistributedJdbcMigrationLauncherFactory factory = + new DistributedJdbcMigrationLauncherFactory(); + DistributedJdbcMigrationLauncher launcher + = (DistributedJdbcMigrationLauncher) factory.createMigrationLauncher(systemName, migrationSettings); + + Map contextMap = launcher.getContexts(); + JdbcMigrationContext context = + (JdbcMigrationContext) contextMap.keySet().iterator().next(); + launcher.createPatchStore(context).unlockPatchStore(); + } + catch (Exception e) + { + log.error(e); + throw e; + } + } +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/DistributedStandaloneMigrationLauncher.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/DistributedStandaloneMigrationLauncher.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/DistributedStandaloneMigrationLauncher.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,107 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration.jdbc; + +import com.tacitknowledge.util.migration.jdbc.util.ConfigurationUtil; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + + +/** + * Launches the migration process as a standalone application. + *

+ * This class expects the following Java environment parameters: + *

    + *
  • migration.systemname - the name of the logical system being migrated
  • + *
+ *

+ * Below is an example of how this class can be configured in build.xml: + *

+ *   ...
+ *  <target name="patch.database" description="Runs the migration system">
+ *   <java
+ *       fork="true"
+ *       classpathref="patch.classpath"
+ *       failonerror="true"
+ *       classname=
+ *         "com.tacitknowledge.util.migration.jdbc.DistributedStandaloneMigrationLauncher">
+ *     <sysproperty key="migration.systemname" value="${application.name}"/>
+ *   </java>
+ * </target>
+ *   ...
+ * 
+ * + * @author Mike Hardy (mike@tacitknowledge.com) + * @see com.tacitknowledge.util.migration.DistributedMigrationProcess + */ +public class DistributedStandaloneMigrationLauncher +{ + /** + * Class logger + */ + private static Log log = LogFactory.getLog(DistributedStandaloneMigrationLauncher.class); + + /** + * Private constructor - this object shouldn't be instantiated + */ + private DistributedStandaloneMigrationLauncher() + { + // does nothing + } + + /** + * Run the migrations for the given system name + * + * @param arguments the command line arguments, if any (none are used) + * @throws Exception if anything goes wrong + */ + public static void main(String[] arguments) throws Exception + { + String systemName = ConfigurationUtil.getRequiredParam("migration.systemname", + System.getProperties(), arguments); + + String migrationSettings = ConfigurationUtil.getOptionalParam("migration.settings", + System.getProperties(), arguments, 1); + + // The MigrationLauncher is responsible for handling the interaction + // between the PatchTable and the underlying MigrationTasks; as each + // task is executed, the patch level is incremented, etc. + try + { + DistributedJdbcMigrationLauncherFactory factory = + new DistributedJdbcMigrationLauncherFactory(); + JdbcMigrationLauncher launcher = null; + + if (migrationSettings == null) + { + log.info("Using migration.properties (default)"); + launcher = factory.createMigrationLauncher(systemName); + } + else + { + log.info("Using " + migrationSettings); + launcher = factory.createMigrationLauncher(systemName, migrationSettings); + } + launcher.doMigrations(); + + } + catch (Exception e) + { + log.error(e); + throw e; + } + } +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/JdbcMigrationContext.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/JdbcMigrationContext.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/JdbcMigrationContext.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,72 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration.jdbc; + +import com.tacitknowledge.util.migration.MigrationContext; +import com.tacitknowledge.util.migration.MigrationException; + +import java.sql.Connection; +import java.sql.SQLException; + +/** + * Contains the configuration and resources for a database patch run. + * + * @author Scott Askew (scott@tacitknowledge.com) + */ +public interface JdbcMigrationContext extends MigrationContext +{ + /** + * Max length for the systemName columne + */ + public static final int MAX_SYSTEMNAME_LENGTH = 30; + + /** + * Returns a database connection to use. The creator + * of the JdbcMigrationContext are responsible for closing the connection. + * + * @return the database connection to use + * @throws SQLException if an unexpected error occurs + */ + public Connection getConnection() throws SQLException; + + /** + * {@inheritDoc} + */ + public void commit() throws MigrationException; + + /** + * {@inheritDoc} + */ + public void rollback() throws MigrationException; + + /** + * @return the name of the system to patch + */ + public String getSystemName(); + + /** + * @return In a federated distributed configuration this should be some + * unique name to identify each node in the system. In other + * configurations where is typically only one node per system, + * the system name should suffice. + */ + public String getDatabaseName(); + + /** + * @return Returns the database type. + */ + public DatabaseType getDatabaseType(); +} \ No newline at end of file Index: 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/JdbcMigrationLauncher.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/JdbcMigrationLauncher.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/JdbcMigrationLauncher.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,776 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 get + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration.jdbc; + +import com.tacitknowledge.util.migration.*; +import com.tacitknowledge.util.migration.jdbc.loader.FlatXmlDataSetTaskSource; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.*; + +/** + * Core starting point for a database migration run. This class obtains a + * connection to the database, checks its patch level, delegates the actual + * execution of the migration tasks to a MigrationProcess + * instance, and then commits and cleans everything up at the end. + *

+ * This class is NOT threadsafe. + * + * @author Scott Askew (scott@tacitknowledge.com) + */ +public class JdbcMigrationLauncher implements RollbackListener +{ + /** + * Class logger + */ + private static Log log = LogFactory.getLog(JdbcMigrationLauncher.class); + + /** + * The MigrationProcess responsible for applying the patches + */ + private MigrationProcess migrationProcess = null; + + /** + * The amount time, in milliseconds, between attempts to obtain a lock on + * the patches table. Defaults to 15 seconds. + */ + private long lockPollMillis = 15000; + + /** + * The number of times to wait for the lock before overriding it. -1 is + * infinite + */ + private int lockPollRetries = -1; + + /** + * The path containing directories and packages to search through to locate + * patches. + */ + private String patchPath = null; + + /** + * The path containing directories and packages to search for post-patch + * tasks. + */ + private String postPatchPath = null; + + /** + * The MigrationContext objects to use for all migrations. + * Each one is the key in the map, with the PatchInfoStore being the value + */ + private LinkedHashMap contexts = new LinkedHashMap(); + + /** + * Holds the migration strategy to use during migration process + */ + private String migrationStrategy; + + /** + * Create a new MigrationProcess and add a SqlScriptMigrationTaskSource + */ + public JdbcMigrationLauncher() + { + } + + /** + * Create a new MigrationLancher. + * + * @param context the JdbcMigrationContext to use. + */ + public JdbcMigrationLauncher(JdbcMigrationContext context) + { + this(); + addContext(context); + } + + /** + * Get the MigrationProcess we'll use to migrate things + * + * @return MigrationProcess for migration control + */ + public MigrationProcess getNewMigrationProcess() + { + MigrationProcess migrationProcess = new MigrationProcess(); + migrationProcess.setMigrationRunnerStrategy + (MigrationRunnerFactory.getMigrationRunnerStrategy(getMigrationStrategy())); + return migrationProcess; + } + + /** + * Starts the application migration process. + * + * @return the number of patches applied + * @throws MigrationException if an unrecoverable error occurs during the migration + */ + public int doMigrations() throws MigrationException + { + if (contexts.size() == 0) + { + throw new MigrationException("You must configure a migration context"); + } + + try + { + Iterator contextIter = contexts.keySet().iterator(); + int migrationCount = 0; + while (contextIter.hasNext()) + { + JdbcMigrationContext context = + (JdbcMigrationContext) contextIter.next(); + migrationCount = doMigrations(context); + log.info("Executed " + migrationCount + " patches for context " + + context); + } + return migrationCount; + } + catch (SQLException e) + { + throw new MigrationException("SqlException during migration", e); + } + } + + /** + * Performs the application rollbacks + * + * @param context the database context to run the patches in + * @param rollbackLevel the level the system should rollback to + * @return the number of patches applied + * @throws SQLException if an unrecoverable database error occurs while working with the patches table. + * @throws MigrationException if an unrecoverable error occurs during the migration + */ + public int doRollbacks(JdbcMigrationContext context, int[] rollbackLevel) + throws SQLException, MigrationException + { + return doRollbacks(context, rollbackLevel, false); + } + + /** + * Performs the application rollbacks + * + * @param context the database context to run the patches in + * @param rollbackLevel the level the system should rollback to + * @param forceRollback is a boolean indicating if the application should ignore a check to see if all patches are rollbackable + * @return the number of patches applied + * @throws SQLException if an unrecoverable database error occurs while working with the patches table. + * @throws MigrationException if an unrecoverable error occurs during the migration + */ + public int doRollbacks(JdbcMigrationContext context, int[] rollbackLevel, boolean forceRollback) + throws SQLException, MigrationException + { + PatchInfoStore patchTable = createPatchStore(context); + + lockPatchStore(context); + + // Now apply the patches + int executedPatchCount = 0; + try + { + + // remember the auto-commit state, and turn auto-commit off + Connection conn = context.getConnection(); + boolean commitState = conn.getAutoCommit(); + conn.setAutoCommit(false); + + // run the rollbacks + try + { + executedPatchCount = migrationProcess.doRollbacks(patchTable, rollbackLevel, context, forceRollback); + } + + // restore autocommit state + finally + { + if ((conn != null) && !conn.isClosed()) + { + conn.setAutoCommit(commitState); + } + } + } + catch (MigrationException me) + { + // If there was any kind of error, we don't want to eat it, but we do + // want to unlock the patch store. So do that, then re-throw. + patchTable.unlockPatchStore(); + throw me; + } + + // Do any post-patch tasks + try + { + migrationProcess.doPostPatchMigrations(context); + return executedPatchCount; + } + finally + { + try + { + patchTable.unlockPatchStore(); + } + catch (MigrationException e) + { + log.error("Error unlocking patch table: ", e); + } + } + } + + /** + * Initiates the application rollback process. + * + * @param rollbackLevel the patch level the system should rollback to + * @return an integer indicating how many patches were rolled back + * @throws MigrationException is thrown in case of an error while rolling back + */ + public int doRollbacks(int[] rollbackLevel) throws MigrationException + { + + if (contexts.size() == 0) + { + throw new MigrationException( + "You must configure a migration context"); + } + int rollbackCount = 0; + + try + { + + for (JdbcMigrationContext context : contexts.keySet()) + { + rollbackCount = doRollbacks(context, rollbackLevel); + log.info("Executed " + rollbackCount + " patches for context " + context); + } + } + catch (SQLException se) + { + throw new MigrationException("SqlException during rollback", se); + } + + return rollbackCount; + } + + /** + * Initiates the application rollback process. + * + * @param rollbackLevel the patch level the system should rollback to + * @param forceRollback a boolean indcating if the check for all tasks being rollbackable should be ignored + * @return an integer indicating how many patches were rolled back + * @throws MigrationException is thrown in case of an error while rolling back + */ + public int doRollbacks(int[] rollbackLevel, boolean forceRollback) throws MigrationException + { + if (contexts.size() == 0) + { + throw new MigrationException( + "You must configure a migration context"); + } + + int rollbackCount = 0; + + try + { + + for (JdbcMigrationContext context : contexts.keySet()) + { + rollbackCount = doRollbacks(context, rollbackLevel, forceRollback); + log.info("Executed " + rollbackCount + " patches for context " + context); + } + + } + catch (SQLException se) + { + throw new MigrationException("SqlException during rollback", se); + } + + + return rollbackCount; + } + + /** + * Returns the colon-separated path of packages and directories within the + * class path that are sources of patches. + * + * @return a colon-separated path of packages and directories within the + * class path that are sources of patches + */ + public String getPatchPath() + { + return patchPath; + } + + /** + * Sets the colon-separated path of packages and directories within the + * class path that are sources of patches. + * + * @param searchPath a colon-separated path of packages and directories within + * the class path that are sources of patches + */ + public void setPatchPath(String searchPath) + { + this.patchPath = searchPath; + StringTokenizer st = new StringTokenizer(searchPath, ":"); + while (st.hasMoreTokens()) + { + String path = st.nextToken(); + if (path.indexOf('/') > -1) + { + getMigrationProcess().addPatchResourceDirectory(path); + } + else + { + getMigrationProcess().addPatchResourcePackage(path); + } + } + } + + /** + * Returns the colon-separated path of packages and directories within the + * class path that are sources of post-patch tasks + * + * @return a colon-separated path of packages and directories within the + * class path that are sources of post-patch tasks + */ + public String getPostPatchPath() + { + return postPatchPath; + } + + /** + * Sets the colon-separated path of packages and directories within the + * class path that are sources of post-patch tasks + * + * @param searchPath a colon-separated path of packages and directories within + * the class path that are sources of post-patch tasks + */ + public void setPostPatchPath(String searchPath) + { + this.postPatchPath = searchPath; + if (searchPath == null) + { + return; + } + StringTokenizer st = new StringTokenizer(searchPath, ":"); + while (st.hasMoreTokens()) + { + String path = st.nextToken(); + if (path.indexOf('/') > -1) + { + migrationProcess.addPostPatchResourceDirectory(path); + } + else + { + migrationProcess.addPostPatchResourcePackage(path); + } + } + } + + /** + * {@inheritDoc} + */ + public void migrationStarted(MigrationTask task, MigrationContext ctx) throws MigrationException + { + log.debug("Started task " + task.getName() + " for context " + ctx); + } + + /** + * {@inheritDoc} + */ + public void migrationSuccessful(MigrationTask task, MigrationContext ctx) throws MigrationException + { + log.debug("Task " + task.getName() + " was successful for context " + ctx + " in launcher " + this); + int patchLevel = task.getLevel().intValue(); + + // update all of our controlled patch tables + for (Iterator patchTableIter = contexts.entrySet().iterator(); patchTableIter.hasNext();) + { + PatchInfoStore store = (PatchInfoStore) ((Map.Entry) patchTableIter.next()).getValue(); + MigrationRunnerStrategy strategy = getMigrationProcess().getMigrationRunnerStrategy(); + if (strategy.shouldMigrationRun(patchLevel, store)) + { + store.updatePatchLevel(patchLevel); + } + } + } + + /** + * {@inheritDoc} + */ + public void migrationFailed(MigrationTask task, MigrationContext ctx, MigrationException e) + throws MigrationException + { + log.debug("Task " + task.getName() + " failed for context " + ctx, e); + } + + /** + * Get the patch level from the database + * + * @param ctx the migration context to get the patch level for + * @return int representing the current database patch level + * @throws MigrationException if there is a database connection error, or the patch level can't be determined + */ + public int getDatabasePatchLevel(MigrationContext ctx) throws MigrationException + { + PatchInfoStore patchTable = (PatchInfoStore) contexts.get(ctx); + return patchTable.getPatchLevel(); + } + + /** + * Get the next patch level, for use when creating a new patch + * + * @return int representing the first unused patch number + * @throws MigrationException if the next patch level can't be determined + */ + public int getNextPatchLevel() throws MigrationException + { + return migrationProcess.getNextPatchLevel(); + } + + /** + * Sets the JdbcMigrationContext used for the migrations. + * + * @param context the JdbcMigrationContext used for the migrations + */ + public void addContext(JdbcMigrationContext context) + { + PatchInfoStore patchTable = new PatchTable(context); + log.debug("Adding context " + context + " with patch table " + patchTable + " in launcher " + this); + contexts.put(context, patchTable); + } + + /** + * Returns the JdbcMigrationContext objects used for the migrations. + * + * @return Map of JdbcMigrationContext and + * PatchInfoStore objects used in the migrations + */ + public LinkedHashMap getContexts() + { + return contexts; + } + + /** + * Performs the application migration process in one go + * + * @param context the database context to run the patches in + * @return the number of patches applied + * @throws SQLException if an unrecoverable database error occurs while working with the patches table. + * @throws MigrationException if an unrecoverable error occurs during the migration + */ + protected int doMigrations(JdbcMigrationContext context) throws SQLException, MigrationException + { + PatchInfoStore patchTable = createPatchStore(context); + + lockPatchStore(context); + + // Now apply the patches + int executedPatchCount = 0; + try + { + + // remember the auto-commit state, and turn auto-commit off + Connection conn = context.getConnection(); + boolean commitState = conn.getAutoCommit(); + conn.setAutoCommit(false); + + // run the migrations + try + { + executedPatchCount = migrationProcess.doMigrations(patchTable, + context); + } + + // restore autocommit state + finally + { + if ((conn != null) && !conn.isClosed()) + { + conn.setAutoCommit(commitState); + } + } + } + catch (MigrationException me) + { + // If there was any kind of error, we don't want to eat it, but we do + // want to unlock the patch store. So do that, then re-throw. + patchTable.unlockPatchStore(); + throw me; + } + + // Do any post-patch tasks + try + { + migrationProcess.doPostPatchMigrations(context); + return executedPatchCount; + } + finally + { + try + { + patchTable.unlockPatchStore(); + } + catch (MigrationException e) + { + log.error("Error unlocking patch table: ", e); + } + } + } + + /** + * Lock the patch store. This is done safely, such that we safely handle the + * case where other migration launchers are patching at the same time. + * + * @param context the context to lock the store in + * @throws MigrationException if the reading or setting lock state fails + */ + private void lockPatchStore(JdbcMigrationContext context) throws MigrationException + { + // Patch locks ensure that only one system sharing a patch store will patch + // it at the same time. + boolean lockObtained = false; + while (!lockObtained) + { + waitForFreeLock(context); + + PatchInfoStore piStore = (PatchInfoStore) contexts.get(context); + piStore.getPatchLevel(); + try + { + piStore.lockPatchStore(); + lockObtained = true; + } + catch (IllegalStateException ise) + { + log.error("IllegalStateException when trying to lock the patch info store", ise); + // this happens when someone woke up at the same time, + // raced us to the lock and won. We re-sleep and try again. + } + } + } + + /** + * create a patch table object for use in migrations + * + * @param context the context to create the store in + * @return PatchTable object for use in accessing patch state information + * @throws MigrationException if unable to create the store + */ + protected PatchInfoStore createPatchStore(JdbcMigrationContext context) throws MigrationException + { + // Make sure the table is created before claiming it exists by returning + return (PatchInfoStore) contexts.get(context); + } + + /** + * Pauses until the patch lock become available. + * + * @param context the context related to the store + * @throws MigrationException if an unrecoverable error occurs + */ + private void waitForFreeLock(JdbcMigrationContext context) throws MigrationException + { + PatchInfoStore piStore = (PatchInfoStore) contexts.get(context); + log.debug("about to wait for free lock"); + for (int i = 0; piStore.isPatchStoreLocked(); i++) + { + // Have we exceeded our threshold of time to wait? + if ((getLockPollRetries() != -1) && (i >= getLockPollRetries())) + { + log.info("Reached maximum lock poll retries (" + getLockPollRetries() + "), overriding patch lock"); + piStore.unlockPatchStore(); + } + else + { + log.info("Waiting for migration lock for system \"" + context.getSystemName() + "\""); + log.info(" If this isn't from a long-running patch, but a stale lock, either:"); + log.info(" 1) run MigrationTableUnlock (probably 'ant patch.unlock')"); + log.info(" 2) set the lockPollRetries property so the lock times out"); + log.info(" (this is dangerous in combination with long-running patches)"); + log.info(" 3) set the 'patch_in_progress' in the patches table to 'F'"); + + if (getLockPollRetries() != -1) + { + log.info("'lockPollRetries' is set, will poll lock " + (getLockPollRetries() - i) + + " more times before overriding lock."); + } + try + { + Thread.sleep(getLockPollMillis()); + } + catch (InterruptedException e) + { + log.error("Received InterruptedException while waiting for patch lock", e); + } + } + } + log.debug("done waiting for free lock"); + } + + /** + * Get how long to wait for the patch store lock + * + * @return the wait time for the patch store, in milliseconds + */ + public long getLockPollMillis() + { + return lockPollMillis; + } + + /** + * Set how long to wait for the patch store lock + * + * @param lockPollMillis the wait time for the patch store, in milliseconds + */ + public void setLockPollMillis(long lockPollMillis) + { + this.lockPollMillis = lockPollMillis; + } + + /** + * Get the migration process to use for migrations + * + * @return MigrationProcess to use for migrations + */ + public MigrationProcess getMigrationProcess() + { + if (migrationProcess == null) + { + setMigrationProcess(getNewMigrationProcess()); + + } + + return migrationProcess; + } + + /** + * Set the migration process to use for migrations + * + * @param migrationProcess the MigrationProcess to use for migrations + */ + public void setMigrationProcess(MigrationProcess migrationProcess) + { + this.migrationProcess = migrationProcess; + + // Make sure this class is notified when a patch is applied so that + // the patch level can be updated (see #migrationSuccessful). + this.migrationProcess.addListener(this); + + this.migrationProcess.addMigrationTaskSource(new SqlScriptMigrationTaskSource()); + + this.migrationProcess.addMigrationTaskSource(new FlatXmlDataSetTaskSource()); + + } + + /** + * See if we are actually applying patches, or if it is just readonly + * + * @return boolean true if we will skip application + */ + public boolean isReadOnly() + { + return getMigrationProcess().isReadOnly(); + } + + /** + * Set whether or not to actually apply patches + * + * @param readOnly boolean true if we should skip application + */ + public void setReadOnly(boolean readOnly) + { + getMigrationProcess().setReadOnly(readOnly); + } + + /** + * Return the number of times to poll the lock before overriding it. -1 is infinite + * + * @return int either -1 for infinite or number of times to poll before override + */ + public int getLockPollRetries() + { + return lockPollRetries; + } + + /** + * Set the number of times to poll the lock before overriding it. -1 is infinite + * + * @param lockPollRetries either -1 for infinite or number of times to poll before override + */ + public void setLockPollRetries(int lockPollRetries) + { + this.lockPollRetries = lockPollRetries; + } + + /** + * Explicitly set the contexts. + * + * @param contexts the collection of contexts that is a map of JDBCMigrationContext -> PatchInfoStore. + */ + public void setContexts(LinkedHashMap contexts) + { + this.contexts = contexts; + } + + /** + * @see com.tacitknowledge.util.migration.MigrationListener#initialize(String, Properties) + */ + public void initialize(String systemName, Properties properties) throws MigrationException + { + } + + /** + * {@inheritDoc} + */ + public void rollbackFailed(RollbackableMigrationTask task, MigrationContext context, MigrationException e) + throws MigrationException + { + log.debug("Task " + task.getName() + " failed for context " + context, e); + } + + /** + * {@inheritDoc} + */ + public void rollbackStarted(RollbackableMigrationTask task, MigrationContext context) throws MigrationException + { + log.debug("Started rollback " + task.getName() + " for context " + context); + } + + /** + * {@inheritDoc} + */ + public void rollbackSuccessful(RollbackableMigrationTask task, int rollbackLevel, MigrationContext context) throws MigrationException + { + log.debug("Rollback of task " + task.getName() + " was successful for context " + context + " in launcher " + this); + int patchLevel = task.getLevel().intValue(); + + // update all of our controlled patch tables + for (Iterator patchTableIter = contexts.entrySet().iterator(); patchTableIter.hasNext();) + { + PatchInfoStore store = (PatchInfoStore) ((Map.Entry) patchTableIter.next()).getValue(); + store.updatePatchLevelAfterRollBack(rollbackLevel); + } + } + + public void setMigrationStrategy(String migrationStrategy) + { + this.migrationStrategy = migrationStrategy; + } + + public String getMigrationStrategy() + { + return migrationStrategy; + } +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/JdbcMigrationLauncherFactory.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/JdbcMigrationLauncherFactory.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/JdbcMigrationLauncherFactory.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,448 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration.jdbc; + +import com.tacitknowledge.util.migration.MigrationContext; +import com.tacitknowledge.util.migration.MigrationException; +import com.tacitknowledge.util.migration.MigrationListener; +import com.tacitknowledge.util.migration.jdbc.util.ConfigurationUtil; +import com.tacitknowledge.util.migration.jdbc.util.NonPooledDataSource; +import org.apache.commons.lang.StringUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import javax.naming.Context; +import javax.naming.InitialContext; +import javax.naming.NamingException; +import javax.servlet.ServletContextEvent; +import javax.sql.DataSource; +import java.io.IOException; +import java.io.InputStream; +import java.util.*; + +/** + * Creates and configures new JdbcMigrationContext objects based on the values + * in the migration.properties file for the given system. This is a convenience + * class for systems that need to initialize the autopatch framework but do not to or can not + * configure the framework themselves. + *

+ * This factory expects a file named migration.properties to be in the root of the + * class path. This file must contain these properties (where systemName is the name of + * the system being patched): + * + * + * + * + * + * + * + * + * + *
Keydescription
systemName.patch.path
systemName.jdbc.database.typeThe database type; also accepts systemName.jdbc.dialect
systemName.jdbc.driverThe JDBC driver to use
systemName.jdbc.urlThe JDBC URL to the database
systemName.jdbc.usernameThe database user name
systemName.jdbc.passwordThe database password
+ *

+ * Optional properties include: + * + * + * + * + * + * + * + *
systemName.postpatch.path
systemName.readonlyboolean true to skip patch application
systemName.jdbc.systemsSet of names for multiple JDBC connections that + * should all have patches applied. Names will be + * looked up in the same properties file as + * systemName.jdbcname.database.type, where + * all of the jdbc entries above should be present
systemName.listenersComma separated list of fully qualified java class names that implement {@link MigrationListener}
+ * + * @author Scott Askew (scott@tacitknowledge.com) + * @author Alex Soto (apsoto@gmail.com) + */ +public class JdbcMigrationLauncherFactory +{ + /** + * Class logger + */ + private static Log log = LogFactory.getLog(JdbcMigrationLauncherFactory.class); + + /** + * Creates and configures a new JdbcMigrationLauncher based on the + * values in the migration.properties file for the given system. + * + * @param systemName the system to patch + * @param propFile the name of the property file to configure from + * @return a fully configured JdbcMigrationLauncher. + * @throws MigrationException if an unexpected error occurs + */ + public JdbcMigrationLauncher createMigrationLauncher(String systemName, String propFile) + throws MigrationException + { + log.info("Creating JdbcMigrationLauncher for system " + systemName); + JdbcMigrationLauncher launcher = getJdbcMigrationLauncher(); + configureFromMigrationProperties(launcher, systemName, propFile); + return launcher; + } + + /** + * Creates and configures a new JdbcMigrationLauncher based on the + * values in the migration.properties file for the given system. + * + * @param systemName the system to patch + * @return a fully configured JdbcMigrationLauncher. + * @throws MigrationException if an unexpected error occurs + */ + public JdbcMigrationLauncher createMigrationLauncher(String systemName) + throws MigrationException + { + log.info("Creating JdbcMigrationLauncher for system " + systemName); + JdbcMigrationLauncher launcher = getJdbcMigrationLauncher(); + configureFromMigrationProperties(launcher, systemName); + return launcher; + } + + /** + * Creates and configures a new JdbcMigrationLauncher based on the + * values in the servlet context and JNDI for a web-application. + * + * @param sce the name of the context event to use in getting properties + * @return a fully configured JdbcMigrationLauncher. + * @throws MigrationException if an unexpected error occurs + */ + public JdbcMigrationLauncher createMigrationLauncher(ServletContextEvent sce) + throws MigrationException + { + JdbcMigrationLauncher launcher = getJdbcMigrationLauncher(); + configureFromServletContext(launcher, sce); + return launcher; + } + + /** + * Used to configure the migration launcher with properties from a servlet + * context. You do not need migration.properties to use this method. + * + * @param launcher the launcher to configure + * @param sce the event to get the context and associated parameters from + * @throws MigrationException if a problem with the look up in JNDI occurs + */ + private void configureFromServletContext(JdbcMigrationLauncher launcher, + ServletContextEvent sce) throws MigrationException + { + String readOnly = sce.getServletContext().getInitParameter("migration.readonly"); + launcher.setReadOnly(false); + if ("true".equals(readOnly)) + { + launcher.setReadOnly(true); + } + + // See if they want to override the lock after a certain amount of time + String lockPollRetries = + sce.getServletContext().getInitParameter("migration.lockPollRetries"); + if (lockPollRetries != null) + { + launcher.setLockPollRetries(Integer.parseInt(lockPollRetries)); + } + + String patchPath = ConfigurationUtil.getRequiredParam("migration.patchpath", sce, this); + launcher.setPatchPath(patchPath); + + String postPatchPath = sce.getServletContext().getInitParameter("migration.postpatchpath"); + launcher.setPostPatchPath(postPatchPath); + + String databases = sce.getServletContext().getInitParameter("migration.jdbc.systems"); + String[] databaseNames; + if ((databases == null) || "".equals(databases)) + { + databaseNames = new String[1]; + databaseNames[0] = ""; + log.debug("jdbc.systems was null or empty, not multi-node"); + } + else + { + databaseNames = databases.split(","); + log.debug("jdbc.systems was set to " + databases + ", configuring multi-node"); + } + + for (int i = 0; i < databaseNames.length; i++) + { + String databaseName = databaseNames[i]; + if (databaseName != "") + { + databaseName = databaseName + "."; + } + String databaseType = + ConfigurationUtil.getRequiredParam("migration." + databaseName + "databasetype", + sce, this); + String systemName = + ConfigurationUtil.getRequiredParam("migration.systemname", + sce, this); + String dataSource = + ConfigurationUtil.getRequiredParam("migration." + databaseName + "datasource", + sce, this); + + DataSourceMigrationContext context = getDataSourceMigrationContext(); + context.setSystemName(systemName); + context.setDatabaseType(new DatabaseType(databaseType)); + + try + { + Context ctx = new InitialContext(); + if (ctx == null) + { + throw new IllegalArgumentException("A jndi context must be " + + "present to use this configuration."); + } + DataSource ds = (DataSource) ctx.lookup("java:comp/env/" + dataSource); + context.setDataSource(ds); + log.debug("adding context with datasource " + dataSource + + " of type " + databaseType); + launcher.addContext(context); + } + catch (NamingException e) + { + throw new MigrationException("Problem with JNDI look up of " + dataSource, e); + } + } + } + + /** + * Loads the configuration from the migration config properties file. + * + * @param launcher the launcher to configure + * @param systemName the name of the system + * @throws MigrationException if an unexpected error occurs + */ + private void configureFromMigrationProperties(JdbcMigrationLauncher launcher, + String systemName) + throws MigrationException + { + configureFromMigrationProperties(launcher, + systemName, + MigrationContext.MIGRATION_CONFIG_FILE); + } + + /** + * Loads the configuration from the migration config properties file. + * + * @param launcher the launcher to configure + * @param systemName the name of the system + * @param propFile the name of the prop file to configure from + * @throws MigrationException if an unexpected error occurs + */ + private void configureFromMigrationProperties(JdbcMigrationLauncher launcher, + String systemName, + String propFile) + throws MigrationException + { + ClassLoader cl = Thread.currentThread().getContextClassLoader(); + InputStream is = cl.getResourceAsStream(propFile); + if (is != null) + { + try + { + Properties props = loadProperties(is); + + configureFromMigrationProperties(launcher, systemName, props); + } + catch (IOException e) + { + throw new MigrationException("Error reading in autopatch properties file", e); + } + finally + { + try + { + is.close(); + } + catch (IOException ioe) + { + throw new MigrationException("Error closing autopatch properties file", ioe); + } + } + } + else + { + throw new MigrationException("Unable to find autopatch properties file '" + + propFile + "'"); + } + } + + protected Properties loadProperties(InputStream is) throws IOException + { + Properties props = new Properties(); + props.load(is); + return props; + } + + /** + * Configure the launcher from the provided properties, system name + * + * @param launcher The launcher to configure + * @param system The name of the system we're configuring + * @param props The Properties object with our configuration information + * @throws IllegalArgumentException if a required parameter is missing + * @throws MigrationException + */ + void configureFromMigrationProperties(JdbcMigrationLauncher launcher, String system, + Properties props) + throws IllegalArgumentException, MigrationException + { + + launcher.setMigrationStrategy(props.getProperty("migration.strategy")); + + launcher.setPatchPath(ConfigurationUtil.getRequiredParam(props, system + ".patch.path")); + launcher.setPostPatchPath(props.getProperty(system + ".postpatch.path")); + launcher.setReadOnly(false); + if ("true".equals(props.getProperty(system + ".readonly"))) + { + launcher.setReadOnly(true); + } + + // See if they want to override the lock after a certain amount of time + String lockPollRetries = props.getProperty(system + ".lockPollRetries"); + if (lockPollRetries != null) + { + launcher.setLockPollRetries(Integer.parseInt(lockPollRetries)); + } + + // See if they want to change the amount of time to wait between lock polls + String lockPollMillis = props.getProperty(system + ".lockPollMillis"); + if (lockPollMillis != null) + { + launcher.setLockPollMillis(Integer.parseInt(lockPollMillis)); + } + + // TODO refactor the database name extraction from this and the servlet example + String databases = props.getProperty(system + ".jdbc.systems"); + String[] databaseNames; + if ((databases == null) || "".equals(databases)) + { + databaseNames = new String[1]; + databaseNames[0] = "jdbc"; + } + else + { + databaseNames = databases.split(","); + } + + for (int i = 0; i < databaseNames.length; i++) + { + String db = databaseNames[i]; + if (db != "") + { + db = "." + db; + } + + // Set up the data source + NonPooledDataSource dataSource = new NonPooledDataSource(); + dataSource.setDriverClass(ConfigurationUtil.getRequiredParam(props, + system + db + ".driver")); + dataSource.setDatabaseUrl(ConfigurationUtil.getRequiredParam(props, + system + db + ".url")); + dataSource.setUsername(ConfigurationUtil.getRequiredParam(props, + system + db + ".username")); + dataSource.setPassword(ConfigurationUtil.getRequiredParam(props, + system + db + ".password")); + + // Set up the JDBC migration context; accepts one of two property names + DataSourceMigrationContext context = getDataSourceMigrationContext(); + String databaseType = + ConfigurationUtil.getRequiredParam(props, + system + db + ".database.type", + system + db + ".dialect"); + log.debug("setting type to " + databaseType); + context.setDatabaseType(new DatabaseType(databaseType)); + + context.setDatabaseName(databaseNames[i]); + + // Finish setting up the context + context.setSystemName(system); + context.setDataSource(dataSource); + + // setup the user-defined listeners + List userDefinedListeners = loadMigrationListeners(system, props); + + + launcher.getMigrationProcess().addListeners(userDefinedListeners); + + // done reading in config, set launcher's context + launcher.addContext(context); + } + } + + /** + * Get a DataSourceMigrationContext + * + * @return DataSourceMigrationContext for use with the launcher + */ + public DataSourceMigrationContext getDataSourceMigrationContext() + { + return new DataSourceMigrationContext(); + } + + /** + * Get a JdbcMigrationLauncher + * + * @return JdbcMigrationLauncher + */ + public JdbcMigrationLauncher getJdbcMigrationLauncher() + { + return new JdbcMigrationLauncher(); + } + + /** + * Returns a list of MigrationListeners for the systemName specified in the properties. + * + * @param systemName The name of the system to load MigrationListeners for. + * @param properties The properties that has migration listeners specified. + * @return A List of zero or more MigrationListeners + * @throws MigrationException if unable to load listeners. + */ + protected List loadMigrationListeners(String systemName, Properties properties) + throws MigrationException + { + try + { + List listeners = new ArrayList(); + String[] listenerClassNames = null; + + String commaSeparatedList = properties.getProperty(systemName + ".listeners"); + // if it's blank, then no listeners configured + if (StringUtils.isNotBlank(commaSeparatedList)) + { + listenerClassNames = commaSeparatedList.split(","); + + for (Iterator it = Arrays.asList(listenerClassNames).iterator(); it.hasNext();) + { + String className = ((String) it.next()).trim(); + // if it's blank, then there is likely a leading or trailing comma + if (StringUtils.isNotBlank(className)) + { + Class c = Class.forName(className); + MigrationListener listener = (MigrationListener) c.newInstance(); + listener.initialize(systemName, properties); + listeners.add(listener); + } + } + } + + return listeners; + } + catch (Exception e) + { + throw new MigrationException("Exception while loading migration listeners", e); + } + } +} + Index: 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/JdbcMigrationLauncherFactoryLoader.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/JdbcMigrationLauncherFactoryLoader.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/JdbcMigrationLauncherFactoryLoader.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,72 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration.jdbc; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Load a MigrationLauncherFactory. This will default to loading the + * JdbcMigrationLauncherFactory, but will examine the system properties + * for a property called "migration.factory" and load that one if specified + * + * @author Jacques Morel + */ +public class JdbcMigrationLauncherFactoryLoader +{ + /** + * Class logger + */ + private static Log log = LogFactory.getLog(JdbcMigrationLauncherFactoryLoader.class); + + + /** + * Create the JdbcMigrationLauncherFactory + * + * @return JdbcMigrationLauncherFactory (or subclass) + */ + public JdbcMigrationLauncherFactory createFactory() + { + // Get the factory name from the system properties if possible + String factoryName = System.getProperties().getProperty("migration.factory"); + if (factoryName == null) + { + factoryName = JdbcMigrationLauncherFactory.class.getName(); + } + log.debug("Creating JdbcMigrationLauncher using " + factoryName); + + // Load the factory + Class factoryClass = null; + try + { + factoryClass = Class.forName(factoryName); + } + catch (ClassNotFoundException e) + { + throw new IllegalArgumentException("Migration factory class '" + + factoryName + "' not found. Aborting."); + } + try + { + return (JdbcMigrationLauncherFactory) factoryClass.newInstance(); + } + catch (Exception e) + { + throw new RuntimeException("Problem while instantiating factory class '" + + factoryName + "'. Aborting.", e); + } + } +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/MigrationInformation.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/MigrationInformation.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/MigrationInformation.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,151 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration.jdbc; + +import com.tacitknowledge.util.migration.jdbc.util.ConfigurationUtil; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import java.util.Iterator; +import java.util.Map; + +/** + * Launches the migration process as a standalone application. + *

+ * This class expects the following Java environment parameters: + *

    + *
  • migration.systemname - the name of the logical system being migrated
  • + *
  • migration.settings (optional) - the name of the settings file to use for migration
  • + *
+ *

+ * Alternatively, you can pass the migration system name on the command line as the + * first argument. + *

+ * Below is an example of how this class can be configured in an Ant build.xml file: + *

+ *   ...
+ *  <target name="patch.information" description="Prints out information about patch levels">
+ *   <java
+ *       fork="true"
+ *       classpathref="patch.classpath"
+ *       failonerror="true"
+ *       classname="com.tacitknowledge.util.migration.jdbc.MigrationInformation">
+ *     <sysproperty key="migration.systemname" value="${application.name}"/>
+ *   </java>
+ * </target>
+ *   ...
+ * 
+ * + * @author Mike Hardy (mike@tacitknowledge.com) + * @see com.tacitknowledge.util.migration.MigrationProcess + * @see com.tacitknowledge.util.migration.jdbc.JdbcMigrationLauncherFactory + */ +public class MigrationInformation +{ + /** + * Class logger + */ + private static Log log = LogFactory.getLog(MigrationInformation.class); + + /** + * Get the migration level information for the given system name + * + * @param arguments the command line arguments, if any (none are used) + * @throws Exception if anything goes wrong + */ + public static void main(String[] arguments) throws Exception + { + MigrationInformation info = new MigrationInformation(); + String migrationSystemName = ConfigurationUtil.getRequiredParam("migration.systemname", + System.getProperties(), arguments, 0); + String migrationSettings = ConfigurationUtil.getOptionalParam("migration.settings", + System.getProperties(), arguments, 1); + + info.getMigrationInformation(migrationSystemName, migrationSettings); + } + + /** + * Get the migration level information for the given system name + * + * @param migrationSystemName the name of the system + * @return returns the current highest source code patch number + * @throws Exception if anything goes wrong + */ + public int getMigrationInformation(String migrationSystemName) throws Exception + { + return getMigrationInformation(migrationSystemName, null); + } + + /** + * Get the migration level information for the given system name + * + * @param migrationSystemName the name of the system + * @param migrationSettings the name of the settings file to use for migration; if + * null is passed then the default name for migration settings will be used + * @return returns the current highest source code patch number + * @throws Exception if anything goes wrong + */ + public int getMigrationInformation(String migrationSystemName, String migrationSettings) + throws Exception + { + // The MigrationLauncher is responsible for handling the interaction + // between the PatchTable and the underlying MigrationTasks; as each + // task is executed, the patch level is incremented, etc. + int highestPatch = 0; + + try + { //TODO should be injected + JdbcMigrationLauncherFactory launcherFactory = + new JdbcMigrationLauncherFactoryLoader().createFactory(); + JdbcMigrationLauncher launcher = null; + + if (migrationSettings == null) + { + log.info("Using migration.properties (default)"); + launcher = launcherFactory.createMigrationLauncher(migrationSystemName); + } + else + { + log.info("Using " + migrationSettings); + launcher = launcherFactory.createMigrationLauncher(migrationSystemName, migrationSettings); + } + + // Print out information for all contexts + Map contextMap = launcher.getContexts(); + for (Iterator contextIter = contextMap.keySet().iterator(); contextIter.hasNext();) + { + JdbcMigrationContext context = (JdbcMigrationContext) contextIter.next(); + + int currentLevel = launcher.getDatabasePatchLevel(context); + int nextPatchLevel = launcher.getNextPatchLevel(); + log.info("Current Database patch level is : " + currentLevel); + int unappliedPatches = nextPatchLevel - launcher.getDatabasePatchLevel(context) - 1; + log.info("Current number of unapplied patches is : " + unappliedPatches); + log.info("The next patch to author should be : " + nextPatchLevel); + if ((nextPatchLevel - 1) > highestPatch) + { + highestPatch = nextPatchLevel - 1; + } + } + return highestPatch; + } + catch (Exception e) + { + log.error(e); + throw e; + } + } +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/MigrationTableUnlock.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/MigrationTableUnlock.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/MigrationTableUnlock.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,107 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration.jdbc; + +import com.tacitknowledge.util.migration.MigrationContext; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import java.util.Iterator; +import java.util.Map; + +/** + * Allows you to force-unlock a migration table with an orphaned lock. Should + * be used in the same way that MigrationInformation is used + * + * @author Mike Hardy (mike@tacitknowledge.com) + * @see com.tacitknowledge.util.migration.jdbc.MigrationInformation + */ +public class MigrationTableUnlock +{ + /** + * Class logger + */ + private static Log log = LogFactory.getLog(MigrationTableUnlock.class); + + /** + * Get the migration level information for the given system name + * + * @param arguments the command line arguments, if any (none are used) + * @throws Exception if anything goes wrong + */ + public static void main(String[] arguments) throws Exception + { + MigrationTableUnlock unlock = new MigrationTableUnlock(); + String migrationName = System.getProperty("migration.systemname"); + if (migrationName == null) + { + if ((arguments != null) && (arguments.length > 0)) + { + migrationName = arguments[0].trim(); + } + else + { + throw new IllegalArgumentException("The migration.systemname " + + "system property is required"); + } + } + unlock.tableUnlock(migrationName); + } + + /** + * unlock the patch table for the given system name + * + * @param systemName the name of the system + * @throws Exception if anything goes wrong + */ + public void tableUnlock(String systemName) throws Exception + { + tableUnlock(systemName, MigrationContext.MIGRATION_CONFIG_FILE); + } + + /** + * unlock the patch table for the given system name + * + * @param systemName the name of the system + * @param migrationSettings migration settings file + * @throws Exception if anything goes wrong + */ + public void tableUnlock(String systemName, String migrationSettings) throws Exception + { + // The MigrationLauncher is responsible for handling the interaction + // between the PatchTable and the underlying MigrationTasks; as each + // task is executed, the patch level is incremented, etc. + try + { //TODO should be injected + JdbcMigrationLauncherFactory launcherFactory = + new JdbcMigrationLauncherFactoryLoader().createFactory(); + JdbcMigrationLauncher launcher = launcherFactory.createMigrationLauncher(systemName, migrationSettings); + + // Print out information for all contexts + Map contextMap = launcher.getContexts(); + for (Iterator contextIter = contextMap.keySet().iterator(); contextIter.hasNext();) + { + JdbcMigrationContext context = (JdbcMigrationContext) contextIter.next(); + launcher.createPatchStore(context).unlockPatchStore(); + } + } + catch (Exception e) + { + log.error(e); + throw e; + } + } +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/PatchTable.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/PatchTable.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/PatchTable.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,459 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration.jdbc; + +import com.tacitknowledge.util.migration.MigrationException; +import com.tacitknowledge.util.migration.PatchInfoStore; +import com.tacitknowledge.util.migration.jdbc.util.SqlUtil; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.HashSet; +import java.util.Set; + + +/** + * Manages interactions with the "patches" table. The patches table stores + * the current patch level for a given system, as well as a system-scoped lock + * use to avoid concurrent patches to the system. A system is defined as an + * exclusive target of a patch. + *

+ * This class is responsible for: + *

    + *
  • Validating the existence of the patches table and creating it if it + * doesn't exist
  • + *
  • Determining if a patch is currently running on a given system
  • + *
  • Obtaining and releasing patch locks for a given system
  • + *
  • Obtaining and incrementing the patch level for a given system
  • + *
+ *

+ * TRANSACTIONS: Transactions should be committed by the calling + * class as needed. This class does not explictly commit or rollback transactions. + * + * @author Scott Askew (scott@tacitknowledge.com) + */ +public class PatchTable implements PatchInfoStore +{ + /** + * Class logger + */ + private static Log log = LogFactory.getLog(PatchTable.class); + + /** + * The migration configuration + */ + private JdbcMigrationContext context = null; + + /** + * Keeps track of table validation (see #createPatchesTableIfNeeded) + */ + private boolean tableExistenceValidated = false; + + /** + * Create a new PatchTable. + * + * @param migrationContext the migration configuration and connection source + */ + public PatchTable(JdbcMigrationContext migrationContext) + { + this.context = migrationContext; + + if (context.getDatabaseType() == null) + { + throw new IllegalArgumentException("The JDBC database type is required"); + } + } + + /** + * {@inheritDoc} + */ + public void createPatchStoreIfNeeded() throws MigrationException + { + if (tableExistenceValidated) + { + return; + } + + Connection conn = null; + PreparedStatement stmt = null; + ResultSet rs = null; + try + { + conn = context.getConnection(); + + stmt = conn.prepareStatement(getSql("level.table.exists")); + stmt.setString(1, context.getSystemName()); + rs = stmt.executeQuery(); + log.debug("'patches' table already exists."); + tableExistenceValidated = true; + } + catch (SQLException e) + { + // logging error in case it's not a simple patch table doesn't exist error + log.debug(e.getMessage()); + SqlUtil.close(null, stmt, rs); + + // check connection is valid before using, because the getConnection() call + // could have thrown the SQLException + if (null == conn) + { + throw new MigrationException("Unable to create a connection.", e); + } + + log.info("'patches' table must not exist; creating...."); + try + { + stmt = conn.prepareStatement(getSql("patches.create")); + if (log.isDebugEnabled()) + { + log.debug("Creating patches table with SQL '" + getSql("patches.create") + "'"); + } + stmt.execute(); + context.commit(); + + // We don't yet have a patch record for this system; create one + createSystemPatchRecord(); + } + catch (SQLException sqle) + { + throw new MigrationException("Unable to create patch table", sqle); + } + tableExistenceValidated = true; + log.info("Created 'patches' table."); + } + catch (Exception ex) + { + throw new MigrationException("Unexpected exception while creating patch store.", ex); + } + finally + { + SqlUtil.close(conn, stmt, rs); + } + } + + /** + * {@inheritDoc} + */ + public int getPatchLevel() throws MigrationException + { + createPatchStoreIfNeeded(); + + Connection conn = null; + PreparedStatement stmt = null; + ResultSet rs = null; + try + { + conn = context.getConnection(); + stmt = conn.prepareStatement(getSql("level.read")); + stmt.setString(1, context.getSystemName()); + rs = stmt.executeQuery(); + if (rs.next()) + { + return rs.getInt(1); + } + + SqlUtil.close(conn, stmt, rs); + conn = null; + stmt = null; + rs = null; + + return 0; + } + catch (SQLException e) + { + throw new MigrationException("Unable to get patch level", e); + } + finally + { + SqlUtil.close(conn, stmt, rs); + } + } + + /** + * {@inheritDoc} + */ + public void updatePatchLevel(int level) throws MigrationException + { + // Make sure a patch record already exists for this system + getPatchLevel(); + + Connection conn = null; + PreparedStatement stmt = null; + try + { + conn = context.getConnection(); + stmt = conn.prepareStatement(getSql("level.update")); + stmt.setInt(1, level); + stmt.setString(2, context.getSystemName()); + stmt.execute(); + context.commit(); + } + catch (SQLException e) + { + throw new MigrationException("Unable to update patch level", e); + } + finally + { + SqlUtil.close(conn, stmt, null); + } + } + + /** + * {@inheritDoc} + */ + public boolean isPatchStoreLocked() throws MigrationException + { + createPatchStoreIfNeeded(); + + Connection conn = null; + PreparedStatement stmt = null; + ResultSet rs = null; + try + { + conn = context.getConnection(); + stmt = conn.prepareStatement(getSql("lock.read")); + stmt.setString(1, context.getSystemName()); + stmt.setString(2, context.getSystemName()); + rs = stmt.executeQuery(); + + if (rs.next()) + { + return ("T".equals(rs.getString(1))); + } + else + { + return false; + } + } + catch (SQLException e) + { + throw new MigrationException("Unable to determine if table is locked", e); + } + finally + { + SqlUtil.close(conn, stmt, rs); + } + } + + /** + * {@inheritDoc} + */ + public void lockPatchStore() throws MigrationException, IllegalStateException + { + createPatchStoreIfNeeded(); + if (!updatePatchLock(true)) + { + throw new IllegalStateException("Patch table is already locked!"); + } + } + + /** + * {@inheritDoc} + */ + public void unlockPatchStore() throws MigrationException + { + updatePatchLock(false); + } + + /** + * {@inheritDoc} + */ + public boolean isPatchApplied(int patchLevel) throws MigrationException + { + Connection conn = null; + PreparedStatement stmt = null; + ResultSet rs = null; + try + { + conn = context.getConnection(); + stmt = conn.prepareStatement(getSql("level.exists")); + stmt.setString(1, context.getSystemName()); + stmt.setString(2, String.valueOf(patchLevel)); + rs = stmt.executeQuery(); + if (rs.next()) + { + return patchLevel == rs.getInt(1); + } + else + { + return false; + } + + } + catch (SQLException e) + { + throw new MigrationException("Unable to determine if patch has been applied", e); + } + finally + { + SqlUtil.close(conn, stmt, rs); + } + } + + public void updatePatchLevelAfterRollBack(int rollbackLevel) throws MigrationException + { + // Make sure a patch record already exists for this system + getPatchLevel(); + + Connection conn = null; + PreparedStatement stmt = null; + try + { + conn = context.getConnection(); + stmt = conn.prepareStatement(getSql("level.rollback")); + stmt.setInt(1, rollbackLevel); + stmt.setString(2, context.getSystemName()); + stmt.execute(); + context.commit(); + } + catch (SQLException e) + { + throw new MigrationException("Unable to update patch level", e); + } + finally + { + SqlUtil.close(conn, stmt, null); + } + + } + + /** + * Returns the SQL to execute for the database type associated with this patch table. + * + * @param key the key within database.properties whose + * SQL should be returned + * @return the SQL to execute for the database type associated with this patch table + */ + protected String getSql(String key) + { + return context.getDatabaseType().getProperty(key); + } + + /** + * Creates an initial record in the patches table for this system. + * + * @throws SQLException if an unrecoverable database error occurs + * @throws MigrationException if an unrecoverable database error occurs + */ + private void createSystemPatchRecord() throws MigrationException, SQLException + { + String systemName = context.getSystemName(); + Connection conn = null; + PreparedStatement stmt = null; + try + { + conn = context.getConnection(); + stmt = conn.prepareStatement(getSql("level.create")); + stmt.setString(1, systemName); + stmt.execute(); + context.commit(); + log.info("Created patch record for " + systemName); + } + catch (SQLException e) + { + log.error("Error creating patch record for system '" + systemName + "'", e); + throw e; + } + finally + { + SqlUtil.close(conn, stmt, null); + } + } + + /** + * Obtains or releases a lock for this system in the patches table. If the lock + * was not obtained or released, then false is returned. + * + * @param lock true if a lock is to be obtained, false + * if it is to be removed + * @return true if the lock was updated successfully, otherwise false + * @throws MigrationException if an unrecoverable database error occurs + */ + private boolean updatePatchLock(boolean lock) throws MigrationException + { + String sqlkey = (lock) ? "lock.obtain" : "lock.release"; + Connection conn = null; + PreparedStatement stmt = null; + + try + { + conn = context.getConnection(); + stmt = conn.prepareStatement(getSql(sqlkey)); + if (log.isDebugEnabled()) + { + log.debug("Updating patch table lock: " + getSql(sqlkey)); + } + stmt.setString(1, context.getSystemName()); + if (lock) + { + stmt.setString(2, context.getSystemName()); + } + + int rowsUpdated = stmt.executeUpdate(); + boolean lockUpdated = (rowsUpdated == 1); + context.commit(); + if (log.isDebugEnabled()) + { + log.debug(((lock) ? "Obtained" : "Released") + " lock? " + lockUpdated); + } + return lockUpdated; + } + catch (SQLException e) + { + throw new MigrationException("Unable to update patch lock to " + lock, e); + } + finally + { + SqlUtil.close(conn, stmt, null); + } + } + + public Set getPatchesApplied() throws MigrationException + { + createPatchStoreIfNeeded(); + + Connection connection = null; + PreparedStatement stmt = null; + ResultSet resultSet = null; + Set patches = new HashSet(); + try + { + connection = context.getConnection(); + stmt = connection.prepareStatement(getSql("patches.all")); + stmt.setString(1, context.getSystemName()); + resultSet = stmt.executeQuery(); + while (resultSet.next()) + { + patches.add(resultSet.getInt("patch_level")); + } + + + } + catch (SQLException e) + { + throw new MigrationException("Unable to get patch levels", e); + } + finally + { + SqlUtil.close(connection, stmt, resultSet); + } + return patches; + } +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/SqlLoadMigrationTask.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/SqlLoadMigrationTask.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/SqlLoadMigrationTask.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,162 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration.jdbc; + +import com.tacitknowledge.util.migration.MigrationContext; +import com.tacitknowledge.util.migration.MigrationException; +import com.tacitknowledge.util.migration.MigrationTaskSupport; +import com.tacitknowledge.util.migration.jdbc.util.SqlUtil; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +/** + * Base class used for creating bulk data loading MigrationTasks. + * + * @author Scott Askew (scott@tacitknowledge.com) + */ +public abstract class SqlLoadMigrationTask extends MigrationTaskSupport +{ + /** + * Class logger + */ + private static Log log = LogFactory.getLog(SqlLoadMigrationTask.class); + + /** + * Creates a new SqlScriptMigrationTask. + */ + public SqlLoadMigrationTask() + { + // Nothing to do + } + + /** + * {@inheritDoc} + */ + public void migrate(MigrationContext ctx) throws MigrationException + { + DataSourceMigrationContext context = (DataSourceMigrationContext) ctx; + + Connection conn = null; + PreparedStatement stmt = null; + try + { + conn = context.getConnection(); + stmt = conn.prepareStatement(getStatmentSql()); + List rows = getData(getResourceAsStream()); + int rowCount = rows.size(); + for (int i = 0; i < rowCount; i++) + { + String data = (String) rows.get(i); + boolean loadRowFlag = insert(data, stmt); + if (loadRowFlag) + { + stmt.addBatch(); + if (i % 50 == 0) + { + stmt.executeBatch(); + } + } + } + stmt.executeBatch(); + context.commit(); + } + catch (Exception e) + { + String message = getName() + ": Error running SQL \"" + getStatmentSql() + "\""; + log.error(message, e); + if (e instanceof SQLException) + { + if (((SQLException) e).getNextException() != null) + { + log.error("Chained SQL Exception", ((SQLException) e).getNextException()); + } + } + + context.rollback(); + + throw new MigrationException(message, e); + } + finally + { + SqlUtil.close(conn, stmt, null); + } + } + + /** + * Returns an input stream representing the data to load. + * + * @return an input stream representing the data to load + */ + protected abstract InputStream getResourceAsStream(); + + /** + * Inserts the given row of data into the database using the given + * prepared statement. Subclasses should parse the row and call the + * appropriate set methods on the prepared statement. + * + * @param data the current row of data to load + * @param stmt the statement used for inserting data into the DB + * @return false if you do not want this row loaded, true otherwise + * @throws Exception if an unexpected error occurs + */ + protected abstract boolean insert(String data, PreparedStatement stmt) throws Exception; + + /** + * Returns the PreparedStatement SQL used for inserting rows + * into the table. + * + * @return the PreparedStatement SQL used for inserting rows + * into the table + */ + protected abstract String getStatmentSql(); + + /** + * {@inheritDoc} + */ + public String toString() + { + return getName(); + } + + /** + * Returns the data to load as a list of rows. + * + * @param is the input stream containing the data to load + * @return the data to load as a list of rows + * @throws IOException if the input stream could not be read + */ + protected List getData(InputStream is) throws IOException + { + List data = new ArrayList(); + BufferedReader reader = new BufferedReader(new InputStreamReader(is)); + String line = null; + while ((line = reader.readLine()) != null) + { + data.add(line); + } + return data; + } +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/SqlScriptMigrationTask.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/SqlScriptMigrationTask.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/SqlScriptMigrationTask.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,451 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration.jdbc; + +import com.tacitknowledge.util.migration.MigrationContext; +import com.tacitknowledge.util.migration.MigrationException; +import com.tacitknowledge.util.migration.MigrationTaskSupport; +import com.tacitknowledge.util.migration.jdbc.util.SqlUtil; +import com.tacitknowledge.util.migration.jdbc.util.SybaseUtil; +import org.apache.commons.lang.StringUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.List; +import java.util.ListIterator; +import java.util.regex.Pattern; + +/** + * Adaptss a SQL or DDL database patch for use with the AutoPatch framework. + * + * @author Scott Askew (scott@tacitknowledge.com) + */ +public class SqlScriptMigrationTask extends MigrationTaskSupport +{ + /** + * Class logger + */ + private static Log log = LogFactory.getLog(SqlScriptMigrationTask.class); + + /** + * The SQL to execute + */ + private String sql = null; + + /** + * SQL to migrate down a patch level + */ + private String downSql = null; + + + /** + * Creates a new SqlScriptMigrationTask. + */ + public SqlScriptMigrationTask() + { + // do nothing + } + + /** + * Creates a new SqlScriptMigrationTask + * + * @param name the name of the SQL script to execute; this is just an + * identifier and does not have to correspond to a file name + * @param level the patch level of the migration task + * @param upSql the SQL to execute to migrate the patch level up one + * @param downSql the SQL to execute to rollback to this patch + */ + public SqlScriptMigrationTask(String name, int level, String upSql, String downSql) + { + setName(name); + setLevel(new Integer(level)); + this.sql = upSql; + this.downSql = downSql; + + setRollbackSupported(!"".equals(downSql)); + } + + /** + * Creates a new SqlScriptMigrationTask + * + * @param name the name of the SQL script to execute; this is just an + * identifier and does not have to correspond to a file name + * @param level the patch level of the migration task + * @param sql the SQL to execute + */ + public SqlScriptMigrationTask(String name, int level, String sql) + { + setName(name); + setLevel(new Integer(level)); + this.sql = sql; + this.downSql = ""; + } + + /** + * Creates a new SqlScriptMigrationTask containing the SQL + * contained in the given InputStream. + * + * @param name the name of the SQL script to execute; this is just an + * identifier and does not have to correspond to a file name + * @param level the patch level of the migration task + * @param is the source of the SQL to execute + * @throws IOException if there was problem reading the input stream + */ + public SqlScriptMigrationTask(String name, int level, InputStream is) throws IOException + { + setName(name); + setLevel(new Integer(level)); + StringBuffer sqlBuffer = new StringBuffer(); + BufferedReader buf = new BufferedReader(new InputStreamReader(is)); + String line = buf.readLine(); + while (line != null) + { + sqlBuffer.append(line).append("\n"); + line = buf.readLine(); + } + sql = sqlBuffer.toString(); + } + + /** + * {@inheritDoc} + */ + public void up(MigrationContext context) throws MigrationException + { + executeSql(context, sql); + } + + /** + * {@inheritDoc} + */ + public void down(MigrationContext context) throws MigrationException + { + executeSql(context, downSql); + } + + /** + * Executes the passed sql in the passed context. + * + * @param ctx the MigrationContext> to execute the SQL in + * @param sqlToExec the SQL to execute + * @throws MigrationException thrown if there is an error when executing the SQL + */ + private void executeSql(MigrationContext ctx, String sqlToExec) + throws MigrationException + { + JdbcMigrationContext context = (JdbcMigrationContext) ctx; + + Connection conn = null; + Statement stmt = null; + String sqlStatement = ""; + ListIterator listIterator = null; + try + { + conn = context.getConnection(); + + // cleaning the slate before we execute the patch. + // This was inspired by a Sybase ASE server that did not allow + // ALTER TABLE statements in multi-statement transactions. Instead of putting + // a if(sybase) conditional, we decided to clean the slate for everyone. + context.commit(); + + List sqlStatements = getSqlStatements(context, sqlToExec); + for (listIterator = sqlStatements.listIterator(); listIterator.hasNext();) + { + sqlStatement = (String) listIterator.next(); + log.debug(getName() + ": Attempting to execute: " + sqlStatement); + + stmt = conn.createStatement(); + + // handle sybase special case with illegal commands in multi + // command transactions + if (isSybase(context) + && SybaseUtil.containsIllegalMultiStatementTransactionCommand(sqlStatement)) + { + log.warn("Committing current transaction since patch " + getName() + + " contains commands that are not allowed in multi statement" + + " transactions. If the patch contains errors, this patch may" + + " not be rolled back cleanly."); + context.commit(); + stmt.execute(sqlStatement); + context.commit(); + } + else // regular case + { + stmt.execute(sqlStatement); + } + + SqlUtil.close(null, stmt, null); + } + + context.commit(); + } + catch (Exception e) + { + String message = getName() + ": Error running SQL at statement number " + + listIterator.previousIndex() + " \"" + sqlStatement + "\""; + log.error(message, e); + + if (e instanceof SQLException) + { + if (((SQLException) e).getNextException() != null) + { + log.error("Chained SQL Exception", ((SQLException) e).getNextException()); + } + } + + context.rollback(); + throw new MigrationException(message, e); + } + finally + { + SqlUtil.close(null, stmt, null); + } + } + + public List getSqlStatements(JdbcMigrationContext context) + { + return getSqlStatements(context, sql); + } + + /** + * Parses the SQL/DDL to execute and returns a list of individual statements. For database + * types that support mulitple statements in a single Statement.execute call, + * this method will return a one-element List containing the entire SQL + * file. + * + * @param context the MigrationContext, to figure out db type and if it + * can handle multiple statements at once + * @return a list of SQL and DDL statements to execute + */ + public List getSqlStatements(JdbcMigrationContext context, String sqlStatements) + { + List statements = new ArrayList(); + if (context.getDatabaseType().isMultipleStatementsSupported()) + { + statements.add(sqlStatements); + return statements; + } + + StringBuffer currentStatement = new StringBuffer(); + boolean inQuotedString = false; + boolean inComment = false; + char[] sqlChars = sqlStatements.toCharArray(); + for (int i = 0; i < sqlChars.length; i++) + { + if (sqlChars[i] == '\n') + { + inComment = false; + } + + if (!inComment) + { + switch (sqlChars[i]) + { + case '-': + case '/': + if (!inQuotedString && i + 1 < sqlChars.length + && sqlChars[i + 1] == sqlChars[i]) + { + inComment = true; + } + else + { + currentStatement.append(sqlChars[i]); + } + break; + case '\'': + inQuotedString = !inQuotedString; + currentStatement.append(sqlChars[i]); + break; + case ';': + if (!inQuotedString) + { + // If we're in a stored procedure, just keep rolling + if (isStoredProcedure(context.getDatabaseType().getDatabaseType(), + currentStatement.toString())) + { + currentStatement.append(sqlChars[i]); + } + else + { + statements.add(currentStatement.toString().trim()); + currentStatement = new StringBuffer(); + } + } + else + { + currentStatement.append(sqlChars[i]); + } + break; + /* sybase uses 'GO' as it's statement delimiter */ + case 'g': + case 'G': + /* + * Build up a string, reading backwards from the current index to + * the previous newline (or beginning of sequence) and from the + * current index up to the next newline. If it matches the regex + * for the GO delimiter, then add the statement otherwise + * just append the current index's character to currentStatement + */ + if (context.getDatabaseType().getDatabaseType().equals("sybase")) + { + // read from current index to previous line terminator + // or start of sequence + StringBuffer previous = new StringBuffer(); + for (int j = i - 1; j >= 0; j--) + { + char c = sqlChars[j]; + previous.append(c); + if (isLineTerminator(c)) + { + break; + } + } + + // reverse previous, since we've been walking backwards, but appending + previous = previous.reverse(); + + // read from current index to upcoming line terminator + // or end of sequence. If it is the GO delimiter, + // we skip up to line terminator + StringBuffer after = new StringBuffer(); + int newIndex = 0; + for (int k = i + 1; k < sqlChars.length; k++) + { + char c = sqlChars[k]; + after.append(c); + newIndex = k; + if (isLineTerminator(c)) + { + break; + } + } + + // check against the pattern if its a GO delimiter + String possibleDelimiter = previous + .append(sqlChars[i]).append(after).toString(); + final String delimiterPattern = "^\\s*[Gg][Oo]\\s*$"; + + if (Pattern.matches(delimiterPattern, possibleDelimiter)) + { + // if it's blank, don't bother adding it since Sybase + // will complain about empty queries. + // This happens if there are two GO's with no + // actual SQL to run between them. + if (!StringUtils.isBlank(currentStatement.toString().trim())) + { + statements.add(currentStatement.toString().trim()); + } + currentStatement = new StringBuffer(); + // skip up to next line terminator + i = newIndex; + } + else // not a delimiter, so just append + { + currentStatement.append(sqlChars[i]); + } + } + else // not a sybase db, so just append + { + currentStatement.append(sqlChars[i]); + } + break; + default: + currentStatement.append(sqlChars[i]); + break; + } + } + } + if (currentStatement.toString().trim().length() > 0) + { + statements.add(currentStatement.toString().trim()); + } + + return statements; + } + + /** + * Return true if the string represents a stored procedure + * + * @param databaseType the type of the database + * @param statement the statement that may be a stored procedure + * @return true if the statement is a stored procedure for the given db type + */ + protected boolean isStoredProcedure(String databaseType, String statement) + { + String currentStatement = statement.trim().toLowerCase(); + if ("oracle".equals(databaseType) + && (currentStatement.startsWith("begin") + || currentStatement.startsWith("create or replace method") + || currentStatement.startsWith("create or replace function") + || currentStatement.startsWith("create or replace procedure") + || currentStatement.startsWith("create or replace package"))) + { + return true; + } + if ("mysql".equals(databaseType) + && (currentStatement.startsWith("create procedure") + || currentStatement.startsWith("create function"))) + { + return true; + } + + return false; + } + + /** + * return true if c is a line terminator as detailed in + * http://java.sun.com/j2se/1.5.0/docs/api/java/util/regex/Pattern.html + * + * @param c the char to test + * @return true if it is a line terminator + */ + protected boolean isLineTerminator(char c) + { + return (c == '\n') // newline + || (c == '\r') // carriage return + || (c == '\u0085') // next-line + || (c == '\u2028') // line-separator + || (c == '\u2029'); // paragraph separator + } + + /** + * Check if the current migration context is against a sybase database. + * + * @param context the context to check. + * @return true if context is in a sybase database. + */ + protected boolean isSybase(JdbcMigrationContext context) + { + return context.getDatabaseType().getDatabaseType().equalsIgnoreCase("sybase"); + } + + /** + * {@inheritDoc} + */ + public String toString() + { + return getName(); + } +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/SqlScriptMigrationTaskSource.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/SqlScriptMigrationTaskSource.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/SqlScriptMigrationTaskSource.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,253 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration.jdbc; + +import com.tacitknowledge.util.discovery.ClassDiscoveryUtil; +import com.tacitknowledge.util.migration.MigrationException; +import com.tacitknowledge.util.migration.MigrationTask; +import com.tacitknowledge.util.migration.MigrationTaskSource; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import java.io.*; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Search a package (directory) for SQL scripts that a specific pattern and + * returns corresponding SqlScriptMigrationTasks. The name of + * each script must follow the pattern of "patch(\d+)(_.+)?\.sql". + * + * @author Scott Askew (scott@tacitknowledge.com) + */ +public class SqlScriptMigrationTaskSource implements MigrationTaskSource +{ + /** + * Class logger + */ + private static Log log = LogFactory + .getLog(SqlScriptMigrationTaskSource.class); + + /** + * The regular expression used to match SQL patch files. + */ + private static final String SQL_PATCH_REGEX = "^patch(\\d++)(?!-rollback)_?(.+)?\\.sql"; + + /** + * The regular expression used to match SQL rollback files + */ + private static final String SQL_ROLLBACK_REGEX = "^patch(\\d++)-rollback_?(.+)?\\.sql"; + + /** + * {@inheritDoc} + */ + public List getMigrationTasks(String packageName) throws MigrationException + { + String path = packageName.replace('.', '/'); + + String[] upScripts = getSqlScripts(path, SQL_PATCH_REGEX); + String[] downScripts = getSqlScripts(path, SQL_ROLLBACK_REGEX); + + return createMigrationScripts(upScripts, downScripts); + } + + /** + * Returns the SQL scripts which exist in the specified patch and match the specified regex. + * + * @param path the Path to search + * @param regex the regex which the scripts should match + * @return a String array of script names. + */ + private String[] getSqlScripts(String path, String regex) + { + String[] scripts = ClassDiscoveryUtil.getResources(path, regex); + if (log.isDebugEnabled()) + { + log.debug("Found " + scripts.length + " patches in path: " + path); + for (int i = 0; i < scripts.length; i++) + { + log.debug(" -- \"" + scripts[i] + "\""); + } + } + return scripts; + } + + /** + * Creates a list of SqlScriptMigrationTasks based on the + * array of SQL scripts. + * + * @param upScripts the classpath-relative array of SQL migration scripts + * @param downScripts the classpath-relative array of SQL migration scripts + * @return a list of SqlScriptMigrationTasks based on the + * array of SQL scripts + * @throws MigrationException if a SqlScriptMigrationTask could no be created + */ + private List createMigrationScripts(String[] upScripts, String[] downScripts) + throws MigrationException + { + Pattern upFileNamePattern = Pattern.compile(SQL_PATCH_REGEX); + Pattern downFileNamePattern = Pattern.compile(SQL_ROLLBACK_REGEX); + + List tasks = new ArrayList(); + for (int i = 0; i < upScripts.length; i++) + { + String script = upScripts[i]; + + if (script != null) + { + // get the file name + File scriptFile = new File(script); + String scriptFileName = scriptFile.getName(); + + // Get the version out of the script name + int order = getOrder(upFileNamePattern, script, scriptFileName); + + // get the down script which matches this patch order + String downScript = getMatchingDownScript(downScripts, order, + downFileNamePattern); + + // read the scripts + String upSql = readSql(getInputStream(script)); + String downSql = ""; + + // if the down script is not the empty string, then try to read + // it. + if (!"".equals(downScript)) + downSql = readSql(getInputStream(downScript)); + + // create a new task + SqlScriptMigrationTask task = new SqlScriptMigrationTask( + scriptFileName, order, upSql, downSql); + task.setName(scriptFileName); + + // add the task to the list of tasks + tasks.add(task); + } + } + return tasks; + } + + /** + * Returns the filename of the downscript which matches the order of the order parameter. + * + * @param downScripts an array of scripts + * @param order the order of the down script whose name should be returned + * @return the name of the down script that matches the order + */ + private String getMatchingDownScript(String[] downScripts, int order, + Pattern fileNamePattern) throws MigrationException + { + boolean isScriptFound = false; + String script = ""; + for (int i = 0; i < downScripts.length && !isScriptFound; i++) + { + File scriptFile = new File(downScripts[i]); + String scriptFileName = scriptFile.getName(); + int downScriptOrder = getOrder(fileNamePattern, downScripts[i], + scriptFileName); + + if (downScriptOrder == order) + { + script = downScripts[i]; + isScriptFound = true; + } + } + + if (!isScriptFound) + { + log.info("There was no rollback script for patch level: " + order); + } + return script; + } + + /** + * Returns an input stream that points to the script name + * + * @param scriptName the name of the script to create an InputStream + * @return an InputStream returns an InputStream based upon the scriptName + */ + private InputStream getInputStream(String scriptName) + { + scriptName = scriptName.replace('\\', '/'); + log.debug("Examining possible SQL patch file \"" + scriptName + "\""); + return Thread.currentThread().getContextClassLoader() + .getResourceAsStream(scriptName); + } + + /** + * Reads the file contents into a String object. + * + * @param is the InputStream + * @return a String with the contents of the InputStream + * @throws MigrationException if there's an error reading in the contents + */ + private String readSql(InputStream is) throws MigrationException + { + StringBuffer sqlBuffer = new StringBuffer(); + BufferedReader buf = new BufferedReader(new InputStreamReader(is)); + + try + { + String line = buf.readLine(); + while (line != null) + { + sqlBuffer.append(line).append("\n"); + line = buf.readLine(); + } + } + catch (IOException ioe) + { + throw new MigrationException( + "There was an error reading in a script", ioe); + + } + finally + { + try + { + is.close(); + } + catch (IOException ioe) + { + log.error("Could not close input stream", ioe); + } + } + return sqlBuffer.toString(); + } + + /** + * Returns the order for the file. + * + * @param p a Pattern defining the file name pattern + * @param script the Script + * @param scriptFileName the name of the file + * @return an int indicating the order + * @throws MigrationException in case the file name is invalid + */ + private int getOrder(Pattern p, String script, String scriptFileName) + throws MigrationException + { + Matcher matcher = p.matcher(scriptFileName); + if (!matcher.matches() || matcher.groupCount() != 2) + { + throw new MigrationException("Invalid SQL script name: " + script); + } + int order = Integer.parseInt(matcher.group(1)); + return order; + } +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/StandaloneMigrationLauncher.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/StandaloneMigrationLauncher.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/StandaloneMigrationLauncher.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,198 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration.jdbc; + +import com.tacitknowledge.util.migration.AutopatchRegistry; +import com.tacitknowledge.util.migration.MigrationException; +import com.tacitknowledge.util.migration.jdbc.util.ConfigurationUtil; +import com.tacitknowledge.util.migration.jdbc.util.MigrationUtil; +import org.apache.commons.lang.ArrayUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.picocontainer.PicoContainer; + +/** + * Launches the migration process as a standalone application. + *

+ * This class expects the following Java environment parameters: + *

    + *
  • migration.systemname - the name of the logical system being migrated
  • + *
  • migration.settings (optional) - the name of the settings file to use for + * migration
  • + *
+ *

+ * Below is an example of how this class can be configured in build.xml: + *

+ *

+ *   ...
+ *  <target name="patch.database" description="Runs the migration system">
+ *   <java
+ *       fork="true"
+ *       classpathref="patch.classpath"
+ *       failonerror="true"
+ *       classname="com.tacitknowledge.util.migration.jdbc.StandaloneMigrationLauncher">
+ *     <sysproperty key="migration.systemname" value="${application.name}"/>
+ *   </java>
+ * </target>
+ *   ...
+ * 
+ * + * @author Mike Hardy (mike@tacitknowledge.com) + * @see com.tacitknowledge.util.migration.MigrationProcess + */ +public final class StandaloneMigrationLauncher +{ + /** + * The force rollback parameter + */ + private static final String FORCE_ROLLBACK = "-force"; + + /** + * The rollback parameter + */ + private static final String ROLLBACK = "-rollback"; + + /** + * Class logger + */ + private static Log log = LogFactory.getLog(StandaloneMigrationLauncher.class); + + private MigrationUtil migrationUtil; + + public StandaloneMigrationLauncher(MigrationUtil migrationUtil) + { + this.migrationUtil = migrationUtil; + } + + /** + * Run the migrations for the given system name + * + * @param arguments the command line arguments, if any + * @throws Exception if anything goes wrong + */ + public static void main(final String[] arguments) throws Exception + { + AutopatchRegistry registry = new AutopatchRegistry(); + PicoContainer pico = registry.configureContainer(); + + JdbcMigrationLauncherFactory launcherFactory = new JdbcMigrationLauncherFactoryLoader().createFactory(); + StandaloneMigrationLauncher migrationLauncher = pico.getComponent(StandaloneMigrationLauncher.class); + migrationLauncher.getMigrationUtil().setLauncherFactory(launcherFactory); + migrationLauncher.run(arguments); + registry.destroyContainer(); + } + + void run(String[] arguments) throws Exception + { + + String migrationSystemName = ConfigurationUtil.getRequiredParam("migration.systemname", + System.getProperties(), arguments, 0); + String migrationSettings = ConfigurationUtil.getOptionalParam("migration.settings", System + .getProperties(), arguments, 1); + + boolean isRollback = false; + int[] rollbackLevels = new int[]{}; + boolean forceRollback = false; + + for (int i = 0; i < arguments.length; i++) + { + String argument1 = arguments[i]; + + if (ROLLBACK.equals(argument1)) + { + isRollback = true; + + if (i + 2 <= arguments.length) + { + String argument2 = arguments[i + 1]; + + if (argument2 != null) + { + try + { + rollbackLevels = getRollbackLevels(argument2); + } + catch (NumberFormatException nfe) + { + throw new MigrationException("The rollbacklevels should be integers separated by a comma"); + } + } + } + + if (rollbackLevels.length == 0) + { + // this indicates that the rollback level has not been set + throw new MigrationException( + "The rollback flag requires a following integer parameter or a list of integer parameters separated by comma to indicate the rollback level(s)."); + } + } + + if (FORCE_ROLLBACK.equals(argument1)) + { + forceRollback = true; + } + + } + + // The MigrationLauncher is responsible for handling the interaction + // between the PatchTable and the underlying MigrationTasks; as each + // task is executed, the patch level is incremented, etc. + try + { + + if (isRollback) + { + String infoMessage = "Found rollback flag. AutoPatch will attempt to rollback the system to patch level(s) " + + ArrayUtils.toString(rollbackLevels) + "."; + log.info(infoMessage); + + migrationUtil.doRollbacks(migrationSystemName, migrationSettings, rollbackLevels, forceRollback); + } + else + { + migrationUtil.doMigrations(migrationSystemName, migrationSettings); + } + } + catch (Exception e) + { + log.error(e); + throw e; + } + } + + private int[] getRollbackLevels(String rollbackLevelsString) throws NumberFormatException + { + String[] rollbackLevelsStringArray = rollbackLevelsString.split(","); + int[] rollbackLevels = new int[rollbackLevelsStringArray.length]; + + for (int i = 0; i < rollbackLevelsStringArray.length; i++) + { + rollbackLevels[i] = Integer.parseInt(rollbackLevelsStringArray[i]); + } + return rollbackLevels; + } + + public void setMigrationUtil(MigrationUtil migrationUtil) + { + this.migrationUtil = migrationUtil; + } + + public MigrationUtil getMigrationUtil() + { + return migrationUtil; + } + +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/WebAppJNDIMigrationLauncher.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/WebAppJNDIMigrationLauncher.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/WebAppJNDIMigrationLauncher.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,113 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration.jdbc; + +import com.tacitknowledge.util.discovery.ClassDiscoveryUtil; +import com.tacitknowledge.util.discovery.WebAppResourceListSource; +import com.tacitknowledge.util.migration.MigrationException; +import com.tacitknowledge.util.migration.jdbc.util.MigrationUtil; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; + +/** + * Used to configure the migration engine using JNDI and properties + * set in the servlet context for a web application. This newer class + * removes the need to use a migration.properties file. Instead, set the + * following properties (context-param) in the web.xml file: + *

+ *

    + *
  • migration.systemname - name of the system to update + *
  • migration.databasetype - ex: mysql + *
  • migration.patchpath - colon separated path to look for files + *
  • migration.datasource - ex: jdbc/clickstream + *
+ * All properties listed above are required. + * + * @author Chris A. (chris@tacitknowledge.com) + */ +public class WebAppJNDIMigrationLauncher implements ServletContextListener +{ + /** + * Keeps track of the first run of the class within this web app deployment. + * This should always be true, but you can never be too careful. + */ + private static boolean firstRun = true; + + /** + * Class logger + */ + private static Log log = LogFactory.getLog(WebAppJNDIMigrationLauncher.class); + + /** + * {@inheritDoc} + */ + public void contextInitialized(ServletContextEvent sce) + { + try + { + + // WEB-INF/classes and WEB-INF/lib are not in the system classpath (as defined by + // System.getProperty("java.class.path")); add it to the search path here + if (firstRun) + { + ClassDiscoveryUtil.addResourceListSource( + new WebAppResourceListSource(sce.getServletContext().getRealPath("/WEB-INF"))); + } + firstRun = false; + + // The MigrationLauncher is responsible for handling the interaction + // between the PatchTable and the underlying MigrationTasks; as each + // task is executed, the patch level is incremented, etc. + try + { + MigrationUtil.doMigrations(sce); + } + catch (MigrationException e) + { + // Runtime exceptions coming from a ServletContextListener prevent the + // application from being deployed. In this case, the intention is + // for migration-enabled applications to fail-fast if there are any + // errors during migration. + throw new RuntimeException("Migration exception caught during migration", e); + } + } + catch (RuntimeException e) + { + // Catch all exceptions for the sole reason of logging in + // as many places as possible - debugging migration + // problems requires detection first, and that means + // getting the word of failures out. + log.error(e); + System.out.println(e.getMessage()); + e.printStackTrace(System.out); + System.err.println(e.getMessage()); + e.printStackTrace(System.err); + + throw e; + } + } + + /** + * {@inheritDoc} + */ + public void contextDestroyed(ServletContextEvent sce) + { + log.debug("context is being destroyed " + sce); + } +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/WebAppMigrationLauncher.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/WebAppMigrationLauncher.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/WebAppMigrationLauncher.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,134 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration.jdbc; + +import com.tacitknowledge.util.discovery.ClassDiscoveryUtil; +import com.tacitknowledge.util.discovery.WebAppResourceListSource; +import com.tacitknowledge.util.migration.MigrationException; +import com.tacitknowledge.util.migration.jdbc.util.ConfigurationUtil; +import com.tacitknowledge.util.migration.jdbc.util.MigrationUtil; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; + +/** + * Launches the migration process upon application context creation. This class + * is intentionally fail-fast, meaning that it throws a RuntimeException if any + * problems arise during migration and will prevent the web application from + * being fully deployed. + *

+ * This class expects the following servlet context init parameters: + *

    + *
  • migration.systemname - the name of the logical system being migrated
  • + *
+ *

+ * Below is an example of how this class can be configured in web.xml: + *

+ *   ...
+ *   <context-param>
+ *       <param-name>migration.systemname</param-name>
+ *       <param-value>milestone</param-value>
+ *   </context-param>
+ *   ...
+ *   <!-- immediately after the filter configs... -->
+ *   ...
+ *   <listener>
+ *       <listener-class>
+ *           com.tacitknowledge.util.migration.jdbc.WebAppMigrationLauncher
+ *       </listener-class>
+ *   </listener>
+ *   ...
+ * 
+ * + * @author Scott Askew (scott@tacitknowledge.com) + * @see com.tacitknowledge.util.migration.MigrationProcess + */ +public class WebAppMigrationLauncher implements ServletContextListener +{ + /** + * Keeps track of the first run of the class within this web app deployment. + * This should always be true, but you can never be too careful. + */ + private static boolean firstRun = true; + + /** + * Class logger + */ + private static Log log = LogFactory.getLog(WebAppMigrationLauncher.class); + + /** + * {@inheritDoc} + */ + public void contextInitialized(ServletContextEvent sce) + { + try + { + + // WEB-INF/classes and WEB-INF/lib are not in the system classpath (as defined by + // System.getProperty("java.class.path")); add it to the search path here + if (firstRun) + { + ClassDiscoveryUtil.addResourceListSource( + new WebAppResourceListSource(sce.getServletContext().getRealPath("/WEB-INF"))); + } + firstRun = false; + + String systemName = ConfigurationUtil.getRequiredParam("migration.systemname", sce, this); + String settings = ConfigurationUtil.getOptionalParam("migration.settings", sce, this); + + // The MigrationLauncher is responsible for handling the interaction + // between the PatchTable and the underlying MigrationTasks; as each + // task is executed, the patch level is incremented, etc. + try + { + MigrationUtil.doMigrations(systemName, settings); + } + catch (MigrationException e) + { + // Runtime exceptions coming from a ServletContextListener prevent the + // application from being deployed. In this case, the intention is + // for migration-enabled applications to fail-fast if there are any + // errors during migration. + throw new RuntimeException("Migration exception caught during migration", e); + } + } + catch (RuntimeException e) + { + // Catch all exceptions for the sole reason of logging in + // as many places as possible - debugging migration + // problems requires detection first, and that means + // getting the word of failures out. + log.error(e); + System.out.println(e.getMessage()); + e.printStackTrace(System.out); + System.err.println(e.getMessage()); + e.printStackTrace(System.err); + + throw e; + } + } + + /** + * {@inheritDoc} + */ + public void contextDestroyed(ServletContextEvent sce) + { + log.debug("context is being destroyed " + sce); + } + +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/loader/DelimitedFileLoader.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/loader/DelimitedFileLoader.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/loader/DelimitedFileLoader.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,205 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration.jdbc.loader; + +import com.tacitknowledge.util.migration.jdbc.SqlLoadMigrationTask; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import java.io.*; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.StringTokenizer; + +/** + * Loads files assumed to be in a delimited format and representing a + * table in the database. The file name should begin with the prefix: + * "<tablename>_tb". The file's first row should represent the name of + * each column in the table that the underlying data elements (rows 2 + * through n) will be mapped to. + * + * @author Chris A. (chris@tacitknowledge.com) + */ +public abstract class DelimitedFileLoader extends SqlLoadMigrationTask +{ + /** + * The path separator + */ + public static final String PATH_SEPARATOR = File.separator; + + /** + * Class logger + */ + private static Log log = LogFactory.getLog(DelimitedFileLoader.class); + + /** + * Private variable that indicates if the header has been parsed or not. + */ + private boolean parsedHeader = false; + + /** + * Gets the expected file delimiter. A pipe-delimited + * reader should return "|", for example. + * + * @return the delimiter string used to separate columns + */ + public abstract String getDelimiter(); + + /** + * Should return the name of the file to load. This will + * support either an absolute name, or the name of a file + * in the classpath of the application in which this is executed. + * + * @return the absolute path name of the table to load + */ + public abstract String getName(); + + /** + * Parses a line of data, and sets the prepared statement with the + * values. If a token contains "<null>" then a null value is passed + * in. + * + * @param data the tokenized string that is mapped to a row + * @param stmt the statement to populate with data to be inserted + * @return false if the header is returned, true otherwise + * @throws SQLException if an error occurs while inserting data into the database + */ + protected boolean insert(String data, PreparedStatement stmt) throws SQLException + { + if (!parsedHeader) + { + parsedHeader = true; + log.info("Header returned: " + data); + return false; + } + StringTokenizer st = new StringTokenizer(data, getDelimiter()); + int counter = 1; + log.info("Row being parsed: " + data); + while (st.hasMoreTokens()) + { + String colVal = st.nextToken(); + if (colVal.equalsIgnoreCase("")) + { + stmt.setString(counter, null); + } + else + { + stmt.setString(counter, colVal); + } + counter++; + } + return true; + } + + /** + * Returns the table name from the full path name + * by parsing it out of a file in the format + * name_db<period>(some extension) + * + * @return the name of the table to add data to + */ + protected String getTableFromName() + { + String name = getName(); + int startTable = name.lastIndexOf(PATH_SEPARATOR); + int endTable = name.indexOf("_db", startTable); + return name.substring((startTable + 1), endTable); + } + + /** + * Parses the table name from the file name, and the column names + * from the header (first row) of the delimited file. Creates an + * insert query to insert a single row. + * + * @return query in the format: INSERT INTO tablename (colname, colname)... + */ + protected String getStatmentSql() + { + try + { + String columnHeader = getHeader(getResourceAsStream()); + String delimiter = getDelimiter(); + StringTokenizer st = new StringTokenizer(columnHeader, delimiter); + ArrayList columnNames = new ArrayList(); + while (st.hasMoreTokens()) + { + columnNames.add((st.nextToken().trim())); + } + StringBuffer query = new StringBuffer("INSERT INTO "); + query.append(getTableFromName()); + query.append(" ("); + Iterator it = columnNames.iterator(); + boolean firstTime = true; + while (it.hasNext()) + { + if (!firstTime) + { + query.append(", "); + } + else + { + firstTime = false; + } + query.append((String) it.next()); + } + query.append(") VALUES ("); + for (int i = 0; i < columnNames.size(); i++) + { + if (i > 0) + { + query.append(", "); + } + query.append("?"); + } + query.append(")"); + return query.toString(); + } + catch (IOException e) + { + log.error("No header was found for file: " + getName(), e); + } + return null; + } + + /** + * Gets an input stream by first checking the current classloader, + * then trying to use the system classloader, and finally, trying + * to access the file on the file system. If the file is not found, + * an IllegalArgumentException will be thrown. + * + * @return the file as an input stream + */ + protected InputStream getResourceAsStream() + { + FileLoadingUtility utility = new FileLoadingUtility(getName()); + return utility.getResourceAsStream(); + } + + /** + * Returns the header (first line) of the file. + * + * @param is the input stream containing the data to load + * @return the first row + * @throws IOException if the input stream could not be read + */ + protected String getHeader(InputStream is) throws IOException + { + BufferedReader reader = new BufferedReader(new InputStreamReader(is)); + return reader.readLine(); + } +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/loader/ExcelFileLoader.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/loader/ExcelFileLoader.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/loader/ExcelFileLoader.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,95 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration.jdbc.loader; + +import com.tacitknowledge.util.migration.MigrationContext; +import com.tacitknowledge.util.migration.MigrationException; +import com.tacitknowledge.util.migration.MigrationTaskSupport; +import com.tacitknowledge.util.migration.jdbc.DataSourceMigrationContext; +import com.tacitknowledge.util.migration.jdbc.util.SqlUtil; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.poifs.filesystem.POIFSFileSystem; + +import java.io.IOException; +import java.sql.Connection; +import java.sql.SQLException; + +/** + * This is a utility class for reading excel files and + * performing a database insert based upon a cell value + * provided. + * + * @author Chris A. (chris@tacitknowledge.com) + */ +public abstract class ExcelFileLoader extends MigrationTaskSupport +{ + /** + * Class logger + */ + private static Log log = LogFactory.getLog(ExcelFileLoader.class); + + /** + * Obtains a database connection, reads a file assumed to be in Excel + * format based on the name provided getName(). Calls the + * abstract method processWorkbook() + * + * @param ctx the JdbcMigrationContext + * @throws MigrationException if an unexpected error occurs + */ + public void migrate(MigrationContext ctx) throws MigrationException + { + DataSourceMigrationContext context = (DataSourceMigrationContext) ctx; + FileLoadingUtility utility = new FileLoadingUtility(getName()); + Connection conn = null; + + try + { + conn = context.getConnection(); + POIFSFileSystem fs = new POIFSFileSystem(utility.getResourceAsStream()); + HSSFWorkbook wb = new HSSFWorkbook(fs); + processWorkbook(wb, conn); + context.commit(); + } + catch (IOException e) + { + log.error("An IO Exception occurred while trying to parse the Excel file.", e); + context.rollback(); + throw new MigrationException("Error reading file.", e); + } + catch (SQLException e) + { + log.error("Caught a SQLException when trying to obtain a database connection"); + context.rollback(); + throw new MigrationException("Error obtaining database connection", e); + } + finally + { + SqlUtil.close(conn, null, null); + } + } + + /** + * Process workbook by overwriting this method + * + * @param wb the excel workbook to process + * @param conn the database connection to use for data loading + * @throws MigrationException if something goes wrong + */ + public abstract void processWorkbook(HSSFWorkbook wb, Connection conn) + throws MigrationException; +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/loader/FileLoadingUtility.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/loader/FileLoadingUtility.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/loader/FileLoadingUtility.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,87 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration.jdbc.loader; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.InputStream; + +/** + * This is a very simple utility that looks for a file + * based upon its existence in the classpath or the + * absolute path if provided. + * + * @author Chris A. (chris@tacitknowledge.com) + */ +public class FileLoadingUtility +{ + /** + * Class logger + */ + private static Log log = LogFactory.getLog(FileLoadingUtility.class); + + /** + * The name of the file to load + */ + private String fileName = null; + + /** + * Creates a new FileLoadingUtility. + * + * @param fileName the name of the file to load + */ + public FileLoadingUtility(String fileName) + { + this.fileName = fileName; + } + + /** + * Gets an input stream by first checking the current classloader, + * then trying to use the system classloader, and finally, trying + * to access the file on the file system. If the file is not found, + * an IllegalArgumentException will be thrown. + * + * @return the file as an input stream + */ + public InputStream getResourceAsStream() + { + InputStream stream = + Thread.currentThread().getContextClassLoader().getResourceAsStream(fileName); + if (stream == null) + { + stream = ClassLoader.getSystemResourceAsStream(fileName); + + } + if (stream == null) + { + File f = new File(fileName); + try + { + stream = new FileInputStream(f); + } + catch (FileNotFoundException e) + { + log.error("The file: " + fileName + " was not found.", e); + throw new IllegalArgumentException("Must have a valid file name."); + } + } + return stream; + } +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/loader/FlatXmlDataSetMigrationTask.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/loader/FlatXmlDataSetMigrationTask.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/loader/FlatXmlDataSetMigrationTask.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,120 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration.jdbc.loader; + +import com.tacitknowledge.util.migration.MigrationContext; +import com.tacitknowledge.util.migration.MigrationException; +import com.tacitknowledge.util.migration.MigrationTaskSupport; +import com.tacitknowledge.util.migration.jdbc.JdbcMigrationContext; +import com.tacitknowledge.util.migration.jdbc.util.SqlUtil; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.dbunit.database.DatabaseConnection; +import org.dbunit.database.IDatabaseConnection; +import org.dbunit.dataset.xml.FlatXmlDataSet; +import org.dbunit.operation.DatabaseOperation; + +import java.io.IOException; +import java.io.InputStream; +import java.sql.Connection; + +/** + * A loader class that supports DbUnit's FlatXmlDataSet format. + * The data in the xml file is loaded via a dbunit INSERT operation. + * + * @author Alex Soto (apsoto@gmail.com) + */ +public class FlatXmlDataSetMigrationTask extends MigrationTaskSupport +{ + /** + * Class logger + */ + private static Log log = LogFactory.getLog(FlatXmlDataSetMigrationTask.class); + + /** + * Default ctor + */ + public FlatXmlDataSetMigrationTask() + { + // does nothing + } + + /** + * Run the migration using the given context. + * + * @param context the context to run under + * @throws MigrationException if an unexpected error occurs + */ + public void migrate(MigrationContext context) throws MigrationException + { + log.debug("Executing patch " + getLevel()); + // down casting, technically not safe, but everyone else is doing it. + JdbcMigrationContext jdbcContext = (JdbcMigrationContext) context; + // used to close connection in finally block + Connection contextConnection = null; + try + { + FlatXmlDataSet xmlDataSet = new FlatXmlDataSet(getXmlAsStream()); + // Set contextConnection so it can be accessed in the finally block. + contextConnection = jdbcContext.getConnection(); + + // run the data load + IDatabaseConnection connection = new DatabaseConnection(contextConnection); + DatabaseOperation.INSERT.execute(connection, xmlDataSet); + context.commit(); + + // Closing here instead of in finally block to keep the signature of this from throwing + // a SqlException. Exceptional condition handled in finally block to make sure + // we don't leak a connection. + connection.close(); + } + catch (Exception e) + { + log.debug("Unable to patch due to " + e.getMessage()); + context.rollback(); + throw new MigrationException("Unable to patch", e); + } + finally + { + // Might already be closed if everything worked fine and connection.close was called + // above, in that case, calling close again shouldn't do any harm. However, if an + // exception occurred the DBUnit based connection wrapper didn't get closed, so we + // catch that case here. + SqlUtil.close(contextConnection, null, null); + } + } + + /** + * get the file with name getName() as a stream. + * + * @return a Stream + * @throws IOException if unable to load file + */ + protected InputStream getXmlAsStream() throws IOException + { + try + { + FileLoadingUtility utility = new FileLoadingUtility(getName()); + return utility.getResourceAsStream(); + } + catch (NullPointerException npe) + { + throw new IOException("Unable to find xml file named '" + getName() + "'"); + } + } + + +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/loader/FlatXmlDataSetTaskSource.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/loader/FlatXmlDataSetTaskSource.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/loader/FlatXmlDataSetTaskSource.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,105 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration.jdbc.loader; + +import com.tacitknowledge.util.discovery.ClassDiscoveryUtil; +import com.tacitknowledge.util.migration.MigrationException; +import com.tacitknowledge.util.migration.MigrationTask; +import com.tacitknowledge.util.migration.MigrationTaskSource; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Search a package (directory) for xml files that match a specific pattern + * and returns corresponding {@link FlatXmlDataSetMigrationTask}s. The name + * of each script must follow the pattern of "patch(\d+)(_.+)?\.xml". + * + * @author Alex Soto (apsoto@gmail.com) + */ +public class FlatXmlDataSetTaskSource implements MigrationTaskSource +{ + + /** + * Class logger + */ + private static Log log = LogFactory.getLog(FlatXmlDataSetTaskSource.class); + + /** + * The regular expression used to match XML patch files. + */ + private static final String XML_PATCH_REGEX = "^patch(\\d+)(_.+)?\\.xml"; + + + /** + * {@inheritDoc} + */ + public List getMigrationTasks(String packageName) throws MigrationException + { + String path = packageName.replace('.', '/'); + String[] xmlFiles = ClassDiscoveryUtil.getResources(path, XML_PATCH_REGEX); + + log.debug("Found " + xmlFiles.length + " xml patch(es) in path: " + path); + for (int i = 0; i < xmlFiles.length; i++) + { + log.debug(" -- \"" + xmlFiles[i] + "\""); + } + return createMigrationTasks(xmlFiles); + } + + /** + * Creates a list of {@link FlatXmlDataSetMigrationTask}s based on the array + * of xml files. + * + * @param xmlFiles the classpath-relative array of xml files + * @return a list of {@link FlatXmlDataSetMigrationTask} + * @throws MigrationException in unexpected error occurs + */ + private List createMigrationTasks(String[] xmlFiles) throws MigrationException + { + Pattern p = Pattern.compile(XML_PATCH_REGEX); + List tasks = new ArrayList(); + for (int i = 0; i < xmlFiles.length; i++) + { + String xmlPathname = xmlFiles[i]; + xmlPathname = xmlPathname.replace('\\', '/'); + log.debug("Examining possible xml patch file \"" + xmlPathname + "\""); + + File xmlFile = new File(xmlPathname); + String xmlFilename = xmlFile.getName(); + + // Get the patch number out of the file name + Matcher matcher = p.matcher(xmlFilename); + if (!matcher.matches() || matcher.groupCount() != 2) + { + throw new MigrationException("Invalid XML patch name: " + xmlFilename); + } + + FlatXmlDataSetMigrationTask task = new FlatXmlDataSetMigrationTask(); + task.setLevel(new Integer(Integer.parseInt(matcher.group(1)))); + task.setName(xmlPathname); + tasks.add(task); + + } + return tasks; + } + +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/util/ConfigurationUtil.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/util/ConfigurationUtil.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/util/ConfigurationUtil.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,254 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration.jdbc.util; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import javax.servlet.ServletContext; +import javax.servlet.ServletContextEvent; +import java.util.Iterator; +import java.util.Properties; + +/** + * A utility class for various configuration needs + * + * @author Mike Hardy (mike@tacitknowledge.com) + */ +public class ConfigurationUtil +{ + /** + * Class logger + */ + private static Log log = LogFactory.getLog(ConfigurationUtil.class); + + /** + * Shouldn't be used + */ + private ConfigurationUtil() + { + // do nothing + } + + /** + * Returns the value of the specified servlet context initialization parameter. + * + * @param param the parameter to return + * @param properties the Properties for the Java system + * @param arguments optionally takes the arguments passed into the main to + * use as the migration system name + * @return the value of the specified system initialization parameter + * @throws IllegalArgumentException if the parameter does not exist + */ + public static String getRequiredParam(String param, Properties properties, + String[] arguments) throws IllegalArgumentException + { + return getRequiredParam(param, properties, arguments, 0); + } + + /** + * Returns the value of the specified servlet context initialization parameter identifed by the + * supplied index in the supplied array. + * + * @param param the parameter to return + * @param properties the Properties for the Java system + * @param arguments optionally takes the arguments passed into the main to use as the + * migration system name + * @param index the index to use in the supplied array + * @return the value of the specified system initialization parameter + * @throws IllegalArgumentException if the parameter does not exist + */ + public static String getRequiredParam(String param, Properties properties, + String[] arguments, int index) throws IllegalArgumentException + { + return getPropertyValue(param, properties, arguments, index, true); + } + + /** + * Returns the value of the specified servlet context initialization parameter identifed by the + * supplied index in the supplied array. Since it is an optional parameter then + * null is returned instead of an exception if an error occurs. + * + * @param param the parameter to return + * @param properties the Properties for the Java system + * @param arguments optionally takes the arguments passed into the main to use as the + * migration system name + * @param index the index to use in the supplied array + * @return the value of the specified system initialization parameter + */ + public static String getOptionalParam(String param, Properties properties, String[] arguments, + int index) + { + return getPropertyValue(param, properties, arguments, index, false); + } + + /** + * Returns the value of the specified configuration parameter. + * + * @param props the properties file containing the values + * @param param the parameter to return + * @return the value of the specified configuration parameter + * @throws IllegalArgumentException if the parameter does not exist + */ + public static String getRequiredParam(Properties props, String param) + throws IllegalArgumentException + { + String value = props.getProperty(param); + if (value == null) + { + log.warn("Parameter named: " + param + " was not found."); + log.warn("-----Parameters found-----"); + Iterator propNameIterator = props.keySet().iterator(); + while (propNameIterator.hasNext()) + { + String name = (String) propNameIterator.next(); + String val = props.getProperty(name); + log.warn(name + " = " + val); + } + log.warn("--------------------------"); + throw new IllegalArgumentException("'" + param + "' is a required " + + "initialization parameter. Aborting."); + } + return value; + } + + /** + * Returns the value of the specified configuration parameter. + * + * @param props the properties file containing the values + * @param param the parameter to return + * @param alternate the alternate parameter to return + * @return the value of the specified configuration parameter + */ + public static String getRequiredParam(Properties props, String param, String alternate) + { + try + { + return getRequiredParam(props, param); + } + catch (IllegalArgumentException e1) + { + try + { + return getRequiredParam(props, alternate); + } + catch (IllegalArgumentException e2) + { + throw new IllegalArgumentException("Either '" + param + "' or '" + alternate + + "' must be specified as an initialization parameter. Aborting."); + } + } + } + + + /** + * Returns the value of the specified servlet context initialization parameter. + * Since it is an optional parameter then null is returned + * instead of an exception if an error occurs. + * + * @param param the parameter to return + * @param sce the ServletContextEvent being handled + * @param caller calling object, used for printing information if there is a problem + * @return the value of the specified servlet context initialization parameter + * @throws IllegalArgumentException if the parameter does not exist + */ + public static String getOptionalParam(String param, ServletContextEvent sce, Object caller) + throws IllegalArgumentException + { + return getServletContextParam(param, sce, caller, false); + } + + /** + * Returns the value of the specified servlet context initialization parameter. + * + * @param param the parameter to return + * @param sce the ServletContextEvent being handled + * @param caller calling object, used for printing information if there is a problem + * @return the value of the specified servlet context initialization parameter + * @throws IllegalArgumentException if the parameter does not exist + */ + public static String getRequiredParam(String param, ServletContextEvent sce, Object caller) + throws IllegalArgumentException + { + return getServletContextParam(param, sce, caller, true); + } + + /** + * Returns the value of the specified servlet context initialization parameter. + * + * @param param the parameter to return + * @param sce the ServletContextEvent being handled + * @param caller calling object, used for printing information if there is a problem + * @param throwException if true then the method will throw an exception; if + * false is supplied then it will return null + * @return the value of the specified servlet context initialization parameter if found; + * null otherwise + * @throws IllegalArgumentException if the parameter does not exist + */ + private static String getServletContextParam(String param, ServletContextEvent sce, + Object caller, boolean throwException) + throws IllegalArgumentException + { + ServletContext context = sce.getServletContext(); + String value = context.getInitParameter(param); + + if (value == null && throwException) + { + throw new IllegalArgumentException("'" + param + "' is a required " + + "servlet context initialization parameter for the \"" + + caller.getClass().getName() + "\" class. Aborting."); + } + + return value; + } + + /** + * Gets the value of the supplied property name. First it searches in system properties. If + * not found then it examines the supplied array of command line arguments. + * + * @param propertyName the property naem to get + * @param properties the Properties for the Java system + * @param arguments the array of command line arguments + * @param index the index of the property in the command line arguments + * @param throwException if true then the method will throw an exception; if + * false is supplied then it will return null + * @return the property value if found; null otherwise + */ + private static String getPropertyValue(String propertyName, Properties properties, + String[] arguments, int index, boolean throwException) + { + String value = properties.getProperty(propertyName); + + if (value == null) + { + if ((arguments != null) && (arguments.length > 0) && (index < arguments.length)) + { + value = arguments[index].trim(); + } + else if (throwException) + { + throw new IllegalArgumentException("The " + propertyName + + " system property is required"); + } + else + { + value = null; + } + } + + return value; + } +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/util/ConnectionWrapperDataSource.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/util/ConnectionWrapperDataSource.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/util/ConnectionWrapperDataSource.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,119 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration.jdbc.util; + +import javax.sql.DataSource; +import java.io.PrintWriter; +import java.sql.Connection; +import java.sql.SQLException; + +/** + * A partial DataSource implementation that simply wraps a single, + * already opened Connection. + *

+ * Only the two getConnection methods are supported. + * + * @author Scott Askew (scott@tacitknowledge.com) + */ +public class ConnectionWrapperDataSource implements DataSource +{ + /** + * The message used in UnsupportedOperationExceptions. + */ + public static final String UNSUPPORTED_OPERATION_EXCEPTION_MSG + = ConnectionWrapperDataSource.class + + " is not a fully functioning DataSource and only" + + " supports the getConnection methods."; + + /** + * The underlying connection + */ + private Connection connection = null; + + /** + * Creates a new ConnectionWrapperDataSource. + * + * @param connection the connection to use for this data source + */ + public ConnectionWrapperDataSource(Connection connection) + { + this.connection = connection; + } + + /** + * {@inheritDoc} + */ + public Connection getConnection() throws SQLException + { + return connection; + } + + /** + * {@inheritDoc} + */ + public Connection getConnection(String user, String pass) throws SQLException + { + return connection; + } + + /** + * {@inheritDoc} + */ + public PrintWriter getLogWriter() throws UnsupportedOperationException + { + throw new UnsupportedOperationException(UNSUPPORTED_OPERATION_EXCEPTION_MSG); + } + + /** + * {@inheritDoc} + */ + public void setLogWriter(PrintWriter arg0) throws UnsupportedOperationException + { + throw new UnsupportedOperationException(UNSUPPORTED_OPERATION_EXCEPTION_MSG); + } + + /** + * {@inheritDoc} + */ + public int getLoginTimeout() throws UnsupportedOperationException + { + throw new UnsupportedOperationException(UNSUPPORTED_OPERATION_EXCEPTION_MSG); + } + + /** + * {@inheritDoc} + */ + public void setLoginTimeout(int arg0) throws UnsupportedOperationException + { + throw new UnsupportedOperationException(UNSUPPORTED_OPERATION_EXCEPTION_MSG); + } + + /** + * {@inheritDoc} + */ + public boolean isWrapperFor(Class iface) + { + return connection != null && iface.isAssignableFrom(connection.getClass()); + } + + /** + * {@inheritDoc} + */ + public Object unwrap(Class iface) + { + return connection; + } +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/util/MigrationUtil.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/util/MigrationUtil.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/util/MigrationUtil.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,106 @@ +package com.tacitknowledge.util.migration.jdbc.util; + +import com.tacitknowledge.util.migration.MigrationException; +import com.tacitknowledge.util.migration.jdbc.JdbcMigrationLauncher; +import com.tacitknowledge.util.migration.jdbc.JdbcMigrationLauncherFactory; +import com.tacitknowledge.util.migration.jdbc.JdbcMigrationLauncherFactoryLoader; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import javax.servlet.ServletContextEvent; + +/** + * A utility class for migration initialization needs + * + * @author Petric Coroli (pcoroli@tacitknowledge.com) + */ +public class MigrationUtil +{ + + private static Log log = LogFactory.getLog(MigrationUtil.class); + + public JdbcMigrationLauncherFactory getLauncherFactory() + { + return launcherFactory; + } + + public void setLauncherFactory(JdbcMigrationLauncherFactory launcherFactory) + { + this.launcherFactory = launcherFactory; + } + + private JdbcMigrationLauncherFactory launcherFactory; + + + /** + * Helper method to initiate the migration process. + * + * @param sce the ServletContextEvent being handled + * @throws MigrationException + */ + public static void doMigrations(final ServletContextEvent sce) throws MigrationException + { + JdbcMigrationLauncherFactory launcherFactory = + new JdbcMigrationLauncherFactoryLoader().createFactory(); + JdbcMigrationLauncher launcher = launcherFactory.createMigrationLauncher(sce); + launcher.doMigrations(); + } + + /** + * Helper method to initiate the migration process. + * + * @param migrationSystemName the name of the system to migrate + * @param migrationSettings additional properties for migration + * @throws MigrationException + */ + public static void doMigrations(final String migrationSystemName, + final String migrationSettings) throws MigrationException + { + JdbcMigrationLauncherFactory launcherFactory = new JdbcMigrationLauncherFactoryLoader() + .createFactory(); + JdbcMigrationLauncher launcher = null; + + if (migrationSettings == null) + { + log.info("Using migration.properties (default)"); + launcher = launcherFactory.createMigrationLauncher(migrationSystemName); + } + else + { + log.info("Using " + migrationSettings); + launcher = launcherFactory.createMigrationLauncher(migrationSystemName, + migrationSettings); + } + + launcher.doMigrations(); + } + + /** + * Helper method to initiate the migration process. + * + * @param migrationSystemName the name of the system to migrate + * @param migrationSettings additional properties for migration + * @throws MigrationException + */ + public void doRollbacks(final String migrationSystemName, + final String migrationSettings, final int[] rollbackLevel, final boolean forceRollback) + throws MigrationException + { + + JdbcMigrationLauncher launcher = null; + + if (migrationSettings == null) + { + log.info("Using migration.properties (default)"); + launcher = getLauncherFactory().createMigrationLauncher(migrationSystemName); + } + else + { + log.info("Using " + migrationSettings); + launcher = getLauncherFactory().createMigrationLauncher(migrationSystemName, + migrationSettings); + } + + launcher.doRollbacks(rollbackLevel, forceRollback); + } +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/util/NonPooledDataSource.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/util/NonPooledDataSource.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/util/NonPooledDataSource.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,223 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration.jdbc.util; + +import javax.sql.DataSource; +import java.io.PrintWriter; +import java.sql.Connection; +import java.sql.SQLException; + +/** + * A partial DataSource implementation that can be used in environments + * where the containing application (usually an applications server) does not + * provide a pooled DataSource. This can be used to run migrations from standalone + * applications. + *

+ * Only the two getConnection methods are supported. + * + * @author Scott Askew (scott@tacitknowledge.com) + */ +public class NonPooledDataSource implements DataSource +{ + /** + * The message used in UnsupportedOperationExceptions. + */ + public static final String UNSUPPORTED_OPERATION_EXCEPTION_MSG + = NonPooledDataSource.class + + " is not a fully functioning DataSource and only" + + " supports the getConnection methods."; + + /** + * The name of the database driver class + */ + private String driverClass = null; + + /** + * The JDBC URL + */ + private String databaseUrl = null; + + /** + * The user to login as + */ + private String username = null; + + /** + * The database password + */ + private String password = null; + + /** + * Creates a new BasicDataSource. + */ + public NonPooledDataSource() + { + // Default constructor + } + + /** + * {@inheritDoc} + */ + public Connection getConnection() throws SQLException + { + return getConnection(getUsername(), getPassword()); + } + + /** + * {@inheritDoc} + */ + public Connection getConnection(String user, String pass) throws SQLException + { + try + { + return SqlUtil.getConnection(getDriverClass(), getDatabaseUrl(), + getUsername(), getPassword()); + } + catch (ClassNotFoundException e) + { + throw new SQLException("Could not locate JDBC driver " + driverClass); + } + } + + /** + * {@inheritDoc} + */ + public PrintWriter getLogWriter() throws UnsupportedOperationException + { + throw new UnsupportedOperationException(UNSUPPORTED_OPERATION_EXCEPTION_MSG); + } + + /** + * {@inheritDoc} + */ + public void setLogWriter(PrintWriter arg0) throws UnsupportedOperationException + { + throw new UnsupportedOperationException(UNSUPPORTED_OPERATION_EXCEPTION_MSG); + } + + /** + * {@inheritDoc} + */ + public int getLoginTimeout() throws UnsupportedOperationException + { + throw new UnsupportedOperationException(UNSUPPORTED_OPERATION_EXCEPTION_MSG); + } + + /** + * {@inheritDoc} + */ + public void setLoginTimeout(int arg0) throws UnsupportedOperationException + { + throw new UnsupportedOperationException(UNSUPPORTED_OPERATION_EXCEPTION_MSG); + } + + /** + * Get the database URL + * + * @return the database url to use + */ + public String getDatabaseUrl() + { + return databaseUrl; + } + + /** + * @param databaseUrl The databaseUrl to set. + */ + public void setDatabaseUrl(String databaseUrl) + { + this.databaseUrl = databaseUrl; + } + + /** + * @return Returns the driverClass. + */ + public String getDriverClass() + { + return driverClass; + } + + /** + * @param driverClass The driverClass to set. + */ + public void setDriverClass(String driverClass) + { + this.driverClass = driverClass; + } + + /** + * @return Returns the password. + */ + public String getPassword() + { + return password; + } + + /** + * @param password The password to set. + */ + public void setPassword(String password) + { + this.password = password; + } + + /** + * @return Returns the username. + */ + public String getUsername() + { + return username; + } + + /** + * @param username The username to set. + */ + public void setUsername(String username) + { + this.username = username; + } + + /** + * {@inheritDoc} + */ + public boolean isWrapperFor(Class iface) + { + return false; + } + + /** + * {@inheritDoc} + */ + public Object unwrap(Class iface) + { + return null; + } + + /** + * Useful for debugging + * + * @return String with state + */ + public String toString() + { + return + "NonPooledDataSource[" + + getDriverClass() + "/" + + getDatabaseUrl() + "/" + + getUsername() + "/" + + "(password omitted)" + "]"; + } +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/util/SqlUtil.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/util/SqlUtil.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/util/SqlUtil.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,201 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration.jdbc.util; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import java.sql.*; +import java.util.Properties; + +/** + * Utility class for dealing with JDBC. + * + * @author Scott Askew (scott@tacitknowledge.com) + */ +public final class SqlUtil +{ + /** + * Class logger + */ + private static Log log = LogFactory.getLog(SqlUtil.class); + + /** + * Hidden constructor for utility class + */ + private SqlUtil() + { + // Hidden + } + + /** + * Ensures the given connection, statement, and result are properly closed. + * + * @param conn the connection to close; may be null + * @param stmt the statement to close; may be null + * @param rs the result set to close; may be null + */ + public static void close(Connection conn, Statement stmt, ResultSet rs) + { + if (rs != null) + { + try + { + boolean rsIsOpen = true; + try + { + rsIsOpen = !rs.isClosed(); + } + catch (AbstractMethodError e) + { + log.debug("AbstractMethodError closing ResultSet. ResultSet might be a DelegatingResultSet with a badly implemented (i.e. missing) delegation to isClosed().", e); + } + if (rsIsOpen) + { + log.debug("Closing ResultSet: " + rs.toString()); + rs.close(); + } + else + { + log.debug("ResultSet (" + rs.toString() + ") already closed."); + } + } + catch (SQLException e) + { + log.error("Error closing ResultSet", e); + } + } + + if (stmt != null) + { + try + { + boolean stmtIsOpen = true; + try + { + stmtIsOpen = !stmt.isClosed(); + } + catch (AbstractMethodError e) + { + log.debug("AbstractMethodError closing Statement.", e); + } + if (stmtIsOpen) + { + log.debug("Closing Statement: " + stmt.toString()); + stmt.close(); + } + else + { + log.debug("Statement (" + stmt.toString() + ") already closed."); + } + } + catch (SQLException e) + { + log.error("Error closing Statement", e); + } + } + + if (conn != null) + { + try + { + boolean connIsOpen = true; + try + { + connIsOpen = !conn.isClosed(); + } + catch (AbstractMethodError e) + { + log.debug("AbstractMethodError closing Connection.", e); + } + if (connIsOpen) + { + log.debug("Closing Connection " + conn.toString()); + conn.close(); + } + else + { + log.debug("Connection (" + conn.toString() + ") already closed."); + } + } + catch (SQLException e) + { + log.error("Error closing Connection", e); + } + } + } + + /** + * Established and returns a connection based on the specified parameters. + * + * @param driver the JDBC driver to use + * @param url the database URL + * @param user the username + * @param pass the password + * @return a JDBC connection + * @throws ClassNotFoundException if the driver could not be loaded + * @throws SQLException if a connnection could not be made to the database + */ + public static Connection getConnection(String driver, String url, String user, String pass) + throws ClassNotFoundException, SQLException + { + Connection conn = null; + try + { + Class.forName(driver); + log.debug("Getting Connection to " + url); + conn = DriverManager.getConnection(url, user, pass); + } + catch (Exception e) + { + /* work around for DriverManager 'feature'. + * In some cases, the jdbc driver jar is injected into a new + * child classloader (for example, maven provides different + * class loaders for different build lifecycle phases). + * + * Since DriverManager uses the calling class' loader instead + * of the current context's loader, it fails to find the driver. + * + * Our work around is to give the current context's class loader + * a shot at finding the driver in cases where DriverManager fails. + * This 'may be' a security hole which is why DriverManager implements + * things in such a way that it doesn't use the current thread context class loader. + */ + try + { + Class driverClass = + Class.forName(driver, true, Thread.currentThread().getContextClassLoader()); + Driver driverImpl = (Driver) driverClass.newInstance(); + Properties props = new Properties(); + props.put("user", user); + props.put("password", pass); + conn = driverImpl.connect(url, props); + } + catch (InstantiationException ie) + { + log.debug(ie); + throw new SQLException(ie.getMessage()); + } + catch (IllegalAccessException iae) + { + log.debug(iae); + throw new SQLException(iae.getMessage()); + } + } + + return conn; + } +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/util/SybaseUtil.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/util/SybaseUtil.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/main/java/com/tacitknowledge/util/migration/jdbc/util/SybaseUtil.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,186 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration.jdbc.util; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Util class for Sybase specific functionality. + * + * @author Alex Soto (apsoto@gmail.com) + */ +public class SybaseUtil +{ + + /** + * alter table pattern - this one is actually not documented + */ + protected static final Pattern ALTER_TABLE_PATTERN = + Pattern.compile("(?is).*alter\\s+table.*"); + + /** + * alter database pattern + */ + protected static final Pattern ALTER_DATABASE_PATTERN = + Pattern.compile("(?is).*alter\\s+database.*"); + + /** + * create database pattern + */ + protected static final Pattern CREATE_DATABASE_PATTERN = + Pattern.compile("(?is).*create\\s+database.*"); + + /** + * dbcc reindex pattern + */ + protected static final Pattern DBCC_REINDEX_PATTERN = + Pattern.compile("(?is).*dbcc\\s+reindex.*"); + + /** + * dbcc_fix_text pattern + */ + protected static final Pattern DBCC_FIX_TEXT_PATTERN = + Pattern.compile("(?is).*dbcc\\s+fix_text.*"); + + /** + * drop database pattern + */ + protected static final Pattern DROP_DATABASE_PATTERN = + Pattern.compile("(?is).*drop\\s+database.*"); + + /** + * dump database pattern + */ + protected static final Pattern DUMP_DATABASE_PATTERN = + Pattern.compile("(?is).*dump\\s+database.*"); + + /** + * dump transaction pattern + */ + protected static final Pattern DUMP_TRANSACTION_PATTERN = + Pattern.compile("(?is).*dump\\s+transaction.*"); + + /** + * load database pattern + */ + protected static final Pattern LOAD_DATABASE_PATTERN = + Pattern.compile("(?is).*load\\s+database.*"); + + /** + * load transaction pattern + */ + protected static final Pattern LOAD_TRANSACTION_PATTERN = + Pattern.compile("(?is).*load\\s+transaction.*"); + + /** + * select into pattern + */ + protected static final Pattern SELECT_INTO_PATTERN = + Pattern.compile("(?is).*select\\s+into.*"); + + /** + * set transaction isolation level pattern + */ + protected static final Pattern SET_TRANSACTION_ISOLATION_LEVEL_PATTERN = + Pattern.compile("(?is).*set\\s+transaction\\s+isolation\\s+level.*", + Pattern.CASE_INSENSITIVE & Pattern.MULTILINE); + + /** + * truncate table pattern + */ + protected static final Pattern TRUNCATE_TABLE_PATTERN = + Pattern.compile("(?is).*truncate\\s+table.*"); + + /** + * update statistics pattern + */ + protected static final Pattern UPDATE_STATISTICS_PATTERN = + Pattern.compile("(?is).*update\\s+statistics.*"); + + /** + * setuser pattern + */ + protected static final Pattern SETUSER_PATTERN = + Pattern.compile("(?is).*setuser.*"); + + /** + * List of {@link Pattern} of statements that are illegal in multi + * statement transactions. + */ + protected static final List ILLEGAL_MULTISTATEMENT_TRANSACTION_COMMANDS; + + static + { + ArrayList list = new ArrayList(); + list.add(ALTER_DATABASE_PATTERN); + list.add(ALTER_TABLE_PATTERN); + list.add(CREATE_DATABASE_PATTERN); + list.add(DBCC_FIX_TEXT_PATTERN); + list.add(DBCC_REINDEX_PATTERN); + list.add(DROP_DATABASE_PATTERN); + list.add(DUMP_DATABASE_PATTERN); + list.add(DUMP_TRANSACTION_PATTERN); + list.add(LOAD_DATABASE_PATTERN); + list.add(LOAD_TRANSACTION_PATTERN); + list.add(SELECT_INTO_PATTERN); + list.add(SET_TRANSACTION_ISOLATION_LEVEL_PATTERN); + list.add(SETUSER_PATTERN); + list.add(TRUNCATE_TABLE_PATTERN); + list.add(UPDATE_STATISTICS_PATTERN); + ILLEGAL_MULTISTATEMENT_TRANSACTION_COMMANDS = Collections.unmodifiableList(list); + } + + /** + * Singleton + */ + protected SybaseUtil() + { + // does nothing + } + + /** + * Determines whether the statement (typically some sql) contains one of the + * sql commands that are not allowed in a multi-statement transaction. + * + * @param statement the text to check + * @return true if one of the illegal commands is found in the statement. + * @see + * Sybase Troubleshooting and Error Messages Guide + */ + public static boolean containsIllegalMultiStatementTransactionCommand(String statement) + { + boolean contains = false; + + for (Iterator iter = ILLEGAL_MULTISTATEMENT_TRANSACTION_COMMANDS.iterator(); + iter.hasNext();) + { + Pattern pattern = (Pattern) iter.next(); + Matcher matcher = pattern.matcher(statement); + if (matcher.matches()) + { + contains = true; + break; + } + } + + return contains; + } +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/main/resources/com/tacitknowledge/util/migration/jdbc/hsqldb.properties =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/main/resources/com/tacitknowledge/util/migration/jdbc/hsqldb.properties (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/main/resources/com/tacitknowledge/util/migration/jdbc/hsqldb.properties (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,24 @@ +supportsMultipleStatements=false + +patches.create=CREATE TABLE patches ( \ + system_name VARCHAR(30) NOT NULL \ + , patch_level INT NOT NULL \ + , patch_date TIMESTAMP \ + , patch_in_progress CHAR(1) default 'F' NOT NULL \ + , PRIMARY KEY (system_name, patch_level)) + +# Validates that a record exists for a given system +level.create=INSERT INTO patches (system_name, patch_level) VALUES ( ?, 0) +level.table.exists=SELECT patch_level FROM patches WHERE system_name = ? +level.read=SELECT MAX(patch_level) FROM patches WHERE system_name = ? +level.rollback=DELETE FROM patches WHERE patch_level = ? and system_name = ? +level.update=INSERT INTO patches (patch_level, system_name, patch_date) VALUES ( ?, ?, NOW()) +level.exists=SELECT patch_level FROM patches WHERE system_name=? and patch_level=? + +patches.all=SELECT patch_level FROM patches WHERE system_name = ? + +# Since most DBs do not have a boolean type, return 0 or 1 row to determine if +# the system is currently locked. +lock.read=SELECT patch_in_progress FROM patches WHERE system_name = ? AND ( patch_in_progress <> 'F' OR patch_level in ( SELECT MAX(patch_level) FROM patches WHERE system_name = ? )) +lock.obtain=UPDATE patches SET patch_in_progress = 'T' WHERE system_name = ? AND patch_in_progress = 'F' AND patch_level in ( SELECT MAX(patch_level) FROM patches WHERE system_name = ? ) +lock.release=UPDATE patches SET patch_in_progress = 'F' WHERE system_name = ? AND patch_in_progress <> 'F' Index: 3rdParty_sources/tacitknowledge/autopatch/src/main/resources/com/tacitknowledge/util/migration/jdbc/mysql.properties =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/main/resources/com/tacitknowledge/util/migration/jdbc/mysql.properties (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/main/resources/com/tacitknowledge/util/migration/jdbc/mysql.properties (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,24 @@ +supportsMultipleStatements=true + +patches.create=CREATE TABLE IF NOT EXISTS patches ( \ + system_name VARCHAR(30) NOT NULL \ + , patch_level INT4 NOT NULL \ + , patch_date TIMESTAMP NOT NULL default CURRENT_TIMESTAMP \ + , patch_in_progress CHAR(1) NOT NULL default 'F' \ + , PRIMARY KEY(system_name, patch_level)) + +# Validates that a record exists for a given system +level.create=INSERT INTO patches (system_name, patch_level) VALUES ( ?, 0 ) +level.table.exists=SELECT patch_level FROM patches WHERE system_name = ? +level.read=SELECT MAX(patch_level) FROM patches WHERE system_name = ? +level.rollback=DELETE FROM patches WHERE patch_level = ? and system_name = ? +level.update=INSERT INTO patches (patch_level, system_name, patch_date) VALUES ( ?, ?, CURRENT_TIMESTAMP) +level.exists=SELECT patch_level FROM patches WHERE system_name=? and patch_level=? + +patches.all=SELECT patch_level FROM patches WHERE system_name = ? + +# Since most DBs do not have a boolean type, return 0 or 1 row to determine if +# the system is currently locked. +lock.read=SELECT patch_in_progress FROM patches WHERE system_name = ? AND ( patch_in_progress <> 'F' OR patch_level in ( SELECT MAX(patch_level) FROM patches WHERE system_name = ? )) +lock.obtain=UPDATE patches SET patch_in_progress = 'T' WHERE system_name = ? AND patch_in_progress = 'F' AND patch_level in ( SELECT max_patch_level FROM (SELECT MAX(patch_level) AS max_patch_level FROM patches WHERE system_name = ? ) AS tmptable ) +lock.release=UPDATE patches SET patch_in_progress = 'F' WHERE system_name = ? AND patch_in_progress <> 'F' \ No newline at end of file Index: 3rdParty_sources/tacitknowledge/autopatch/src/main/resources/com/tacitknowledge/util/migration/jdbc/oracle.properties =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/main/resources/com/tacitknowledge/util/migration/jdbc/oracle.properties (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/main/resources/com/tacitknowledge/util/migration/jdbc/oracle.properties (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,24 @@ +supportsMultipleStatements=false + +patches.create=CREATE TABLE tk_patches (\ + system_name VARCHAR2(30) NOT NULL\ + , patch_level NUMBER NOT NULL\ + , patch_date DATE DEFAULT SYSDATE NOT NULL\ + , patch_in_progress CHAR(1) DEFAULT 'F' NOT NULL\ + , CONSTRAINT tk_patches_pk PRIMARY KEY (system_name, patch_level) ) + +# Validates that a record exists for a given system +level.create=INSERT INTO tk_patches (system_name, patch_level) VALUES ( ?, 0 ) +level.table.exists=SELECT patch_level FROM tk_patches WHERE system_name = ? +level.read=SELECT MAX(patch_level) FROM tk_patches WHERE system_name = ? +level.rollback=DELETE FROM tk_patches WHERE patch_level = ? and system_name = ? +level.update=INSERT INTO tk_patches (patch_level, system_name, patch_date) VALUES ( ?, ?, SYSDATE) +level.exists=SELECT patch_level FROM tk_patches WHERE system_name=? and patch_level=? + +patches.all=SELECT patch_level FROM tk_patches WHERE system_name = ? + +# Since most DBs do not have a boolean type, return 0 or 1 row to determine if +# the system is currently locked. +lock.read=SELECT patch_in_progress FROM tk_patches WHERE system_name = ? AND ( patch_in_progress <> 'F' OR patch_level in ( SELECT MAX(patch_level) FROM tk_patches WHERE system_name = ? )) +lock.obtain=UPDATE tk_patches SET patch_in_progress = 'T' WHERE system_name = ? AND patch_in_progress = 'F' AND patch_level in ( SELECT MAX(patch_level) FROM tk_patches WHERE system_name = ? ) +lock.release=UPDATE tk_patches SET patch_in_progress = 'F' WHERE system_name = ? AND patch_in_progress <> 'F' Index: 3rdParty_sources/tacitknowledge/autopatch/src/main/resources/com/tacitknowledge/util/migration/jdbc/postgres.properties =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/main/resources/com/tacitknowledge/util/migration/jdbc/postgres.properties (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/main/resources/com/tacitknowledge/util/migration/jdbc/postgres.properties (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,24 @@ +supportsMultipleStatements=true + +patches.create=CREATE TABLE patches ( \ + system_name VARCHAR(30) NOT NULL \ + , patch_level INT4 NOT NULL \ + , patch_date DATE NOT NULL DEFAULT (now()) \ + , patch_in_progress CHAR(1) NOT NULL DEFAULT ('F') \ + , PRIMARY KEY (system_name, patch_level)) + +# Validates that a record exists for a given system +level.create=INSERT INTO patches (system_name, patch_level) VALUES ( ?, 0 ) +level.table.exists=SELECT patch_level FROM patches WHERE system_name = ? +level.read=SELECT MAX(patch_level) FROM patches WHERE system_name = ? +level.rollback=DELETE FROM patches WHERE patch_level = ? and system_name = ? +level.update=INSERT INTO patches (patch_level, system_name, patch_date) VALUES ( ?, ?, now()) +level.exists=SELECT patch_level FROM patches WHERE system_name=? and patch_level=? + +patches.all=SELECT patch_level FROM patches WHERE system_name = ? + +# Since most DBs do not have a boolean type, return 0 or 1 row to determine if +# the system is currently locked. +lock.read=SELECT patch_in_progress FROM patches WHERE system_name = ? AND ( patch_in_progress <> 'F' OR patch_level in ( SELECT MAX(patch_level) FROM patches WHERE system_name = ? )) +lock.obtain=UPDATE patches SET patch_in_progress = 'T' WHERE system_name = ? AND patch_in_progress = 'F' AND patch_level in ( SELECT MAX(patch_level) FROM patches WHERE system_name = ? ) +lock.release=UPDATE patches SET patch_in_progress = 'F' WHERE system_name = ? AND patch_in_progress <> 'F' Index: 3rdParty_sources/tacitknowledge/autopatch/src/main/resources/com/tacitknowledge/util/migration/jdbc/sqlserver.properties =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/main/resources/com/tacitknowledge/util/migration/jdbc/sqlserver.properties (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/main/resources/com/tacitknowledge/util/migration/jdbc/sqlserver.properties (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,24 @@ +supportsMultipleStatements=false + +patches.create=CREATE TABLE patches ( \ + system_name VARCHAR(30) NOT NULL \ + , patch_level INT NOT NULL \ + , patch_date DATETIME \ + , patch_in_progress CHAR(1) default 'F' NOT NULL \ + , PRIMARY KEY(system_name, patch_level)) + +# Validates that a record exists for a given system +level.create=INSERT INTO patches (system_name, patch_level) VALUES ( ?, 0) +level.table.exists=SELECT patch_level FROM patches WHERE system_name = ? +level.read=SELECT MAX(patch_level) FROM patches WHERE system_name = ? +level.rollback=DELETE FROM patches WHERE patch_level = ? and system_name = ? +level.update=INSERT INTO patches (patch_level, system_name, patch_date) VALUES ( ?, ?, getDate()) +level.exists=SELECT patch_level FROM patches WHERE system_name=? and patch_level=? + +patches.all=SELECT patch_level FROM patches WHERE system_name = ? + +# Since most DBs do not have a boolean type, return 0 or 1 row to determine if +# the system is currently locked. +lock.read=SELECT patch_in_progress FROM patches WHERE system_name = ? AND ( patch_in_progress <> 'F' OR patch_level in ( SELECT MAX(patch_level) FROM patches WHERE system_name = ? )) +lock.obtain=UPDATE patches SET patch_in_progress = 'T' WHERE system_name = ? AND patch_in_progress = 'F' AND patch_level in ( SELECT MAX(patch_level) FROM patches WHERE system_name = ? ) +lock.release=UPDATE patches SET patch_in_progress = 'F' WHERE system_name = ? AND patch_in_progress <> 'F' Index: 3rdParty_sources/tacitknowledge/autopatch/src/main/resources/com/tacitknowledge/util/migration/jdbc/sybase.properties =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/main/resources/com/tacitknowledge/util/migration/jdbc/sybase.properties (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/main/resources/com/tacitknowledge/util/migration/jdbc/sybase.properties (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,24 @@ +supportsMultipleStatements=false + +patches.create=CREATE TABLE patches (\ + system_name VARCHAR(30) NOT NULL PRIMARY KEY,\ + patch_level INT NOT NULL,\ + patch_date DATETIME DEFAULT getdate() NOT NULL,\ + patch_in_progress CHAR(1) DEFAULT 'F' NOT NULL,\ + primary key clustered (system_name, patch_level)) + +# Validates that a record exists for a given system +level.create=INSERT INTO patches (system_name, patch_level) VALUES ( ?, 0 ) +level.table.exists=SELECT patch_level FROM patches WHERE system_name = ? +level.read=SELECT MAX(patch_level) FROM patches WHERE system_name = ? +level.rollback=DELETE FROM patches WHERE patch_level = ? and system_name = ? +level.update=INSERT INTO patches (patch_level, system_name, patch_date) VALUES ( ?, ?, getdate()) +level.exists=SELECT patch_level FROM patches WHERE system_name=? and patch_level=? + +patches.all=SELECT patch_level FROM patches WHERE system_name = ? + +# Since most DBs do not have a boolean type, return 0 or 1 row to determine if +# the system is currently locked. +lock.read=SELECT patch_in_progress FROM patches WHERE system_name = ? AND ( patch_in_progress <> 'F' OR patch_level in ( SELECT MAX(patch_level) FROM patches WHERE system_name = ? )) +lock.obtain=UPDATE patches SET patch_in_progress = 'T' WHERE system_name = ? AND patch_in_progress = 'F' AND patch_level in ( SELECT MAX(patch_level) FROM patches WHERE system_name = ? ) +lock.release=UPDATE patches SET patch_in_progress = 'F' WHERE system_name = ? AND patch_in_progress <> 'F' Index: 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/ClassMigrationTaskSourceTest.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/ClassMigrationTaskSourceTest.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/ClassMigrationTaskSourceTest.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,79 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration; + +import java.util.List; + +import junit.framework.TestCase; + +/** + * Exercise the ClassMigrationTaskSource object + * + * @author Mike Hardy (mike@tacitknowledge.com) + */ +public class ClassMigrationTaskSourceTest extends TestCase +{ + /** + * Make sure class instantiation fails on null package name + */ + public void testInstantiateTasksNullPackage() + { + ClassMigrationTaskSource source = new ClassMigrationTaskSource(); + try + { + source.getMigrationTasks(null); + fail("We should have gotten an exception for the null package"); + } + catch (MigrationException me) + { + // we expect this + } + } + + /** + * Make sure class instantiation fails on package with no types + */ + public void testInstantiateTasksNoTasks() + { + ClassMigrationTaskSource source = new ClassMigrationTaskSource(); + try + { + List tasks = source.getMigrationTasks("com.tacitknowledge.foo.bar"); + assertEquals(0, tasks.size()); + } + catch (MigrationException me) + { + fail("We should not have gotten an exception"); + } + } + + /** + * Make sure class instantiation fails on package with bad tasks + */ + public void testInstantiateTasksInstantiationException() + { + ClassMigrationTaskSource source = new ClassMigrationTaskSource(); + try + { + source.getMigrationTasks(getClass().getPackage().getName() + ".tasks.instantiation"); + fail("We should have gotten an exception"); + } + catch (MigrationException me) + { + assertTrue(me.getCause() instanceof RuntimeException); + } + } +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/DistributedAutoPatchRollbackTest.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/DistributedAutoPatchRollbackTest.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/DistributedAutoPatchRollbackTest.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,367 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration; + +import java.util.*; + +import com.tacitknowledge.util.migration.builders.MockBuilder; +import com.tacitknowledge.util.migration.jdbc.*; +import com.tacitknowledge.util.migration.tasks.rollback.*; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.easymock.EasyMock; +import org.easymock.MockControl; + +import com.mockrunner.mock.jdbc.MockDataSource; +import org.easymock.classextension.IMocksControl; + +import static org.easymock.EasyMock.eq; +import static org.easymock.EasyMock.expect; +import static org.easymock.classextension.EasyMock.createControl; + +/** + * Test the Distributed auto patch service to make sure it configures and runs + * correctly + * + * @author Artie Pesh-Imam (apeshimam@tacitknowledge.com) + */ +public class DistributedAutoPatchRollbackTest extends MigrationListenerTestBase +{ + /** Class logger */ + private static Log log = LogFactory.getLog(DistributedAutoPatchRollbackTest.class); + private static final int[] ROLLBACK_LEVELS = new int[]{8}; + + /** The launcher we're testing */ + private DistributedJdbcMigrationLauncher launcher = null; + + /** A MigrationContext for us */ + private TestMigrationContext context = null; + private PatchInfoStore currentPatchInfoStore; + + /** + * Just delegates to the superclass + * + * @param name of the test to run + */ + public DistributedAutoPatchRollbackTest(String name) + { + super(name); + } + + /** + * Configures a DistributedAutoPatchService and it's child AutoPatchService + * objects to match the "migration.properties" configuration in the + * AutoPatch test suite. This let's us reuse the actual functionality checks + * that verify the configuration was correct, as the launcher is the same, + * it's just the adapter that's different. + * + * @see junit.framework.TestCase#setUp() + */ + protected void setUp() throws Exception + { + // configure the controlled AutoPatchService, first by calling super + super.setUp(); + log.debug("setting up " + this.getClass().getName()); + + // Make sure we load our test launcher factory, which fakes out the data source context + System.getProperties().setProperty("migration.factory", + "com.tacitknowledge.util.migration.jdbc.TestJdbcMigrationLauncherFactory"); + DistributedJdbcMigrationLauncherFactory factory = new TestDistributedJdbcMigrationLauncherFactory(); + + // Create the launcher (this does configure it as a side-effect) + launcher = (DistributedJdbcMigrationLauncher) factory.createMigrationLauncher("orchestration"); + + // Make sure we get notification of any migrations + launcher.getMigrationProcess().addListener(this); + + context = new TestMigrationContext(); + + // core sub-system + AutoPatchService coreService = new TestAutoPatchService(); + coreService.setSystemName("core"); + coreService.setDatabaseType("postgres"); + coreService.setDataSource(new MockDataSource()); + coreService.setPatchPath("patch.core:com.tacitknowledge.util.migration.jdbc.test"); + + // orders: patch path + // patches.orders.com.tacitknowledge.util.migration.tasks.normal + AutoPatchService ordersService = new TestAutoPatchService(); + ordersService.setSystemName("orders"); + ordersService.setDatabaseType("postgres"); + ordersService.setDataSource(new MockDataSource()); + ordersService.setPatchPath("patch.orders:com.tacitknowledge.util.migration.tasks.rollback"); + + // catalog: patch path patches.catalog + AutoPatchService catalogService = new TestAutoPatchService(); + catalogService.setPatchPath("patch.catalog"); + + // make catalog a multi-node patch service + TestDataSourceMigrationContext catalogContext1 = new TestDataSourceMigrationContext(); + TestDataSourceMigrationContext catalogContext2 = new TestDataSourceMigrationContext(); + catalogContext1.setSystemName("catalog"); + catalogContext2.setSystemName("catalog"); + catalogContext1.setDatabaseType(new DatabaseType("postgres")); + catalogContext2.setDatabaseType(new DatabaseType("postgres")); + catalogContext1.setDataSource(new MockDataSource()); + catalogContext2.setDataSource(new MockDataSource()); + catalogService.addContext(catalogContext1); + catalogService.addContext(catalogContext2); + + // configure the DistributedAutoPatchService + DistributedAutoPatchService distributedPatchService = new TestDistributedAutoPatchService(); + distributedPatchService.setSystemName("orchestration"); + distributedPatchService.setDatabaseType("postgres"); + distributedPatchService.setReadOnly(false); + AutoPatchService[] controlledSystems = new AutoPatchService[3]; + controlledSystems[0] = coreService; + controlledSystems[1] = ordersService; + controlledSystems[2] = catalogService; + distributedPatchService.setControlledSystems(controlledSystems); + distributedPatchService.setDataSource(new MockDataSource()); + + // instantiate everything + setLauncher(distributedPatchService.getLauncher()); + + // set ourselves up as a listener for any migrations that run + getLauncher().getMigrationProcess().addListener(this); + currentPatchInfoStore = MockBuilder.getPatchInfoStore(12); + } + + /** + * Make sure that the task loading works correctly + * + * @exception MigrationException if anything goes wrong + */ + public void testDistributedMigrationTaskLoading() throws MigrationException + { + DistributedMigrationProcess process = (DistributedMigrationProcess) getLauncher().getMigrationProcess(); + assertEquals(8, process.getMigrationTasks().size()); + assertEquals(8, process.getMigrationTasksWithLaunchers().size()); + } + + /** + * Ensure that overlapping tasks even among sub-launchers are detected + * + * @exception Exception if anything goes wrong + */ + public void testDistributedMigrationTaskValidation() throws Exception + { + MigrationProcess process = getLauncher().getMigrationProcess(); + process.validateTasks(process.getMigrationTasks()); + + // Make one of the sub-tasks conflict with a sub-task from another launcher + TestRollbackableTask2.setPatchLevelOverride(new Integer(8)); + try + { + process.validateTasks(process.getMigrationTasks()); + fail("We should have thrown an exception - " + + "there were overlapping tasks among sub-launchers"); + } + catch (MigrationException me) + { + // we expect this + } + finally + { + // make sure future tests work + TestRollbackableTask2.reset(); + } + } + + /** + * Make sure we get notified of patch application + * + * @exception Exception if anything goes wrong + */ + public void testDistributedRollbackEvents() throws Exception + { + IMocksControl mockControl = createControl(); + + // There should be five listener on the main process + // 1) the distributed launcher + // 2) this test object + // 3-5) the three sub-launchers + assertEquals(5, getLauncher().getMigrationProcess().getListeners().size()); + + // The sub-MigrationProcesses should have one listener each - the + // sub-launcher + HashMap controlledSystems = ((DistributedMigrationProcess) getLauncher() + .getMigrationProcess()).getControlledSystems(); + + + List migrationTasks = new ArrayList(); + + for (Iterator controlledSystemIter = controlledSystems.keySet().iterator(); controlledSystemIter.hasNext();) + { + String controlledSystemName = (String) controlledSystemIter.next(); + JdbcMigrationLauncher subLauncher = (JdbcMigrationLauncher) controlledSystems.get(controlledSystemName); + MigrationProcess subProcess = subLauncher.getMigrationProcess(); + List migrationTasksList = subProcess.getMigrationTasks(); + migrationTasks.addAll(migrationTasksList); + assertEquals(1, subProcess.getListeners().size()); + + MigrationProcess migrationProcessMock = mockControl.createMock(MigrationProcess.class); + expect(migrationProcessMock.getMigrationTasks()).andReturn(migrationTasksList); + subLauncher.setMigrationProcess(migrationProcessMock); + + } + + + // Now do the migrations, and make sure we get the right number of + // events + DistributedMigrationProcess process = (DistributedMigrationProcess) getLauncher().getMigrationProcess(); + + List rollbackCandidates = new ArrayList(); + + for (MigrationTask migrationTask : migrationTasks) { + if (migrationTask instanceof TestRollbackableTask2 + || migrationTask instanceof TestRollbackableTask3 + || migrationTask instanceof TestRollbackableTask4 + || migrationTask instanceof TestRollbackableTask5) { + + rollbackCandidates.add(migrationTask); + } + } + + + MigrationRunnerStrategy migrationRunnerStrategyMock = mockControl.createMock(MigrationRunnerStrategy.class); + + + expect(migrationRunnerStrategyMock.getRollbackCandidates(EasyMock.>anyObject(), + eq(ROLLBACK_LEVELS), eq(currentPatchInfoStore))).andReturn(rollbackCandidates); + expect(migrationRunnerStrategyMock.getRollbackCandidates(rollbackCandidates, + ROLLBACK_LEVELS, currentPatchInfoStore)).andReturn(Collections.EMPTY_LIST); + + expect(migrationRunnerStrategyMock.isSynchronized(eq(currentPatchInfoStore), + EasyMock.anyObject())).andReturn(true).anyTimes(); + + process.setMigrationRunnerStrategy(migrationRunnerStrategyMock); + mockControl.replay(); + + int currentPatchlevel = 12; + + setReportedPatchLevel(process.getControlledSystems().values(), currentPatchlevel); + int patches = process.doRollbacks(currentPatchInfoStore, ROLLBACK_LEVELS, getContext(), false); + assertEquals(4, patches); + assertEquals(4, getRollbackStartedCount()); + assertEquals(4, getRollbackSuccessCount()); + } + + /** + * Ensure that read-only mode actually works + * + * @exception Exception if anything goes wrong + */ + public void testDistributedReadOnlyMode() throws Exception + { + int currentPatchLevel = 12; + + DistributedMigrationProcess process = (DistributedMigrationProcess) getLauncher().getMigrationProcess(); + process.validateTasks(process.getMigrationTasks()); + + // need to mock the patch info stores to return the expected patch levels + HashMap controlledSystems = process.getControlledSystems(); + setReportedPatchLevel(controlledSystems.values(), currentPatchLevel); + + // Make it readonly + process.setReadOnly(true); + + // Now do the migrations, and make sure we get the right number of events + try + { + process.doRollbacks(currentPatchInfoStore, ROLLBACK_LEVELS, getContext(), false); + fail("There should have been an exception - unapplied patches + read-only don't work"); + } + catch (MigrationException me) + { + // we expect this + } + + currentPatchLevel = 13; + // need to mock the patch info stores to return the expected patch levels + setReportedPatchLevel(controlledSystems.values(), currentPatchLevel); + //int patches = process.doRollbacks(currentPatchLevel, rollbackPatchLevel, getContext()); + // assertEquals(0, patches); + assertEquals(0, getRollbackStartedCount()); + assertEquals(0, getRollbackSuccessCount()); + } + + /** + * For the given launchers, set all it's context's patch info stores as mocks + * that report the given patch level. This method is a helper to get this + * test to pass the DisitributedMigrationProcess::validateControlledSystems() test. + * @param launchers Collection of JDBCMigrationLaunchers + * @param levelToReport the patch level the mock should report + * @throws MigrationException + */ + protected void setReportedPatchLevel(Collection launchers, int levelToReport) throws MigrationException + { + for(Iterator launchersIterator = launchers.iterator(); launchersIterator.hasNext(); ) + { + JdbcMigrationLauncher launcher = (JdbcMigrationLauncher) launchersIterator.next(); + for(Iterator it = launcher.getContexts().keySet().iterator(); it.hasNext(); ) + { + MigrationContext ctx = (MigrationContext) it.next(); + MockControl patchInfoStoreControl = MockControl.createControl(PatchInfoStore.class); + PatchInfoStore patchInfoStore = (PatchInfoStore) patchInfoStoreControl.getMock(); + patchInfoStore.getPatchLevel(); + patchInfoStoreControl.setReturnValue(levelToReport); + patchInfoStoreControl.replay(); + launcher.getContexts().put(ctx, patchInfoStore); + } + } + } + + /** + * Get the MigrationContext to use during testing + * + * @return TestMigrationContext object + */ + public TestMigrationContext getContext() + { + return context; + } + + /** + * Set the MigrationContext to use for testing + * + * @param context a TestMigrationContext object to use for testing + */ + public void setContext(TestMigrationContext context) + { + this.context = context; + } + + /** + * Get the launcher to use for testing + * + * @return DistributedJdbcMigrationLauncher to use for testing + */ + public DistributedJdbcMigrationLauncher getLauncher() + { + return launcher; + } + + /** + * Set the launcher to test + * + * @param launcher the DistributedJdbcMigrationLauncher to test + */ + public void setLauncher(DistributedJdbcMigrationLauncher launcher) + { + this.launcher = launcher; + } +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/DistributedAutoPatchServiceTest.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/DistributedAutoPatchServiceTest.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/DistributedAutoPatchServiceTest.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,107 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration; + +import com.mockrunner.mock.jdbc.MockDataSource; +import com.tacitknowledge.util.migration.jdbc.AutoPatchService; +import com.tacitknowledge.util.migration.jdbc.DatabaseType; +import com.tacitknowledge.util.migration.jdbc.DistributedAutoPatchService; +import com.tacitknowledge.util.migration.jdbc.TestAutoPatchService; +import com.tacitknowledge.util.migration.jdbc.TestDataSourceMigrationContext; +import com.tacitknowledge.util.migration.jdbc.TestDistributedAutoPatchService; +import com.tacitknowledge.util.migration.jdbc.DistributedJdbcMigrationLauncherFactoryTest; + +/** + * Test the Distributed auto patch service to make sure it configures + * and runs correctly + * + * @author Mike Hardy (mike@tacitknowledge.com) + */ +public class DistributedAutoPatchServiceTest extends DistributedJdbcMigrationLauncherFactoryTest +{ + /** + * Just delegates to the superclass + * + * @param name of the test to run + */ + public DistributedAutoPatchServiceTest(String name) + { + super(name); + } + + /** + * Configures a DistributedAutoPatchService and it's child AutoPatchService objects + * to match the "migration.properties" configuration in the AutoPatch test suite. + * This let's us reuse the actual functionality checks that verify the configuration + * was correct, as the launcher is the same, it's just the adapter that's different. + * + * @see junit.framework.TestCase#setUp() + */ + protected void setUp() throws Exception + { + // configure the controlled AutoPatchService, first by calling super + super.setUp(); + + // core sub-system + AutoPatchService coreService = new TestAutoPatchService(); + coreService.setSystemName("core"); + coreService.setDatabaseType("postgres"); + coreService.setDataSource(new MockDataSource()); + coreService.setPatchPath("patch.core:com.tacitknowledge.util.migration.jdbc.test"); + + // orders: patch path patches.orders.com.tacitknowledge.util.migration.tasks.normal + AutoPatchService ordersService = new TestAutoPatchService(); + ordersService.setSystemName("orders"); + ordersService.setDatabaseType("postgres"); + ordersService.setDataSource(new MockDataSource()); + ordersService.setPatchPath("patch.orders:com.tacitknowledge.util.migration.tasks.normal"); + + // catalog: patch path patches.catalog + AutoPatchService catalogService = new TestAutoPatchService(); + catalogService.setPatchPath("patch.catalog"); + + // make catalog a multi-node patch service + TestDataSourceMigrationContext catalogContext1 = new TestDataSourceMigrationContext(); + TestDataSourceMigrationContext catalogContext2 = new TestDataSourceMigrationContext(); + catalogContext1.setSystemName("catalog"); + catalogContext2.setSystemName("catalog"); + catalogContext1.setDatabaseType(new DatabaseType("postgres")); + catalogContext2.setDatabaseType(new DatabaseType("postgres")); + catalogContext1.setDataSource(new MockDataSource()); + catalogContext2.setDataSource(new MockDataSource()); + catalogService.addContext(catalogContext1); + catalogService.addContext(catalogContext2); + + + // configure the DistributedAutoPatchService + DistributedAutoPatchService distributedPatchService = new TestDistributedAutoPatchService(); + distributedPatchService.setSystemName("orchestration"); + distributedPatchService.setDatabaseType("postgres"); + distributedPatchService.setReadOnly(false); + AutoPatchService[] controlledSystems = new AutoPatchService[3]; + controlledSystems[0] = coreService; + controlledSystems[1] = ordersService; + controlledSystems[2] = catalogService; + distributedPatchService.setControlledSystems(controlledSystems); + distributedPatchService.setDataSource(new MockDataSource()); + + // instantiate everything + setLauncher(distributedPatchService.getLauncher()); + + // set ourselves up as a listener for any migrations that run + getLauncher().getMigrationProcess().addListener(this); + } +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/DistributedMigrationProcessTest.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/DistributedMigrationProcessTest.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/DistributedMigrationProcessTest.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,228 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration; + +import java.util.*; + +import com.tacitknowledge.util.migration.builders.MockBuilder; +import com.tacitknowledge.util.migration.tasks.rollback.*; +import junit.framework.TestCase; + +import org.easymock.EasyMock; +import org.easymock.classextension.IMocksControl; +import org.easymock.MockControl; + +import com.tacitknowledge.util.migration.jdbc.JdbcMigrationContext; +import com.tacitknowledge.util.migration.jdbc.JdbcMigrationLauncher; + +import static org.easymock.EasyMock.eq; +import static org.easymock.EasyMock.expect; +import static org.easymock.classextension.EasyMock.createControl; +import static org.easymock.classextension.EasyMock.createMock; +import static org.easymock.classextension.EasyMock.createStrictControl; + +/** + * Test the {@link DistributedMigrationProcess} class. + * + * @author Alex Soto (apsoto@gmail.com) + */ +public class DistributedMigrationProcessTest extends TestCase +{ + private static final int CURRENT_PATCH_LEVEL = 4; + /** class under test */ + private DistributedMigrationProcess migrationProcess = null; + private PatchInfoStore currentPatchInfoStore; + private IMocksControl migrationRunnerStrategyControl; + private MigrationRunnerStrategy migrationRunnerStrategy; + + + /** + * Setup our tests. + */ + protected void setUp() throws Exception + { + super.setUp(); + migrationProcess = new DistributedMigrationProcess(); + currentPatchInfoStore = MockBuilder.getPatchInfoStore(CURRENT_PATCH_LEVEL); + migrationRunnerStrategyControl = createStrictControl(); + migrationRunnerStrategy = migrationRunnerStrategyControl.createMock(MigrationRunnerStrategy.class); + } + + protected HashMap createSystems() + { + HashMap systems = new HashMap(); + String systemNames[] = {"system1", "system2"}; + + for(int i = 0; i < systemNames.length; i++) + { + //JDBCMi + + } + + return systems; + } + + public void testValidateControlledSystemsWhenNodePatchLevelsAreInSync() throws Exception + { + + // system has one node + String systemName = "system1"; + JdbcMigrationLauncher launcher = new JdbcMigrationLauncher(); + + MockControl contextControl = MockControl.createControl(JdbcMigrationContext.class); + + JdbcMigrationContext context = (JdbcMigrationContext) contextControl.getMock(); + MockControl patchInfoStoreControl = MockControl.createControl(PatchInfoStore.class); + PatchInfoStore patchInfoStore = (PatchInfoStore) patchInfoStoreControl.getMock(); + + expect(migrationRunnerStrategy.isSynchronized(currentPatchInfoStore, patchInfoStore)).andReturn(true); + + migrationRunnerStrategyControl.replay(); + + migrationProcess.setMigrationRunnerStrategy(migrationRunnerStrategy); + + // create the launcher's contexts collection + LinkedHashMap contexts = new LinkedHashMap(); + contexts.put(context, patchInfoStore); + launcher.setContexts(contexts); + + HashMap controlledSystems = new HashMap(); + controlledSystems.put(systemName, launcher); + + migrationProcess.setControlledSystems(controlledSystems); + + try + { + migrationProcess.validateControlledSystems(currentPatchInfoStore); + } + catch(Exception e) + { + fail("Unexpected exception when validating controlled systems."); + } + } + + public void testValidateControlledSystemsWhenNodePatchLevelsAreOutOfSync() throws Exception + { + // system has one node + String systemName = "system1"; + JdbcMigrationLauncher launcher = new JdbcMigrationLauncher(); + + // first node is at the 'current' patch level + MockControl node1ContextControl = MockControl.createControl(JdbcMigrationContext.class); + JdbcMigrationContext node1Context = (JdbcMigrationContext) node1ContextControl.getMock(); + MockControl node1PatchInfoStoreControl = MockControl.createControl(PatchInfoStore.class); + PatchInfoStore node1PatchInfoStore = (PatchInfoStore) node1PatchInfoStoreControl.getMock(); + // setup mock patch info store to return the patch level we want + node1Context.getDatabaseName(); + node1ContextControl.setReturnValue("node1", MockControl.ONE_OR_MORE); + node1ContextControl.replay(); + + // second node simulates a newly added database instance, it has not been patched + MockControl node2ContextControl = MockControl.createControl(JdbcMigrationContext.class); + JdbcMigrationContext node2Context = (JdbcMigrationContext) node2ContextControl.getMock(); + MockControl node2PatchInfoStoreControl = MockControl.createControl(PatchInfoStore.class); + PatchInfoStore node2PatchInfoStore = (PatchInfoStore) node2PatchInfoStoreControl.getMock(); + // setup mock patch info store to return the patch level we want + node2Context.getDatabaseName(); + node2ContextControl.setReturnValue("node2", MockControl.ONE_OR_MORE); + node2ContextControl.replay(); + + // create the launcher's contexts collection + LinkedHashMap contexts = new LinkedHashMap(); + contexts.put(node1Context, node1PatchInfoStore); + contexts.put(node2Context, node2PatchInfoStore); + launcher.setContexts(contexts); + + HashMap controlledSystems = new HashMap(); + controlledSystems.put(systemName, launcher); + + migrationProcess.setControlledSystems(controlledSystems); + expect(migrationRunnerStrategy.isSynchronized(currentPatchInfoStore, node1PatchInfoStore)).andReturn(true); + expect(migrationRunnerStrategy.isSynchronized(currentPatchInfoStore, node2PatchInfoStore)).andReturn(false); + + migrationRunnerStrategyControl.replay(); + + migrationProcess.setMigrationRunnerStrategy(migrationRunnerStrategy); + try + { + migrationProcess.validateControlledSystems(currentPatchInfoStore); + fail("Unexpected exception when validating controlled systems."); + } + catch(MigrationException me) + { + } + catch(Exception e) + { + fail("Unexpected exception when validating controlled systems."); + } + } + + + public void testDoRollbacksActionMockingAsIfOrderedStrategyWereUsed() throws MigrationException { + + String systemName = "system1"; + JdbcMigrationLauncher launcher = new JdbcMigrationLauncher(); + int[] rollbackLevels = new int[]{9}; + IMocksControl mockControl = createControl(); + + JdbcMigrationContext migrationContextMock = mockControl.createMock(JdbcMigrationContext.class); + MigrationRunnerStrategy migrationRunnerStrategyMock = mockControl.createMock(MigrationRunnerStrategy.class); + + TestRollbackableTask3 rollbackableTask3 = new TestRollbackableTask3(); + TestRollbackableTask4 rollbackableTask4 = new TestRollbackableTask4(); + TestRollbackableTask5 rollbackableTask5 = new TestRollbackableTask5(); + + + List migrationTaskList = new ArrayList(); + migrationTaskList.add(new TestRollbackableTask1()); + migrationTaskList.add(new TestRollbackableTask2()); + migrationTaskList.add(rollbackableTask3); + migrationTaskList.add(rollbackableTask4); + migrationTaskList.add(rollbackableTask5); + + MigrationProcess migrationProcessMock = mockControl.createMock(MigrationProcess.class); + expect(migrationProcessMock.getMigrationTasks()).andReturn(migrationTaskList); + + HashMap controlledSystems = new HashMap(); + controlledSystems.put(systemName, launcher); + launcher.setMigrationProcess(migrationProcessMock); + + PatchInfoStore patchInfoStoreMock = mockControl.createMock(PatchInfoStore.class); + expect(patchInfoStoreMock.getPatchLevel()).andReturn(12); + + expect(migrationRunnerStrategyMock.isSynchronized(eq(currentPatchInfoStore), EasyMock.anyObject())).andReturn(true).anyTimes(); + List rollbackCandidates = new ArrayList(); + rollbackCandidates.add(rollbackableTask5); + rollbackCandidates.add(rollbackableTask4); + rollbackCandidates.add(rollbackableTask3); + + expect(migrationRunnerStrategyMock.getRollbackCandidates(migrationTaskList, rollbackLevels, patchInfoStoreMock)).andReturn(rollbackCandidates); + expect(migrationRunnerStrategyMock.getRollbackCandidates(rollbackCandidates, rollbackLevels, patchInfoStoreMock)).andReturn(Collections.EMPTY_LIST); + + mockControl.replay(); + + DistributedMigrationProcess distributedMigrationProcess = new DistributedMigrationProcess(); + distributedMigrationProcess.setMigrationRunnerStrategy(migrationRunnerStrategyMock); + distributedMigrationProcess.setControlledSystems(controlledSystems); + + + boolean forceRollback=false; + int rollbacksApplied = distributedMigrationProcess.doRollbacks(patchInfoStoreMock,rollbackLevels,migrationContextMock,forceRollback); + + assertEquals("Two rollbacks should be applied", 3, rollbacksApplied); + } + +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/MigrationBroadcasterTest.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/MigrationBroadcasterTest.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/MigrationBroadcasterTest.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,147 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration; + +import java.util.Properties; + +import junit.framework.TestCase; + +/** + * Exercise the MigrationBroadcaster + * + * @author Mike Hardy (mike@tacitknowledge.com) + */ +public class MigrationBroadcasterTest extends TestCase implements MigrationListener +{ + /** whether we've been started */ + private boolean started = false; + + /** whether we've succeeded */ + private boolean succeeded = false; + + /** whether we've failed */ + private boolean failed = false; + + /** The broadcaster being tested */ + private MigrationBroadcaster broadcaster = null; + + /** + * Set up our test state + */ + public void setUp() + { + broadcaster = new MigrationBroadcaster(); + broadcaster.addListener(this); + } + + /** + * Test how the broadcaster deals with null listeners + */ + public void testNullListeners() + { + try + { + broadcaster.addListener(null); + fail("We should have failed - adding null listeners is illegal"); + } + catch (IllegalArgumentException iae) + { + // we expect this + } + + try + { + broadcaster.removeListener(null); + fail("We should have failed - removing null listeners is illegal"); + } + catch (IllegalArgumentException iae) + { + // we expect this + } + } + + /** + * Test add/remove listeners + */ + public void testAddRemoveListeners() + { + // we're already there (from setUp), so try removing us + assertTrue(broadcaster.removeListener(this)); + + // now we're not there, make sure it fails + assertFalse(broadcaster.removeListener(this)); + } + + /** + * Test broadcast of the basic events + * + * @exception MigrationException if the notify fails + */ + public void testBroadcast() throws MigrationException + { + assertFalse(started); + broadcaster.notifyListeners(null, null, MigrationBroadcaster.TASK_START); + assertTrue(started); + assertFalse(succeeded); + broadcaster.notifyListeners(null, null, MigrationBroadcaster.TASK_SUCCESS); + assertTrue(succeeded); + assertFalse(failed); + broadcaster.notifyListeners(null, null, MigrationBroadcaster.TASK_FAILED); + assertTrue(failed); + try + { + broadcaster.notifyListeners(null, null, Integer.MAX_VALUE); + fail("We threw an unknown event - it should have failed"); + } + catch (IllegalArgumentException iae) + { + // we expect this + } + } + + /** + * @see MigrationListener#migrationStarted(MigrationTask, MigrationContext) + */ + public void migrationStarted(MigrationTask task, MigrationContext context) + { + started = true; + } + + /** + * @see MigrationListener#migrationSuccessful(MigrationTask, MigrationContext) + */ + public void migrationSuccessful(MigrationTask task, MigrationContext context) + { + succeeded = true; + } + + /** + * @see MigrationListener#migrationFailed(MigrationTask, MigrationContext, MigrationException) + */ + public void migrationFailed(MigrationTask task, + MigrationContext context, + MigrationException e) + { + failed = true; + } + + /** + * @see com.tacitknowledge.util.migration.MigrationListener#initialize(Properties) + */ + public void initialize(String systemName, Properties properties) throws MigrationException + { + } +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/MigrationListenerTestBase.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/MigrationListenerTestBase.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/MigrationListenerTestBase.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,243 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration; + +import java.util.Properties; + +import com.mockrunner.jdbc.JDBCTestCaseAdapter; + +/** + * This class helps with testing AutoPatch by providing a listener which implements + * both the MigrationListener and RollbackListener interfaces. The action which still + * class takes upon receiving a message of an event is to increment a particular count. + * + * Test classes can retrieve this count to see if the correct action occurred. + * + * @author Mike Hardy (mike@tacitknowledge.com) + * @author Artie Pesh-Imam (apeshimam@tacitknowledge.com) + */ +public class MigrationListenerTestBase extends JDBCTestCaseAdapter implements RollbackListener +{ + /** the count of times "migration started" was broadcast */ + private int migrationStartedCount = 0; + + /** the count of times "migration success" was broadcast */ + private int migrationSuccessCount = 0; + + /** the count of times "migration failed" was broadcast */ + private int migrationFailedCount = 0; + + /** the count of times "migration started" was broadcast */ + private int rollbackStartedCount = 0; + + /** the count of times "migration success" was broadcast */ + private int rollbackSuccessCount = 0; + + /** the count of times "migration failed" was broadcast */ + private int rollbackFailedCount = 0; + + /** + * Constructor for MigrationTest. + * + * @param name the name of the test to run + */ + public MigrationListenerTestBase(String name) + { + super(name); + } + + /** {@inheritDoc} */ + public void rollbackStarted(RollbackableMigrationTask task, MigrationContext con) + { + setRollbackStartedCount(getRollbackStartedCount() + 1); + } + + /** {@inheritDoc} */ + public void rollbackSuccessful(RollbackableMigrationTask taskn, int rollbackLevel, + MigrationContext con) + { + setRollbackSuccessCount(getRollbackSuccessCount() + 1); + } + + /** {@inheritDoc} */ + public void rollbackFailed(RollbackableMigrationTask task, MigrationContext con, + MigrationException me) + { + setRollbackFailedCount(getRollbackFailedCount() + 1); + } + + /** + * Returns the number of rollbacks started + * @return the count of rollbacks that were started + */ + public int getRollbackStartedCount() + { + return rollbackStartedCount; + } + + /** + * Set the rollback started count attribute + * @param rollbackStartedCount + */ + public void setRollbackStartedCount(int rollbackStartedCount) + { + this.rollbackStartedCount = rollbackStartedCount; + } + + /** + * Returns the number of rollbacks that succeeded + * @return the count of rollbacks that succeeded + */ + public int getRollbackSuccessCount() + { + return rollbackSuccessCount; + } + + /** + * Set the rollback started count attribute + * @param rollbackSuccessCount + */ + public void setRollbackSuccessCount(int rollbackSuccessCount) + { + this.rollbackSuccessCount = rollbackSuccessCount; + } + + /** + * Get the number of failed rollbacks + * @return the count of rollbacks that failed + */ + public int getRollbackFailedCount() + { + return rollbackFailedCount; + } + + /** + * Set the rollbackFailedCount attribute + * @param rollbackFailedCount + */ + public void setRollbackFailedCount(int rollbackFailedCount) + { + this.rollbackFailedCount = rollbackFailedCount; + } + + /** + * Implements the migration started listener + * + * @param task the task that ran + * @param con the context for the task + */ + public void migrationStarted(MigrationTask task, MigrationContext con) + { + setMigrationStartedCount(getMigrationStartedCount() + 1); + } + + /** + * Implements the migration succeeded listener + * + * @param task the task that ran + * @param con the context for the task + */ + public void migrationSuccessful(MigrationTask task, MigrationContext con) + { + setMigrationSuccessCount(getMigrationSuccessCount() + 1); + } + + /** + * Implements the migration failed listener + * + * @param task the task that ran + * @param con the context for the task + * @param exception the exception that ocurred + */ + public void migrationFailed(MigrationTask task, MigrationContext con, + MigrationException exception) + { + setMigrationFailedCount(getMigrationFailedCount() + 1); + } + + /** + * Reset all of the counters + */ + public void resetMigrationListenerState() + { + setMigrationFailedCount(0); + setMigrationStartedCount(0); + setMigrationSuccessCount(0); + } + + /** + * @return Returns the migrationFailedCount. + */ + public int getMigrationFailedCount() + { + return migrationFailedCount; + } + + /** + * @param migrationFailedCount The migrationFailedCount to set. + */ + public void setMigrationFailedCount(int migrationFailedCount) + { + this.migrationFailedCount = migrationFailedCount; + } + + /** + * @return Returns the migrationStartedCount. + */ + public int getMigrationStartedCount() + { + return migrationStartedCount; + } + + /** + * @param migrationStartedCount The migrationStartedCount to set. + */ + public void setMigrationStartedCount(int migrationStartedCount) + { + this.migrationStartedCount = migrationStartedCount; + } + + /** + * @return Returns the migrationSuccessCount. + */ + public int getMigrationSuccessCount() + { + return migrationSuccessCount; + } + + /** + * @param migrationSuccessCount The migrationSuccessCount to set. + */ + public void setMigrationSuccessCount(int migrationSuccessCount) + { + this.migrationSuccessCount = migrationSuccessCount; + } + + /** + * Get JUnit to shut up. + */ + public void testNoOp() + { + // does nothing + } + + /** + * @see com.tacitknowledge.util.migration.MigrationListener#initialize(Properties) + */ + public void initialize(String systemName, Properties properties) throws MigrationException + { + } +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/MigrationProcessRollbackTest.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/MigrationProcessRollbackTest.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/MigrationProcessRollbackTest.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,323 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration; + +import com.tacitknowledge.util.migration.builders.MockBuilder; +import com.tacitknowledge.util.migration.tasks.rollback.TestRollbackableTask2; +import com.tacitknowledge.util.migration.tasks.rollback.TestRollbackableTask3; +import com.tacitknowledge.util.migration.tasks.rollback.TestRollbackableTask4; +import com.tacitknowledge.util.migration.tasks.rollback.TestRollbackableTask5; +import org.easymock.EasyMock; +import org.easymock.MockControl; +import org.easymock.classextension.IMocksControl; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import static org.easymock.EasyMock.eq; +import static org.easymock.EasyMock.expect; +import static org.easymock.classextension.EasyMock.createStrictControl; + +/** + * This class defines unit tests for the Rollback functionality. + * + * @author Artie Pesh-Imam (apeshimam@tacitknowledge.com) + */ +public class MigrationProcessRollbackTest extends MigrationListenerTestBase +{ + /** + * The class under test + */ + private MigrationProcess runner = null; + + /** + * Test migration context + */ + private TestMigrationContext context = null; + private MockControl patchInfoStoreControl; + private PatchInfoStore patchInfoStore; + private PatchInfoStore currentPatchInfoStore; + private IMocksControl mockControl; + private MigrationRunnerStrategy migrationStrategy; + + private static final int[] ROLLBACK_LEVELS = new int[]{8}; + + /** + * Constructor for MigrationProcessRollbackTest. + * + * @param name the name of the test to run + */ + public MigrationProcessRollbackTest(String name) + { + super(name); + } + + /** + * @see junit.framework.TestCase#setUp() + */ + protected void setUp() throws Exception + { + super.setUp(); + runner = new MigrationProcess(); + runner.setMigrationRunnerStrategy(MigrationRunnerFactory.getMigrationRunnerStrategy(null)); + runner.addPatchResourceDirectory(getClass().getPackage().getName() + + ".tasks.rollback"); + runner.addPostPatchResourceDirectory(getClass().getPackage().getName() + + ".tasks.post"); + runner.addListener(this); + context = new TestMigrationContext(); + patchInfoStoreControl = MockControl.createStrictControl(PatchInfoStore.class); + patchInfoStore = (PatchInfoStore) patchInfoStoreControl.getMock(); + currentPatchInfoStore = MockBuilder.getPatchInfoStore(12); + + mockControl = createStrictControl(); + migrationStrategy = mockControl.createMock(MigrationRunnerStrategy.class); + List rollbackCandidates; + rollbackCandidates = new ArrayList(); + rollbackCandidates.add(new TestRollbackableTask5()); + rollbackCandidates.add(new TestRollbackableTask4()); + rollbackCandidates.add(new TestRollbackableTask3()); + rollbackCandidates.add(new TestRollbackableTask2()); + expect(migrationStrategy.getRollbackCandidates(EasyMock.>anyObject(), eq(ROLLBACK_LEVELS), eq(currentPatchInfoStore))).andReturn(rollbackCandidates); + expect(migrationStrategy.getRollbackCandidates(EasyMock.>anyObject(), eq(ROLLBACK_LEVELS), eq(currentPatchInfoStore))).andReturn(Collections.EMPTY_LIST); + + } + + /** + * @see junit.framework.TestCase#tearDown() + */ + protected void tearDown() throws Exception + { + super.tearDown(); + // Reset the "fail" bit that may have been set on a previous run. + // TestMigrationTask2.reset(); + // TestMigrationTask3.setFail(false); + } + + /** + * This method tests the basic rollback functionality. + * + * @throws MigrationException + */ + public void testRollbackAllTasks() throws MigrationException + { + List migrationTasks = runner.getMigrationTasks(); + assertEquals(5, migrationTasks.size()); + + patchInfoStoreControl.expectAndReturn(patchInfoStore.getPatchLevel(), 1, MockControl.ONE_OR_MORE); + patchInfoStoreControl.replay(); + + int level = runner.doMigrations(patchInfoStore, context); + runner.doPostPatchMigrations(context); + assertEquals(5, level); + assertTrue(context.hasExecuted("TestRollbackableTask1")); + assertTrue(context.hasExecuted("TestRollbackableTask2")); + assertTrue(context.hasExecuted("TestRollbackableTask3")); + assertTrue(context.hasExecuted("TestRollbackableTask4")); + assertTrue(context.hasExecuted("TestRollbackableTask5")); + + // check that the migrations occurred successfully + assertEquals(5, getMigrationStartedCount()); + assertEquals(5, getMigrationSuccessCount()); + + // execute the rollback + mockControl.replay(); + runner.setMigrationRunnerStrategy(migrationStrategy); + level = runner.doRollbacks(currentPatchInfoStore, ROLLBACK_LEVELS, context, false); + assertEquals(4, level); + assertEquals(4, getRollbackSuccessCount()); + + } + + /** + * This method tests the basic rollback functionality. + * + * @throws MigrationException + */ + public void testRollbackPartialTasks() throws MigrationException + { + List l = runner.getMigrationTasks(); + assertEquals(5, l.size()); + + patchInfoStoreControl.expectAndReturn(patchInfoStore.getPatchLevel(), 8, MockControl.ONE_OR_MORE); + patchInfoStoreControl.replay(); + + int level = runner.doMigrations(patchInfoStore, context); + runner.doPostPatchMigrations(context); + assertEquals(4, level); + assertEquals(4, getMigrationSuccessCount()); + + assertFalse(context.hasExecuted("TestRollbackableTask1")); + assertTrue(context.hasExecuted("TestRollbackableTask2")); + assertTrue(context.hasExecuted("TestRollbackableTask3")); + assertTrue(context.hasExecuted("TestRollbackableTask4")); + assertTrue(context.hasExecuted("TestRollbackableTask5")); + + // execute the rollback + mockControl.replay(); + runner.setMigrationRunnerStrategy(migrationStrategy); + level = runner.doRollbacks(currentPatchInfoStore, ROLLBACK_LEVELS, context, false); + assertEquals(4, level); + assertEquals(4, getRollbackSuccessCount()); + } + + /** + * This method tests the scneario when a non rollbackable task is attempted to rollback. + * Note that in this scenario, the forceRollback is false. + * + * @throws MigrationException + */ + public void testRollbackNotRollbackableTask() throws MigrationException + { + doInitialMigrations(); + + // execute the rollback + try + { + int[] rollbackLevels = new int[]{7}; + runner.doRollbacks(currentPatchInfoStore, rollbackLevels, context, false); + } catch (MigrationException me) + { + // expecting exception + } + assertEquals(0, getRollbackSuccessCount()); + } + + /** + * This tests the forceRollback functionality. + * + * @throws MigrationException + */ + public void testForceRollback() throws MigrationException + { + doInitialMigrations(); + + // execute the rollback + try + { + int[] rollbackLevels = new int[]{7}; + runner.doRollbacks(currentPatchInfoStore, rollbackLevels, context, true); + } catch (MigrationException me) + { + // expecting exception + } + assertEquals(5, getRollbackSuccessCount()); + } + + /** + * this is a private helper method to perform initial migrations + * + * @throws MigrationException + */ + private void doInitialMigrations() throws MigrationException + { + List l = runner.getMigrationTasks(); + assertEquals(5, l.size()); + + patchInfoStoreControl.expectAndReturn(patchInfoStore.getPatchLevel(), 0, MockControl.ONE_OR_MORE); + patchInfoStoreControl.replay(); + int level = runner.doMigrations(patchInfoStore, context); + runner.doPostPatchMigrations(context); + assertEquals(5, level); + assertEquals(5, getMigrationSuccessCount()); + + assertTrue(context.hasExecuted("TestRollbackableTask1")); + assertTrue(context.hasExecuted("TestRollbackableTask2")); + assertTrue(context.hasExecuted("TestRollbackableTask3")); + assertTrue(context.hasExecuted("TestRollbackableTask4")); + assertTrue(context.hasExecuted("TestRollbackableTask5")); + } + + /** + * this tests the scenario when the user tries to rollback to a level + * which is greater than the current patch level. + * + * @throws MigrationException + */ + public void testInvalidRollbackLevel() throws MigrationException + { + List l = runner.getMigrationTasks(); + assertEquals(5, l.size()); + + patchInfoStoreControl.expectAndReturn(patchInfoStore.getPatchLevel(), 0, MockControl.ONE_OR_MORE); + patchInfoStoreControl.replay(); + + int level = runner.doMigrations(patchInfoStore, context); + runner.doPostPatchMigrations(context); + assertEquals(5, level); + assertEquals(5, getMigrationSuccessCount()); + + assertTrue(context.hasExecuted("TestRollbackableTask1")); + assertTrue(context.hasExecuted("TestRollbackableTask2")); + assertTrue(context.hasExecuted("TestRollbackableTask3")); + assertTrue(context.hasExecuted("TestRollbackableTask4")); + assertTrue(context.hasExecuted("TestRollbackableTask5")); + + + try + { + PatchInfoStore patchInfoStoreBasedOnLevel = MockBuilder.getPatchInfoStore(level); + int[] rollbackLevels = new int[]{7}; + level = runner.doRollbacks(patchInfoStoreBasedOnLevel, rollbackLevels, context, false); + } catch (MigrationException me) + { + // expected + } + + } + + /** + * this test checks that the system correctly when a rollback is attempted + * on a non-rollbackable task. + * + * @throws MigrationException + */ + public void testMigrationTaskRollback() throws MigrationException + { + // add additional directory containing MigrationTask + runner.addPatchResourceDirectory(getClass().getPackage().getName() + + ".tasks.rollback.migrationtasks"); + List l = runner.getMigrationTasks(); + + assertEquals(6, l.size()); + + patchInfoStoreControl.expectAndReturn(patchInfoStore.getPatchLevel(), 7, MockControl.ONE_OR_MORE); + patchInfoStoreControl.replay(); + + int level = runner.doMigrations(patchInfoStore, context); + runner.doPostPatchMigrations(context); + assertEquals(6, level); + assertEquals(6, getMigrationSuccessCount()); + assertTrue(context.hasExecuted("TestRollbackableTask1")); + assertTrue(context.hasExecuted("TestRollbackableTask2")); + assertTrue(context.hasExecuted("TestRollbackableTask3")); + assertTrue(context.hasExecuted("TestRollbackableTask4")); + assertTrue(context.hasExecuted("TestRollbackableTask5")); + assertTrue(context.hasExecuted("TestMigrationTaskRollback1")); + + try + { + PatchInfoStore nestedPatchInfoStore = MockBuilder.getPatchInfoStore(13); + int[] rollbackLevels = new int[]{12}; + level = runner.doRollbacks(nestedPatchInfoStore, rollbackLevels, context, false); + } catch (MigrationException me) + { + // expected + } + assertEquals(0, level); + } +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/MigrationProcessTest.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/MigrationProcessTest.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/MigrationProcessTest.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,201 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration; + +import com.tacitknowledge.util.migration.builders.MockBuilder; +import com.tacitknowledge.util.migration.tasks.normal.TestMigrationTask2; +import com.tacitknowledge.util.migration.tasks.normal.TestMigrationTask3; +import junit.framework.TestCase; +import org.easymock.MockControl; + +import java.util.ArrayList; +import java.util.List; + + +/** + * Test the {@link MigrationProcess} class. + * + * @author Oscar Gonzalez (oscar@tacitknowledge.com) + */ + +public class MigrationProcessTest extends TestCase +{ + + private MigrationProcess migrationProcess = null; + + private MockControl migrationContextControl = null; + + private MigrationContext migrationContextMock = null; + + private MockControl migrationTaskSourceControl = null; + + private MigrationTaskSource migrationTaskSourceMock = null; + + private MockControl patchInfoStoreControl = null; + + private PatchInfoStore patchInfoStoreMock = null; + private PatchInfoStore patchInfoStore; + + public void setUp() throws Exception + { + super.setUp(); + migrationProcess = new MigrationProcess(); + migrationProcess. + setMigrationRunnerStrategy(MigrationRunnerFactory.getMigrationRunnerStrategy(null)); + migrationContextControl = MockControl.createStrictControl(MigrationContext.class); + migrationContextMock = + (MigrationContext) migrationContextControl.getMock(); + migrationTaskSourceControl = MockControl.createStrictControl(MigrationTaskSource.class); + migrationTaskSourceMock = (MigrationTaskSource) migrationTaskSourceControl.getMock(); + migrationProcess.addPatchResourcePackage("testPackageName"); + patchInfoStoreControl = MockControl.createStrictControl(PatchInfoStore.class); + patchInfoStoreMock = (PatchInfoStore) patchInfoStoreControl.getMock(); + patchInfoStore = MockBuilder.getPatchInfoStore(3); + } + + public void testAddMigrationTaskSourceWhenNullSourceIsPassed() + { + try + { + migrationProcess.addMigrationTaskSource(null); + } + catch (IllegalArgumentException iaex) + { + assertEquals("source cannot be null.", iaex.getMessage()); + return; + } + fail("We should have fail before this."); + + } + + public void testApplyPatchWithNoBroadCasters() throws MigrationException + { + migrationContextMock.commit(); + migrationContextControl.replay(); + TestMigrationTask2 migrationTask = new TestMigrationTask2(); + migrationProcess.applyPatch(migrationContextMock, migrationTask, false); + migrationContextControl.verify(); + } + + public void testApplyPatchWithBroadcasters() throws MigrationException + { + migrationContextMock.commit(); + migrationContextControl.replay(); + TestMigrationTask2 migrationTask = new TestMigrationTask2(); + migrationProcess.setMigrationBroadcaster(new MigrationBroadcaster()); + migrationProcess.applyPatch(migrationContextMock, migrationTask, true); + migrationContextControl.verify(); + } + + public void testDryRunWithEmptyMigrationList() throws MigrationException { + + int taskCount = migrationProcess.dryRun(patchInfoStore, migrationContextMock, new ArrayList()); + assertEquals("Task count should be zero with an empty MigrationList", 0, taskCount); + } + + public void testDryRunWithNullMigrationList() throws MigrationException { + try + { + migrationProcess.dryRun(patchInfoStore, migrationContextMock, null); + } + catch (NullPointerException npe) + { + return; // We expected this + } + fail("A null List of migrations should throw a NPE"); + } + + public void testDryRunWithMigrationsInOrder() throws MigrationException { + patchInfoStoreControl.expectAndReturn(patchInfoStoreMock.getPatchLevel(), 3, 2); + patchInfoStoreControl.replay(); + int taskCount = migrationProcess.dryRun(patchInfoStoreMock, migrationContextMock, getMigrationTasks()); + assertEquals("TaskCount should be equal to 2", 2, taskCount); + patchInfoStoreControl.verify(); + } + + private List getMigrationTasks() + { + RollbackableMigrationTask migrationTask2 = new TestMigrationTask2(); + RollbackableMigrationTask migrationTask3 = new TestMigrationTask3(); + List migrationsList = new ArrayList(); + migrationsList.add(migrationTask2); + migrationsList.add(migrationTask3); + return migrationsList; + } + + + public void testDoMigrationInReadOnlyWithExistingTasksThrowsError() throws MigrationException + { + try + { + migrationProcess.setReadOnly(true); + migrationTaskSourceControl.expectAndReturn(migrationTaskSourceMock. + getMigrationTasks("testPackageName"), getMigrationTasks()); + migrationTaskSourceControl.replay(); + patchInfoStoreControl.expectAndReturn(patchInfoStoreMock.getPatchLevel(), 2, 2); + patchInfoStoreControl.replay(); + migrationProcess.addMigrationTaskSource(migrationTaskSourceMock); + migrationProcess.doMigrations(patchInfoStoreMock, migrationContextMock); + } + catch (MigrationException miex) + { + migrationTaskSourceControl.verify(); + return; // We expect this, succesful scenario + } + fail("We should have thrown an error since we have migrations but we are in " + + "read only mode"); + } + + + public void testDoMigrationInReadOnlyWithZeroTasks() throws MigrationException + { + migrationProcess.setReadOnly(true); + migrationTaskSourceControl.expectAndReturn(migrationTaskSourceMock. + getMigrationTasks("testPackageName"), new ArrayList()); + migrationTaskSourceControl.replay(); + migrationProcess.addMigrationTaskSource(migrationTaskSourceMock); + patchInfoStoreControl.expectAndReturn(patchInfoStoreMock.getPatchLevel(), 0); + patchInfoStoreControl.replay(); + migrationProcess.doMigrations(patchInfoStoreMock, migrationContextMock); + } + + public void testDoTwoMigrations() throws MigrationException + { + migrationProcess.setReadOnly(false); + migrationTaskSourceControl.expectAndReturn(migrationTaskSourceMock. + getMigrationTasks("testPackageName"), getMigrationTasks()); + migrationTaskSourceControl.replay(); + migrationProcess.addMigrationTaskSource(migrationTaskSourceMock); + patchInfoStoreControl.expectAndReturn(patchInfoStoreMock.getPatchLevel(), 2, 4); + patchInfoStoreControl.replay(); + assertEquals("We should have executed 2 migrations", + 2, migrationProcess.doMigrations(patchInfoStoreMock, migrationContextMock)); + } + + public void testDontDoMigrations() throws MigrationException + { + migrationProcess.setReadOnly(false); + migrationTaskSourceControl.expectAndReturn(migrationTaskSourceMock. + getMigrationTasks("testPackageName"), getMigrationTasks()); + migrationTaskSourceControl.replay(); + migrationProcess.addMigrationTaskSource(migrationTaskSourceMock); + patchInfoStoreControl.expectAndReturn(patchInfoStoreMock.getPatchLevel(), 100, 4); + patchInfoStoreControl.replay(); + assertEquals("We should have executed no migrations", + 0, migrationProcess.doMigrations(patchInfoStoreMock, migrationContextMock)); + } + +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/MigrationTest.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/MigrationTest.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/MigrationTest.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,289 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration; + +import java.util.List; + +import com.tacitknowledge.util.migration.builders.MockBuilder; +import com.tacitknowledge.util.migration.tasks.normal.TestMigrationTask2; +import com.tacitknowledge.util.migration.tasks.normal.TestMigrationTask3; +import org.easymock.MockControl; + +/** + * Test basic migration functionality + * + * @author Scott Askew (scott@tacitknowledge.com) + */ +public class MigrationTest extends MigrationListenerTestBase +{ + /** The class under test */ + private MigrationProcess runner = null; + + /** Test migration context */ + private TestMigrationContext context = null; + private MockControl patchInfoStoreControl; + private PatchInfoStore patchInfoStore; + + /** + * Constructor for MigrationTest. + * + * @param name the name of the test to run + */ + public MigrationTest(String name) + { + super(name); + } + + /** + * @see junit.framework.TestCase#setUp() + */ + protected void setUp() throws Exception + { + super.setUp(); + runner = new MigrationProcess(); + runner.setMigrationRunnerStrategy(MigrationRunnerFactory.getMigrationRunnerStrategy(null)); + runner.addPatchResourceDirectory(getClass().getPackage().getName() + ".tasks.normal"); + runner.addPatchResourceDirectory(getClass().getPackage().getName() + ".tasks.rollback"); + runner.addPostPatchResourceDirectory(getClass().getPackage().getName() + ".tasks.post"); + runner.addListener(this); + context = new TestMigrationContext(); + patchInfoStoreControl = MockControl.createStrictControl(PatchInfoStore.class); + patchInfoStore = (PatchInfoStore) patchInfoStoreControl.getMock(); + } + + /** + * @see junit.framework.TestCase#tearDown() + */ + protected void tearDown() throws Exception + { + super.tearDown(); + // Reset the "fail" bit that may have been set on a previous run. + TestMigrationTask2.reset(); + TestMigrationTask3.setFail(false); + } + + /** + * Validates that the migration runner can run the all patches. + * + * @throws MigrationException if an unexpected error occurs + */ + public void testRunAllMigrationTasks() throws MigrationException + { + List l = runner.getMigrationTasks(); + assertEquals(9, l.size()); + + patchInfoStoreControl.expectAndReturn(patchInfoStore.getPatchLevel(), 0, MockControl.ONE_OR_MORE); + patchInfoStoreControl.replay(); + + int level = runner.doMigrations(patchInfoStore, context); + runner.doPostPatchMigrations(context); + assertEquals(9, level); + assertTrue(context.hasExecuted("TestTask1")); + assertTrue(context.hasExecuted("TestTask2")); + assertTrue(context.hasExecuted("TestTask3")); + assertTrue(context.hasExecuted("TestTask4")); + assertTrue(context.hasExecuted("TestRollbackableTask1")); + assertTrue(context.hasExecuted("TestRollbackableTask2")); + assertTrue(context.hasExecuted("TestRollbackableTask3")); + assertTrue(context.hasExecuted("TestRollbackableTask4")); + assertTrue(context.hasExecuted("TestRollbackableTask5")); + assertTrue(context.hasExecuted("TestPostTask1")); + assertTrue(context.hasExecuted("TestPostTask2")); + assertEquals(9, getMigrationStartedCount()); + assertEquals(9, getMigrationSuccessCount()); + } + + /** + * Validates that a re-run won't run any patches + * + * @throws MigrationException if an unexpected error occurs + */ + public void testReRunAllMigrationTasks() throws MigrationException + { + List l = runner.getMigrationTasks(); + assertEquals(9, l.size()); + + // run them all once + patchInfoStoreControl.expectAndReturn(patchInfoStore.getPatchLevel(), 0, MockControl.ONE_OR_MORE); + patchInfoStoreControl.replay(); + + int level = runner.doMigrations(patchInfoStore, context); + runner.doPostPatchMigrations(context); + assertEquals(9, level); + assertTrue(context.hasExecuted("TestTask1")); + assertTrue(context.hasExecuted("TestTask2")); + assertTrue(context.hasExecuted("TestTask3")); + assertTrue(context.hasExecuted("TestTask4")); + assertTrue(context.hasExecuted("TestRollbackableTask1")); + assertTrue(context.hasExecuted("TestRollbackableTask2")); + assertTrue(context.hasExecuted("TestRollbackableTask3")); + assertTrue(context.hasExecuted("TestRollbackableTask4")); + assertTrue(context.hasExecuted("TestRollbackableTask5")); + assertTrue(context.hasExecuted("TestPostTask1")); + assertTrue(context.hasExecuted("TestPostTask2")); + assertEquals(9, getMigrationStartedCount()); + assertEquals(9, getMigrationSuccessCount()); + + // now re-run them and see what happens + setMigrationStartedCount(0); + setMigrationFailedCount(0); + setMigrationSuccessCount(0); + + patchInfoStoreControl.reset(); + patchInfoStoreControl.expectAndReturn(patchInfoStore.getPatchLevel(), 15, MockControl.ONE_OR_MORE); + patchInfoStoreControl.replay(); + + level = runner.doMigrations(patchInfoStore, context); + runner.doPostPatchMigrations(context); + assertEquals(0, level); + assertTrue(context.hasExecuted("TestTask1")); + assertTrue(context.hasExecuted("TestTask2")); + assertTrue(context.hasExecuted("TestTask3")); + assertTrue(context.hasExecuted("TestTask4")); + assertTrue(context.hasExecuted("TestRollbackableTask1")); + assertTrue(context.hasExecuted("TestRollbackableTask2")); + assertTrue(context.hasExecuted("TestRollbackableTask3")); + assertTrue(context.hasExecuted("TestRollbackableTask4")); + assertTrue(context.hasExecuted("TestRollbackableTask5")); + assertTrue(context.hasExecuted("TestPostTask1")); + assertTrue(context.hasExecuted("TestPostTask2")); + assertEquals(0, getMigrationStartedCount()); + assertEquals(0, getMigrationSuccessCount()); + } + + /** + * Validates that the migration runner will only run the necessary patches + * to bring the system current + * + * @throws MigrationException if an unexpected error occurs + */ + public void testRunPartialMigrationTasks() throws MigrationException + { + List l = runner.getMigrationTasks(); + assertEquals(9, l.size()); + + patchInfoStoreControl.expectAndReturn(patchInfoStore.getPatchLevel(), 9, MockControl.ONE_OR_MORE); + patchInfoStoreControl.replay(); + + int level = runner.doMigrations(patchInfoStore, context); + runner.doPostPatchMigrations(context); + assertEquals(3, level); + assertFalse(context.hasExecuted("TestTask1")); + assertFalse(context.hasExecuted("TestTask2")); + assertFalse(context.hasExecuted("TestTask3")); + assertFalse(context.hasExecuted("TestTask4")); + assertFalse(context.hasExecuted("TestRollbackableTask1")); + assertFalse(context.hasExecuted("TestRollbackableTask2")); + assertTrue(context.hasExecuted("TestRollbackableTask3")); + assertTrue(context.hasExecuted("TestRollbackableTask4")); + assertTrue(context.hasExecuted("TestRollbackableTask5")); + assertTrue(context.hasExecuted("TestPostTask1")); + assertTrue(context.hasExecuted("TestPostTask2")); + assertEquals(3, getMigrationStartedCount()); + assertEquals(3, getMigrationSuccessCount()); + } + + /** + * Validates that the migration runner will handle busted Migrations ok + * + * @throws MigrationException if an unexpected error occurs + */ + public void testRunBrokenMigrationTasks() throws MigrationException + { + List l = runner.getMigrationTasks(); + TestMigrationTask3.setFail(true); + assertEquals(9, l.size()); + + int executedTasks = 0; + try + { + patchInfoStoreControl.expectAndReturn(patchInfoStore.getPatchLevel(), 5, MockControl.ONE_OR_MORE); + patchInfoStoreControl.replay(); + executedTasks = runner.doMigrations(patchInfoStore, context); + runner.doPostPatchMigrations(context); + fail("We called a migration that failed, this should have thrown an exception"); + } + catch (MigrationException me) + { + // we expect this + } + assertEquals(0, executedTasks); + assertFalse(context.hasExecuted("TestTask1")); + assertFalse(context.hasExecuted("TestTask2")); + assertFalse(context.hasExecuted("TestTask3")); + assertFalse(context.hasExecuted("TestTask4")); + assertFalse(context.hasExecuted("TestRollbackableTask1")); + assertFalse(context.hasExecuted("TestRollbackableTask2")); + assertFalse(context.hasExecuted("TestRollbackableTask3")); + assertFalse(context.hasExecuted("TestRollbackableTask4")); + assertFalse(context.hasExecuted("TestRollbackableTask5")); + assertFalse(context.hasExecuted("TestPostTask1")); + assertFalse(context.hasExecuted("TestPostTask2")); + assertEquals(1, getMigrationStartedCount()); + assertEquals(0, getMigrationSuccessCount()); + assertEquals(1, getMigrationFailedCount()); + } + + /** + * Verifies that the migration runner checks for patch level uniqueness + * between migration tasks + */ + public void testMigrationPatchLevelUniqueness() + { + try + { + TestMigrationTask2.setPatchLevelOverride(new Integer(7)); + runner.doMigrations(MockBuilder.getPatchInfoStore(0), context); + fail("Expected a MigrationException due to a task patch level conflict"); + } + catch (MigrationException e) + { + assertTrue("Expected a patch conflict migration exception, not \"" + + e.getMessage() + "\"", + e.getMessage().indexOf("conflicting patch level") > -1); + } + } + + /** + * Validates that the migration runner will flag tasks with null patch levels + */ + public void testNullPatchLevel() + { + try + { + TestMigrationTask2.setPatchLevelOverride(null); + runner.doMigrations(MockBuilder.getPatchInfoStore(0), new TestMigrationContext()); + fail("Expected a MigrationException due to a null patch level"); + } + catch (MigrationException e) + { + assertTrue("Expected a patch level missing exception, not \"" + + e.getMessage() + "\"", + e.getMessage().indexOf("patch level defined") > -1); + } + } + + /** + * Validates Migration.getNextPatchLevel. + * + * @throws MigrationException if an unexpected error occurs + */ + public void testGetNextPatchLevel() throws MigrationException + { + int level = runner.getNextPatchLevel(); + assertEquals(13, level); + } +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/MissingPatchMigrationRunnerStrategyTest.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/MissingPatchMigrationRunnerStrategyTest.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/MissingPatchMigrationRunnerStrategyTest.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,181 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.tacitknowledge.util.migration; + +import com.tacitknowledge.util.migration.builders.MockBuilder; +import com.tacitknowledge.util.migration.tasks.normal.TestMigrationTask1; +import com.tacitknowledge.util.migration.tasks.normal.TestMigrationTask2; +import com.tacitknowledge.util.migration.tasks.normal.TestMigrationTask3; +import com.tacitknowledge.util.migration.tasks.normal.TestMigrationTask4; +import com.tacitknowledge.util.migration.tasks.rollback.*; +import junit.framework.TestCase; +import org.easymock.EasyMock; +import org.easymock.MockControl; +import org.easymock.classextension.IMocksControl; + +import java.util.*; + +import static org.easymock.EasyMock.expect; +import static org.easymock.classextension.EasyMock.createControl; + +/** + * Test the {@link MissingPatchMigrationRunnerStrategy} class. + * + * @author Hemri Herrera (hemri@tacitknowledge.com) + * @author Ulises Pulido (upulido@tacitknowledge.com) + */ +public class MissingPatchMigrationRunnerStrategyTest extends TestCase { + + private MissingPatchMigrationRunnerStrategy strategy; + private MockControl patchInfoStoreControl; + private PatchInfoStore patchInfoStore; + private List allMigrationTasks; + private TestRollbackableTask2 rollbackableTask2; + private TestRollbackableTask4 rollbackableTask4; + private static final int[] ROLLBACK_LEVELS = new int[]{9, 11}; + private IMocksControl mockControl; + private PatchInfoStore currentPatchInfoStore; + + protected void setUp() throws Exception { + strategy = new MissingPatchMigrationRunnerStrategy(); + patchInfoStoreControl = MockControl.createControl(PatchInfoStore.class); + patchInfoStore = (PatchInfoStore) patchInfoStoreControl.getMock(); + allMigrationTasks = new ArrayList(); + rollbackableTask2 = new TestRollbackableTask2(); + rollbackableTask4 = new TestRollbackableTask4(); + mockControl = createControl(); + currentPatchInfoStore = mockControl.createMock(PatchInfoStore.class); + + allMigrationTasks.add(new TestRollbackableTask1()); + allMigrationTasks.add(rollbackableTask2); + allMigrationTasks.add(new TestRollbackableTask3()); + allMigrationTasks.add(rollbackableTask4); + allMigrationTasks.add(new TestRollbackableTask5()); + } + + public void testShouldMigrationRunReturnsTrueIfPatchWasNotApplied() throws MigrationException { + patchInfoStoreControl.expectAndReturn(patchInfoStore.isPatchApplied(5), false); + patchInfoStoreControl.replay(); + boolean actualResult = strategy.shouldMigrationRun(5, patchInfoStore); + assertTrue("The patch was already applied.", actualResult); + } + + public void testShouldMigrationRunReturnsFalseIfPatchWasApplied() throws MigrationException { + patchInfoStoreControl.expectAndReturn(patchInfoStore.isPatchApplied(4), true); + patchInfoStoreControl.replay(); + boolean actualResult = strategy.shouldMigrationRun(4, patchInfoStore); + assertFalse("The patch was not applied.", actualResult); + } + + public void testShouldMigrationThrowIllegalArgumentExceptionIfPatchInfoStoreParameterIsNull() throws MigrationException { + + try { + + strategy.shouldMigrationRun(3, null); + fail("If parameter Is null an Illegal Argument Exception should have been thrown"); + + } catch (IllegalArgumentException exception) { + + } + + } + + public void testSystemIsSynchronized() throws MigrationException { + Set patchInfoStorePatches = new HashSet(); + patchInfoStorePatches.add(1); + PatchInfoStore patchInfoStore = MockBuilder.getPatchInfoStore(3, patchInfoStorePatches); + + Set currentPatchInfoStorePatches = new HashSet(); + currentPatchInfoStorePatches.add(1); + PatchInfoStore currentPatchInfoStore = MockBuilder.getPatchInfoStore(3, currentPatchInfoStorePatches); + boolean systemSync = strategy.isSynchronized(currentPatchInfoStore, patchInfoStore); + assertTrue("System should be synchronized", systemSync); + } + + + public void testSystemIsNotSynchronized() throws MigrationException { + Set patchInfoStorePatches = new HashSet(); + patchInfoStorePatches.add(12); + PatchInfoStore patchInfoStore = MockBuilder.getPatchInfoStore(3, patchInfoStorePatches); + + Set currentPatchInfoStorePatches = new HashSet(); + currentPatchInfoStorePatches.add(1); + PatchInfoStore currentPatchInfoStore = MockBuilder.getPatchInfoStore(3, currentPatchInfoStorePatches); + boolean systemSync = strategy.isSynchronized(currentPatchInfoStore, patchInfoStore); + assertFalse("System shouldn't be synchronized", systemSync); + } + + public void testShouldMigrationThrowIllegalArgumentExceptionIfPatchInfoStoreParametersAreNullWhenIsSync() throws MigrationException { + + try { + strategy.isSynchronized(null, null); + fail("If arguments are null an Illegal Argument Exception should have been thrown"); + } catch (IllegalArgumentException exception) { + + } + + } + + + public void testGetRollbackCandidatesAction() throws MigrationException { + + expect(currentPatchInfoStore.isPatchApplied(9)).andReturn(true); + expect(currentPatchInfoStore.isPatchApplied(11)).andReturn(true); + mockControl.replay(); + + List rollbackCandidates = strategy.getRollbackCandidates(allMigrationTasks, ROLLBACK_LEVELS, currentPatchInfoStore); + + assertEquals("There should be 2 tasks to be rolledback", 2, rollbackCandidates.size()); + assertTrue("Task 2 should be a candidate", rollbackCandidates.contains(rollbackableTask2)); + assertTrue("Task 4 should be a candidate", rollbackCandidates.contains(rollbackableTask4)); + + } + + public void testGetRollbackCandidatesIfPatchNotAppliedItShouldNotBeMarkedAsCandidate() throws MigrationException { + + expect(currentPatchInfoStore.isPatchApplied(9)).andReturn(true); + expect(currentPatchInfoStore.isPatchApplied(11)).andReturn(false); + mockControl.replay(); + + List rollbackCandidates = strategy.getRollbackCandidates(allMigrationTasks, ROLLBACK_LEVELS, currentPatchInfoStore); + + assertEquals("There should be one tasks to be rolledback", 1, rollbackCandidates.size()); + assertTrue("Task 2 should be a candidate", rollbackCandidates.contains(rollbackableTask2)); + assertFalse("Task 4 should be a candidate", rollbackCandidates.contains(rollbackableTask4)); + + } + + public void testGetRollbackCandidatesRollbackLevelShouldNotBeNull() { + + try { + strategy.getRollbackCandidates(allMigrationTasks, null, currentPatchInfoStore); + fail("An Exception should have been thrown due to rollback levels being null"); + } catch (MigrationException e) { + //expected + } + } + + public void testGetRollbackCandidatesRollbackLevelShouldNotBeEmpty() { + + try { + strategy.getRollbackCandidates(allMigrationTasks, new int[]{}, currentPatchInfoStore); + fail("An Exception should have been thrown due to rollback levels being empty"); + } catch (MigrationException e) { + //expected + } + } + + +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/OrderedMigrationRunnerStrategyTest.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/OrderedMigrationRunnerStrategyTest.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/OrderedMigrationRunnerStrategyTest.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,188 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration; + +import com.tacitknowledge.util.migration.builders.MockBuilder; +import com.tacitknowledge.util.migration.tasks.rollback.*; +import junit.framework.TestCase; +import org.easymock.IMocksControl; + +import java.util.ArrayList; +import java.util.List; + +import static org.easymock.EasyMock.expect; +import static org.easymock.classextension.EasyMock.createControl; + +/** + * Test the {@link OrderedMigrationRunnerStrategy} class. + * + * @author Oscar Gonzalez (oscar@tacitknowledge.com) + * @author Hemri Herrera (hemri@tacitknowledge.com) + * @author Ulises Pulido (upulido@tacitknowledge.com) + */ +public class OrderedMigrationRunnerStrategyTest extends TestCase +{ + + private MigrationRunnerStrategy migrationRunnerStrategy; + private List allMigrationTasks; + private PatchInfoStore currentPatchInfoStore; + + public void setUp() throws Exception + { + super.setUp(); + migrationRunnerStrategy = new OrderedMigrationRunnerStrategy(); + allMigrationTasks = new ArrayList(); + IMocksControl mockControl = createControl(); + currentPatchInfoStore = mockControl.createMock(PatchInfoStore.class); + allMigrationTasks.add(new TestRollbackableTask1()); + allMigrationTasks.add(new TestRollbackableTask2()); + allMigrationTasks.add(new TestRollbackableTask3()); + allMigrationTasks.add(new TestRollbackableTask4()); + allMigrationTasks.add(new TestRollbackableTask5()); + expect(currentPatchInfoStore.getPatchLevel()).andReturn(12); + mockControl.replay(); + + } + + public void testShouldMigrationsRunInOrder() throws MigrationException + { + PatchInfoStore patchInfoStore = MockBuilder.getPatchInfoStore(2); + + assertTrue("Should be able to run migration if current level is below migration level", + migrationRunnerStrategy.shouldMigrationRun(3, patchInfoStore)); + + } + + public void testShouldMigrationFailIfCurrentLevelIsAboveMigrationLevel() throws MigrationException + { + PatchInfoStore patchInfoStore = MockBuilder.getPatchInfoStore(3); + assertFalse("Should not be able to run migration if current level is above migration level", + migrationRunnerStrategy.shouldMigrationRun(2, patchInfoStore)); + + } + + public void testShouldMigrationFailIfCurrentAndMigrationLevelAreEquals() throws MigrationException + { + + PatchInfoStore patchInfoStore = MockBuilder.getPatchInfoStore(3); + assertFalse("Should not be able to run migration if current level and migration level are equal", + migrationRunnerStrategy.shouldMigrationRun(3, patchInfoStore)); + } + + public void testSystemIsSynchronized() throws MigrationException + { + + PatchInfoStore patchInfoStore = MockBuilder.getPatchInfoStore(3); + PatchInfoStore currentPatchInfoStore = MockBuilder.getPatchInfoStore(3); + + boolean systemSync = migrationRunnerStrategy.isSynchronized(currentPatchInfoStore, patchInfoStore); + + assertTrue("System should be synchronized", systemSync); + } + + public void testSystemIsNotSynchronized() throws MigrationException + { + PatchInfoStore patchInfoStore = MockBuilder.getPatchInfoStore(4); + PatchInfoStore currentPatchInfoStore = MockBuilder.getPatchInfoStore(3); + + + boolean systemSync = migrationRunnerStrategy.isSynchronized(currentPatchInfoStore, patchInfoStore); + + assertFalse("System shouldn't be synchronized", systemSync); + } + + public void testShouldMigrationThrowIllegalArgumentExceptionIfPatchInfoStoreParametersAreNullWhenIsSync() throws MigrationException + { + + try + { + migrationRunnerStrategy.isSynchronized(null, null); + fail("If arguments are null an Illegal Argument Exception should have been thrown"); + } catch (IllegalArgumentException exception) + { + + } + + } + + public void testGetRollbackCandidatesAction() throws MigrationException + { + + int[] rollbackLevels = new int[]{9}; + + List rollbackCandidates = migrationRunnerStrategy.getRollbackCandidates(allMigrationTasks, rollbackLevels, currentPatchInfoStore); + + assertEquals("Expected rollback candidates should be 3", 3, rollbackCandidates.size()); + + } + + public void testGetRollbackCandidatesRollbackLevelsShouldContainOnlyOneLevel() throws MigrationException + { + + int[] rollbackLevels = new int[]{9, 10}; + try + { + List rollbackCandidates = migrationRunnerStrategy.getRollbackCandidates(allMigrationTasks, rollbackLevels, currentPatchInfoStore); + fail("MigrationException is expected due to the strategy does not support more than one rollbackLevel"); + } catch (MigrationException exception) + { + + } + } + + public void testGetRollbackCandidatesRollbackLevelShouldBeLowerThanCurrentPatchLevel() + { + int[] rollbackLevels = new int[]{13}; + + try + { + List rollbackCandidates = migrationRunnerStrategy.getRollbackCandidates(allMigrationTasks, rollbackLevels, currentPatchInfoStore); + fail("The rollbackLevel should be lower than the currentPatchLevel"); + } catch (MigrationException e) + { + + } + } + + public void testGetRollbackCandidatesRollbackLevelShouldNotBeNull() + { + int[] rollbackLevels = null; + + try + { + List rollbackCandidates = migrationRunnerStrategy.getRollbackCandidates(allMigrationTasks, rollbackLevels, currentPatchInfoStore); + fail("The rollbackLevel should not be null"); + } catch (MigrationException e) + { + + } + } + + public void testGetRollbackCandidatesRollbackLevelShouldNotBeEmpty() + { + int[] rollbackLevels = new int[]{}; + + try + { + List rollbackCandidates = migrationRunnerStrategy.getRollbackCandidates(allMigrationTasks, rollbackLevels, currentPatchInfoStore); + fail("The rollbackLevel should not be empty"); + } catch (MigrationException e) + { + + } + } + +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/PatchRollbackPredicateTest.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/PatchRollbackPredicateTest.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/PatchRollbackPredicateTest.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,96 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration; + +import junit.framework.TestCase; + +import org.apache.commons.collections.Predicate; + +import com.tacitknowledge.util.migration.tasks.rollback.TestRollbackableTask1; + +/** + * this test cases test the PatchRollbackPredicate + * + * @author Artie Pesh-Imam (apeshimam@tacitknowledge.com) + */ +public class PatchRollbackPredicateTest extends TestCase { + + public void testPredicateReturnsInRange() { + + //create a new predicate + Predicate patchRollbackPredicate = new PatchRollbackPredicate(10,1); + TestRollbackableTask1 task = new TestRollbackableTask1(4); + + boolean result = patchRollbackPredicate.evaluate(task); + assertTrue("PatchRollbackPredicate returned false unexpectedly",result); + } + + public void testPredicateReturnsLessThanRollback() { + + //create a new predicate + Predicate patchRollbackPredicate = new PatchRollbackPredicate(10,2); + TestRollbackableTask1 task = new TestRollbackableTask1(1); + + boolean result = patchRollbackPredicate.evaluate(task); + assertFalse("PatchRollbackPredicate returned true unexpectedly",result); + } + + public void testPredicateReturnsGreaterThanCurrent() { + + //create a new predicate + Predicate patchRollbackPredicate = new PatchRollbackPredicate(10,2); + TestRollbackableTask1 task = new TestRollbackableTask1(11); + + boolean result = patchRollbackPredicate.evaluate(task); + assertFalse("PatchRollbackPredicate returned true unexpectedly",result); + } + + public void testPredicateReturnsLowerBoundary() { + + //create a new predicate + Predicate patchRollbackPredicate = new PatchRollbackPredicate(10,2); + TestRollbackableTask1 task = new TestRollbackableTask1(2); + + boolean result = patchRollbackPredicate.evaluate(task); + assertFalse("PatchRollbackPredicate returned true unexpectedly",result); + } + public void testPredicateReturnsUpperBoundary() { + + //create a new predicate + Predicate patchRollbackPredicate = new PatchRollbackPredicate(10,2); + TestRollbackableTask1 task = new TestRollbackableTask1(10); + + boolean result = patchRollbackPredicate.evaluate(task); + assertTrue("PatchRollbackPredicate returned false unexpectedly",result); + } + public void testPatchRollbackPredicateNull() { + + //create a new predicate + Predicate patchRollbackPredicate = new PatchRollbackPredicate(10,2); + + boolean result = patchRollbackPredicate.evaluate(null); + assertFalse("PatchRollbackPredicate returned true unexpectedly",result); + } + + public void testPatchRollbackPredicateWrongClass() { + + //create a new predicate + Predicate patchRollbackPredicate = new PatchRollbackPredicate(10,2); + + boolean result = patchRollbackPredicate.evaluate(new Object()); + assertFalse("testPatchRollbackPredicateWrongClass returned true unexpectedly",result); + } +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/TestMigrationContext.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/TestMigrationContext.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/TestMigrationContext.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,71 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration; + +import java.util.HashMap; +import java.util.Map; + +import com.tacitknowledge.util.migration.jdbc.DataSourceMigrationContext; + +/** + * Extends MigrationContext by adding a log of test executions. + * + * @author Scott Askew (scott@tacitknowledge.com) + */ +public class TestMigrationContext extends DataSourceMigrationContext +{ + /** + * A record of task executions + */ + private Map executionLog = new HashMap(); + + /** + * Records a successful task execution + * + * @param taskName the name of the task + */ + public void recordExecution(String taskName) + { + executionLog.put(taskName, Boolean.TRUE); + } + + /** + * Determines if the given task has been executed + * + * @param taskName the name of the task to validate + * @return true if the task has successfully executed + */ + public boolean hasExecuted(String taskName) + { + return executionLog.containsKey(taskName); + } + + /** + * @see com.tacitknowledge.util.migration.MigrationContext#commit() + */ + public void commit() throws MigrationException + { + // does nothing + } + + /** + * @see com.tacitknowledge.util.migration.MigrationContext#rollback() + */ + public void rollback() throws MigrationException + { + // does nothing + } +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/builders/MockBuilder.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/builders/MockBuilder.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/builders/MockBuilder.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,77 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration.builders; + +import com.tacitknowledge.util.migration.MigrationException; +import com.tacitknowledge.util.migration.PatchInfoStore; +import org.easymock.classextension.IMocksControl; + +import java.util.Properties; +import java.util.Set; + +import static org.easymock.EasyMock.expect; +import static org.easymock.classextension.EasyMock.createStrictControl; + +/** + * MockBuilder to retrieve simple objects used in different tests + * + * @author Oscar Gonzalez (oscar@tacitknowledge.com) + */ +public class MockBuilder +{ + + public static PatchInfoStore getPatchInfoStore(int patchLevel) throws MigrationException + { + return getPatchInfoStore(patchLevel, null); + } + + public static PatchInfoStore getPatchInfoStore(int patchLevel, Set patchesApplied) + throws MigrationException + { + IMocksControl patchInfoStoreControl = createStrictControl(); + PatchInfoStore patchInfoStoreMock = patchInfoStoreControl.createMock(PatchInfoStore.class); + expect(patchInfoStoreMock.getPatchLevel()).andReturn(patchLevel).anyTimes(); + expect(patchInfoStoreMock.getPatchesApplied()).andReturn(patchesApplied); + patchInfoStoreControl.replay(); + return patchInfoStoreMock; + } + + public static Properties getPropertiesWithSystemConfiguration(String system, String strategy) + { + Properties properties = new Properties(); + properties.setProperty(system + ".jdbc.database.type", "hsqldb"); + properties.setProperty(system + ".patch.path", "systemPath"); + properties.setProperty(system + ".jdbc.driver", "jdbcDriver"); + properties.setProperty(system + ".jdbc.url", "jdbcUrl"); + properties.setProperty(system + ".jdbc.username", "jdbcUsername"); + properties.setProperty(system + ".jdbc.password", "jdbcPassword"); + properties.setProperty(system + ".jdbc.dialect", "hsqldb"); + properties.setProperty("migration.strategy", strategy); + return properties; + } + + public static Properties getPropertiesWithDistributedSystemConfiguration(String system, + String strategy, String subsystems) + { + Properties properties = getPropertiesWithSystemConfiguration(system, strategy); + properties.setProperty(system + ".context", system); + properties.setProperty(system + ".controlled.systems", subsystems); + + return properties; + } + + +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/jdbc/DataSourceMigrationContextTest.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/jdbc/DataSourceMigrationContextTest.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/jdbc/DataSourceMigrationContextTest.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,99 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration.jdbc; + +import java.sql.Connection; +import java.sql.SQLException; + +import junit.framework.TestCase; + +import com.tacitknowledge.util.migration.jdbc.util.SqlUtil; + +/** + * Exercise the data source migration context + * + * @author Mike Hardy (mike@tacitknowledge.com) + */ +public class DataSourceMigrationContextTest extends TestCase +{ + /** + * A sample system name to use for testing + */ + public static final String TEST_SYSTEM_NAME = "testdb"; + + /** + * Test the system name setting + */ + public void testSystemName() + { + DataSourceMigrationContext context = new DataSourceMigrationContext(); + + StringBuffer testNameBuffer = new StringBuffer(""); + for (int i = 0; i < JdbcMigrationContext.MAX_SYSTEMNAME_LENGTH; i++) + { + testNameBuffer.append("a"); + } + String testName = testNameBuffer.toString(); + + try + { + context.setSystemName(null); + fail("Should have thrown an exception on a null system name"); + } + catch (IllegalArgumentException iae) + { + // we expect this, assertion only to satisfy checkstyle + // complaint about empty block + assertNotNull(iae); + } + try + { + context.setSystemName(testName + "."); + fail("We should have thrown an exception on a too-long system name"); + } + catch (IllegalArgumentException iae) + { + // we expect this, assertion only to satisfy checkstyle + // complaint about empty block + assertNotNull(iae); + } + context.setSystemName(testName); + } + + /** + * Test getting a connection on an uninitialized context + */ + public void testUseOfUninitializedContext() + { + DataSourceMigrationContext context = new DataSourceMigrationContext(); + Connection conn = null; + try + { + conn = context.getConnection(); + fail("Expected SQLException"); + } + catch (SQLException e) + { + // we expect this, assertion only to satisfy checkstyle + // complaint about empty block + assertNotNull(e); + } + finally + { + SqlUtil.close(conn, null, null); + } + } +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/jdbc/DatabaseTypeTest.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/jdbc/DatabaseTypeTest.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/jdbc/DatabaseTypeTest.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,136 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration.jdbc; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; + +import junit.framework.TestCase; + +/** + * @author Alex Soto (apsoto@gmail.com) + */ +public class DatabaseTypeTest extends TestCase +{ + /** + * @see junit.framework.TestCase#setUp() + */ + protected void setUp() throws Exception + { + super.setUp(); + } + + /** + * Test method for {@link com.tacitknowledge.util.migration.jdbc.DatabaseType#getDatabaseType()}. + */ + public void testGetDatabaseType() + { + String type = "mysql"; + DatabaseType databseType = new DatabaseType(type); + assertEquals(type, databseType.getDatabaseType()); + } + + /** + * Test method for {@link com.tacitknowledge.util.migration.jdbc.DatabaseType#isMultipleStatementsSupported()}. + */ + public void testOverrideProperties() + { + /* + * Load a known database properties file, and make sure the value + * is what we expect before we override it. + */ + String type = "mysql"; + String dbTypeExpectedValue="true"; + DatabaseType databaseType = new DatabaseType(type); + Properties dbProperties = new Properties(); + + InputStream is = DatabaseType.class.getResourceAsStream(type + ".properties"); + if (is == null) + { + fail("Could not find SQL properties " + + " file for database '" + databaseType + "'; make sure that there " + + " is a '" + databaseType + ".properties' file in package '" + + DatabaseType.class.getPackage().getName() + "'."); + return; + } + + try + { + dbProperties.load(is); + assertEquals(dbTypeExpectedValue, dbProperties.getProperty("supportsMultipleStatements")); + } + catch (IOException e) + { + fail("Could not read SQL properties file for database '" + type + "'."); + return; + } + finally + { + try + { + is.close(); + } + catch (IOException e1) + { + // not important + } + } + + /* + * Load a known migration.properties file and make sure the expected property exists + * and is the value we assume. + */ + Properties migrationProperties = new Properties(); + String migrationExpectedValue = "false"; + InputStream is2 = getClass().getResourceAsStream("/migration.properties"); + if (is2 == null) + { + fail("Could not find migration.properties."); + return; + } + + try + { + migrationProperties.load(is2); + assertEquals(migrationExpectedValue, migrationProperties.getProperty("mysql.supportsMultipleStatements")); + } + catch (IOException e) + { + fail("Could not read migrations properties file."); + return; + } + finally + { + try + { + is2.close(); + } + catch (IOException e1) + { + // not important + } + } + + /* + * Finally, test that the database type properties returns expected override + */ + boolean overrideExpectedValue = false; + assertEquals(overrideExpectedValue, databaseType.isMultipleStatementsSupported()); + } + + +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/jdbc/DistributedJdbcMigrationLauncherFactoryTest.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/jdbc/DistributedJdbcMigrationLauncherFactoryTest.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/jdbc/DistributedJdbcMigrationLauncherFactoryTest.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,336 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration.jdbc; + + +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Properties; + +import com.tacitknowledge.util.migration.*; +import com.tacitknowledge.util.migration.builders.MockBuilder; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.easymock.MockControl; + +import com.tacitknowledge.util.migration.tasks.normal.TestMigrationTask2; +import org.easymock.classextension.EasyMock; +import org.easymock.classextension.IMocksControl; + +import static org.easymock.EasyMock.expect; +import static org.easymock.classextension.EasyMock.createNiceControl; + +/** + * Test the distributed launcher factory + * + * @author Mike Hardy (mike@tacitknowledge.com) + */ +public class DistributedJdbcMigrationLauncherFactoryTest extends MigrationListenerTestBase +{ + /** + * Class logger + */ + private static Log log = LogFactory.getLog(DistributedJdbcMigrationLauncherFactoryTest.class); + + /** + * The launcher we're testing + */ + private DistributedJdbcMigrationLauncher launcher = null; + + /** + * A MigrationContext for us + */ + private TestMigrationContext context = null; + + /** + * constructor that takes a name + * + * @param name of the test to run + */ + public DistributedJdbcMigrationLauncherFactoryTest(String name) + { + super(name); + } + + /** + * @see junit.framework.TestCase#setUp() + */ + protected void setUp() throws Exception + { + super.setUp(); + + log.debug("setting up " + this.getClass().getName()); + + // Make sure we load our test launcher factory, which fakes out the data source context + System.getProperties() + .setProperty("migration.factory", + "com.tacitknowledge.util.migration.jdbc.TestJdbcMigrationLauncherFactory"); + DistributedJdbcMigrationLauncherFactory factory = + new TestDistributedJdbcMigrationLauncherFactory(); + + // Create the launcher (this does configure it as a side-effect) + launcher = + (DistributedJdbcMigrationLauncher) factory.createMigrationLauncher("orchestration"); + + // Make sure we get notification of any migrations + launcher.getMigrationProcess().addListener(this); + context = new TestMigrationContext(); + } + + /** + * @see junit.framework.TestCase#tearDown() + */ + protected void tearDown() throws Exception + { + super.tearDown(); + } + + /** + * For the given launchers, set all it's context's patch info stores as mocks + * that report the given patch level. This method is a helper to get this + * test to pass the DisitributedMigrationProcess::validateControlledSystems() test. + * + * @param launchers Collection of JDBCMigrationLaunchers + * @param levelToReport the patch level the mock should report + * @throws com.tacitknowledge.util.migration.MigrationException + */ + protected void setReportedPatchLevel(Collection launchers, int levelToReport) throws MigrationException + { + for (Iterator launchersIterator = launchers.iterator(); launchersIterator.hasNext(); ) + { + JdbcMigrationLauncher launcher = (JdbcMigrationLauncher) launchersIterator.next(); + for (Iterator it = launcher.getContexts().keySet().iterator(); it.hasNext(); ) + { + MigrationContext ctx = (MigrationContext) it.next(); + + launcher.getContexts().put(ctx, MockBuilder.getPatchInfoStore(levelToReport)); + } + } + } + + + /** + * Test the configuration of the launchers versus a known property file + */ + public void testDistributedLauncherConfiguration() + { + HashMap controlledSystems = + ((DistributedMigrationProcess) launcher.getMigrationProcess()).getControlledSystems(); + assertEquals(3, controlledSystems.size()); + } + + /** + * Make sure that the task loading works correctly + * + * @throws MigrationException if anything goes wrong + */ + public void testDistributedMigrationTaskLoading() throws MigrationException + { + DistributedMigrationProcess process = + (DistributedMigrationProcess) launcher.getMigrationProcess(); + assertEquals(7, process.getMigrationTasks().size()); + assertEquals(7, process.getMigrationTasksWithLaunchers().size()); + } + + /** + * Ensure that overlapping tasks even among sub-launchers are detected + * + * @throws Exception if anything goes wrong + */ + public void testDistributedMigrationTaskValidation() throws Exception + { + MigrationProcess process = launcher.getMigrationProcess(); + process.validateTasks(process.getMigrationTasks()); + + // Make one of the sub-tasks conflict with a sub-task from another launcher + TestMigrationTask2.setPatchLevelOverride(new Integer(3)); + try + { + process.validateTasks(process.getMigrationTasks()); + fail("We should have thrown an exception - " + + "there were overlapping tasks among sub-launchers"); + } + catch (MigrationException me) + { + // we expect this + } + finally + { + // make sure future tests work + TestMigrationTask2.reset(); + } + } + + /** + * Ensure that read-only mode actually works + * + * @throws Exception if anything goes wrong + */ + public void testDistributedReadOnlyMode() throws Exception + { + int currentPatchLevel = 3; + + DistributedMigrationProcess process = (DistributedMigrationProcess) launcher.getMigrationProcess(); + process.validateTasks(process.getMigrationTasks()); + + // need to mock the patch info stores to return the expected patch levels + HashMap controlledSystems = process.getControlledSystems(); + setReportedPatchLevel(controlledSystems.values(), currentPatchLevel); + + // Make it readonly + process.setReadOnly(true); + + // Now do the migrations, and make sure we get the right number of events + try + { + process.doMigrations(MockBuilder.getPatchInfoStore(currentPatchLevel), context); + fail("There should have been an exception - unapplied patches + read-only don't work"); + } + catch (MigrationException me) + { + // we expect this + log.debug("got exception: " + me.getMessage()); + } + + currentPatchLevel = 8; + // need to mock the patch info stores to return the expected patch levels + setReportedPatchLevel(controlledSystems.values(), currentPatchLevel); + + int patches = process.doMigrations(MockBuilder.getPatchInfoStore(currentPatchLevel), context); + assertEquals(0, patches); + assertEquals(0, getMigrationStartedCount()); + assertEquals(0, getMigrationSuccessCount()); + } + + /** + * Make sure we get notified of patch application + * + * @throws Exception if anything goes wrong + */ + public void testDistributedMigrationEvents() throws Exception + { + // There should be five listener on the main process + // 1) the distributed launcher + // 2) this test object + // 3-5) the three sub-launchers + assertEquals(5, launcher.getMigrationProcess().getListeners().size()); + + // The sub-MigrationProcesses should have one listener each - the sub-launcher + HashMap controlledSystems = + ((DistributedMigrationProcess) launcher.getMigrationProcess()).getControlledSystems(); + + for (Iterator controlledSystemIter = controlledSystems.keySet().iterator(); + controlledSystemIter.hasNext(); ) + { + String controlledSystemName = (String) controlledSystemIter.next(); + JdbcMigrationLauncher subLauncher = + (JdbcMigrationLauncher) controlledSystems.get(controlledSystemName); + MigrationProcess subProcess = subLauncher.getMigrationProcess(); + assertEquals(1, subProcess.getListeners().size()); + } + + // Now do the migrations, and make sure we get the right number of events + DistributedMigrationProcess process = (DistributedMigrationProcess) launcher.getMigrationProcess(); + int currentPatchlevel = 3; + setReportedPatchLevel(process.getControlledSystems().values(), currentPatchlevel); + int patches = process.doMigrations(MockBuilder.getPatchInfoStore(currentPatchlevel), context); + assertEquals(4, patches); + assertEquals(4, getMigrationStartedCount()); + assertEquals(4, getMigrationSuccessCount()); + } + + /** + * Make sure we the right patches go in the right spot + * + * @throws Exception if anything goes wrong + */ + public void testDistributedMigrationContextTargetting() throws Exception + { + int currentPatchLevel = 3; + HashMap controlledSystems = + ((DistributedMigrationProcess) launcher.getMigrationProcess()).getControlledSystems(); + // set the patch info store to report the current patch level + setReportedPatchLevel(controlledSystems.values(), currentPatchLevel); + // Now do the migrations, and make sure we get the right number of events + MigrationProcess process = launcher.getMigrationProcess(); + process.setMigrationRunnerStrategy(new OrderedMigrationRunnerStrategy()); + process.doMigrations(MockBuilder.getPatchInfoStore(currentPatchLevel), context); + + // The orders schema has four tasks that should go, make sure they did + JdbcMigrationLauncher ordersLauncher = + (JdbcMigrationLauncher) controlledSystems.get("orders"); + // FIXME need to test multiple contexts + TestDataSourceMigrationContext ordersContext = + (TestDataSourceMigrationContext) + ordersLauncher.getContexts().keySet().iterator().next(); + assertEquals("orders", ordersContext.getSystemName()); + assertTrue(ordersContext.hasExecuted("TestTask1")); + assertTrue(ordersContext.hasExecuted("TestTask2")); + assertTrue(ordersContext.hasExecuted("TestTask3")); + assertTrue(ordersContext.hasExecuted("TestTask4")); + + // The core schema has three tasks that should not go, make sure they exist but did not go + JdbcMigrationLauncher coreLauncher = + (JdbcMigrationLauncher) controlledSystems.get("core"); + // FIXME need to test multiple contexts + TestDataSourceMigrationContext coreContext = + (TestDataSourceMigrationContext) coreLauncher.getContexts().keySet().iterator().next(); + assertEquals(3, coreLauncher.getMigrationProcess().getMigrationTasks().size()); + assertEquals("core", coreContext.getSystemName()); + assertFalse(coreContext.hasExecuted("patch0001_first_patch")); + assertFalse(coreContext.hasExecuted("patch0002_second_patch")); + assertFalse(coreContext.hasExecuted("patch0003_third_patch")); + } + + public void testShouldSetMigrationStrategyToDistributedJdbcMigrationLauncherFromProperties( ) throws MigrationException { + DistributedJdbcMigrationLauncherFactory factory = new DistributedJdbcMigrationLauncherFactory(); + IMocksControl control = createNiceControl(); + DistributedJdbcMigrationLauncher distributedLauncher = control.createMock(DistributedJdbcMigrationLauncher.class); + + String systemName="mysystem"; + String propertyFileName="migration.properties"; + Properties properties = MockBuilder.getPropertiesWithDistributedSystemConfiguration("mysystem", "mystrategy", "orders"); + distributedLauncher.setMigrationStrategy("mystrategy"); + DistributedMigrationProcess migrationProcess=new DistributedMigrationProcess(); + expect(distributedLauncher.getMigrationProcess() ).andReturn(migrationProcess).anyTimes(); + control.replay(); + + factory.configureFromMigrationProperties(distributedLauncher, systemName,properties, propertyFileName); + + control.verify(); + } + + /** + * Get the launcher to use for testing + * + * @return DistributedJdbcMigrationLauncher to use for testing + */ + public DistributedJdbcMigrationLauncher getLauncher() + { + return launcher; + } + + /** + * Set the launcher to test + * + * @param launcher the DistributedJdbcMigrationLauncher to test + */ + public void setLauncher(DistributedJdbcMigrationLauncher launcher) + { + this.launcher = launcher; + } +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/jdbc/JdbcMigrationLauncherFactoryTest.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/jdbc/JdbcMigrationLauncherFactoryTest.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/jdbc/JdbcMigrationLauncherFactoryTest.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,188 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration.jdbc; + +import java.io.IOException; +import java.util.List; +import java.util.Properties; + +import com.tacitknowledge.util.migration.MigrationProcess; +import com.tacitknowledge.util.migration.builders.MockBuilder; +import junit.framework.TestCase; + +import com.tacitknowledge.util.migration.MigrationException; +import com.tacitknowledge.util.migration.MigrationListener; +import com.tacitknowledge.util.migration.test.listeners.TestListener1; +import org.easymock.classextension.IMocksControl; + +import static org.easymock.classextension.EasyMock.*; + +public class JdbcMigrationLauncherFactoryTest extends TestCase +{ + private static final String MIGRATION_STRATEGY = "migrationStrategy"; + /** class under test */ + JdbcMigrationLauncherFactory factory = null; + + protected void setUp() throws Exception + { + super.setUp(); + factory = new DistributedJdbcMigrationLauncherFactory(); + } + + public void testConfigureMigrationListeners() throws IOException + { + String systemName = "test-system"; + Properties properties = new Properties(); + properties.put(systemName + ".listeners", "com.tacitknowledge.util.migration.test.listeners.TestListener1"); + + factory = new JdbcMigrationLauncherFactory(); + + List listeners = null; + try + { + listeners = factory.loadMigrationListeners(systemName, properties); + assertNotNull(listeners); + assertEquals(1, listeners.size()); + TestListener1 listener = (TestListener1) listeners.get(0); + assertTrue(listener.getSystemName().equals(systemName)); + + } catch (MigrationException e) + { + fail("Unexpected exception while loading Migration Listeners for " + systemName); + } + } + + public void testConfigureMigrationListenersDetectsInvalidListeners() + { + String systemName = "test-system"; + Properties properties = new Properties(); + properties.put(systemName + ".listeners", "java.lang.String"); + + factory = new JdbcMigrationLauncherFactory(); + + try + { + factory.loadMigrationListeners(systemName, properties); + fail("An exception reporting an invalid listener class should have occurred"); + } catch (MigrationException e) + { + // expected + assertTrue(e.getCause() instanceof ClassCastException); + } + } + + public void testConfigureMigrationListenersReportsIfItCannotFindListeners() + { + String systemName = "test-system"; + Properties properties = new Properties(); + properties.put(systemName + ".listeners", "com.foo.listener"); + + factory = new JdbcMigrationLauncherFactory(); + + try + { + factory.loadMigrationListeners(systemName, properties); + fail("An exception reporting an invalid listener class should have occurred"); + } catch (MigrationException e) + { + // expected + assertTrue(e.getCause() instanceof ClassNotFoundException); + } + } + + public void testConfigureMigrationListenersLoadsMultipleListeners() + { + String systemName = "test-system"; + Properties properties = new Properties(); + // notice the spaces and the commas. I want to make sure it can parse classnames out correctly regardless of whitespace, etc + properties.put(systemName + ".listeners", " ,com.tacitknowledge.util.migration.test.listeners.TestListener1, com.tacitknowledge.util.migration.test.listeners.TestListener2 , "); + + factory = new JdbcMigrationLauncherFactory(); + + List listeners = null; + try + { + listeners = factory.loadMigrationListeners(systemName, properties); + assertNotNull(listeners); + assertEquals(2, listeners.size()); + assertTrue(listeners.get(0) instanceof MigrationListener); + assertTrue(listeners.get(1) instanceof MigrationListener); + } catch (MigrationException e) + { + fail("Unexpected exception while loading Migration Listeners for " + systemName); + } + } + + public void testConfigureMigrationListenersCanHandleNoConfiguredListeners() + { + String systemName = "test-system"; + Properties properties = new Properties(); + factory = new JdbcMigrationLauncherFactory(); + + List listeners = null; + try + { + listeners = factory.loadMigrationListeners(systemName, properties); + assertNotNull(listeners); + assertEquals(0, listeners.size()); + } catch (MigrationException e) + { + fail("Unexpected exception while loading Migration Listeners for " + systemName); + } + } + + public void testConfigureMigrationListenersCanHandleNullParameters() + { + factory = new JdbcMigrationLauncherFactory(); + + try + { + factory.loadMigrationListeners(null, null); + fail("An exception was expected because the parameters are null"); + } catch (MigrationException e) + { + // expected + } + + } + + public void testConfigureMigrationLauncherFactorySetsMigrationStrategy() throws MigrationException { + factory = new JdbcMigrationLauncherFactory(); + + IMocksControl launcherControl = createNiceControl(); + JdbcMigrationLauncher launcher = launcherControl.createMock(JdbcMigrationLauncher.class); + + launcher.setMigrationStrategy(MIGRATION_STRATEGY); + + MigrationProcess migrationProcess = new MigrationProcess(); + + expect(launcher.getMigrationProcess()).andReturn(migrationProcess); + + launcherControl.replay(); + + String system = "anySystem"; + Properties properties = MockBuilder.getPropertiesWithSystemConfiguration("anySystem",MIGRATION_STRATEGY); + + + + factory.configureFromMigrationProperties(launcher, system, properties); + + + launcherControl.verify(); + + } + +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/jdbc/JdbcMigrationLauncherTest.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/jdbc/JdbcMigrationLauncherTest.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/jdbc/JdbcMigrationLauncherTest.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,490 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration.jdbc; + +import java.io.IOException; +import java.io.InputStream; +import java.sql.Connection; +import java.sql.SQLException; +import java.util.*; + +import com.tacitknowledge.util.migration.*; +import org.easymock.EasyMock; +import org.easymock.IMocksControl; +import org.easymock.MockControl; + +import com.mockrunner.jdbc.PreparedStatementResultSetHandler; +import com.mockrunner.mock.jdbc.MockConnection; +import com.mockrunner.mock.jdbc.MockResultSet; +import com.tacitknowledge.util.migration.jdbc.util.ConnectionWrapperDataSource; + +import static org.easymock.EasyMock.anyInt; +import static org.easymock.EasyMock.eq; +import static org.easymock.EasyMock.expect; +import static org.easymock.classextension.EasyMock.createControl; +import static org.easymock.classextension.EasyMock.createStrictControl; + +/** + * Exercise the data source migration context + * + * @author Mike Hardy (mike@tacitknowledge.com) + */ +public class JdbcMigrationLauncherTest extends MigrationListenerTestBase { + private static final String ORDERED_MIGRATION_STRATEGY = "com.tacitknowledge.util.migration.OrderedMigrationRunnerStrategy"; + /** + * The mock JDBC connection to use during the tests + */ + private MockConnection conn = null; + + /** + * The launcher we're testing + */ + private JdbcMigrationLauncher launcher = null; + + /** + * The JDBCMigrationConteext used for testing + */ + private DataSourceMigrationContext context = new DataSourceMigrationContext(); + private static final String MISSING_PATCH_MIGRATION_STRATEGY = "com.tacitknowledge.util.migration.MissingPatchMigrationRunnerStrategy"; + private IMocksControl rollbackMocksControl; + private MigrationProcess rollbackMigrationProcessMock; + private JdbcMigrationLauncher rollbackLauncher; + private static final int ROLLBACK_LEVEL = 3; + private static final int[] ROLLBACK_LEVELS = new int[]{ROLLBACK_LEVEL}; + private static final int ROLLBACK_EXPECTED = 5; + private static final boolean FORCE_ROLLBACK = false; + + /** + * constructor that takes a name + * + * @param name of the test to run + */ + public JdbcMigrationLauncherTest(String name) { + super(name); + } + + /** + * The Launcher to test + * + * @return JdbcMigrationLauncher to test + */ + public JdbcMigrationLauncher getLauncher() { + return launcher; + } + + /** + * Set the Launcher to test + * + * @param launcher JdbcMigrationLauncher to use for testing + */ + public void setLauncher(JdbcMigrationLauncher launcher) { + this.launcher = launcher; + } + + /** + * @see com.mockrunner.jdbc.JDBCTestCaseAdapter#setUp() + */ + protected void setUp() throws Exception { + super.setUp(); + + // Set up a mock context for some of the tests + conn = getJDBCMockObjectFactory().getMockConnection(); + context = new DataSourceMigrationContext(); + context.setDataSource(new ConnectionWrapperDataSource(conn)); + context.setSystemName("milestone"); + context.setDatabaseType(new DatabaseType("postgres")); + + // Set up a complete launcher from a known properties file for some other tests + + // Make sure we load our test launcher factory, which fakes out the data source context + System.getProperties() + .setProperty("migration.factory", + "com.tacitknowledge.util.migration.jdbc.TestJdbcMigrationLauncherFactory"); + setupMigrationLauncher(MigrationRunnerFactory.DEFAULT_MIGRATION_STRATEGY); + rollbackMocksControl = createControl(); + + int migrationTasksExecuted = 3; + + JdbcMigrationContext contextMock = rollbackMocksControl.createMock(JdbcMigrationContext.class); + rollbackMigrationProcessMock = rollbackMocksControl.createMock(MigrationProcess.class); + PatchInfoStore patchInfoStoreMock = rollbackMocksControl.createMock(PatchInfoStore.class); + Connection connectionMock = rollbackMocksControl.createMock(Connection.class); + + //Dependency Interactions + expect(patchInfoStoreMock.isPatchStoreLocked()).andReturn(false); + expect(patchInfoStoreMock.getPatchLevel()).andReturn(3); + patchInfoStoreMock.lockPatchStore(); + patchInfoStoreMock.unlockPatchStore(); + expect(contextMock.getConnection()).andReturn(connectionMock); + expect(connectionMock.getAutoCommit()).andReturn(true); + connectionMock.setAutoCommit(false); + expect(connectionMock.isClosed()).andReturn(false); + connectionMock.setAutoCommit(true); + expect(rollbackMigrationProcessMock.doRollbacks(patchInfoStoreMock, + ROLLBACK_LEVELS, + contextMock, + FORCE_ROLLBACK)).andReturn(ROLLBACK_EXPECTED); + + + + //Setting Dependencies + rollbackLauncher = new JdbcMigrationLauncher(); + + rollbackMigrationProcessMock.addListener(rollbackLauncher); + rollbackMigrationProcessMock.addMigrationTaskSource(EasyMock.anyObject()); + rollbackMigrationProcessMock.addMigrationTaskSource(EasyMock.anyObject()); + expect(rollbackMigrationProcessMock.doPostPatchMigrations(contextMock)).andReturn(migrationTasksExecuted); + + LinkedHashMap contexts=new LinkedHashMap(); + contexts.put(contextMock, patchInfoStoreMock); + rollbackLauncher.setContexts(contexts); + + } + + private JdbcMigrationLauncher setupMigrationLauncher(final String strategy) throws MigrationException { + JdbcMigrationLauncherFactory factory = new TestJdbcMigrationLauncherFactory(){ + + protected Properties loadProperties( InputStream is ) throws IOException { + Properties props = new Properties(); + props.load(is); + + props.setProperty( "migration.strategy", strategy ); + + return props; + } + }; + + // Create the launcher (this does configure it as a side-effect) + launcher = factory.createMigrationLauncher("catalog"); + + // Make sure we get notification of any migrations + launcher.getMigrationProcess().addListener(this); + + return launcher; + } + + /** + * Verify that the configuration for multi-node made it through + * + * @throws Exception if there is a problem + */ + public void testMultiNodeConfiguration() throws Exception { + Map contexts = launcher.getContexts(); + assertEquals(2, contexts.size()); + Iterator contextIter = contexts.keySet().iterator(); + JdbcMigrationContext context1 = (JdbcMigrationContext) contextIter.next(); + JdbcMigrationContext context2 = (JdbcMigrationContext) contextIter.next(); + assertNotSame(context1, context2); + assertEquals("sybase", context1.getDatabaseType().getDatabaseType()); + assertEquals("postgres", context2.getDatabaseType().getDatabaseType()); + } + + /** + * Test doing migrations with a lock race from a quick cluster + * + * @throws Exception if there is a problem + */ + public void testDoMigrationsWithLockRace() throws Exception { + // Setup enough for the first + PreparedStatementResultSetHandler h = conn.getPreparedStatementResultSetHandler(); + MockResultSet rs = h.createResultSet(); + rs.addRow(new Integer[]{new Integer(0)}); + h.prepareGlobalResultSet(rs); + + + MockControl mockControl = MockControl.createStrictControl(PatchInfoStore.class); + PatchInfoStore patchStore = (PatchInfoStore) mockControl.getMock(); + + // First they see if it is locked, and it is, so they spin + patchStore.isPatchStoreLocked(); + mockControl.setReturnValue(true); + + // Second they see if it is locked again, and it isn't, so they try and fail and spin + patchStore.isPatchStoreLocked(); + mockControl.setReturnValue(false); + patchStore.getPatchLevel(); + mockControl.setReturnValue(0); + patchStore.lockPatchStore(); + mockControl.setThrowable(new IllegalStateException("The table is already locked")); + + // Finally they see if it is locked again, and it isn't, and it works + patchStore.isPatchStoreLocked(); + mockControl.setReturnValue(false); + + + patchStore.getPatchLevel(); + mockControl.setReturnValue(2, MockControl.ONE_OR_MORE); + patchStore.lockPatchStore(); + + IMocksControl migrationRunnerStrategyControl = createStrictControl(); + MigrationRunnerStrategy migrationStrategyMock = migrationRunnerStrategyControl.createMock(MigrationRunnerStrategy.class); + expect(migrationStrategyMock.shouldMigrationRun(anyInt(), eq(patchStore))).andReturn(true).anyTimes(); + + patchStore.updatePatchLevel(4); + patchStore.updatePatchLevel(5); + patchStore.updatePatchLevel(6); + patchStore.updatePatchLevel(7); + patchStore.unlockPatchStore(); + + mockControl.replay(); + migrationRunnerStrategyControl.replay(); + + TestJdbcMigrationLauncher testLauncher = new TestJdbcMigrationLauncher(context); + testLauncher.getMigrationProcess().setMigrationRunnerStrategy(migrationStrategyMock); + testLauncher.setLockPollMillis(0); + testLauncher.setLockPollRetries(4); + testLauncher.setIgnoreMigrationSuccessfulEvents(false); + testLauncher.setPatchStore(patchStore); + testLauncher.setPatchPath("com.tacitknowledge.util.migration.tasks.normal"); + testLauncher.doMigrations(); + mockControl.verify(); + } + + /** + * Test doing migrations with a lock override + * + * @throws Exception if there is a problem + */ + public void testLockOverride() throws Exception { + // Setup enough for the first + PreparedStatementResultSetHandler h = conn.getPreparedStatementResultSetHandler(); + MockResultSet rs = h.createResultSet(); + rs.addRow(new Integer[]{new Integer(0)}); + h.prepareGlobalResultSet(rs); + + + MockControl mockControl = MockControl.createStrictControl(PatchInfoStore.class); + PatchInfoStore patchStore = (PatchInfoStore) mockControl.getMock(); + + // First they see if it is locked three times, and it is, so they spin + patchStore.isPatchStoreLocked(); + mockControl.setReturnValue(true); + patchStore.isPatchStoreLocked(); + mockControl.setReturnValue(true); + patchStore.isPatchStoreLocked(); + mockControl.setReturnValue(true); + patchStore.isPatchStoreLocked(); + mockControl.setReturnValue(true); + + // after the third time, they unlock it + patchStore.unlockPatchStore(); + + // now the lock succeeds + patchStore.isPatchStoreLocked(); + mockControl.setReturnValue(false); + patchStore.getPatchLevel(); + mockControl.setReturnValue(2); + patchStore.lockPatchStore(); + + IMocksControl migrationRunnerStrategyControl = createStrictControl(); + MigrationRunnerStrategy migrationStrategyMock = migrationRunnerStrategyControl.createMock(MigrationRunnerStrategy.class); + expect(migrationStrategyMock.shouldMigrationRun(anyInt(), eq(patchStore))).andReturn(true).anyTimes(); + + patchStore.updatePatchLevel(4); + patchStore.updatePatchLevel(5); + patchStore.updatePatchLevel(6); + patchStore.updatePatchLevel(7); + patchStore.unlockPatchStore(); + + TestJdbcMigrationLauncher testLauncher = new TestJdbcMigrationLauncher(context); + testLauncher.getMigrationProcess().setMigrationRunnerStrategy(migrationStrategyMock); + testLauncher.setLockPollMillis(0); + testLauncher.setLockPollRetries(3); + testLauncher.setIgnoreMigrationSuccessfulEvents(false); + testLauncher.setPatchStore(patchStore); + testLauncher.setPatchPath("com.tacitknowledge.util.migration.tasks.normal"); + + mockControl.replay(); + migrationRunnerStrategyControl.replay(); + testLauncher.doMigrations(); + mockControl.verify(); + } + + /** + * Test that when a migrationSuccessful event fires. If the + * 'successful' patch level is less than the current patch level + * for the context's that is updating, then do not blow away the + * existing patch level. + * This case occurs when running patching in force sync mode. + * Example: + * node 1 is at patch level 2 + * node 2 is out of sync (or patch level zero). + * When node 2 is forcesync'ed, node2 applies patch 1, + * it succeeds and all migration listeners are notified. + * If we do not protect the nodes, then node 1 would have it's patch level set + * to 1. Then when patch 2 is executed, node 1 now thinks it needs to apply + * it, and chaos ensues. + * + * @throws MigrationException + */ + public void testMigrationSuccessfulDoesNotOverWritePatchLevel() throws MigrationException { + // not using the 'setup' method's initilization because I really just want + // to unit test the migration successful method. + launcher = new JdbcMigrationLauncher(); + + int migrationSuccessfulPatchLevel = 1; + int node1PatchLevel = 2; + int node2PatchLevel = 0; + + // create mocks + MockControl node1ContextControl = MockControl.createControl(JdbcMigrationContext.class); + MockControl node2ContextControl = MockControl.createControl(JdbcMigrationContext.class); + JdbcMigrationContext node1Context = (JdbcMigrationContext) node1ContextControl.getMock(); + JdbcMigrationContext node2Context = (JdbcMigrationContext) node2ContextControl.getMock(); + + MockControl node1PatchInfoStoreControl = MockControl.createControl(PatchInfoStore.class); + MockControl node2PatchInfoStoreControl = MockControl.createControl(PatchInfoStore.class); + PatchInfoStore node1PatchInfoStore = (PatchInfoStore) node1PatchInfoStoreControl.getMock(); + PatchInfoStore node2PatchInfoStore = (PatchInfoStore) node2PatchInfoStoreControl.getMock(); + + LinkedHashMap contexts = new LinkedHashMap(); + contexts.put(node1Context, node1PatchInfoStore); + contexts.put(node2Context, node2PatchInfoStore); + launcher.setContexts(contexts); + + MockControl taskControl = MockControl.createControl(RollbackableMigrationTask.class); + MockControl contextControl = MockControl.createControl(MigrationContext.class); + RollbackableMigrationTask task = (RollbackableMigrationTask) taskControl.getMock(); + MigrationContext context = (MigrationContext) contextControl.getMock(); + + // set expectations + // the migration successful event is for patch level 1 + task.getLevel(); + taskControl.setDefaultReturnValue(new Integer(migrationSuccessfulPatchLevel)); + task.getName(); + taskControl.setDefaultReturnValue("patch001_test.sql"); + + taskControl.replay(); + + IMocksControl migrationStrategyControl = createStrictControl(); + MigrationRunnerStrategy migrationStrategyMock = migrationStrategyControl.createMock(MigrationRunnerStrategy.class); + + // node1 is at patchlevel 2 + node1PatchInfoStore.getPatchLevel(); + node1PatchInfoStoreControl.setDefaultReturnValue(node1PatchLevel); + node1PatchInfoStoreControl.replay(); + expect(migrationStrategyMock.shouldMigrationRun(1, node1PatchInfoStore)).andReturn(false); + + // node2 is at patchlevel 1 + node2PatchInfoStore.getPatchLevel(); + node2PatchInfoStoreControl.setDefaultReturnValue(node2PatchLevel); + expect(migrationStrategyMock.shouldMigrationRun(1, node2PatchInfoStore)).andReturn(true); + + + node2PatchInfoStore.updatePatchLevel(migrationSuccessfulPatchLevel); + node2PatchInfoStoreControl.setVoidCallable(); + + node2PatchInfoStoreControl.replay(); + migrationStrategyControl.replay(); + + launcher.getMigrationProcess().setMigrationRunnerStrategy(migrationStrategyMock); + + // test and validate + launcher.migrationSuccessful(task, context); + taskControl.verify(); + node1PatchInfoStoreControl.verify(); + node2PatchInfoStoreControl.verify(); + } + + + public void testGetTheMigrationStrategyAlreadyConfigured() throws MigrationException { + setupMigrationLauncher(MISSING_PATCH_MIGRATION_STRATEGY); + + String currentMigrationStrategy = launcher.getMigrationStrategy(); + assertEquals("Current migration strategy should be '" + MISSING_PATCH_MIGRATION_STRATEGY + "'", + MISSING_PATCH_MIGRATION_STRATEGY, currentMigrationStrategy); + + } + + public void testGetOrderedAsDefaultStrategyWhenEmptyStrategyIsConfigured() throws MigrationException { + setupMigrationLauncher(" "); + + MigrationProcess process = launcher.getMigrationProcess(); + + assertTrue("Should be instance of default object", process.getMigrationRunnerStrategy() instanceof OrderedMigrationRunnerStrategy); + } + + public void testNotSettingStrategyGeneratesDefaultMigrationRunner() throws MigrationException { + setupMigrationLauncher(""); + + MigrationProcess process = launcher.getMigrationProcess(); + + assertTrue("Should be instance of default object", process.getMigrationRunnerStrategy() instanceof OrderedMigrationRunnerStrategy); + + } + + + public void testSettingOrderedStrategyGeneratesDefaultMigrationRunner() throws MigrationException { + setupMigrationLauncher(ORDERED_MIGRATION_STRATEGY); + + MigrationProcess process = launcher.getMigrationProcess(); + + assertTrue("Should be instance of default object", process.getMigrationRunnerStrategy() instanceof OrderedMigrationRunnerStrategy); + + } + + public void testSettingFullClassNameForStrategyGeneratesMigrationProcessAccordingToTheClasNameReceived() throws MigrationException { + setupMigrationLauncher(MISSING_PATCH_MIGRATION_STRATEGY); + + MigrationProcess process = launcher.getMigrationProcess(); + + assertTrue("Should be instance of defined object", process.getMigrationRunnerStrategy() instanceof MissingPatchMigrationRunnerStrategy); + } + + public void testSettingFullClassNameWithTrailingSpacesForStrategyGeneratesMigrationProcessAccordingToTheClasNameReceived() throws MigrationException { + setupMigrationLauncher(" " +MISSING_PATCH_MIGRATION_STRATEGY + " "); + + MigrationProcess process = launcher.getMigrationProcess(); + + assertTrue("Should be instance of defined object", process.getMigrationRunnerStrategy() instanceof MissingPatchMigrationRunnerStrategy); + } + + public void testSettingUnrecognizableStrategyThrowsIllegalArgumentException() throws MigrationException { + + try { + setupMigrationLauncher("unrecognized.strategy"); + fail("It should have thrown an IllegalArgumentException for an unrecognized strategy"); + } catch (IllegalArgumentException ae) { + //expected + } + + } + + public void testDoRollbacksActionWithForceRollBackParameter() throws MigrationException, SQLException { + + rollbackMocksControl.replay(); + rollbackLauncher.setMigrationProcess(rollbackMigrationProcessMock); + + + int actual = rollbackLauncher.doRollbacks(ROLLBACK_LEVELS, FORCE_ROLLBACK ); + + assertEquals("Expected rollbacks should be 5", ROLLBACK_EXPECTED, actual); + rollbackMocksControl.verify(); + } + + + public void testDoRollbacksActionWithoutForceRollbackParameter() throws MigrationException, SQLException { + + rollbackMocksControl.replay(); + rollbackLauncher.setMigrationProcess(rollbackMigrationProcessMock); + + int actual = rollbackLauncher.doRollbacks(ROLLBACK_LEVELS); + + assertEquals("Expected rollbacks should be 5", ROLLBACK_EXPECTED, actual); + rollbackMocksControl.verify(); + } + +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/jdbc/MockDatabaseType.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/jdbc/MockDatabaseType.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/jdbc/MockDatabaseType.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,55 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration.jdbc; + +/** + * MockDatabaseType since DatabaseType is not interface based and + * can't mock it via easymock (without upgrading version to use the + * class extension lib) + * + * @author Alex Soto (apsoto@gmail.com) + */ +class MockDatabaseType extends DatabaseType +{ + /** does database type support multipe sql statements per stmt.execute() */ + private boolean multipleStatementsSupported; + + /** + * constructor + * @param databaseType set the type + */ + public MockDatabaseType(String databaseType) + { + super(databaseType); + } + + /** {@inheritDoc} */ + public boolean isMultipleStatementsSupported() + { + return multipleStatementsSupported; + } + + /** + * simple setter + * @param multipleStatementsSupported the value to set + */ + public void setMultipleStatementsSupported(boolean multipleStatementsSupported) + { + this.multipleStatementsSupported = multipleStatementsSupported; + } + +} + Index: 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/jdbc/PatchTableTest.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/jdbc/PatchTableTest.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/jdbc/PatchTableTest.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,409 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration.jdbc; + +import java.sql.SQLException; +import java.util.HashSet; +import java.util.Set; + +import org.easymock.MockControl; + +import com.mockrunner.jdbc.JDBCTestCaseAdapter; +import com.mockrunner.jdbc.PreparedStatementResultSetHandler; +import com.mockrunner.mock.jdbc.MockConnection; +import com.mockrunner.mock.jdbc.MockResultSet; +import com.tacitknowledge.util.migration.MigrationException; +import com.tacitknowledge.util.migration.jdbc.util.ConnectionWrapperDataSource; + +/** + * Out-of-container tests the PatchTable class using a + * mock JDBC driver. + * + * @author Scott Askew (scott@tacitknowledge.com) + */ +public class PatchTableTest extends JDBCTestCaseAdapter +{ + /** + * The PatchTable to test + */ + private PatchTable table = null; + + /** + * The mock JDBC connection to use during the tests + */ + private MockConnection conn = null; + + /** + * The JDBCMigrationConteext used for testing + */ + private DataSourceMigrationContext context = new DataSourceMigrationContext(); + + /** Used to specify different statements in the tests */ + private PreparedStatementResultSetHandler handler = null; + private MockControl contextControl; + private JdbcMigrationContext mockContext; + + + /** + * Constructor for PatchTableTest. + * + * @param name the name of the test to run + */ + public PatchTableTest(String name) + { + super(name); + } + + /** + * {@inheritDoc} + */ + protected void setUp() throws Exception + { + super.setUp(); + + conn = getJDBCMockObjectFactory().getMockConnection(); + + context = new DataSourceMigrationContext(); + context.setDataSource(new ConnectionWrapperDataSource(conn)); + context.setSystemName("milestone"); + context.setDatabaseType(new DatabaseType("hsqldb")); + + table = new PatchTable(context); + contextControl = MockControl.createControl(JdbcMigrationContext.class); + mockContext = (JdbcMigrationContext) contextControl.getMock(); + } + + /** + * Ensures that the class throws an IllegalArgumentException + * if an unknown database type is specified in the constructor. + */ + public void testUnknownDatabaseType() + { + try + { + context.setDatabaseType(new DatabaseType("bad-database-type")); + new PatchTable(context); + fail("Expected IllegalArgumentException because of unknown database type"); + } + catch (IllegalArgumentException e) + { + // Expected + } + } + + /** + * Validates the automatic creation of the patches table. + * + * @throws Exception if an unexpected error occurs + */ + public void testCreatePatchesTable() throws Exception + { + // Test-specific setup + PreparedStatementResultSetHandler h = conn.getPreparedStatementResultSetHandler(); + h.prepareThrowsSQLException(table.getSql("level.table.exists")); + + table.createPatchStoreIfNeeded(); + + commonVerifications(); + verifyCommitted(); + verifyPreparedStatementParameter(0, 1, "milestone"); + verifySQLStatementExecuted(table.getSql("patches.create")); + } + + /** + * Tests when trying to get a connection to the database to create the patch table fails. + * @throws SQLException shouldn't occur, only declared to make the code below more readable. + */ + public void testCreatePatchesTableWithoutConnection() throws SQLException + { + + // setup mock calls + mockContext.getDatabaseType(); + contextControl.setReturnValue(new DatabaseType("postgres"), MockControl.ONE_OR_MORE); + + mockContext.getConnection(); + contextControl.setThrowable(new SQLException("An exception during getConnection")); + contextControl.replay(); + + table = new PatchTable(mockContext); + try + { + table.createPatchStoreIfNeeded(); + fail("Expected a MigrationException"); + } + catch (MigrationException e) + { + contextControl.verify(); + } + } + + /** + * Validates that the system recognizes an existing patches table. + * + * @throws Exception if an unexpected error occurs + */ + public void testVerifyPatchesTable() throws Exception + { + // Test-specific setup + handler = conn.getPreparedStatementResultSetHandler(); + MockResultSet rs = handler.createResultSet(); + handler.prepareGlobalResultSet(rs); + rs.addRow(new Integer[] {new Integer(13)}); + + table.createPatchStoreIfNeeded(); + + commonVerifications(); + verifyNotCommitted(); + verifyPreparedStatementParameter(0, 1, "milestone"); + verifyPreparedStatementNotPresent(table.getSql("patches.create")); + } + + /** + * Validates that getPatchLevel works on an existing system. + * + * @throws Exception if an unexpected error occurs + */ + public void testGetPatchLevel() throws Exception + { + // Test-specific setup + handler = conn.getPreparedStatementResultSetHandler(); + MockResultSet rs = handler.createResultSet(); + rs.addRow(new Integer[]{new Integer(13)}); + handler.prepareGlobalResultSet(rs); + + int i = table.getPatchLevel(); + + assertEquals(13, i); + commonVerifications(); + verifyNotCommitted(); + verifyPreparedStatementParameter(1, 1, "milestone"); + verifyPreparedStatementNotPresent(table.getSql("level.create")); + } + + /** + * Validates that getPatchLevel works on a new system. + * + * @throws Exception if an unexpected error occurs + */ + public void testGetPatchLevelFirstTime() throws Exception + { + // Test-specific setup + handler = conn.getPreparedStatementResultSetHandler(); + MockResultSet rs = handler.createResultSet(); + // empty result set + handler.prepareResultSet(table.getSql("level.read"), rs); + handler.prepareThrowsSQLException(table.getSql("level.table.exists")); + + int i = table.getPatchLevel(); + + assertEquals(0, i); + commonVerifications(); + verifyPreparedStatementPresent(table.getSql("level.create")); + } + + /** + * Validates that the patch level can be updated. + * + * @throws Exception if an unexpected error occurs + */ + public void testUpdatePatchLevel() throws Exception + { + handler = conn.getPreparedStatementResultSetHandler(); + MockResultSet rs = handler.createResultSet(); + rs.addRow(new Integer[]{new Integer(12)}); + handler.prepareResultSet(table.getSql("level.read"), rs, new String[]{"milestone"}); + + table.updatePatchLevel(13); + + verifyPreparedStatementParameter(table.getSql("level.update"), 1, new Integer(13)); + verifyPreparedStatementParameter(table.getSql("level.update"), 2, "milestone"); + commonVerifications(); + verifyCommitted(); + } + + /** + * Validates that isPatchTableLocked works when no lock exists. + * + * @throws Exception if an unexpected error occurs + */ + public void testIsPatchTableNotLocked() throws Exception + { + // Test-specific setup + // Return a non-empty set in response to the patch lock query + handler = conn.getPreparedStatementResultSetHandler(); + MockResultSet rs = handler.createResultSet(); + rs.addRow(new String[]{"F"}); + handler.prepareResultSet(table.getSql("lock.read"), rs, new String[]{"milestone", "milestone"}); + + assertFalse(table.isPatchStoreLocked()); + commonVerifications(); + verifyNotCommitted(); + } + + /** + * Validates that isPatchTableLocked works when a lock already exists. + * + * @throws Exception if an unexpected error occurs + */ + public void testIsPatchTableLocked() throws Exception + { + // Test-specific setup + // Return a non-empty set in response to the patch lock query + handler = conn.getPreparedStatementResultSetHandler(); + MockResultSet rs = handler.createResultSet(); + rs.addRow(new String[]{"T"}); + handler.prepareResultSet(table.getSql("lock.read"), rs, new String[]{"milestone", "milestone"}); + + assertTrue(table.isPatchStoreLocked()); + commonVerifications(); + verifyNotCommitted(); + } + + /** + * Validates that an IllegalStateException is thrown when trying + * to lock an already locked patches table. + * + * @throws Exception if an unexpected error occurs + */ + public void testLockPatchTableWhenAlreadyLocked() throws Exception + { + // Test-specific setup + // Return a non-empty set in response to the patch lock query + handler = conn.getPreparedStatementResultSetHandler(); + handler.prepareUpdateCount(table.getSql("lock.obtain"), 0, new String[] {"milestone", "milestone"}); + + try + { + table.lockPatchStore(); + fail("Expected an IllegalStateException since a lock already exists."); + } + catch (IllegalStateException e) + { + // Expected + } + + verifyPreparedStatementParameter(table.getSql("lock.obtain"), 1, "milestone"); + verifyPreparedStatementParameter(table.getSql("lock.obtain"), 2, "milestone"); + commonVerifications(); + verifyCommitted(); + } + + /** + * Validates that the patches table can be locked as long as no other lock + * is in place. + * + * @throws Exception if an unexpected error occurs + */ + public void testLockPatchTableWhenNotAlreadyLocked() throws Exception + { + // Test-specific setup + // Return an empty set in response to the patch lock query + handler = conn.getPreparedStatementResultSetHandler(); + MockResultSet rs = handler.createResultSet(); + handler.prepareUpdateCount(table.getSql("lock.obtain"), 1, new String[] {"milestone", "milestone"}); + + table.lockPatchStore(); + verifyPreparedStatementParameter(table.getSql("lock.obtain"), 1, "milestone"); + verifyPreparedStatementParameter(table.getSql("lock.obtain"), 2, "milestone"); + commonVerifications(); + verifyCommitted(); + } + + /** + * Validates that the patches table lock can be removed. + * + * @throws Exception if an unexpected error occurs + */ + public void testUnlockPatchTable() throws Exception + { + table.unlockPatchStore(); + + verifyPreparedStatementParameter(table.getSql("lock.release"), 1, "milestone"); + commonVerifications(); + verifyCommitted(); + } + + public void testIsPatchApplied() throws MigrationException + { + handler = conn.getPreparedStatementResultSetHandler(); + MockResultSet rs = handler.createResultSet(); + rs.addRow(new Integer[]{new Integer(3)}); + handler.prepareGlobalResultSet(rs); + + assertEquals(true, table.isPatchApplied(3)); + commonVerifications(); + verifyPreparedStatementPresent(table.getSql("level.exists")); + + } + + public void testMigrationExceptionIsThrownIfSQLExceptionHappens() throws SQLException { + + mockContext.getDatabaseType(); + contextControl.setReturnValue(new DatabaseType("postgres"), MockControl.ONE_OR_MORE); + + mockContext.getConnection(); + contextControl.setThrowable(new SQLException("An exception during getConnection")); + contextControl.replay(); + + table = new PatchTable(mockContext); + + try { + + table.isPatchApplied(3); + fail("MigrationException should have happened if SQLException"); + + } catch (MigrationException e) { + + //Expected + } + + } + + public void testIsPatchAppliedWithMissingLevel() throws MigrationException + { + handler = conn.getPreparedStatementResultSetHandler(); + MockResultSet rs = handler.createResultSet(); + handler.prepareGlobalResultSet(rs); + + assertEquals(false, table.isPatchApplied(3)); + commonVerifications(); + verifyPreparedStatementPresent(table.getSql("level.exists")); + } + + public void testPatchRetrievesSetWithPatchesApplied () throws SQLException, MigrationException { + handler = conn.getPreparedStatementResultSetHandler(); + MockResultSet rs = handler.createResultSet(); + rs.addColumn("patch_level", new Object[]{1, 2}); + handler.prepareGlobalResultSet(rs); + + Set expected = new HashSet(); + expected.add(1); + expected.add(2); + assertEquals(expected, table.getPatchesApplied()); + commonVerifications(); + verifyPreparedStatementPresent(table.getSql("patches.all")); + } + + + private void commonVerifications() + { + verifyAllResultSetsClosed(); + verifyAllStatementsClosed(); + verifyConnectionClosed(); + } + + +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/jdbc/SqlScriptMigrationTaskSourceTest.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/jdbc/SqlScriptMigrationTaskSourceTest.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/jdbc/SqlScriptMigrationTaskSourceTest.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,94 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration.jdbc; + +import java.util.Iterator; +import java.util.List; + +import junit.framework.TestCase; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import com.tacitknowledge.util.migration.MigrationException; +import com.tacitknowledge.util.migration.RollbackableMigrationTask; + +/** + * Exercise the SqlScriptMigrationTaskSource + * + * @author Mike Hardy (mike@tacitknowledge.com) + */ +public class SqlScriptMigrationTaskSourceTest extends TestCase +{ + /** Class logger */ + private static Log log = LogFactory.getLog(SqlScriptMigrationTaskSource.class); + + /** + * Test loading up all the scripts in our test package + */ + public void testScriptLoad() + { + SqlScriptMigrationTaskSource source = new SqlScriptMigrationTaskSource(); + List tasks = null; + try + { + tasks = source.getMigrationTasks(this.getClass().getPackage().getName() + ".test"); + } + catch (MigrationException me) + { + log.info("Unexpectedly caught: "+ me); + fail("There shouldn't have been a problem loading the tasks: "+ me); + } + + // There are 3 scripts in our package of scripts, make sure they are all here + assertEquals(3, tasks.size()); + } + + /** + * Test that a Migration which does not have a rollback script correctly + * returns false for isRollbackSupported. + */ + public void testNonRollbackableScript() + { + SqlScriptMigrationTaskSource source = new SqlScriptMigrationTaskSource(); + List tasks = null; + RollbackableMigrationTask task = null; + try + { + tasks = source.getMigrationTasks(this.getClass().getPackage().getName() + ".test"); + + for(Iterator i=tasks.iterator(); i.hasNext(); ) + { + //patch with ID 2 has no rollback + task = (RollbackableMigrationTask) i.next(); + if(task.getLevel().equals(Integer.valueOf(2))) + { + assertFalse(task.isRollbackSupported()); + } + else + { + assertTrue(task.isRollbackSupported()); + } + } + + } + catch (MigrationException me) + { + log.info("Unexpectedly caught: "+ me); + fail("There shouldn't have been a problem loading the tasks: "+ me); + } + } +} \ No newline at end of file Index: 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/jdbc/SqlScriptMigrationTaskTest.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/jdbc/SqlScriptMigrationTaskTest.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/jdbc/SqlScriptMigrationTaskTest.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,330 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration.jdbc; + +import java.io.IOException; +import java.io.InputStream; +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Iterator; +import java.util.List; + +import javax.sql.DataSource; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.easymock.MockControl; + +import com.mockrunner.jdbc.JDBCTestCaseAdapter; +import com.mockrunner.mock.jdbc.MockConnection; +import com.tacitknowledge.util.migration.MigrationException; +import com.tacitknowledge.util.migration.MigrationTaskSupport; +import com.tacitknowledge.util.migration.RollbackableMigrationTask; +import com.tacitknowledge.util.migration.jdbc.util.ConnectionWrapperDataSource; + +/** + * Tests the SqlScriptMigrationTask. + * + * @author Scott Askew (scott@tacitknowledge.com) + */ +public class SqlScriptMigrationTaskTest extends JDBCTestCaseAdapter +{ + private static Log log = LogFactory.getLog(SqlScriptMigrationTaskTest.class); + /** + * The task to test. + */ + private SqlScriptMigrationTask task = null; + + /** + * The JDBCMigrationConteext used for testing + */ + private DataSourceMigrationContext context = new DataSourceMigrationContext(); + + /** + * {@inheritDoc} + */ + protected void setUp() throws Exception + { + super.setUp(); + + MockConnection conn = getJDBCMockObjectFactory().getMockConnection(); + + context = new DataSourceMigrationContext(); + context.setDataSource(new ConnectionWrapperDataSource(conn)); + context.setSystemName("milestone"); + context.setDatabaseType(new DatabaseType("postgres")); + } + + /** + * Test doing a migration (with the connection silently succeeding) + * + * @throws IOException + * if the test patch file doesn't load correctly + */ + public void testMigrate() throws IOException + { + InputStream is = getClass().getResourceAsStream( + "test/patch0003_third_patch.sql"); + task = new SqlScriptMigrationTask("test", 1, is); + is.close(); + + try + { + task.migrate(context); + } catch (MigrationException me) + { + log.info("Unexpected exception", me); + fail("unexpected exception"); + } + } + + /** + * Ensures that a rollback script is correctly read from disk and then executed. + * + * @throws IOException + */ + public void testRollback() throws IOException + { + SqlScriptMigrationTaskSource source = new SqlScriptMigrationTaskSource(); + List tasks = null; + try + { + tasks = source.getMigrationTasks(this.getClass().getPackage() + .getName() + + ".test"); + MigrationTaskSupport task = (MigrationTaskSupport)tasks.get(2); + + if(task.isRollbackSupported()) + task.down(context); + else + fail("Rollback should be supported for this task"); + } catch (Exception e) + { + log.info("Unexpected exception", e); + fail(); + } + } + + /** + * Ensures that isRollbackSupported returns false if there is no + * rollback script. + * + * @throws IOException + */ + public void testIsRollbackSupported() throws IOException + { + SqlScriptMigrationTaskSource source = new SqlScriptMigrationTaskSource(); + List tasks = null; + try + { + tasks = source.getMigrationTasks(this.getClass().getPackage() + .getName() + + ".test"); + + for(Iterator i=tasks.iterator(); i.hasNext();) { + //patch with ID 2 has no rollback + RollbackableMigrationTask rollbackableTask = (RollbackableMigrationTask) i.next(); + if(rollbackableTask.getLevel().equals(Integer.valueOf(2))) + assertFalse(rollbackableTask.isRollbackSupported()); + else + assertTrue(rollbackableTask.isRollbackSupported()); + + } + + } catch (Exception e) + { + log.info("Unexpected exception", e); + fail(); + } + } + + /** + * Ensures that the task can correctly parse multiple SQL statements from a + * single file, with embedded comments. + * + * @throws IOException + * if an unexpected error occurs while attempting to read + * the test SQL patch file; it's a system resource, so this + * shouldn't happen + */ + public void testParsingMultipleStatement() throws IOException + { + InputStream is = getClass().getResourceAsStream( + "test/patch0001.sql"); + task = new SqlScriptMigrationTask("test", 1, is); + is.close(); + + context.setDatabaseType(new DatabaseType("oracle")); + List l = task.getSqlStatements(context); + assertEquals(3, l.size()); + assertEquals( + "insert into user_role_assoc (user_role_id, application_user_id, " + + "role_code, project_id) \n\t\t\tvalues (nextval('role_id_seq'),2, 'SYSA', 3)", + l.get(0).toString()); + assertEquals( + "insert into user_role_assoc (user_role_id, application_user_id, " + + "role_code, project_id) \n\t\t\tvalues (nextval('role_id_seq'),3, 'SYSA', 3)", + l.get(1).toString()); + assertEquals( + "insert into user_role_assoc (user_role_id, application_user_id, " + + "role_code, project_id) \n\t\t\tvalues (nextval('role_--id_seq;'),4, 'SYSA', 3)", + l.get(2).toString()); + + is = getClass().getResourceAsStream("test/patch0002_second_patch.sql"); + task = new SqlScriptMigrationTask("test", 1, is); + is.close(); + + l = task.getSqlStatements(context); + assertEquals(1, l.size()); + assertEquals( + "insert into user_role_assoc (user_role_id, application_user_id, " + + "role_code, project_id) \n\t\t\tvalues (nextval('role_id_seq'),2, 'SYSA', 3)", + l.get(0).toString()); + } + + /** + * Make sure that if we can do one big statement, it correctly does one big + * statement + * + * @exception IOException + * if an unexpected error happens while reading test SQL + */ + public void testParsingSingleStatement() throws IOException + { + InputStream is = getClass().getResourceAsStream( + "test/patch0003_third_patch.sql"); + task = new SqlScriptMigrationTask("test", 1, is); + is.close(); + + List l = task.getSqlStatements(context); + assertEquals(1, l.size()); + assertEquals("select * from dual;\nselect * from dual;\n", l.get(0) + .toString()); + } + + /** + * See that the name and toString are the same, given a file name to load + * + * @exception IOException + * if an unexpected error happens while reading test SQL + */ + public void testTaskName() throws IOException + { + InputStream is = getClass().getResourceAsStream( + "test/patch0003_third_patch.sql"); + task = new SqlScriptMigrationTask("patch0003_third_patch", 1, is); + is.close(); + assertEquals("patch0003_third_patch", task.toString()); + } + + /** + * Tests that sybase tsql statements are parsed correctly + * + * @throws IOException + * if an unexpected error occurs. + */ + public void testParsesSybaseTSql() throws IOException + { + InputStream is = getClass().getResourceAsStream("test/sybase_tsql.sql"); + assertNotNull(is); + task = new SqlScriptMigrationTask("sybase_tsql.sql", 1, is); + + MockDatabaseType dbType = new MockDatabaseType("sybase"); + dbType.setMultipleStatementsSupported(false); + context.setDatabaseType(dbType); + List statements = task.getSqlStatements(context); + assertEquals(8, statements.size()); + } + + /** + * Test that sybase database patches are committed when illegal multi + * statement transaction commands are used. + * + * @throws IOException + * if an unexpected error occurs + * @throws MigrationException + * if an unexpected error occurs + * @throws SQLException + * if an unexpected error occurs + */ + public void testSybasePatchesCommitsOnEveryStatement() throws IOException, + MigrationException, SQLException + { + InputStream is = getClass().getResourceAsStream("test/sybase_tsql.sql"); + assertNotNull(is); + task = new SqlScriptMigrationTask("sybase_tsql.sql", 1, is); + + MockDatabaseType dbType = new MockDatabaseType("sybase"); + dbType.setMultipleStatementsSupported(false); + context.setDatabaseType(dbType); + int numStatements = task.getSqlStatements(context).size(); + + // setup mocks to verify commits are called + MockControl dataSourceControl = MockControl + .createControl(DataSource.class); + DataSource dataSource = (DataSource) dataSourceControl.getMock(); + context.setDataSource(dataSource); + + MockControl connectionControl = MockControl + .createControl(Connection.class); + Connection connection = (Connection) connectionControl.getMock(); + + dataSourceControl.expectAndReturn(dataSource.getConnection(), + connection); + + MockControl statementControl = MockControl + .createControl(Statement.class); + Statement statement = (Statement) statementControl.getMock(); + statement.execute(""); + statementControl.setMatcher(MockControl.ALWAYS_MATCHER); + statementControl.setReturnValue(true, MockControl.ONE_OR_MORE); + statementControl.expectAndReturn(statement.isClosed(), false, MockControl.ONE_OR_MORE); + statement.close(); + statementControl.setVoidCallable(MockControl.ONE_OR_MORE); + + connectionControl.expectAndReturn(connection.isClosed(), false, + MockControl.ONE_OR_MORE); + connectionControl.expectAndReturn(connection.createStatement(), + statement, numStatements); + connectionControl.expectAndReturn(connection.getAutoCommit(), false, + MockControl.ONE_OR_MORE); + connection.commit(); + /* + * Magic Number 4 derived from the assumption that the fixture sql + * contains only one statement that is not allowed in a multi statement + * transaction: commit at beginning of migrate method commit prior to + * running the command not allowed in multi statement transaction to + * clear the transaction state. commit after running the multi statement + * transaction to clear transaction state for upcoming statements. + * commit at end of migrate method once all statements executed. + * + * Therefore, if you add more illegal statements to the fixture, add 2 + * more commit call's for each illegal statement. + */ + connectionControl.setVoidCallable(4); + + dataSourceControl.replay(); + connectionControl.replay(); + statementControl.replay(); + + // run tests + task.migrate(context); + dataSourceControl.verify(); + connectionControl.verify(); + } + +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/jdbc/StandaloneMigrationLauncherTest.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/jdbc/StandaloneMigrationLauncherTest.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/jdbc/StandaloneMigrationLauncherTest.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,69 @@ +package com.tacitknowledge.util.migration.jdbc; + +import com.tacitknowledge.util.migration.MigrationException; +import com.tacitknowledge.util.migration.jdbc.util.MigrationUtil; +import junit.framework.TestCase; +import org.easymock.EasyMock; +import org.easymock.classextension.IMocksControl; + +import static org.easymock.EasyMock.eq; +import static org.easymock.classextension.EasyMock.createStrictControl; + +/** + * @author Hemri Herrera hemri@tacitknowledge.com + * @author Ulises Pulido ulises@tacitknowledge.com + */ + +public class StandaloneMigrationLauncherTest extends TestCase +{ + + public void testShouldRunMigrationsForcingRollback() throws Exception + { + IMocksControl mockControl = createStrictControl(); + MigrationUtil migrationUtil = mockControl.createMock(MigrationUtil.class); + StandaloneMigrationLauncher migrationLauncher = new StandaloneMigrationLauncher(migrationUtil); + String[] arguments = new String[]{"orders","migration.properties","-force", "-rollback", "1"}; + + + migrationUtil.doRollbacks(eq("orders"), eq("migration.properties"), EasyMock.anyObject(), eq(true)); + mockControl.replay(); + + migrationLauncher.setMigrationUtil(migrationUtil); + migrationLauncher.run(arguments); + + mockControl.verify(); + } + + + public void testShouldRunMigrationsMultipleRollbacks() throws Exception + { + IMocksControl mockControl = createStrictControl(); + MigrationUtil migrationUtil = mockControl.createMock(MigrationUtil.class); + StandaloneMigrationLauncher migrationLauncher = new StandaloneMigrationLauncher(migrationUtil); + String[] arguments = new String[]{"orders","migration.properties","-force", "-rollback", "1,2,3,4,5,6"}; + + migrationUtil.doRollbacks(eq("orders"), eq("migration.properties"), EasyMock.anyObject(), eq(true)); + mockControl.replay(); + migrationLauncher.run(arguments); + + mockControl.verify(); + } + + + public void testShouldRunMigrationsMultipleRollbacksInvalidRollbackLevels() throws Exception + { + IMocksControl mockControl = createStrictControl(); + MigrationUtil migrationUtil = mockControl.createMock(MigrationUtil.class); + StandaloneMigrationLauncher migrationLauncher = new StandaloneMigrationLauncher(migrationUtil); + String[] arguments = new String[]{"orders","migration.properties","-force", "-rollback", "1,2C,3B,4D,5A,600"}; + + try { + migrationLauncher.run(arguments); + fail("Should have thrown migration exception"); + } catch (MigrationException me) { + assertEquals("The rollbacklevels should be integers separated by a comma", me.getMessage()); + } + + } + +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/jdbc/TestAutoPatchService.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/jdbc/TestAutoPatchService.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/jdbc/TestAutoPatchService.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,44 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration.jdbc; + +/** + * Overrides methods in the normal launcher factory that make it difficult to test + * + * @author Mike Hardy (mike@tacitknowledge.com) + */ +public class TestAutoPatchService extends AutoPatchService +{ + /** + * Returns TestDataSourceMigrationContext + * + * @return TestDataSourceMigrationContext + */ + public DataSourceMigrationContext getDataSourceMigrationContext() + { + return new TestDataSourceMigrationContext(); + } + + /** + * Returns a TestJdbcMigrationLauncher + * + * @return TestJdbcMigrationLauncher + */ + public JdbcMigrationLauncher getJdbcMigrationLauncher() + { + return new TestJdbcMigrationLauncher(); + } +} \ No newline at end of file Index: 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/jdbc/TestDataSourceMigrationContext.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/jdbc/TestDataSourceMigrationContext.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/jdbc/TestDataSourceMigrationContext.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,40 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration.jdbc; + +import java.sql.Connection; +import java.sql.SQLException; + +import com.tacitknowledge.util.migration.TestMigrationContext; + +/** + * A DataSourceMigrationContext that doesn't actually talk to a database. + * + * @author Mike Hardy (mike@tacitknowledge.com) + */ +public class TestDataSourceMigrationContext extends TestMigrationContext +{ + /** + * Always returns null, doesn't talk to a database + * + * @return null every time + * @exception SQLException never throws + */ + public Connection getConnection() throws SQLException + { + return null; + } +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/jdbc/TestDistributedAutoPatchService.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/jdbc/TestDistributedAutoPatchService.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/jdbc/TestDistributedAutoPatchService.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,44 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration.jdbc; + +/** + * Overrides methods in the normal launcher factory that make it difficult to test + * + * @author Mike Hardy (mike@tacitknowledge.com) + */ +public class TestDistributedAutoPatchService extends DistributedAutoPatchService +{ + /** + * Returns TestDataSourceMigrationContext + * + * @return TestDataSourceMigrationContext + */ + public DataSourceMigrationContext getDataSourceMigrationContext() + { + return new TestDataSourceMigrationContext(); + } + + /** + * Returns a TestJdbcMigrationLauncher + * + * @return TestJdbcMigrationLauncher + */ + public DistributedJdbcMigrationLauncher getDistributedJdbcMigrationLauncher() + { + return new TestDistributedJdbcMigrationLauncher(); + } +} \ No newline at end of file Index: 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/jdbc/TestDistributedJdbcMigrationLauncher.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/jdbc/TestDistributedJdbcMigrationLauncher.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/jdbc/TestDistributedJdbcMigrationLauncher.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,103 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration.jdbc; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import com.tacitknowledge.util.migration.MigrationContext; +import com.tacitknowledge.util.migration.MigrationException; +import com.tacitknowledge.util.migration.MigrationTask; +import com.tacitknowledge.util.migration.PatchInfoStore; +import com.tacitknowledge.util.migration.RollbackableMigrationTask; + +/** + * Override select things in the JdbcMigrationLauncher for testing purposes + * + * @author Mike Hardy (mike@tacitknowledge.com) + */ +public class TestDistributedJdbcMigrationLauncher extends DistributedJdbcMigrationLauncher +{ + /** Class logger */ + private static Log log = LogFactory.getLog(TestDistributedJdbcMigrationLauncher.class); + + /** The PatchInfoStore to use for migrations FIXME need to store a map of them */ + private PatchInfoStore patchStore = null; + + /** + * Delegates to the superclass + */ + public TestDistributedJdbcMigrationLauncher() + { + super(); + } + + /** + * Delegating constructors + * + * @param context the context to use for migration loading + */ + public TestDistributedJdbcMigrationLauncher(JdbcMigrationContext context) + { + super(context); + } + + /** + * Override the patch store creation to be the patch table we have + * + * @param context the context to use for the patch store + * @return patchStore held internally + * @throws MigrationException if creating the store fails + */ + protected PatchInfoStore createPatchStore(JdbcMigrationContext context) + throws MigrationException + { + if (patchStore != null) + { + return patchStore; + } + + return super.createPatchStore(context); + } + + /** + * Set the PatchInfoStore object to use for migrations + * + * @param patchStore the PatchInfoStore to use + */ + public void setPatchStore(PatchInfoStore patchStore) + { + this.patchStore = patchStore; + } + + /** + * {@inheritDoc} + */ + public void migrationSuccessful(MigrationTask task, MigrationContext ctx) + throws MigrationException + { + log.debug(this + " silently ignoring a migrationSuccessful call"); + } + + /** + * {@inheritDoc} + */ + public void rollbackSuccessful(RollbackableMigrationTask task, int rollbackLevel, + MigrationContext context) throws MigrationException + { + log.debug(this + " silently ignoring a rollbackSuccessful call"); + } +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/jdbc/TestDistributedJdbcMigrationLauncherFactory.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/jdbc/TestDistributedJdbcMigrationLauncherFactory.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/jdbc/TestDistributedJdbcMigrationLauncherFactory.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,55 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration.jdbc; + +/** + * Overrides methods in the normal launcher factory that make it difficult to test + * + * @author Mike Hardy (mike@tacitknowledge.com) + */ +public class TestDistributedJdbcMigrationLauncherFactory + extends DistributedJdbcMigrationLauncherFactory +{ + /** + * Returns TestDataSourceMigrationContext + * + * @return TestDataSourceMigrationContext + */ + public DataSourceMigrationContext getDataSourceMigrationContext() + { + return new TestDataSourceMigrationContext(); + } + + /** + * Returns TestDistributedJdbcMigrationLauncher + * + * @return TestDistributedJdbcMigrationLauncher + */ + public JdbcMigrationLauncher getJdbcMigrationLauncher() + { + return new TestDistributedJdbcMigrationLauncher(); + } + + /** + * Returns TestDistributedJdbcMigrationLauncher + * + * @return TestDistributedJdbcMigrationLauncher + */ + public DistributedJdbcMigrationLauncher getDistributedJdbcMigrationLauncher() + { + return new TestDistributedJdbcMigrationLauncher(); + } +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/jdbc/TestJdbcMigrationLauncher.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/jdbc/TestJdbcMigrationLauncher.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/jdbc/TestJdbcMigrationLauncher.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,138 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration.jdbc; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import com.tacitknowledge.util.migration.MigrationContext; +import com.tacitknowledge.util.migration.MigrationException; +import com.tacitknowledge.util.migration.MigrationTask; +import com.tacitknowledge.util.migration.PatchInfoStore; +import com.tacitknowledge.util.migration.RollbackableMigrationTask; + +/** + * Override select things in the JdbcMigrationLauncher for testing purposes + * + * @author Mike Hardy (mike@tacitknowledge.com) + */ +public class TestJdbcMigrationLauncher extends JdbcMigrationLauncher +{ + /** Class logger */ + private static Log log = LogFactory.getLog(TestJdbcMigrationLauncher.class); + + /** The PatchInfoStore to use for migrations FIXME need to store a map of them */ + private PatchInfoStore patchStore = null; + + /** whether we should ignore migration success events or not */ + private boolean ignoreMigrationSuccessfulEvents = true; + + /** + * Delegates to the superclass + */ + public TestJdbcMigrationLauncher() + { + super(); + } + + /** + * Delegating constructors + * + * @param context the context to use for migration loading + */ + public TestJdbcMigrationLauncher(JdbcMigrationContext context) + { + super(context); + } + + /** + * Override the patch store creation to be the patch table we have + * + * @param context the context to use for the patch store + * @return patchStore held internally + * @throws MigrationException if creating the store fails + */ + protected PatchInfoStore createPatchStore(JdbcMigrationContext context) + throws MigrationException + { + if (patchStore != null) + { + getContexts().put(context, patchStore); + return patchStore; + } + + return super.createPatchStore(context); + } + + /** + * Set the PatchInfoStore object to use for migrations + * + * @param patchStore the PatchInfoStore to use + */ + public void setPatchStore(PatchInfoStore patchStore) + { + this.patchStore = patchStore; + } + + /** + * {@inheritDoc} + */ + public void migrationSuccessful(MigrationTask task, MigrationContext ctx) + throws MigrationException + { + if (isIgnoreMigrationSuccessfulEvents()) + { + log.debug(this + " silently ignoring a migrationSuccessful call"); + } + else + { + super.migrationSuccessful(task, ctx); + } + } + + /** + * Whether to return migration successful events or not + * + * @return boolean true if you want to ignore migration success events + */ + public boolean isIgnoreMigrationSuccessfulEvents() + { + return ignoreMigrationSuccessfulEvents; + } + + /** + * Whether to return migration successful events or not + * + * @param ignoreMigrationSuccessfulEvents boolean true to ignore success events + */ + public void setIgnoreMigrationSuccessfulEvents(boolean ignoreMigrationSuccessfulEvents) + { + this.ignoreMigrationSuccessfulEvents = ignoreMigrationSuccessfulEvents; + } + + public void rollbackSuccessful(RollbackableMigrationTask task,int rollbackLevel, + MigrationContext context) throws MigrationException + { + if (isIgnoreMigrationSuccessfulEvents()) + { + log.debug(this + " silently ignoring a migrationSuccessful call"); + } + else + { + super.rollbackSuccessful(task, rollbackLevel,context); + } + } +} \ No newline at end of file Index: 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/jdbc/TestJdbcMigrationLauncherFactory.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/jdbc/TestJdbcMigrationLauncherFactory.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/jdbc/TestJdbcMigrationLauncherFactory.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,44 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration.jdbc; + +/** + * Overrides methods in the normal launcher factory that make it difficult to test + * + * @author Mike Hardy (mike@tacitknowledge.com) + */ +public class TestJdbcMigrationLauncherFactory extends JdbcMigrationLauncherFactory +{ + /** + * Returns TestDataSourceMigrationContext + * + * @return TestDataSourceMigrationContext + */ + public DataSourceMigrationContext getDataSourceMigrationContext() + { + return new TestDataSourceMigrationContext(); + } + + /** + * Returns a TestJdbcMigrationLauncher + * + * @return TestJdbcMigrationLauncher + */ + public JdbcMigrationLauncher getJdbcMigrationLauncher() + { + return new TestJdbcMigrationLauncher(); + } +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/jdbc/WebAppServletContextFactoryTest.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/jdbc/WebAppServletContextFactoryTest.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/jdbc/WebAppServletContextFactoryTest.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,154 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration.jdbc; + +import java.util.Iterator; + +import javax.naming.InitialContext; +import javax.naming.NamingException; +import javax.servlet.ServletContextEvent; + +import junit.framework.TestCase; + +import org.mockejb.jndi.MockContextFactory; + +import com.mockrunner.mock.jdbc.MockDataSource; +import com.mockrunner.mock.web.MockServletContext; +import com.tacitknowledge.util.migration.MigrationException; + +/** + * Tests that the factory successfully returns parameters found in the + * servlet context. + * + * @author Chris A. (chris@tacitknowledge.com) + */ +public class WebAppServletContextFactoryTest extends TestCase +{ + /** + * @see junit.framework.TestCase#setUp() + */ + public void setUp() throws Exception + { + super.setUp(); + MockContextFactory.setAsInitial(); + InitialContext context = new InitialContext(); + context.createSubcontext("java"); + } + + /** + * @see junit.framework.TestCase#tearDown() + */ + public void tearDown() throws Exception + { + super.tearDown(); + InitialContext context = new InitialContext(); + context.destroySubcontext("java"); + } + + /** + * Tests that the new mechanism for configuring a launcher from a + * servlet context works. + * + * @throws NamingException a problem with the test + */ + public void testConfiguredContext() throws NamingException + { + JdbcMigrationLauncherFactory launcherFactory = new JdbcMigrationLauncherFactory(); + MockServletContext sc = new MockServletContext(); + + String dbType = "mysql"; + String sysName = "testSystem"; + + sc.setInitParameter("migration.systemname", sysName); + sc.setInitParameter("migration.readonly", "true"); + sc.setInitParameter("migration.databasetype", dbType); + sc.setInitParameter("migration.patchpath", "patches"); + sc.setInitParameter("migration.datasource", "jdbc/testsource"); + + MockDataSource ds = new MockDataSource(); + InitialContext context = new InitialContext(); + context.bind("java:comp/env/jdbc/testsource", ds); + ServletContextEvent sce = new ServletContextEvent(sc); + JdbcMigrationLauncher launcher = null; + try + { + launcher = launcherFactory.createMigrationLauncher(sce); + } + catch (MigrationException e) + { + e.printStackTrace(); + fail("There should not have been an exception"); + } + + JdbcMigrationContext jdbcContext = + (JdbcMigrationContext) launcher.getContexts().keySet().iterator().next(); + assertEquals(dbType, jdbcContext.getDatabaseType().getDatabaseType()); + assertEquals(sysName, jdbcContext.getSystemName()); + assertEquals(true, launcher.isReadOnly()); + } + + /** + * Tests that the new mechanism for configuring a launcher from a + * servlet context works, with multi-node configurations + * + * @throws NamingException a problem with the test + */ + public void testConfiguredMultiNodeContext() throws NamingException + { + JdbcMigrationLauncherFactory launcherFactory = new JdbcMigrationLauncherFactory(); + MockServletContext sc = new MockServletContext(); + + String dbType1 = "mysql"; + String dbType2 = "sybase"; + String sysName = "testSystem"; + + sc.setInitParameter("migration.systemname", sysName); + sc.setInitParameter("migration.readonly", "true"); + sc.setInitParameter("migration.jdbc.systems", "system1,system2"); + sc.setInitParameter("migration.system1.databasetype", dbType1); + sc.setInitParameter("migration.system1.datasource", "jdbc/testsource1"); + sc.setInitParameter("migration.system2.databasetype", dbType2); + sc.setInitParameter("migration.system2.datasource", "jdbc/testsource2"); + sc.setInitParameter("migration.patchpath", "patches"); + + MockDataSource ds = new MockDataSource(); + MockContextFactory.setAsInitial(); + InitialContext context = new InitialContext(); + context.bind("java:comp/env/jdbc/testsource1", ds); + context.bind("java:comp/env/jdbc/testsource2", ds); + ServletContextEvent sce = new ServletContextEvent(sc); + JdbcMigrationLauncher launcher = null; + try + { + launcher = launcherFactory.createMigrationLauncher(sce); + } + catch (MigrationException e) + { + e.printStackTrace(); + fail("There should not have been an exception"); + } + + Iterator contextIter = launcher.getContexts().keySet().iterator(); + JdbcMigrationContext jdbcContext1 = (JdbcMigrationContext) contextIter.next(); + JdbcMigrationContext jdbcContext2 = (JdbcMigrationContext) contextIter.next(); + assertEquals(dbType1, jdbcContext1.getDatabaseType().getDatabaseType()); + assertEquals(sysName, jdbcContext1.getSystemName()); + assertEquals(dbType2, jdbcContext2.getDatabaseType().getDatabaseType()); + assertEquals(sysName, jdbcContext2.getSystemName()); + + assertEquals(true, launcher.isReadOnly()); + } +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/jdbc/loader/NameParseTest.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/jdbc/loader/NameParseTest.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/jdbc/loader/NameParseTest.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,105 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration.jdbc.loader; + +import junit.framework.TestCase; + +/** + * Test used to verify that the table name is correctly + * parsed. + * + * @author Chris A. (chris@tacitknowledge.com) + */ +public class NameParseTest extends TestCase +{ + /** + * The dummy loader used for testing + */ + private TestLoader loader = null; + + /** + * Constructor that invokes its parent's constructor + * + * @param name the test name + */ + public NameParseTest(String name) + { + super(name); + } + + /** + * @see junit.framework.TestCase#setUp() + */ + public void setUp() throws Exception + { + super.setUp(); + loader = new TestLoader(); + } + + /** + * Tests several combinations of input names to insure things are + * working well. + */ + public void testNameParsing() + { + String longName = DelimitedFileLoader.PATH_SEPARATOR + "parent-dir" + + DelimitedFileLoader.PATH_SEPARATOR + "child-dir" + + DelimitedFileLoader.PATH_SEPARATOR + "table_db20040704.load"; + String medName = DelimitedFileLoader.PATH_SEPARATOR + "table_db20040704.load"; + String[] names = {"table_db.dat", longName, medName}; + for (int i = 0; i < names.length; i++) + { + loader.setName(names[i]); + String tb = loader.getTableFromName(); + assertTrue("table".equals(tb)); + } + } + + /** + * Inner class used to test file loader + */ + private class TestLoader extends DelimitedFileLoader + { + /** + * The name of the file + */ + private String name = null; + + /** + * @see com.tacitknowledge.util.migration.MigrationTask#getName() + */ + public String getName() + { + return name; + } + + /** + * @see com.tacitknowledge.util.migration.MigrationTaskSupport#setName(java.lang.String) + */ + public void setName(String name) + { + this.name = name; + } + + /** + * @see com.tacitknowledge.util.migration.jdbc.loader.DelimitedFileLoader#getDelimiter() + */ + public String getDelimiter() + { + return "|"; + } + } +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/jdbc/loader/QueryTest.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/jdbc/loader/QueryTest.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/jdbc/loader/QueryTest.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,122 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration.jdbc.loader; + +import java.io.IOException; +import java.io.InputStream; + +import junit.framework.TestCase; + +/** + * Test used to verify that the sql query is correctly + * created + * + * @author Chris A. (chris@tacitknowledge.com) + */ +public class QueryTest extends TestCase +{ + /** + * The dummy loader used for testing + */ + private TestLoader loader = null; + + /** + * Constructor that invokes its parent's constructor + * + * @param name the test name + */ + public QueryTest(String name) + { + super(name); + } + + /** + * @see junit.framework.TestCase#setUp() + */ + public void setUp() throws Exception + { + super.setUp(); + loader = new TestLoader(); + } + + /** + * Tests query generation + */ + public void testQuery() + { + loader.setName("mocktable_db.dat"); + String sql = loader.getStatmentSql(); + String answer = "INSERT INTO mocktable (col1, col2, col3, col4) " + + "VALUES (?, ?, ?, ?)"; + assertTrue(answer.equals(sql)); + } + + /** + * Inner class used to test file loader + */ + private class TestLoader extends DelimitedFileLoader + { + /** + * The name of the file + */ + private String name = null; + + /** + * @see com.tacitknowledge.util.migration.MigrationTask#getName() + */ + public String getName() + { + return name; + } + + /** + * @see com.tacitknowledge.util.migration.MigrationTaskSupport#setName(java.lang.String) + */ + public void setName(String name) + { + this.name = name; + } + + /** + * @see com.tacitknowledge.util.migration.jdbc.loader.DelimitedFileLoader#getDelimiter() + */ + public String getDelimiter() + { + return "|"; + } + + /** + * Returns the header (first line) of the file. + * + * @param is the input stream containing the data to load + * @return the first row + * @throws IOException if the input stream could not be read + */ + protected String getHeader(InputStream is) throws IOException + { + return "col1|col2|col3|col4"; + } + + /** + * @see com.tacitknowledge.util.migration.jdbc.SqlLoadMigrationTask#getResourceAsStream() + */ + protected InputStream getResourceAsStream() + { + return null; + } + } +} + Index: 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/jdbc/util/ConnectionWrapperDataSourceTest.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/jdbc/util/ConnectionWrapperDataSourceTest.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/jdbc/util/ConnectionWrapperDataSourceTest.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,100 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration.jdbc.util; + +import java.sql.Connection; + +import com.mockrunner.jdbc.JDBCTestCaseAdapter; + +/** + * Test our ConnectionWrapperDataSource + * + * @author Mike Hardy (mike@tacitknowledge.com) + */ +public class ConnectionWrapperDataSourceTest extends JDBCTestCaseAdapter +{ + /** A connection object we can use as a fixture */ + private Connection conn = null; + + /** + * @see com.mockrunner.jdbc.JDBCTestCaseAdapter#setUp() + */ + public void setUp() throws Exception + { + super.setUp(); + conn = getJDBCMockObjectFactory().getMockConnection(); + } + + /** + * Test connection wrapping to make sure we get and return the same one + * + * @exception Exception if anything goes wrong + */ + public void testConnectionWrapping() throws Exception + { + ConnectionWrapperDataSource ds = new ConnectionWrapperDataSource(conn); + assertEquals(conn, ds.getConnection()); + assertEquals(conn, ds.getConnection("foo", "bar")); + } + + /** + * Make sure our unsuupported operations really are unsupported + */ + public void testUnsupportedOperations() + { + ConnectionWrapperDataSource ds = new ConnectionWrapperDataSource(conn); + + try + { + ds.getLogWriter(); + fail("Should have gotten an unsupported operation exception"); + } + catch (UnsupportedOperationException uoe) + { + // we expect this + } + + try + { + ds.setLogWriter(null); + fail("Should have gotten an unsupported operation exception"); + } + catch (UnsupportedOperationException uoe) + { + // we expect this + } + + try + { + ds.getLoginTimeout(); + fail("Should have gotten an unsupported operation exception"); + } + catch (UnsupportedOperationException uoe) + { + // we expect this + } + + try + { + ds.setLoginTimeout(-1); + fail("Should have gotten an unsupported operation exception"); + } + catch (UnsupportedOperationException uoe) + { + // we expect this + } + } +} \ No newline at end of file Index: 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/jdbc/util/MigrationUtilTest.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/jdbc/util/MigrationUtilTest.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/jdbc/util/MigrationUtilTest.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,73 @@ +/* Copyright 2011 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration.jdbc.util; + + +import com.tacitknowledge.util.migration.MigrationException; +import com.tacitknowledge.util.migration.jdbc.JdbcMigrationLauncher; +import com.tacitknowledge.util.migration.jdbc.JdbcMigrationLauncherFactory; +import junit.framework.TestCase; +import org.easymock.classextension.IMocksControl; + +import static org.easymock.EasyMock.expect; +import static org.easymock.classextension.EasyMock.createStrictControl; + +public class MigrationUtilTest extends TestCase{ + + private static final int PATCHES_APPLIED = 2; + private static final int ROLLBACK_LEVEL = 3; + private static final int[] ROLLBACK_LEVELS = new int[]{ROLLBACK_LEVEL}; + private MigrationUtil migrationUtil; + private IMocksControl mockControl; + private JdbcMigrationLauncherFactory launcherFactoryMock; + private JdbcMigrationLauncher launcherMock; + private static final String MIGRATION_NAME = "mysystem"; + private static final boolean FORCE_ROLLBACK = false; + private static final String MIGRATION_SETTINGS = "mymigrationsettings"; + + protected void setUp() throws Exception { + super.setUp(); + + mockControl = createStrictControl(); + launcherFactoryMock = mockControl.createMock(JdbcMigrationLauncherFactory.class); + launcherMock = mockControl.createMock( JdbcMigrationLauncher.class ); + migrationUtil = new MigrationUtil(); + migrationUtil.setLauncherFactory(launcherFactoryMock); + } + + public void testDoRollbacksActionWithoutMigrationSettings() throws MigrationException { + + expect( launcherFactoryMock.createMigrationLauncher(MIGRATION_NAME)).andReturn(launcherMock); + expect(launcherMock.doRollbacks(ROLLBACK_LEVELS, FORCE_ROLLBACK)).andReturn(PATCHES_APPLIED); + mockControl.replay(); + + + migrationUtil.doRollbacks(MIGRATION_NAME, null, ROLLBACK_LEVELS, FORCE_ROLLBACK); + mockControl.verify(); + + } + + public void testDoRollbackActionWithMigrationSettings() throws MigrationException{ + + expect( launcherFactoryMock.createMigrationLauncher(MIGRATION_NAME, MIGRATION_SETTINGS)).andReturn(launcherMock); + expect( launcherMock.doRollbacks(ROLLBACK_LEVELS, FORCE_ROLLBACK)).andReturn(PATCHES_APPLIED); + mockControl.replay(); + + migrationUtil.doRollbacks(MIGRATION_NAME, MIGRATION_SETTINGS,ROLLBACK_LEVELS,FORCE_ROLLBACK); + mockControl.verify(); + + } +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/jdbc/util/SybaseUtilTest.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/jdbc/util/SybaseUtilTest.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/jdbc/util/SybaseUtilTest.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,53 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration.jdbc.util; + +import junit.framework.TestCase; + +/** + * Test class for {@link SybaseUtil}. + * + * @author Alex Soto (apsoto@gmail.com) + */ +public class SybaseUtilTest extends TestCase +{ + + /** + * {@inheritDoc} + */ + protected void setUp() throws Exception + { + super.setUp(); + } + + /** + * Test the containsIllegalMultiStatementTransactionCommand method. + * + */ + public void testContainsIllegalMultiStatementTransactionCommand() + { + String simpleSql = "ALTER TABLE foo ADD version DEFAULT 0"; + String multiLineSql = "INSERT INTO TABLE foo(id) VALUES(1)\n" + + "alter table foo ADD VERSION DEFAULT 0\n" + + "INSERT INTO TABLE foo(id, version) VALUES (1, 1)\n"; + String noIllegalSql = "SELECT * FROM foo"; + + assertTrue(SybaseUtil.containsIllegalMultiStatementTransactionCommand(simpleSql)); + assertTrue(SybaseUtil.containsIllegalMultiStatementTransactionCommand(multiLineSql)); + assertFalse(SybaseUtil.containsIllegalMultiStatementTransactionCommand(noIllegalSql)); + } + +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/tasks/BaseTestMigrationTask.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/tasks/BaseTestMigrationTask.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/tasks/BaseTestMigrationTask.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,76 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration.tasks; + +import com.tacitknowledge.util.migration.MigrationContext; +import com.tacitknowledge.util.migration.MigrationException; +import com.tacitknowledge.util.migration.MigrationTaskSupport; +import com.tacitknowledge.util.migration.TestMigrationContext; + +/** + * Base class for migration task tests. + * + * @author Scott Askew (scott@tacitknowledge.com) + */ +public abstract class BaseTestMigrationTask extends MigrationTaskSupport +{ + /** + * Create a new BaseTestMigrationTask. + * + * @param name the name of the task + * @param level the patch level of the task + */ + protected BaseTestMigrationTask(String name, int level) + { + setName(name); + setLevel(new Integer(level)); + } + + /** + * @see MigrationTaskSupport#migrate(MigrationContext) + */ + public void migrate(MigrationContext context) throws MigrationException + { + if (context instanceof TestMigrationContext) + { + TestMigrationContext ctx = (TestMigrationContext) context; + ctx.recordExecution(getName()); + } + } + + public void up(MigrationContext context) throws MigrationException + { + if (context instanceof TestMigrationContext) + { + TestMigrationContext ctx = (TestMigrationContext) context; + ctx.recordExecution(getName()); + } + } + + public void down(MigrationContext context) throws MigrationException + { + if (context instanceof TestMigrationContext) + { + TestMigrationContext ctx = (TestMigrationContext) context; + ctx.recordExecution(getName()); + } + } + + public boolean isRollbackSupported() { + return true; + } + +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/tasks/instantiation/TestMigrationTaskInstantiationException.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/tasks/instantiation/TestMigrationTaskInstantiationException.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/tasks/instantiation/TestMigrationTaskInstantiationException.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,37 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration.tasks.instantiation; + +import com.tacitknowledge.util.migration.tasks.BaseTestMigrationTask; + +/** + * + * + * @author Mike Hardy (mike@tacitknowledge.com) + */ +public class TestMigrationTaskInstantiationException extends BaseTestMigrationTask +{ + /** + * Constructor throws an exception - this task never works + * + * @exception RuntimeException when instantiated + */ + public TestMigrationTaskInstantiationException() throws RuntimeException + { + super("TestMigrationTaskInstantiationException", 1); + throw new RuntimeException("This class always throws exceptions when instantiated"); + } +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/tasks/normal/TestMigrationTask1.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/tasks/normal/TestMigrationTask1.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/tasks/normal/TestMigrationTask1.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,34 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration.tasks.normal; + +import com.tacitknowledge.util.migration.tasks.BaseTestMigrationTask; + +/** + * Basic test migration task. + * + * @author Scott Askew (scott@tacitknowledge.com) + */ +public class TestMigrationTask1 extends BaseTestMigrationTask +{ + /** + * Creates a new TestMigrationTask1. + */ + public TestMigrationTask1() + { + super("TestTask1", 4); + } +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/tasks/normal/TestMigrationTask2.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/tasks/normal/TestMigrationTask2.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/tasks/normal/TestMigrationTask2.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,67 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration.tasks.normal; + +import com.tacitknowledge.util.migration.tasks.BaseTestMigrationTask; + +/** + * Basic test migration task. + * + * @author Scott Askew (scott@tacitknowledge.com) + */ +public class TestMigrationTask2 extends BaseTestMigrationTask +{ + /** + * The patch level to use instead of '2' + */ + private static Integer patchLevelOverride = new Integer(5); + + /** + * Creates a new TestMigrationTask3. + */ + public TestMigrationTask2() + { + super("TestTask2", 5); + } + + /** + * @see com.tacitknowledge.util.migration.MigrationTaskSupport#getLevel() + */ + public Integer getLevel() + { + return patchLevelOverride; + } + + /** + * Sets the patch level to use for all instances of this class. + * + * @param level the patch level to use for all instances of this class; if + * null, then the default patch level (2) will be used + */ + public static void setPatchLevelOverride(Integer level) + { + patchLevelOverride = level; + } + + /** + * Resets the task the its default state. + */ + public static void reset() + { + patchLevelOverride = new Integer(5); + } + +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/tasks/normal/TestMigrationTask3.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/tasks/normal/TestMigrationTask3.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/tasks/normal/TestMigrationTask3.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,63 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration.tasks.normal; + +import com.tacitknowledge.util.migration.MigrationContext; +import com.tacitknowledge.util.migration.MigrationException; +import com.tacitknowledge.util.migration.tasks.BaseTestMigrationTask; + +/** + * Basic test migration task. + * + * @author Scott Askew (scott@tacitknowledge.com) + */ +public class TestMigrationTask3 extends BaseTestMigrationTask +{ + /** + * Determines if the task should simulate a MigrationException + */ + private static boolean fail = false; + + /** + * Creates a new TestMigrationTask3. + */ + public TestMigrationTask3() + { + super("TestTask3", 6); + } + + /** + * @see BaseTestMigrationTask#migrate(MigrationContext) + */ + public void migrate(MigrationContext context) throws MigrationException + { + if (fail) + { + throw new MigrationException("Test exception"); + } + super.migrate(context); + } + + /** + * Determins if the task should simulate a MigrationException + * + * @param f trueif the task should simulate a MigrationException + */ + public static void setFail(boolean f) + { + TestMigrationTask3.fail = f; + } +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/tasks/normal/TestMigrationTask4.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/tasks/normal/TestMigrationTask4.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/tasks/normal/TestMigrationTask4.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,34 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration.tasks.normal; + +import com.tacitknowledge.util.migration.tasks.BaseTestMigrationTask; + +/** + * Basic test migration task. + * + * @author Scott Askew (scott@tacitknowledge.com) + */ +public class TestMigrationTask4 extends BaseTestMigrationTask +{ + /** + * Creates a new TestMigrationTask3. + */ + public TestMigrationTask4() + { + super("TestTask4", 7); + } +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/tasks/post/TestPostMigrationTask1.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/tasks/post/TestPostMigrationTask1.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/tasks/post/TestPostMigrationTask1.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,34 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration.tasks.post; + +import com.tacitknowledge.util.migration.tasks.BaseTestMigrationTask; + +/** + * Basic test post migration task. + * + * @author Mike Hardy (mike@tacitknowledge.com) + */ +public class TestPostMigrationTask1 extends BaseTestMigrationTask +{ + /** + * Creates a new TestMigrationTask1. + */ + public TestPostMigrationTask1() + { + super("TestPostTask1", 1); + } +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/tasks/post/TestPostMigrationTask2.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/tasks/post/TestPostMigrationTask2.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/tasks/post/TestPostMigrationTask2.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,34 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration.tasks.post; + +import com.tacitknowledge.util.migration.tasks.BaseTestMigrationTask; + +/** + * Basic test post migration task. + * + * @author Mike Hardy (mike@tacitknowledge.com) + */ +public class TestPostMigrationTask2 extends BaseTestMigrationTask +{ + /** + * Creates a new TestMigrationTask2. + */ + public TestPostMigrationTask2() + { + super("TestPostTask2", 2); + } +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/tasks/rollback/BaseTestRollbackableMigrationTask.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/tasks/rollback/BaseTestRollbackableMigrationTask.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/tasks/rollback/BaseTestRollbackableMigrationTask.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,67 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration.tasks.rollback; + +import com.tacitknowledge.util.migration.MigrationContext; +import com.tacitknowledge.util.migration.MigrationException; +import com.tacitknowledge.util.migration.MigrationTaskSupport; +import com.tacitknowledge.util.migration.TestMigrationContext; + +/** + * Base class for rollback task tests. + * + * @author Artie Pesh-Imam (apeshimam@tacitknowledge.com) + */ +public abstract class BaseTestRollbackableMigrationTask extends MigrationTaskSupport +{ + /** + * Create a new BaseTestRollbackableMigrationTask. + * + * @param name + * the name of the task + * @param level + * the patch level of the task + */ + protected BaseTestRollbackableMigrationTask(String name, int level) + { + setName(name); + setLevel(new Integer(level)); + } + + /** + * Perform a rollback + * @param context migration context + * @throws MigrationException if an error happens + */ + public void down(MigrationContext context) throws MigrationException + { + if (context instanceof TestMigrationContext) + { + TestMigrationContext ctx = (TestMigrationContext) context; + ctx.recordExecution(getName()); + } + } + + /** + * Return true indicating that rollback is supported + * @return true if rollback is supported. + */ + public boolean isRollbackSupported() + { + return true; + } + +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/tasks/rollback/TestRollbackableTask1.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/tasks/rollback/TestRollbackableTask1.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/tasks/rollback/TestRollbackableTask1.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,55 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration.tasks.rollback; + +import com.tacitknowledge.util.migration.MigrationContext; +import com.tacitknowledge.util.migration.MigrationException; +import com.tacitknowledge.util.migration.MigrationTaskSupport; +import com.tacitknowledge.util.migration.RollbackableMigrationTask; +import com.tacitknowledge.util.migration.TestMigrationContext; + +public class TestRollbackableTask1 extends BaseTestRollbackableMigrationTask + implements RollbackableMigrationTask + { + + public TestRollbackableTask1() + { + super("TestRollbackableTask1", 8); + } + + public TestRollbackableTask1(int level) + { + super("TestRollbackableTask1", level); + } + + public boolean isRollbackSupported() + { + return false; + } + + /** + * @see MigrationTaskSupport#migrate(MigrationContext) + */ + public void migrate(MigrationContext context) throws MigrationException + { + if (context instanceof TestMigrationContext) + { + TestMigrationContext ctx = (TestMigrationContext) context; + ctx.recordExecution(getName()); + } + } + + } Index: 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/tasks/rollback/TestRollbackableTask2.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/tasks/rollback/TestRollbackableTask2.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/tasks/rollback/TestRollbackableTask2.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,65 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration.tasks.rollback; + +import com.tacitknowledge.util.migration.MigrationContext; +import com.tacitknowledge.util.migration.MigrationException; +import com.tacitknowledge.util.migration.RollbackableMigrationTask; +import com.tacitknowledge.util.migration.TestMigrationContext; + +public class TestRollbackableTask2 extends BaseTestRollbackableMigrationTask + implements RollbackableMigrationTask +{ + private static Integer patchLevelOverride = new Integer(9); + + public static void setPatchLevelOverride(Integer i) + { + patchLevelOverride = (i); + } + + public Integer getLevel() + { + return patchLevelOverride; + } + + public static void reset() + { + patchLevelOverride = new Integer(9); + } + + public TestRollbackableTask2() + { + super("TestRollbackableTask2", 9); + } + + public void down(MigrationContext context) throws MigrationException + { + if (context instanceof TestMigrationContext) + { + TestMigrationContext ctx = (TestMigrationContext) context; + ctx.recordExecution(getName()); + } + } + + public void up(MigrationContext context) throws MigrationException + { + if (context instanceof TestMigrationContext) + { + TestMigrationContext ctx = (TestMigrationContext) context; + ctx.recordExecution(getName()); + } + } +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/tasks/rollback/TestRollbackableTask3.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/tasks/rollback/TestRollbackableTask3.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/tasks/rollback/TestRollbackableTask3.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,49 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration.tasks.rollback; + +import com.tacitknowledge.util.migration.MigrationContext; +import com.tacitknowledge.util.migration.MigrationException; +import com.tacitknowledge.util.migration.RollbackableMigrationTask; +import com.tacitknowledge.util.migration.TestMigrationContext; + +public class TestRollbackableTask3 extends BaseTestRollbackableMigrationTask + implements RollbackableMigrationTask + { + + public TestRollbackableTask3() + { + super("TestRollbackableTask3", 10); + } + + public void down(MigrationContext context) throws MigrationException + { + if (context instanceof TestMigrationContext) + { + TestMigrationContext ctx = (TestMigrationContext) context; + ctx.recordExecution(getName()); + } + } + + public void up(MigrationContext context) throws MigrationException + { + if (context instanceof TestMigrationContext) + { + TestMigrationContext ctx = (TestMigrationContext) context; + ctx.recordExecution(getName()); + } + } + } Index: 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/tasks/rollback/TestRollbackableTask4.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/tasks/rollback/TestRollbackableTask4.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/tasks/rollback/TestRollbackableTask4.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,49 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration.tasks.rollback; + +import com.tacitknowledge.util.migration.MigrationContext; +import com.tacitknowledge.util.migration.MigrationException; +import com.tacitknowledge.util.migration.RollbackableMigrationTask; +import com.tacitknowledge.util.migration.TestMigrationContext; + +public class TestRollbackableTask4 extends BaseTestRollbackableMigrationTask + implements RollbackableMigrationTask + { + + public TestRollbackableTask4() + { + super("TestRollbackableTask4", 11); + } + + public void down(MigrationContext context) throws MigrationException + { + if (context instanceof TestMigrationContext) + { + TestMigrationContext ctx = (TestMigrationContext) context; + ctx.recordExecution(getName()); + } + } + + public void up(MigrationContext context) throws MigrationException + { + if (context instanceof TestMigrationContext) + { + TestMigrationContext ctx = (TestMigrationContext) context; + ctx.recordExecution(getName()); + } + } + } Index: 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/tasks/rollback/TestRollbackableTask5.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/tasks/rollback/TestRollbackableTask5.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/tasks/rollback/TestRollbackableTask5.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,49 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration.tasks.rollback; + +import com.tacitknowledge.util.migration.MigrationContext; +import com.tacitknowledge.util.migration.MigrationException; +import com.tacitknowledge.util.migration.RollbackableMigrationTask; +import com.tacitknowledge.util.migration.TestMigrationContext; + +public class TestRollbackableTask5 extends BaseTestRollbackableMigrationTask + implements RollbackableMigrationTask +{ + public TestRollbackableTask5() + { + super("TestRollbackableTask5", 12); + } + + public void down(MigrationContext context) throws MigrationException + { + if (context instanceof TestMigrationContext) + { + TestMigrationContext ctx = (TestMigrationContext) context; + ctx.recordExecution(getName()); + } + } + + public void up(MigrationContext context) throws MigrationException + { + if (context instanceof TestMigrationContext) + { + TestMigrationContext ctx = (TestMigrationContext) context; + ctx.recordExecution(getName()); + } + } + +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/tasks/rollback/migrationtasks/TestMigrationTaskRollback1.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/tasks/rollback/migrationtasks/TestMigrationTaskRollback1.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/tasks/rollback/migrationtasks/TestMigrationTaskRollback1.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,42 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration.tasks.rollback.migrationtasks; + +import com.tacitknowledge.util.migration.MigrationContext; +import com.tacitknowledge.util.migration.MigrationException; +import com.tacitknowledge.util.migration.MigrationTaskSupport; +import com.tacitknowledge.util.migration.RollbackableMigrationTask; +import com.tacitknowledge.util.migration.TestMigrationContext; + +public class TestMigrationTaskRollback1 extends MigrationTaskSupport implements RollbackableMigrationTask +{ + + public TestMigrationTaskRollback1() + { + setName("TestMigrationTaskRollback1"); + setLevel(new Integer(13)); + } + + public void migrate(MigrationContext context) throws MigrationException + { + if (context instanceof TestMigrationContext) + { + TestMigrationContext ctx = (TestMigrationContext) context; + ctx.recordExecution(getName()); + } + + } +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/test/listeners/TestListener1.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/test/listeners/TestListener1.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/test/listeners/TestListener1.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,69 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration.test.listeners; + +import java.util.Properties; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import com.tacitknowledge.util.migration.MigrationContext; +import com.tacitknowledge.util.migration.MigrationException; +import com.tacitknowledge.util.migration.MigrationListener; +import com.tacitknowledge.util.migration.MigrationTask; + + +/** + * @author Alex Soto + * + */ +public class TestListener1 implements MigrationListener +{ + /** Class logger */ + private static Log log = LogFactory.getLog(TestListener1.class); + + /** system name I was configured with */ + protected String systemName = null; + + public void migrationFailed(MigrationTask task, MigrationContext context, MigrationException e) throws MigrationException + { + log.debug("migration failed"); + } + + public void migrationStarted(MigrationTask task, MigrationContext context) throws MigrationException + { + log.debug("migration started"); + } + + public void migrationSuccessful(MigrationTask task, MigrationContext context) throws MigrationException + { + log.debug("migration successful"); + } + + /** + * @see com.tacitknowledge.util.migration.MigrationListener#initialize(Properties) + */ + public void initialize(String systemName, Properties properties) throws MigrationException + { + log.debug("initialized"); + this.systemName = systemName; + } + + public String getSystemName() + { + return this.systemName; + } +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/test/listeners/TestListener2.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/test/listeners/TestListener2.java (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/test/java/com/tacitknowledge/util/migration/test/listeners/TestListener2.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,33 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.migration.test.listeners; + +import java.util.Properties; + +import com.tacitknowledge.util.migration.AbstractMigrationListener; +import com.tacitknowledge.util.migration.MigrationException; + +/** + * @author Alex Soto (apsoto@gmail.com) + */ +public class TestListener2 extends AbstractMigrationListener +{ + + public void initialize(String systemName, Properties properties) throws MigrationException + { + } + +} Index: 3rdParty_sources/tacitknowledge/autopatch/src/test/resources/com/tacitknowledge/util/migration/jdbc/test/patch0001-rollback.sql =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/test/resources/com/tacitknowledge/util/migration/jdbc/test/patch0001-rollback.sql (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/test/resources/com/tacitknowledge/util/migration/jdbc/test/patch0001-rollback.sql (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,4 @@ + delete from user_role_assoc where application_user_id = '2'; + delete from user_role_assoc where application_user_id = '3'; + delete from user_role_assoc where application_user_id = '4'; + \ No newline at end of file Index: 3rdParty_sources/tacitknowledge/autopatch/src/test/resources/com/tacitknowledge/util/migration/jdbc/test/patch0001.sql =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/test/resources/com/tacitknowledge/util/migration/jdbc/test/patch0001.sql (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/test/resources/com/tacitknowledge/util/migration/jdbc/test/patch0001.sql (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,13 @@ + + insert into user_role_assoc (user_role_id, application_user_id, role_code, project_id) + values (nextval('role_id_seq'),2, 'SYSA', 3); + +// Testing +insert into user_role_assoc (user_role_id, application_user_id, role_code, project_id) + values (nextval('role_id_seq'),3, 'SYSA', 3); + + -- This is a comment +insert into user_role_assoc (user_role_id, application_user_id, role_code, project_id) + values (nextval('role_--id_seq;'),4, 'SYSA', 3); + + \ No newline at end of file Index: 3rdParty_sources/tacitknowledge/autopatch/src/test/resources/com/tacitknowledge/util/migration/jdbc/test/patch0002_second_patch.sql =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/test/resources/com/tacitknowledge/util/migration/jdbc/test/patch0002_second_patch.sql (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/test/resources/com/tacitknowledge/util/migration/jdbc/test/patch0002_second_patch.sql (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,4 @@ +-- Note that the end of this statement does *not* have a semi-colon; this should +-- be OK since there's only one script in the file +insert into user_role_assoc (user_role_id, application_user_id, role_code, project_id) + values (nextval('role_id_seq'),2, 'SYSA', 3) \ No newline at end of file Index: 3rdParty_sources/tacitknowledge/autopatch/src/test/resources/com/tacitknowledge/util/migration/jdbc/test/patch0003-rollback_third_patch.sql =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/test/resources/com/tacitknowledge/util/migration/jdbc/test/patch0003-rollback_third_patch.sql (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/test/resources/com/tacitknowledge/util/migration/jdbc/test/patch0003-rollback_third_patch.sql (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,2 @@ +select * from dual; +select * from dual; \ No newline at end of file Index: 3rdParty_sources/tacitknowledge/autopatch/src/test/resources/com/tacitknowledge/util/migration/jdbc/test/patch0003_third_patch.sql =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/test/resources/com/tacitknowledge/util/migration/jdbc/test/patch0003_third_patch.sql (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/test/resources/com/tacitknowledge/util/migration/jdbc/test/patch0003_third_patch.sql (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,2 @@ +select * from dual; +select * from dual; \ No newline at end of file Index: 3rdParty_sources/tacitknowledge/autopatch/src/test/resources/com/tacitknowledge/util/migration/jdbc/test/sybase_tsql.sql =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/test/resources/com/tacitknowledge/util/migration/jdbc/test/sybase_tsql.sql (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/test/resources/com/tacitknowledge/util/migration/jdbc/test/sybase_tsql.sql (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,40 @@ +/* just some sane sql at first */ + + +PRINT 'Creating photo table' +go +create table photo +( +id numeric(14,0) NOT NULL, +ownerid numeric(14,0) NOT NULL, +date_created datetime NOT NULL, +status tinyint NULL +) +lock ALLPAGES +go + +create clustered index c_ownerid_status_date on +photos(ownerid,status,date_created) +go + +alter table photo add version default 0 +go + +create unique nonclustered index unc_id on +photo(id) +go + +create nonclustered index nc_ownerid on +photo(ownerid) +Go + +grant delete,insert,select,update on photo to user + go + +/* will this table name screw up the parser looking for GO delimiter :)? */ +create table gogo +( +id numeric(14,0) NOT NULL +value varchar(32) +) +GO \ No newline at end of file Index: 3rdParty_sources/tacitknowledge/autopatch/src/test/resources/log4j.properties =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/test/resources/log4j.properties (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/test/resources/log4j.properties (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,8 @@ +# Set root logger level to DEBUG for lots of output +log4j.rootLogger=INFO, CONSOLE + +# If you would like to have output simply be on the console, set the +# rootLogger to CONSOLE +log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender +log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout +log4j.appender.CONSOLE.layout.ConversionPattern=%d %-4r [%t] %-5p %c{1} %x - %m%n Index: 3rdParty_sources/tacitknowledge/autopatch/src/test/resources/migration.properties =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/src/test/resources/migration.properties (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/src/test/resources/migration.properties (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,45 @@ +# +# Which context do we use for the orchestration patch store? Core. +# +orchestration.context=core +orchestration.controlled.systems=core,orders,catalog +# +# Configure a context named "core" +# +core.jdbc.database.type=hsqldb +core.jdbc.driver=org.hsqldb.jdbcDriver +core.jdbc.url=jdbc:hsqldb:mem:core +core.jdbc.username=sa +core.jdbc.password= +core.patch.path=patches.core:com.tacitknowledge.util.migration.jdbc.test +core.lockPollRetries=10 +# +# Configure a context named "orders" +# +orders.jdbc.database.type=hsqldb +orders.jdbc.driver=org.hsqldb.jdbcDriver +orders.jdbc.url=jdbc:hsqldb:mem:orders +orders.jdbc.username=sa +orders.jdbc.password= +orders.patch.path=patches.orders:com.tacitknowledge.util.migration.tasks.normal +# +# Configure a context named catalog, and make it a multi-node context +# +catalog.jdbc.systems=jdbccatalog1,jdbccatalog2 +catalog.jdbccatalog1.database.type=sybase +catalog.jdbccatalog1.driver=org.hsqldb.jdbcDriver +catalog.jdbccatalog1.url=jdbc:hsqldb:mem:catalog1 +catalog.jdbccatalog1.username=sa +catalog.jdbccatalog1.password= +catalog.jdbccatalog2.database.type=postgres +catalog.jdbccatalog2.driver=org.hsqldb.jdbcDriver +catalog.jdbccatalog2.url=jdbc:hsqldb:mem:catalog2 +catalog.jdbccatalog2.username=sa +catalog.jdbccatalog2.password= +catalog.patch.path=patches.catalog + + +# +# for databaseType.properties override tests +# +mysql.supportsMultipleStatements=false \ No newline at end of file Index: 3rdParty_sources/tacitknowledge/autopatch/tacit_checkstyle.config =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/autopatch/tacit_checkstyle.config (revision 0) +++ 3rdParty_sources/tacitknowledge/autopatch/tacit_checkstyle.config (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,205 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/tacitknowledge/discovery/LICENSE =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/discovery/LICENSE (revision 0) +++ 3rdParty_sources/tacitknowledge/discovery/LICENSE (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,234 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +APACHE SLING SUBCOMPONENTS: + +Apache Sling includes subcomponents with separate copyright notices and +license terms. Your use of these subcomponents is subject to the terms +and conditions of the following licenses. + +JSON in Java + + Copyright (c) 2002 JSON.org + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + The Software shall be used for Good, not Evil. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. Index: 3rdParty_sources/tacitknowledge/discovery/README.md =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/discovery/README.md (revision 0) +++ 3rdParty_sources/tacitknowledge/discovery/README.md (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,26 @@ +Discovery +========= + +Discovery is used to detect resources (files or interface +implementations) in a Java classpath. + +Maven +----- + + + com.tacitknowledge + discovery + 1.0.3 + + + +Simple Examples +--------------- + +To find any implementation of MigrationTask in com.example do: + + Class[] taskClasses = ClassDiscoveryUtil.getClasses("com.example", MigrationTask.class); + +To find any resource named foo\*.sql in a "patch" folder within the classpath do: + + String[] scripts = ClassDiscoveryUtil.getResources("patch", "^foo.*\\.sql$"); Index: 3rdParty_sources/tacitknowledge/discovery/pom.xml =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/discovery/pom.xml (revision 0) +++ 3rdParty_sources/tacitknowledge/discovery/pom.xml (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,81 @@ + + + 4.0.0 + com.tacitknowledge + discovery + 1.0.5 + jar + + + com.tacitknowledge + oss-parent + 1 + + + Resource Discovery + Java Resource Discovery Library + https://github.com/tacitknowledge/discovery + + + Tacit Knowledge + http://www.tacitknowledge.com/ + + + + + Apache 2 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + A business-friendly OSS license + + + + + scm:git:https://github.com/tacitknowledge/discovery.git + scm:git:git@github.com:tacitknowledge/discovery.git + https://github.com/tacitknowledge/discovery + + + + 1.5 + 1.5 + UTF-8 + UTF-8 + 100 + 79 + 79 + 74 + + + + + scottfromsf + Scott Askew + + + Vladimir Pertu + + + Matthew Short + + + Mike Hardy + + + + + + commons-logging + commons-logging + 1.1.1 + compile + + + junit + junit + 3.8.1 + test + + + + Index: 3rdParty_sources/tacitknowledge/discovery/src/main/java/com/tacitknowledge/util/discovery/AggregateResourceListSource.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/discovery/src/main/java/com/tacitknowledge/util/discovery/AggregateResourceListSource.java (revision 0) +++ 3rdParty_sources/tacitknowledge/discovery/src/main/java/com/tacitknowledge/util/discovery/AggregateResourceListSource.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,100 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.discovery; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +/** + * Combines two or more ResourceListSource implementations. + * + * @author Scott Askew (scott@tacitknowledge.com) + */ +public class AggregateResourceListSource implements ResourceListSource +{ + /** + * The list of sources to aggregate + */ + private List sources = new ArrayList(); + + /** + * Creates a new AggregateResourceListSource containing + * no ResourceListSources. + */ + public AggregateResourceListSource() + { + // Nothing to do + } + + /** + * Creates a new AggregateResourceListSource containing + * the given list of ResourceListSources. + * + * @param l the list of ResourceListSources to aggregate + */ + public AggregateResourceListSource(List l) + { + if (l == null) + { + throw new IllegalArgumentException("ResourceListSources list cannot be null"); + } + sources.addAll(l); + } + + /** + * Creates a new AggregateResourceListSource containing + * the given array of ResourceListSources. + * + * @param sources the array of ResourceListSources to aggregate + */ + public AggregateResourceListSource(ResourceListSource[] sources) + { + if (sources == null) + { + throw new IllegalArgumentException("ResourceListSources array cannot be null"); + } + this.sources.addAll(Arrays.asList(sources)); + } + + /** + * @see ResourceListSource#getResources(String, ResourceCriteria) + */ + public String[] getResources(String basePath, ResourceCriteria criteria) + { + Set resourceNames = new HashSet(); + for (Iterator i = sources.iterator(); i.hasNext();) + { + ResourceListSource source = (ResourceListSource) i.next(); + resourceNames.addAll(Arrays.asList(source.getResources(basePath, criteria))); + } + return (String[]) resourceNames.toArray(new String[resourceNames.size()]); + } + + /** + * Adds the given ResourceListSource to the list of sources + * used by getResources. + * + * @param source the source to add + */ + public void addResourceListSource(ResourceListSource source) + { + sources.add(source); + } +} Index: 3rdParty_sources/tacitknowledge/discovery/src/main/java/com/tacitknowledge/util/discovery/ArchiveResourceListSource.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/discovery/src/main/java/com/tacitknowledge/util/discovery/ArchiveResourceListSource.java (revision 0) +++ 3rdParty_sources/tacitknowledge/discovery/src/main/java/com/tacitknowledge/util/discovery/ArchiveResourceListSource.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,115 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.discovery; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +/** + * ResourceListSource that returns all resources in a specified + * directory that exist in an archive (.jar or .zip) in the classpath. Files + * that can be found in directories in the classpath are + * ignored. + *

+ * The performance of this class should be a consideration when using it. It + * performs an O(n) search through every entry in + * every .jar or .zip file in the classpath. On a medium- + * horsepower machine (1.2GHz Pentium III running Windows XP) with the JDK, + * Eclipse, and the full complement of libraries required for a typical web appliation, + * getResources(Strin) takes about 120 milliseconds to execute. + * + * @author Scott Askew (scott@tacitknowledge.com) + * @see DirectoryResourceListSource + */ +public class ArchiveResourceListSource extends ResourceListSourceSupport +{ + /** + * @see ResourceListSourceSupport#getResources(String) + */ + protected String[] getResources(String basePath) + { + String root = (basePath == null) ? "" : basePath; + + // Archives paths are always delimited by Unix-like forward slashes + // Convert any Windows paths over. + root = root.replace('\\', '/'); + + List resourceNames = new ArrayList(); + List jars = getJars(); + + for (Iterator i = jars.iterator(); i.hasNext();) + { + String jarName = (String) i.next(); + try + { + ZipFile file = new ZipFile(jarName); + resourceNames.addAll(getResources(file, root)); + } + catch (IOException e) + { + // Handle + } + } + + return (String[]) resourceNames.toArray(new String[resourceNames.size()]); + } + + /** + * Returns a list of JARs to search through. + * + * @return a list of JARs to search through + */ + protected List getJars() + { + return ClasspathUtils.getClasspathArchives(); + } + + /** + * Returns a list of file resources contained in the specified directory + * within a given Zip'd archive file. + * + * @param file the zip file containing the resources to return + * @param root the directory within the zip file containing the resources + * @return a list of file resources contained in the specified directory + * within a given Zip'd archive file + */ + private List getResources(ZipFile file, String root) + { + List resourceNames = new ArrayList(); + Enumeration e = file.entries(); + while (e.hasMoreElements()) + { + ZipEntry entry = (ZipEntry) e.nextElement(); + String name = entry.getName(); + if (name.startsWith(root) + && !(name.indexOf('/') > root.length()) + && !entry.isDirectory()) + { + // Calling File.getPath() cleans up the path so that it's using + // the proper path separators for the host OS + name = new File(name).getPath(); + resourceNames.add(name); + } + } + return resourceNames; + } +} Index: 3rdParty_sources/tacitknowledge/discovery/src/main/java/com/tacitknowledge/util/discovery/ClassCriteria.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/discovery/src/main/java/com/tacitknowledge/util/discovery/ClassCriteria.java (revision 0) +++ 3rdParty_sources/tacitknowledge/discovery/src/main/java/com/tacitknowledge/util/discovery/ClassCriteria.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,96 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.discovery; + +import java.io.File; +import java.lang.reflect.Modifier; + +/** + * Used to search on classes that implement one or more specified interfaces. + * + * @author Scott Askew (scott@tacitknowledge.com) + */ +public class ClassCriteria implements ResourceCriteria +{ + /** + * The interfaces that the subject class must implement + */ + private Class[] interfaces = null; + + /** + * Creates new ClassCritera object. + * + * @param c the interface that the subject class must implement + */ + public ClassCriteria(Class c) + { + this(new Class[] {c}); + } + + /** + * Creates new ClassCritera object. + * + * @param interfaces the set of interfaces that the subject class must + * implement + */ + public ClassCriteria(Class[] interfaces) + { + this.interfaces = interfaces; + } + + /** + * @see ResourceCriteria#matches(String) + */ + public boolean matches(String resourceName) + { + if (resourceName.endsWith(".class")) + { + String className = resourceName.substring(0, resourceName.length() - 6); + className = className.replace(File.separatorChar, '.'); + try + { + Class c = Class.forName(className); + boolean isRightType = isConcrete(c); + for (int i = 0; isRightType && i < interfaces.length; i++) + { + Class interfaceClass = interfaces[i]; + if (!interfaceClass.isAssignableFrom(c)) + { + isRightType = false; + } + } + return isRightType; + } + catch (ClassNotFoundException e) + { + return false; + } + } + return false; + } + + /** + * Determines if the given class is concrete. + * + * @param c the class to examine + * @return true if the given class is concrete (in other words, + * is not an interface or abstract class) + */ + private boolean isConcrete(Class c) + { + return !(c.isInterface() || Modifier.isAbstract(c.getModifiers())); + } +} \ No newline at end of file Index: 3rdParty_sources/tacitknowledge/discovery/src/main/java/com/tacitknowledge/util/discovery/ClassDiscoveryUtil.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/discovery/src/main/java/com/tacitknowledge/util/discovery/ClassDiscoveryUtil.java (revision 0) +++ 3rdParty_sources/tacitknowledge/discovery/src/main/java/com/tacitknowledge/util/discovery/ClassDiscoveryUtil.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,155 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.discovery; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +/** + * Convenience methods for using the class discovery API. + * + * @author Scott Askew (scott@tacitknowledge.com) + */ +public final class ClassDiscoveryUtil +{ + /** + * A ResourceListSource for both directories and archives in the classpath + */ + private static AggregateResourceListSource classpathResources = null; + static + { + classpathResources = + new AggregateResourceListSource( + new ResourceListSource[] { + new DirectoryResourceListSource(), + new ArchiveResourceListSource(), + }); + } + + /** + * Hidden constructor for utility class + */ + private ClassDiscoveryUtil() + { + // Hidden + } + + /** + * Adds the given ResourceListSource to the list of sources + * used by getResources. + * + * @param source the source to add + */ + public static void addResourceListSource(ResourceListSource source) + { + classpathResources.addResourceListSource(source); + } + + /** + * Returns the names of the file resources in the given directory. + * + * @param basePath the directory containing the resources + * @return the names of the file resources in the given directory + */ + public static String[] getResources(String basePath) + { + return getResources(basePath, null); + } + + /** + * Returns the names of the resources in the given directory that match the + * given regular expression. + * + * @param basePath the directory containing the resources + * @param regex the regular expression used to filter the resources + * @return the names of the resources in the given directory that match the + * given regular expression + */ + public static String[] getResources(String basePath, String regex) + { + RegexResourceCriteria criteria = new RegexResourceCriteria(regex); + String[] resourcesNames = classpathResources.getResources(basePath, criteria); + return resourcesNames; + } + + /** + * Returns an array of concrete classes in the given package that implement the + * specified interface. + * + * @param basePackage the name of the package containing the classes to discover + * @param requiredInterface the inteface that the returned classes must implement + * @return an array of concrete classes in the given package that implement the + * specified interface + */ + public static Class[] getClasses(String basePackage, Class requiredInterface) + { + return getClasses(basePackage, new Class[] {requiredInterface}); + } + + /** + * Returns an array of concrete classes in the given package that implement + * all of the specified interfaces. + * + * @param basePackage the name of the package containing the classes to discover + * @param requiredInterfaces the intefaces that the returned classes must implement + * @return an array of concrete classes in the given package that implement the + * specified interface + */ + public static Class[] getClasses(String basePackage, Class[] requiredInterfaces) + { + List classes = new ArrayList(); + ClassCriteria criteria = new ClassCriteria(requiredInterfaces); + String basePath = basePackage.replace('.', File.separatorChar); + String[] resourcesNames = classpathResources.getResources(basePath, criteria); + for (int i = 0; i < resourcesNames.length; i++) + { + String resourceName = resourcesNames[i]; + if (resourceName == null) + { + continue; + } + String className = getClassName(resourceName); + try + { + Class c = Class.forName(className); + classes.add(c); + } + catch (ClassNotFoundException e) + { + // This should not happen since we've already validated the class + } + } + return (Class[]) classes.toArray(new Class[classes.size()]); + } + + /** + * Returns the fully qualified class name represented by the given resource. + * + * @param resourceName the file name of the resource to convert to a class + * name; may not be null + * @return the fully qualified class name represented by the given resource + */ + public static String getClassName(String resourceName) + { + String className = resourceName.replace(File.separatorChar, '.'); + if (className.length() > 7) + { + className = className.substring(0, className.length() - 6); + } + return className; + } +} Index: 3rdParty_sources/tacitknowledge/discovery/src/main/java/com/tacitknowledge/util/discovery/ClasspathFileReader.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/discovery/src/main/java/com/tacitknowledge/util/discovery/ClasspathFileReader.java (revision 0) +++ 3rdParty_sources/tacitknowledge/discovery/src/main/java/com/tacitknowledge/util/discovery/ClasspathFileReader.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,293 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.discovery; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Used to load files from the classpath, similar to + * ClassLoader.getResourceAsStream(), but with the ability to + * detect file modifications. This class can be used to allow applications to + * respond to changes in resource files loaded via the classpath without needing + * to restart the VM. + *

+ * For example: + * + * // Assume that xmlContent has been populated previously + * + * if (classpathFileReader.isModified(pathToXml)) + * { + * InputStream is = classpathFileReader.getResourceAsStream(pathToXml); + * + * // Re-read is into xmlContent + * } + * + * + * @author Scott Askew (scott@tacitknowledge.com) + */ +public class ClasspathFileReader +{ + /** + * The number of milliseconds per second + */ + private static final int MILLIS_PER_SECOND = 1000; + + /** Used to log messages **/ + private Log log = LogFactory.getLog(getClass()); + + /** + * File locations and last modified times of all previously loaded files. + * Keys are file names relative to the classpath; values are + * CacheEntrys. + */ + private Map cache = new HashMap(); + + /** + * The last time the disk was stat'd. isModified uses this + * to reduce frequent file access. + */ + private long lastTimeDiskWasAccessed = 0L; + + /** + * The number of milliseconds between file accesses. + */ + private long reloadMillis = 0L; + + /** + * Contains information pertinent to previously loaded files. + * + * @author Scott Askew (scott@tacitknowledge.com) + */ + private class CacheEntry + { + /** + * The full path to the file; used to speed subsequent lookups + */ + private String fullPath = null; + + /** + * The file's last modification time + */ + private long lastModTime = 0L; + + /** + * Determines if the file is an actual file on disk, as opposed to a + * file inside a JAR + */ + private boolean isFileDirectlyOnDisk = true; + + /** + * Creates a new CacheEntry for a file contained in a JAR. + */ + public CacheEntry() + { + isFileDirectlyOnDisk = false; + } + + /** + * Creates a new CacheEntry for a file located directly on + * disk and not in any archive (.zip, .jar). + * + * @param file the file on disk; may not be null + */ + public CacheEntry(File file) + { + fullPath = file.getAbsolutePath(); + lastModTime = file.lastModified(); + } + + /** + * Determines if the file has been modified since the last time it was + * loaded + * + * @return true if the file has been modified since the + * last time it was loaded; if the file has never been loaded, + * or is in a JAR, true will always be returned + */ + public boolean isModified() + { + if (isFileDirectlyOnDisk) + { + File file = new File(fullPath); + if (!file.exists()) + { + return true; + } + else + { + return (lastModTime != file.lastModified()); + } + } + else + { + return false; + } + } + } + + /** + * Resolves the given file name, relative to the classpath, and returns a + * corresponding File object. Only files that actually exist + * on their own on disk (as opposed to being in a JAR) will be resolved. + * + * @param fileName the name of the file to lookup, relative to the classpath + * @return the requested file + * @throws FileNotFoundException if the file could not be found + */ + public File getFile(String fileName) throws FileNotFoundException + { + final String methodName = "getFile(): "; + lastTimeDiskWasAccessed = System.currentTimeMillis(); + List classpath = ClasspathUtils.getClasspathDirectories(); + + log.debug(methodName + "looking for file '" + fileName + "' in classpath"); + File file = findFile(fileName, classpath); + if (file != null) + { + log.debug(methodName + " found file '" + fileName + "'"); + return file; + } + log.debug(methodName + " did not find file '" + fileName + "'"); + throw new FileNotFoundException("Could not locate file in classpath"); + } + + /** + * Finds a file if it exists in a list of provided paths + * + * @param fileName the name of the file + * @param path a list of file paths + * @return a file or null if none is found + */ + private File findFile(String fileName, List path) + { + final String methodName = "findFile(): "; + String fileSeparator = System.getProperty("file.separator"); + for (Iterator i = path.iterator(); i.hasNext();) + { + String directory = (String) i.next(); + if (!directory.endsWith(fileSeparator) && !fileName.startsWith(fileSeparator)) + { + directory = directory + fileSeparator; + } + File file = new File(directory + fileName); + if (log.isDebugEnabled()) + { + log.debug(methodName + " checking '" + file.getAbsolutePath() + "'"); + } + if (file.exists() && file.canRead()) + { + log.debug(methodName + " found it - file is '" + file.getAbsolutePath() + "'"); + cache.put(fileName, new CacheEntry(file)); + return file; + } + } + return null; + } + + + /** + * Returns an input stream for reading the specified resource. This method + * functions similarly to + * ClassLoader.getResourceAsStream. Make sure you + * close the stream when you are done with it, as it allocates a + * file handle on the system, and leaving them open degrades + * performance or causes failures. + * + * @param fileName the name of the resource to resolve + * @return an input stream opened to the requested resource, or null + * if the resource could not be found + */ + public InputStream getResourceAsStream(String fileName) + { + try + { + File file = getFile(fileName); + cache.put(fileName, new CacheEntry(file)); + return new FileInputStream(file); + } + catch (FileNotFoundException e) + { + InputStream is = + Thread.currentThread().getContextClassLoader().getResourceAsStream(fileName); + if (is != null) + { + cache.put(fileName, new CacheEntry()); + } + return is; + } + } + + /** + * Returns true if the given resource has been modified since + * the last time it was loaded with getResourceAsStream. + * + * @param fileName the name of the file to check + * @return true if the given resource has been modified since + * the last time it was loaded with getResourceAsStream; + * otherwise false. true will be returned + * if the file has never been loaded by getResourceAsStream + * @see #getResourceAsStream(String) + */ + public boolean isModified(String fileName) + { + if ((lastTimeDiskWasAccessed + reloadMillis) > System.currentTimeMillis()) + { + return false; + } + lastTimeDiskWasAccessed = System.currentTimeMillis(); + CacheEntry cacheEntry = (CacheEntry) cache.get(fileName); + if (cacheEntry != null) + { + return cacheEntry.isModified(); + } + else + { + return true; + } + } + + /** + * Sets the number of seconds between up-to-date checks. The default is + * 0. + * + * @param seconds the number of seconds between up-to-date checks + */ + public void setReloadSeconds(int seconds) + { + reloadMillis = seconds * MILLIS_PER_SECOND; + } + + /** + * Returns the number of seconds between up-to-date checks. The default is + * 0. + * + * @return the number of seconds between up-to-date checks + */ + public int getReloadSeconds() + { + return (int) reloadMillis / MILLIS_PER_SECOND; + } +} Index: 3rdParty_sources/tacitknowledge/discovery/src/main/java/com/tacitknowledge/util/discovery/ClasspathUtils.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/discovery/src/main/java/com/tacitknowledge/util/discovery/ClasspathUtils.java (revision 0) +++ 3rdParty_sources/tacitknowledge/discovery/src/main/java/com/tacitknowledge/util/discovery/ClasspathUtils.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,255 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.discovery; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Method; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.StringTokenizer; + +/** + * Utility class for dealing with the classpath. + * + * @author Scott Askew (scott@tacitknowledge.com) + */ +public final class ClasspathUtils +{ + private static Log log = LogFactory.getLog(ClasspathUtils.class); + + /** + * Hidden constructor for utility class. + */ + private ClasspathUtils() + { + // Hidden constructor + } + + /** + * Returns the classpath as a list of directories. Any classpath component + * that is not a directory will be ignored. + * + * @return the classpath as a list of directories; if no directories can + * be found then an empty list will be returned + */ + public static List getClasspathDirectories() + { + List directories = new ArrayList(); + List components = getClasspathComponents(); + for (Iterator i = components.iterator(); i.hasNext();) + { + String possibleDir = (String) i.next(); + File file = new File(possibleDir); + if (file.isDirectory()) + { + directories.add(possibleDir); + } + } + List tomcatPaths = getTomcatPaths(); + if (tomcatPaths != null) + { + directories.addAll(tomcatPaths); + } + return directories; + } + + /** + * Returns the classpath as a list of the names of archive files. Any + * classpath component that is not an archive will be ignored. + * + * @return the classpath as a list of archive file names; if no archives can + * be found then an empty list will be returned + */ + public static List getClasspathArchives() + { + List archives = new ArrayList(); + List components = getClasspathComponents(); + for (Iterator i = components.iterator(); i.hasNext();) + { + String possibleDir = (String) i.next(); + File file = new File(possibleDir); + if (file.isFile() + && (file.getName().endsWith(".jar") || file.getName().endsWith(".zip"))) + { + archives.add(possibleDir); + } + } + return archives; + } + + /** + * Returns the classpath as a list directory and archive names. + * + * @return the classpath as a list of directory and archive file names; if + * no components can be found then an empty list will be returned + */ + public static List getClasspathComponents() + { + List components = new LinkedList(); + + // walk the classloader hierarchy, trying to get all the components we can + ClassLoader cl = Thread.currentThread().getContextClassLoader(); + + while ((null != cl) && (cl instanceof URLClassLoader)) + { + URLClassLoader ucl = (URLClassLoader) cl; + components.addAll(getUrlClassLoaderClasspathComponents(ucl)); + + try + { + cl = ucl.getParent(); + } + catch (SecurityException se) + { + cl = null; + } + } + + // walking the hierarchy doesn't guarantee we get everything, so + // lets grab the system classpath for good measure. + String classpath = System.getProperty("java.class.path"); + String separator = System.getProperty("path.separator"); + StringTokenizer st = new StringTokenizer(classpath, separator); + while (st.hasMoreTokens()) + { + String component = st.nextToken(); + // Calling File.getPath() cleans up the path so that it's using + // the proper path separators for the host OS + component = getCanonicalPath(component); + components.add(component); + } + + // Set removes any duplicates, return a list for the api. + return new LinkedList(new HashSet(components)); + } + + public static String getCanonicalPath(String path) { + File file = new File(path); + String canonicalPath = null; + if (file.exists()) { + try { + canonicalPath = file.getCanonicalPath(); + } catch (IOException e) { + log.warn("Error resolving filename to canonical file: " + e.toString()); + } + } + + if (canonicalPath == null) { + canonicalPath = file.getPath(); + } + + return canonicalPath; + } + + /** + * Get the list of classpath components + * + * @param ucl url classloader + * @return List of classpath components + */ + private static List getUrlClassLoaderClasspathComponents(URLClassLoader ucl) + { + List components = new ArrayList(); + + URL[] urls = new URL[0]; + + // Workaround for running on JBoss with UnifiedClassLoader3 usage + // We need to invoke getClasspath() method instead of getURLs() + if (ucl.getClass().getName().equals("org.jboss.mx.loading.UnifiedClassLoader3")) + { + try + { + Method classPathMethod = ucl.getClass().getMethod("getClasspath", new Class[] {}); + urls = (URL[]) classPathMethod.invoke(ucl, new Object[0]); + } + catch(Exception e) + { + LogFactory.getLog(ClasspathUtils.class).debug("Error invoking getClasspath on UnifiedClassLoader3: ", e); + } + } + else + { + // Use regular ClassLoader method to get classpath + urls = ucl.getURLs(); + } + + for (int i = 0; i < urls.length; i++) + { + URL url = urls[i]; + components.add(getCanonicalPath(url.getPath())); + } + + return components; + } + + /** + * If the system is running on Tomcat, this method will parse the + * common.loader property to reach deeper into the + * classpath to get Tomcat common paths + * + * @return a list of paths or null if tomcat paths not found + */ + private static List getTomcatPaths() + { + String tomcatPath = System.getProperty("catalina.home"); + if (tomcatPath == null) + { + //not running Tomcat + return null; + } + String commonClasspath = System.getProperty("common.loader"); + if (commonClasspath == null) + { + //didn't find the common classpath + return null; + } + StringBuffer buffer = new StringBuffer(commonClasspath); + String pathDeclaration = "${catalina.home}"; + int length = pathDeclaration.length(); + boolean doneReplace = false; + do + { + int start = commonClasspath.indexOf(pathDeclaration); + if (start >= 0) + { + buffer.replace(start, (start + length), tomcatPath); + commonClasspath = buffer.toString(); + } + else + { + doneReplace = true; + } + } + while (!doneReplace); + String[] paths = commonClasspath.split(","); + + List pathList = new ArrayList(paths.length); + for (int i = 0; i < paths.length; i++) { + String path = paths[i]; + pathList.add(getCanonicalPath(path)); + } + return pathList; + } +} \ No newline at end of file Index: 3rdParty_sources/tacitknowledge/discovery/src/main/java/com/tacitknowledge/util/discovery/DirectoryFilter.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/discovery/src/main/java/com/tacitknowledge/util/discovery/DirectoryFilter.java (revision 0) +++ 3rdParty_sources/tacitknowledge/discovery/src/main/java/com/tacitknowledge/util/discovery/DirectoryFilter.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,33 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.discovery; + +import java.io.File; +import java.io.FileFilter; + +/** + * @author Scott Askew (scott@tacitknowledge.com) + */ +class DirectoryFilter implements FileFilter +{ + /** + * @see java.io.FilenameFilter#accept(File, String) + */ + public boolean accept(File f) + { + return !f.isDirectory(); + } +} Index: 3rdParty_sources/tacitknowledge/discovery/src/main/java/com/tacitknowledge/util/discovery/DirectoryResourceListSource.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/discovery/src/main/java/com/tacitknowledge/util/discovery/DirectoryResourceListSource.java (revision 0) +++ 3rdParty_sources/tacitknowledge/discovery/src/main/java/com/tacitknowledge/util/discovery/DirectoryResourceListSource.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,87 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.discovery; + +import java.io.File; +import java.io.FileFilter; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * ResourceListSource that returns all resources in a specified + * directory that exist unpacked on disk in the classpath. In other words, only + * files that can be found in directories in the classpath are + * considered; files in archives (.jar, .zip) are not examined. + * + * @author Scott Askew (scott@tacitknowledge.com) + * @see ArchiveResourceListSource + */ +public class DirectoryResourceListSource extends ResourceListSourceSupport +{ + /** + * Filter used to remove directories from the result list + */ + private FileFilter directoryFilter = new DirectoryFilter(); + + /** + * @see ResourceListSourceSupport#getResources(String) + */ + protected String[] getResources(String basePath) + { + String root = (basePath == null) ? "" : basePath; + List resourceNames = new ArrayList(); + List directories = getDirectories(); + for (Iterator i = directories.iterator(); i.hasNext();) + { + String cpDirName = (String) i.next(); + String dirName = null; + if (root.startsWith(File.separator) || cpDirName.endsWith(File.separator)) + { + dirName = cpDirName + root; + } + else + { + dirName = cpDirName + File.separatorChar + root; + } + + File dir = new File(dirName); + if (dir.exists() && dir.isDirectory()) + { + File[] files = dir.listFiles(directoryFilter); + for (int j = 0; j < files.length; j++) + { + String fileName = files[j].getName(); + String resourceName = root + File.separator + fileName; + resourceName = new File(resourceName).getPath(); + resourceNames.add(resourceName); + } + } + } + + return (String[]) resourceNames.toArray(new String[resourceNames.size()]); + } + + /** + * Returns the directories to search through + * + * @return the directories to search through + */ + protected List getDirectories() + { + return ClasspathUtils.getClasspathDirectories(); + } +} Index: 3rdParty_sources/tacitknowledge/discovery/src/main/java/com/tacitknowledge/util/discovery/RegexResourceCriteria.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/discovery/src/main/java/com/tacitknowledge/util/discovery/RegexResourceCriteria.java (revision 0) +++ 3rdParty_sources/tacitknowledge/discovery/src/main/java/com/tacitknowledge/util/discovery/RegexResourceCriteria.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,59 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.discovery; + +import java.io.File; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Matches a resource based on a JDK 1.4-style regular expression. + * + * @author Scott Askew (scott@tacitknowledge.com) + * @see java.util.regex.Pattern + */ +public class RegexResourceCriteria implements ResourceCriteria +{ + /** + * The pattern to match. This is threadsafe. + */ + private Pattern pattern = null; + + /** + * Creates a new RegexResourceCriteria that matches resources + * based on the given regular expression. + * + * @param regex the regular expression to match resources against + */ + public RegexResourceCriteria(String regex) + { + pattern = Pattern.compile(regex); + } + + /** + * @see ResourceCriteria#matches(String) + */ + public boolean matches(String resourceName) + { + if (resourceName == null) + { + return false; + } + File file = new File(resourceName); + Matcher matcher = pattern.matcher(file.getName()); + return matcher.matches(); + } +} \ No newline at end of file Index: 3rdParty_sources/tacitknowledge/discovery/src/main/java/com/tacitknowledge/util/discovery/ResourceCriteria.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/discovery/src/main/java/com/tacitknowledge/util/discovery/ResourceCriteria.java (revision 0) +++ 3rdParty_sources/tacitknowledge/discovery/src/main/java/com/tacitknowledge/util/discovery/ResourceCriteria.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,35 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.discovery; + +/** + * Used to define search criteria for the class discovery API. Instances of + * this interface are used as filters by ResourceListSources. + * + * @author Scott Askew (scott@tacitknowledge.com) + * @see ResourceListSource#getResources(String, ResourceCriteria) + */ +public interface ResourceCriteria +{ + /** + * Determines if the given resource matches specific search criteria. + * + * @param resourceName the name of the resource to match + * @return true if the given resource matches specific search + * criteria; otherwise, false + */ + public boolean matches(String resourceName); +} Index: 3rdParty_sources/tacitknowledge/discovery/src/main/java/com/tacitknowledge/util/discovery/ResourceListSource.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/discovery/src/main/java/com/tacitknowledge/util/discovery/ResourceListSource.java (revision 0) +++ 3rdParty_sources/tacitknowledge/discovery/src/main/java/com/tacitknowledge/util/discovery/ResourceListSource.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,35 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.discovery; + +/** + * @author Scott Askew (scott@tacitknowledge.com) + */ +public interface ResourceListSource +{ + /** + * Returns a list of the names of the resources in the given directory that + * match the specified search criteria. + * + * @param basePath the directory to search; the search is not + * recursive; may be null + * @param criteria the search criteria to filter on; may be null + * @return a list of the names of the resources in the given directory that + * match the specified search criteria; if no matched are found, then + * an empty array is returned + */ + public String[] getResources(String basePath, ResourceCriteria criteria); +} \ No newline at end of file Index: 3rdParty_sources/tacitknowledge/discovery/src/main/java/com/tacitknowledge/util/discovery/ResourceListSourceSupport.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/discovery/src/main/java/com/tacitknowledge/util/discovery/ResourceListSourceSupport.java (revision 0) +++ 3rdParty_sources/tacitknowledge/discovery/src/main/java/com/tacitknowledge/util/discovery/ResourceListSourceSupport.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,63 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.discovery; + +import java.util.ArrayList; +import java.util.List; + +/** + * Provides base functionality of ResourceListSource implementations. + * Subclasses do not need to deal with ResourceCriteria; they only + * need to provide lists of resources to examine. + * + * @author Scott Askew (scott@tacitknowledge.com) + */ +public abstract class ResourceListSourceSupport implements ResourceListSource +{ + /** + * @see ResourceListSource#getResources(String, ResourceCriteria) + */ + public String[] getResources(String basePath, ResourceCriteria criteria) + { + String root = (basePath == null) ? "" : basePath; + List resources = new ArrayList(); + String[] resourceNames = getResources(root); + + for (int i = 0; i < resourceNames.length; i++) + { + String resourceName = resourceNames[i]; + boolean addToResourceList = true; + if (criteria != null) + { + addToResourceList = criteria.matches(resourceName); + } + + if (addToResourceList) + { + resources.add(resourceName); + } + } + return (String[]) resources.toArray(new String[resources.size()]); + } + + /** + * Returns an iterator of the names of resources in the given path. + * + * @param root the directory containing to resources to enumerate + * @return an iterator of the names of resources in the given path + */ + protected abstract String[] getResources(String root); +} Index: 3rdParty_sources/tacitknowledge/discovery/src/main/java/com/tacitknowledge/util/discovery/WebAppResourceListSource.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/discovery/src/main/java/com/tacitknowledge/util/discovery/WebAppResourceListSource.java (revision 0) +++ 3rdParty_sources/tacitknowledge/discovery/src/main/java/com/tacitknowledge/util/discovery/WebAppResourceListSource.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,39 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.discovery; + +/** + * A ResourceListSource that exposes the WEB-INF/classes + * directory and web application libraries to the discovery API. + * + * @author Scott Askew (scott@tacitknowledge.com) + */ +public class WebAppResourceListSource extends AggregateResourceListSource +{ + /** + * Creates a new WebAppResourceListSource + * + * @param webinfPath the real, on-disk path to WEB-INF. + */ + public WebAppResourceListSource(final String webinfPath) + { + super(); + ArchiveResourceListSource jarsrc = new WebArchiveResourceListSource(webinfPath); + DirectoryResourceListSource dirsrc = new WebDirectoryResourceListSource(webinfPath); + addResourceListSource(jarsrc); + addResourceListSource(dirsrc); + } +} Index: 3rdParty_sources/tacitknowledge/discovery/src/main/java/com/tacitknowledge/util/discovery/WebArchiveResourceListSource.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/discovery/src/main/java/com/tacitknowledge/util/discovery/WebArchiveResourceListSource.java (revision 0) +++ 3rdParty_sources/tacitknowledge/discovery/src/main/java/com/tacitknowledge/util/discovery/WebArchiveResourceListSource.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,83 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.discovery; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +/** + * Source for resources within libraries within web archive (WAR) files. + * + * @author Scott Askew (scott@tacitknowledge.com) + */ +class WebArchiveResourceListSource extends ArchiveResourceListSource +{ + /** + * The full path on-disk to /WEB-INF + */ + private String path = null; + + /** + * Creates a new WebArchiveResourceListSource + * + * @param path the full on-disk path to WEB-INF + */ + public WebArchiveResourceListSource(String path) + { + this.path = ClasspathUtils.getCanonicalPath(path); + } + + /** + * {@inheritDoc} + */ + protected List getJars() + { + String libPath = path + File.separator + "lib"; + List jars = new ArrayList(); + String[] entries = getLibEntries(libPath); + for (String jar : entries) + { + if (jar.endsWith(".jar") || jar.endsWith(".zip")) + { + String jarName = libPath + File.separator + jar; + jars.add(jarName); + } + } + return jars; + } + + /** + * Returns a list of entries in the given directory. This method is + * separated so that unit tests can override it to return test-friendly + * file names. + * + * @param dir the dir containing the files to return + * @return the files and directories in the given directory, or + * an empty array if the given directory does not exist or is + * a file. + */ + protected String[] getLibEntries(String dir) + { + String[] entries = new String[0]; + File libdir = new File(dir); + if (libdir.exists() && libdir.isDirectory()) + { + entries = libdir.list(); + } + return entries; + } +} \ No newline at end of file Index: 3rdParty_sources/tacitknowledge/discovery/src/main/java/com/tacitknowledge/util/discovery/WebDirectoryResourceListSource.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/discovery/src/main/java/com/tacitknowledge/util/discovery/WebDirectoryResourceListSource.java (revision 0) +++ 3rdParty_sources/tacitknowledge/discovery/src/main/java/com/tacitknowledge/util/discovery/WebDirectoryResourceListSource.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,53 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.discovery; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +/** + * Source for resources within the classes directory in web archive (WAR) files. + * + * @author Scott Askew (scott@tacitknowledge.com) + */ +class WebDirectoryResourceListSource extends DirectoryResourceListSource +{ + /** + * The full path on-disk to /WEB-INF + */ + private String path = null; + + /** + * Creates a new WebDirectoryResourceListSource + * + * @param path the full on-disk path to WEB-INF + */ + public WebDirectoryResourceListSource(String path) + { + this.path = ClasspathUtils.getCanonicalPath(path); + } + + /** + * {@inheritDoc} + */ + protected List getDirectories() + { + List dirs = new ArrayList(1); + dirs.add(path + File.separator + "classes"); + return dirs; + } +} \ No newline at end of file Index: 3rdParty_sources/tacitknowledge/discovery/src/main/resources/META-INF/log4j.properties =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/discovery/src/main/resources/META-INF/log4j.properties (revision 0) +++ 3rdParty_sources/tacitknowledge/discovery/src/main/resources/META-INF/log4j.properties (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,8 @@ +# Set root logger level to DEBUG and its only appender to CONSOLE +log4j.rootLogger=DEBUG, CONSOLE + +# If you would like to have output simply be on the console, this is what you want +# This is useful for debugging. Comment out the previous two appender sections, of course +log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender +log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout +log4j.appender.CONSOLE.layout.ConversionPattern=%d %-4r [%t] %-5p %c{1} %x - %m%n \ No newline at end of file Index: 3rdParty_sources/tacitknowledge/discovery/src/test/java/com/tacitknowledge/util/discovery/ArchiveResourceListSourceTest.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/discovery/src/test/java/com/tacitknowledge/util/discovery/ArchiveResourceListSourceTest.java (revision 0) +++ 3rdParty_sources/tacitknowledge/discovery/src/test/java/com/tacitknowledge/util/discovery/ArchiveResourceListSourceTest.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,96 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.discovery; + +import java.io.File; + +import junit.framework.TestCase; + +/** + * Tests the ArchiveResourceListSourceTest class. + * + * @author Scott Askew (scott@tacitknowledge.com) + */ +public class ArchiveResourceListSourceTest extends TestCase +{ + /** + * Constructor for ArchiveResourceListSourceTest. + * + * @param name the name of the test to run + */ + public ArchiveResourceListSourceTest(String name) + { + super(name); + } + + /** + * Tests basic functionality + */ + public void testGetResources() + { + ArchiveResourceListSource source = new ArchiveResourceListSource(); + String[] names = source.getResources("junit/framework"); + + // As of 3.8.1, the core JUnit framework has 12 classes in it. JUnit + // is very stable (no new releases in two years); however, if a new version + // were to come out, this test may fail if new classes are added in new + // versions + // Futhermore, if the JUnit library is in the path more than once, 12 + // framework classes will be found per jar. + assertEquals(0, names.length % 12); + + // Make sure one of these is a class + try + { + // Build out the package name from the directory path + String className = names[0].replace(File.separatorChar, '.'); + // Chop off the .class + className = className.substring(0, className.length() - 6); + + Class.forName(className); + } + catch (ClassNotFoundException e) + { + e.printStackTrace(); + fail("Could not load class found in getResources(String)"); + } + } + + /** + * Ensures that getResources returns an empty array whenever + * no resources are found + */ + public void testGetResourcesAlwaysReturnsNotNull() + { + ArchiveResourceListSource source = new ArchiveResourceListSource(); + String[] names = source.getResources("foo/bar"); + assertNotNull(names); + assertEquals(0, names.length); + } + + /** + * A null path argument should return all resources in the + * root of any archive in the classpath. Unfortunately there's no way to + * know exactly how many this should be, so a test cannot be made for the + * number. + */ + public void testGetResourcesHandlesNullPath() + { + ArchiveResourceListSource source = new ArchiveResourceListSource(); + String[] names = source.getResources(null); + assertNotNull(names); + } +} Index: 3rdParty_sources/tacitknowledge/discovery/src/test/java/com/tacitknowledge/util/discovery/ClassCriteriaTest.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/discovery/src/test/java/com/tacitknowledge/util/discovery/ClassCriteriaTest.java (revision 0) +++ 3rdParty_sources/tacitknowledge/discovery/src/test/java/com/tacitknowledge/util/discovery/ClassCriteriaTest.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,83 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.discovery; + +import java.io.File; +import java.io.Serializable; + +import junit.framework.TestCase; + +/** + * Tests the ClassCritera class. + *

+ * This class implements Serializable so that it can be used as + * test fodder when determining if a named class implements all of a specific + * number of interfaces. + * + * @author Scott Askew (scott@tacitknowledge.com) + */ +public class ClassCriteriaTest extends TestCase implements Serializable +{ + + /** + * Constructor for ClassCriteriaTest. + * + * @param name the name of the test to run + */ + public ClassCriteriaTest(String name) + { + super(name); + } + + /** + * Valiates the normal operation of matches when matching on a + * single class. + */ + public void testMatchesSingleInterface() + { + ClassCriteria criteria = new ClassCriteria(getClass()); + String className = getClass().getName().replace('.', File.separatorChar); + assertTrue(criteria.matches(className + ".class")); + assertFalse(criteria.matches(new File("java/lang/Object.class").getPath())); + } + + /** + * Valiates the normal operation of matches when matching on a + * single class. + */ + public void testMatchesMultipleInterfaces() + { + ClassCriteria criteria = new ClassCriteria(new Class[] {getClass(), Serializable.class}); + String className = getClass().getName().replace('.', File.separatorChar); + // This class implements both interfaces + assertTrue(criteria.matches(className + ".class")); + // Object doesn't implement either + assertFalse(criteria.matches(new File("java/lang/Object.class").getPath())); + // String implement Serializable, but not ClassCriteriaTest + assertFalse(criteria.matches(new File("java/lang/String.class").getPath())); + } + + /** + * Ensures that classes that implement the right interfaces but are not + * concrete themselves are do not match. + */ + public void testMatchesOnlyConcreteClasses() + { + ClassCriteria criteria = new ClassCriteria(Serializable.class); + String className = TestAbstractClass.class.getName().replace('.', File.separatorChar); + assertFalse(criteria.matches(className + ".class")); + } +} \ No newline at end of file Index: 3rdParty_sources/tacitknowledge/discovery/src/test/java/com/tacitknowledge/util/discovery/ClassDiscoveryUtilTest.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/discovery/src/test/java/com/tacitknowledge/util/discovery/ClassDiscoveryUtilTest.java (revision 0) +++ 3rdParty_sources/tacitknowledge/discovery/src/test/java/com/tacitknowledge/util/discovery/ClassDiscoveryUtilTest.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,102 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.discovery; + +import junit.framework.TestCase; +import junit.framework.TestResult; + +import java.io.File; + +/** + * Tests the ClassDiscoveryUtil class. + * + * @author Scott Askew (scott@tacitknowledge.com) + */ +public class ClassDiscoveryUtilTest extends TestCase +{ + /** + * Constructor for ClassDiscoveryUtilTest. + * + * @param name the name of the test to run + */ + public ClassDiscoveryUtilTest(String name) + { + super(name); + } + + /** + * Basic test of ClassDiscoveryUtil.getResources(String, String) + * when the resource lives in a directory. + */ + public void testGetRegexResourcesInDirectory() + { + String dir = getClass().getPackage().getName().replace('.', File.separatorChar); + String regex = "^ClassDiscoveryUtilTest.cl.+"; + String[] names = ClassDiscoveryUtil.getResources(dir, regex); + for (int i = 0; i < names.length; i++) + { + System.out.println("name " + i + " is '" + names[i] + "'"); + } + assertNotNull(names); + assertEquals(1, names.length); + assertEquals(dir + File.separator + "ClassDiscoveryUtilTest.class", names[0]); + } + + /** + * Basic test of ClassDiscoveryUtil.getResources(String, String) + * when the resource lives in an archive. + */ + public void testGetRegexResourcesInArchive() + { + String dir = TestCase.class.getPackage().getName().replace('.', File.separatorChar); + String regex = "^TestCase.+"; + String[] names = ClassDiscoveryUtil.getResources(dir, regex); + assertNotNull(names); + assertTrue(names.length >= 1); + assertEquals(dir + File.separator + "TestCase.class", names[0]); + } + + /** + * Basic test of ClassDiscoveryUtil.getClasses(String, Class) + * when the resource lives in a directory. + */ + public void testGetClassesInDirectory() + { + Class[] classes + = ClassDiscoveryUtil.getClasses(getClass().getPackage().getName(), getClass()); + for (int i = 0; i < classes.length; i++) + { + System.err.println("name " + i + " is '" + classes[i].getName() + "'"); + } + assertNotNull(classes); + assertEquals(1, classes.length); + assertEquals(getClass().getName(), classes[0].getName()); + } + + /** + * Basic test of ClassDiscoveryUtil.getClasses(String, Class) + * when the resource lives in an archive. + */ + public void testGetClassesInArchive() + { + String packageName = TestCase.class.getPackage().getName(); + Class[] classes + = ClassDiscoveryUtil.getClasses(packageName, TestResult.class); + assertNotNull(classes); + assertTrue(classes.length >= 1); + assertEquals(TestResult.class.getName(), classes[0].getName()); + } +} Index: 3rdParty_sources/tacitknowledge/discovery/src/test/java/com/tacitknowledge/util/discovery/ClasspathReaderTest.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/discovery/src/test/java/com/tacitknowledge/util/discovery/ClasspathReaderTest.java (revision 0) +++ 3rdParty_sources/tacitknowledge/discovery/src/test/java/com/tacitknowledge/util/discovery/ClasspathReaderTest.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,132 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.discovery; + +import junit.framework.TestCase; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.InputStream; + +/** + * Test the ClasspathReader + * + * @author Mike Hardy + */ +public class ClasspathReaderTest extends TestCase +{ + /** The reader we're testing */ + private ClasspathFileReader reader = new ClasspathFileReader(); + + /** + * Test loading a non-existent class + */ + public void testLoadBogusFile() + { + try + { + reader.getFile("bogus"); + fail("Should have gotten a FileNotFoundException"); + } + catch (FileNotFoundException fnfe) + { + // this is good + } + catch (Exception e) + { + fail("Should have been a FileNotFoundException, was: " + e.getClass().getName()); + } + } + + /** + * Test loading a file from the classpath + * + * @exception Exception if anything goes wrong + */ + public void testBasicFileLoad() throws Exception + { + // load ourselves up + reader.getFile("com" + File.separator + + "tacitknowledge" + File.separator + + "util" + File.separator + + "discovery" + File.separator + + "ClasspathFileReader.class"); + + // TODO actually assert something - harder than it sounds + } + + /** + * Test file loading as a stream + * + * @exception Exception if anything goes wrong + */ + public void testBasicStreamLoad() throws Exception + { + // load as a stream + InputStream stream = reader.getResourceAsStream("com" + File.separator + + "tacitknowledge" + File.separator + + "util" + File.separator + + "discovery" + File.separator + + "ClasspathFileReader.class"); + stream.close(); + + // TODO actually assert something - harder than it sounds + } + + /** + * Test modification access + * + * @exception Exception if anything goes wrong + */ + public void testModification() throws Exception + { + // load ourselves up + File file = reader.getFile("com" + File.separator + + "tacitknowledge" + File.separator + + "util" + File.separator + + "discovery" + File.separator + + "ClasspathFileReader.class"); + + // no reload time, but no modification, should be false + reader.setReloadSeconds(0); + boolean modified = reader.isModified("com" + File.separator + + "tacitknowledge" + File.separator + + "util" + File.separator + + "discovery" + File.separator + + "ClasspathFileReader.class"); + assertFalse(modified); + + // alter the file, set a reload time, should be false + file.setLastModified(System.currentTimeMillis()); + reader.setReloadSeconds(1); + assertEquals(1, reader.getReloadSeconds()); + modified = reader.isModified("com" + File.separator + + "tacitknowledge" + File.separator + + "util" + File.separator + + "discovery" + File.separator + + "ClasspathFileReader.class"); + assertFalse(modified); + + // wait for reload time to expire, should be true + Thread.sleep(1500); + modified = reader.isModified("com" + File.separator + + "tacitknowledge" + File.separator + + "util" + File.separator + + "discovery" + File.separator + + "ClasspathFileReader.class"); + assertTrue(modified); + } +} Index: 3rdParty_sources/tacitknowledge/discovery/src/test/java/com/tacitknowledge/util/discovery/ClasspathUtilsTest.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/discovery/src/test/java/com/tacitknowledge/util/discovery/ClasspathUtilsTest.java (revision 0) +++ 3rdParty_sources/tacitknowledge/discovery/src/test/java/com/tacitknowledge/util/discovery/ClasspathUtilsTest.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,162 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.discovery; + +import java.io.File; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import junit.framework.TestCase; + +public class ClasspathUtilsTest extends TestCase { + + protected void setUp() throws Exception { + super.setUp(); + } + + /** + * return the number of times o appears in c + * @param c collection + * @param o object + * @return number of times o appears in c + */ + protected int frequency(Collection c, Object o) + { + int count = 0; + for (Iterator iter = c.iterator(); iter.hasNext();) + { + Object element = iter.next(); + if (o.equals(element)) + { + ++count; + } + } + + return count; + } + + public void testGetClasspathComponentsOnSystemClasspath() + { + String classpath = System.getProperty("java.class.path"); + try + { + String pathSep = System.getProperty("path.separator"); + String fileSep = System.getProperty("file.separator"); + + List components = ClasspathUtils.getClasspathComponents(); + Collections.sort(components); + String myAdditionalDir = "some" + fileSep + "package" + fileSep + "dir"; + // first make sure my package isn't in the classpath yet + assertTrue(Collections.binarySearch(components, myAdditionalDir) < 0); + // add my package to the classpath + System.setProperty("java.class.path", classpath + pathSep + myAdditionalDir); + components = ClasspathUtils.getClasspathComponents(); + Collections.sort(components); + assertTrue(Collections.binarySearch(components, myAdditionalDir) >= 0); + } + finally + { + // set classpath back regardless of the outcome of the test + System.setProperty("java.class.path", classpath); + } + } + + public void testGetClasspathComponentsFromClassLoader() throws MalformedURLException + { + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + try + { + File file = new File("/some/package/dir"); + URL[] urls = new URL[] { file.toURL() }; + // assert my package can't be found yet + List components = ClasspathUtils.getClasspathComponents(); + Collections.sort(components); + assertTrue(Collections.binarySearch(components, file.getPath()) < 0); + // test my package is found + URLClassLoader ucl = new URLClassLoader(urls, classLoader); + Thread.currentThread().setContextClassLoader(ucl); + components = ClasspathUtils.getClasspathComponents(); + Collections.sort(components); + assertTrue( Collections.binarySearch(components, file.getAbsolutePath()) >= 0); + } + finally + { + Thread.currentThread().setContextClassLoader(classLoader); + } + } + + public void testGetClasspathComponentsFromParentClassLoader() throws MalformedURLException + { + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + try + { + File file = new File("/some/package/dir"); + URL[] urls = new URL[] { file.toURL() }; + // assert my package can't be found yet + List components = ClasspathUtils.getClasspathComponents(); + Collections.sort(components); + assertTrue(Collections.binarySearch(components, file.getPath()) < 0); + // test my package is found + URLClassLoader parentClassLoader = new URLClassLoader(urls, classLoader); + URLClassLoader childClassLoader = new URLClassLoader(new URL[] {}, parentClassLoader); + Thread.currentThread().setContextClassLoader(childClassLoader); + components = ClasspathUtils.getClasspathComponents(); + Collections.sort(components); + assertTrue(Collections.binarySearch(components, file.getAbsolutePath()) >= 0); + } + finally + { + Thread.currentThread().setContextClassLoader(classLoader); + } + } + + public void testGetClasspathComponentsDontHaveDuplicates() throws MalformedURLException + { + String classpath = System.getProperty("java.class.path"); + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + try + { + String pathSep = System.getProperty("path.separator"); + File file = new File("/some/package/dir"); + URL[] urls = new URL[] { file.toURL() }; + // assert my package can't be found yet + List components = ClasspathUtils.getClasspathComponents(); + Collections.sort(components); + assertTrue(Collections.binarySearch(components, file.getPath()) < 0); + // test my package is found and only appears once + URLClassLoader parentClassLoader = new URLClassLoader(urls, classLoader); + URLClassLoader childClassLoader = new URLClassLoader(new URL[] {}, parentClassLoader); + Thread.currentThread().setContextClassLoader(childClassLoader); + System.setProperty("java.class.path", classpath + pathSep + file.getPath()); + components = ClasspathUtils.getClasspathComponents(); + Collections.sort(components); + assertTrue(Collections.binarySearch(components, file.getPath()) >= 0); + assertTrue(frequency(components, file.getPath()) == 1); + } + finally + { + Thread.currentThread().setContextClassLoader(classLoader); + System.setProperty("java.class.path", classpath); + } + + } + +} Index: 3rdParty_sources/tacitknowledge/discovery/src/test/java/com/tacitknowledge/util/discovery/DirectoryResourceListSourceTest.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/discovery/src/test/java/com/tacitknowledge/util/discovery/DirectoryResourceListSourceTest.java (revision 0) +++ 3rdParty_sources/tacitknowledge/discovery/src/test/java/com/tacitknowledge/util/discovery/DirectoryResourceListSourceTest.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,75 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.discovery; + +import java.io.File; + +import junit.framework.TestCase; + +/** + * Tests the DirectoryResourceListSourceTest class. + * + * @author Scott Askew (scott@tacitknowledge.com) + */ +public class DirectoryResourceListSourceTest extends TestCase +{ + /** + * Constructor for DirectoryResourceListSourceTest. + * + * @param name the name of the test to run + */ + public DirectoryResourceListSourceTest(String name) + { + super(name); + } + + /** + * Tests the normal operation of getResources + */ + public void testGetResources() + { + DirectoryResourceListSource source = new DirectoryResourceListSource(); + String dir = getClass().getPackage().getName().replace('.', File.separatorChar); + String[] names = source.getResources(dir); + assertNotNull(names); + assertTrue(names.length > 0); + } + + /** + * Ensures that empty resuls are represented by an empty array, not null + */ + public void testGetResourcesHandlesEmptyResults() + { + DirectoryResourceListSource source = new DirectoryResourceListSource(); + String dir = "foo/bar"; + String[] names = source.getResources(dir); + assertNotNull(names); + assertEquals(0, names.length); + } + + /** + * A null path argument should return all resources in the + * root of any directory in the classpath. Unfortunately there's no way to + * know exactly how many this should be, so a test cannot be made for the + * number. + */ + public void testGetResourcesAcceptsNull() + { + DirectoryResourceListSource source = new DirectoryResourceListSource(); + String[] names = source.getResources(null); + assertNotNull(names); + } +} Index: 3rdParty_sources/tacitknowledge/discovery/src/test/java/com/tacitknowledge/util/discovery/RegexResourceCriteriaTest.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/discovery/src/test/java/com/tacitknowledge/util/discovery/RegexResourceCriteriaTest.java (revision 0) +++ 3rdParty_sources/tacitknowledge/discovery/src/test/java/com/tacitknowledge/util/discovery/RegexResourceCriteriaTest.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,49 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.discovery; + +import junit.framework.TestCase; + +/** + * Tests the RegexResourceCriteria class. + * + * @author Scott Askew (scott@tacitknowledge.com) + */ +public class RegexResourceCriteriaTest extends TestCase +{ + /** + * Constructor for RegexResourceCriteriaTest. + * + * @param name the name of the test to run + */ + public RegexResourceCriteriaTest(String name) + { + super(name); + } + + /** + * Validates the normal operation of the matches method. + */ + public void testMatches() + { + RegexResourceCriteria criteria = new RegexResourceCriteria("^Test[0-9]+.txt"); + assertTrue(criteria.matches("foo/bar/Test12.txt")); + assertTrue(criteria.matches("Test12.txt")); + assertFalse(criteria.matches("foo/bar/Test.txt")); + assertFalse(criteria.matches("")); + assertFalse(criteria.matches(null)); + } +} Index: 3rdParty_sources/tacitknowledge/discovery/src/test/java/com/tacitknowledge/util/discovery/TestAbstractClass.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/discovery/src/test/java/com/tacitknowledge/util/discovery/TestAbstractClass.java (revision 0) +++ 3rdParty_sources/tacitknowledge/discovery/src/test/java/com/tacitknowledge/util/discovery/TestAbstractClass.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,28 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.discovery; + +import java.io.Serializable; + +/** + * Used with ClassCriteriaTest. + * + * @author Scott Askew (scott@tacitknowledge.com) + */ +public abstract class TestAbstractClass implements Serializable +{ + // Just an abstract class used for testing +} Index: 3rdParty_sources/tacitknowledge/discovery/src/test/java/com/tacitknowledge/util/discovery/WebArchiveResourceListSourceTest.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/discovery/src/test/java/com/tacitknowledge/util/discovery/WebArchiveResourceListSourceTest.java (revision 0) +++ 3rdParty_sources/tacitknowledge/discovery/src/test/java/com/tacitknowledge/util/discovery/WebArchiveResourceListSourceTest.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,68 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.discovery; + +import java.io.File; +import java.util.List; + +import junit.framework.TestCase; + +/** + * + * + * @author Scott Askew (scott@tacitknowledge.com) + */ +public class WebArchiveResourceListSourceTest extends TestCase +{ + /** + * Constructor for WebArchiveResourceListSourceTest. + * + * @param name the name of the test to run + */ + public WebArchiveResourceListSourceTest(String name) + { + super(name); + } + + /** + * Tests the getJars method. + */ + public void testJars() + { + final String path = File.separator + "test"; + final String libPath = path + File.separator + "lib"; + WebArchiveResourceListSource s = new WebArchiveResourceListSource(path) { + /** + * {@inheritDoc} + */ + protected String[] getLibEntries(String dir) + { + assertEquals(dir, libPath); + String[] files = new String[] { + "test.jar", + "test.zip", + "fail.txt", + }; + return files; + } + }; + + List jars = s.getJars(); + assertEquals(2, jars.size()); + assertTrue(jars.contains(libPath + File.separator + "test.jar")); + assertTrue(jars.contains(libPath + File.separator + "test.zip")); + } +} Index: 3rdParty_sources/tacitknowledge/discovery/src/test/java/com/tacitknowledge/util/discovery/WebDirectoryResourceListSourceTest.java =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/discovery/src/test/java/com/tacitknowledge/util/discovery/WebDirectoryResourceListSourceTest.java (revision 0) +++ 3rdParty_sources/tacitknowledge/discovery/src/test/java/com/tacitknowledge/util/discovery/WebDirectoryResourceListSourceTest.java (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,52 @@ +/* Copyright 2004 Tacit Knowledge + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tacitknowledge.util.discovery; + +import java.io.File; +import java.util.List; + +import junit.framework.TestCase; + +/** + * Tests the WebDirectoryResourceListSource class. + * + * @author Scott Askew (scott@tacitknowledge.com) + */ +public class WebDirectoryResourceListSourceTest extends TestCase +{ + /** + * Constructor for WebDirectoryResourceListSourceTest. + * + * @param name the name of the test to run + */ + public WebDirectoryResourceListSourceTest(String name) + { + super(name); + } + + /** + * Tests the getDirectories method. + */ + public void testDirectories() + { + String path = File.separator + "test"; + String classPath = path + File.separator + "classes"; + WebDirectoryResourceListSource source = new WebDirectoryResourceListSource(path); + List result = source.getDirectories(); + assertEquals(1, result.size()); + assertEquals(classPath, (String) result.get(0)); + } +} Index: 3rdParty_sources/tacitknowledge/discovery/src/test/resources/log4j.properties =================================================================== diff -u --- 3rdParty_sources/tacitknowledge/discovery/src/test/resources/log4j.properties (revision 0) +++ 3rdParty_sources/tacitknowledge/discovery/src/test/resources/log4j.properties (revision 60fd31cb3d049512a0549ad11dbbe4ba81cae54e) @@ -0,0 +1,8 @@ +# Set root logger level to DEBUG and its only appender to CONSOLE +log4j.rootLogger=DEBUG, CONSOLE + +# If you would like to have output simply be on the console, this is what you want +# This is useful for debugging. Comment out the previous two appender sections, of course +log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender +log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout +log4j.appender.CONSOLE.layout.ConversionPattern=%d %-4r [%t] %-5p %c{1} %x - %m%n \ No newline at end of file