ServerMods Query API
The ServerMods Query API (package com.quartercode.quarterbukkit.api.query
) is a powerful tool for accessing the Bukkit ServerMods API without having to worry about JSON or parsing.
Let's start with the most abstract classes.
Object Queries
Object queries are queries which return an entire dto (data object) without much effort. Data objects are objects which only store other fields and don't provide any interactivity. You will see, using the object queries is extremely simple.
SearchQuery
The search query can be used to search for BukkitDev projects whose slug contains a given string (just normal search). The slug is the part in the BukkitDev url of a project which specifies the project (if your BukkitDev url is http://dev.bukkit.org/server-mods/quarterbukkit
, quarterbukkit
would be the slug).
A basic example of listing all projects with the slug quarterbukkit
would be:
List<Project> projects = new SearchQuery("quarterbukkit").execute();
If you don't like oneliners, you can also write the same thing on two lines:
SearchQuery query = new SearchQuery("quarterbukkit");
List<Project> projects = query.execute();
That's it! projects
now contains all projects whose slug contains quarterbukkit
. You could iterate over the list and access the properties of the projects by using the following methods:
public int getId()
public String getName()
public String getSlug()
public ProjectStage getStage()
Using the search query is equivalent to using the following server mods api call:
https://api.curseforge.com/servermods/projects?search={slug}
FilesQuery
The files query can be used to list all files of a BukkitDev project which are currently avaiable and not deleted. In order to create a files query, you need to provide the id of the project whose files you want to retrieve.
A basic example of listing all files of a project whose project id is 47006
(QuarterBukkit):
List<ProjectFile> files = new FilesQuery(47006).execute();
Or the longer version:
FilesQuery query = new FilesQuery(47006);
List<ProjectFile> files = query.execute();
The resulting ProjectFile
objects have the following properties:
public int getName()
public ReleaseType getReleaseType()
public URL getLocation()
public String getFileName()
public String getVersion()
Using the search query is equivalent to using the following server mods api call:
https://api.curseforge.com/servermods/files?projectids={projectId}
Parsing the Version
As you may have noticed, we emphasized the version
property somehow. The reason is that the files query doesn't automatically retrieve the version because it simply isn't possible. There is no version field or something like that when uploading a file to BukkitDev. That means that the version field is null
if you don't parse the version yourself.
You can do that by writing a simple VersionParser
which takes an uncompleted ProjectFile
objects without the version property and returns the version that file would have. You can then pass the version parser to the files query which uses its information in order to set the version field.
Here's an example of a simple version parser which uses the file title for parsing the version. As you probably now, the files of QuarterBukkit have a title like QuarterBukkit <version>
(e.g. QuarterBukkit 1.1.2
). The parser takes that title and just removes the QuarterBukkit
in front of it (notice the space after QuarterBukkit
):
List<ProjectFile> files = new FilesQuery(47006, new VersionParser() {
@Override
public String parseVersion(ProjectFile file) {
return file.getName().replace("QuarterBukkit ", "");
}
}).execute();
Or the longer version:
VersionParser versionParser = new VersionParser() { ... };
FilesQuery query = new FilesQuery(47006, versionParser);
List<ProjectFile> files = query.execute();
QueryException
Every one of these queries (also the ServerModsAPIQuery
which is described below) can throw a QueryException
. That exception contains the queried url (e.g. https://api.curseforge.com/servermods/projects?search=quarterbukkit
), the ServerModsAPIQuery
which was used to get the data (actually, the ServerModsAPIQuery
is the only one which throws the exception, but that's another topic) and the type of error which occurred. In some cases, the exception also contains a catched exception which was thrown during the querying process.
There are different types of query exceptions. As mentioned above, the type is set by an enumeration object.
Type | Description | Exception |
---|---|---|
MALFORMED_URL | The request url isn't in the correct format. | MalformedURLException |
CANNOT_OPEN_CONNECTION | Can't open a connection to the request url. | IOException |
CANNOT_READ_RESPONSE | Can't read the response the server mods api may return. | IOException |
INVALID_API_KEY | The api key provided in QuarterBukkit's configuration was rejected by the server mods api. | IOException |
INVALID_RESPONSE | The response of the server mods api isn't a valid JSON array. | - |
JSON Query
The object queries internally use something called the ServerModsAPIQuery
. That is a query object that doesn't take any clear parameters and doesn't return a nice data object. Instead, it takes a query string and returns a JSON array.
The ServerModsAPIQuery
is just there for providing an implementation the object queries can use. If it's possible, you should really try to use the object queries. However, one use case for the query would be a newly added api endpoint. It takes a while until a new object query is added, and you could use the "raw" ServerModsAPIQuery
until then.
Query String
The first thing is the query string. That's just the string you would append to https://api.curseforge.com/servermods/
in order to send a GET request to the service. For example, if you would want to search for projects without using the SearchQuery (don't know why you would do that), you would call the url:
https://api.curseforge.com/servermods/projects?search={search}
In that case, the query string would be:
projects?search={search}
JSON Array Response
For the query projects?search=minecartrevolution
, the service would respond with something like:
[
{
"id":50146,
"name":"MinecartRevolutionTags",
"slug":"minecartrevolutiontags",
"stage":"alpha"
},
{
"id":36965,
"name":"MinecartRevolution",
"slug":"minecartrevolution",
"stage":"release"
}
]
That's actually a JSON array. The square brackets define the start and the end of the array, the ,
in the middle starts a new entry, and the curly brackets define an object. As you can see, every "project" object contains an id, a name a slug and a (project) stage. For example, the search query just takes these objects, creates Project
objects out of them and returns all of these newly created objects in a list.
Using the ServerModsAPIQuery
Using the ServerModsAPIQuery
is fairly straightforward. You just create a query object and invoke the execute()
method in order to get a JSONArray
. You could then iterate over that array and print out every entry, for example:
JSONArray result = new ServerModsAPIQuery("projects?search=minecartrevolution").execute();
for (Object entry : result) {
if (entry instanceof JSONObject) {
System.out.println(((JSONObject) entry).get("name"));
}
}
Examples
Updater
You can implement a very flexible updater using the SeverMods Query API. It is so flexible because you don't need to extend any superclass or something like that, you just call some methods which do some magical things in the background :). In my opinion, it's the best updater you can get because you have full control over what happens while using an elegant and simple api.
Here's a simple updater which just downloads a jar file and reloads the plugin. Note that we use the FileUtils class which contains utility methods for download, copy, delete and unzip:
public boolean checkAndUpdate() {
try {
// Get latest version
List<ProjectFile> avaiableFiles = new FilesQuery(PROJECT_ID, new VersionParser() { ... }).execute();
if (avaiableFiles.size() == 0) {
// No file avaiable
return false;
}
ProjectFile latestFile = avaiableFiles.get(avaiableFiles.size() - 1);
if (latestFile.getVersion().equals(plugin.getDescription().getVersion())) {
// No update required (latest version already installed)
return false;
}
// Set download variables
File pluginJar = new File(plugin.getDataFolder().getParentFile(), File.separator + plugin.getName() + ".jar");
// Disable plugin
Bukkit.getPluginManager().disablePlugin(plugin);
// Download jar
FileUtils.download(latestFile.getLocation().toURL(), pluginJar);
// Load plugin from file
try {
Bukkit.getPluginManager().enablePlugin(Bukkit.getPluginManager().loadPlugin(pluginJar));
}
catch (Exception e) {
ExceptionHandler.exception(new InstallException(plugin, e, "Error while reloading the plugin with the new jar"));
return false;
}
return true;
}
catch (QueryException e) {
ExceptionHandler.exception(new InstallException(plugin, e, "Error while querying the server mods api: " + e.getType()));
}
catch (IOException e) {
ExceptionHandler.exception(new InstallException(plugin, e, "Error while doing some file operations"));
}
return false;
}