-
Notifications
You must be signed in to change notification settings - Fork 186
Quickstart to Spacewalk Java Codebase
This document is intended to be read from people starting hacking into the Java codebase.
Note that developing new code using Struts is strongly discouraged. ReactJS should now be used for anything new.
The Java code implements:
- A user-facing web application based on Struts (an MVC framework which predates Rails).
- An XMLRPC API for general use that covers basically all functionality of the web app (implemented using the [Redstone XMLRPC library] (http://xmlrpc.sourceforge.net/)).
- A daemon called "Taskomatic" which runs jobs at specified time intervals. If this sounds a lot like
cron
, well, it's because the concept is basically the same. - Another daemon to implement full-text search (
spacewalk-search
).
All web-facing Java code is contained in Tomcat and served via Apache httpd with the jk module (mod_jk
).
TL;DR: Search for the path in the URL (removing the ".do" part) in struts-config.xml
. There you will find the name of the JSP file (see input=*.jsp
) and Java Action class (see type=...
) containing the code you are looking for.
This is what actually is happening:
- You click on some link in any of the menus or navigation, e.g. Patches from the main menu
- HTTP request hits httpd, eg. GET https://manager.suse.de/rhn/errata/RelevantErrata.do
- httpd routes it to Tomcat through
mod_jk
- Tomcat dispatches it to the correct Servlet, which is a Java term for "something written in Java that responds to HTTP requests". The choice of the right Servlet depends on rules in
java/code/webapp/WEB-INF/web.xml
, which is called a deployment descriptor. It contains rules like this one:
<servlet-mapping>
<servlet-name>action</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
This rule defines that all URLs ending with ".do" should go to the action
servlet, defined in the same file to be org.apache.struts.action.ActionServlet
with configuration file java/code/webapp/WEB-INF/struts-config.xml
-
ActionServlet
looks intojava/code/webapp/WEB-INF/struts-config.xml
to decide what class should pick up. In this case:
<action path="/errata/RelevantErrata"
scope="request"
input="/WEB-INF/pages/errata/relevant.jsp"
type="com.redhat.rhn.frontend.action.errata.ErrataListRelevantAction">
<forward name="default"
path="/WEB-INF/pages/errata/relevant.jsp" />
</action>
This means that the next Java class to pick up the request will be ErratListRelevantAction
(in file ErrataListRelevantAction.java
) together with JSP file pages/errata/relevant.jsp
(roughly equivalent to routes.rb
in Rails). Note that in struts-config.xml
the .do part of the URL is stripped.
-
ErratListRelevantAction
is a Struts action class, that is a controller in MVC. It inherits from Action and it has a method like this:
ActionForward execute(ActionMapping mapping,
ActionForm formIn,
HttpServletRequest request,
HttpServletResponse response) {
// .. controller code
}
In this case ErrataListRelevantAction extends ErrataListBaseAction, and the execute
method is actually defined in the latter.
Controller code is finally executed. It will typically access the DB in some way, and set Context objects in the response
variable, which is then passed to the template (.jsp
file).
- In general, any Struts Action may result in the rendering of one among several JSPs (in this case only one is used), those are called "forwards" in
struts.xml
. Long story short, theexecute
method above must decide which forward to use by callingactionMapping.findForward("<forward name here>");
- JSP is rendered. To know the markup, please see the official documentation.
- Output is sent back to httpd, then to the browser.
The data (relevant patches) in the above example are determined using the ErrataManager
class. Most domain classes have their corresponding *Manager
class. These act as a service to interact with the database in terms of performing queries that return results, but also to insert new data.
If for example you wanted to list all patches in a certain channel, where in Rails you did something like:
Errata.find_in_channel(:channel => channel)
In Spacewalk you would do:
ErrataManager.errataInChannel(cid)
The ErrataManager
class uses a NIH framework for performing the queries (other classes might use Hibernate 3, which is a whole different story and has a much heftier manual). If you look at the code, it does the following:
public static DataResult errataInChannel(Long cid) {
SelectMode m = ModeFactory.getMode("Errata_queries", "channel_errata_for_list");
Map params = new HashMap();
params.put("cid", cid);
DataResult dr = m.execute(params);
return dr;
}
The actual queries are defined in XML files where there is roughly one file per domain entity: Channel_queries.xml
, Errata_queries.xml
, etc. In our example ModeFactory
looks for the "channel_errata_for_list" query that is defined in this file:
/code/src/com/redhat/rhn/common/db/datasource/xml/Errata_queries.xml
<mode name="channel_errata_for_list" class="com.redhat.rhn.frontend.dto.ErrataOverview">
<query params="cid">
SELECT DISTINCT E.id,
E.advisory_name AS advisory,
E.update_date AS update_date,
E.synopsis as advisory_synopsis,
E.advisory_type as advisory_type
FROM rhnErrata E,
rhnChannelErrata CE
WHERE CE.channel_id = :cid
AND CE.errata_id = E.id
</query>
</mode>
The XML file specifies that the result of this query is mapped to the ErrataOverview
class in the dto namespace. DTO (Data Transfer Object) classes are simple data containers used to carry data between processes, e.g. from the backend to the UI of an application.
There is more than one DTO per domain entity as DTOs contain only the data needed for a certain user interface display. ErrataOverview
for instance contains only some attributes with their respective getter and setter methods (getFoo()
and setFoo()
) and is used in various places where a selectable list of Errata is displayed.
Those XML files further also specify the parameters that need to be passed to the defined queries.
The Spacewalk Java code is full of Collection
usage without generics. This is from the Java 1.4 times and it is discouraged nowadays. Assignments like:
Map params = new HashMap();
Should be written like this (assuming that the map uses strings as keys and values):
Map<String, String> params = new HashMap<String, String>();
This moves runtime errors to compile time errors avoiding casting.
If you ask yourself why Map
on the left and HashMap
on the right: this is to program against the interface, and not the specific implementation (HashMap
implements the interface Map
, see Map Javadoc).
In the same way DataResult
should actually be DataResult<ErrataOverview>
.
JSP is a templating technology based on XML, that means a JSP file is basically an XML document that has some markup changed at runtime (we also use it for HTML5, which is technically not XML, but it works nevertheless).
JSPs are typically compiled at runtime into Java classes by Tomcat, or they can be pre-compiled at RPM time.
Here are some basics, refer to the reference cheat sheet for more details.
<%@ taglib uri="http://struts.apache.org/tags-html" prefix="html"%>
This loads a set of custom tags that can be used later. Those could be from standard libraries, third-party libraries or Spacewalk-specific custom tag libraries.
In this particular case, we are loading the HTML set of tags from the Struts tag library. This allows us to use tags such as:
<html:form action="/audit/CVEAudit.do" styleClass="form-horizontal">
Here html:
is the tag prefix (that can be freely chosen in the above statement where it is loaded) and form
is the tag name. The purpose of this tag is to output a standard HTML form
tag with some extra bells and whistles (I know, it's a silly example, but I had to start somewhere). The full list of html: tags is here.
Other typical taglibs are:
-
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
: control structures like<c:if>
and<c:for>
; -
<%@ taglib uri="http://struts.apache.org/tags-bean" prefix="bean"%>
: allow to read Java Beans from the JSP; -
<%@ taglib uri="http://rhn.redhat.com/rhn" prefix="rhn"%>
: Spacewalk specific, generic tags; -
<%@ taglib uri="http://rhn.redhat.com/tags/list" prefix="rl"%>
: Spacewalk specific tags for lists (eg. systems list, patch list, etc);
We will cover these briefly.
<c:if test="${group.systemCount != 1}">
Hello World!
</c:if>
This "prints" Hello World! in the HTML markup only if the condition in test
is true. Unfortunately, the expression ${group.systemCount != 1}
is written in a specific language which is not Java! It's called EL and it's documented here.
Note that <c:if>
tags have no else
clause (grrr!). Thankfully there are multi-ifs, which are called choose/when:
<c:choose>
<c:when test="${some condition 1}">
Condition 1 is true!
</c:when>
<c:when test="${some condition 2}">
Condition 2 is true!
</c:when>
</c:choose>
Now an iteration:
<c:forEach items="${groups}" var="group">
<tr class="group" data-sort-order="${group.sortOrder}">
</c:forEach>
This code emits a tr
tag for every item in groups
. Notice how EL is used for items
and data-sort-order
.
Add an EL expression result to the page:
<c:out value="${1 + 2}"/> <!-- "prints out" 3 -->
Add a translated string that's defined in a translation file, using the current language:
<bean:message key="actionchain.jsp.title"/>
See Localization for translation file locations.
We prefer always using translations instead of hardcoding strings to be uniform to what upstream does.
The simplest way is to use Java Beans in EL (Beans are normal Java objects that have getSomething()
methods, colloquially referred to as getters). Beans can be made visible to EL by setting them as attributes to the request object, like this:
// from ActionChainEditAction.execute(...)
ActionChain actionChain = ...; // assign some Java Bean here
request.setAttribute("actionChain", actionChain); // pass it to JSP
Corresponding JSP might include:
<c:out value="${actionChain.label}"/> <!-- Prints out actionChain.getLabel() -->
Note the Java Bean convention here: getters like getLabel()
or isMseidling()
are used instead of fields when accessing members like .label
or .mseidling
automatically.
<jsp:include page="/WEB-INF/pages/common/fragments/date-picker.jspf">
<jsp:param name="widget" value="date"/>
</jsp:include>
This includes another JSP file with a parameter.
There are quite many, and the usage is typically non-trivial. Nevertheless we at least have the sources, so we can look at implementations (control-click on a JSP file in Eclipse will open up the corresponding Java file).
The most used Spacewalk custom tags are:
-
<rhn:require acl="...">
: "prints" everything it encloses only if the ACL is verified. Typical use is to limit functions to certain users, types of systems, etc; -
<rhn:toolbar>
: the standard Spacewalk title toolbar with help links and icons; -
<rl:listset>
: the standard Spacewalk list/table with alphabar, pagination, item selection, etc.; -
<rhn:csrf/>
: includes CSRF attack protection in the form. Basically necessary in every<form>
tag.
SUSE Manager only supports English at this time. There are multiple files where the localized messages are defined depending on the context of the message.
Place | Context |
---|---|
link | strings used in internal classes/libraries |
link | strings used directly in JSPs |
link | used in non-JSP templates |
link | strings that directly correspond to identifies in the database (rarely used, see contents to know in which cases it should actually be used) |
link | strings that appear in menus, handled by the navigation classes |
link | strings that pertain to ACLs |
link | strings used in unit tests exclusively |
link | SUSE-specific strings that should not end up upstream |