Tutorial - JIRA issue CRUD servlet and issue search

Applicable:

This tutorial applies to JIRA 4.3 or higher.

Level of experience:

This is an intermediate tutorial. 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.

On this page:

Overview of the Tutorial

This tutorial takes you through the steps for developing a plugin 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 plugin will contain the following components:

  • Java classes encapsulating the plugin logic.
  • Resources for display of the plugin UI.
  • A plugin descriptor (XML file) to enable the plugin module in the Atlassian application.

When you have finished, all these components will be contained within a single JAR file.

About these Instructions

You can use any supported combination of OS and IDE to create this plugin.These instructions were written using Ubuntu Linux and Eclipse Galileo. If you are using another OS or IDE combination, you should use the equivalent operations for your specific environment.

This tutorial was last tested with JIRA 6.0.

Prerequisite knowledge

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

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

Plugin 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 plugin source code on Atlassian Bitbucket. Bitbucket serves a public Git repository containing the tutorial's code. To clone the repository, issue the following command:

$ git clone https://bitbucket.org/atlassian/tutorial-jira-simple-issue-crud

Alternatively, you can download the source using the get source option here: https://bitbucket.org/atlassian/tutorial-jira-simple-issue-crud.

Step 1. Create the Plugin Project

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

  1. If you have not already set up the Atlassian Plugin SDK, do that now: Set up the Atlassian Plugin SDK and Build a Project.
  2. Open a terminal and navigate to your Eclipse workspace directory.
  3. Enter the following command to create the initial project files and source code for a JIRA plugin:

    atlas-create-jira-plugin
    
  4. Choose 1 for JIRA 5 when asked which version of JIRA you want to create the plugin for. 
  5. As prompted, enter the following information to identify the plugin:

    group-id

    com.example.plugins.tutorial

    artifact-id

    tutorial-jira-simple-issue-crud

    version

    1.0-SNAPSHOT

    package

    com.example.plugins.tutorial.crud

  6. Confirm your entries when prompted.
  7. Change to the tutorial-jira-simple-issue-crud directory created by the previous step.
  8. Run the command:

    atlas-mvn eclipse:eclipse
    
  9. Start Eclipse.
  10. Select File > Import.
    Eclipse starts the Import wizard.
  11. Filter for Existing Projects into Workspace (or expand the General folder tree).
  12. Press Next and browse to the root directory of your plugin (where the pom.xml file is located). 
    Your Atlassian plugin folder should appear under Projects.
  13. Select your plugin and click Finish.
    Eclipse imports your project.

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

It is a good idea to familiarize yourself with the stub plugin code. In this section, we'll check a version value and tweak a generated stub class. Open your plugin project in Eclipse and follow along in the next sessions to tweak some

Add plugin metadata to the POM

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

Add some metadata about your plugin and your company or organization to the POM as follows.

  1. Edit the pom.xml file in the root folder of your plugin.
  2. Add your company or organization name and your website to the <organization> element:

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

    <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 plugin descriptor

Your stub code contains a plugin descriptor file atlassian-plugin.xml. This is an XML file that identifies the plugin to the host application (JIRA) and defines the required plugin functionality. In your IDE, open the descriptor file which is located in your project under src/main/resources and you should see something like this (comments removed):

<atlassian-plugin key="${project.groupId}.${project.artifactId}" 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>
    <component key="myPluginComponent" class="com.example.plugins.tutorial.MyPluginComponentImpl" public="true">
        <interface>com.example.plugins.tutorial.MyPluginComponent</interface>
    </component>
    <component-import key="applicationProperties" interface="com.atlassian.sal.api.ApplicationProperties" />
</atlassian-plugin>

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

Step 3. Update your project and refresh your IDE

If you change your Atlassian project, Eclipse is not automatically aware of the changes. Moreover, sometimes your project dependencies require an update. We need to fix that.

  1. Switch to a terminal window.
  2. Change directory to the project root.
    This is the directory with the pom.xml file.
  3. Update the on-disk project metadata with the new POM information.

    atlas-mvn eclipse:eclipse
    
  4. Back in Eclipse, refresh the plugin project to pick up the changes.

Remember to do this update and refresh step each time you edit your pom.xml or otherwise modify your plugin source with an Atlassian command.

Step 4. Add the servlet module

  1. Open a command window and go to the plugin root folder (where the pom.xml is located).
  2. Run atlas-create-jira-plugin-module.
  3. Choose the option labeled Servlet.
  4. Supply the following information as prompted:

    New Classname

    IssueCRUD

    Package Name

    com.example.plugins.tutorial.crud.servlet

    Show Advanced Setup

    N

  5. Choose N for Add Another Plugin Module.

Review the changes made by the generator

The generator added the following elements to your plugin:

  • A IssueCRUD Java class
  • A <servlet> module in your plugin descriptor
  • Required Maven dependencies (in your POM)

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

 <servlet name="Issue CRUD" i18n-name-key="issue-crud.name" key="issue-crud" class="com.example.plugins.tutorial.crud.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 should see the following new entries in the <dependencies> section.

<dependencies>
...
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>servlet-api</artifactId>
      <version>2.4</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>org.mockito</groupId>
      <artifactId>mockito-all</artifactId>
      <version>1.8.5</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.apache.httpcomponents</groupId>
      <artifactId>httpclient</artifactId>
      <version>4.1.1</version>
      <scope>test</scope>
    </dependency>
...
<dependencies>

Good Time for an Update

Now would be a good time to run atlas-mvn eclipse:eclipse and refresh your Eclipse project.

Step 5. Add Velocity and user management modules

By default, the servlet module is not pre-configured 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 the plugin module generator to import the TemplateRenderer which imports the Velocity template renderer. We'll also import UserManager a feature of the shared access layer (SAL) that will allow us to get information about the logged in user. To add these components, do the following:

  1. Open a command window and go to the plugin root folder (where the pom.xml is located).
  2. Run atlas-create-jira-plugin-module.
  3. Choose the option labeled Component Import.
  4. Follow the prompts to add the TemplateRenderer

    Enter Fully Qualified Interface

    com.atlassian.templaterenderer.TemplateRenderer

    Module Key

    Press return to take the default TemplateRenderer

    Filter (not require)

    Press return to accept the default which is none.

  5. Choose Y for Add Another Plugin Module.
  6. Choose the Component Import again.
  7. Follow the prompts to add the UserManager

    Enter Fully Qualified Interface

    com.atlassian.sal.api.user.UserManager

    Module Key

    Press return to take the default userManager

    Filter (not require)

    Press return to accept the default which is none.

  8. Choose N for Add Another Plugin Module.
  9. Open the pom.xml file inside the root of your project.
  10. Find the <dependencies> section.
  11. Insert the following inside the <dependencies>section:

    <dependency>
      <groupId>com.atlassian.templaterenderer</groupId>
      <artifactId>atlassian-template-renderer-api</artifactId>
      <version>1.3.1</version>
      <scope>provided</scope>
    </dependency>
    
  12. Save your file.
  13. Refresh your dependencies and project.

Step 6. Build your plugin and do a little test

At this point, you haven't actually written any Java code. You can however run JIRA and see your plugin 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 plugin root folder (where the pom.xml file is).
  3. Run the following command:

    atlas-run
    

    This command builds your plugin code, starts a JIRA instance, and installs your plugin in it. This may take several seconds or so, when the process completes you see many status lines on your screen concluding with something like the following lines:

    [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. Open your browser and navigate to the local JIRA instance started by atlas-run
    Use the URL indicated in the print output, such as http://localhost:2990/jira.
  5. At the JIRA login, enter a username of admin and a password of admin.
    The first time you start a JIRA instance, the New Project wizard appears.
  6. Follow the prompts to create a new blank project named TUTORIAL with a key of TUTORIAL. We'll need it for later.

    For our tutorial, it's important to give the project the name and key of TUTORIAL. To keep the focus on coding issue CRUD, we're relying on hard coded project identifers in our plugin. If the new project wizard doesn't appear, be sure to create a project with these values.

  7. Now navigate to your servlet by opening http://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, open the project's src/main/resources/atlassian-plugin.xml files and look for the <servlet> module. It includes a <url-pattern> element identifying this path.
  8. Log out of JIRA.
  9. Return to the terminal window where you ran atlas-run and press Ctrl-C to shut down JIRA.

Step 8. Create Velocity templates

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

  1. Open a terminal window.
  2. From the project home, change to the src/main/resources directory.
  3. Create a subdirectory named templates.

  4. Create a new file named edit.vm at that location with the following content:

    <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">&nbsp;
                        <a href="issuecrud">Cancel</a>
                    </div>
                </form>
            </div>
        </div>
    </div>
    </body>
    </html>
  5. Create a file named list.vm with the following content:

    <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> &nbsp;
                                    <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>
  6. Create a file named new.vm with the following content:

     <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">&nbsp;
                        <a href="issuecrud">Cancel</a>
                    </div>
                </form>
            </div>
        </div>
    
    </div>
    </body>
    </html>

Now you are ready to write some code! 

Step 9. Write your Java classes

In this section, 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 file: 

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

Servlet set up

Let's configure our basic servlet.

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

    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
     
    import java.io.IOException;
    import java.util.List;
    import java.util.Map;
    
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    import com.atlassian.crowd.embedded.api.User;
    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.issue.Issue;
    import com.atlassian.jira.issue.IssueInputParameters;
    import com.atlassian.jira.issue.MutableIssue;
    import com.atlassian.jira.issue.search.SearchException;
    import com.atlassian.jira.jql.builder.JqlClauseBuilder;
    import com.atlassian.jira.jql.builder.JqlQueryBuilder;
    import com.atlassian.jira.project.Project;
    import com.atlassian.jira.web.bean.PagerFilter;
    import com.atlassian.sal.api.user.UserManager;
    import com.atlassian.templaterenderer.TemplateRenderer;
    import com.google.common.collect.Maps;
    
  3. Create a constructor for the IssueCRUD class.

    private IssueService issueService;
    private ProjectService projectService;
    private SearchService searchService;
    private UserManager userManager;
    private TemplateRenderer templateRenderer;
    private com.atlassian.jira.user.util.UserManager jiraUserManager;
    private static final String LIST_BROWSER_TEMPLATE = "/templates/list.vm";
    private static final String NEW_BROWSER_TEMPLATE = "/templates/new.vm";
    private static final String EDIT_BROWSER_TEMPLATE = "/templates/edit.vm";
    
    public IssueCRUD(IssueService issueService, ProjectService projectService, 
                     SearchService searchService, UserManager userManager,
                     com.atlassian.jira.user.util.UserManager jiraUserManager,
                     TemplateRenderer templateRenderer) {
        this.issueService = issueService;
        this.projectService = projectService;
        this.searchService = searchService;
        this.userManager = userManager;
        this.templateRenderer = templateRenderer;
        this.jiraUserManager = jiraUserManager;
    }
    

The constructor's parameters instruct Spring to inject the specified interface Atlassian services into our servlet object. To be able to use them, we also need to assign them as fields in our class and initialize them.

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

After wiring up the JIRA API services that the plugin needs, we can begin working on our request handlers. We'll add a helper method called getCurrentUser to IssueCrud. This method gets the current user through the UserManager service. This is the user that we'll use later to create issues and to search for issues reported.

private User getCurrentUser(HttpServletRequest req) {
	// To get the current user, we first get the username from the session.
	// Then we pass that over to the jiraUserManager in order to get an
	// actual User object.
	return jiraUserManager.getUser(userManager.getRemoteUsername(req));
} 

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

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
                       throws ServletException, IOException {

    if ("y".equals(req.getParameter("new"))) {
        // Renders new.vm template if the "new" parameter is passed

        // Create an empty context map to pass into the render method
        Map<String, Object> context = Maps.newHashMap();
        // Make sure to set the contentType otherwise bad things happen
        resp.setContentType("text/html;charset=utf-8");
        // Render the velocity template (new.vm). Since the new.vm template 
        // doesn't need to render any in dynamic content, we just pass it an empty context
       templateRenderer.render(NEW_BROWSER_TEMPLATE, context, resp.getWriter());
    } else if ("y".equals(req.getParameter("edit"))) {
        // Renders edit.vm template if the "edit" parameter is passed

        // Retrieve issue with the specified key
        IssueService.IssueResult issue = issueService.getIssue(getCurrentUser(req), 
                                                               req.getParameter("key"));
        Map<String, Object> context = Maps.newHashMap();
        context.put("issue", issue.getIssue());
        resp.setContentType("text/html;charset=utf-8");
        // Render the template with the issue inside the context
        templateRenderer.render(EDIT_BROWSER_TEMPLATE, context, resp.getWriter());
    } else {
        // Render the list of issues (list.vm) if no params are passed in
        List<Issue> issues = getIssues(req);
        Map<String, Object> context = Maps.newHashMap();
        context.put("issues", issues);
        resp.setContentType("text/html;charset=utf-8");
        // Pass in the list of issues as the context
        templateRenderer.render(LIST_BROWSER_TEMPLATE, context, resp.getWriter());
    }
}

If you look closely, accessing the issue is all done through the IssueService. We'll also need a method for creating the list of JIRAs. Go ahead and add the getIssues method which will look for issues belonging to the TUTORIAL project.

   private List<Issue> getIssues(HttpServletRequest req) {
		// User is required to carry out a search
		User user = getCurrentUser(req);

		// search issues

		// The search interface requires JQL clause... so let's build one
		JqlClauseBuilder jqlClauseBuilder = JqlQueryBuilder.newClauseBuilder();
		// Our JQL clause is simple project="TUTORIAL"
		com.atlassian.query.Query query = jqlClauseBuilder.project("TUTORIAL").buildQuery();
		// A page filter is used to provide pagination. Let's use an unlimited filter to
		// to bypass pagination.
		PagerFilter pagerFilter = PagerFilter.getUnlimitedFilter();
		com.atlassian.jira.issue.search.SearchResults searchResults = null;
		try {
			// Perform search results
			searchResults = searchService.search(user, query, pagerFilter);
		} catch (SearchException e) {
			e.printStackTrace();
		}
		// return the results
		return searchResults.getIssues();
    }

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

Which page is rendered is determined by the request parameter that the servlet receives. If the edit=y parameter is passed in, the servlet renders the edit.vm template. If the new=y parameter is passed in, the servlet renders the new.vm template. If neither parameters are passed in, then it just renders list.vm.

Handle POST request (create issue and update issue)

Now that we can render the GET request, let's focus on the actual creation and updating of an issue. This happens in a POST request and is handled in our code by the doPost method. Notice that the code assumes a hard-coded project key of TUTORIAL.

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
	    Map params = req.getParameterMap();
	 
	    User user = getCurrentUser(req);
	 
	    if ("y".equals(req.getParameter("edit"))) {
	        // Perform update if the "edit" param is passed in
	        // First get the issue from the key that's passed in
	        IssueService.IssueResult issueResult = issueService.getIssue(user, req.getParameter("key"));
	        MutableIssue issue = issueResult.getIssue();
	        // Next we need to validate the updated issue
	        IssueInputParameters issueInputParameters = issueService.newIssueInputParameters();
	        issueInputParameters.setSummary(req.getParameter("summary"));
	        issueInputParameters.setDescription(req.getParameter("description"));
	        IssueService.UpdateValidationResult result = issueService.validateUpdate(user, issue.getId(),
	                issueInputParameters);
	 
	        if (result.getErrorCollection().hasAnyErrors()) {
	            // If the validation fails, we re-render the edit page with the errors in the context
	            Map<String, Object> context = Maps.newHashMap();
	            context.put("issue", issue);
	            context.put("errors", result.getErrorCollection().getErrors());
	            resp.setContentType("text/html;charset=utf-8");
	            templateRenderer.render(EDIT_BROWSER_TEMPLATE, context, resp.getWriter());
	        } else {
	            // If the validation passes, we perform the update then redirect the user back to the
	            // page with the list of issues
	            issueService.update(user, result);
	            resp.sendRedirect("issuecrud");
	        }
	 
	    } else {
	        // Perform creation if the "new" param is passed in
	        // First we need to validate the new issue being created
	        IssueInputParameters issueInputParameters = issueService.newIssueInputParameters();
	        // We're only going to set the summary and description. The rest are hard-coded to
	        // simplify this tutorial.
	        issueInputParameters.setSummary(req.getParameter("summary"));
	        issueInputParameters.setDescription(req.getParameter("description"));
	        // We need to set the assignee, reporter, project, and issueType...
	        // For assignee and reporter, we'll just use the currentUser
	        issueInputParameters.setAssigneeId(user.getName());
	        issueInputParameters.setReporterId(user.getName());
	        // We hard-code the project name to be the project with the TUTORIAL key
	        Project project = projectService.getProjectByKey(user, "TUTORIAL").getProject();
	        issueInputParameters.setProjectId(project.getId());
	        // We also hard-code the issueType to be a "bug" == 1
	        issueInputParameters.setIssueTypeId("1");
	        // Perform the validation
	        IssueService.CreateValidationResult result = issueService.validateCreate(user, issueInputParameters);
	 
	        if (result.getErrorCollection().hasAnyErrors()) {
	            // If the validation fails, render the list of issues with the error in a flash message
	            List<Issue> issues = getIssues(req);
	            Map<String, Object> context = Maps.newHashMap();
	            context.put("issues", issues);
	            context.put("errors", result.getErrorCollection().getErrors());
	            resp.setContentType("text/html;charset=utf-8");
	            templateRenderer.render(LIST_BROWSER_TEMPLATE, context, resp.getWriter());
	        } else {
	            // If the validation passes, redirect the user to the main issue list page
	            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 edit=y. 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. If there are errors, we pass those into the template context so that Velocity can render it in the HTML.

Delete issues

We can delete issues using our plugin as well. Add the following method to delete issues:

   @Override
    protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        User user = getCurrentUser(req);
        // This will be the output string that we will put the JSON in
        String respStr = "";
        // Retrieve the issue with the specified key
        IssueService.IssueResult issue = issueService.getIssue(user, req.getParameter("key"));
        if (issue.isValid()) {
            // If the issue is found, let's delete it...
            // ... but first, we must validate that user can delete issue
            IssueService.DeleteValidationResult result = issueService.validateDelete(user, issue.getIssue().getId());
            if (result.getErrorCollection().hasAnyErrors()) {
                // If the validation fails, we send the error back to the user in a JSON payload
                respStr = "{ \"success\": \"false\", error: \"" + result.getErrorCollection().getErrors().get(0) + "\" }";
            } else {
                // If the validation passes, we perform the delete, then return a success msg back to the user
                issueService.delete(user, result);
                respStr = "{ \"success\" : \"true\" }";
            }
        } else {
            // The issue can't be found... so we send an error to the user
            respStr = "{ \"success\" : \"false\", error: \"Couldn't find issue\"}";
        }
        // We set the content-type to application/json here so that the AJAX client knows how to deal with it
        resp.setContentType("application/json;charset=utf-8");
        // Send the raw output string we put together
        resp.getWriter().write(respStr);
    }

Step 10. Build your plugin and test the completed plugin

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 plugin root folder (where the pom.xml file is).
  3. Run the following command:

    atlas-run
    

    This command builds your plugin code, starts a JIRA instance, and installs your plugin in it. This may take several seconds or so, when the process completes you see many status lines on your screen concluding with something like the following lines:

    [INFO] jira started successfully in 71s at http://localhost:2990/jira
    [INFO] Type CTRL-D to shutdown gracefully
    [INFO] Type CTRL-C to exit
    
  4. Open your browser and navigate to the local JIRA instance started by atlas-run.
    If you followed the instructions, enter http://localhost:2990/jira in your browser.
  5. At the JIRA login, enter a username of admin and a password of admin.
  6. Now, navigate to your servlet page at this address: http://localhost:2990/jira/plugins/servlet/issuecrud
    You should see a page that looks something like this:
  7. Click Create new issue.
  8. Enter a Summary and a Description.
  9. Press Create.
    The servlet page appears again, this time with your new issue listed. 

Congratulations, that's it

Have a chocolate!

Was this page helpful?

Have a question about this article?

See questions about this article

Powered by Confluence and Scroll Viewport