Edison corporate blog
Website development
JavaScript
Designing and refactoring
April 2015 29

Разработка простого плагина для JIRA для работы с базой данных

Плагин будет представлять собой вкладку в административной части проекта, через которую и будем осуществлять работу с базой данных.

Плагин буду делать для джира 4.4.4. Для начала создадим пустой проект. Проект можно создать с помощью Atlassian SDK, а затем открыть в любимой IDE. В данном случае я буду работать с Netbeans. Файловая структура проекта будет выглядеть следующим образом:


Создадим страничку, на которой будет находиться наш функционал. В джире есть несколько механизмов для создания страниц – report, webwork actions, servlet, для решения этой задачи выберем webwork actions. В момент знакомства с JIRA в этом механизме все было предельно просто, понятно и знакомо (по сути это тот же Apache Struts). Создадим новый класс MyAction.java, унаследованный от класса JiraWebActionSupport. И создадим velocity шаблон succeess.vm такого содержания:

MyAction template

Теперь осталось добавить следующий элемент в atlassian-plugin.xml для создания новой страницы:

<webwork1 key="actions" name="MyActions">
 <actions>            
  <action name="com.edsd.jira.plugins.simpleplugin.action.MyAction" alias="action" roles-required="admin">
   <view name="success">/myaction/success.vm</view>
  </action>
 </actions>
</webwork1>

И при переходе по ссылке http://myserver/secure/action.jspa мы увидим созданную нами страницу:



Теперь сделаем так, чтоб эта страничка стала такой же, как вкладка в админской части проекта.
Для начала сделаем саму ссылку:



Чтоб ссылка выглядела отдельной группой, добавим в atlassian-plugin.xml новую секцию (web-section). Затем в эту секцию уже добавим саму ссылку (web-item):

<web-section key="my_section" name="MySection" location="atl.jira.proj.config" weight="50"/>
<web-item key="my_item_link" name="MyTab" section="atl.jira.proj.config/my_section"  weight="10">
  <label key="MyLink" />
  <link linkId="my_item_link">/secure/action.jspa</link>
</web-item>

Но при клике по ссылке мы все равно будем получать страницу без оформления, чтобы это исправить внесем изменения в MyAction.java. На самом деле при переходе по ссылке http://myserver/secure/action.jspa вызывается метод execute() и возвращаемое им значение –это success поэтому-то мы именно так описали наш action в atlassian-plugin.xml. Так мы будем явно передавать, какой проект сейчас активен, и добавлять «шапку» проекта к вкладке.

public class MyAction extends JiraWebActionSupport {
  private Project project;
  @Override
  public String execute() throws Exception {
    project = getSelectedProjectObject();
//добавление шапки проекта к вкладке
    request.setAttribute("com.atlassian.jira.projectconfig.util.ServletRequestProjectConfigRequestCache:project", project);
    return super.execute();
  }
}

Но так передается только html код без css. Чтоб добавить css к странице допишем в atlassian-plugin.xml

<web-resource key="my-resources">
    <dependency>com.atlassian.jira.jira-project-config-plugin:project-config-global</dependency>
    <context>my-resources</context>
  </web-resource>

Здесь мы добавили зависимость dependency для самих ресурсов и объявили через context то, как мы можем получить доступ к этим ресурсам через менеджера.
Тогда velocity шаблон приведем к такому виду:

<!DOCTYPE HTML>
<html>
    <head>
        <title>MyTestPage</title>
        <meta name="decorator" content="atl.admin"/>
        <meta name="projectKey" content="$project.getKey()"/>
        <meta name="projectId" content="$project.getId()"/>
        <meta name="admin.active.tab" content="my_item_link"/>
        <meta name="admin.active.section" content="atl.jira.proj.config"/>
        $webResourceManager.requireResourcesForContext("my-resources")
    </head> 
    <body>
        MyAction template
    </body>
</html>

В результате получим страницу:



Добавим функционал работы с базой данных. Пользоваться будем Active Objects (дальше AO). Для начала создадим пакет logic. В нем опишем объект, с которым будем работать, пусть это будет элементарный Student:

public interface Student {
    public void setName(String name);
    public String getName();
}

В пакете entity опишем наш интерфейс-сущность, который будем хранить в БД. Пусть для примера в базе еще будем хранить время создания этой записи:

import java.util.Date;
import net.java.ao.Entity;
public interface StudentEntity extends Entity, Student {
    public Date getCreated();
    public void setCreated(Date created);
}

Добавим теперь наш AO в atlassian-plugin.xml:

<component-import key="ao" name="Active Objects service" interface="com.atlassian.activeobjects.external.ActiveObjects">
  <description>Component to access Active Objects functionality from the plugin</description>
</component-import>
    
<ao key="ao-module">
  <entity>com.edsd.jira.plugins.simpleplugin.entity.StudentEntity</entity>
</ao>

Подробнее про подключение AO к плагину можно почитать тут.

Теперь разберемся с взаимодействием нашего приложения с базой данных. Определим интерфейс StudentDAO из пакета DAO, содержащий набор необходимых методов:

public interface StudentDAO {
    public StudentEntity addStudent(Student student) throws Exception; 
    public StudentEntity[] getStudents() throws Exception; 
}
Реализацию интерфейса сделаем в пакете DAO.Impl в классе StudentDAOImpl. 
public class StudentDAOImpl implements StudentDAO {
    private final ActiveObjects ao; 
    public StudentDAOImpl(ActiveObjects ao) {
        this.ao = ao;
    }
    @Override
    public StudentEntity addStudent(final Student student) throws Exception {
        return ao.executeInTransaction(new TransactionCallback<StudentEntity>() {
            @Override
            public StudentEntity doInTransaction() {
                StudentEntity entity = ao.create(StudentEntity.class); 
                entity.setName(student.getName());
                entity.setCreated(new Date(System.currentTimeMillis()));
                entity.save();
                return entity;
            }
        });
    }
    @Override
    public StudentEntity[] getStudents() throws Exception {
        return ao.executeInTransaction(new TransactionCallback<StudentEntity[]>() {
            @Override
            public StudentEntity[] doInTransaction() {
                return ao.find(StudentEntity.class);
            }
        });
    }
}

Теперь создадим класс DAOFactory в пакете DAO, к которому будем обращаться за нашими реализациями DAO, от которых и будем вызывать необходимые нам методы:

public class DAOFactory {
    private static StudentDAO studentDAO = null;
    private static DAOFactory instance = null;
    private static ActiveObjects ao;
    public DAOFactory(ActiveObjects ao) {
        DAOFactory.ao = ao;
    }
    public static synchronized DAOFactory getInstance() {
        if (instance == null) {
            instance = new DAOFactory(ao);
        }
        return instance;
    }
    public StudentDAO getStudentDAO() {
        if (studentDAO == null) {
            studentDAO = new StudentDAOImpl(ao);
        }
        return studentDAO;
    }
}

Добавим в atlassian-plugin.xml нашу DAOFactory. Это необходимо чтобы избежать проблем с обращением к AO. Кому интересно: рассуждение на тему Active Objects injection.

   <component key="dao-factory" class="com.edsd.jira.plugins.simpleplugin.DAO.DAOFactory">
    </component>

Собственно, все необходимое для работы с базой данных мы описали и реализовали, осталось теперь добавить функционал в MyAction:

public class MyAction extends JiraWebActionSupport {
    private Project project;
    private StudentEntity[] students;
    @Override
    public String execute() throws Exception {
        project = getSelectedProjectObject();
request.setAttribute("com.atlassian.jira.projectconfig.util.ServletRequestProjectConfigRequestCache:project", project);
        students = DAOFactory.getInstance().getStudentDAO().getStudents();
        return super.execute();
    }
    public String doAdd() throws Exception {
        String name = request.getParameterValues("name")[0]; 
        Student student = new StudentImpl(name); 
        DAOFactory.getInstance().getStudentDAO().addStudent(student); 
        ServletActionContext.getResponse().sendRedirect("/secure/action.jspa");
        return NONE;
    }
}

И в шаблоне velocity привести тег body к такому виду:

<body>
        <form action="/secure/action!add.jspa">
            <input type="text" name="name"/>
            <input type="submit" value="OK"/>
        </form>
        <table>
            <thead>
                <th>id</th>
                <th>name</th>
                <th>created</th>
            </thead>
            <tbody>
            #foreach($student in $students)
                <tr>
                    <td>$student.getID()</td>
                    <td>$student.getName()</td>
                    <td>$student.getCreated()</td>
                </tr>
            #end
            </tbody>
        </table>
    </body>

В общем, запускаем, тестируем, получаем:



На этом на первый раз все, в следующей статье расскажу:

  • как создать таблицы, аналогичные таблицам во вкладках Компоненты и Версии,
  • как создать выпадающий список, с таким же оформлением как стандартный в JIRA,
  • что такое JIRA.Restfultable
  • и как пользоваться JIRA REST API.

Весь код плагина на GitHub

Продолжение: придаём нашему плагину нормальный внешний вид.



Больше проектов:
Как за 5233 человеко-часа создать софт для микротомографа
SDK для внедрения поддержки электронных книг в формате FB2
Управление доступом к электронным документам. От DefView до Vivaldi
Интегрируем две системы видеонаблюдения: Axxon Next и SureView
Подробнее о разработке софта рентгеновского томографа
«Сфера»: как мониторить миллиарды киловатт-часов
Разработка простого плагина для JIRA для работы с базой данных
В помощь DevOps: сборщик прошивок для сетевых устройств на Debian за 1008 часов
Автообновление службы Windows через AWS для бедных
+9
15.7k 75
Comments 5
Top of the day