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

org.yamcs.timeline.TimelineBandDb Maven / Gradle / Ivy

There is a newer version: 5.10.9
Show newest version
package org.yamcs.timeline;

import java.util.Collection;
import java.util.Collections;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import org.yamcs.InitException;
import org.yamcs.logging.Log;
import org.yamcs.timeline.protobuf.BandFilter;
import org.yamcs.utils.parser.ParseException;
import org.yamcs.yarch.DataType;
import org.yamcs.yarch.Stream;
import org.yamcs.yarch.TableColumnDefinition;
import org.yamcs.yarch.Tuple;
import org.yamcs.yarch.TupleDefinition;
import org.yamcs.yarch.YarchDatabase;
import org.yamcs.yarch.YarchDatabaseInstance;
import org.yamcs.yarch.streamsql.ResultListener;
import org.yamcs.yarch.streamsql.StreamSqlException;
import org.yamcs.yarch.streamsql.StreamSqlResult;
import org.yamcs.yarch.streamsql.StreamSqlStatement;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.util.concurrent.UncheckedExecutionException;

public class TimelineBandDb {
    public static final TupleDefinition TIMELINE_DEF = new TupleDefinition();
    public static final String CNAME_ID = "uuid";
    public static final String CNAME_NAME = "name";
    public static final String CNAME_DESCRIPTION = "description";
    public static final String CNAME_TYPE = "type";
    public static final String CNAME_SHARED = "shared";
    public static final String CNAME_USERNAME = "username";
    public static final String CNAME_TAGS = "tags";
    public static final String CNAME_SOURCE = "source";
    public static final String CNAME_FILTER = "filter";

    protected static final String PROP_PREFIX = "prop_";

    static {
        TIMELINE_DEF.addColumn(CNAME_ID, DataType.UUID);
        TIMELINE_DEF.addColumn(CNAME_NAME, DataType.STRING);
        TIMELINE_DEF.addColumn(CNAME_DESCRIPTION, DataType.STRING);
        TIMELINE_DEF.addColumn(CNAME_SHARED, DataType.BOOLEAN);
        TIMELINE_DEF.addColumn(CNAME_USERNAME, DataType.STRING);
        TIMELINE_DEF.addColumn(CNAME_TYPE, DataType.ENUM);
        TIMELINE_DEF.addColumn(CNAME_TAGS, DataType.array(DataType.ENUM));
        TIMELINE_DEF.addColumn(CNAME_SOURCE, DataType.ENUM);
        TIMELINE_DEF.addColumn(CNAME_FILTER, DataType.protobuf(BandFilter.class));
    }

    final Log log;
    final private ReadWriteLock rwlock = new ReentrantReadWriteLock();
    final static String TABLE_NAME = "timeline_band";

    final YarchDatabaseInstance ydb;
    final Stream bandStream;

    LoadingCache bandCache = CacheBuilder.newBuilder()
            .maximumSize(1000)
            .expireAfterWrite(10, TimeUnit.MINUTES)
            .build(new CacheLoader() {
                @Override
                public TimelineBand load(UUID uuid) {
                    return doGetBand(uuid);
                }
            });

    public TimelineBandDb(String yamcsInstance) throws InitException {
        log = new Log(getClass(), yamcsInstance);

        ydb = YarchDatabase.getInstance(yamcsInstance);
        try {
            bandStream = setupTimelineRecording();
        } catch (ParseException | StreamSqlException e) {
            throw new InitException(e);
        }

    }

    public TimelineBand deleteBand(UUID uuid) {
        rwlock.writeLock().lock();
        try {
            TimelineBand band = doGetBand(uuid);
            if (band == null) {
                return null;
            }

            doDeleteBand(uuid);

            return band;
        } finally {
            rwlock.writeLock().unlock();
        }
    }

    private Stream setupTimelineRecording() throws StreamSqlException, ParseException {
        String streamName = TABLE_NAME + "_in";
        if (ydb.getTable(TABLE_NAME) == null) {
            String query = "create table " + TABLE_NAME + "(" + TIMELINE_DEF.getStringDefinition1()
                    + ", primary key(uuid))";
            ydb.execute(query);
        }
        if (ydb.getStream(streamName) == null) {
            ydb.execute("create stream " + streamName + TIMELINE_DEF.getStringDefinition());
        }
        ydb.execute("upsert into " + TABLE_NAME + " select * from " + streamName);
        return ydb.getStream(streamName);
    }

    private TimelineBand doGetBand(UUID uuid) {
        StreamSqlResult r = ydb.executeUnchecked("select * from " + TABLE_NAME + " where uuid = ?", uuid);
        try {
            if (r.hasNext()) {
                Tuple tuple = r.next();
                try {
                    TimelineBand band = new TimelineBand(tuple);
                    log.trace("Read band from db {}", band);
                    return band;
                } catch (Exception e) {
                    log.error("Cannot decode tuple {} to band", tuple);
                }
            }
        } finally {
            r.close();
        }
        throw new NoSuchItemException();
    }

    private void doDeleteBand(UUID uuid) {
        bandCache.invalidate(uuid);
        StreamSqlResult r = ydb.executeUnchecked("delete from " + TABLE_NAME + " where uuid = ?", uuid);
        r.close();
    }

    public Collection getTags() {
        rwlock.readLock().lock();
        try {
            TableColumnDefinition tcd = ydb.getTable(TABLE_NAME).getColumnDefinition(CNAME_TAGS);
            return Collections.unmodifiableSet(tcd.getEnumValues().keySet());
        } finally {
            rwlock.readLock().unlock();
        }
    }

    // returns null if uuid does not exist
    private TimelineBand bandFromCache(UUID uuid) {
        try {
            return bandCache.getUnchecked(uuid);
        } catch (UncheckedExecutionException e) {
            if (e.getCause() instanceof NoSuchItemException) {
                return null;
            } else {
                throw e;
            }
        }
    }

    @SuppressWarnings("serial")
    static class NoSuchItemException extends RuntimeException {
    }

    public TimelineBand addBand(TimelineBand band) {
        rwlock.writeLock().lock();
        try {
            Tuple tuple = band.toTuple();
            log.debug("Adding timeline band to RDB: {}", tuple);
            bandStream.emitTuple(tuple);
            return band;
        } finally {
            rwlock.writeLock().unlock();
        }
    }

    public TimelineBand updateBand(TimelineBand band) {
        rwlock.writeLock().lock();
        try {
            doDeleteBand(band.getId());

            Tuple tuple = band.toTuple();
            log.debug("Updating timeline band in RDB: {}", tuple);
            bandStream.emitTuple(tuple);
            return band;
        } finally {
            rwlock.writeLock().unlock();
        }
    }

    public TimelineBand getBand(UUID uuid) {
        rwlock.readLock().lock();
        try {
            return bandFromCache(uuid);
        } finally {
            rwlock.readLock().unlock();
        }
    }

    public void listBands(String user, BandListener consumer) {
        rwlock.readLock().lock();
        try {
            StreamSqlStatement stmt = ydb.createStatement(
                    "select * from " + TABLE_NAME + " where shared or username = ?", user);

            ydb.execute(stmt, new ResultListener() {
                @Override
                public void next(Tuple tuple) {
                    consumer.next(new TimelineBand(tuple));
                }

                @Override
                public void completeExceptionally(Throwable t) {
                    consumer.completeExceptionally(t);
                }

                @Override
                public void complete() {
                    consumer.complete(null);
                }
            });

        } catch (StreamSqlException | ParseException e) {
            log.error("Exception when executing query", e);
        } finally {
            rwlock.readLock().unlock();
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy