Table of Contents
- Plan API version 5 - DataExtension API
- DataExtension API
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
Plan API version 5 - DataExtension API
This page is about API that is available in version 4.8.0 and above.
API can be called on all platforms.
This API requires DATA_EXTENSION_VALUES
capability.
See APIv5 for dependency information
💭 What is this API for?
DataExtension API is for adding data from your plugin to Plan so that it can be displayed on the Plan website.
Table of contents
- DataExtension API
- Getting Started
- 🔔 How to register a DataExtension
- ℹ️ PluginInfo Annotation
- 📏 Provider Annotations
- 🔨 Special Provider Annotations
- 👥
@GroupProvider
- 🧰
@DataBuilderProvider
, dynamic definition of data at runtime instead of provider annotations
- 👥
- 📐 Extra annotations
- ❕ Preventing runtime errors
- Implementation violations
- API in Action
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.
Getting started
You can follow this step-by-step guide for getting started with the API.
🔔 How to register a DataExtension
- Create a new object that implements
DataExtension
. - Obtain instance of
ExtensionService
and register usingExtensionService#register(DataExtension)
try {
DataExtension yourExtension = new YourDataExtensionImplementation();
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. Getting started, step 2.1
ℹ️ @PluginInfo
annotation
Every DataExtension
implementation requires @PluginInfo
annotation.
Usage & Parameters
@PluginInfo(
name = "Your Plugin", // ALWAYS REQUIRED
iconName = "cube"
iconFamily = Family.SOLID,
color = Color.NONE
)
public class YourExtension implements DataExtension {}
name
- Name of your pluginiconName
- Icon names can be found from https://fontawesome.com/icons?d=gallery&m=freeiconFamily
- Icon family should match details on the site abovecolor
- Colors are available here Link to be added
📏 Provider Annotations
Provider annotations are method annotations that tell Plan what kind of data methods in your DataExtension
class provide.
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
Controlling when Plan calls your DataExtension methods
@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 (default: PLAYER_JOIN, PLAYER_LEAVE, SERVER_EXTENSION_REGISTER).- 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 theDataExtension
SERVER_PERIODICAL
Plan calls server methods periodically via a task
Manual update calls
DataExtension yourExtension;
Optional<Caller> caller = extensionService.register(yourExtension);
ExtensionService#register
returns aOptional<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
insideDataExtension
- This might lead to unbound recursion!
Caller caller;
caller.updatePlayerData(playerUUID, playerName);
caller.updateServerData();
@BooleanProvider
Speciality: boolean
values, Can work with @Conditional
-annotation for conditional execution
Usage & Parameters
@BooleanProvider(
text = "Has Island", // ALWAYS REQUIRED
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 valuepriority
- Ordering number, highest value is placed top mosticonName
- Icon names can be found from https://fontawesome.com/icons?d=gallery&m=freeiconFamily
- Icon family should match details on the site aboveiconColor
- Color of the icon that appears next to the valueconditionName
- Name of a condition this boolean represents, used with@Conditional
annotationshidden
- Should this value be hidden from the web panel?
Usage with @Conditional
@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 for more
Hiding "Yes"/"No" results
@BooleanProvider(
...
conditionName = "hasLinkedAccount", // REQUIRED to use hidden
hidden = true
)
public boolean hasLinkedAccount(UUID playerUUID) {...}
- If
hidden
istrue
, theconditionName
parameter is required. - "Yes" / "No" for this method will not appear on the web panel.
@NumberProvider
Speciality: Whole numbers, Time amounts, Timestamps
Usage & Parameters
@NumberProvider(
text = "Number of Islands", // ALWAYS REQUIRED
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 valuepriority
- Ordering number, highest value is placed top mosticonName
- Icon names can be found from https://fontawesome.com/icons?d=gallery&m=freeiconFamily
- Icon family should match details on the site aboveiconColor
- Color of the icon that appears next to the valueformat
-FormatType
tells that the value is time amount (milliseconds) or a timestamp
FormatTypes (Dates / Time amounts)
@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 importanceDATE_SECOND
- Value is an Epoch Millisecond, Formats the date based on Plan settings. Seconds are given importanceTIME_MILLISECOND
- Value is amount of milliseconds, Formats time amount based on Plan settings.NONE
- Value is not formatted
@DoubleProvider
Speciality: Floating point numbers
Usage & Parameters
@DoubleProvider(
text = "Balance", // ALWAYS REQUIRED
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 valuepriority
- Ordering number, highest value is placed top mosticonName
- Icon names can be found from https://fontawesome.com/icons?d=gallery&m=freeiconFamily
- Icon family should match details on the site aboveiconColor
- Color of the icon that appears next to the value
@PercentageProvider
Speciality: Percentages between 0% and 100%. Requires return values between 0.0 and 1.0.
Usage & Parameters
@PercentageProvider(
text = "Quest completion", // ALWAYS REQUIRED
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 valuepriority
- Ordering number, highest value is placed top mosticonName
- Icon names can be found from https://fontawesome.com/icons?d=gallery&m=freeiconFamily
- Icon family should match details on the site aboveiconColor
- Color of the icon that appears next to the value
@StringProvider
Speciality: String values, Links to player page when playerName
is true
Usage & Parameters
@StringProvider(
text = "Town Name", // ALWAYS REQUIRED
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 valuepriority
- Ordering number, highest value is placed top mosticonName
- Icon names can be found from https://fontawesome.com/icons?d=gallery&m=freeiconFamily
- Icon family should match details on the site aboveiconColor
- Color of the icon that appears next to the valueplayerName
- Does this provider return a player name that can be linked to? Links are automatic
Links to player pages
@StringProvider(
...
playerName = true
)
public String townMayor(Group town) {...}
- If
playerName
istrue
, the returned value will be formatted to display a link to a player's page.
@ComponentProvider
Speciality: String values that have chat colors in them (legacy / bungee / minimessage)
Usage & Parameters
@ComponentProvider(
text = "Display name", // ALWAYS REQUIRED
description = "What name is the player using",
priority = 5,
iconName = "question",
iconFamily = Family.SOLID,
iconColor = Color.NONE
)
public Component displayName(UUID playerUUID) {
return ComponentService.getInstance().fromAutoDetermine(... /* Put the string in here */);
}
text
- Text that appears next to the value, for example "Has Island: No"description
- Text that appears when hovering over the valuepriority
- Ordering number, highest value is placed top mosticonName
- Icon names can be found from https://fontawesome.com/icons?d=gallery&m=freeiconFamily
- Icon family should match details on the site aboveiconColor
- Color of the icon that appears next to the value
🔨 Special Provider annotations
These annotations can be used to add information structures. They might have different limitations than other providers.
@GroupProvider
Speciality: Multiple Group
s the player is in. Any providers with Group
parameter will be called with the groups that this method provides (Not implemented yet).
- Requires
DATA_EXTENSION_GROUPS
capability
Limitations and Additional data
- Method requires UUID or String as method parameter.
- Group names over 50 characters will be truncated
Plan will construct following from given group data:
- Players in different groups are counted
Usage & Parameters
@GroupProvider(
text = "Jobs", // ALWAYS REQUIRED
groupColor = Color.NONE
iconName = "question",
iconFamily = Family.SOLID,
)
public String[] playerJobs(UUID playerUUID) {
return new String[]{"Mason", "Woodcutter"}
}
text
- Text that appears next to the value, for example "Jobs: None"groupColor
- Color of the icon or table that appears near the groupsiconName
- Icon names can be found from https://fontawesome.com/icons?d=gallery&m=freeiconFamily
- Icon family should match details on the site above
@TableProvider
Speciality: Table structures.
- Requires
DATA_EXTENSION_TABLES
capability
Limitations
- Player tables can have 4 columns.
- Server tables can have 5 columns.
- Values can only be 50 characters long when
toString()
is applied.
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
(andGroup
parameter methods) instead.
Usage & Parameters
@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
.columnOneFormat(TableColumnFormat.DATE_SECOND) // All columns support formatting similar to other Providers (numbers, strings)
.columnTwo("...", new Icon(...)) // Icon colors are ignored.
.columnThree("...", new Icon(...))
.columnFour("...", new Icon(...));
for (YourData data : yourData) {
banTable.addRow(System.currentTimeMillis(), true, "Reason", "...");
}
return banTable.build();
}
tableColor
- Color of the table header.
@DataBuilderProvider
annotation
Speciality: Dynamic definition of providers at runtime.
- Requires
DATA_EXTENSION_BUILDER_API
capability
Usage
@DataBuilderProvider
public ExtensionDataBuilder lotsOfData(UUID playerUUID) {
ExtensionDataBuilder builder = newExtensionDataBuilder();
...
return builder;
}
@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.
Usage & Parameters
@TabInfo(
tab = "Economy", // REQUIRED
iconName = "circle",
iconFamily = Family.SOLID,
elementOrder = {ElementOrder.VALUES, ElementOrder.TABLE, ElementOrder.GRAPH}
)
@TabInfo(tab = "Second Tab") // REQUIRED
@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 tabiconName
- Icon names can be found from https://fontawesome.com/icons?d=gallery&m=freeiconFamily
- Icon family should match details on the site aboveelementOrder
- Order different elements are placed inside the tab.VALUES
represents all single values
@Conditional
Specialilty: Control execution of another methods. If provided boolean
is true
the method with @Conditional
annotation will be called.
Usage & Parameters
@BooleanProvider(..., conditionName="hasPet")
public boolean hasPet(UUID playerUUID) {...}
@Conditional("hasPet")
@StringProvider(text = "Pet Name")
public String petName(UUID playerUUID) {...}
value
- Name of the conditionnegated
- Require the condition to befalse
for this method to be called.
Reversed Conditions
@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
.
Nested Conditions (Conjunction / AND)
@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.
@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.
Usage & Parameters
@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.
❕ 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 ofboolean
) - 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 ifdescription
) - 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
ExtensionFactory
classes are not important, since they are used by Plan for creating the DataExtension
instances when Plan enables. The registration is not automatic.
Documentation
Tutorials
Configuration
- Game server Set-up
- Network Set-up
- Game server Configuration
- Network Configuration
- Login / HTTPS Set-Up
- Commands & Permissions
- Database management
- PlaceholderAPI