...
Apereo distributes a project called cas-server-webapp to create an initial prototype template WAR file. The This WAR it creates is not particularly useful, but it contains at least a starter version of all the Spring XML used to configure CAS and many CSS, JSP, and HTML pages. It also contains a WEB-INF/lib with the basic JAR libraries needed by a basic CAS system.
Although you can modify the cas-server-webapp project directly, this results in a directory with a mixture of Apereo files and Yale files, and the next time you get a new CAS release from Apereo you have to sift through them to find out which ones have been changed by Apereo.
Apereo recommends using the WAR Overlay feature of Maven. Yale creates a second WAR project called cas-server-yale-webapp . Instead of copying all the files from the Apereo project, the WAR Overlay project that contains only the files that Yale has changed or added. Generally the WAR Overlay includes:
- Yale Java source for Spring Beans that are slightly modified versions of standard Apereo beans.
- Yale "branded" look and feel (CSS, JSP, HTML).
- Spring XML to configure Apereo options that Yale has selected and a few Yale additions.
- A pom.xml file with additional dependencies beyond just the cas-server-core that was included in the template WAR built by dependency statements for Apereo optional and Yale additional JAR files referenced by the Spring XML that will be added to WEB-INF/lib.
Maven realizes this is a WAR Overlay project because the cas-server-webapp
...
The WAR Overlay project references the template WAR, and at the end of processing the files that Yale added or changed are "overlayed" on top of the template to build the cas.war file that is actually deployed in production. Modified files are replaced. New files are added.
At Yale, when you run the Jenkins Build job for CAS, the result of that job is to create a cas.war file and store it in the Yale Artifactory server with a version number and some hierarchical naming. For example, Version 1.1.1 of the Yale CAS Server is stored WAR is referenced as a dependency. Since one WAR cannot be included inside another, this is a Maven idiom that everything in this project modifies or adds to this original template war. Files in this project with the same name replace files in the template project. Other files are added. The section of the pom.xml file that configures the maven-war-plugin also contains a list of files in the template that should be deleted, usually because they are replaced by new files with slightly different names.
Because Yale wrote CAS Versions 1 and 2, Apereo/JASIG numbering starts with Version 3. We know that the Apereo numbering won't go backwards, so any cas.war file with a Version number less than 3 is safe from collision. The Yale WAR file generated by the Overlay project, and all the Yale internal CAS projects are assigned Versions such as 1.1.2. An actual CAS artifact that might be installed into production will be store in Artifactory as https://repository.its.yale.edu/maven2/libs-releases-local/edu/yale/its/cas/cas-server-war/1.1.1/cas-server-war-1.1.1.war.
One or Two Projects?
If you were never ever going to change any Apereo source, then it would make sense to have two separate projects in your Eclipse workspace. One project would be the absolutely vanilla CAS source you downloaded from Apereo, and the other project would be the additional Yale modules and the WAR Overlay project. The Yale project would depend on the Apereo artifacts, but the only reason for having the source is so you can see methods that you step into while debugging your own code.
However, Apereo code can have problems. Sometimes it is a bug. Sometimes it is just a difference of opinion between the Yale way of doing things and the Apereo way. Sometimes you want to add in specific support for features that Apereo is working on but has not yet finished.
- Apereo defaults all registered services to be able to Proxy, but it seems better if the default is that they cannot Proxy unless you explicitly authorize it. This involves changing the default value for allowedToProxy in AbstractRegisteredService.java from "true" to "false".
- AbstractTicket is a Serializable class, but Apereo source forgot to give it a VersionUID. As a result, every time you deploy a new version of cas-server-core you have to "cold start" CAS by wiping out all the current tickets. You should only have to do that if you actually have changed the Ticket classes, not just because you recompiled the library to change something that has nothing to do with Tickets.
- Yale does not use Single Sign-Out, but code in TicketGrantingTicketImpl added to support that option has a very small but non-zero chance of throwing a ConcurrentAccessException and possibly screwing CAS up. Since we don't need it, Yale might just comment out the statement that causes the problem.
Each of these is a one line change, and only the last is important. If CAS was not open source we would probably just live with the old code, but we have the chance to fix it.
Once you start to consider changing Apereo code, then it is inconvenient to maintain two separate projects. For one thing, you need two separate Jenkins build jobs, one for each project. Then you have release numbering issues.
The alternative is to have a single project that combines both the Apereo source for a CAS release (example: 3.5.2.1) and the Yale code that has been built and tested for that CAS release (separately designated Version 1.1.x). Most of the time the Apereo source just sits there and is not used and is not compiled because it is not changed. If you need to make a modification, it is immediately available.
The single project may be confusing, unless you have read this document. There is a large block of source checked into Subversion that appears in your Eclipse workspace, except that none of it is actually used. Why is it there at all? If the Apereo source is not going to be changed, shouldn't it be separated out for clarity?
There are advantages to the Two Project approach, and advantages to the One Project approach. Currently we use the single project structure, but if you feel really strongly about it you can separate the code in some future release.
The Parent pom.xml
The top level directory is the "parent" project. Typically it is named "cas-server" although this is simply a choice you make when you check the source out from SVN. The only thing in the parent project is its pom.xml file, and a few README type files.
However, one of the more complicated problems when Yale migrates from one release of CAS to another is to reconcile our changes to the parent pom.xml with any Apereo changes. This is not the same type of code migration that occurs if you have modifications to some HTML or Java source file.
All the CAS code, configuration files, and HTML do the same thing at Yale that they do everywhere else and that Apereo designed them to do. So our version is fairly close to the original.
However, the top level pom.xml file is not just about compiling the source and building the template WAR file (which is the part we need). Apereo is in the business of maintaining an Open Source Project that is distributed widely on the Internet. A large part of their concern is to make sure that all the legal boilerplate is properly maintained. Do the files all have a proper copyright notice? Are all the licenses to the dependency files properly listed? Maven has some impressive support for doing all this open source release management, but unless that is something you specifically have learned, setting up the legal boilerplate is a daunting task.
Yale is a CAS customer. We may have our own files, but unless and until we contribute them back to Apereo we do not care if a file that nobody sees outside Yale has a proper open source copyright notice and license declaration. If we try to use the Apereo top level pom.xml file as distributed, then every time we try to cut a Yale release we get error messages for all the boilerplate that is missing from our own files. So we remove all that stuff and create our own top level pom.xml.
Given that we have to make substantial changes, we might accept that this pom.xml is a Yale file and no longer a modified version of the original Apereo project. Then for sanity, and because our release process requires it, we change it to reflect the rest of the Yale environment:
...
CAS development at Yale could be separated into one project with vanilla Apereo source and one project with Yale additions and modifications. That keeps things cleanly separated if Yale was never going to make any changes.
However, CAS is not really distributed as a bunch of equal projects. The cas-server-core project is 95% (or more) of the source and all the other projects are relatively small. There are always changes that we make to cas-server-core during development, although some of these changes are just for testing or exploration. In other cases there are CAS bugs, or simply differences of opinion about how CAS should work. Where possible, Yale will take a CAS core source file, generate a Yale named version of the same code, and put it in the Java source section of the WAR Overlay project. Therefore, at least during development the idea of a "pristine" Apereo source is unreasonable.
Some examples of differences of opinion or bugs:
- The "service=" parameter value on the CAS login has to exactly match the value of the same parameter on the validate, serviceValidate, or samlValidate request. There is a difference of option about how carefully they have to match. The JASIG code matched the entire string excluding JSESSIONID if it was present. Yale believes that the entire query string (everything after the "?") should be excluded, and maybe the match should stop with context (https://servername/context/stuff-to-be-ingnored). This changes the substrings used in the equals() test.
- When you are having a CAS problem, you may want to insert additional logging. For example, if the validate request is failing you may want to print out the exact service= strings being compared and the point at which they differ.
- Apereo defaults all registered services to be able to Proxy, but it seems better if the default is that they cannot Proxy unless you explicitly authorize it. This involves changing the default value for allowedToProxy in AbstractRegisteredService.java from "true" to "false".
- AbstractTicket is a Serializable class, but Apereo source forgot to give it a VersionUID. As a result, every time you deploy a new version of cas-server-core you have to "cold start" CAS by wiping out all the current tickets. You should only have to do that if you actually have changed the Ticket classes, not just because you recompiled the library to change something that has nothing to do with Tickets.
- Yale does not use Single Sign-Out, but code in TicketGrantingTicketImpl added to support that option has a very small but non-zero chance of throwing a ConcurrentAccessException and possibly screwing CAS up. Since we don't need it, Yale might just comment out the statement that causes the problem.
Each of these is a one line change, and only the first is user visible and the last is important for reliablity. If CAS was not open source we would probably just live with the old code, but we have the chance to fix/change it.
Once you start to consider changing Apereo code, then it is inconvenient to maintain two separate projects. For one thing, you need two separate Jenkins build jobs, one for each project. Then you have release numbering issues.
The alternative is to have a single project that combines both the Apereo source for a CAS release (example: 3.5.2.1) and the Yale code that has been built and tested for that CAS release (separately designated Version 1.1.x). Most of the time the Apereo source just sits there and is not used and is not compiled because it is not changed. If you need to make a modification, it is immediately available and can be compiled simply by adding or re-enabling the <module> statement for that project in the top level pom.xml file.
If you look at the Yale single project file without reading this explanation, it may be confusing. There are a large number of subdirectories of Apereo source, and one of them (cas-server-core) is huge. Yet many of these Maven projects are ignored by the Build job. Why are they there? If the base Apereo code on which you are working is all preloaded into the Project directory tree, then it is easily available for debugging, testing, and possible modification during development. If you don't need to use it, then just ignore it.
The Parent pom.xml
The top level directory is the "parent" project. Typically it is named "cas-server" although this is simply a choice you make when you check the source out from SVN into Eclipse. The only thing in the parent project is its pom.xml file, and a few README-type files.
Changing the parent pom.xml file is one of the most complicated problems of migration from one CAS release to another. Any other file in CAS, whether Java source or HTML has pretty much the same purpose at Yale that it had in the original Apereo code.
However, the top level pom.xml file is not just about compiling the source and building the template WAR file (which is the part Yale needs). Apereo is in the business of maintaining an Open Source Project that is distributed widely on the Internet. A large part of their concern is to make sure that all the legal boilerplate is properly maintained. Do the files all have a proper copyright notice? Are all the licenses to the dependency files properly listed? Maven has some impressive support for doing all this open source release management, but unless that is something you specifically have learned, setting up the legal boilerplate is a daunting task.
Yale is a CAS customer. We may have our own files, but unless and until we contribute them back to Apereo we do not care if a file that nobody sees outside Yale has a proper open source copyright notice and license declaration. If we try to use the Apereo top level pom.xml file as distributed, then every time we try to cut a Yale release we get error messages for all the boilerplate that is missing from our own files. So we create our own top level pom.xml that just has the Maven configuration to compile the code and build the WAR.
Every so often Apereo generates a release that has some new processing step added to the build, and that in turn requires adding something to the top level pom.xml file. When that happens we have to notice it and add the same configuration item to our version of the file.
The Yale top level pom.xml file contains the following sections:
- Version - The Yale pom.xml has Yale version IDs (1.1.2-SNAPSHOT) instead of Apereo (3.5.2.1).
- SCM - defines the Yale Subversion server where all Yale projects maintain their production source.
- Repositories - defines Yale's Artifactory, but merged with other sources for artifacts from the Apereo pom.
- pluginManagement - Maven is a modular system of extensible functions implemented by optional "plugin" modules. Yale has to maintain the plugin configurations that actually build the code (the AspectJ processing plugin for example) while dumping the open source project boilerplate managing steps (the maven-license-plugin and maven-notice-plugin for example). We also reconfigure some plugins. For example, when you are done working on Version 1.1.1-SNAPSHOT and want to create the official 1.1.1 Yale version of CAS and then reset the project to begin work on 1.1.2-SNAPSHOT, you call the Yale Jenkins Build job "Perform Maven Release" operation, which in turn uses the maven-release-plugin. At Yale we find that running the JUnit tests during this process is not only slow but a bad idea, so we change the configuration of the release plugin in the parent pom to bypass tests.
- dependencyMangement - the top level pom.xml lists standard version numbers for dependency JAR libraries shared by all the projects at compile and run time. Unfortunately, when Yale develops its own code it may need a later version of the same library. For example, Apereo is happy with commons-io Version 2.0, but Yale code needs Version 2.4. Apache is smart enough to make the 2.4 library downward compatible with programs written for 2.0, but swapping a newer better version of a library is tricky. The first step is that our top level pom.xml declares the 2.4 version to be the one we want so that all our projects use the same version. This also ensures that the 2.4 version of the commons-io JAR will be merged into the WAR during the Overlay processing.
- However, there is a final processing step that has to be done in the WAR Overlay project and is related to but not directly specified in the parent pom.xml file. Because we are using some unmodified CAS 3.5.2.1 modules (particularly cas-server-webapp) and then we merge in new libraries from the WAR Overlay project, the resulting cas.war file would have a WEB-INF/lib with both commons-io-2.0.jar (from vanilla cas-server-webapp) and commons-io-2.4.jar from the Yale Overlay WAR project. So we have to add an exclude statement in the configuration of maven-war-plugin in the WAR Overlay project to remove the unwanted commons-io-2.0.jar file). Generally speaking, we remove any file that the build process would normally include that is bad for JBoss or where there are multiple versions of the same code and we only want to keep the latest version.
- modules - The directory contains the source and pom files for all the Apereo code. However, we only want to compile things that have been changed. Maven only looks at subdirectories mentioned by a <module> statement in the top level pom.xml file. Initially we comment out all the Apereo <module> statements and add statements for the Yale subprojects. When an Apereo project is to be changed, then it is reassigned a Yale (1.1.x-SNAPSHOT) ID and its <module> statement is uncommented. From one CAS release to another the inventory of Apereo projects and <module> statements changes and has to be updated even if they start out all commented out.
- Parameters - At the end of the POM there are parameters <parameter> statements that Apereo uses to specify the version numbers of dependency libraries and Java.
...