📜 ⬆️ ⬇️

Spring AOP and JavaConfig in Atlassian Jira plugins

In this article, we will develop a plug-in for Atlassian Jira, where using JavaConfig we will define a prototype bean, log the bean's method calls using AOP, and display information from external beans (ApplicationProperties, JiraAuthenticationContext and ConstantsManager).

The source code of the plugin can be found here .

1. Create a plugin.


To do this, open the terminal and enter:

atlas-create-jira-plugin 

Questions asked in the terminal should be answered like this:
')
 Define value for groupId: : ru.matveev.alexey.plugins.spring Define value for artifactId: : spring-tutorial Define value for version: 1.0.0-SNAPSHOT: : Define value for package: ru.matveev.alexey.plugins.spring: : groupId: ru.matveev.alexey.plugins.spring artifactId: spring-tutorial version: 1.0.0-SNAPSHOT package: ru.matveev.alexey.plugins.spring Y: : Y 

2. Make changes to pom.xml


You need to change the scope of the atlassian-spring-scanner-annotation from compile to provided.

 <dependency> <groupId>com.atlassian.plugin</groupId> <artifactId>atlassian-spring-scanner-annotation</artifactId> <version>${atlassian.spring.scanner.version}</version> <scope>compile</scope> </dependency> 

Remove dependency atlassian-spring-scanner-runtime.
Change the atlassian.spring.scanner.version property to 2.0.0

Add the following dependencies:

 <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>4.2.5.RELEASE</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>4.2.5.RELEASE</version> <scope>provided</scope> </dependency> 

Add the following line to the instructions tag in maven-jira-plugin:

 <DynamicImport-Package>*</DynamicImport-Package> 

This line allows the plugin to find Spring classes at run time.

3. Create the interface and implementation of the HelloWorld entity.


HelloWorld.java

 package ru.matveev.alexey.plugins.spring.api; public interface HelloWorld { String getMessage(); void setMessage(String value); } 

HelloWorldImpl.java

 package ru.matveev.alexey.plugins.spring.impl; import com.atlassian.jira.config.ConstantsManager; import com.atlassian.jira.security.JiraAuthenticationContext; import com.atlassian.sal.api.ApplicationProperties; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ru.matveev.alexey.plugins.spring.api.HelloWorld; public class HelloWorldImpl implements HelloWorld { private static final Logger LOG = LoggerFactory.getLogger(HelloWorldImpl.class); private String message = "Hello World!!!"; private final ApplicationProperties applicationProperties; private final ConstantsManager constantsManager; private final JiraAuthenticationContext jiraAuthenticationContext; public HelloWorldImpl(ApplicationProperties applicationProperties, JiraAuthenticationContext jiraAuthenticationContext, ConstantsManager constantsManager) { this.applicationProperties = applicationProperties; this.constantsManager = constantsManager; this.jiraAuthenticationContext = jiraAuthenticationContext; } public String getMessage() { LOG.debug("getMessage executed"); return applicationProperties.getDisplayName() + " logged user: " + jiraAuthenticationContext.getLoggedInUser().getName() + " default priority: " + constantsManager.getDefaultPriority().getName() + " " + this.message; } public void setMessage(String value) { LOG.debug("setMessage executed"); message = value; } } 

The class accepts three external beans and outputs data from these beans to the getMessage method.

4. Create a class for importing Jira bins.


JiraBeansImporter.java

 import com.atlassian.jira.config.ConstantsManager; package ru.matveev.alexey.plugins.spring.impl; import com.atlassian.jira.security.JiraAuthenticationContext; import com.atlassian.plugin.spring.scanner.annotation.imports.ComponentImport; import com.atlassian.sal.api.ApplicationProperties; import javax.inject.Inject; import javax.inject.Named; @Named public class JiraBeansImporter { @Inject public JiraBeansImporter(@ComponentImport ApplicationProperties applicationProperties, @ComponentImport JiraAuthenticationContext jiraAuthenticationContext, @ComponentImport ConstantsManager constantsManager ) { } } 

This class is needed only for JavaConfig to see the external beans we need.

5. Create classes for logging data about the called methods of objects.


HijackBeforeMethod.java
This class logs information before calling the object's method.

 package ru.matveev.alexey.plugins.spring.aop; import java.lang.reflect.Method; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.aop.MethodBeforeAdvice; public class HijackBeforeMethod implements MethodBeforeAdvice { private static final Logger LOG = LoggerFactory.getLogger(HijackBeforeMethod.class); public void before(Method method, Object[] objects, Object o) throws Throwable { LOG.debug("HijackBeforeMethod : method {} in", method.toString()); } } 

HijackAroundMethod.java
This class logs information before calling and after calling an object method.

 package ru.matveev.alexey.plugins.spring.aop; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Arrays; public class HijackAroundMethod implements MethodInterceptor { private static final Logger LOG = LoggerFactory.getLogger(HijackAroundMethod.class); public Object invoke(MethodInvocation methodInvocation) throws Throwable { LOG.debug("HijackAroundMethod : Method name : " + methodInvocation.getMethod().getName()); LOG.debug("HijackAroundMethod : Method arguments : " + Arrays.toString(methodInvocation.getArguments())); LOG.debug("HijackAroundMethod : Before method hijacked!"); try { Object result = methodInvocation.proceed(); LOG.debug("HijackAroundMethod : Before after hijacked!"); return result; } catch (IllegalArgumentException e) { LOG.debug("HijackAroundMethod : Throw exception hijacked!"); throw e; } } } 

6. Create JavaConfig


 package ru.matveev.alexey.plugins.spring.config; import com.atlassian.jira.config.ConstantsManager; import com.atlassian.jira.security.JiraAuthenticationContext; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; import ru.matveev.alexey.plugins.spring.aop.HijackAroundMethod; import ru.matveev.alexey.plugins.spring.aop.HijackBeforeMethod; import com.atlassian.sal.api.ApplicationProperties; import org.springframework.aop.framework.ProxyFactoryBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import ru.matveev.alexey.plugins.spring.api.HelloWorld; import ru.matveev.alexey.plugins.spring.impl.HelloWorldImpl; @Component @Configuration public class Config{ @Bean(name = "helloWorld") @Scope("prototype") public HelloWorld helloWorld(ApplicationProperties applicationProperties, JiraAuthenticationContext jiraAuthenticationContext, ConstantsManager constantsManager) { return new HelloWorldImpl(applicationProperties, jiraAuthenticationContext, constantsManager); } @Bean(name="hijackBeforeMethodBean") public HijackBeforeMethod hijackBeforeMethod() { return new HijackBeforeMethod(); } @Bean(name="hijackAroundMethodBean") public HijackAroundMethod hijackAroudnMethod() { return new HijackAroundMethod(); } @Bean (name = "helloWorldBeforeProxy") @Scope("prototype") public ProxyFactoryBean proxyBeforeFactoryBean(ApplicationProperties applicationProperties, JiraAuthenticationContext jiraAuthenticationContext, ConstantsManager constantsManager) { ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean(); proxyFactoryBean.setTarget(helloWorld(applicationProperties,jiraAuthenticationContext,constantsManager)); proxyFactoryBean.setProxyTargetClass(true); proxyFactoryBean.setInterceptorNames("hijackBeforeMethodBean"); return proxyFactoryBean; } @Bean (name = "helloWorldAroundProxy") @Scope("prototype") public ProxyFactoryBean proxyAroundFactoryBean(ApplicationProperties applicationProperties, JiraAuthenticationContext jiraAuthenticationContext, ConstantsManager constantsManager) { ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean(); proxyFactoryBean.setTarget(helloWorld(applicationProperties,jiraAuthenticationContext,constantsManager)); proxyFactoryBean.setProxyTargetClass(true); proxyFactoryBean.setInterceptorNames("hijackAroundMethodBean"); return proxyFactoryBean; } } 

In JavaConfig, we create:


7. Create two servlets.


Servlets will be used to test our application.

We open the terminal and execute:

 atlas-create-jira-plugin-module 

When asked about the type of module being created, select 21 (Servlet):

 Choose a number (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): 21 

Further questions are answered as follows:

 Enter New Classname MyServlet: : MyServlet1 Enter Package Name ru.matveev.alexey.plugins.spring.servlet: : Show Advanced Setup? (Y/y/N/n) N: : N Add Another Plugin Module? (Y/y/N/n) N: : Y Choose a number (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): 21 Enter New Classname MyServlet: : MyServlet2 Enter Package Name ru.matveev.alexey.plugins.spring.servlet: : Show Advanced Setup? (Y/y/N/n) N: : N Add Another Plugin Module? (Y/y/N/n) N: : N 

As a result, we will create the files MyServlet1.java and MyServlet2.java. Change the code in these files:
MyServlet1.java
We pass our proxy bin to the servlet helloWorldBeforeProxy. That is, when accessing HelloWorld, the information will be logged before calling the HelloWorld methods.

 package ru.matveev.alexey.plugins.spring.servlet; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Qualifier; import ru.matveev.alexey.plugins.spring.api.HelloWorld; import javax.inject.Inject; import javax.servlet.*; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; public class MyServlet1 extends HttpServlet{ private static final Logger log = LoggerFactory.getLogger(MyServlet1.class); private final HelloWorld helloWorld; @Inject public MyServlet1(@Qualifier("helloWorldBeforeProxy") HelloWorld helloWorld) { this.helloWorld = helloWorld; } @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { log.debug("MyServlet1 called"); resp.setContentType("text/html"); String message = "<html><body>" + helloWorld.getMessage() + "</body></html>"; helloWorld.setMessage("message changed MyServlet"); resp.getWriter().write(message); } } 

MyServlet2.java
We send our proxy bin to the servlet helloWorldAroundProxy. That is, when accessing HelloWorld, information will be logged before and after calling HelloWorld methods.

 package ru.matveev.alexey.plugins.spring.servlet; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Qualifier; import ru.matveev.alexey.plugins.spring.api.HelloWorld; import javax.inject.Inject; import javax.servlet.*; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; public class MyServlet2 extends HttpServlet{ private static final Logger log = LoggerFactory.getLogger(MyServlet2.class); private final HelloWorld helloWorld; @Inject public MyServlet2(@Qualifier("helloWorldAroundProxy") HelloWorld helloWorld) { this.helloWorld = helloWorld; } @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { log.debug("MyServlet2 called"); resp.setContentType("text/html"); String message = "<html><body>" + helloWorld.getMessage() + "</body></html>"; helloWorld.setMessage("message changed MyServlet"); resp.getWriter().write(message); } } 

8. Check the result.


We need to check the following:

  1. Our plugin is launched.
  2. Displays information from external bins imported by us (ApplicationProperties, JiraAuthenticationContext, ConstantsManager)
  3. helloWorld works in prototype scope.
  4. Log information about calls to helloWorld methods.

Open a terminal and enter:

 atlas-run 

After Jira is launched, open Jira in the browser at localhost : 2990 / jira / and log in to Jira.



Set the logging level of the ru.matveev.alexey package in DEBUG. To do this, go to System-> Logging and Profiling:



Go to localhost : 2990 / jira / plugins / servlet / myservlet1 to call MyServlet1.java.



From the screenshot we see that our plugin is working, and information from external bins is transmitted. We successfully checked the first two items.

To check that helloWorld has a prototype scope, we’ll refresh the page in the browser:



We see that the message “Hello World !!!” was replaced with “message changed MyServlet”. Since helloWorld has a prototype scope, when accessing MyServlet2.java, we should get the value “Hello World !!!” (if the scope was a singleton, we would receive the message “changed changed MyServlet”).

We contact MyServlet2.java at localhost : 2990 / jira / plugins / servlet / myservlet2:



We see that the message “Hello World !!!” appeared in the caption, which means that the prototype is really the scope of the helloWorld.

Then check if the information was logged when accessing the helloWorld methods. In the logs we find:

 [INFO] [talledLocalContainer] 2018-04-01 17:06:52,609 http-nio-2990-exec-4 DEBUG admin 1026x406x1 1fz8ddf 127.0.0.1 /plugins/servlet/myservlet1 [rmapspring.servlet.MyServlet1] MyServlet1 called [INFO] [talledLocalContainer] 2018-04-01 17:06:52,610 http-nio-2990-exec-4 DEBUG admin 1026x406x1 1fz8ddf 127.0.0.1 /plugins/servlet/myservlet1 [rmapspring.aop.HijackBeforeMethod] HijackBeforeMethod : method public java.lang.String ru.matveev.alexey.plugins.spring.impl.HelloWorldImpl.getMessage() in [INFO] [talledLocalContainer] 2018-04-01 17:06:52,610 http-nio-2990-exec-4 DEBUG admin 1026x406x1 1fz8ddf 127.0.0.1 /plugins/servlet/myservlet1 [rmapspring.impl.HelloWorldImpl] getMessage executed [INFO] [talledLocalContainer] 2018-04-01 17:06:52,611 http-nio-2990-exec-4 DEBUG admin 1026x406x1 1fz8ddf 127.0.0.1 /plugins/servlet/myservlet1 [rmapspring.aop.HijackBeforeMethod] HijackBeforeMethod : method public void ru.matveev.alexey.plugins.spring.impl.HelloWorldImpl.setMessage(java.lang.String) in [INFO] [talledLocalContainer] 2018-04-01 17:06:52,611 http-nio-2990-exec-4 DEBUG admin 1026x406x1 1fz8ddf 127.0.0.1 /plugins/servlet/myservlet1 [rmapspring.impl.HelloWorldImpl] setMessage executed [INFO] [talledLocalContainer] 2018-04-01 17:06:55,024 http-nio-2990-exec-3 DEBUG admin 1026x407x1 1fz8ddf 127.0.0.1 /plugins/servlet/myservlet2 [rmapspring.servlet.MyServlet2] MyServlet2 called [INFO] [talledLocalContainer] 2018-04-01 17:06:55,025 http-nio-2990-exec-3 DEBUG admin 1026x407x1 1fz8ddf 127.0.0.1 /plugins/servlet/myservlet2 [rmapspring.aop.HijackAroundMethod] HijackAroundMethod : Method name : getMessage [INFO] [talledLocalContainer] 2018-04-01 17:06:55,026 http-nio-2990-exec-3 DEBUG admin 1026x407x1 1fz8ddf 127.0.0.1 /plugins/servlet/myservlet2 [rmapspring.aop.HijackAroundMethod] HijackAroundMethod : Method arguments : [] [INFO] [talledLocalContainer] 2018-04-01 17:06:55,026 http-nio-2990-exec-3 DEBUG admin 1026x407x1 1fz8ddf 127.0.0.1 /plugins/servlet/myservlet2 [rmapspring.aop.HijackAroundMethod] HijackAroundMethod : Before method hijacked! [INFO] [talledLocalContainer] 2018-04-01 17:06:55,026 http-nio-2990-exec-3 DEBUG admin 1026x407x1 1fz8ddf 127.0.0.1 /plugins/servlet/myservlet2 [rmapspring.impl.HelloWorldImpl] getMessage executed [INFO] [talledLocalContainer] 2018-04-01 17:06:55,026 http-nio-2990-exec-3 DEBUG admin 1026x407x1 1fz8ddf 127.0.0.1 /plugins/servlet/myservlet2 [rmapspring.aop.HijackAroundMethod] HijackAroundMethod : Before after hijacked! [INFO] [talledLocalContainer] 2018-04-01 17:06:55,026 http-nio-2990-exec-3 DEBUG admin 1026x407x1 1fz8ddf 127.0.0.1 /plugins/servlet/myservlet2 [rmapspring.aop.HijackAroundMethod] HijackAroundMethod : Method name : setMessage [INFO] [talledLocalContainer] 2018-04-01 17:06:55,026 http-nio-2990-exec-3 DEBUG admin 1026x407x1 1fz8ddf 127.0.0.1 /plugins/servlet/myservlet2 [rmapspring.aop.HijackAroundMethod] HijackAroundMethod : Method arguments : [message changed MyServlet] [INFO] [talledLocalContainer] 2018-04-01 17:06:55,026 http-nio-2990-exec-3 DEBUG admin 1026x407x1 1fz8ddf 127.0.0.1 /plugins/servlet/myservlet2 [rmapspring.aop.HijackAroundMethod] HijackAroundMethod : Before method hijacked! [INFO] [talledLocalContainer] 2018-04-01 17:06:55,026 http-nio-2990-exec-3 DEBUG admin 1026x407x1 1fz8ddf 127.0.0.1 /plugins/servlet/myservlet2 [rmapspring.impl.HelloWorldImpl] setMessage executed [INFO] [talledLocalContainer] 2018-04-01 17:06:55,026 http-nio-2990-exec-3 DEBUG admin 1026x407x1 1fz8ddf 127.0.0.1 /plugins/servlet/myservlet2 [rmapspring.aop.HijackAroundMethod] HijackAroundMethod : Before after hijacked! 

From the logs, we see that the logging of information about the called methods has occurred.
Thus, we successfully coped with the goals.

Source: https://habr.com/ru/post/352558/


All Articles