Created DataExtension API - Getting started (markdown)
parent
025ce21614
commit
7ce742a5de
|
@ -0,0 +1,420 @@
|
|||
![Plan Header](https://puu.sh/AXSg7/5f2f78c06c.jpg)
|
||||
|
||||
# DataExtension API - Getting started
|
||||
|
||||
This tutorial attempts to guide you through adding a DataExtension to your plugin, for more in-depth documentation about different parts of the API, see [DataExtension API](https://github.com/plan-player-analytics/Plan/wiki/APIv5---DataExtension-API).
|
||||
|
||||
These icons are used to aid understanding
|
||||
|
||||
💭 Question about possible issues (Someone has had these before)
|
||||
💡 Extra stuff
|
||||
|
||||
## ✔️ Requirements
|
||||
|
||||
- A java plugin project for a minecraft server
|
||||
|
||||
## 🚩 Tutorial Goals
|
||||
|
||||
Here are the goals the tutorial aims to guide you through.
|
||||
At the end of this tutorial you will have
|
||||
|
||||
- .. Added Plan API as a dependency to your project
|
||||
- (.. added Plan as soft-dependency to your plugin)
|
||||
- .. Created 2 new classes to use the API
|
||||
- .. Created 1 new test class to test use of the API
|
||||
- .. Defined a DataExtension that adds a number to a player's page
|
||||
|
||||
> 💭 **What is this API for?**
|
||||
>
|
||||
> DataExtension API is for displaying data from a plugin on the Plan website.
|
||||
|
||||
----
|
||||
|
||||
# Goal #1: Adding Plan API as a dependency
|
||||
|
||||
## Step 1: Add Plan repository to your project
|
||||
|
||||
### Maven
|
||||
|
||||
- Add the repository to your `<repositories>`-block in `pom.xml` of your project
|
||||
```xml
|
||||
<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>
|
||||
```
|
||||
|
||||
### Gradle
|
||||
|
||||
- Add the repository to your `repositories`-block in `build.gradle` of your project
|
||||
```groovy
|
||||
maven {
|
||||
url "https://dl.bintray.com/rsl1122/Plan-repository"
|
||||
}
|
||||
```
|
||||
|
||||
### Other build tools
|
||||
|
||||
- Go to https://bintray.com/rsl1122/Plan-repository/Plan-API and click Set Me Up!
|
||||
- Select your build tool or downloading alternative & follow the instructions there
|
||||
|
||||
## Step 2: Add Plan API as a dependency
|
||||
|
||||
- Go to https://bintray.com/rsl1122/Plan-repository/Plan-API and see what is the latest version number under *Versions*. No need to download anything.
|
||||
|
||||
### Maven
|
||||
|
||||
- Add Plan API as a dependency to your `<dependencies>`-block in in `pom.xml` of your project
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>com.djrapitops</groupId>
|
||||
<artifactId>Plan-api</artifactId>
|
||||
<version>...</version> <!-- Add the version number here -->
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
### Gradle
|
||||
|
||||
- Add Plan API as a compile & test compile time dependency to your `dependencies`-block in `build.gradle` of your project.
|
||||
```groovy
|
||||
compileOnly 'com.djrapitops:Plan-api:...'
|
||||
testCompileOnly 'com.djrapitops:Plan-api:...'
|
||||
```
|
||||
|
||||
### Other
|
||||
|
||||
- Add Plan API as a dependency in your build tool.
|
||||
|
||||
## Step 3: Add Plan as a soft-dependency in your plugin
|
||||
|
||||
### Spigot, Nukkit & Bungeecord (plugin.yml)
|
||||
|
||||
- Add Plan in `softdepend` in `plugin.yml` of your project
|
||||
|
||||
```yml
|
||||
softdepend:
|
||||
- Plan
|
||||
# nukkit
|
||||
softdepend: ["Plan"]
|
||||
```
|
||||
|
||||
### Sponge & Velocity (Plugin annotation)
|
||||
|
||||
- Add Plan as an optional dependency to the `@Plugin` annotation
|
||||
```scala
|
||||
@Plugin(
|
||||
id = ...,
|
||||
dependencies = {
|
||||
@Dependency(id = "plan", optional = true)
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
✔️ Your project now includes Plan API as a dependency!
|
||||
|
||||
# Goal #2: Access Plan API from your plugin
|
||||
|
||||
## Step 1: Create a class to separate Plan imports from your main class
|
||||
|
||||
In order to keep Plan as an optional dependency, all access to the Plan API should be made from a separate class. In this tutorial this will be called `PlanHook`, but you can call it whatever you want.
|
||||
|
||||
Let's take a look at this example class:
|
||||
```java
|
||||
import com.djrapitops.plan.capability.CapabilityService;
|
||||
import com.djrapitops.plan.extension.ExtensionService;
|
||||
|
||||
public class PlanHook {
|
||||
private final MyPluginDatabase database;
|
||||
|
||||
public PlanHook(MyPluginDatabase database) {
|
||||
this.database = database;
|
||||
}
|
||||
|
||||
public void hookIntoPlan() {
|
||||
if (!areAllCapabilitiesAvailable()) return;
|
||||
registerDataExtension();
|
||||
listenForPlanReloads();
|
||||
}
|
||||
|
||||
private boolean areAllCapabilitiesAvailable() {
|
||||
CapabilityService capabilities = CapabilityService.getInstance();
|
||||
return capabilities.hasCapability("DATA_EXTENSION_VALUES");
|
||||
}
|
||||
|
||||
private void registerDataExtension() {
|
||||
try {
|
||||
ExtensionService.getInstance().register(new MyPluginDataExtension(database));
|
||||
} catch (IllegalStateException planIsNotEnabled) {
|
||||
// Plan is not enabled, handle exception
|
||||
} catch (IllegalArgumentException dataExtensionImplementationIsInvalid) {
|
||||
// The DataExtension implementation has an implementation error, handle exception
|
||||
}
|
||||
}
|
||||
|
||||
private void listenForPlanReloads() {
|
||||
CapabilityService.getInstance().registerEnableListener(
|
||||
isPlanEnabled -> {
|
||||
// Register DataExtension again
|
||||
if (isPlanEnabled) registerDataExtension();
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Creating a separate class is necessary to keep `NoClassDefFoundError` away from loading your plugin when Plan is not enabled!
|
||||
|
||||
Here is some more explanation for each section of the code in case you need more information.
|
||||
|
||||
<details>
|
||||
<summary>Construction</summary>
|
||||
|
||||
```java
|
||||
private final MyPluginDatabase database;
|
||||
|
||||
public PlanHook(MyPluginDatabase database) {
|
||||
this.database = database;
|
||||
}
|
||||
```
|
||||
- `MyPluginDatabase` is the database of the example plugin, and is given so that the DataExtension has some data to display later.
|
||||
|
||||
</details>
|
||||
<details>
|
||||
<summary>hookIntoPlan()</summary>
|
||||
|
||||
```java
|
||||
public void hookIntoPlan() {
|
||||
if (!areAllCapabilitiesAvailable()) return;
|
||||
registerDataExtension();
|
||||
listenForPlanReloads();
|
||||
}
|
||||
```
|
||||
- This method checks if Plan has the capabilities you need, the check is similar to how some plugins ask you to check the version number.
|
||||
- If the capabilities are available, the extension is registered.
|
||||
- Additionally a listener for Plan reloads is registered to re-register the data extension.
|
||||
|
||||
</details>
|
||||
<details>
|
||||
<summary>areAllCapabilitiesAvailable()</summary>
|
||||
|
||||
```java
|
||||
private boolean areAllCapabilitiesAvailable() {
|
||||
CapabilityService capabilities = CapabilityService.getInstance();
|
||||
return capabilities.hasCapability("DATA_EXTENSION_VALUES");
|
||||
}
|
||||
```
|
||||
- Checks that DATA_EXTENSION_VALUES capability is available. Some features might need more capabilities, and when they do it is mentioned in the documentation. Those capabilities can then be added here.
|
||||
|
||||
</details>
|
||||
<details>
|
||||
<summary>registerDataExtension()</summary>
|
||||
|
||||
```java
|
||||
private void registerDataExtension() {
|
||||
try {
|
||||
ExtensionService.getInstance().register(new MyPluginDataExtension(database));
|
||||
} catch (IllegalStateException planIsNotEnabled) {
|
||||
// Plan is not enabled, handle exception
|
||||
} catch (IllegalArgumentException dataExtensionImplementationIsInvalid) {
|
||||
// The DataExtension implementation has an implementation error, handle exception
|
||||
}
|
||||
}
|
||||
```
|
||||
- Creates `MyPluginDataExtension` with the database of the plugin, and registers it to Plan
|
||||
- `IllegalStateException` might be thrown if Plan has not enabled properly
|
||||
- `IllegalArgumentException` might be thrown if the `MyPluginDataExtension` has an implementation error. Later a test class is created to avoid this issue.
|
||||
|
||||
</details>
|
||||
<details>
|
||||
<summary>listenForPlanReloads()</summary>
|
||||
|
||||
```java
|
||||
private void listenForPlanReloads() {
|
||||
CapabilityService.getInstance().registerEnableListener(
|
||||
isPlanEnabled -> {
|
||||
// Register DataExtension again
|
||||
if (isPlanEnabled) registerDataExtension();
|
||||
}
|
||||
)
|
||||
}
|
||||
```
|
||||
- Registers a listener to Plan that registers the extension again in case Plan is reloaded.
|
||||
|
||||
</details>
|
||||
|
||||
## Step 2: construct and call the PlanHook in your plugin enable.
|
||||
|
||||
In this example the Spigot `JavaPlugin#onEnable` is used, but you can add these methods to wherever you wish, as long as it is called after Plan has been loaded & enabled.
|
||||
|
||||
```java
|
||||
MyPluginDatabase database; // The example plugin has a database
|
||||
|
||||
public void onEnable() {
|
||||
... // The example plugin enables itself
|
||||
|
||||
try {
|
||||
new PlanHook(database).hookIntoPlan();
|
||||
} catch (NoClassDefFoundError planIsNotInstalled) {
|
||||
// Plan is not installed
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
✔️ You can now access Plan API from somewhere!
|
||||
|
||||
# Goal #3: Creating a DataExtension
|
||||
|
||||
You might have noticed that there was a `MyPluginDataExtension` in the previous section, this is what is going to be implemented next.
|
||||
|
||||
## Step 1: Create your DataExtension class
|
||||
|
||||
Let's look at this example class:
|
||||
```java
|
||||
@PluginInfo(
|
||||
name = "MyPlugin",
|
||||
iconName = "vial",
|
||||
iconFamily = Family.SOLID,
|
||||
color = Color.LIGHT_BLUE
|
||||
)
|
||||
public class MyPluginDataExtension implements DataExtension {
|
||||
|
||||
private final MyPluginDatabase database;
|
||||
|
||||
public MyPluginDataExtension(MyPluginDatabase database) {
|
||||
this.database = database;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CallEvents[] callExtensionMethodsOn() {
|
||||
return new CallEvents[]{
|
||||
CallEvents.PLAYER_JOIN,
|
||||
CallEvents.PLAYER_LEAVE
|
||||
};
|
||||
}
|
||||
|
||||
@NumberProvider(
|
||||
text = "Completed Challenges",
|
||||
description = "How many challenges has the player completed",
|
||||
iconName = "bookmark",
|
||||
iconColor = Color.GREEN,
|
||||
priority = 100,
|
||||
showInPlayerTable = true
|
||||
)
|
||||
public long challengesCompleted(UUID playerUUID) {
|
||||
return database.getCompletedChallengeCount(playerUUID);
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
Individual sections explained:
|
||||
|
||||
<details>
|
||||
<summary>PluginInfo annotation</summary>
|
||||
|
||||
```java
|
||||
@PluginInfo(
|
||||
name = "MyPlugin",
|
||||
iconName = "vial",
|
||||
iconFamily = Family.SOLID,
|
||||
color = Color.LIGHT_BLUE
|
||||
)
|
||||
public class MyPluginDataExtension implements DataExtension {...}
|
||||
```
|
||||
- PluginInfo annotation provides meta-data to Plan on how to display your plugin on the page.
|
||||
- It is a class annotation, the class needs to implement `DataExtension`
|
||||
- [Documentation](https://github.com/plan-player-analytics/Plan/wiki/APIv5---DataExtension-API#%E2%84%B9%EF%B8%8F-plugininfo-annotation) (middle-click to open to new tab)
|
||||
|
||||
</details>
|
||||
<details>
|
||||
<summary>Construction</summary>
|
||||
|
||||
```java
|
||||
private final MyPluginDatabase database;
|
||||
|
||||
public MyPluginDataExtension(MyPluginDatabase database) {
|
||||
this.database = database;
|
||||
}
|
||||
```
|
||||
- `MyPluginDatabase` is the database of the example plugin, and is given so that the DataExtension has some data to display.
|
||||
|
||||
</details>
|
||||
<details>
|
||||
<summary>Overriding callExtensionMethodsOn</summary>
|
||||
|
||||
```java
|
||||
@Override
|
||||
public CallEvents[] callExtensionMethodsOn() {
|
||||
return new CallEvents[]{
|
||||
CallEvents.PLAYER_JOIN,
|
||||
CallEvents.PLAYER_LEAVE
|
||||
};
|
||||
}
|
||||
```
|
||||
- This method determines when Plan calls the Provider methods you declare in your DataExtension. You might need to change this depending on what kind of methods you put in. Forcing updates is also possible.
|
||||
- [Documentation (controlling when Plan calls your DataExtension methods)](https://github.com/plan-player-analytics/Plan/wiki/APIv5---DataExtension-API#-provider-annotations) (middle-click to open to new tab)
|
||||
|
||||
</details>
|
||||
<details>
|
||||
<summary>NumberProvider and challengesCompleted method</summary>
|
||||
|
||||
```java
|
||||
@NumberProvider(
|
||||
text = "Completed Challenges",
|
||||
description = "How many challenges has the player completed",
|
||||
iconName = "bookmark",
|
||||
iconColor = Color.GREEN,
|
||||
priority = 100,
|
||||
showInPlayerTable = true
|
||||
)
|
||||
public long challengesCompleted(UUID playerUUID) {
|
||||
return database.getCompletedChallengeCount(playerUUID);
|
||||
}
|
||||
```
|
||||
- Provider annotations give meta-data to Plan on how to display the data you are about to give, such as name, description, where to show it on the page etc.
|
||||
- Each provider annotation needs specific return type, `NumberProvider` requires `long` to be returned.
|
||||
- The method parameters determine if the method is a "Player method" or "Server method", in this case `UUID` -> This is a player method
|
||||
- [Documentation for NumberProvider](https://github.com/plan-player-analytics/Plan/wiki/APIv5---DataExtension-API#numberprovider) (middle-click to open to new tab)
|
||||
- [Documentation for all Provider annotations](https://github.com/plan-player-analytics/Plan/wiki/APIv5---DataExtension-API#-provider-annotations) (middle-click to open to new tab)
|
||||
|
||||
</details>
|
||||
|
||||
✔️ You now have a DataExtension!
|
||||
|
||||
# Goal #4: Test your DataExtension implementation
|
||||
|
||||
Place a test method to a test class.
|
||||
|
||||
```java
|
||||
@Test
|
||||
public void noImplementationErrors() {
|
||||
DataExtension yourExtension = new MyPluginExtension();
|
||||
|
||||
// Throws IllegalArgumentException if there is an implementation error or warning.
|
||||
new ExtensionExtractor(yourExtension).validateAnnotations();
|
||||
}
|
||||
```
|
||||
|
||||
✔️ Less errors at runtime!
|
||||
|
||||
----
|
||||
|
||||
There is still a lot of additional annotations to further control the meta-data and for different kinds of data. I'll list some here for you to check out from the [DataExtension API documentation](https://github.com/plan-player-analytics/Plan/wiki/APIv5---DataExtension-API)
|
||||
|
||||
- Providers
|
||||
- BooleanProvider: Yes/No answers and possibility for conditional data
|
||||
- NumberProvider: Numbers, time amounts and dates.
|
||||
- DoubleProvider: Floating point numbers
|
||||
- PercentageProvider: Percentages between 0% and 100%
|
||||
- StringProvider: Strings and player names that link to the player's page
|
||||
- GroupProvider: Names of different groups the player belongs in, eg. Permission groups
|
||||
- TableProvider: Data for displaying a table
|
||||
- Extra annotations
|
||||
- Tab, TabInfo and TabOrder: Place provided data to different sections on the page (For cases like where your plugin provides lots of different kinds of data, such as an Essentials or punishment plugin)
|
||||
- Conditional: Controls conditional execution of the provider method
|
||||
- InvalidateMethod: If you remove a extension method later this annotation is used to remove the old data
|
Loading…
Reference in New Issue