Skip to content
This repository has been archived by the owner on Nov 23, 2020. It is now read-only.

Using the Object Cache

Jonah edited this page Sep 25, 2018 · 1 revision

Object Cache

To get started with an Object Cache, you first need to make your own object to store data in: (or just implement the ObjectCacheable interface provided by Payload)

For this example, I'm going to be using a simple Faction object.

import com.jonahseguin.payload.object.obj.ObjectCacheable;
import org.bson.types.ObjectId;
import org.mongodb.morphia.annotations.Entity;
import org.mongodb.morphia.annotations.Id;

@Entity("factions")
public class Faction implements ObjectCacheable {

    @Id private ObjectId id = new ObjectId();
    private String name;

    public Faction(String name) {
        this.name = name;
    }

    @Override
    public String getIdentifier() {
        return this.name; 
        // For factions we will use the faction's name as the identifier, because no two factions
        // can have the same name.
        // For other objects you would probably want to use an ID or other unique field.
        // Objects are stored by their identifier, so whatever you want to look up the object by,
        // make that the identifier.
    }

    @Override
    public boolean persist() {
        return true; // Persist this!
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public ObjectId getId() {
        return id;
    }
}

It is very important that you add the @Entity("collectionName") annotation to your object. Also, fields that you wish to persist cannot be final. We use Morphia for Object Mapping (ORM). Visit their GitHub/website for more information on how to set up your objects.

The next step is to set up your Object Cache instance.

First, we will need to provide our database credentials and instances into a CacheDatabase object. Of course, you should use your own credentials. (don't write them in plain-text! use a config!).

import com.jonahseguin.payload.common.cache.CacheDatabase;
import com.mongodb.MongoClient;
import com.mongodb.client.MongoDatabase;
import org.mongodb.morphia.Datastore;
import org.mongodb.morphia.Morphia;
import redis.clients.jedis.Jedis;

import org.bukkit.plugin.java.JavaPlugin;

public class FactionManager {

    public FactionManager(JavaPlugin plugin) {
        MongoClient mongoClient = new MongoClient("localhost");
        MongoDatabase database = mongoClient.getDatabase("myDatabase");
        Morphia morphia = new Morphia();
        Jedis jedis = new Jedis("localhost");
        morphia.mapPackage("com.jonahseguin.payloadtest"); // Maps all my objects in this package
        Datastore datastore = morphia.createDatastore(mongoClient, "myDatabase");

        CacheDatabase cacheDatabase = new CacheDatabase(mongoClient, database, jedis, morphia, datastore); // Our cache database we will provide to Payload!


    }
}

Next, we will go about the creation of our PayloadObjectCache, using the provided builder:

this.factionCache = new ObjectCacheBuilder<>(plugin, Faction.class)
                .withCreateOnNull(false)
                .withDatabase(cacheDatabase)
                .withDebugger(new MyDebugger())
                .withRedisKey("factions")
                .withRedis(true)
                .withSaveAfterLoad(false)
                .withMongo(true)
                .withMongoIdentifier("name") // our 'name' field in our Faction object is our identifier
                .withObjectInstantiator(new ObjectInstantiator<Faction>() {
                    @Override
                    public Faction instantiate(String s) {
                        return new Faction(s);
                    }

                    @Override
                    public Faction instantiate() {
                        return new Faction("defaultName"); // this name will be changed by Morphia
                    }
                })
                .build();

You're probably wondering about the MyDebugger class. You need to pass your own Debugger for Payload to use to handle errors, exceptions, and debug messages. Simply implement the CacheDebugger class:

I highly recommend using the Sentry.io error capturing library for all your projects!, however you can handle the errors however you want (in-game stack traces, etc.) Don't suppress your errors, or debugging will be a nightmare!

import com.jonahseguin.payload.common.cache.CacheDebugger;
import io.sentry.Sentry;
import io.sentry.event.Event;
import io.sentry.event.EventBuilder;
import io.sentry.event.interfaces.ExceptionInterface;

import org.bukkit.Bukkit;

public class MyDebugger implements CacheDebugger {

    @Override
    public void debug(String s) {
        Sentry.capture(s);
    }

    @Override
    public void error(Exception e) {
        Sentry.capture(e);
    }

    @Override
    public void error(String s) {
        Sentry.capture(s);
    }

    @Override
    public void error(Exception e, String s) {
        capture(e, s);
    }

    @Override
    public boolean onStartupFailure() {
        Sentry.capture("Startup failed for Payload cache!");
        return true; // Return whether or not to shut down the cache on failure (True = shutdown)
    }

    public static void capture(String message) {
        // using Sentry.io for error capturing
        Bukkit.getServer().getLogger().warning("[Error] " + message);
        Sentry.capture(
                new EventBuilder()
                        .withMessage(message)
                        .withLevel(Event.Level.ERROR)
        );
    }

    public static void capture(Throwable throwable, String message) {
        // using Sentry.io for error capturing
        Bukkit.getServer().getLogger().warning("[Error] " + message);
        Sentry.capture(
                new EventBuilder()
                        .withMessage(message)
                        .withLevel(Event.Level.ERROR)
                        .withSentryInterface(new ExceptionInterface(throwable))
        );
    }

Don't forget to initialize your cache at startup!

this.factionCache.init(); // Do not forget this part!  Initialize your cache!

And make sure to call shutdown() during your plugin's onDisable()

public void onDisable() {
    // Call this after you have saved everything.
    this.factionManager.getCache().shutdown();
}

Final Code

And now you're ready to start using your object cache!

Examples:

Get an object: such as a Faction

Faction faction = factionObjectCache.get("factionName");

Get an object from a specific layer

factionObjectCache.getFrom(OLayerType.REDIS, "factionName");

Remove an object from the local cache

factionObjectCache.uncache(faction);
// or
factionObjectCache.uncache("factionName");

Save an object

factionObjectCache.saveEverywhere(faction);

Delete an object from everywhere (Local + Redis + Mongo)

factionObjectCache.deleteEverywhere("factionName");

You can even get or delete an object from a specific layer

factionObjectCache.getLayerController().getLocalLayer().provide("factionName");

// or delete:
factionObjectCache.getLayerController().getLocalLayer().remove("factionName");

// from any layer
factionObjectCache.getLayerController().getRedisLayer()...
factionObjectCache.getLayerController().getMongoLayer()...

You can even access the controller for each cached object to see how they were cached, or manually load the object

OLayerType cacheSource = factionObjectCache.getController("factionName").getLoadedFrom(); // Local / Redis / Mongo

// or manually load it / cache it
Faction faction = factionObjectCache.getController("factionName").cache(); // If no controller exists, one is automatically created
if (faction == null) {
    // doesn't exist; create the faction [there is also a setting to allow Payload to do this automatically: 'createOnNull']
    faction = new Faction("factionName");
    factionObjectCache.saveEverywhere(faction);
}