org.ulyssis.ipp.snapshot.Event.java Source code

Java tutorial

Introduction

Here is the source code for org.ulyssis.ipp.snapshot.Event.java

Source

/*
 * Copyright (C) 2014-2015 ULYSSIS VZW
 *
 * This file is part of i++.
 * 
 * i++ is free software: you can redistribute it and/or modify
 * it under the terms of version 3 of the GNU Affero General Public License
 * as published by the Free Software Foundation. No other versions apply.
 * 
 * This program 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 Affero General Public License for more details.
 * 
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>
 */
package org.ulyssis.ipp.snapshot;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.core.JsonProcessingException;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.ulyssis.ipp.utils.Serialization;

import java.io.IOException;
import java.sql.*;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
@JsonSubTypes({ @JsonSubTypes.Type(value = StartEvent.class), @JsonSubTypes.Type(value = EndEvent.class),
        @JsonSubTypes.Type(value = AddTagEvent.class), @JsonSubTypes.Type(value = RemoveTagEvent.class),
        @JsonSubTypes.Type(value = CorrectionEvent.class), @JsonSubTypes.Type(value = TagSeenEvent.class),
        @JsonSubTypes.Type(value = IdentityEvent.class), @JsonSubTypes.Type(value = MessageEvent.class),
        @JsonSubTypes.Type(value = StatusChangeEvent.class) })
public abstract class Event {
    private static final Logger LOG = LogManager.getLogger(Event.class);

    @JsonIgnore
    private long id = -1;
    @JsonIgnore
    private boolean removed = false;

    private Instant time;

    protected Event(Instant time) {
        this.time = time;
    }

    @JsonIgnore
    public final Optional<Long> getId() {
        if (id != -1)
            return Optional.of(id);
        else
            return Optional.empty();
    }

    @JsonIgnore
    public final boolean isRemoved() {
        if (id == -1)
            throw new IllegalStateException("Trying to request removed state of event not in db");
        return removed;
    }

    public final Instant getTime() {
        return time;
    }

    /**
     * Determines whether this event should be isUnique, defaults to false
     *
     * @return whether this event should be isUnique (default implementation = false)
     */
    @JsonIgnore
    public boolean isUnique() {
        return false;
    }

    /**
     * Determines whether this event can be removed or undone.
     *
     * @return whether this event can be removed or undone (default implementation = isUnique())
     */
    @JsonIgnore
    public boolean isRemovable() {
        return isUnique();
    }

    /**
     * Apply this event to a snapshot, yielding the new snapshot
     */
    protected abstract Snapshot doApply(Snapshot before);

    public final Snapshot apply(Snapshot before) {
        assert !removed;
        Snapshot result = doApply(before);
        if (result == before && before.getEventId().isPresent()) {
            // We need to copy it anyway
            result = Snapshot.builder(getTime(), before).build();
        }
        result.eventId = this.id;
        return result;
    }

    // TODO: How to deal with deserialization problem?
    public static List<Event> loadAll(Connection connection) throws SQLException, IOException {
        String statement = "SELECT \"id\", \"data\", \"removed\" FROM \"events\" ORDER BY \"time\" ASC, \"id\" ASC";
        List<Event> events = new ArrayList<>();
        try (Statement stmt = connection.createStatement(); ResultSet result = stmt.executeQuery(statement)) {
            while (result.next()) {
                String evString = result.getString("data");
                Event event = Serialization.getJsonMapper().readValue(evString, Event.class);
                event.id = result.getLong("id");
                event.removed = result.getBoolean("removed");
                events.add(event);
            }
        }
        return events;
    }

    public static Optional<Event> loadUnique(Connection connection, Class<? extends Event> eventType)
            throws SQLException, IOException {
        String statement = "SELECT \"id\", \"data\" FROM \"events\" WHERE \"type\" = ? AND \"removed\" = false";
        try (PreparedStatement stmt = connection.prepareStatement(statement)) {
            stmt.setString(1, eventType.getSimpleName());
            ResultSet result = stmt.executeQuery();
            if (result.next()) {
                String evString = result.getString("data");
                Event event = Serialization.getJsonMapper().readValue(evString, Event.class);
                event.id = result.getLong("id");
                event.removed = false;
                return Optional.of(event);
            } else {
                return Optional.empty();
            }
        }
    }

    public static Optional<Event> load(Connection connection, long id) throws SQLException, IOException {
        try (PreparedStatement statement = connection
                .prepareStatement("SELECT \"data\",\"removed\" FROM \"events\" WHERE \"id\"=?")) {
            statement.setLong(1, id);
            ResultSet result = statement.executeQuery();
            if (result.next()) {
                String evString = result.getString("data");
                Event event = Serialization.getJsonMapper().readValue(evString, Event.class);
                event.id = id;
                event.removed = result.getBoolean("removed");
                return Optional.of(event);
            } else {
                return Optional.empty();
            }
        }
    }

    public static List<Event> loadFrom(Connection connection, Instant time) throws SQLException, IOException {
        String statement = "SELECT \"id\",\"data\",\"removed\" FROM \"events\" "
                + "WHERE \"time\" >= ? ORDER BY \"time\" ASC, \"id\" ASC";
        List<Event> events = new ArrayList<>();
        try (PreparedStatement stmt = connection.prepareStatement(statement)) {
            stmt.setTimestamp(1, Timestamp.from(time));
            ResultSet rs = stmt.executeQuery();
            while (rs.next()) {
                String evString = rs.getString("data");
                Event event = Serialization.getJsonMapper().readValue(evString, Event.class);
                event.id = rs.getLong("id");
                event.removed = rs.getBoolean("removed");
                events.add(event);
            }
        }
        return events;
    }

    public static List<Event> loadAfter(Connection connection, Instant time, long id)
            throws SQLException, IOException {
        String statement = "SELECT \"id\",\"data\",\"removed\" FROM \"events\" "
                + "WHERE \"time\" > ? OR (\"time\" = ? AND \"id\" > ?) ORDER BY \"time\" ASC, \"id\" ASC";
        List<Event> events = new ArrayList<>();
        try (PreparedStatement stmt = connection.prepareStatement(statement)) {
            stmt.setTimestamp(1, Timestamp.from(time));
            stmt.setTimestamp(2, Timestamp.from(time));
            stmt.setLong(3, id);
            LOG.debug("Executing query: {}", stmt);
            ResultSet rs = stmt.executeQuery();
            while (rs.next()) {
                String evString = rs.getString("data");
                Event event = Serialization.getJsonMapper().readValue(evString, Event.class);
                event.id = rs.getLong("id");
                event.removed = rs.getBoolean("removed");
                events.add(event);
            }
        }
        LOG.debug("Loaded {} events", events.size());
        return events;
    }

    public void save(Connection connection) throws SQLException {
        if (id != -1)
            return;
        try (PreparedStatement statement = connection.prepareStatement(
                "INSERT INTO \"events\" (\"time\",\"type\",\"data\",\"removed\") " + "VALUES (?,?,?,?)",
                Statement.RETURN_GENERATED_KEYS)) {
            statement.setTimestamp(1, Timestamp.from(time));
            String serialized;
            try {
                serialized = Serialization.getJsonMapper().writeValueAsString(this);
            } catch (JsonProcessingException e) {
                assert false;
                throw new IllegalStateException(e); // TODO(Roel): is this appropriate?
            }
            statement.setString(2, this.getClass().getSimpleName());
            statement.setString(3, serialized);
            statement.setBoolean(4, removed);
            statement.executeUpdate();
            ResultSet keys = statement.getGeneratedKeys();
            keys.next();
            this.id = keys.getLong(1);
        }
    }

    public void setRemoved(Connection connection, boolean removed) throws SQLException {
        if (!isRemovable()) {
            assert false; // This is a programming error
            return;
        }
        PreparedStatement statement = connection
                .prepareStatement("UPDATE \"events\" SET \"removed\"=? WHERE \"id\"=?");
        statement.setBoolean(1, removed);
        statement.setLong(2, id);
        boolean result = statement.execute();
        assert (!result);
        this.removed = true;
    }
}