package org.yamcs.web.rest.archive;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.yamcs.YamcsServer;
import org.yamcs.api.EventProducer;
import org.yamcs.api.EventProducerFactory;
import org.yamcs.api.MediaType;
import org.yamcs.archive.EventRecorder;
import org.yamcs.protobuf.Rest.ListEventsResponse;
import org.yamcs.protobuf.SchemaRest;
import org.yamcs.protobuf.SchemaYamcs;
import org.yamcs.protobuf.Yamcs.Event;
import org.yamcs.utils.TimeEncoding;
import org.yamcs.web.BadRequestException;
import org.yamcs.web.HttpException;
import org.yamcs.web.InternalServerErrorException;
import org.yamcs.web.rest.RestHandler;
import org.yamcs.web.rest.RestRequest;
import org.yamcs.web.rest.RestRequest.IntervalResult;
import org.yamcs.web.rest.RestStreamSubscriber;
import org.yamcs.web.rest.RestStreams;
import org.yamcs.web.rest.Route;
import org.yamcs.web.rest.SqlBuilder;
import org.yamcs.yarch.Stream;
import org.yamcs.yarch.TableDefinition;
import org.yamcs.yarch.Tuple;
import org.yamcs.yarch.YarchDatabase;
import com.csvreader.CsvWriter;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufOutputStream;
public class ArchiveEventRestHandler extends RestHandler {
private static final Logger log = LoggerFactory.getLogger(ArchiveEventRestHandler.class);
Map<String, EventProducer> eventProducerMap = new HashMap<>();
@Route(path = "/api/archive/:instance/events", method = "GET")
public void listEvents(RestRequest req) throws HttpException {
String instance = verifyInstance(req, req.getRouteParam("instance"));
verifyEventArchiveSupport(instance);
long pos = req.getQueryParameterAsLong("pos", 0);
int limit = req.getQueryParameterAsInt("limit", 100);
Set<String> sourceSet = new HashSet<>();
for (String names : req.getQueryParameterList("source", Collections.emptyList())) {
for (String name : names.split(",")) {
sourceSet.add(name);
}
}
SqlBuilder sqlb = new SqlBuilder(EventRecorder.TABLE_NAME);
IntervalResult ir = req.scanForInterval();
if (ir.hasInterval()) {
sqlb.where(ir.asSqlCondition("gentime"));
}
if (!sourceSet.isEmpty()) {
sqlb.where("source in ('" + String.join("','", sourceSet) + "')");
}
sqlb.descend(req.asksDescending(true));
String sql = sqlb.toString();
if (req.asksFor(MediaType.CSV)) {
ByteBuf buf = req.getChannelHandlerContext().alloc().buffer();
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new ByteBufOutputStream(buf)));
CsvWriter w = new CsvWriter(bw, '\t');
try {
w.writeRecord(ArchiveHelper.EVENT_CSV_HEADER);
} catch (IOException e) {
throw new InternalServerErrorException(e);
}
RestStreams.stream(instance, sql, new RestStreamSubscriber(pos, limit) {
@Override
public void processTuple(Stream stream, Tuple tuple) {
try {
w.writeRecord(ArchiveHelper.tupleToCSVEvent(tuple));
} catch (IOException e) {
// TODO maybe support passing up as rest exception using custom listeners
log.error("Could not write csv record ", e);
}
}
@Override
public void streamClosed(Stream stream) {
w.close();
completeOK(req, MediaType.CSV, buf);
}
});
} else {
ListEventsResponse.Builder responseb = ListEventsResponse.newBuilder();
RestStreams.stream(instance, sql, new RestStreamSubscriber(pos, limit) {
@Override
public void processTuple(Stream stream, Tuple tuple) {
Event.Builder event = Event.newBuilder((Event) tuple.getColumn("body"));
event.setGenerationTimeUTC(TimeEncoding.toString(event.getGenerationTime()));
event.setReceptionTimeUTC(TimeEncoding.toString(event.getReceptionTime()));
responseb.addEvent(event);
}
@Override
public void streamClosed(Stream stream) {
completeOK(req, responseb.build(), SchemaRest.ListEventsResponse.WRITE);
}
});
}
}
@Route(path = "/api/archive/:instance/events", method = "POST")
public void postEvent(RestRequest req) throws HttpException {
// get event from request
String instance = verifyInstance(req, req.getRouteParam("instance"));
Event event = req.bodyAsMessage(SchemaYamcs.Event.MERGE).build();
// get event producer for this instance
EventProducer eventProducer = null;
if(eventProducerMap.containsKey(instance))
eventProducer = eventProducerMap.get(instance);
else {
eventProducer = EventProducerFactory.getEventProducer(instance);
eventProducerMap.put(instance, eventProducer);
}
// update event reception time
event = event.toBuilder().setReceptionTime(YamcsServer.getTimeService(instance).getMissionTime()).build();
// send event
log.debug("Adding event from REST API: {}", event.toString());
eventProducer.sendEvent(event);
completeOK(req);
}
/**
* Checks if events are supported for the specified instance. This will succeed in two cases:
* <ol>
* <li>EventRecorder is currently enabled
* <li>EventRecorder has been enabled in the past, but may not be any longer
* </ol>
*/
public static void verifyEventArchiveSupport(String instance) throws BadRequestException {
YarchDatabase ydb = YarchDatabase.getInstance(instance);
TableDefinition table = ydb.getTable(EventRecorder.TABLE_NAME);
if (table == null) {
throw new BadRequestException("No event archive support for instance '" + instance + "'");
}
}
}