Mapping Web Resources to Code
The following sample URLs are examples of web resources in various Atlassian applications, and which frameworks they use.
URL | Framework |
---|---|
All
All
| XWork and WebWork |
Anything with
| Java Servlets |
Anything with
| REST API |
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
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.xml
/ xwork.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:
- Application Links Diagnostics: https://JIRA_HOSTNAME/plugins/servlet/applinksDiagnostics/ui
- Embedded Crowd: https://JIRA_HOSTNAME/plugins/servlet/embedded-crowd/configure/delegatingldap/
- Universal Add-on Manager (UPM): https://JIRA_HOSTNAME/plugins/servlet/upm
- Atlassian Analytics: https://JIRA_HOSTNAME/plugins/servlet/analytics/configuration
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-plugin.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:
URL | Name |
---|---|
https://JIRA_HOSTNAME/rest/api/2/issue/JRA-1 | issue |
https://CONFLUENCE_HOSTNAME/rest/api/content?spaceKey=JIRAKB | content |
https://BITBUCKET_SERVER_HOSTNAME/rest/api/1.0/projects/JIRA/repos/jira/commits?start=25 | projects |
Atlassian Products
After identifying the name, search for any of the following, for example:
@Path ("<name>")
@Path("<name>")
@Path ("/<name>")
@Path("/<name>")
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:
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.