Created APIv5 - DataExtension API (markdown)

Risto Lahtela 2019-04-07 09:32:39 +03:00
parent 041d115dd4
commit 2c279ac8a0
1 changed files with 634 additions and 0 deletions

@ -0,0 +1,634 @@
![Plan Header](http://puu.sh/AXSg7/5f2f78c06c.jpg)
# Plan API version 5 - DataExtension API
> This API is still under development and special providers are not available yet.
This page is about API that is available in **version 4.8.0** and above.
API can be called on all platforms.
### Table of contents
- [Dependency information](https://github.com/Rsl1122/Plan-PlayerAnalytics/wiki/APIv5#dependency-information)
- DataExtension API
- 🔔 [How to register a DataExtension](https://github.com/Rsl1122/Plan-PlayerAnalytics/wiki/APIv5#-how-to-register-a-dataextension)
- [PluginInfo Annotation](https://github.com/Rsl1122/Plan-PlayerAnalytics/wiki/APIv5#%E2%84%B9%EF%B8%8F-plugininfo-annotation)
- 📏 [Provider Annotations](https://github.com/Rsl1122/Plan-PlayerAnalytics/wiki/APIv5#-provider-annotations)
- 🔨 [Special Provider Annotations](https://github.com/plan-player-analytics/Plan/wiki/APIv5#-special-provider-annotations)
- 👥 [`@GroupProvider`](https://github.com/plan-player-analytics/Plan/wiki/APIv5#groupprovider)
- 📐 [Extra annotations](https://github.com/Rsl1122/Plan-PlayerAnalytics/wiki/APIv5#-extra-annotations)
- ❕ [Preventing runtime errors](https://github.com/Rsl1122/Plan-PlayerAnalytics/wiki/APIv5#-preventing-runtime-errors)
- Implementation violations
- API in Action
## Dependency information
> The API is still under heavy development and details might be subject to change,
use at your own risk.
Plan API is distributed via Bintray repository:
```
<repository>
<snapshots>
<enabled>false</enabled>
</snapshots>
<id>bintray-rsl1122-Plan-repository</id>
<name>bintray</name>
<url>https://dl.bintray.com/rsl1122/Plan-repository</url>
</repository>
```
```
<dependency>
<groupId>com.djrapitops</groupId>
<artifactId>Plan-api</artifactId>
<version>...</version>
</dependency>
```
Dependency version information can be found here:
https://bintray.com/rsl1122/Plan-repository/Plan-API
Remember to add `"Plan"` as a softdependency to your plugin information (plugin.yml / Plugin annotation).
# DataExtension API
DataExtension API is used for providing Plan with data of your plugin, so that the data can be viewed on the web panel.
This makes it easier for users to reason about their server.
## 🔔 How to register a DataExtension
1. Create a new object that implements `DataExtension`.
2. Obtain instance of `ExtensionService` and register using `ExtensionService#register(DataExtension)`
```
DataExtension yourExtension = new YourDataExtensionImplementation();
try {
ExtensionService.getInstance().register(yourExtension);
} catch (NoClassDefFoundError planIsNotInstalled) {
// Plan is not installed, handle exception
} catch (IllegalStateException planIsNotEnabled) {
// Plan is not enabled, handle exception
} catch (IllegalArgumentException dataExtensionImplementationIsInvalid) {
// The DataExtension implementation has an implementation error, handle exception
}
```
> Registration should be done in it's own class to avoid `NoClassDefFoundError` if Plan is not installed.
You might need to catch the error when calling your method that does the registering.
## `@PluginInfo` annotation
Every `DataExtension` implementation **requires** `@PluginInfo` annotation.
> **Notation:**
> `*` means required parameter
<details>
<summary>Usage & Parameters</summary>
```
@PluginInfo(
name* = "Your Plugin",
iconName = "cube"
iconFamily = Family.SOLID,
color = Color.NONE
)
public class YourExtension implements DataExtension {}
```
- `name` - Name of your plugin
- `iconName` - Icon names can be found from https://fontawesome.com/icons?d=gallery&m=free
- `iconFamily` - Icon family should match details on the site above
- `color` - Colors are available here *Link to be added*
</details>
## 📏 Provider Annotations
Provider annotations are method annotations that tell Plan what kind of data methods in your `DataExtension` class provide.
> **Notation:**
> `*` means required parameter
The name of the methods are used for storing information the methods provide.
Methods can have 4 different parameters (**But only one**):
```
T method(); // The value is about the server as a whole
T method(UUID playerUUID); // The value is about a player
T method(String playerName); // The value is about a player
T method(Group group); // The value is about a Group a player is in
```
<details>
<summary>Controlling when Plan calls your DataExtension methods</summary>
```
@Override
public CallEvents[] callExtensionMethodsOn() {
return new CallEvents[]{
CallEvents.PLAYER_JOIN,
CallEvents.PLAYER_LEAVE,
CallEvents.SERVER_EXTENSION_REGISTER,
CallEvents.SERVER_PERIODICAL
};
}
```
- `DataExtension#callExtensionMethodsOn` can be overridden to change default event call behavior.
- Explanations
- Empty array: Plan will not call the methods automatically, see below for manual calls.
- `PLAYER_JOIN`: Plan calls player methods after a Join event (Bukkit/Bungee: MONITOR, Sponge: POST)
- `PLAYER_LEAVE`: Plan calls player methods after a Quit event (Bukkit/Bungee: NORMAL, Sponge: DEFAULT)
- `SERVER_EXTENSION_REGISTER` Plan calls server methods after registering the `DataExtension`
- `SERVER_PERIODICAL` Plan calls server methods periodically via a task
</details>
<details>
<summary>Manual update calls</summary>
```
DataExtension yourExtension;
Optional<Caller> caller = extensionService.register(yourExtension);
```
- `ExtensionService#register` returns a `Optional<Caller>`. The Caller is present if the extension was registered.
- You can use the `Caller` methods in your listeners when your data is updated.
> **HOX** Do not use `Caller` inside `DataExtension` - This might lead to unbound recursion!
```
Caller caller;
caller.updatePlayerData(playerUUID, playerName);
caller.updateServerData();
```
</details>
### `@BooleanProvider`
**Speciality:** `boolean` values, Can work with `@Conditional`-annotation for conditional execution
<details>
<summary>Usage & Parameters</summary>
```
@BooleanProvider(
text* = "Has Island",
description = "Whether or not the player has an island in the island world",
priority = 5,
iconName = "question",
iconFamily = Family.SOLID,
iconColor = Color.NONE,
conditionName = "islandCondition",
hidden = false
)
public boolean hasIsland(UUID playerUUID) {...}
```
- `text` - Text that appears next to the value, for example "Has Island: No"
- `description` - Text that appears when hovering over the value
- `priority` - Ordering number, highest value is placed top most
- `iconName` - Icon names can be found from https://fontawesome.com/icons?d=gallery&m=free
- `iconFamily` - Icon family should match details on the site above
- `iconColor` - Color of the icon that appears next to the value
- `conditionName` - Name of a condition this boolean represents, used with `@Conditional` annotations
- `hidden` - Should this value be hidden from the web panel?
</details>
<details>
<summary>Usage with @Conditional</summary>
```
@BooleanProvider(
...
conditionName = "hasIsland"
)
public boolean hasIsland(UUID playerUUID) {
return true;
}
@Conditional("hasIsland)
@StringProvider(...) // Another provider is required, can be any Provider.
public String islandName(UUID playerUUID) {...}
```
- `conditionName` is provided by a `@BooleanProvider`.
- If the value is `true`, other methods with `@Conditional(conditionName)` will be called.
- See [Conditional](https://github.com/plan-player-analytics/Plan/wiki/APIv5#conditional) for more
</details>
<details>
<summary>Hiding "Yes"/"No" results</summary>
```
@BooleanProvider(
...
conditionName* = "hasLinkedAccount",
hidden = true
)
public boolean hasLinkedAccount(UUID playerUUID) {...}
```
- If `hidden` is `true`, the `conditionName` parameter is required.
- "Yes" / "No" for this method will not appear on the web panel.
</details>
### `@NumberProvider`
**Speciality:** Whole numbers, Time amounts, Timestamps
<details>
<summary>Usage & Parameters</summary>
```
@NumberProvider(
text* = "Number of Islands",
description = "How many islands does the player own",
priority = 4,
iconName = "question",
iconFamily = Family.SOLID,
iconColor = Color.NONE,
format = FormatType.NONE
)
public long islandCount(UUID playerUUID) {...}
```
- `text` - Text that appears next to the value, for example "Has Island: No"
- `description` - Text that appears when hovering over the value
- `priority` - Ordering number, highest value is placed top most
- `iconName` - Icon names can be found from https://fontawesome.com/icons?d=gallery&m=free
- `iconFamily` - Icon family should match details on the site above
- `iconColor` - Color of the icon that appears next to the value
- `format` - `FormatType` tells that the value is time amount (milliseconds) or a timestamp
</details>
<details>
<summary>FormatTypes (Dates / Time amounts)</summary>
```
@NumberProvider(
...
format = FormatType.DATE_YEAR
)
public long banDate(UUID playerUUID) {...}
```
- `DATE_YEAR` - Value is an Epoch Millisecond, Formats the date based on Plan settings. Year is given importance
- `DATE_SECOND` - Value is an Epoch Millisecond, Formats the date based on Plan settings. Seconds are given importance
- `TIME_MILLISECOND` - Value is amount of milliseconds, Formats time amount based on Plan settings.
- `NONE` - Value is not formatted
</details>
### `@DoubleProvider`
**Speciality:** Floating point numbers
<details>
<summary>Usage & Parameters</summary>
```
@DoubleProvider(
text* = "Balance",
description = "Amount of money the player has",
priority = 3,
iconName = "question",
iconFamily = Family.SOLID,
iconColor = Color.NONE
)
public double balance(UUID playerUUID) {...}
```
- `text` - Text that appears next to the value, for example "Has Island: No"
- `description` - Text that appears when hovering over the value
- `priority` - Ordering number, highest value is placed top most
- `iconName` - Icon names can be found from https://fontawesome.com/icons?d=gallery&m=free
- `iconFamily` - Icon family should match details on the site above
- `iconColor` - Color of the icon that appears next to the value
</details>
### `@PercentageProvider`
**Speciality:** Percentages between 0% and 100%. Requires return values between 0.0 and 1.0.
<details>
<summary>Usage & Parameters</summary>
```
@PercentageProvider(
text* = "Quest completion",
description = "Quest completion percentage",
priority = 5,
iconName = "question",
iconFamily = Family.SOLID,
iconColor = Color.NONE
)
public double questCompletion(UUID playerUUID) {...}
```
- `text` - Text that appears next to the value, for example "Has Island: No"
- `description` - Text that appears when hovering over the value
- `priority` - Ordering number, highest value is placed top most
- `iconName` - Icon names can be found from https://fontawesome.com/icons?d=gallery&m=free
- `iconFamily` - Icon family should match details on the site above
- `iconColor` - Color of the icon that appears next to the value
</details>
### `@StringProvider`
**Speciality:** String values, Links to player page when `playerName` is `true`
<details>
<summary>Usage & Parameters</summary>
```
@StringProvider(
text* = "Town Name",
description = "What town the player has residency in.",
priority = 5,
iconName = "question",
iconFamily = Family.SOLID,
iconColor = Color.NONE,
playerName = false
)
public String townName(UUID playerUUID) {...}
```
- `text` - Text that appears next to the value, for example "Has Island: No"
- `description` - Text that appears when hovering over the value
- `priority` - Ordering number, highest value is placed top most
- `iconName` - Icon names can be found from https://fontawesome.com/icons?d=gallery&m=free
- `iconFamily` - Icon family should match details on the site above
- `iconColor` - Color of the icon that appears next to the value
- `playerName` - Does this provider return a player name that can be linked to? Links are automatic
</details>
<details>
<summary>Links to player pages</summary>
```
@StringProvider(
...
playerName = true
)
public String townMayor(Group town) {...}
```
- If `playerName` is `true`, the returned value will be formatted to display a link to a player's page.
</details>
## 🔨 Special Provider annotations
These annotations can be used to add information structures. They might have different limitations than other providers.
### `@GroupProvider`
> `@GroupProvider` will be included in the second update for DataExtension API.
**Speciality:** Multiple `Group`s the player is in. Any providers with `Group` parameter will be called with the groups that this method provides.
<details>
<summary>Limitations and Additional data</summary>
- There can only be one GroupProvider per DataExtension - If you need multiple kinds of groups, create another `DataExtension` class.
- Method requires UUID or String as method parameter.
- Does not work with `@Tab` annotations
Plan will construct following from given group data:
- Players in different groups are counted
- Additional data given by `Group` parameter methods about each group.
- Additional data about players is grouped based on groups.
- Additional data is displayed under a dropdown of the group.
</details>
<details>
<summary>Usage & Parameters</summary>
```
@GroupProvider(
text* = "Jobs",
description = "What jobs the player has",
priority = 5,
iconName = "question",
iconFamily = Family.SOLID,
iconColor = Color.NONE
)
public String[] playerJobs(UUID playerUUID) {...}
```
- `text` - Text that appears next to the value on Player's page, for example "Jobs: None"
- `description` - Text that appears when hovering over the value
- `priority` - Ordering number, highest value is placed top most
- `iconName` - Icon names can be found from https://fontawesome.com/icons?d=gallery&m=free
- `iconFamily` - Icon family should match details on the site above
- `iconColor` - Color of the icon that appears next to the value
</details>
### `@TableProvider`
> `@TableProvider` will be included in the second update for DataExtension API.
**Speciality:** Table structures.
<details>
<summary>Limitations</summary>
- Player tables can have 4 columns.
- Server tables can have 5 columns.
> If you want to display a table that lists how many players are using something (or data about groups), eg. Players on each version, use `@GroupProvider` (and `Group` parameter methods) instead.
</details>
<details>
<summary>Usage & Parameters</summary>
```
@TableProvider(tableColor = Color.NONE)
public Table banHistory(UUID playerUUID) {
Table.Factory banTable = Table.builder()
.columnOne("When", new Icon(Family.SOLID, "gavel")) // Define column names and icons
.columnTwo("...", new Icon(...)) // Icon colors are ignored.
.columnThree("...", new Icon(...))
.columnFour("...", new Icon(...));
for (YourData data : yourData) {
banTable.addRow(String...);
}
return banTable.build();
}
```
- `tableColor` - Color of the table header.
</details>
### `@GraphProvider`
**Speciality:** Graphs.
This will be implemented later.
## 📐 Extra annotations
These annotations can be used to further control how the values are displayed.
### `@Tab`, `@TabInfo` and `@TabOrder`
**Specialilty:** Control what plugin-tab the provided value appears in.
<details>
<summary>Usage & Parameters</summary>
```
@TabInfo(
tab* = "Economy",
iconName = "circle",
iconFamily = Family.SOLID,
elementOrder = {ElementOrder.VALUES, ElementOrder.TABLE, ElementOrder.GRAPH}
)
@TabInfo(tab* = "Second Tab")
@TabOrder({"Economy", "Second Tab"})
@PluginInfo(...)
public class YourExtension implements DataExtension {
@BooleanProvider(text = "Has Pet")
@Tab("Second Tab")
public boolean hasPet(UUID playerUUID) {...}
}
```
- `tab` - Name of the tab
- `iconName` - Icon names can be found from https://fontawesome.com/icons?d=gallery&m=free
- `iconFamily` - Icon family should match details on the site above
- `elementOrder` - Order different elements are placed inside the tab. `VALUES` represents all single values
</details>
### `@Conditional`
**Specialilty:** Control execution of another methods. If provided `boolean` is `true` the method with `@Conditional` annotation will be called.
<details>
<summary>Usage & Parameters</summary>
```
@BooleanProvider(..., conditionName="hasPet")
public boolean hasPet(UUID playerUUID) {...}
@Conditional("hasPet")
@StringProvider(text = "Pet Name")
public String petName(UUID playerUUID) {...}
```
- `value` - Name of the condition
- `negated` - Require the condition to be `false` for this method to be called.
</details>
<details>
<summary>Reversed Conditions</summary>
```
@BooleanProvider(..., conditionName="permanentBan")
public boolean isPermanentlyBanned(UUID playerUUID) {...}
@Conditional(value = "permanentBan", negated = true)
@NumberProvider(...)
public long expiryDate(UUID playerUUID) {...}
```
- Negated Conditionals are only called when the provided condition is `false`.
</details>
<details>
<summary>Nested Conditions (Conjunction / AND)</summary>
```
@BooleanProvider(..., conditionName="isBanned")
public boolean isBanned(UUID playerUUID) {...}
@Conditional("isBanned")
@BooleanProvider(..., conditionName="isTemporaryBan")
public boolean isTemporaryBan(UUID playerUUID) {...}
@Conditional("isTemporaryBan")
@NumberProvider(...)
public long expireDate(UUID playerUUID) {...}
```
- Conditions can be nested by adding a `@Conditional` `@BooleanProvider` that returns a second condition.
</details>
### `@InvalidateMethod`
**Speciality:** Removes old values from database if you decide to rename a method. The method name is used when storing the values, so this annotation exists to remove the old values.
<details>
<summary>Usage & Parameters</summary>
```
@InvalidateMethod("oldMethodName")
@PluginInfo(...)
public class YourExtension implements DataExtension {
@BooleanProvider(...)
public boolean newMethodName(UUID playerUUID) {...}
}
```
- `value` - Name of a removed method, for removing stored results of that method.
</details>
## ❕ Preventing runtime errors
Since annotations do not have any compiler checks, invalid implementation can not be enforced at compile time, and runtime exceptions are used instead.
To make implementation easier it is possible to Unit test against the implementation errors.
How:
```
@Test
public void noImplementationErrors() {
DataExtension yourExtension = new YourExtensionImplementation();
// Throws IllegalArgumentException if there is an implementation error or warning.
new ExtensionExtractor(yourExtension).validateAnnotations();
}
```
### Implementation violations
Here is a short list of what throws an exception
- No `@PluginInfo` class annotation
- Class contains no public methods with Provider annotations
- Class contains private method with Provider annotation
- Non-primitive return type when primitive is required (eg. `Boolean` instead of `boolean`)
- Method doesn't have correct parameters (none, UUID, String or Group)
- `@BooleanProvider` is annotated with a `@Conditional` that requires same condition the provider provides.
- `@Conditional` without a `@BooleanProvider` that provides value for the condition
Warnings:
- An annotation String `<variable>` is over 50 characters (Or over 150 if `description`)
- Method name is over 50 characters (Used as an identifier for storage)
## API in action
If you would like to see how the API is being used in built in plugin support, [check out implementations in repositories that start with Extension](https://github.com/plan-player-analytics)