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

org.yamcs.memento.MementoDb Maven / Gradle / Ivy

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

import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import org.yamcs.YamcsServerInstance;
import org.yamcs.management.ManagementListener;
import org.yamcs.management.ManagementService;
import org.yamcs.utils.parser.ParseException;
import org.yamcs.yarch.DataType;
import org.yamcs.yarch.Stream;
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.StreamSqlException;

import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;

/**
 * General purpose facility for persisting state information.
 */
public class MementoDb implements ManagementListener {

    private static final String TABLE_NAME = "memento";
    private static final TupleDefinition TDEF = new TupleDefinition();
    private static final String CNAME_KEY = "key";
    private static final String CNAME_VALUE = "value";
    private static ConcurrentMap dbs = new ConcurrentHashMap<>();
    static {
        TDEF.addColumn(CNAME_KEY, DataType.STRING);

        // the 'value' column is a string type, but we really treat it as if it was a json type
        // (which yarch does not natively support), so it can represent primitives, objects or arrays.
        TDEF.addColumn(CNAME_VALUE, DataType.STRING);

        // TODO Figure out a better way to get rid of outdated DBs.
        // (we don't have a service available here).
        ManagementService.getInstance().addManagementListener(new ManagementListener() {
            @Override
            public void instanceStateChanged(YamcsServerInstance ysi) {
                switch (ysi.state()) {
                case OFFLINE:
                case FAILED:
                    dbs.remove(ysi.getName());
                    break;
                default:
                    // Ignore
                }
            }
        });
    }

    private YarchDatabaseInstance ydb;
    private Stream tableStream;
    private ReadWriteLock rwlock = new ReentrantReadWriteLock();

    private MementoDb(String yamcsInstance) throws StreamSqlException, ParseException {
        ydb = YarchDatabase.getInstance(yamcsInstance);

        var streamName = "memento_in";
        if (ydb.getTable(TABLE_NAME) == null) {
            var query = "create table memento(" + TDEF.getStringDefinition1() + ", primary key(\"key\"))";
            ydb.execute(query);
        }
        if (ydb.getStream(streamName) == null) {
            ydb.execute("create stream " + streamName + TDEF.getStringDefinition());
        }
        ydb.execute("upsert into memento select * from " + streamName);
        tableStream = ydb.getStream(streamName);
    }

    /**
     * Retrieve a {@link MementoDb} for the given Yamcs instance.
     */
    public static MementoDb getInstance(String yamcsInstance) {
        return dbs.computeIfAbsent(yamcsInstance, x -> {
            try {
                return new MementoDb(yamcsInstance);
            } catch (StreamSqlException | ParseException e) {
                throw new RuntimeException(e);
            }
        });
    }

    /**
     * Returns the boolean value of the given key.
     * 

* An empty optional is returned if the key is missing, or the value cannot be read as a boolean. */ public Optional getBoolean(String key) { var value = getJsonValue(key); if (value != null && value.isJsonPrimitive() && value.getAsJsonPrimitive().isBoolean()) { return Optional.of(value.getAsBoolean()); } return Optional.empty(); } /** * Sets the value of the given key to the given boolean value. */ public void putBoolean(String key, Boolean value) { putJsonValue(key, value != null ? new JsonPrimitive(value) : null); } /** * Returns the number value of the given key. *

* An empty optional is returned if the key is missing, or the value cannot be read as a number. */ public Optional getNumber(String key) { var value = getJsonValue(key); if (value != null && value.isJsonPrimitive() && value.getAsJsonPrimitive().isNumber()) { return Optional.of(value.getAsNumber()); } return Optional.empty(); } /** * Sets the value of the given key to the given number value. */ public void putNumber(String key, Number value) { putJsonValue(key, value != null ? new JsonPrimitive(value) : null); } /** * Returns the string value of the given key. *

* An empty optional is returned if the key is missing, or the value cannot be read as a string. */ public Optional getString(String key) { var value = getJsonValue(key); if (value != null && value.isJsonPrimitive() && value.getAsJsonPrimitive().isString()) { return Optional.of(value.getAsString()); } return Optional.empty(); } /** * Sets the value of the given key to the given string value. */ public void putString(String key, String value) { putJsonValue(key, value != null ? new JsonPrimitive(value) : null); } /** * Returns the JsonObject value of the given key. *

* An empty optional is returned if the key is missing, or the value cannot be read as a JsonObject. */ public Optional getJsonObject(String key) { var value = getJsonValue(key); if (value != null && value.isJsonObject()) { return Optional.of(value.getAsJsonObject()); } return Optional.empty(); } /** * Sets the value of the given key to the given JsonObject value. */ public void putJsonObject(String key, JsonObject value) { putJsonValue(key, value); } /** * Returns the JsonArray value of the given key. *

* An empty optional is returned if the key is missing, or the value cannot be read as a JsonArray. */ public Optional getJsonArray(String key) { var value = getJsonValue(key); if (value != null && value.isJsonArray()) { return Optional.of(value.getAsJsonArray()); } return Optional.empty(); } /** * Sets the value of the given key to the given JsonArray value. */ public void putJsonArray(String key, JsonArray value) { putJsonValue(key, value); } /** * Retrieves an object saved as memento. The object is deserialized using GSON. */ public Optional getObject(String key, Class clazz) { var jsonObject = getJsonObject(key); if (jsonObject.isPresent()) { var obj = new Gson().fromJson(jsonObject.get(), clazz); return Optional.of(obj); } else { return Optional.empty(); } } /** * Sets the value of the given key by serializing the provided object using GSON. */ public void putObject(String key, T object) { var jsonObject = new Gson().toJsonTree(object).getAsJsonObject(); putJsonObject(key, jsonObject); } private JsonElement getJsonValue(String key) { rwlock.readLock().lock(); try { var queryResult = ydb.executeUnchecked("select * from memento where \"key\" = ?", key); try { if (queryResult.hasNext()) { var tuple = queryResult.next(); var stringValue = tuple. getColumn(CNAME_VALUE); if (stringValue != null) { return new Gson().fromJson(stringValue, JsonElement.class); } } return null; } finally { queryResult.close(); } } finally { rwlock.readLock().unlock(); } } private void putJsonValue(String key, JsonElement value) { Objects.requireNonNull(key, "key must not be null"); rwlock.writeLock().lock(); try { if (value == null) { var sqlResult = ydb.executeUnchecked("delete from memento where \"key\" = ?", key); sqlResult.close(); } else { var tuple = new Tuple(); tuple.addColumn(CNAME_KEY, key); tuple.addColumn(CNAME_VALUE, value.toString()); tableStream.emitTuple(tuple); } } finally { rwlock.writeLock().unlock(); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy