Quickstart to Spacewalk Java Codebase - uyuni-project/uyuni GitHub Wiki

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

What's inside

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).

Web application - typical code flow

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 into java/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, the execute method above must decide which forward to use by calling actionMapping.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.

A Struts Action: typical contents

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.

Good Java programmer's nitpicks

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>.

Rendering to JSP

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 loading

<%@ 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.

Control structures

<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.

"Printing" data to the page

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.

Accessing data from the Struts Action (controller)

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.

Including other JSP files or snippets

<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.

Spacewalk-specific tags

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.

Localization

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
⚠️ **GitHub.com Fallback** ⚠️