Mapping Web Resources to Code

Introduction

This best practice guide is for developers and administrators of Atlassian applications who are looking to find the relevant code of the product they're developing or supporting. It's a guide on how to track something from the browser, logs or stack traces into the corresponding code.

The source code to Atlassian products can be downloaded through my.atlassian.com when a starter license for that product is purchased.

Quick Guide

Atlassian products consist of a core application that provides plugin points to provide additional functionality from plugins within web pages, servlets and REST endpoints. This means when accessing a web resource you could be accessing the core application, a bundled plugin or a third-party plugin (or perhaps all of these!).

(info) When searching for the location of code, unless you explicitly know it's in a plugin search the product first and then look for plugins.

The following sample URLs are examples of web resources in various Atlassian applications, and which frameworks they use. 

XWork and WebWork

This is a Java server-side Model-View-Controller (MVC) framework, which is used to structure how an application is designed. It's a way to think about splitting up the responsibilities about how a web page is rendered. It can be broken down as:

  • Model: The way all the different services interact with each other - notifies the view and controllers when there has been a change in state. This is largely handled with Spring and PICO in Atlassian products.
  • View: This is the way the page is rendered and displayed to the user. Typically it will use frameworks such as JSP, Soy or Velocity in our products.
  • Controller: This is the action between the two and is defined as an action. For example ViewUser in JIRA is an action. It sends commands to the associated view to change the presentation of the model.

In WebWork and XWork the user will call the action which will run through its code and then provide the appropriate view to display to the user, based on the business logic in the class.

Atlassian Products

This functionality is declared in the following files in the different products:

  • JIRA: jira-project/jira-components/jira-core/src/main/resources/actions.xml
  • Confluence: confluence-project/confluence-core/confluence/src/etc/java/xwork.xml
  • FishEye: content/WEB-INF/classes/xwork.xml

(info) If you're unable to find the action (either the jsp, jspa or action file) in the Atlassian product it's likely that the action is coming from a bundled or third-party plugin, which are described in the following section.

JIRA

For example if we look at JIRA's action.xml there are a series of actions with different declared views. Search for the JSP file in the browser (for example viewprofile.jsp) and it will reveal an action, such as the below:

    <action name="user.ViewProfile" alias="ViewProfile">
        <view name="success">/secure/views/user/viewprofile.jsp</view>
        <view name="contentonly">/secure/views/user/profile/viewprofile-content.jsp</view>
        <view name="json">/secure/views/versions/json-content.jsp</view>
        <view name="usernotfound">/secure/views/user/viewprofile-usernotfound.jsp</view>
        <view name="securitybreach">/secure/views/permissionviolation.jsp</view>
    </action>

The name in this example is a partial name of the package and class in the name element. For JIRA we can browse straight to the ViewUser class which will contain the relevant source code that handles the viewuser.jsp page. In JIRA you can generally directly search for a class named the same as the JSP page and find it the majority of the time. If not searching the actions.xml will reveal the class name.

Sometimes multiple views have the same JSP name and the difference is the messages that are displayed on that JSP - for example a success or error message. When the class is accessed, the doValidate() method is invoked (if it exists) and then the doExecute() method, which returns the view as a String to display to the user.

Confluence and FishEye

These products are a bit different as they use XWork which is located in xwork.xml. For Confluence search for the name of the action file and map it similar to JIRA, for example this is for viewmyprofile.action - we search for just viewmyprofile as it may be velocity/soy are used:

        <action name="viewuserprofile" class="com.atlassian.confluence.user.actions.ViewUserProfileAction">
            <result name="input" type="velocity">/users/viewmyprofile.vm</result>
            <result name="error" type="velocity">/users/viewmyprofile.vm</result>
            <result name="success" type="velocity">/users/viewmyprofile.vm</result>
        </action>

This tells us that the class for the viewuserprofile.action is com.atlassian.confluence.user.actions.ViewUserProfileAction.

Xwork will call the validate() method and then the execute() method which will return a String of the view to display, similar to WebWork.

Plugins

Trying to find out what URL maps to a plugin's XWork/WebWork is particularly difficult as there is often product knowledge required. In an ideal world the naming convention will match some known functionality that exists within that plugin. 

If what you're looking for doesn't exist in actions.xmlxwork.xml it will most likely be from a plugin. In plugins, this is contained within the atlassian-plugins.xml file. For example this is from JIRA Agile:

            <action name="com.atlassian.greenhopper.web.rapid.RapidBoardAction" alias="RapidBoardAction">
                <command name="showBoard" alias="RapidBoard">
                    <view name="success">/templates/greenhopper/web/board/rapid/rapid-board.vm</view>
                    <view name="indexchecks">/templates/greenhopper/web/board/rapid/index-checks.vm</view>
                    <view name="unsupportedbrowser">/templates/greenhopper/web/board/rapid/unsupported-browser.vm</view>
                </command>
                <command name="show" alias="RapidView">
                    <view name="success">/templates/greenhopper/web/board/rapid/rapid-view.vm</view>
                    <view name="unsupportedbrowser">/templates/greenhopper/web/board/rapid/unsupported-browser.vm</view>
                </command>
                <command name="show" alias="ManageRapidViews">
                    <view name="success">/templates/greenhopper/web/board/rapid/manage-views.vm</view>
                    <view name="unsupportedbrowser">/templates/greenhopper/web/board/rapid/unsupported-browser.vm</view>
                </command>
                <command name="showWelcome" alias="RapidStart">
                    <view name="success">/templates/greenhopper/web/board/rapid/welcome.vm</view>
                    <view name="unsupportedbrowser">/templates/greenhopper/web/board/rapid/unsupported-browser.vm</view>
                </command>
            </action>

This shows us the com.atlassian.greenhopper.web.rapid.RapidBoardAction class is used for the above actions - viewing rapid boards, managing them and so on.

For further information on plugins please see JIRA Webwork Actions & Adding a Custom Action to Confluence.

Java Servlets

Java Servlets are basically a class that responds to HTTP requests and are used to implement web applications. There are various frameworks that run on top of servlets to provide additional functionality.

Servlets run in a servlet container which looks after the networking side of handling web traffic (e.g.: looking after the user sessions and connections, reading the HTTP request and so on). The most common use of a servlet container within Atlassian products is Tomcat and FishEye uses Jetty.

Servlets also allow for any number of filters to manipulate the HTTP request before it reaches the servlet code - they dynamically intercept request and responses to transform them. A number of servlet containers (e.g.: Tomcat) also have their own inbuilt filters that provide functionality that enables servlets to have additional functionality.

Servlets will typically be provided by the application and bundled / installed plugins. They can easily be identified as they will appear in the URL after /plugins/servlet/, for example http://jira.example.com/plugins/servlet/stp/view/. This is the Atlassian Support Tools plugin - the URL is displayed in the browser when accessing this plugin in the browser:

The plugin name is defined in the URL, and relies upon the plugin developer to create a name that makes the plugin easily identifiable. If multiple plugins provide the same name the URLs will clobber and the application will fail to work properly, so plugin developers should be using unique names.

Below are some examples:

Atlassian Products

The servlets and filters are defined within the web.xml file, for example <CONFLUENCE_INSTALL>/confluence/WEB-INF/web.xml or <JIRA_INSTALL>/atlassian-jira/WEB-INF/web.xml. Within that file we have the following information that can be used to look at a URL and map back to the class it's using.

Filters

The class for the filter is defined below, which indicates that anything mapped to this filter will invoke the below Java class.

    <filter>
        <filter-name>login</filter-name>
        <filter-class>com.atlassian.jira.web.filters.JiraLoginFilter</filter-class>
    </filter>

Filter Mapping

This defines the URL pattern that maps back to the filter name. When accessing any URLs that fall into the pattern, they will pass through the defined filter.

    <filter-mapping>
        <filter-name>login</filter-name>
        <url-pattern>/*</url-pattern>
        <dispatcher>REQUEST</dispatcher>
        <dispatcher>FORWARD</dispatcher> <!-- we want security/login to be applied after urlrewrites, for example -->
    </filter-mapping>

Servlets

These are defined similar to filters - they have a name that is referenced from other parts of the configuration, and also display the class that is invoked when the servlet is accessed in the browser.

    <servlet>
        <servlet-name>viewattachment</servlet-name>
        <servlet-class>com.atlassian.jira.web.servlet.ViewAttachmentServlet</servlet-class>
    </servlet>

Servlet Mapping

The same with filters - it defines the URL pattern that maps back to the servlet name. In the below example, any URL that contains secure/attachment will be using the viewattachment servlet above, which is the ViewAttachmentServlet.class. If we were accessing an attachment in the browser we'd know it was using that class.

    <servlet-mapping>
        <servlet-name>viewattachment</servlet-name>
        <url-pattern>/secure/attachment/*</url-pattern>
    </servlet-mapping>

Plugins

Atlassian plugins store servlet information in the atlassian-plugins.xml file in a similar way to web.xml, for example this is from the Applinks Diagnostics Plugin (the source is available publicly at https://bitbucket.org/atlassianlabs/applinks-diagnostics):

    <servlet name="Diagnostics UI" key="diagnosticsUI"
             class="com.atlassian.applinks.ui.diagnostics.PageServlet">
        <description>Diagnostics UI</description>
        <url-pattern>/applinksDiagnostics/ui</url-pattern>
    </servlet>

Here we see any URLs that match the /applinksDiagnostics/ui URL pattern are directed to the com.atlassian.applinks.ui.diagnostics.PageServlet class.

Tomcat

There is a Tomcat conf directory in the installation directory of our products that contains a web.xml file. This has the classes and filters that correspond to Tomcat's code. The source code for Tomcat can be downloaded from http://tomcat.apache.org/. This document doesn't detail information on Tomcat as it's a third-party app and we're focused primarily on Atlassian products here.

Rest API

Atlassian products provide public REST APIs to allow integration with the products that are used by plugins, external sources and also the products themselves. These APIs are documented, for example in Confluence REST API and JIRA REST API. The easiest way to identify the location of these is to extract the name from the REST URL, which can be found after /rest/api/, or rest/api/<ver>/ where ver is the version number. Some examples are below:

Atlassian Products

After identifying the name, search for any of the following, for example:

@Path ("<name>")
@Path("<name>")
@Path ("/<name>")
@Path("/<name>")

(info) The syntax will vary differently depending upon how the annotation was written. If using IntelliJ or something with Regular Expression support the following search can be used to pickup all those potential annotations:

This will return the class that the REST URL is provided by, in this example it's SpaceResource. The majority of the time a Java Class for REST will be suffixed by Resource, for example ContentResource or ProjectResource. Searching through those classes will show methods that also have the @Path annotation, which is an extension of the REST URL. For example /rest/api/2/issue/{issueIdOrKey} leads us to:

    @Path("{issueIdOrKey}")
    public Response getIssue(@PathParam ("issueIdOrKey") final String issueIdOrKey,
            @QueryParam ("fields") List<StringList> fields, @QueryParam("expand") String expand)
    {

If the search does not return results it's likely the REST endpoint is provided by a plugin.

Plugins

See if you can identify the plugin from the REST name and then go through the same search above to find the relevant class.

Stack Traces

A stack trace is an excellent way of easy identifying the code that has led to a particular error or exception. Copy the stack trace and then select Analyze > Analyze Stack Trace, then paste it in there. Ensure the correct version of the source is being reviewed or the line numbers will not be correct, and then the stack trace can be clicked on to take you to that code:

Expand to see the above stack trace...

This is from JIRA 6.1.7.

2014-07-31 23:08:08,654 http-bio-8080-exec-23 ERROR amikami 1388x2846415x4 lsnykx 10.0.3.55,127.0.0.1 /secure/DeleteComment.jspa [jira.event.issue.IssueEventListenerHandler] Exception thrown invoking listener [com.pyxis.greenhopper.jira.listeners.CacheEvictionListener] : No event type with id 17
java.lang.IllegalArgumentException: No event type with id 17
	at com.atlassian.jira.event.type.DefaultEventTypeManager.getEventType(DefaultEventTypeManager.java:93)
	at com.atlassian.jira.event.issue.AbstractIssueEventListener.workflowEvent(AbstractIssueEventListener.java:92)
	at com.atlassian.jira.event.issue.IssueEventListenerHandler$IssueEventInvoker.invoke(IssueEventListenerHandler.java:51)
	at com.atlassian.event.internal.AsynchronousAbleEventDispatcher$2.run(AsynchronousAbleEventDispatcher.java:66)
	at com.atlassian.event.internal.AsynchronousAbleEventDispatcher$1.execute(AsynchronousAbleEventDispatcher.java:32)
	at com.atlassian.event.internal.AsynchronousAbleEventDispatcher.dispatch(AsynchronousAbleEventDispatcher.java:60)
	at com.atlassian.event.internal.EventPublisherImpl.invokeListeners(EventPublisherImpl.java:160)
	at com.atlassian.event.internal.EventPublisherImpl.publish(EventPublisherImpl.java:79)
	at com.atlassian.jira.event.issue.DefaultIssueEventManager.publishEvent(DefaultIssueEventManager.java:124)
	at com.atlassian.jira.event.issue.DefaultIssueEventManager.dispatchEvent(DefaultIssueEventManager.java:102)
	at com.atlassian.jira.issue.util.DefaultIssueUpdater.storeModifiedFields(DefaultIssueUpdater.java:95)
	at com.atlassian.jira.issue.util.DefaultIssueUpdater.doUpdate(DefaultIssueUpdater.java:49)
	at com.atlassian.jira.bc.issue.comment.DefaultCommentService.doUpdateWithChangelog(DefaultCommentService.java:565)
	at com.atlassian.jira.bc.issue.comment.DefaultCommentService.delete(DefaultCommentService.java:441)

Searching the Code

Searching the code for error messages or display elements in the browser is also an effective way to locate the relevant code. For example looking at the below logs:

2014-08-29 03:03:19,287 http-bio-8080-exec-5 ERROR jconnect 183x1x1 azttb3 10.50.3.35,127.0.0.1 /rest/api/2/search [jira.issue.managers.DefaultCustomFieldManager] Could not load custom field type plugin with key 'com.atlassian.bonfire.plugin:bonfire-text'. Is the plugin present and enabled?

Searching for "Could not load custom field type plugin" will lead you to the relevant code, DefaultCustomFieldManager. The fully qualified path is also in the log file extract above, which is another way to quickly browse to it.

When searching for text that appears within the browser, it's important to keep in mind that the majority of output within Atlassian products will be translated. Additionally to locate where that information is coming from (i.e.: which plugin) will require some product knowledge. If we look at an example such as the below:

Searching for "Sprint scope will be affected by this action." leads us to the following in BoardAction.properties:

gh.sprint.issue.move.dialog.warning.scopechange=Sprint scope will be affected by this action.

To search for the untranslated property then we search for "gh.sprint.issue.move.dialog.warning.scopechange", which takes us to SprintPicker.js, the Javascript file that causes this error to appear.

Last modified on Sep 15, 2014

Was this helpful?

Yes
No
Provide feedback about this article
Powered by Confluence and Scroll Viewport.