...
The "cas-server-webapp" project is a standard JASIG project that builds a WAR file, but that is not the WAR file we deploy.
WAR Overlay
Maven has a second mechanism for building WAR files. This is called a "WAR Overlay". It is triggered when the POM indicates that the project builds a WAR artifact, but the project also contains a WAR artifact as its first dependency. A WAR cannot contain a second WAR file, so when Maven sees this it decides that the current project is intended to update the contents of the WAR-dependency rather than building a new WAR from scratch.
...
If you made no changes at all to JASIG code, the WAR Overlay project is actually the only thing you would need to check out and edit. Yale would never need to locally compile any CAS source, because the JASIG repository version of the cas-server-webapp.war artifact for that release version number would be perfectly good input to the WAR Overlay process. However, at this time we include all the JASIG source and recompile the JASIG code every time we build the CAS source trunk. If that is a small waste of time, it prepares the structure so we can fix JASIG bugs if they become a problem.
The "cas-server-yale-webapp" project is the WAR Overlay that builds the artifact we actually deploy into production.
Source Build
CAS is distributed as a cas-server directory with a bunch of subdirectories and a POM file. The subdirectories are JAR and WAR projects. The POM file contains a certain amount of boilerplate for JASIG and the CAS project, plus two things that are important. The parameters and dependency management section ensure that all elements of CAS are compiled or built with the same version of library modules (quartz for timer scheduling, hibernate for object to database mapping, etc.). Then a list of <module> statements indicate which subdirectory projects are to be run, and the order in which they are run, when Maven processes the parent POM. The possible modules include:
- cas-server-core - This is the single really big project that contains most of CAS Server code and build the big JAR library artifact. It is built first and all the other projects depend on it and the classes in the JAR file.
- cas-server-webapp - This is a dummy WAR file that contains the vanilla version of the JSP, CSS, HTML, and the XML files that configure Spring and everything else. The dependencies in this project load the basic required JAR libraries into WEB-INF/lib. This WAR is not intended to install or run on its own. It is the input to the second step WAR overlay where additional library jar files will be added and some of these initial files will be replaced with Yale customizations.
- Additional optional extension subprojects of CAS supporting specific features. For example, the cas-server-support-ldap module is needed if you are going to authenticate Netid passwords to AD using LDAP protocol (you can authenticate to AD using Kerberos, in which case you don't need this module). A JASIG standard distribution includes all possible subprojects and the parent cas-server POM file builds all of them. To save development time, you can comment out the subproject <module> statement in the parent POM of modules you don't use. Disk space is cheap, and deleting the subdirectory of modules you don't use may be confusing.
- Yale CAS extension JAR projects. For example, the cas-server-expired-password-change module is the Yale version of what CAS subsequently added as a feature called "LPPE". Yale continues to use its code because the Yale Netid system involves databases and services beyond simply the Active Directory component and support
- cas-server-uberyale-webapp - This is the "sample" version of the local customization Yale customization is (unless it is impossible) stored in the WAR Overlay project . This directory may be renamed to match the artifact name that the POM builds, which is also the artifact name spedified in the Jenkins Install job. However it is named and wherever it is located, Yale customization is (unless it is impossible) stored in the WAR Overlay project directory. If we need Java code, we put this directory. If we need Java code, we put this source here and it is compiled and ends up in WEB-INF/classes. We replace some standard JSP and CSS files to get the Yale look and feel and wording. The XML files that configure Spring and therefore CAS behavior are here. This project must always be the last module executedlisted in the cas-server parent POM so it is built last. The cas-server-webapp project is a dependency, so it that is the base WAR file that will be updated. This project replaces JSP, CSS, and XML files in the base WAR with identically named files from this project. It adds new files and library jars (from the dependency list of this project). Details will be described below, but this is where all the action happens.
...
Yale will have modified the POM file in the cas-server project POM once on your sandbox machine and a target of "install". This compiles all the source files, builds all the JAR and WAR files, and stores them in the local Maven repository on the machine. There are other ways to get the artifacts from the Yale Artifactory server or from JASIG, but compiling them is the easiest way and you probably need the project source anyway. After that, you technically only have to run the WAR Overlay project unless you make a change to any JASIG projects. However, in practice you may find it necessary to change the Version numbers in the parameters and dependency management section of the parent POM if you discover that the JBoss server you deploy to wants a different version of any standard library. Then you want to recompile everything to maintain sanity.
The WAR Overlay project can contain new dependency statements that include JAR files not used by JASIG CAS, but it should never try to introduce a different version number of a library that CAS has already introduced. The Overlay mechanism replaces identically named files, but it will not notice that quartz-1.6.1.jar in WEB-INF/lib from the cas-server-webapp prototype project is a different version of the same library as quartz-1.6.2.jar in the WAR Overlay project.
There are now two ways to organize the projects. You can put everything in the cas-server directory, starting with the JASIG subdirectories and then adding the Yale subdirectories. Then adjust the parent POM in cas-server to build what you want to build each time you run the Build job under Eclipse. Alternately, you can separate the entire JASIG source tree from the Yale project directories. You can also build the JASIG artifacts first with one job and then build the Yale artifacts with a second job, or you can create a single job that builds both at once. There are advantages and disadvantages to either approach, and on a sandbox feel free to use whichever structure seems most comfortable.
It is a common Maven convention to make the parent POM a parent directory and the <module> POMs listed in the parent subdirectories of the parent. However, the child projects reference the parent by the artifact name and version from the local Maven repository, and the parent can reference modules by path expressions. So any arrangement of directories can be made to work. However, remember that the Jenkins "trunk build" job reads Maven source projects checked into Subversion, compiles them, and stores them as artifacts in the Yale Artifactory server. So if at the end of development you end up checking in two directory trees to the Yale SVN repository, you are going to need to Jenkins build jobs.
The POM in a vanilla JASIG component subproject will have a Version number that corresponds to CAS releases (3.5.2). The POM in a Yale customization subcomponent will have a Version number that corresponds to the Yale CAS installation "Release" number for Jenkins tracking (1.1.0-SNAPSHOT). The artifact name and version number in the WAR Overlay project must match the artifact name and version number of the Jenkins Install job parameters.
The Installer
Yale has a convention that there is a separate Install job. This job reads a Maven project from SVN that contains parameters and an Ant script. Maven runs Ant, and Ant downloads the artifact from Artifactory (in this case, the WAR file produced by the WAR Overlay project) and copies it with some variable substitution to the deploy directory of JBoss.
The Installer is driven by an install.properties file. This file contains passwords used to access AD and databases, so it is not checked into SVN. Get a copy of this file saved by the last person who worked on CAS saved in some more secure or private file area and add it to the project directory after checking the Installer project out of SVN.
Standalone.xml
In theory, the developer of a project does not have passwords for the production databases, so runtime parameters are provided from a source outside the WAR. A common Yale convention was to build XML files defining each database as a JBoss Datasource and creating a service that stuffed a group of parameters into the JBoss JNDI space where they could be read at runtime using the built in java.naming classes. The Install job used to build these XML files.
However, the JBoss EAP 6.1 rewrite has changed the JBoss best practice. Now both Datasources and JNDI values are stored in the main "subsystem" configuration file. In the sandbox, this is the standalone.xml file in the /config subdirectory of the JBoss server. You need to get a copy of an appropriate configuration file for your JBoss and install it yourself. Production services will be responsible for providing the file in DEV, TEST, and PROD, but you need to notify them if you add something during sandbox development so they can also add it to the versions they manage.
To Work on Current Release
Check out from SVN the current version of the cas-server source. (If the Yale source is in a separate directory check it out too, but it has probably been added as subdirectories of the cas-server directory.) Also check out the Installer project directory. Get a copy of install.properties for the Installer project and make sure the jboss.deploy.dir points to the location where you put JBoss on your sandbox.
If it is at all possible, make changes only to the WAR Overlay project subdirectory.
Sometimes you need behavior that is just slightly different from an existing JASIG version of some bean. The design of CAS is to have standard interfaces, classes that implement these interfaces, and configuration done with Spring XML files that select the appropriate implementation class using a <bean> element. If the class you want to change is configured this way to Spring (search for its class name in XML files), then you can often make a copy of the JASIG source in the WAR Overlay src/main/java tree, changing the org.jasig package name to edu.yale and so on. Then change the behavior of the source you just copied. Now go to the XML configuration that references the original class by name and change the fully qualified classname to match the new class you just created. Typically the interfaces don't change between releases, so you may not have to update your modified source when you migrate to a new release.
What cannot be changed in the WAR Overlay project? If a class or interface is referenced by import in Java source, then the Java mechanism to search for a class by name (the "ClassLoader") typically searches first for a class of the desired name that came from the same source as the class that is looking for it. JBoss regards the WEB-INF/lib and the WEB-INF/classes as two separate sources. Although WEB-INF/classes is said to be "searched first", when some code in cas-server-core is looking for a class name it will find the version of that name that is also in cas-server-core and will not find something with the same name in WEB-INF/classes. So in general you cannot simply copy any org.jasig source to the WAR Overlay source directory, compile it, and then assume that the version you just compiled will override the original code because it is in the "classes" directory and "classes is searched before lib". Think really hard if you need to change something here, but then if you really need to change it you need to make a modification to the original JASIG source and then copy and reconcile that change to each subsequent releaseparent project to comment out "module" references to optional artifacts that we are not using in the current CAS project. It makes no sense to build cas-server-support-radius if you are not using it.
It is possible for Yale to also delete the subproject subdirectory from the instance of cas-server source checked into SVN. If we are not building or using the module, we don't need to maintain it in the project source. However, this makes relatively little difference. In CAS 3.5.2 the entire JASIG source is 1628 files or 10 megabytes of storage, while the optional projects we don't use account for only 210 files or 500K. So deleting the unused optional source modules saves very little time for every Trunk Build checkout and since we don't compile them, there is no processing time cost.
The Road Not Taken
The reference from a child project to its parent uses an artifact name and version, and the parent is found in the local Maven repository rather than the parent directory. So storing the subprojects as subdirectories under the cas-server parent is not a physical requirement. It does make things simpler.
It would have been possible to separate out the JASIG code in one directory of SVN, and then put the Yale code in another directory. This would slightly simplify the documentation because you would know what is Yale and what is JASIG. But that would only be a slight improvement.
We still have to decide how to handle bugs or problems in the JASIG source. When an individual file has been changed in the JASIG source because that is the only place it can be changed, then that file and the project directory that contains it is no longer vanilla JASIG code. At that point there has to be an entry in the Release Notes to show that the file has been changed, and now separate directories do not simplify the documentation.
This also means that there have to be two Jenkins Build jobs, one for the JASIG part of the source and one for the Yale part of the source. Remember, the Jenkins job structure at Yale assumes that a Build job checks out one directory and all its subdirectories.
Therefore, we compared the advantages and disadvantages, flipped a coin, and decided to check into SVN a directory consisting of JASIG source merged with Yale created project subdirectories. We do not claim that this is the right way to do things, but neither is it obviously wrong.
The POM in a vanilla JASIG component subproject will have a Version number that corresponds to CAS releases (3.5.2). The POM in a Yale customization subcomponent will have a Version number that corresponds to the Yale CAS installation "Release" number for Jenkins tracking (1.1.0-SNAPSHOT). The artifact name and version number in the WAR Overlay project must match the artifact name and version number of the Jenkins Install job parameters.
Standalone.xml
In theory, the developer of a project does not have passwords for the production databases, so runtime parameters are provided from a source outside the WAR. A common Yale convention was to build XML files defining each database as a JBoss Datasource and creating a service that stuffed a group of parameters into the JBoss JNDI space where they could be read at runtime using the built in java.naming classes. The Install job used to build these XML files.
However, the JBoss EAP 6.1 rewrite has changed the JBoss best practice. Now both Datasources and JNDI values are stored in the main "subsystem" configuration file. In the sandbox, this is the standalone.xml file in the /config subdirectory of the JBoss server. You need to get a copy of an appropriate configuration file for your JBoss and install it yourself. Production services will be responsible for providing the file in DEV, TEST, and PROD, but you need to notify them if you add something during sandbox development so they can also add it to the versions they manage.
Best Practice
Check out from SVN the current version of the cas-server source. Also check out the Installer project directory. Get a copy of install.properties for the Installer project from someone who has one, and make sure the jboss.deploy.dir points to the location where you put JBoss on your sandbox.
If it is at all possible, make changes only to the WAR Overlay project subdirectory.
CAS is vastly over engineered. An effort was made to deliver a product that anyone can configure and customize using only XML files. No Java programming is required. So if you are a Java programmer, you already have an advantage over the target audience.
CAS has a set of key interfaces which are then implemented with one or more Java classes. The choice of which Java class you will use as a AuthenticationManager, AuthenticationHandler, TicketCache, or Web Flow state is made when the fully qualified name of the class is specified in one of the XML bean configuration statements.
Sometimes you need behavior that is just slightly different from the standard JASIG behavior. Make a reasonable effort to see if you can find an alternate implementation class or an alternate configuration of the standard class that has the behavior you want. If not, then instead of modifying the org.jasig.cas code in the JASIG source, see if this is one of the "bean" classes named in the XML. If so, then just make your own copy of the original source in the WAR Overlay project source area and rename its package to a edu.yale.its.tp.cas name. Change it to do what you want, and then change the fully qualified name in the XML to reference the new name of the modified class you have put in the Yale customization project.
Of course, JASIG may change the source of its original class in some future release. However, classes that implement standard interfaces do not really need to pick up every JASIG change. If they fix a bug, then you want to copy over the new code. If they simply add a new feature or change the behavior of something that you are not using, then there is no need to update your modified copy of the original JASIG code. Essentially, the modified code has become 100% Yale stuff no matter who originally authored it and if it continues to do what you need then there is no need to change it in future releases, unless the interface changes.
Most strategic changes in behavior can be coded in Java source complied as part of the WAR Overlay project. What cannot be changed in the WAR Overlay project? If a class is not named in the XML configuration files, but instead is imported into some other Java source file, then you cannot in general replace it with new code in the WEB-INF/classes directory.
This is a consequence of some fine print in the Java implementation of ClassLoaders. Since this is computer logic stuff, I can boil it down to some simple logic, but it has to be expressed as a set of rules:
Every class is loaded by a ClassLoader.
In JBoss, there is one ClassLoader for WEB-INF/classes and a second ClassLoader for WEB-INF/lib. Then there are ClassLoaders for EARs and for the JBoss module JAR files, but they aren't important here.
When the XML references a class by name, JBoss searches first through the classes compiled in the WAR Overlay project and stored in the WEB-INF/classes directory, and then it searches through WEB-INF/lib.So it finds the WAR Overlay source first and uses it.
However, when an already loaded class references another class with an import statement, then Java searches for the new class by first using the ClassLoader that loaded the class that is looking for it. This means that any class loaded from WEB-INF/lib will first search for all of its import names in WEB-INF/lib first, and only later search WEB-INF/classes.
Therefore, you can override XML named classes using source from the WAR Overlay project, but the other classes that are imported into JASIG source files probably have to be edited and changed in their original JASIG project. If you have to make a change, identify the modified JASIG source in the ReleaseNotes, and if it is a bug change submit it to JASIG to be fixed in future releases.