Shadowed Code Resolution Guide
Shadowed code is code added to project to directly exploit Tomcat's classloader which looks for classes from WEB-INF/classes
prior to looking in WEB-INF/lib
. Placing a java class file into the war-building module (site, web, etc.) with the same class and package name as one contained in an upstream dependency will result in a duplicate .class
file in WEB-INF/classes
. This duplicate .class
file will be preferentially loaded instead of the upstream file in WEB-INF/lib
. The shadowed java class and its methods will be instantiated and invoked instead of the original code for the upstream dependency. While this trick can be useful as a way of diagnosing suspected problems in upstream dependencies, it also has consequences that can complicate the upgrade process. Using code shadowing as a means of building divergent functionality from the upstream library should be avoided.
The following problems are commonly associated with shadowed java code:
- Shadowed classes' code is "locked in" as forked versions of their original upstream versions. These files will no longer automatically receive changes when the upstream dependency is changed. This can be detrimental and confusing when expected bug fixes or changes are not present after updating the dependency.
- Shadowed classes may contain code which is no longer invoked or supported in the upstream library after an upgrade. Regression testing of shadowed functionality can be complex and involves both the previous and latest test cases, which can be difficult to obtain or coordinate.
- Analysis of shadowed class files that are not documented can be difficult to discern, and the consequence of making updates to these files may be unclear if their original purpose is not known.
When upgrading a project, the complications from shadowed code described above warrant a thorough analysis and remediation of this code to the extent possible. In an ideal remediation, the shadowed code can be removed in favor of more transparent and upgrade-compatible implementations.
In order of preference, shadowed code should be:
- Removed in favor of an upstream accommodation or fix.
- Removed in favor of an alternate implementation.
- Materialized into the project along with all other files from the dependency and the dependency removed. This is explicit code forking and is only warranted in some special cases, such as an archetype or starter kit artifact being used as a dependency. Consult your account representative prior to proceeding with this type of activity.
- Updated using knowledge of the newer version of the original upstream file to incorporate both upstream and custom changes made since the file was shadowed. This activity is often extremely complex, and a form of removal should always be preferred to maintaining a shadowed java file in perpetuity. If this update activity is undertaken, the shadowed file will continue to exist and require a full analysis at every upgrade of the upstream dependency.
In order to decide on a course of action for each shadowed file, a thorough analysis of the file is required.
Plan to construct an analysis document for each file, and collect any relevant information from the following sources to assist in this analysis:
- Comments at the top of the file describing the purpose and origin version of the shadowed file.
- Full commit history of the shadowed file.
- Any branch / pull-request containing git commits to the shadowed file.
- Jira or other work-tracking tickets or materials related to the file via commits / pull request titles / branch names.
Try to answer these basic questions before deciding on a course of action:
- Which file is being shadowed, and from which upstream dependency?
- What version of the upstream dependency was the shadow created from?
- What logic was changed from the original version of the file?
- What was the purpose of the logic that was changed, and what was the intended purpose of the change in the shadowed file?
- What parts of the original file have been changed in the upgraded dependency version?
- Has the logic that was altered in the shadow been changed in the original?
After you have done the analysis above, you should have enough information to determine the intent and can begin considering resolution techniques.
Below are some possible scenarios with resolution techniques that can be used with the shadowed code. The scenarios are a sampling and not meant to cover every case. All scenarios require that the above analysis has been completed prior to attempting to decide how to proceed.
- Was the shadowed file a temporary workaround for an upstream bug?
- If the bug was fixed in the updated version, the shadow can be safely removed.
- If the bug was not fixed in the updated version, contact your account representative for further guidance.
- Was the shadowed file added to produce functionality that is not otherwise supported by the upstream dependency?
- If the custom functionality is no longer needed or used, then proceed to remove the shadowed code.
- If the custom functionality is still needed or used, contact your account representative for further guidance.
- If all else fails, and it is determined that the shadowed file is needed, make a fresh copy from the upstream source at the new version and attempt to re-apply the logic changes to the file. It is highly recommended to work completely through the above two options before taking on this activity. An updated shadow still needs to be thoroughly regression tested and fully re-anlyzed for every future upgrade.