CAS source and configuration files are stored in the Yale Subversion repository at https://svn.its.yale.edu/repos/cas. If you are updating the current release, everything you need will be there. Check out the cas-server project and the installer project and follow the instructions below.
An SVN directory of CAS source begins by checking in a new JASIG release. Then the additional Yale JAR project directories are added and one Yale WAR project is added. The top level POM file will be modified to add the new Yale projects, to disable recompilation of the optional JASIG projects that we are not using, and possibly to set the Version numbers of certain standard library files that are dependencies for the JASIG and Yale source.
JASIG uses Git as a source management tool, while Yale still uses SVN. So while you are free to check out the current release with Git, you can just as easily download the zip file. Either way, the resulting code has to be checked into SVN.
Yale attempts to make very few changes to any JASIG code. By starting with a fresh JASIG release and by then merging Yale code and reapplying any Yale fixes or modifications, the SVN history will describe what has been changed in the past release and will tell you what needs to be changed to migrate to the next release.
The ReleaseNotes.html file in in the root of the cas-server project will identify the current state of new directories and any modified files. This should be a descriptive match for the physical code change history in SVN.
Yale and JASIG use Eclipse as the IDE. The most current version of Eclipse works best. Start with the J2EE package of Eclipse, and add SVN and Maven support.
Check Out and Build the Project
Open the SVN Repository Exploring "perspective" (page) of Eclipse and define the repository url. Right click the trunk of the current CAS source project and select the simple Check out menu option. Do not run the Check Out As wizard or try at this point to make it a Java project. Simple check out creates an Eclipse "project" directory in the workspace but at this point it has no attributes or nature or build process.
Now return to the J2EE perspective, right click the new project directory, and choose Import - Maven - Existing Maven Projects. The Eclipse support for Maven will search the directory for its POM and then find POM files in the subdirectories.It will then display a list of the subdirectories that contain Maven projects listed in the parent POM. Select the checkbox for all of them and push the OK button.
At this point Eclipse will do a lot of work. It is running through all the POM files understanding what they say and is downloading from the internet into the local Maven repository all the JAR files that any of the CAS projects identify as a dependency. It creates in the Eclipse workspace a "shadow project" for each subdirectory project. This shadow is an Eclipse project with .project, .classpath, and .settings files and folders with the correct options and settings to compile Java source for the corresponding JAR or WAR that the Maven POM is going to build.
There are several types of error messages generated. Some JSP and HTML files are fragments that are not regarded as well formed, but that is because CAS will assemble a page from several fragment pieces which only become well formed when they are completely assembled. There will also be a Maven error because the Eclipse support for Maven (really the Eclipse interactive simulation of Maven batch function) does not simulate the Aspectj maven plugin. This is not a real problem because we will not be using Eclipse simulation to run CAS under JBoss, but will instead run a full Maven batch command where all the standard Maven plugins are available to build real JAR and WAR files. So ignore the errors or click OK.
When Eclipse is done with the import, it will have created shadow project directories named cas-server-core, cas-server-documentation, and so on. There is now a discrepancy between the Eclipse view of the workspace and the reality you would see if you opened the workspace directory using the operating system file explorer. From the Eclipse project view, each new project appears to have the Java source files and resource data files for that project. However, Eclipse also shows what appears to be a second copy of the same files in the various src/main subdirectories of each project subdirectory of the parent "cas-server" project. These are two views of the same files, and really the only copy of the real file is in the subdirectory of cas-server. The Java view of packages and source files in the shadow directories such as "cas-server-support-ldap" are a logical view of what Eclipse found in the project POM. This means that if you do certain global operations like Search - File you may find the same file returned twice by the matching process, once in its physical location under cas-server and once in its logical project location under one of the shadow projects. You can edit the file in either parent "folder" and any changes you make apply to both views because there is really only one file.
Interactive and Batch Modes
Eclipse wants to make things easier than they can really be. It wants to extract information from the Maven POM but then to compile the source and build the "WAR" itself. However, it doesn't really want to build a WAR file and then copy it over to the Tomcat or JBoss server. So it has some special logic to "hack" the configuration of Tomcat and JBoss servers so that they run with an imaginary WAR that doesn't really exist in their directories but is being simulated by Eclipse.
This works fine for Hello World and maybe the next five Web applications you write, but pretty soon you want to do something that Eclipse is not ready to simulate. Alternately, you may want to use some Maven feature that Eclipse cannot duplicate.
At this point what you really want to do is to run not the integrated simulation of Maven and JBoss function built into Eclipse, but rather you want to run Real Maven and Real JBoss. You can do that in Eclipse, but for sanity you have to realize explicitly that you are rejecting the "simplified" environment that Eclipse tried to create for you. You also have to understand what Eclipse just tried to do, what it accomplished, and how to do the rest yourself.
Eclipse reads the Maven POM file and creates an Eclipse project that tries to do the same thing. The Eclipse compiler can use roughly the same options to compile the same source library to the same target directory of class files. The Eclipse Maven support can turn the dependencies in the POM file into an Eclipse .classpath file that gets the right Java library files from the local Maven (.m2) repository directory. However, Maven has support for additional operations controlled by "plugin" elements of the POM file, and Eclipse does not support these extra steps.
What you can hope for is an Eclipse project where Eclipse has a .classpath built from the dependencies in the POM to find the right version of the right JAR files from the local Maven repository (by default in the .m2 subdirectory of you home directory). Thanks to this work, the source compiles without any missing includes and you get the benefits of autocomplete and autocorrect from the Eclipse editor. That is about as much as you need. It isn't important that Eclipse cannot build the real WAR file because you are going to use a real Maven batch run to do that.
Eclipse recompiles each Java source file when you save it. Since the Eclipse project was created from the Maven POM, it compiles the same source directories that Maven uses and it puts the class files in the same place Maven puts files when it compiles them. By default, Maven only recompiles a program if the source is newer than the class file. So normally, the Eclipse compiler always "wins" the race and all the source files in the project will have been compiled by Eclipse. If there is strange behavior and you believe it is due to differences in the Eclipse and Maven compile options, you can recompile everything by adding "clean" to the Maven goals.
Running Maven Jobs Under Eclipse
At any time you can right click a POM file and choose "Run as ..." and select a goal (clean, compile, install). Rather than taking all the default configuration parameters, or entering them each time you run Maven, you can build a Run Configuration. Basically, this is a simple job with one step that runs Maven. The Run Configuration panel allows you to select every possible option, including the Java Runtime you want to use to run Maven, and a specific Maven installation so you can run some things under Maven 2 and some under Maven 3, plus the standard Maven options to not run tests or to print verbose messages. Since the Jenkins jobs each run one Maven POM, in the Sandbox you can build two Maven Run Configurations that duplicate the function of each Jenkins job.
First, lets review what the Jenkins jobs do.
- The "Trunk Build" job checks the source project out of subversion. In this case, it is a "parent" project with subdirectories that all get checked out. It then runs a "mvn install" on the parent. The parent POM contains "module" XML elements indicating which subdirectory projects are to be run. Each subdirectory project generates a JAR or WAR artifact. The Jenkins Trunk Build installs these artifacts on the Artifactory server replacing any prior artifact with the same name and version number.
- The Jenkins Install job checks the source of the Install project out of SVN. By Yale convention, each Install project is an Ant build.xml script and a package of parameters in an install.properties file created from the Parameters entered or defaulted by the person running the Jenkins job. Minimally the Install job downloads the artifact from Artifactory and copies it to the JBoss deploy directory, although typically the artifact will be unzipped, variables will be located and replaced by the values in the properties file, and then the file will be rezipped and deployed.
In the Sandbox you already have a copy of the CAS source files checked out in your Eclipse project in your workspace, and you can also check out a copy of the Installer project. So there is no need to access SVN. Similarly, in the Sandbox we do not want to mess up Artifactory, so the local Maven repository (which defaults to a subdirectory tree under the .m2 directory in your HOME folder) holds the generated artifacts.
With these changes, a Build project compiles the source (in the Eclipse workspace) and generates artifacts in the .m2 Maven local repostory. The Install project runs the Ant script with a special Sandbox version of the install.properties to copy the artifact from the .m2 local repository to the Sandbox JBoss Deploy directory (with property edits).
There is one last step. The Jenkins install job stops and starts JBoss. In the Sandbox you will manually start and stop the JBoss server using the JBoss AS Eclipse plugin. This plugin generates icons in the Eclipse toolbar to Start, Stop, and Debug JBoss under Eclipse. You Stop JBoss before running the Install job and then start it (typically using the Debug version of start) after the Install completes.
Now to the mechanics of defining a Run Configuration. You can view them by selecting the Run - Run Configurations menu option. Normally you think of using Run configurations to run an application, but once you install M2E there is also a Maven Build section. Here you can choose a particular Maven runtime directory, a set of goals ("clean install" for example), and some options to run Maven in a project directory. The recommendation is that on your sandbox machine you create two Maven Run Configurations. The configuration labelled "CAS Build" runs a Maven "install" or "clean install" goal on the parent POM of the CAS source project. The configuration labelled "CAS Install" runs Maven on the POM of the Installer project (and it has to run Maven 2 because this is still the standard for Installer jobs).
CAS Source Project Structure
In Maven, a POM with a packaging type of "pom" contains only documentation, parameters, and dependency configuration info. It is used to configure other projects, but generates no artifact. The parent "cas-server" directory is a "pom" project. All the subdirectories point to this parent project, so when the parent sets the Log4J version to 1.2.15 then all the subprojects inherit that version number from the parent and every part of CAS uses the same Log4J library to compile and at runtime.
A "jar" Maven project compiles Java source and combines the resulting *.class files and any resource data files into a zip file with an extension of ".jar". This is an artifact that is stored in the repository and is used by other projects. The most important CAS subproject is "cas-server-core", which compiles most of the CAS source and puts it in a very large JAR file. The other CAS subprojects compile optional JAR files that you may or may not need depending on which configuration options you use. Any JAR file created by one of these subprojects will end up in the WEB-INF/lib directory of the CAS WAR file.
A type "war" Maven project builds a WAR file from three sources:
- It can have some Java source in src/main/java, which is compiled and the resulting class files are stored in the WEB-INF/classes directory of the WAR.
- The files in the src/main/webapp resource directory contain the HTML, JSP, XML, CSS, and other Web data files.They are simply copied to the WAR.
- Any JAR files that are listed as a runtime dependency not provided by the container in the WAR project POM are copied from the Maven repository to the WEB-INF/lib directory.
Then the resulting files are zipped up to create a WAR artifact which is stored in the Maven repository under the version number specified in the POM.
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.
A WAR Overlay project can have Java source in src/main/java, in which case the source will be compiled and will be added to or replace identically named *.class files in the old WAR-dependency.
Mostly, the WAR Overlay replaces or adds files from src/main/webapp. This allows Yale to customize the CSS files or create special wording in the JSP and HTML pages.
JAR files listed as a runtime dependency of the Web Overlay project are added to the WEB-INF/lib directory along with all the other JAR files in the original WAR. In this case, however, replacing doesn't make any sense. A JAR file with the same name has the same Version number, so it will be a copy of the exact same file that is already there. Because WAR Overlay only replaces identically named files, you really do not want to have an artifact with one Version number in the WAR Overlay project and a different Version number in the old WAR because they cannot be merged and the result will be garbage.
The JASIG recommends using the WAR Overlay mechanism, and the Yale CAS customizations follow that rule. In the JASIG distributed source the WAR Overlay project subdirectory is named cas-server-uber-webapp, but Yale's version of this project is named cas-server-yale-webapp. This project produces the artifact named edu.yale.its.cas.cas-server-war with the version number that we track in Jenkins and Subversion as the actual CAS Server executable.
CAS has traditionally been created using the "WAR Overlay" technique of Maven. First, the cas-server-webapp directory builds a conventional WAR from its own files and dependencies. This is a generic starting point WAR file with all the basic JAR files and XML configuration. Then a second type "war" project is run that (in the standard JASIG CAS distribution) is called cas-server-uber-webapp. What makes it different from a standard type "war" Maven project is that the first dependency in this project is the cas-server-webapp.war file build by the previous step.
A "WAR Overlay" project depends on an initial vanilla prototype WAR file as its starting point. Everything in that WAR will be copied to a new WAR file with a new name and version, unless a replacement file with the same name is found in the WAR Overlay. When Yale or any other CAS customer is building their own configuration of CAS, the WAR Overlay project directory provides a convenient place to store the Yale configuration changes that replace the prototype files of the same name in the previously run generic cas-server-webapp type "war" project.
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-yale-webapp - Yale customization is (unless it is impossible) stored in the WAR Overlay project 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 listed in the cas-server parent POM so it is built last. The cas-server-webapp project is a dependency, so 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).
Yale will have modified the POM file in the cas-server parent 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.
The Debug Cycle
Make changes to the files and save them.
Once you run the CAS Build and CAS Install Run Configuration once, they appear in the recently used Run Configurations from the Run pulldown in the Eclipse toolbar. There is a Run icon (a right pointing triangle on a green circle) and following it there is a downward pointing "pulldown menu" icon that shows the list of recently run configurations.
Once you install JBoss Tools, there is a separate JBoss Run (a green right arrow) farther over to the right on the toolbar.However, during testing you probably do not want to run JBoss in normal mode but instead want to press the JBoss Debug button (the icon of a bug that follows the JBoss Run arrow on the toolbar.
So the normal cycle is to stop the JBoss Server, edit and save the source, the run the CAS Build Maven job, then the CAS Install Maven job, and then restart JBoss (in debug mode).
After The Sandbox
Changed files must eventually be Committed to the SVN trunk.
Do not commit the generated "target" subdirectories to SVN. They contain temporary files that should not be saved.
Once you are ready to proceed to the next phase, run a Jenkins Trunk Build and do a DEV Install.