...via RPC over HTTPS :
1. Ensure you have your root certificate in your trusted authority store in the Microsoft Management Console (mmc.exe)
2. Ensure you have your personal certificate in your personal key store in the Microsoft Management Console (mmc.exe)
3. Ensure you've got the correct server settings
Friday, April 17, 2009
Monday, April 13, 2009
Setting up a VPN connection to an Astaro security applicance on Mac OS X
So I finally got a working VPN configuration to be able to connect my shiny new MacBook Pro to our VPN on our production system which uses an Astaro firewall appliance. Turns out it's fairly easy too :
Oh, and make sure you've actually got Tunnelblick started, or else you won't be able to connect ;)
- Go to your Astaro firewall's user profile page, ie login as a user, and download the key / OpenVPN configuration package for your router. The great thing about these appliances is that they provide the package for you right from the device
- Download Tunnelblick. It's an (apparently very easy to use) GUI front end for OpenVPN on Mac OS X. The latest version (as of the time of this writing) is 3.0 beta 10. Tunnelblick is hosted on Google Code. The installation's even easier. Load up the disk image, and drag the sole application icon into your applications folder.
- Open the VPN package you downloaded from your Astaro firewall appliance, and copy everything in it to the ~/Library/openvpn folder created by the Tunnelblick installation
- Tunnelblick should automatically detect the additional files and provide a listing for the Astaro configuration in its connection list. Once you've got the item in the list, just click on it and it should automagically connect to the Astaro firewall appliance and your VPN connection should be working.
Oh, and make sure you've actually got Tunnelblick started, or else you won't be able to connect ;)
Tuesday, April 07, 2009
Playing with my new Mac
I recently managed to convince my employer to provide me with a MacBook Pro so that I could start developing in-house iPhone applications for the company. I'm not yet at the point where I'm writing applications, but I am installing tons of stuff on the laptop so that I can use it as my mobile computer for when I'm on the road for work. It certainly has its quirks that I have to get used to over running everything in Windows. This will be a very brief post, but suffice it to say that I've learned a few things :
1) When it comes to Maven, ensure that JAVA_HOME is set in your environment variables.
2) Make sure you're choosing the right JVM. MacOSX comes with a bunch of default JVMs installed, and you can configure them through the Java control panel in Applications -> Utilities
1) When it comes to Maven, ensure that JAVA_HOME is set in your environment variables.
2) Make sure you're choosing the right JVM. MacOSX comes with a bunch of default JVMs installed, and you can configure them through the Java control panel in Applications -> Utilities
Wednesday, March 25, 2009
More Hibernate glitches, part 2
I had previously written on an Expected positional parameter glitch in Hibernate when joining between entities using OneToOne and PrimaryKey + ForeignKey columns. It turns out, Hibernate has this problem no matter what field you query on the joined entity if you're quering by another entity. A way around this is to use the ID property of the joined entities and add another association to your HQL / Criteria query.
ie So if you have Person and Address having a OneToOne relationship
... and you want to query for a Person by Address, you would probably write something like :
... in HQL. Well, this is where you start running into the Expected positional parameter glitch. Instead, write your query like this :
... and pass in a long / int / whatever type you use for an identifier for Person / Address instead and this will work around the glitch until it can be fixed.
ie So if you have Person and Address having a OneToOne relationship
public class Person {
@OneToOne
private Address address;
}
... and you want to query for a Person by Address, you would probably write something like :
from Person p where p.address = ?
... in HQL. Well, this is where you start running into the Expected positional parameter glitch. Instead, write your query like this :
from Person p where p.address.id = ?
... and pass in a long / int / whatever type you use for an identifier for Person / Address instead and this will work around the glitch until it can be fixed.
Sunday, March 15, 2009
Stupid MySQL tricks
Recently, I found that our secondary server's MySQL partition was starting to get full. After running du -h --max-depth=1 several times recursively, I found that the source was the MySQL binary logs. As it turns out, MySQL comes with a very handy command for purging all log files. If you run :
show master logs, you'll be given a list of the logs that the MySQL server is currently using. You can clear out old log files (some of which can be very large) by running the command
purge master logs to 'log-name-here -from-display'. This will remove the old log files from the hard disk and save up some potentially much needed space.
Friday, March 13, 2009
Spring Webflow 2 Quirks
As I recently found out after a marathon debugging session, if you have a <transition> element that executes actions, and any beans involved in an expression evaluation don't exist in your application context, SWF2 will spin it's wheels instead of properly resolving things, leaving your flow in the lurch. At the time of this writing, I'm using Spring Webflow 2.
Thursday, March 12, 2009
More Hibernate glitches
I've recently discovered the joy of actually getting OneToOne primary key columns on objects working, so I've started using them more often (where appropriate). Naturally, increased use means increased chances of finding a bug, which I have. It seems there's a parser problem in HQL when it comes to querying on ID columns that are also objects. If you try to query on the object type (ie Person) rather than the ID type (ie Long), you'll get an exception saying something along the lines of :
The above line is from HHH-2254, but it's representative of the problem I ran across. The problem is that hibernate doesn't properly parse and fill in the parameters in the query. You can get around this by changing the query to use a simple type for ID instead of the object type.
Expected positional parameter count: 1, actual parameters: [Parent@bec357b] [from Child this where this.id.parent = ?]
The above line is from HHH-2254, but it's representative of the problem I ran across. The problem is that hibernate doesn't properly parse and fill in the parameters in the query. You can get around this by changing the query to use a simple type for ID instead of the object type.
Wednesday, March 11, 2009
Stupid Hibernate Tricks
I've recently been finding myself looking for a way to get Hibernate to override the default type for strings (varchar) and use char instead in order to boost performance of the database for fields that I know are going to have a fixed length. During my search, I came upon a couple of solutions.
- Using annotations
- You can use the 'columnDefinition' attribute of the @Column annotation to specify the SQL type directly. This has the unfortunate side effective of possibly reducing the database portability of your application.
- Using SQL Dialects
- You can override one of Hibernate's built in SQL Dialects and make char() types the default for strings. One other interesting possibility this opens up is that it allows you to only override the definition for certain lengths, ie, you could make any strings less than or equal to 4 characters in length use a char() definition, and the rest could use varchar definitions. This is done using the 'registerColumnType' method of Dialect.
Wednesday, February 25, 2009
I love Dojo
I have recently discovered the magic that is Dojo. I love it. The widgets that it provides are uber-useful and have really boosted the functionality of our site. And once I integrated it with Spring Webflow 2, using some custom tags I wrote, my development speed shot right up.
Dojo Home Page
Dojo Campus - Great tutorial site
Dojo Home Page
Dojo Campus - Great tutorial site
Wednesday, February 04, 2009
Solved! Finally fixed classpath issues with Maven, Eclipse and JUnit
I have several pieces of code in one of our company's projects that dynamically reads the classpath and scans for classes that have certain annotations and values in those annotations. For the longest time, I was able to unit test these successfully. Then one day, I started getting strange ClassNotFoundExceptions when trying to execute the tests, and I could never figure out why. The project still ran just fine on our servers, so I let the issue slide at the time because I was pressed for time and needed to get other shit done. Overtime, I continued to let the problem bounce up and down over rocks as I tried to unit test certain things and couldn't because of this problem. Today I finally got fed up and wound up investing 3 hours troubleshooting the problem, and here's what happened:
Right around the same time I started getting this problem, I had upgraded my version of the M2Eclipse plugin (which I use to integrate the Maven build system and Eclipse) to a new version. As it turns out, in the intervening versions, the Maven developers changed how Maven integrates with the Eclipse classpath, and if you got the plugin to update your project's Eclipse configuration, it would specify separated output folders for your projects code and your project's test classes, and that's what caused the problem. Separating the two meant I could no longer dynamically resolve my project's model classes in a test environment, because the classpath visitor wouldn't receive the correct source folder from the test environment's classloader. So, the solution as it turned out, was to have Eclipse's output go into the same folder for source and test classes.
Right around the same time I started getting this problem, I had upgraded my version of the M2Eclipse plugin (which I use to integrate the Maven build system and Eclipse) to a new version. As it turns out, in the intervening versions, the Maven developers changed how Maven integrates with the Eclipse classpath, and if you got the plugin to update your project's Eclipse configuration, it would specify separated output folders for your projects code and your project's test classes, and that's what caused the problem. Separating the two meant I could no longer dynamically resolve my project's model classes in a test environment, because the classpath visitor wouldn't receive the correct source folder from the test environment's classloader. So, the solution as it turned out, was to have Eclipse's output go into the same folder for source and test classes.
Tuesday, January 06, 2009
Allowing external root access to your MySQL database
...is recommended against in the MySQL documentation, but it can be oh so handy for running scripts and adding databases, tables and views when you can't (or don't want to) log into that remote system on the command line to update it. However, when you try it (at least on a fresh Ubuntu install), you may be greeted with an Error 2003. This happens when your system does not allow root external access. To enable external access, you can run this query (as root) on your server from the command line (must be localhost) :
* EDIT * : The above statement does not necessarily copy all privileges over to the new entry for 'root'@'%' in the mysql.user table. A better way to accomplish full external root access is the following :
GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY ('myrootpassword');* EDIT * : The above statement does not necessarily copy all privileges over to the new entry for 'root'@'%' in the mysql.user table. A better way to accomplish full external root access is the following :
DELETE FROM `mysql`.`user` WHERE User = 'root' AND Host = '%';
INSERT INTO `mysql`.`user` (Host, User, Password, Select_priv, Insert_priv, Update_priv, Delete_priv, Create_priv, Drop_priv, Reload_priv, Shutdown_priv, Process_priv, File_priv, Grant_priv, References_priv, Index_priv, Alter_priv, Show_db_priv, Super_priv, Create_tmp_table_priv, Lock_tables_priv, Execute_priv, Repl_slave_priv, Repl_client_priv, Create_view_priv, Show_view_priv, Create_routine_priv, Alter_routine_priv, Create_user_priv, ssl_type, ssl_cipher, x509_issuer, x509_subject, max_questions, max_updates, max_connections, max_user_connections) (SELECT '%', User, Password, Select_priv, Insert_priv, Update_priv, Delete_priv, Create_priv, Drop_priv, Reload_priv, Shutdown_priv, Process_priv, File_priv, Grant_priv, References_priv, Index_priv, Alter_priv, Show_db_priv, Super_priv, Create_tmp_table_priv, Lock_tables_priv, Execute_priv, Repl_slave_priv, Repl_client_priv, Create_view_priv, Show_view_priv, Create_routine_priv, Alter_routine_priv, Create_user_priv, ssl_type, ssl_cipher, x509_issuer, x509_subject, max_questions, max_updates, max_connections, max_user_connections FROM `mysql`.`user` WHERE User = 'root' AND Host = 'localhost');
FLUSH PRIVILEGES;
Labels:
2003,
error 2003,
errors,
external login,
mysql,
root,
ubuntu
Setting up MySQL server on Ubuntu
...is really easy. Run the command :
Once that's done, you'll need to perform some additional configuration and setup the root password. My added configuration involves making table name comparisons all lower case (convenient for queries) and making the server run entirely in utf8. In order to configure the server as such, you'll need to make the following changes :
Add the following lines to the [mysqld] section of the my.cnf configuration :
Add the following line to the [mysql] section of my.cnf (note that this is not the same as the [mysqld] section, the difference being the 'd' on the end) :
In order to have the Ubuntu MySQL installation properly run on external hosts, you'll have to comment out the line that looks like :
After you've configured the my.cnf file, you'll have to stop the server, and restart it with --skip-grant-tables enabled so that you can have free, unfettered access to the system with privileges disabled. This is required so that you can set the initial root password. Run the following commands as root :
The latter command will start the MySQL server with privileges disabled.
Then log in with the command
Once logged in, run the command :
This will set the user password to 'thepassword' (though I recommend you don't use that exact password). Once you've set the root password, exit mysql and run the command
This will restart the server as usual, so that you'll no longer have unlimited privileges (and neither will anyone else).
apt-get install -y mysql-server
Once that's done, you'll need to perform some additional configuration and setup the root password. My added configuration involves making table name comparisons all lower case (convenient for queries) and making the server run entirely in utf8. In order to configure the server as such, you'll need to make the following changes :
Add the following lines to the [mysqld] section of the my.cnf configuration :
default-character-set=utf8
default-collation=utf8_general_ci
lower_case_table_names=1
Add the following line to the [mysql] section of my.cnf (note that this is not the same as the [mysqld] section, the difference being the 'd' on the end) :
default-character-set=utf8
In order to have the Ubuntu MySQL installation properly run on external hosts, you'll have to comment out the line that looks like :
bind-address 127.0.0.1
After you've configured the my.cnf file, you'll have to stop the server, and restart it with --skip-grant-tables enabled so that you can have free, unfettered access to the system with privileges disabled. This is required so that you can set the initial root password. Run the following commands as root :
/etc/init.d/mysql stop
mysqld_safe --skip-grant-tables --skip-networking &
The latter command will start the MySQL server with privileges disabled.
Then log in with the command
mysql -u root -h localhost
Once logged in, run the command :
UPDATE `mysql`.`user` SET Password = PASSWORD('thepassword') WHERE User = 'root';
This will set the user password to 'thepassword' (though I recommend you don't use that exact password). Once you've set the root password, exit mysql and run the command
/etc/init.d/mysql restart
This will restart the server as usual, so that you'll no longer have unlimited privileges (and neither will anyone else).
Setting up an SSH Server on Ubuntu
Ubuntu is usually pretty good about coming pre-configured with everything you need for your system, but one thing I've found that it's lacking (at least the server distribution is, anyway) is a pre-setup SSH server. Fortunately though, it's quite easy to set one up. All you need to do is issue the command:
...as root. If you get an error to the effect of :
...then you'll have to run :
...to update your system's packages first.
apt-get install openssh-server
...as root. If you get an error to the effect of :
Package does not exist
...then you'll have to run :
apt-get update
...to update your system's packages first.
Tuesday, December 30, 2008
Configuring MySQL for table case-insensitivity
While trying to add a recent update to our systems, I noticed that our demo system wouldn't start, which was very strange considering I had no problems on my local test system. I eventually traced the problem down to MySQL not being able to find certain tables. This was because I had created the tables in a different case than the Quartz library was expecting. My local testing box is a Windows box whereas our demo and production systems run Linux, the difference being that my Windows MySQL installation has case-insensitivity on by default, whereas the Linux installations do not. In order to remedy this, I had to explicitly add a line to my my.ini / my.cnf configurations :
You can determine the case-sensitivity of your own MySQL installation by logging in with the command line client or running the query browser and entering the following command :
lower_case_table_names=1
You can determine the case-sensitivity of your own MySQL installation by logging in with the command line client or running the query browser and entering the following command :
SHOW VARIABLES LIKE 'lower_case%'
Thursday, December 11, 2008
Integrating Quartz and Spring using persistent jobs
I've recently found myself having a lot of jobs running in the background of our Java applications, often requiring changes to the scheduling based on my boss' whims. Combine this with the fact that our system's API is now seeing heavy use, and having the triggers configured in the Spring application context (and thus requiring a restart in order to change them) just doesn't cut it any more. Thus, I now find myself needing persistent jobs. However, there's not a lot of documentation out there for dealing with Quartz' persistent job feature in Spring, so I'm going to hopefully provide some help. To start, you need to create the quartz tables in whatever database you're going to be using. There are scripts for creating these tables in the Quartz distribution available on their website for whatever database you may be using. For convenience, I've copied the MySQL Inno DB script here :
Once this is done, you'll need to integrate Quartz with Spring. If you're using Maven 2, getting Quartz into your project is as simple as adding the following snippet to the <dependencies> section of your pom.xml configuration file.
Note that you're going to need Quartz version 1.6.0 to work with Spring 2.5.3 or greater, otherwise you can use Quartz 1.5.x. After you've got Quartz into your project, you'll need to define a Scheduler that accesses your database to schedule your jobs. Fortunately, Spring happens to come with a handy FactoryBean for defining Quartz schedulers. I've used the following :
The above Spring beans configuration snippet requires some explanation, so let's go through it.
At this point in time, you've got the tables which Quartz requires for persistent jobs defined (and presumably accessible) in your database, you've got the Quartz libraries present in your classpath, and you've got Quartz configured within Spring so that Quartz will run and check the database for jobs to be run. However, we're still missing a few more important pieces of information.
For the rest of this article, lets assume that you've set your Quartz table prefix as 'qrtz_'. In your database, lets look at the definition of the 'qrtz_job_details' table. It includes columns for defining job_name, job_group, description, job_class_name, is_durable, is_volatile, is_stateful, requests_recovery, and job_data.
The 'job_name' field is, as you've almost certainly guessed, the name of the job that you want to define. The job must also have a 'job_group' specified because triggers can be used to signal the execution of entire groups of jobs at once, not just single jobs. The two of these fields together form a unique key for the table. If you really don't give a shit about grouping jobs, you can just use Quartz' default group name, which is 'DEFAULT'. You also need to set a 'job_class_name'. This value is a fully qualified Java class name that must have a default, no-arg constructor (ie it's a bean, according to the Java Beans specification). This class will get instantiated at run time by the SpringBeanJobFactory and run as a Quartz job. Note that this class, obviously, must implement the org.quartz.Job interface. A decription of the job can be put in the 'description' field if you want to have a log-friendly message available to you. The is_durable, is_volatile, is_stateful, and requests_recovery fields are all (essentially) boolean fields that Quartz defines as being single characters. These fields can have the values 'Y' or 'N'. See the Quartz API for org.quartz.JobDetail for further elaboration on the meaning of these fields. The 'job_data' field is a BLOB object that's used to serialize the 'jobDataMap' (java.util.Map) associated with the JobDetail. In order to populate this field, you'll obviously have to write some JDBC code.
The 'qrtz_job_details' table is, obviously, where you store the information for the org.quartz.Jobs you want to run. Anytime you need to define a new job, you'll need to insert a new row into this table, properly configured of course and paying particular attention to the 'job_class_name' field.
Ok, so assuming you've got a couple rows in this table (ie defined a couple of jobs), now you'll need to schedule them so that, at some point, they'll actually run. This is where triggers come in. Now, in Quartz, there are a few different methods of triggering off Jobs, by far the most common being org.quartz.SimpleTriggers (qrtz_simple_triggers table) and org.quartz.CronTriggers (qrtz_cron_triggers table). SimpleTriggers have their use, but our company uses some pretty complex scheduling at times, so I'm going to do an example of using the cron triggers.
The 'qrtz_cron_triggers' table is pretty simple, it only has four fields : 'trigger_name', 'trigger_group', 'cron_expression' and 'time_zone_id', all of which should be pretty indicative of their content. The 'time_zone_id' field should be a valid id for a java.util.TimeZone, ie 'America/Denver'. See the documentation for that class for more on valid IDs.
Once you've set up a trigger with a valid name, group (ie DEFAULT), cron expression and TimeZone ID, you're pretty much ready to go. When Quartz starts up with your application, it'll consult the 'qrtz_triggers' table and ensure that entries exist for all of your corresponding cron / simple / blob triggers. It'll then proceed to fire off triggers and the Scheduler will pass off jobs to the SpringBeanJobFactory for execution. Now, if you haven't figured it out already, if you've got a lot of jobs, so far from what this article has shown you, you'll have to create a Quartz Job implementation for each job you want to run off, and even then, it'll have to be pretty simple, since you'll have to be able to instantiate it with a no-arg constructor, and so far you've got no way of injecting it with anything from Spring, aside from simple properties that are defined by the 'schedulerContextAsMap' property on the Spring SchedulerFactoryBean. So, what I did was create a class that uses its JobDetail#name as a convention for getting a bean from the application context, assumes it's an org.quartz.Job implementation, and executes it. The code is attached :
Now, this makes the persisted jobs a lot more capable : we can now run org.quartz.Job beans that are declared in our Spring ApplicationContext. What if we want to be able to run arbitrary methods on arbitrary beans as jobs ? Well, we can write an adapter class for that too, quite similar (though not as functional as) Spring's MethodInvokingJobDetailFactoryBean :
Note that the class above is implements org.quartz.Job, not org.quartz.JobDetail as is the product of MethodInvokingJobDetailFactoryBean. I hope this article has been useful for anybody reading it, feel free to comment on it.
DROP TABLE IF EXISTS QRTZ_JOB_LISTENERS;
DROP TABLE IF EXISTS QRTZ_TRIGGER_LISTENERS;
DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS;
DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE;
DROP TABLE IF EXISTS QRTZ_LOCKS;
DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_JOB_DETAILS;
DROP TABLE IF EXISTS QRTZ_CALENDARS;
CREATE TABLE QRTZ_JOB_DETAILS(
JOB_NAME VARCHAR(200) NOT NULL,
JOB_GROUP VARCHAR(200) NOT NULL,
DESCRIPTION VARCHAR(250) NULL,
JOB_CLASS_NAME VARCHAR(250) NOT NULL,
IS_DURABLE VARCHAR(1) NOT NULL,
IS_VOLATILE VARCHAR(1) NOT NULL,
IS_STATEFUL VARCHAR(1) NOT NULL,
REQUESTS_RECOVERY VARCHAR(1) NOT NULL,
JOB_DATA BLOB NULL,
PRIMARY KEY (JOB_NAME,JOB_GROUP))
TYPE=InnoDB;
CREATE TABLE QRTZ_JOB_LISTENERS (
JOB_NAME VARCHAR(200) NOT NULL,
JOB_GROUP VARCHAR(200) NOT NULL,
JOB_LISTENER VARCHAR(200) NOT NULL,
PRIMARY KEY (JOB_NAME,JOB_GROUP,JOB_LISTENER),
INDEX (JOB_NAME, JOB_GROUP),
FOREIGN KEY (JOB_NAME,JOB_GROUP)
REFERENCES QRTZ_JOB_DETAILS(JOB_NAME,JOB_GROUP))
TYPE=InnoDB;
CREATE TABLE QRTZ_TRIGGERS (
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
JOB_NAME VARCHAR(200) NOT NULL,
JOB_GROUP VARCHAR(200) NOT NULL,
IS_VOLATILE VARCHAR(1) NOT NULL,
DESCRIPTION VARCHAR(250) NULL,
NEXT_FIRE_TIME BIGINT(13) NULL,
PREV_FIRE_TIME BIGINT(13) NULL,
PRIORITY INTEGER NULL,
TRIGGER_STATE VARCHAR(16) NOT NULL,
TRIGGER_TYPE VARCHAR(8) NOT NULL,
START_TIME BIGINT(13) NOT NULL,
END_TIME BIGINT(13) NULL,
CALENDAR_NAME VARCHAR(200) NULL,
MISFIRE_INSTR SMALLINT(2) NULL,
JOB_DATA BLOB NULL,
PRIMARY KEY (TRIGGER_NAME,TRIGGER_GROUP),
INDEX (JOB_NAME, JOB_GROUP),
FOREIGN KEY (JOB_NAME,JOB_GROUP)
REFERENCES QRTZ_JOB_DETAILS(JOB_NAME,JOB_GROUP))
TYPE=InnoDB;
CREATE TABLE QRTZ_SIMPLE_TRIGGERS (
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
REPEAT_COUNT BIGINT(7) NOT NULL,
REPEAT_INTERVAL BIGINT(12) NOT NULL,
TIMES_TRIGGERED BIGINT(7) NOT NULL,
PRIMARY KEY (TRIGGER_NAME,TRIGGER_GROUP),
INDEX (TRIGGER_NAME, TRIGGER_GROUP),
FOREIGN KEY (TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(TRIGGER_NAME,TRIGGER_GROUP))
TYPE=InnoDB;
CREATE TABLE QRTZ_CRON_TRIGGERS (
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
CRON_EXPRESSION VARCHAR(120) NOT NULL,
TIME_ZONE_ID VARCHAR(80),
PRIMARY KEY (TRIGGER_NAME,TRIGGER_GROUP),
INDEX (TRIGGER_NAME, TRIGGER_GROUP),
FOREIGN KEY (TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(TRIGGER_NAME,TRIGGER_GROUP))
TYPE=InnoDB;
CREATE TABLE QRTZ_BLOB_TRIGGERS (
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
BLOB_DATA BLOB NULL,
PRIMARY KEY (TRIGGER_NAME,TRIGGER_GROUP),
INDEX (TRIGGER_NAME, TRIGGER_GROUP),
FOREIGN KEY (TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(TRIGGER_NAME,TRIGGER_GROUP))
TYPE=InnoDB;
CREATE TABLE QRTZ_TRIGGER_LISTENERS (
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
TRIGGER_LISTENER VARCHAR(200) NOT NULL,
PRIMARY KEY (TRIGGER_NAME,TRIGGER_GROUP,TRIGGER_LISTENER),
INDEX (TRIGGER_NAME, TRIGGER_GROUP),
FOREIGN KEY (TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(TRIGGER_NAME,TRIGGER_GROUP))
TYPE=InnoDB;
CREATE TABLE QRTZ_CALENDARS (
CALENDAR_NAME VARCHAR(200) NOT NULL,
CALENDAR BLOB NOT NULL,
PRIMARY KEY (CALENDAR_NAME))
TYPE=InnoDB;
CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS (
TRIGGER_GROUP VARCHAR(200) NOT NULL,
PRIMARY KEY (TRIGGER_GROUP))
TYPE=InnoDB;
CREATE TABLE QRTZ_FIRED_TRIGGERS (
ENTRY_ID VARCHAR(95) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
IS_VOLATILE VARCHAR(1) NOT NULL,
INSTANCE_NAME VARCHAR(200) NOT NULL,
FIRED_TIME BIGINT(13) NOT NULL,
PRIORITY INTEGER NOT NULL,
STATE VARCHAR(16) NOT NULL,
JOB_NAME VARCHAR(200) NULL,
JOB_GROUP VARCHAR(200) NULL,
IS_STATEFUL VARCHAR(1) NULL,
REQUESTS_RECOVERY VARCHAR(1) NULL,
PRIMARY KEY (ENTRY_ID))
TYPE=InnoDB;
CREATE TABLE QRTZ_SCHEDULER_STATE (
INSTANCE_NAME VARCHAR(200) NOT NULL,
LAST_CHECKIN_TIME BIGINT(13) NOT NULL,
CHECKIN_INTERVAL BIGINT(13) NOT NULL,
PRIMARY KEY (INSTANCE_NAME))
TYPE=InnoDB;
CREATE TABLE QRTZ_LOCKS (
LOCK_NAME VARCHAR(40) NOT NULL,
PRIMARY KEY (LOCK_NAME))
TYPE=InnoDB;
INSERT INTO QRTZ_LOCKS values('TRIGGER_ACCESS');
INSERT INTO QRTZ_LOCKS values('JOB_ACCESS');
INSERT INTO QRTZ_LOCKS values('CALENDAR_ACCESS');
INSERT INTO QRTZ_LOCKS values('STATE_ACCESS');
INSERT INTO QRTZ_LOCKS values('MISFIRE_ACCESS');
commit;
Once this is done, you'll need to integrate Quartz with Spring. If you're using Maven 2, getting Quartz into your project is as simple as adding the following snippet to the <dependencies> section of your pom.xml configuration file.
<dependency>
<groupId>opensymphony</groupId>
<artifactId>quartz</artifactId>
<version>1.6.0</version>
<scope>provided</scope>
</dependency>
Note that you're going to need Quartz version 1.6.0 to work with Spring 2.5.3 or greater, otherwise you can use Quartz 1.5.x. After you've got Quartz into your project, you'll need to define a Scheduler that accesses your database to schedule your jobs. Fortunately, Spring happens to come with a handy FactoryBean for defining Quartz schedulers. I've used the following :
<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="jobFactory">
<bean class="org.springframework.scheduling.quartz.SpringBeanJobFactory"/>
</property>
<property name="dataSource" ref="mainDataSource" />
<property name="transactionManager" ref="mainDataSourceTransactionManager" />
<property name="quartzProperties">
<util:properties location="/WEB-INF/config/quartz.properties"/>
</property>
<property name="applicationContextSchedulerContextKey" value="applicationContext"/>
<property name="waitForJobsToCompleteOnShutdown" value="true" />
</bean>
The above Spring beans configuration snippet requires some explanation, so let's go through it.
- The property 'jobFactory' is a Spring implementation of the Quartz JobFactory interface. When the scheduler encounters a trigger in the database that's to be fired, it loads the corresponding JobDetails from the database, loads it into a JobDetail object, places that JobDetail object into a TriggerFiredBundle, and passes the TriggerFiredBundle to the JobFactory interface to obtain a Job that's to be run. Now, you might think that because of the name of Spring's JobFactory implementation, it's going to do something with a bean defined in the Spring ApplicationContext, right ? Wrong. What this implementation does is get the Job class provided by JobDetail.getClass(), instantiate a new instance of it with a default no-arg constructor, possibly populate it with properties, and then run the Job. Which sucks, and is kind of useless, because no Jobs I need can do so without getting something from Spring's ApplicationContext. More on this later.
- The 'dataSource' property should be the javax.sql.DataSource used by your application to connect to whatever database you're using that stores the persistent Jobs in the tables used by Quartz.
- The 'transactionManager' property should be set to a org.springframework.transaction.PlatformTransactionManager implementation that's used to demarcate transactions in the database. If you don't have one for your dataSource already defined, then it's fortunate that Spring comes with one that you can define and use, like so :
<bean id="mainDataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager" p:dataSource-ref="mainDataSource" /> - The 'quartzProperties' property should be configured with a java.util.Properties instance containing configuration values for Quartz. A sample is posted here for you :
org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount=5
org.quartz.threadPool.threadPriority=4
org.quartz.jobStore.tablePrefix=qrtz_
org.quartz.jobStore.isClustered=false
org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
This sample contains a few simple properties that should be enough to get you started. There are numerous others that you should look into in the Quartz documentation. The first three properties are for configuring Quartz' thread-pooling capabilities. The latter three are for configuring the database (and access thereto-) that Quartz uses for storing persistent jobs. The table prefix is used to prefix Quartz' tables so that they don't conflict with any pre-existing tables in your database. The 'isClustered' property is used to determine whether Quartz is acting in a cluster. Clustered Quartz configurations are beyond the scope of this article. The 'driverDelegateClass' property is used to determine the class Quartz will use for dealing with your specific dialect of SQL (ie MySQL, MS-SQL, PostgreSQL, etc). - The 'applicationContextSchedulerContextKey' property sets a key that's used to access the Spring ApplicationContext in the JobExecutionContext given to a Job implementation at run time after the SpringBeanJobFactory has populated it for you. This is important as this property does not have a default value and will not put the ApplicationContext into the JobExecutionContext for you unless you specify one.
- The 'waitForJobsToCompleteOnShutdown' property specifies, well, I think you can figure that one out.
At this point in time, you've got the tables which Quartz requires for persistent jobs defined (and presumably accessible) in your database, you've got the Quartz libraries present in your classpath, and you've got Quartz configured within Spring so that Quartz will run and check the database for jobs to be run. However, we're still missing a few more important pieces of information.
- JobDetails stored in the database describing jobs to be run
- Triggers defined in the database for describing when JobDetails will be executed
For the rest of this article, lets assume that you've set your Quartz table prefix as 'qrtz_'. In your database, lets look at the definition of the 'qrtz_job_details' table. It includes columns for defining job_name, job_group, description, job_class_name, is_durable, is_volatile, is_stateful, requests_recovery, and job_data.
The 'job_name' field is, as you've almost certainly guessed, the name of the job that you want to define. The job must also have a 'job_group' specified because triggers can be used to signal the execution of entire groups of jobs at once, not just single jobs. The two of these fields together form a unique key for the table. If you really don't give a shit about grouping jobs, you can just use Quartz' default group name, which is 'DEFAULT'. You also need to set a 'job_class_name'. This value is a fully qualified Java class name that must have a default, no-arg constructor (ie it's a bean, according to the Java Beans specification). This class will get instantiated at run time by the SpringBeanJobFactory and run as a Quartz job. Note that this class, obviously, must implement the org.quartz.Job interface. A decription of the job can be put in the 'description' field if you want to have a log-friendly message available to you. The is_durable, is_volatile, is_stateful, and requests_recovery fields are all (essentially) boolean fields that Quartz defines as being single characters. These fields can have the values 'Y' or 'N'. See the Quartz API for org.quartz.JobDetail for further elaboration on the meaning of these fields. The 'job_data' field is a BLOB object that's used to serialize the 'jobDataMap' (java.util.Map) associated with the JobDetail. In order to populate this field, you'll obviously have to write some JDBC code.
The 'qrtz_job_details' table is, obviously, where you store the information for the org.quartz.Jobs you want to run. Anytime you need to define a new job, you'll need to insert a new row into this table, properly configured of course and paying particular attention to the 'job_class_name' field.
Ok, so assuming you've got a couple rows in this table (ie defined a couple of jobs), now you'll need to schedule them so that, at some point, they'll actually run. This is where triggers come in. Now, in Quartz, there are a few different methods of triggering off Jobs, by far the most common being org.quartz.SimpleTriggers (qrtz_simple_triggers table) and org.quartz.CronTriggers (qrtz_cron_triggers table). SimpleTriggers have their use, but our company uses some pretty complex scheduling at times, so I'm going to do an example of using the cron triggers.
The 'qrtz_cron_triggers' table is pretty simple, it only has four fields : 'trigger_name', 'trigger_group', 'cron_expression' and 'time_zone_id', all of which should be pretty indicative of their content. The 'time_zone_id' field should be a valid id for a java.util.TimeZone, ie 'America/Denver'. See the documentation for that class for more on valid IDs.
Once you've set up a trigger with a valid name, group (ie DEFAULT), cron expression and TimeZone ID, you're pretty much ready to go. When Quartz starts up with your application, it'll consult the 'qrtz_triggers' table and ensure that entries exist for all of your corresponding cron / simple / blob triggers. It'll then proceed to fire off triggers and the Scheduler will pass off jobs to the SpringBeanJobFactory for execution. Now, if you haven't figured it out already, if you've got a lot of jobs, so far from what this article has shown you, you'll have to create a Quartz Job implementation for each job you want to run off, and even then, it'll have to be pretty simple, since you'll have to be able to instantiate it with a no-arg constructor, and so far you've got no way of injecting it with anything from Spring, aside from simple properties that are defined by the 'schedulerContextAsMap' property on the Spring SchedulerFactoryBean. So, what I did was create a class that uses its JobDetail#name as a convention for getting a bean from the application context, assumes it's an org.quartz.Job implementation, and executes it. The code is attached :
public class SpringBeanDelegatingJob implements Job {
private static final Log LOGGER = LogFactory.getLog(SpringBeanDelegatingJob.class);
public static final String APPLICATION_CONTEXT_KEY = "applicationContext";
@SuppressWarnings("unchecked")
public void execute(JobExecutionContext arg0) throws JobExecutionException {
JobDetail jobDetail = arg0.getJobDetail();
String beanName = substringBefore(jobDetail.getName(), "Detail");
if (LOGGER.isInfoEnabled()) {
LOGGER.info ("Running SpringBeanDelegatingJob - Job Name ["+jobDetail.getName()+"], Group Name ["+jobDetail.getGroup()+"]");
LOGGER.info ("Delegating to bean ["+beanName+"]");
}
ApplicationContext applicationContext = null;
try {
applicationContext = (ApplicationContext) arg0.getScheduler().getContext().get(APPLICATION_CONTEXT_KEY);
} catch (SchedulerException e2) {
throw new JobExecutionException("Holy fuck, there was some kind of god-damned problem with the fucking Scheduler", e2);
}
Job bean = null;
try {
bean = (Job) applicationContext.getBean (beanName, Job.class);
} catch (BeansException e1) {
throw new JobExecutionException("Unable to retrieve target bean that is to be used as a job source", e1);
}
bean.execute (arg0);
return;
}
}
Now, this makes the persisted jobs a lot more capable : we can now run org.quartz.Job beans that are declared in our Spring ApplicationContext. What if we want to be able to run arbitrary methods on arbitrary beans as jobs ? Well, we can write an adapter class for that too, quite similar (though not as functional as) Spring's MethodInvokingJobDetailFactoryBean :
public class SpringBeanMethodInvokingJob implements InitializingBean, Job {
private Object targetBean;
private String targetMethod;
//Constructors
public SpringBeanMethodInvokingJob() {
super();
}
//Behaviour Methods
public void execute(JobExecutionContext arg0) throws JobExecutionException {
Method method = null;
try {
method = targetBean.getClass().getMethod(targetMethod);
} catch (Exception e) {
throw new JobExecutionException("Unable to get targetMethod ["+targetMethod+
"] on bean with class ["+targetBean.getClass().getName()+"]");
}
try {
method.invoke(targetBean);
} catch (Exception e) {
throw new JobExecutionException("Unable to invoke method ["+method.getName()+"] on bean ["+targetBean.toString()+"]");
}
return; //done
}
public void afterPropertiesSet() throws Exception {
Assert.notNull(targetBean, "'targetBean' cannot be null");
Assert.isTrue(isNotBlank(targetMethod), "'targetMethod' cannot be blank");
}
//Property Accessors
@Required
public final void setTargetBean(Object targetBean) {
this.targetBean = targetBean;
}
@Required
public final void setTargetMethod(String targetMethod) {
this.targetMethod = targetMethod;
}
}
Note that the class above is implements org.quartz.Job, not org.quartz.JobDetail as is the product of MethodInvokingJobDetailFactoryBean. I hope this article has been useful for anybody reading it, feel free to comment on it.
Friday, December 05, 2008
The greatest MySQL companion ever
I just recently found out about the 'show processlist' command in MySQL that lets you get a resultset of all the processes MySQL is currently using and what query they're running. Today I was working in top monitoring the performance of our servers as I restarted our production server. MySQL popped up briefly on the top list of running processes and it occurred to me, "Wouldn't it be great of there was something like this for MySQL?". So while I was waiting, I popped open google, and ran a query for a MySQL and top. To my surprise, up came mytop. It's literally top for MySQL and it's so ridiculously useful you wouldn't believe it. Even better, it's got a .deb made for it and it's in the Ubuntu repositories, so installing it is as simple as running :
It's a good day today :)
apt-get install mytop
It's a good day today :)
Setting a user's password in MySQL
I've recently found the need to change a bunch of passwords in MySQL. The command to do it is as follows :
You can also read the documentation on the MySQL website.
SET PASSWORD FOR 'myuser'@'%.wherever.com' = PASSWORD('newpass');You can also read the documentation on the MySQL website.
Wednesday, December 03, 2008
Finding out what MySQL is doing
I was recently updating our production system when I noticed that our main MySQL database was going unusually hard. On a quad-core processor system, it was using up three processors simaltaneously. This prompted me to find out what MySQL is doing, and after being unable to read the logs, I did a Google search and ran across this useful little command :
This runs a query (can be run from either the command line or the query browser) and shows you a summary of all the processes that MySQL is currently using.
show processlist
This runs a query (can be run from either the command line or the query browser) and shows you a summary of all the processes that MySQL is currently using.
Wednesday, November 26, 2008
Migrating from Spring Webflow 1 to Webflow 2
...was relatively painless. At least in the very small app that I'm currently working on as a helper application for dealing with one of our partners' systems. The flow definition migration assistant that comes with Webflow 2 is a huge help. There are, however, a few things to take note of when it comes to differences between Webflow 1 and Webflow 2. They are significantly different beasts to say the least :
Once all the new configuration was done and out of the way, it was simply a matter of upgrading my existing flows to the new Webflow 2 syntax, which was almost entirely taken care of by the migrator that comes with Webflow 2. Previously, I used the 'var' element heavily to make use of beans in my Spring ApplicationContext, but the 'var' element has since had its usage completely reworked for Webflow 2, so I'm going to have to refactor a couple of my flows and change my style of design with Webflow 2.
One other feature that I'm learning about as I write this post is the Webflow 2 PersistenceContext. It's a solution for the use case where you want to be able to load objects from a database, and only allow a single user to be able to access them at once, ie you wouldn't want multiple people logging into a database and being able to access data on a particular user at the same time and potentially overwrite each others' changes. I'll post more about that later.
My configuration went from this (in Webflow 1) :
<flow:registry id="flowRegistry">
<flow:location path="/WEB-INF/flows/**/*-flow.xml"/>
</flow:registry>
<flow:executor id="flowExecutor" registry-ref="flowRegistry">
<!--flow:execution-listeners>
<flow:listener ref="webflowDebugListener"/>
</flow:execution-listeners-->
</flow:executor>
<!--bean id="webflowDebugListener" class="org.springframework.webflow.execution.DebuggingListener"/-->
<bean name="flowController" class="org.springframework.webflow.executor.mvc.FlowController">
<property name="flowExecutor" ref="flowExecutor" />
<property name="argumentHandler">
<bean class="org.springframework.webflow.executor.support.RequestParameterFlowExecutorArgumentHandler" />
</property>
</bean>
To this (in Webflow 2) :
<webflow:flow-registry id="flowRegistry" flow-builder-services="flowBuilderServices">
<webflow:flow-location-pattern value="/WEB-INF/flows/**/*-flow.xml"/>
</webflow:flow-registry>
<webflow:flow-executor id="flowExecutor" flow-registry="flowRegistry">
<webflow:flow-execution-listeners>
<!-- webflow:listener ref="webflowDebugListener"/ -->
<webflow:listener ref="securityFlowExecutionListener"/>
</webflow:flow-execution-listeners>
</webflow:flow-executor>
<bean id="securityFlowExecutionListener" class="org.springframework.webflow.security.SecurityFlowExecutionListener" />
<webflow:flow-builder-services id="flowBuilderServices" view-factory-creator="viewFactoryCreator" conversion-service="webflowConversionService"/>
<bean id="webflowConversionService" class="com.mypackage.modules.springframework.webflow2.ConversionServiceFactoryBean">
<property name="editorMappings">
<map>
<entry key="java.util.Calendar"><idref bean="sqlDateCalendarEditor"/></entry>
</map>
</property>
</bean>
<bean id="viewFactoryCreator" class="org.springframework.webflow.mvc.builder.MvcViewFactoryCreator">
<property name="viewResolvers">
<list>
<ref local="xmlViewResolver"/>
<ref local="decoratedJstlViewResolver"/>
<ref local="urlBasedViewResolver"/>
</list>
</property>
</bean>
<bean id="webflowDebugListener" class="org.springframework.webflow.execution.DebuggingListener"/>
<bean name="flowController" class="org.springframework.webflow.mvc.servlet.FlowController">
<property name="flowExecutor" ref="flowExecutor" />
<property name="flowUrlHandler">
<bean class="org.springframework.webflow.context.servlet.DefaultFlowUrlHandler" />
</property>
</bean>
Now, you may be asking yourself, "Why would there be more configuration in Spring Webflow 2 ? Doesn't Spring generally improve on things like configuration syntax between major versions ? The answer is, of course, yes. However, there are a number of new features that come with Webflow 2 that need to be configured, hence the extra beans for configuration. Note also, that the syntax has changed. I direct your attention to the added 'flow-' prefixes on element names of the previously existing Webflow elements.
Webflow 2 has much better, much closer integration with Spring Security, hence the need for SecurityFlowExecutionListener listed above. I haven't had a chance to play around with the new integration between Spring Security 2 and Spring Webflow 2, but I'm quickly getting there.
Thanks to the built in model binding and validation that comes with Spring Webflow 2, there's much less syntax for views that simply bind to a model object on transitions. Instead of having to add in FormActions and adding action elements to the view-state you want them applied to, you can now simply specify a model name for the 'model' attribute of the view state, and Webflow 2 will automatically bind the form object for you (which leads us into our next point). Of course, the Spring developers maintained their usual style of keeping things as configurable as possible, so you can not only choose not to bind the model object for particular state transitions, but you can disable validation as well, using the new 'bind' and 'validate' attributes of Webflow 2 'transition' elements.
Because Webflow 2 can now do binding and validation for us right in the flow definition without having to define FormAction beans in our Spring ApplicationContext, Webflow 2 comes with its own updates to the spring-binding framework, hence the need for the 'webflowConversionService' bean in the configuration above. Now, you're probably saying to yourself right now, "Well what the fuck am I supposed to do with the custom PropertyEditors I wrote for my objects when dealing with Spring / Webflow 1 ?". Don't worry, all is not lost. There's a PropertyEditorConverter class that adapts PropertyEditor implementations to the new Webflow 2 Converter interface. You can use this to interface your editors with Webflow 2, and in fact that's what I did.
Webflow 1 left it up to the Spring framework to resolve and render views. Some people found this inconvenient, so Webflow 2 now allows you to have Webflow directly resolve and render views itself. However, since I'm already using the Spring framework and don't need Webflow's abstraction away from the underlaying framework, I chose to keep the Webflow 1 style of resolution and view rendering, hence the need for the 'viewFactoryCreator' bean above.
Once all the new configuration was done and out of the way, it was simply a matter of upgrading my existing flows to the new Webflow 2 syntax, which was almost entirely taken care of by the migrator that comes with Webflow 2. Previously, I used the 'var' element heavily to make use of beans in my Spring ApplicationContext, but the 'var' element has since had its usage completely reworked for Webflow 2, so I'm going to have to refactor a couple of my flows and change my style of design with Webflow 2.
One other feature that I'm learning about as I write this post is the Webflow 2 PersistenceContext. It's a solution for the use case where you want to be able to load objects from a database, and only allow a single user to be able to access them at once, ie you wouldn't want multiple people logging into a database and being able to access data on a particular user at the same time and potentially overwrite each others' changes. I'll post more about that later.
Friday, November 21, 2008
Migrating from Acegi Security to Spring Security 2
In an effort to keep our applications current, I've been looking at upgrading from Acegi Security to Spring Security 2 in one of our smaller applications as a test, to see how well it'd go over for our larger applications. So far, the tests are pretty positive. The main advantage to the migration that I can see is improved Web Service security (which I'll definitely be needing), as well as a considerably simplified configuration syntax. The following is a small migration guide :
0. If you haven't already, update your project to use Spring 2.5.4 or greater. This is necessary because of changes to Spring that Spring Security (as well as other Spring subprojects) requires. If you're using Maven 2, this is dead easy, you need only update the <version> elements of the appropriate dependencies.
1. Change all package references starting with 'org.acegisecurity' to 'org.springframework.security' in your code. If you've architected your system right, this should mean very few changes.
2. In your POM, remove all mention of Acegi security and replace it with the following :
The exclusions are necessary to prevent Spring 2.0.x dependencies from being unnecessarily pulled in.
3. Go through your JSPs and remove all xmlns:authz="..." declarations and replace them with :
4. Go through your applicationContext*.xml files and replace any constants you may have referring to Acegi security with their proper Spring Security 2 counterparts. This means replacing the package prefixes 'org.acegisecurity' with 'org.springframework.security' and in some cases replacing ACEGI with SPRING_SECURITY in some static constant names.
This migration guide was based on (and expanded from) the one provided by Matt Raible. If you get the time, I recommend you check out his site, he has a lot of good wisdom and examples to share.
0. If you haven't already, update your project to use Spring 2.5.4 or greater. This is necessary because of changes to Spring that Spring Security (as well as other Spring subprojects) requires. If you're using Maven 2, this is dead easy, you need only update the <version> elements of the appropriate dependencies.
1. Change all package references starting with 'org.acegisecurity' to 'org.springframework.security' in your code. If you've architected your system right, this should mean very few changes.
2. In your POM, remove all mention of Acegi security and replace it with the following :
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core-tiger</artifactId>
<version>${spring.security.version}</version>
<exclusions>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-support</artifactId>
</exclusion>
</exclusions>
</dependency>
...
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-taglibs</artifactId>
<version>${spring.security.version}</version>
<exclusions>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</exclusion>
</exclusions>
</dependency>
The exclusions are necessary to prevent Spring 2.0.x dependencies from being unnecessarily pulled in.
3. Go through your JSPs and remove all xmlns:authz="..." declarations and replace them with :
xmlns:security="http://www.springframework.org/security/tags"
4. Go through your applicationContext*.xml files and replace any constants you may have referring to Acegi security with their proper Spring Security 2 counterparts. This means replacing the package prefixes 'org.acegisecurity' with 'org.springframework.security' and in some cases replacing ACEGI with SPRING_SECURITY in some static constant names.
This migration guide was based on (and expanded from) the one provided by Matt Raible. If you get the time, I recommend you check out his site, he has a lot of good wisdom and examples to share.
Subscribe to:
Posts (Atom)