Last updatedMay 11, 2018

Creating a Jira issue CRUD servlet and issue search

Applicable:

This tutorial applies to Jira 7.0.0 and later.

Level of experience:

Intermediate. You should have completed at least one beginner tutorial before working through this tutorial. See the list of developer tutorials.

Time to Complete:

It should take you approximately 1 hour to complete this tutorial.

Overview of the tutorial

This tutorial takes you through the steps for developing an app that can perform CRUD operations in a Jira project. In the tutorial, you will create a servlet that presents a page in Jira where users can:

  • Create an issue.
  • Edit an issue.
  • Delete an issue.

In addition to CRUD operations, this tutorial demonstrates how to use a servlet module to perform a simple issue listing with the IssueService and SearchService interfaces.

The completed app will consist of the following components:

  1. Java classes encapsulating the app logic.
  2. Resources for display of the app UI.
  3. An app descriptor (that is, an XML file) to enable the plugin module in the Atlassian application.

When you are finished, all these components will be packaged in a single JAR file.

About these instructions

You can use any supported combination of operating system and IDE to create this app.These instructions were written using macOS Sierra and IntelliJ IDEA 2017.3. If you use another operating system or IDE combination, you should use the equivalent operations for your specific environment.

This tutorial was last tested with Jira 7.7.1 using Atlassian Plugin SDK 6.3.10.

Before you begin

To complete this tutorial, you need to know the following: 

  1. The basics of Java development: classes, interfaces, methods, how to use the compiler, and so on.
  2. How to create an Atlassian plugin project using the Atlassian Plugin SDK.
  3. The basics of Jira administration. 

App Source

We encourage you to work through this tutorial. If you want to skip ahead or check your work when you are done, you can find the app source code on Atlassian Bitbucket.

To clone the repository, run the following command:

1
git clone https://bitbucket.org/atlassian_tutorial/tutorial-jira-simple-issue-crud

Alternatively, you can download the source as ZIP archive.

Step 1. Create the app project

In this step, you'll use two atlas- commands to generate stub code for your app. The atlas- commands are part of the Atlassian Plugin SDK and automate much of the work of app development for you.

  1. Set up the Atlassian Plugin SDK and build a project if you did not do that yet.
  2. Open a Terminal and navigate to the directory where you would like to keep the app code.
  3. To create the initial project files and source code for a Jira app, run the following command:

    1
    atlas-create-jira-plugin
  4. To identify the app, enter the following information.

    group-id

    com.example.plugins.tutorial

    artifact-id

    tutorial-jira-simple-issue-crud

    version

    1.0-SNAPSHOT

    package

    com.example.plugins.tutorial

  5. Confirm your entries when prompted.

  6. Navigate to the directory created by SDK.

    1
    cd tutorial-jira-simple-issue-crud
  7. Remove auto generated test directories.

    1
    2
    rm -rf ./src/test/java
    rm -rf ./src/test/resources/
  8. Delete the unneeded Java class files.

    1
    rm -rf ./src/main/java/com/example/plugins/tutorial/*
  9. Import the project to your favorite IDE.

Step 2. Review and tweak (optional) the generated code

It is a good idea to familiarize yourself with the stub app code. In this step, we'll check the version value and tweak the generated stub class.

Add app metadata to the POM

The POM (that is, Project Object Model definition file) is located at the root of your project and declares the project dependencies and other information.

In this step you'll add some metadata about your app and your company or organization to the POM.

  1. Navigate to the root folder and open the pom.xml file.
  2. Add your company or organization name and your website URL to the organization element:

    1
    2
    3
    4
    <organization>
        <name>Example Company</name>
        <url>http://www.example.com/</url>
    </organization>
  3. Update the description element:

    1
    <description>This plugin demonstrates how to perform basic CRUD operations on JIRA Issues using the IssueService and SearchService interface through a servlet module.</description>
  4. Save the file.

Review the generated app descriptor

Your stub code contains an app descriptor file atlassian-plugin.xml. This is an XML file that identifies the app to the host application (that is, to Jira) and defines the required app functionality.

  1. In your IDE, navigate to src/main/resources and open the descriptor file.

You should see something like this (comments removed):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?xml version="1.0" encoding="UTF-8"?>
<atlassian-plugin key="${atlassian.plugin.key}" name="${project.name}" plugins-version="2">
  <plugin-info>
    <description>${project.description}</description>
    <version>${project.version}</version>
    <vendor name="${project.organization.name}" url="${project.organization.url}"/>
    <param name="plugin-icon">images/pluginIcon.png</param>
    <param name="plugin-logo">images/pluginLogo.png</param>
  </plugin-info>
  <resource type="i18n" name="i18n" location="tutorial-jira-simple-issue-crud"/>
  <web-resource key="tutorial-jira-simple-issue-crud-resources" name="tutorial-jira-simple-issue-crud Web Resources">
    <dependency>com.atlassian.auiplugin:ajs</dependency>
    <resource type="download" name="tutorial-jira-simple-issue-crud.css" location="/css/tutorial-jira-simple-issue-crud.css"/>
    <resource type="download" name="tutorial-jira-simple-issue-crud.js" location="/js/tutorial-jira-simple-issue-crud.js"/>
    <resource type="download" name="images/" location="/images"/>
    <context>tutorial-jira-simple-issue-crud</context>
  </web-resource>
</atlassian-plugin>

In later steps, we'll use the plugin module generator (that is, another atlas- command) to generate the stub code for additional modules required by the app.

Step 3. Add the servlet module

  1. Open a Terminal window and navigate to the app root folder where the pom.xml is located.
  2. Run the following command:

    1
    atlas-create-jira-plugin-module
  3. Select the Servlet option.

  4. Add the following information when prompted.

    New Classname

    IssueCRUD

    Package Name

    com.example.plugins.tutorial.servlet

    Show Advanced Setup

    N

  5. Select N for Add Another Plugin Module.

Review the changes made by the generator

The generator added the following elements to your app:

  • An IssueCRUD Java class.
  • A servlet module in your app descriptor.
  • Required Maven dependencies (in your POM).

If you open the atlassian-plugin.xml file in your IDE, you will see the following module information added by the generator:

1
2
3
4
<servlet name="Issue CRUD" i18n-name-key="issue-crud.name" key="issue-crud" class="com.example.plugins.tutorial.servlet.IssueCRUD">
   <description key="issue-crud.description">The Issue CRUD Plugin</description>
   <url-pattern>/issuecrud</url-pattern>
 </servlet>

If you open the pom.xml file in your IDE, you will see the following new entries in the dependencies section:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<dependencies>
...
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>servlet-api</artifactId>
        <version>2.4</version>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.6.6</version>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>org.apache.httpcomponents</groupId>
        <artifactId>httpclient</artifactId>
        <version>4.1.1</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.mockito</groupId>
        <artifactId>mockito-all</artifactId>
        <version>1.8.5</version>
        <scope>test</scope>
    </dependency>
...
<dependencies>

Step 4. Add Velocity and user management modules

By default, the servlet module is not preconfigured to use Velocity templates (Atlassian's preferred template engine for servlets). Let's set up Velocity so that we don't write HTML inside our servlet code. We'll use Atlassian Spring Scanner to import the TemplateRenderer which imports the Velocity template renderer. To add this component, do the following:

  1. In the root of your project, open the pom.xml file.
  2. Find the dependencies section and insert the following:

    1
    2
    3
    4
    5
    6
    <dependency>
      <groupId>com.atlassian.templaterenderer</groupId>
      <artifactId>atlassian-template-renderer-api</artifactId>
      <version>2.0.0</version>
      <scope>provided</scope>
    </dependency>
  3. Save your file.

Step 5. Build your app and do a little test

At this point, you haven't actually written any Java code. You can, however, run Jira and see your app with its server in action. In this step, you will start Jira, create a project you'll use later, and test the servlet.

  1. Make sure you have saved all your code changes to this point.
  2. Open a Terminal window and navigate to the app root folder where the pom.xml file is located.
  3. Run the following command:

    1
    atlas-run

    This command builds your app code, starts a Jira instance, and installs your app in it. This may take several minutes or so. When the process completes you will see many status lines on your screen concluding with something like the following:

    1
    2
    3
    [INFO] jira started successfully in 71s at http://localhost:2990/jira
    [INFO] Type CTRL-D to shutdown gracefully
    [INFO] Type CTRL-C to exit

    Notice the URL for the Jira instance.

  4. Go to the local Jira instance in your browser (the URL is indicated in the Terminal output).

  5. Log in using default admin/admin.
    The first time you start a Jira instance, the New Project wizard appears.
  6. Create a new blank project called "TUTORIAL" with key "TUTORIAL". We'll need it for later.

    To keep the focus on coding IssueCRUD, we rely on hard coded project identifiers in our app. It's important that the Jira project has the name and key "TUTORIAL". If the new project wizard doesn't appear, make sure to create a project with these values.

  7. To open your servlet, go to localhost:2990/jira/plugins/servlet/issuecrud in your browser.
    You should see a "Hello World" message in the browser window. To see where the /issuecrud path is specified, navigate to src/main/resources, open the atlassian-plugin.xml file and look for the servletmodule. It includes a <url-pattern> element that identifies this path.

  8. Leave Jira running in browser.

Step 6. Create Velocity templates

We'll need Velocity templates to create, edit, and list issues.

  1. Open a new Terminal window and navigate to src/main/resources.
  2. Create a subdirectory named templates.
  3. Create a new file named edit.vm, then add the following content:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    <html>
    <head>
        <title>Edit Issue &mdash; Issue CRUD Tutorial</title>
        <meta name="decorator" content="atl.general">
    </head>
    <body class="page-type-admin">
    <div class="content-container">
    
        <div class="content-body">
            <h1>Edit issue $issue.getKey()</h1>
    
            #if ($errors.size()>0)
                <div class="aui-message error shadowed">
                    #foreach($error in $errors)
                        <p class="title">
                            <span class="aui-icon icon-error"></span>
                            <strong>$error</strong>
                        </p>
                    #end
                </div>
                <!-- .aui-message -->
            #end
    
            <div class="create-issue-panel">
    
                <form method="post" id="h" action="issuecrud" class="aui">
                    <input type="hidden" name="edit" value="y">
                    <input type="hidden" name="key" value="$issue.getKey()">
                    <div class="field-group">
                        <label for="h-fsummary">
                            Summary
                            <span class="aui-icon icon-required"></span>
                            <span class="content">required</span>
                        </label>
                        <input id="h-fsummary" class="text long-field" type="text" name="summary" value="$issue.getSummary()">
                    </div>
                    <div class="field-group">
                        <label for="h-fdescription">
                            Description
                            <span class="aui-icon icon-required"></span>
                            <span class="content">required</span>
                        </label>
                        <textarea id="h-fdescription" name="description">$issue.getDescription()</textarea>
                    </div>
                    <div class="buttons">
                        <input class="button" type="submit" value="Update"> 
                        <a href="issuecrud">Cancel</a>
                    </div>
                </form>
            </div>
        </div>
    </div>
    </body>
    </html>
  4. Create a file named list.vm, then add the following content:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    <html>
    <head>
        <title>All Tutorial Issues &mdash; Issue CRUD Tutorial</title>
        <meta name="decorator" content="atl.general">
        <script>
            AJS.$(document).ready(function() {
                jQuery('.delete-issue').click(function() {
                    console.log('deleting');
                    var self = jQuery(this);
                    jQuery.ajax({
                        type: "delete",
                        url: "issuecrud?key=" + self.data("key"),
                        success: function(data) {
                            console.log('dom', self, data);
                            self.parent().parent().remove();
                        },
                        error: function() {
                            console.log('error', arguments);
                        }
                    });
                    return false;
                });
            });
        </script>
    </head>
    <body class="page-type-admin">
    <div class="content-container">
    
        <div class="content-body">
            <h1>You've Got #if($issues.size()==0)<span style="color:red">NO</span>#end Issues!</h1>
    
            #if ($errors.size()>0)
                <div class="aui-message error shadowed">
                    #foreach($error in $errors)
                        <p class="title">
                            <span class="aui-icon icon-error"></span>
                            <strong>$error</strong>
                        </p>
                    #end
                </div>
                <!-- .aui-message -->
            #end
    
            #if ($issues.size() > 0)
                <div class="issues">
                    <table class="aui">
                        <thead>
                        <tr>
                            <th>Key</th>
                            <th>Summary</th>
                            <th>Description</th>
                            <th>Assignee</th>
                            <th>Reporter</th>
                            <th></th>
                        </tr>
                        </thead>
                        <tbody>
                            #foreach( $issue in $issues )
                            <tr>
                                <td>$issue.getKey()</td>
                                <td>$issue.getSummary()</td>
                                <td>
                                    #if($issue.getDescription())
                                $issue.getDescription()
                            #end
                                </td>
                                <td>
                                    $issue.getAssignee().getName()
                                </td>
                                <td>
                                    $issue.getReporter().getName()
                                </td>
                                <td>
                                    <a href="issuecrud?edit=y&key=$issue.getKey()">Edit</a>  
                                    <a href="#" class="delete-issue" data-key="$issue.getKey()">Delete</a>
                                </td>
                            </tr>
                            #end
                        </tbody>
                    </table>
                </div>
            #end
            <form method="get" action="issuecrud" class="aui">
                <input type="hidden" name="new" value="y">
                <input type="submit" class="button" value="Create new issue">
            </form>
        </div>
    </div>
    </body>
    </html>
  5. Create a file named new.vm, then add the following content:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
     <html>
    <head>
        <title>Create Issue &mdash; Issue CRUD Tutorial</title>
        <meta name="decorator" content="atl.general">
    </head>
    <body class="page-type-admin">
    <div class="content-container">
    
        <div class="content-body">
            <h1>Create issue</h1>
            <div class="create-issue-panel">
    
                <form method="post" id="h" action="issuecrud" class="aui">
                    <div class="field-group">
                        <label for="h-fsummary">
                            Summary
                            <span class="aui-icon icon-required"></span>
                            <span class="content">required</span>
                        </label>
                        <input id="h-fsummary" class="text long-field" type="text" name="summary">
                    </div>
                    <div class="field-group">
                        <label for="h-fdescription">
                            Description
                            <span class="aui-icon icon-required"></span>
                            <span class="content">required</span>
                        </label>
                        <textarea id="h-fdescription" name="description"></textarea>
                    </div>
                    <div class="buttons">
                        <input class="button" type="submit" value="Create"> 
                        <a href="issuecrud">Cancel</a>
                    </div>
                </form>
            </div>
        </div>
    
    </div>
    </body>
    </html>

This is how we will use the files:

  • list.vm to render a list of available issues.
  • edit.vm to edit selected issue.
  • new.vm to render a simple issue creation form.

Now you are ready to write Java code. 

Step 7. Write your Java classes

In this step, you'll update the servlet code so that it displays something more interesting than "Hello World". All work in this section will be in the IssueCRUD.java file that is located here: 

/src/main/java/com/example/plugins/tutorial/servlet/

Servlet setup

Let's configure our basic servlet.

  1. Open the IssueCRUD.java file.
  2. Replace the existing imports section (without disturbing the package definition) so that it looks like this:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    import com.atlassian.jira.bc.issue.IssueService;
    import com.atlassian.jira.bc.issue.search.SearchService;
    import com.atlassian.jira.bc.project.ProjectService;
    import com.atlassian.jira.config.ConstantsManager;
    import com.atlassian.jira.issue.Issue;
    import com.atlassian.jira.issue.IssueInputParameters;
    import com.atlassian.jira.issue.MutableIssue;
    import com.atlassian.jira.issue.issuetype.IssueType;
    import com.atlassian.jira.issue.search.SearchException;
    import com.atlassian.jira.issue.search.SearchResults;
    import com.atlassian.jira.web.bean.PagerFilter;
    import com.atlassian.plugin.spring.scanner.annotation.component.Scanned;
    import com.atlassian.plugin.spring.scanner.annotation.imports.JiraImport;
    import com.atlassian.query.Query;
    import com.atlassian.templaterenderer.TemplateRenderer;
    import com.atlassian.jira.jql.builder.JqlClauseBuilder;
    import com.atlassian.jira.jql.builder.JqlQueryBuilder;
    import com.atlassian.jira.project.Project;
    import com.atlassian.jira.security.JiraAuthenticationContext;
    import com.atlassian.jira.user.ApplicationUser;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    import java.util.Collections;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    import java.util.Optional;
  3. Put a @Scanned annotation on IssueCRUD class and create a constructor.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    @Scanned
    public class IssueCRUD extends HttpServlet {
        private static final Logger log = LoggerFactory.getLogger(IssueCRUD.class);
    
        @JiraImport
        private IssueService issueService;
        @JiraImport
        private ProjectService projectService;
        @JiraImport
        private SearchService searchService;
        @JiraImport
        private TemplateRenderer templateRenderer;
        @JiraImport
        private JiraAuthenticationContext authenticationContext;
        @JiraImport
        private ConstantsManager constantsManager;
    
        private static final String LIST_ISSUES_TEMPLATE = "/templates/list.vm";
        private static final String NEW_ISSUE_TEMPLATE = "/templates/new.vm";
        private static final String EDIT_ISSUE_TEMPLATE = "/templates/edit.vm";
    
        public IssueCRUD(IssueService issueService, ProjectService projectService,
                         SearchService searchService,
                         TemplateRenderer templateRenderer,
                         JiraAuthenticationContext authenticationContext,
                         ConstantsManager constantsManager) {
            this.issueService = issueService;
            this.projectService = projectService;
            this.searchService = searchService;
            this.templateRenderer = templateRenderer;
            this.authenticationContext = authenticationContext;
            this.constantsManager = constantsManager;
        }

The @JiraImport annotations instruct Atlassian Spring Scanner to import specified interface Atlassian services from host application and inject them into our servlet object. @Scanned annotation is used to mark class for Atlassian Spring Scanner.

Handle GET request (list issues, new issue, edit issue)

After wiring the Jira API services that the app needs, we can start working on our request handlers.

  1. Replace the existing doGet method. This new method generates a page that, depending on the user action, lists all issues, creates new issues, and updates existing issues.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        String action = Optional.ofNullable(req.getParameter("actionType")).orElse("");
    
        Map<String, Object> context = new HashMap<>();
        resp.setContentType("text/html;charset=utf-8");
        switch (action) {
            case "new":
                templateRenderer.render(NEW_ISSUE_TEMPLATE, context, resp.getWriter());
                break;
            case "edit":
                IssueService.IssueResult issueResult = issueService.getIssue(authenticationContext.getLoggedInUser(),
                        req.getParameter("key"));
                context.put("issue", issueResult.getIssue());
                templateRenderer.render(EDIT_ISSUE_TEMPLATE, context, resp.getWriter());
                break;
            default:
                List<Issue> issues = getIssues();
                context.put("issues", issues);
                templateRenderer.render(LIST_ISSUES_TEMPLATE, context, resp.getWriter());
        }
    
    }

    If you look closely, accessing the issue is done using the IssueService. We'll also need a method for creating the list of Jiras.

  2. Add the getIssues method that will look for issues belonging to the "TUTORIAL" project.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    private List<Issue> getIssues() {   
        ApplicationUser user = authenticationContext.getLoggedInUser();
        JqlClauseBuilder jqlClauseBuilder = JqlQueryBuilder.newClauseBuilder();
        Query query = jqlClauseBuilder.project("TUTORIAL").buildQuery();
        PagerFilter pagerFilter = PagerFilter.getUnlimitedFilter();
    
        SearchResults searchResults = null;
        try {
            searchResults = searchService.search(user, query, pagerFilter);
        } catch (SearchException e) {
            e.printStackTrace();
        }
        return searchResults != null ? searchResults.getIssues() : null;
    }

If we want to get a list of issues for our project, we access the SearchService with a specified JQL clause.

The request parameter that the servlet receives determines which page to render:

  • If the actionType parameter is edit, the servlet renders the edit.vm template.
  • If the actionType parameter is new, the servlet renders the new.vm template.
  • If no parameter is passed in, then the servlet renders the list.vm template.

Handle POST request (create issue and update issue)

Now that we can render the GET request, let's focus on creating and updating of an issue. This happens in a POST request and is handled in our code by the doPost method. We will also create handleIssueEdit and handleIssueCreation methods to separate logic. Notice that the code assumes a hard coded "TUTORIAL" project key.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    String actionType = req.getParameter("actionType");   
    switch (actionType) {
       case "edit":
           handleIssueEdit(req, resp);
           break;
       case "new":
           handleIssueCreation(req, resp);
           break;
       default:
           resp.sendError(HttpServletResponse.SC_NOT_FOUND);
    }
}


private void handleIssueEdit(HttpServletRequest req, HttpServletResponse resp) throws IOException {

    ApplicationUser user = authenticationContext.getLoggedInUser();

    Map<String, Object> context = new HashMap<>();

    IssueInputParameters issueInputParameters = issueService.newIssueInputParameters();
    issueInputParameters.setSummary(req.getParameter("summary"))
            .setDescription(req.getParameter("description"));

    MutableIssue issue = issueService.getIssue(user, req.getParameter("key")).getIssue();

    IssueService.UpdateValidationResult result =
            issueService.validateUpdate(user, issue.getId(), issueInputParameters);

    if (result.getErrorCollection().hasAnyErrors()) {
        context.put("issue", issue);
        context.put("errors", result.getErrorCollection().getErrors());
        resp.setContentType("text/html;charset=utf-8");
        templateRenderer.render(EDIT_ISSUE_TEMPLATE, context, resp.getWriter());
    } else {
        issueService.update(user, result);
        resp.sendRedirect("issuecrud");
    }
}

private void handleIssueCreation(HttpServletRequest req, HttpServletResponse resp) throws IOException {
    ApplicationUser user = authenticationContext.getLoggedInUser();

    Map<String, Object> context = new HashMap<>();

    Project project = projectService.getProjectByKey(user, "TUTORIAL").getProject();

    if (project == null) {
        context.put("errors", Collections.singletonList("Project doesn't exist"));
        templateRenderer.render(LIST_ISSUES_TEMPLATE, context, resp.getWriter());
        return;
    }

    IssueType taskIssueType = constantsManager.getAllIssueTypeObjects().stream().filter(
            issueType -> issueType.getName().equalsIgnoreCase("task")).findFirst().orElse(null);

    if(taskIssueType == null) {
        context.put("errors", Collections.singletonList("Can't find Task issue type"));
        templateRenderer.render(LIST_ISSUES_TEMPLATE, context, resp.getWriter());
        return;
    }

    IssueInputParameters issueInputParameters = issueService.newIssueInputParameters();
    issueInputParameters.setSummary(req.getParameter("summary"))
            .setDescription(req.getParameter("description"))
            .setAssigneeId(user.getName())
            .setReporterId(user.getName())
            .setProjectId(project.getId())
            .setIssueTypeId(taskIssueType.getId());

    IssueService.CreateValidationResult result = issueService.validateCreate(user, issueInputParameters);

    if (result.getErrorCollection().hasAnyErrors()) {
        List<Issue> issues = getIssues();
        context.put("issues", issues);
        context.put("errors", result.getErrorCollection().getErrors());
        resp.setContentType("text/html;charset=utf-8");
        templateRenderer.render(LIST_ISSUES_TEMPLATE, context, resp.getWriter());
    } else {
        issueService.create(user, result);
        resp.sendRedirect("issuecrud");
    }
}

Just as we did in the doGet code, in the doPost we separate a creation from an update by checking for a request parameter actionType.

Creating and updating an issue requires that you perform a validation step. For creation, you must use the validateCreate method of the IssueService. The validation step will return an error collection if there are errors with the validation results. In this case, we will pass those into the template context so that Velocity can render it in the HTML.

Delete issues

We can delete issues using our app.

  1. To enable deleting issues, add the following method:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Override
protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws IOException {
    ApplicationUser user = authenticationContext.getLoggedInUser();
    String respStr;
    IssueService.IssueResult issueResult = issueService.getIssue(user, req.getParameter("key"));
    if (issueResult.isValid()) {
        IssueService.DeleteValidationResult result = issueService.validateDelete(user, issueResult.getIssue().getId());
        if (result.getErrorCollection().hasAnyErrors()) {
           respStr = "{ \"success\": \"false\", error: \"" + result.getErrorCollection().getErrors().get(0) + "\" }";
        } else {
           issueService.delete(user, result);
           respStr = "{ \"success\" : \"true\" }";
        }
    } else {
        respStr = "{ \"success\" : \"false\", error: \"Couldn't find issue\"}";
    }
    resp.setContentType("application/json;charset=utf-8");
    resp.getWriter().write(respStr);
}

Step 8. Build your app and test it

Now you are ready to test your cool new servlet for creating issues.

  1. Make sure you have saved all your code changes to this point.
  2. Open a Terminal window and navigate to the app root folder where the pom.xml file is located.
  3. If you left Jira running, use atlas-package command to trigger QuickReload. In case you closed Jira, use atlas-run command to run it.

  4. In your browser, go to the local Jira instance.

  5. Log in with the default admin/admin.
  6. Go to your servlet page: localhost:2990/jira/plugins/servlet/issuecrud.
  7. Click Create new issue.
  8. Enter some test data to Summary and Description fields.
  9. Click Create.
    The servlet page appears again, this time with your new issue listed. 

Congratulations, that's it!

Have a treat!