...
Each subproject creates a JAR or WAR file. The first project is cas-server-core and it contains builds a JAR file containing about 95% or more of all the CAS code. It has to be built first because all the other projects depend on it. After that, there are projects to create optional components that you may or may not choose to use.
...
Apereo recommends using the WAR Overlay feature of Maven. Yale creates a second WAR project called cas-server-yale-webapp that contains only the files that Yale has changed or added. Generally the WAR Overlay includes:
...
Maven realizes this is a WAR Overlay project because the cas-server-webapp 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 the Yale Overlay project replace files with the same name replace files in the template Template cas-server-webapp project. Other files Files with new names 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 to the output WAR.
Unfortunately, there is no elegant way to delete files in the Template project that you no longer want. There is, however, an Exclude list of files that can be configured in the plugins section of the Overlay project POM that deletes files inelegantly. Yale uses this to delete libraries that Apereo adds to their WAR so it can run in Tomcat, but which we do not want when it runs in JBoss or under Java 1.7 (because newer versions of the same API are built into our execution environment).
Version Numbers and Project Structure
The first Apereo version of CAS was 3.0.0 and now there are CAS 4.x versions. CAS 1 and CAS 2 were written at Yale and are no longer meaningful, but this means that the Maven version numbers starting 1.x and 2.x are free and will never be used by Apereo. So Yale internally is reusing the Version values of 1.0.x, 1.1.x (for our work with CAS 3.5.2) and 1.2.x (for our work with CAS 4).
This means that the actual CAS module that will go into production is stored in the Yale Artifactory network server 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.
Version Numbers and Project Structure
CAS development at Yale only requires Maven projects to build the Yale added JARs (notably the cas-server-expired-password-change and the cas-server-support-CushyTicketRegistry projects) and the WAR Overlay project (cas-server-yale-webapp). There is no need to recompile Apereo projects if we are not going to change them. The WAR Overlay project has <dependency> statements with <version> values that select specific Apereo artifacts. These artifacts do not have to be created at Yale, but could be downloaded from the network like any other JAR library. This even extends to the base cas-server-webapp WAR file. If the WAR Overlay references Version 3.5.3 of cas-server-webapp, then it will start with the vanilla Apereo WAR template downloaded from apereo.org. This single project (the cas-server-yale-webapp project) builds the version of CAS that is ultimately installed and run at Yale. So if you want to know what is actually being used (vanilla Apereo code or some Yale modification) looking at the POM file in cas-server-yale-weapp is the authoritative source of information.
However, Yale does occasionally need to compile and perhaps modify Apereo source. Sometimes these changes are exploratory. Sometimes they help us debug a problem. Sometimes they fix an error that is important to Yale. For whatever reason this code gets compiled, it is critical that no Yale modified version of an Apereo project ever be created with the same Version number as the corresponding vanilla Apereo module. If that happens, then there are two versions of the same JAR file with the same name. One is vanilla code and one is Yale modified code, and once the two get into the Yale system there will be no way to know which you have in any given context.
So even if there is absolutely no intent to change Apereo code, or to use anything other than a Vanilla Apereo library, the first step when working with a new release of CAS from Apereo is to create a Yale base version number for this generation of Yale development and to make sure that any Yale compilation of any Apereo source use the Yale <version> value and not the Apereo vanilla value. Generally speaking, the Yale convention is to increment the middle dotted number (1.x.0) for each iteration of CAS development, and we do not install every CAS version. So Yale version 1.1.x corresponds to the Apereo CAS 3.5.y versions, and Yale 1.2.x corresponds to Apereo 4.0.0. Generally you have to change the version number in the parent project POM, but then you also have to change the <parent> version number in all the subprojects.
Now every time Yale compiles an Apereo project the resulting generated JAR will have the current Yale project number. That JAR will go into the local Maven Repository on the Sandbox machine with the Yale version number (say 1.1.4-SNAPSHOT) and if the source gets checked into Maven and you run the Jenkins Build job then a JAR file with the Yale Version number (say 1.1.4) will be uploaded to the Yale Artifactory network server. However, the Artifactory server will also have the vanilla Apereo version of the same file with the original version number (say 3.5.3). Just because we compiled our own copy of cas-server-core and uploaded it to Artifactory doesn't mean that that JAR file will eventually end up in production. If the cas-server-yale-webapp project calls for version 3.5.3 then it is the vanilla JAR that gets deployed to DEV, TEST, and PROD CAS.
However, if a Yale modified version of the source was every checked into SVN with the Apereo version number and was then built by the Jenkins Build job, then the Yale compiled version would replace the Apereo vanilla version on the Artifactory server and you would lose the ability to distinguish vanilla from Yale modified. It is not a problem if, during the initial exploration of a new release of CAS, you compile Apereo source on a sandbox machine. That just changes the local Maven Repository, and you can always clean it up later by deleting that subdirectory tree in the repository and rebuilding it. You just have to replace Apereo with Yale version numbers on the source before the first Jenkins Build job.
During development, we may make changes to vanilla CAS source to see how something works or to track down a problem. Those changes will be abandoned in the final deployment. The only time we deploy a Yale modified version of a standard Apereo JAR file is if there is a critical Yale bug fix that we need for reliability, or if we have a serious difference of opinion with the Apereo source committers.
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.
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, but it will be build using a vanilla Apereo JAR file stored as http://repository.its.yale.edu/maven2/simple/libs-releases-local/org/jasig/cas/cas-server-core/3.5.2/cas-server-core-3.5.2.jar. It is important to always distinguish the pairs of Version numbers for the Yale components and Yale modified code and the corresponding vanilla Apereo release.
Although there may be additional code in various stages of development, a CAS release at Yale only depends on three Yale projects:
- cas-server-expired-password-change (to prompt the user to change a password that is a year old)
- cas-server-support-CushyTicketRegistry (only the configuration module is currently used to configure ehcache)
- cas-server-yale-webapp (the WAR Overlay with all the Yale configuration and HTML changes)
You could create an Eclipse workspace that included only these three projects. The cas-server-yale-webapp project could be configured with <dependency> statements for the corresponding Apereo release (3.5.2 or 4.0.0) and, of course, it would depend on the JAR files built by the other two projects. That would build a version of CAS that deployed and ran at Yale.
However, if you have any problems that you need to debug, or you find a problem in Apereo code and cannot wait for them to fix it, then you need a way to organize a mixture of unmodified and modified Apereo JAR files.
Yale does not want to modify Apereo code. Even when we find a bug and fix it, if the bug is not critical we may prefer to use the unmodified Apereo module with the bug over deploying a Yale modified version of the same JAR file. However, this is not always possible. It is unreasonable to decide late in a release to switch to a modified version of an Apereo JAR, so Yale adopts a convention.
- Whenever we start to work with a new version of Apereo CAS, we configure the projects so that Yale can easily modify any code and deploy the modifications cleanly and without confusion.
- We almost never actually use this capability.In some releases we don't use it at all.
The rest of this section is to explain how we organize the capability we almost never use. If you need it, you will appreciate why all this effort was worth it.
The trick is manage the version numbers assigned to each project. Remember that, for example, Yale Version 1.2.0 corresponds to Apereo Version 4.0.0.
If you download or check out the Apereo 4.0.0 release and look through the POM files, you will note that:
The top level (parent directory) POM has
Code Block |
---|
<groupId>org.jasig.cas</groupId>
<artifactId>cas-server</artifactId>
<packaging>pom</packaging>
<version>4.0.0</version> |
While all the subdirectories reference this directly as
Code Block |
---|
<parent>
<artifactId>cas-server</artifactId>
<groupId>org.jasig.cas</groupId>
<version>4.0.0</version>
</parent> |
The subprojects inherit the <version> value of the parent they reference. Most of the time, Yale projects are going to want to use and depend on Apereo vanilla JAR files, so inside each Yale project there should by default be a dependency on a 4.0.0 version of all dependencies.
However, the only reason for having Apereo source in the first place is that you may eventually have to change it. When you do, it is no longer Version 4.0.0, nor will it logically have any other Apereo Version number like 4.0.1. Modified code becomes Yale code, and to make that clear it has to have a Yale Version number. So even though you have not changed a single line, the first thing to do with the project is to assign everything the Yale version number it would have if at some future point we needed to change it and deploy the changes. That means changing the 4.0.0 in the parent project and all the subprojects to whatever is the current Yale version number (1.2.0-SNAPSHOT in this example).
Of course, the additional Yale projects also have the same Version number. They have to go into the final cas.war artifact that gets deployed and run on the CAS servers. However, if you now build the Apereo parent project and all its subprojects, you will now get a 1.2.0-SNAPSHOT version of cas-server-core and all the other code. Not only will these JAR files not go into Yale production CAS, they may never be used at all, not even in Sandbox testing. There is no need to include any of the generated 1.2.0-SNAPSHOT JAR files in anything you are doing. The reason for the version number change, and the resulting JAR file name change, is to guarantee a clear distinction between anything you build and the pure vanilla unmodified Apereo version of the same code.
If the WAR Overlay project builds a cas.war file that contains WEB-INF/lib./cas-server-core-4.0.0.jar, then you know it is using vanilla Apereo code. If instead it contains WEB-INF/lib./cas-server-core-1.2.0.jar, then you know it is using a JAR file you compiled. Whether there are any changes to the original source and what those changes are you can only determine by checking the Subversion history, but the JAR file name makes it clear that this library has some Yale modifications.
If the very first thing you do is to change the Version values for all the source projects, then you are assured that Yale modified versions of Apereo modules cannot accidentally get installed. The normal Build process compiles libraries and replaces them in the Artifactory network server. Once a Yale modified version gets installed on top of an Apereo file with the same name, it will be very hard to straighten things out. If the Yale compiled versions always have a different version number and file name, then you know that putting an Apereo Version number in a <dependency> statement will always end up including unmodified Apereo code.
With this preparation, it is no longer necessary or even useful to separate out the Apereo source code from the Yale source code. The Yale projects can be added as new subdirectories of the Apereo parent project, and new <module> statements can be added to build them. The combined Apereo and Yale code can be checked into Subversion, and it can be built with the Jenkins Trunk Build project.
However, when Jenkins builds cas-server-core or cas-server-webapp this way, it creates a version 1.2.0-SNAPSHOT of the artifact. Just because that version is created during the Build job does not mean that it actually ends up in the cas.war. The version that finally goes into the deployed artifact is the version referenced explicitly by number in the cas-server-yale-webapp project, and most of the time that will be 4.0.0.
Why Not Vanilla?
During development, we may make changes to vanilla CAS source to see how something works or to track down a problem. Those changes will be abandoned in the final deployment. The only time we deploy a Yale modified version of a standard Apereo JAR file is if there is a critical Yale bug fix that we need for reliability, or if we have a serious difference of opinion with the Apereo source committers.
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.
Whether we use vanilla Apereo code or make Yale modifications depends on Yale requirements, staffing, and management. There is a cost and commitment to maintaining a modification, but there is also a problem if some application does not work correctly or if some Yale need is not being properly addressed.
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.
...
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 WARgenerally modify the Apereo top level POM file to remove the "open source project and distribution management" junk we don't need. This has to be done again each time we get a new release of CAS.
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.
...