📜 ⬆️ ⬇️

Atlassian Plugins: Diving into Active Objects and Plugin Settings

Hi, Habr! I work in Mail.Ru Group in the plug-in development department of JIRA. Plugins allow you to extend or change the functionality of the application. For example, with their help, you can create new types of fields, gadgets, JQL-queries, panels with various information, charts and much more .

Most of our plugins require the storage of additional data that they use. In this article I want to tell how we solve this problem. There are two main ways to store such data: Active Objects and Plugin Settings. Consider them in more detail and understand in what case it is better and more convenient to use one, and in which - the other.

image

1. Active Objects


Active Objects is a library that is based on ORM technology (Object Relational Mapping). It links databases with concepts of object-oriented programming, creating a so-called virtual object database.
')
Active Objects are used to work with data groups of the same type. For example, it can be lists of equipment, warehouses, contractors. Such information can be synchronized with other services or entered manually. Active Objects are also suitable for storing project settings, fields, and many others.

Creating objects


To create an Active Objects entity, use an interface inherited from net.java.ao.Entity:

public interface Product extends Entity { String getName(); void setName(String name); double getPrice(); void setPrice(double price); } 

Receiving and recording data takes place with the help of paired get and set methods. Each pair refers to one field in the database table where information will be stored.

To use Active Objects you need to connect the library in the pom-file.

 <dependency> <groupId>com.atlassian.activeobjects</groupId> <artifactId>activeobjects-plugin</artifactId> <version>0.23.7</version> <scope>provided</scope> </dependency> 

The ActiveObjects component and all created entities are imported into the plugin structure file (atlassian-plugin.xml).

 <component-import key="ao" interface="com.atlassian.activeobjects.external.ActiveObjects" /> <ao key="ao-entities"> <entity>com.jira.plugins.shop.model.Product</entity> <entity>com.jira.plugins.shop.model.Shop</entity> </ao> 

Work with Active Objects


To work with instances of Active Objects, it is convenient to use a separate class manager. It aggregates the functions that allow you to create, modify and retrieve such objects. After creating this class, connect it as a component in the atlassian-plugin.xml file:

 <component key="product-manager" class="com.jira.plugins.shop.ProductManager" /> 

All operations on objects are carried out in separate transactions. For example, to get a store by its ID, you can write the following method:

 private final ActiveObjects ao; public Product getProduct(final int id) { return ao.executeInTransaction(new TransactionCallback<Shop>() { @Override public Product doInTransaction() { return ao.get(Product.class, id); } }); } 

Often it is required to receive various data samples. Using the net.java.ao.Query class, net.java.ao.Query can create any SQL queries. However, it is undesirable to do this, because we will be tied to the names of the fields from the database.

 public Product[] getProducts(final String name) { return ao.executeInTransaction(new TransactionCallback<Product[]>() { @Override public Product[] doInTransaction() { return ao.find(Product.class, Query.select().where("NAME = ?", name).order("NAME")); } }); } 

An instance of Active Objects is created using the ao.create function. Then, if necessary, fill its fields. Important note: the object must be saved after editing, otherwise all changes will be lost.

 public Product createProduct(final String name, final double price) { return ao.executeInTransaction(new TransactionCallback<Product>() { @Override public Product doInTransaction() { Product product = ao.create(Product.class); product.setName(name); product.setPrice(price); product.save(); return product; } }); } 

When changing, you must first get the object from the database, and then change its content.

Deletion occurs with ao.delete , to which the instance itself is passed.

 public void deleteProduct(final int id) { ao.executeInTransaction(new TransactionCallback<Void>() { @Override public Void doInTransaction() { Product product = ao.get(Product.class, id); ao.delete(product); return null; } }); } 

In some cases, it is better not to delete the object permanently, but simply mark it as deleted, for example, with the deleted field.

Connections between objects


Active Objects can be linked together. There are three types of links. We will tell more about each of them.

One to one relationship


For example, a store can have only one address and, accordingly, one store from the network can be at one address.

 public interface Shop extends Entity { @OneToOne Address getAddress(); } public interface Address extends Entity { Shop getShop(); void setShop(Shop shop); } 

In order to link objects, you must call setShop(Shop shop) .

One to many relationship


For example, a store may have several sellers, but the seller only works in one store.

 public interface Shop extends Entity { @OneToMany Seller[] getSellers(); } public interface Seller extends Entity { Shop getShop(); void setShop(Shop shop); } 

Many to many relationship


For example, a product may be in different stores of a chain and there may be many products in a store. Accordingly, this relationship will be applied in the Product and Shop classes. It is obligatory to create a third entity that will link the other two (as an additional table in relational databases).

The annotation is set only for get-methods.

 public interface Shop extends Entity { @ManyToMany(value = ProductToShop.class) Product[] getProducts(); } public interface Product extends Entity { @ManyToMany(value = ProductToShop.class) Shop[] getShops(); } public interface ProductToShop extends Entity { Product getProduct(); void setProduct(Product product); Shop getShop(); void setShop(Shop shop); } 

Data storage


Active Objects are stored in a separate database table. By default, the table name is formed from three parts. The first consists of the prefix AO (Active Objects). The second is the six characters of the hexadecimal value of the MD5 hash of the plug-in key or, if present, the Active Objects module namespace attribute. The last part is the name of the Active Objects entity. An example of a standard name looks like this: AO_28BE2D_MY_OBJECT .

The names of the table columns are determined by the methods of inserting and receiving values ​​from the database. Titles containing uppercase letters will be separated by an underscore. For example, if the method was called getProductId() , then the column would be called PRODUCT_ID .

Active Objects work with the following data types:


Rename tables and columns


Renaming is used in refactoring code and in long field names, since there is a limit on the length of a name in databases.

To change the standard table name, you must use the @Table("NewName") annotation.

 @Table("Item") public interface Product extends Entity { double getPrice(); void getPrice(double price); } 

When renaming fields, you must apply the annotations @Mutator("NewName") and @Accessor("NewName") . In this case, the names of the columns in the table itself will not be changed. The @Accessor annotation @Accessor specified for the function that returns a value, and @Mutator is @Mutator for the function that changes it.

 public interface Product extends Entity { @Accessor("Cost") double getPrice(); @Mutator("Cost") void getPrice(double price); } 

Underwater rocks


Currently Active Objects do not work with the BLOB data type. In this case, the information can be stored in the file system directly.

There is also a problem when working with links. Let's explain it with an example. We have two entities: address and store. One to one connection is established between them. Let the name of the city be changed. If you request the store object from the address, and the address object from it, the city value will return in the old version. The fact is that the fields are not initialized when the object is changed. In this case, if the object has references to other objects, it is necessary to initialize it again after changing it.

In the creation and search operations in the database, the case of letters in the column name can be set. Also, the length cannot exceed 30 characters and the reserved words cannot be used: BLOB , CLOB , NUMBER , ROWID , TIMESTAMP , VARCHAR2 .

If an object requires a long text field, then the @StringLength(StringLength.UNLIMITED) annotation is placed in front of it. Since, for example, in MySQL a regular String will be 255 characters long.

2. Plugin Settings


Plugin Settings are part of the Shared Access Layer in the Atlassian framework. They provide data storage in the form of a key-value pair, which the plugin will refer to during operation.

There are situations when it is necessary to store general settings for the plugin. At the same time, it is impractical to create a separate table for one record. In such cases, it is convenient to use Plugin Settings.

Creating settings and using them


To create a Plugin Settings object, use the PluginSettingsFactory interface. It allows you to create settings in the form of a key value. Important: the key must be unique, so you can take the full name of your plug-in for it. An example of working with Plugin Settings is as follows:

 public interface PluginData { String getDistributingFacilitiesName(); void setDistributingFacilitiesName(String distributingFacilitiesName); } public class PluginDataImpl implements PluginData { private static final String PLUGIN_PREFIX = "com.jira.plugins.shop:"; private static final String DISTRIBUTING_FACILITIES_NAME = PLUGIN_PREFIX + "distributingFacilitiesName"; private final PluginSettingsFactory pluginSettingsFactory; public PluginDataImpl(PluginSettingsFactory pluginSettingsFactory) { this.pluginSettingsFactory = pluginSettingsFactory; } @Override public String getDistributingFacilities() { return (String) pluginSettingsFactory.createGlobalSettings().get(DISTRIBUTING_FACILITIES_NAME); } @Override public void getDistributingFacilities(String distributingFacilitiesName) { pluginSettingsFactory.createGlobalSettings().put(DISTRIBUTING_FACILITIES_NAME, distributingFacilitiesName); } } 

You can create both global settings and local ones for the project:


Data storage


Information is stored in tables in a database. The propertyentry table records the name, key, and value type of the Plugin Settings. The value of the property is written to the table corresponding to its type, for example propertystring . Data Types Supported by Plugin Settings:


Underwater rocks


To use Plugin Settings in different classes, you must create an object for each operation. The fact is that when creating an object, it will be initialized once. If it is changed elsewhere, then in the current class these changes will not be displayed.

A bad style is to store big data, such as a list of values, in one property of an object. To access one item from this list, you need to initialize it entirely. For the same type of settings, you will have to create keys of the form COLOR_1 , COLOR_2 , etc. You can easily get confused in them, forgetting what they refer to.

It is worth noting that when storing users, you need to write down their key (key), and not the name (name), since the name can be changed, and the key always remains the same and unique.

Conclusion


We considered two ways to store data in plugins. Plugin Settings are suitable for single configurations. Their key-value structure allows you to quickly obtain the necessary settings. Using Plugin Settings, you can create both local and global configurations.

For large data sets of the same type, such as lists of equipment, contractors, etc., it is better to use Active Objects.
According to Atlassian, they are a simple, fast and scalable way to store and access information. Data is stored in a separate table in the database. Objects can be linked to each other.

Sources used:

  1. Atlassian Active Objects Documentation
  2. Introducing Active Objects Plugin
  3. Atlassian Plugin Settings Documentation
  4. How and where to store Plugin Settings

PS We have a professional community in social networks, where we discuss the use of Atlassian products, share experiences and even arrange live events. Write your plugins and share the results! Join us:

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


All Articles