All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.endoscope.storage.jdbc.AggregatedJdbcStorage Maven / Gradle / Ivy

package com.github.endoscope.storage.jdbc;

import com.github.endoscope.core.Stats;
import com.github.endoscope.storage.Filters;
import com.github.endoscope.storage.StatDetails;
import com.github.endoscope.storage.Storage;
import org.apache.commons.lang3.time.DateUtils;
import org.slf4j.Logger;

import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.TimeZone;

import static java.util.Arrays.asList;
import static org.apache.commons.lang3.time.DateUtils.addSeconds;
import static org.apache.commons.lang3.time.DateUtils.ceiling;
import static org.apache.commons.lang3.time.DateUtils.truncate;
import static org.slf4j.LoggerFactory.getLogger;

public class AggregatedJdbcStorage implements Storage {
    private static final Logger log = getLogger(AggregatedJdbcStorage.class);

    private static final long MINUTE_LENGTH_MS = 60 * 1000;
    private static final long DAY_LENGTH_MS = 24 * 60 * MINUTE_LENGTH_MS;
    private static final long WEEK_LENGTH_MS = 7 * DAY_LENGTH_MS;
    private static final long MONTH_LENGTH_MS = 30 * DAY_LENGTH_MS;

    private JdbcStorage defaultStorage;
    private JdbcStorage dailyStorage;
    private JdbcStorage weeklyStorage;
    private JdbcStorage monthlyStorage;

    private boolean aggregateOnly = false;

    public AggregatedJdbcStorage(){}

    public AggregatedJdbcStorage(JdbcStorage defaultStorage, JdbcStorage dailyStorage, JdbcStorage weeklyStorage, JdbcStorage monthlyStorage) {
        this.defaultStorage = defaultStorage;
        this.dailyStorage = dailyStorage;
        this.weeklyStorage = weeklyStorage;
        this.monthlyStorage = monthlyStorage;
    }

    /**
     * When true then saves aggregated stats only. For example when you want to create aggregated stats for already
     * existing stats.
     * @return
     */
    public boolean isAggregateOnly() {
        return aggregateOnly;
    }

    public void setAggregateOnly(boolean aggregateOnly) {
        this.aggregateOnly = aggregateOnly;
    }

    @Override
    public void setup(String initParam) {
        defaultStorage  = new JdbcStorage().setTablePrefix("");
        defaultStorage.setup(initParam);

        dailyStorage    = new JdbcStorage().setTablePrefix("day_");
        weeklyStorage   = new JdbcStorage().setTablePrefix("week_");
        monthlyStorage  = new JdbcStorage().setTablePrefix("month_");

        //save DB connection resources and re-use the same DS for aggregated storages
        asList( dailyStorage, weeklyStorage, monthlyStorage)
                .forEach( s -> s.setRun( defaultStorage.getRun() ) );
    }

    @Override
    public String save(Stats stats, String instance, String type) {
        validateStartDate(stats);

        String result = null;
        if( !isAggregateOnly()){
            result = defaultStorage.save(stats, instance, type);
        }

        //We group stats per type for better performance - hence we loose instance information in following stats
        updateAggregated(dailyStorage, Calendar.DAY_OF_MONTH, stats, type);
        updateAggregated(weeklyStorage, Calendar.WEEK_OF_YEAR, stats, type);
        updateAggregated(monthlyStorage, Calendar.MONTH, stats, type);

        return result;
    }

    @Override
    public String replace(String statsId, Stats stats, String instance, String type) {
        throw new RuntimeException("Operation not supported");//there is no way to un-merge stats
    }

    private Calendar cal(Date d){
        Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
        calendar.setTime(d);
        return calendar;
    }

    private void updateAggregated(Storage storage, int timeUnit, Stats stats, String type) {
        Date start, end;
        if(timeUnit == Calendar.WEEK_OF_YEAR){
            Calendar c = cal(stats.getStartDate());
            c.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY);
            start = truncate(c, Calendar.DAY_OF_MONTH).getTime();
            end = addSeconds(DateUtils.addDays(start, 7), -1);//a second before next stats
        } else {
            start = truncate(cal(stats.getStartDate()), timeUnit).getTime();
            end   = addSeconds(ceiling(cal(stats.getStartDate()), timeUnit).getTime(), -1);//a second before next stats
        }

        //search second before and second after to avoid round problems
        List ids = storage.find(addSeconds(start, -1), addSeconds(end, 1), null, type);
        String replaceId = null;
        Stats aggregated;
        if( ids.isEmpty() ){
            aggregated = new Stats();
            aggregated.setStartDate( start );
            aggregated.setEndDate( end );
        } else {
            replaceId = ids.get(0);
            aggregated = storage.load(replaceId);
        }
        aggregated.merge(stats, true);

        //Possible race condition when multiple threads try to update aggregated stats at the same time
        storage.replace(replaceId, aggregated, null, type);
    }

    private void validateStartDate(Stats stats) {
        if( stats.getStartDate() == null ){
            throw new IllegalStateException("Start date cannot be null");
        }
    }

    @Override
    public Stats load(String groupId) {
        return defaultStorage.load(groupId);
    }

    private Storage chooseStorage(Date from, Date to){
        long periodLength = to.getTime() - from.getTime() + MINUTE_LENGTH_MS;

        if( periodLength < DAY_LENGTH_MS ){
            return defaultStorage;
        }
        if( periodLength < WEEK_LENGTH_MS ){
            return dailyStorage;
        }
        if( periodLength < MONTH_LENGTH_MS ){
            return weeklyStorage;
        }
        return monthlyStorage;
    }

    /**
     * In aggregated stats we don't keep this information.
     * We need to reset it in order to find values in that storage.
     *
     * @param storage
     * @param instance
     * @return
     */
    private String fixInstance(Storage storage, String instance){
        return defaultStorage == storage ? instance : null;
    }

    @Override
    public List find(Date from, Date to, String instance, String type) {
        Storage storage = chooseStorage(from, to);
        instance = fixInstance(storage, instance);
        return storage.find(from, to, instance, type);
    }

    @Override
    public Filters findFilters(Date from, Date to, String type) {
        Storage storage = chooseStorage(from, to);
        return storage.findFilters(from, to, type);
    }

    @Override
    public StatDetails loadDetails(String detailsId, List groupIds) {
        throw new RuntimeException("Operation not supported");//there is no way to identify storage without time period
    }

    @Override
    public StatDetails loadDetails(String detailsId, Date from, Date to, String instance, String type) {
        Storage storage = chooseStorage(from, to);
        instance = fixInstance(storage, instance);
        return storage.loadDetails(detailsId, from, to, instance, type);
    }

    @Override
    public Stats loadAggregated(boolean topLevelOnly, Date from, Date to, String instance, String type) {
        Storage storage = chooseStorage(from, to);
        instance = fixInstance(storage, instance);
        return storage.loadAggregated(topLevelOnly, from, to, instance, type);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy