Hello!
In this article we will talk how to extend your own Jira Server/Data Center app with the ModuleType annotation.
The ModuleType annotation lets you expose a module from you Jira Server/Data Center app.
Why would we need it?
Let's take Configuration Manager. This app allows you to migrate Jira configuration from one instance to another. During this migration we also migrate custom fields. But the developers of Configuration Manager can not migrate all types of custom fields because there are custom fields which contain its own settings in the Jira database. For example, Insight. The developers of Configuration Manager would have to learn how the Insight app stores configuration data for the Insight custom fields to implement migration of these custom fields. That is why it would be much easier for Configuration Manager developers to provide an interface which Insight developers would use to implement migration of their custom fields. And Configuration Manager developers did it. They created modules which can be extended by other developers. You can read more about it here.
And in this article we will create such a module for our app.
We will create a calculator app.
Our app will be able to sum two values and we will add a module which will let other developers to add new operations in their apps. And also we will develop an app which will add the minus operation this way.
Ok. Let's get started!
Calculator
You can see the source code for this section here.
We will get the result from our Calculator app by a rest call. That is why I created a rest endpoint in the Calculator app.
src/main/java/ru/matveev/alexey/atlassian/calculator/rest/CalculatorRest.java
@Path("/calculate")
public class CalculatorRest {
private final CalculatorService calculatorService;
public CalculatorRest(CalculatorService calculatorService) {
this.calculatorService = calculatorService;
}
@POST
@AnonymousAllowed
@Consumes(MediaType.APPLICATION_JSON)
@Produces({MediaType.APPLICATION_JSON})
public Response calculate(CalculatorModel body) throws OperationNotFoundException {
int result = this.calculatorService.calculate(body.getOperation(),
body.getValue1(),
body.getValue2());
return Response.ok(new CalculatorResponseModel(result)).build();
}
We add a POST REST endpoint. We accept CalculatorModel as the request body for this endpoint then we calculate the result and return this result as CalculatorResponseModel.
Here is our CalculatorModel.
src/main/java/ru/matveev/alexey/atlassian/calculator/rest/CalculatorModel.java
@Data
public class CalculatorModel {
private String operation;
private int value1;
private int value2;
}
Which means that our request body should look like this:
{"operation": "sum", "value1": 1, "value2" : 3}
And here is our CalculatorResponseModel.
src/main/java/ru/matveev/alexey/atlassian/calculator/rest/CalculatorResponseModel.java
@XmlRootElement(name = "result")
@XmlAccessorType(XmlAccessType.FIELD)
@Data
public class CalculatorResponseModel {
@XmlElement(name = "result")
private int result;
public CalculatorResponseModel(int result) {
this.result = result;
}
}
Which means that our response will be like this:
{"result": 4}
Have a look at our REST endpoint (CalculatorRest.java). We get the result with the CalculatorService service:
int result = this.calculatorService.calculate(body.getOperation(),
body.getValue1(),
body.getValue2());
Here is our CalculatorService.
src/main/java/ru/matveev/alexey/atlassian/calculator/service/CalculatorService.java
@Named
public class CalculatorService {
private final SumOperation sumOperation;
public CalculatorService(SumOperation sumOperation) {
this.sumOperation = sumOperation;
}
public int calculate(String operation, int val1, int val2) throws OperationNotFoundException {
if (operation.equals(sumOperation.getName())) {
return sumOperation.calculate(val1, val2);
}
throw new OperationNotFoundException(String.format("Operation %s not found", operation));
}
}
As you can see we have the calculate method where we check if the operation parameter equals to the operation name defined in SumOperation, we call the calculate method of the SumOperation class instance.
If the value of the operation parameter does not equal to the name defined in SumOperation, we throw the OperationNotFoundException.
src/main/java/ru/matveev/alexey/atlassian/calculator/exception/OperationNotFoundException.java
public class OperationNotFoundException extends Exception {
public OperationNotFoundException(String errorMessage) {
super(errorMessage);
}
}
Here is our SumOperation class:
src/main/java/ru/matveev/alexey/atlassian/calculator/impl/SumOperation.java
@Named
public class SumOperation implements Operation
{
@Override
public String getName() {
return "sum";
}
@Override
public int calculate(int val1, int val2) {
return val1 + val2;
}
}
We just implemented Operation interface which should be implemented by all operations for our calculator.
src/main/java/ru/matveev/alexey/atlassian/calculator/api/Operation.java
public interface Operation
{
String getName();
int calculate(int val1, int val2);
}
That is all. Let's run our app.
Run app
Run our app and open the following URL in your browser:
http://localhost:2990/jira/plugins/servlet/restbrowser#/resource/calculator-1-0-calculate
You will see an interface to run our REST endpoint.
First, pass this json as the request body:
{"operation": "sum", "value1": 1, "value2" : 3}
And you will get the following result:
The result is correct.
Let's try to perform the minus operation with a request body like this:
{"operation": "minus", "value1": 1, "value2" : 3}
And OperationNotFoundException will be thrown:
Everything is correct. Now let's extend our calculator with a moduletype to allow other apps to implement other operations.
Alexey Matveev
software developer
MagicButtonLabs
Philippines
1,574 accepted answers
0 comments