de.cubeisland.engine.module.stats.StatsManager.java Source code

Java tutorial

Introduction

Here is the source code for de.cubeisland.engine.module.stats.StatsManager.java

Source

/**
 * This file is part of CubeEngine.
 * CubeEngine is licensed under the GNU General Public License Version 3.
 *
 * CubeEngine is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * CubeEngine is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with CubeEngine.  If not, see <http://www.gnu.org/licenses/>.
 */
package de.cubeisland.engine.module.stats;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import de.cubeisland.engine.converter.ConversionException;
import de.cubeisland.engine.converter.ConverterManager;
import de.cubeisland.engine.core.module.Module;
import de.cubeisland.engine.core.storage.database.Database;
import de.cubeisland.engine.core.task.TaskManager;
import de.cubeisland.engine.logscribe.Log;
import de.cubeisland.engine.module.stats.annotations.Configured;
import de.cubeisland.engine.module.stats.annotations.Scheduled;
import de.cubeisland.engine.module.stats.configuration.DynamicSection;
import de.cubeisland.engine.module.stats.stat.Stat;
import de.cubeisland.engine.module.stats.storage.StatsModel;
import de.cubeisland.engine.module.stats.storage.TableStats;
import de.cubeisland.engine.module.stats.storage.TableStatsData;
import de.cubeisland.engine.reflect.annotations.Comment;
import de.cubeisland.engine.reflect.annotations.Name;
import org.jooq.DSLContext;

import static de.cubeisland.engine.module.stats.storage.TableStats.TABLE_STATS;
import static de.cubeisland.engine.module.stats.storage.TableStatsData.TABLE_STATSDATA;

public class StatsManager {
    private final Stats module;
    private final DSLContext dsl;
    private final Log log;
    private final ConverterManager converterManager;

    private final ObjectMapper jsonMapper;
    private final Map<Class<? extends Stat>, Stat> stats;
    private final Set<Class<? extends Stat>> registeredStats;
    private final Map<Class<? extends Stat>, Set<Integer>> tasks;

    private boolean started = false;

    public StatsManager(Stats stats, ConverterManager converterManager) {
        this.module = stats;
        this.log = stats.getLog();
        this.converterManager = converterManager;

        final Database database = stats.getCore().getDB();
        this.dsl = database.getDSL();
        database.registerTable(TableStats.class);
        database.registerTable(TableStatsData.class);

        this.jsonMapper = new ObjectMapper();
        this.stats = new HashMap<>();
        this.registeredStats = new HashSet<>();
        this.tasks = new HashMap<>();

        for (StatsModel stat : dsl.selectFrom(TableStats.TABLE_STATS).fetch()) {
            final String className = stat.getValue(TABLE_STATS.STAT);
            try {
                final Class statClass = Class.forName(className);
                if (Stat.class.isAssignableFrom(statClass)) {
                    registeredStats.add((Class<? extends Stat>) statClass);
                } else {
                    stats.getLog().warn("The statistic class {} is not compatible anymore.", className);
                }
            } catch (ClassNotFoundException e) {
                stats.getLog().warn(e, "The previously existing statistic class {} could not be loaded.",
                        className);
            }
        }

        this.started = true;
    }

    /**
     * Register a statistic.
     * This will make this StatsManager handle the statistic's database connection
     * and register it as a listener.
     *
     * @param statType The stat class to register
     */
    public void register(Class<? extends Stat> statType) {
        if (!this.started) {
            throw new IllegalStateException("StatsManager not started!");
        }

        try {
            // Get the Constructor, and construct a new instance of the Stat.
            Constructor<? extends Stat> constructor = statType.getConstructor();
            Stat stat = constructor.newInstance();
            Method init = statType.getMethod("init", this.getClass(), Module.class);
            init.invoke(stat, this, this.module);

            // Get or register the stat in the database
            if (!registeredStats.contains(statType)) {
                synchronized (this.dsl) {
                    StatsModel model = this.dsl.newRecord(TABLE_STATS).newStatsModel(statType.getName());
                    model.insert();
                    registeredStats.add(statType);
                }
            }
            this.stats.put(statType, stat);

            // Load configured fields

            for (Field field : statType.getFields()) {
                if (!field.isAnnotationPresent(Configured.class)) {
                    return;
                }

                String name = field.getName();
                String[] comment = {};
                if (field.isAnnotationPresent(Name.class)) {
                    Name nameAnnotation = field.getAnnotation(Name.class);
                    name = nameAnnotation.value();
                }
                if (field.isAnnotationPresent(Comment.class)) {
                    Comment commentAnnotation = field.getAnnotation(Comment.class);
                    comment = commentAnnotation.value();
                }

                if (!module.getConfig().statConfigs.containsKey(statType.getSimpleName())) {
                    module.getConfig().statConfigs.put(statType.getSimpleName(),
                            new DynamicSection(converterManager));
                }
                DynamicSection section = module.getConfig().statConfigs.get(statType.getSimpleName());

                if (section.hasKey(name)) {
                    section.getNode(name).setComments(comment);
                    field.set(stat, section.get(name));
                } else {
                    section.put(name, field.get(stat), comment);
                }
            }

            // Activate hook in the stat
            stat.activate();

            // Register Schedulers
            for (Method method : stat.getClass().getMethods()) {
                if (!method.isAnnotationPresent(Scheduled.class)) {
                    continue;
                }
                Scheduled annotation = method.getAnnotation(Scheduled.class);

                long interval;
                if (annotation.periodFinal()) {
                    interval = annotation.interval();
                } else {
                    if (!module.getConfig().statConfigs.containsKey(statType.getSimpleName())) {
                        module.getConfig().statConfigs.put(statType.getSimpleName(),
                                new DynamicSection(converterManager));
                    }
                    DynamicSection section = module.getConfig().statConfigs.get(statType.getSimpleName());
                    if (!section.hasKey("tasks")) {
                        section.put("tasks", new DynamicSection(converterManager),
                                "Intervals for the tasks this statistic schedules");
                    } else {
                        section.getNode("tasks")
                                .setComments(new String[] { "Intervals for the tasks this statistic schedules" });
                    }
                    DynamicSection tasks = (DynamicSection) section.get("tasks", DynamicSection.class);
                    if (!tasks.hasKey(annotation.name())) {
                        tasks.put(annotation.name(), annotation.interval(), annotation.comment());
                    } else {
                        tasks.getNode(annotation.name()).setComments(annotation.comment());
                    }
                    interval = (Long) tasks.get(annotation.name(), Long.class);
                }

                if (!this.tasks.containsKey(statType)) {
                    this.tasks.put(statType, new HashSet<Integer>());
                }
                Set<Integer> tasks = this.tasks.get(statType);
                Runnable wrapper = new ScheduledMethod(this.module.getLog(), stat, method);
                TaskManager taskManager = module.getCore().getTaskManager();
                if (annotation.async()) {
                    tasks.add(taskManager.runAsynchronousTimer(module, wrapper, 1, interval));
                } else {
                    tasks.add(taskManager.runTimer(module, wrapper, 1, interval));
                }

                this.module.getLog().debug("Scheduled method {} at interval {}", annotation.name(), interval);
            }
        } catch (ReflectiveOperationException | ConversionException ex) {
            this.module.getLog().error(ex, "An error occurred while registering statistic");
        }
        this.module.getLog().debug("Registered statistic {}.", statType.getSimpleName());
    }

    public void disableStat(Class<? extends Stat> stat) {
        if (!this.started) {
            throw new IllegalStateException("StatsManager not started!");
        }
        if (this.tasks.containsKey(stat)) {
            for (Integer id : tasks.get(stat)) {
                this.module.getCore().getTaskManager().cancelTask(this.module, id);
            }
        }
        this.stats.get(stat).deactivate();
    }

    /**
     * Save an object to the database
     *
     * The object must be able to be parsed through ObjectMapper.writeValue()
     *
     * @param stat The stat that owns this data
     * @param object The data to save
     */
    public void save(Stat stat, Object object) {
        if (!this.started) {
            throw new IllegalStateException("StatsManager not started!");
        }
        synchronized (this.dsl) {
            try {
                String data = this.jsonMapper.writeValueAsString(object);
                int result = this.dsl.insertInto(TABLE_STATSDATA)
                        .set(TABLE_STATSDATA.STAT,
                                dsl.select(TABLE_STATS.KEY).from(TABLE_STATS)
                                        .where(TABLE_STATS.STAT.eq(stat.getClass().getName())).getQuery())
                        .set(TABLE_STATSDATA.DATA, data).execute();
                if (result != 1) {
                    log.warn("Failed to insert a row in the data table for statistics!");
                    log.debug("Result int from jOOQ was {}", result);
                }
            } catch (JsonProcessingException ex) {
                log.warn(ex, "An error occurred while parsing an object to JSON.");
            }
        }
    }

    public void shutdown() {
        for (Class<? extends Stat> stat : this.registeredStats) {
            this.disableStat(stat);
        }
        this.started = false;
    }

    private static final class ScheduledMethod implements Runnable {
        private final Log logger;
        private final Object object;
        private final Method method;

        ScheduledMethod(Log logger, Object object, Method method) {
            this.logger = logger;
            this.object = object;
            this.method = method;
        }

        public void run() {
            try {
                method.invoke(object);
            } catch (IllegalAccessException | InvocationTargetException ex) {
                logger.warn(ex, "An error occurred while invoking a scheduled method");
            }
        }
    }
}