/******************************************************************************* * Copyright (c) 2011 The Board of Trustees of the Leland Stanford Junior University * as Operator of the SLAC National Accelerator Laboratory. * Copyright (c) 2011 Brookhaven National Laboratory. * EPICS archiver appliance is distributed subject to a Software License Agreement found * in file LICENSE that is included with this distribution. *******************************************************************************/ package org.epics.archiverappliance.utils.ui; import java.io.IOException; import java.io.OutputStream; import java.sql.Timestamp; import org.apache.log4j.Logger; import org.epics.archiverappliance.ByteArray; import org.epics.archiverappliance.Event; import org.epics.archiverappliance.EventStream; import org.epics.archiverappliance.common.TimeUtils; import org.epics.archiverappliance.retrieval.RemotableEventStreamDesc; import org.epics.archiverappliance.retrieval.RemotableOverRaw; import edu.stanford.slac.archiverappliance.PB.EPICSEvent.PayloadInfo; import edu.stanford.slac.archiverappliance.PB.EPICSEvent.PayloadInfo.Builder; import edu.stanford.slac.archiverappliance.PB.data.PartionedTime; import edu.stanford.slac.archiverappliance.PB.utils.LineEscaper; /** * Utility class with method to push an EventStream into an OutputStream * @author mshankar * */ public class StreamPBIntoOutput { private static final Logger logger = Logger.getLogger(StreamPBIntoOutput.class); /** * Push the events in st into the output stream os. * This is highly simplistic and does not accommodate year transitions, pv transitions and such. * @param st EventStream * @param os OutputStream * @param start The start time; could be null in which case we begin at the very beginning * @param end The end time; could be null in which case we end at the very end * @return totalEvents   * @throws IOException   */ public static int streamPBIntoOutputStream(EventStream st, OutputStream os, Timestamp start, Timestamp end) throws IOException { long startTimeInEpochSeconds = 0; if(start != null) startTimeInEpochSeconds = TimeUtils.convertToEpochSeconds(start); long endTimeInEpochSeconds = Long.MAX_VALUE; if(end != null) endTimeInEpochSeconds = TimeUtils.convertToEpochSeconds(end); // Write the PB header. assert(st instanceof RemotableOverRaw); RemotableEventStreamDesc desc = ((RemotableOverRaw)st).getDescription(); assert(desc != null); short previousYear = -1; int totalEvents = 0; try { for(Event e : st) { previousYear = writeHeader(previousYear, os, desc, e, startTimeInEpochSeconds); long epochSeconds = e.getEpochSeconds(); if(epochSeconds >= startTimeInEpochSeconds && epochSeconds <= endTimeInEpochSeconds) { ByteArray val = e.getRawForm(); os.write(val.data, val.off, val.len); os.write(LineEscaper.NEWLINE_CHAR); totalEvents++; } else { if(logger.isDebugEnabled()) { logger.debug("Skipping event" + " with timestamp " + TimeUtils.convertToISO8601String(TimeUtils.convertFromEpochSeconds(epochSeconds, 0)) + " and start " + TimeUtils.convertToISO8601String(TimeUtils.convertFromEpochSeconds(startTimeInEpochSeconds, 0)) + " with end " + TimeUtils.convertToISO8601String(TimeUtils.convertFromEpochSeconds(endTimeInEpochSeconds, 0)) ); } } } if(totalEvents == 0 && previousYear != -1) { // If we did not write any events, we should at least write the header indicating that the PV is valid // Otherwise we see some exceptions in PBOverHTTPStoragePlugin that we really want to watch. // If we already wrote the header out, then previous year should be something other than -1. logger.debug("Writing a header for an empty event stream for pv " + desc.getPvName()); previousYear = writeHeader(previousYear, os, desc, null, startTimeInEpochSeconds); } } finally { try { st.close(); } catch(Throwable t) {} } return totalEvents; } private static short writeHeader(short previousYear, OutputStream os, RemotableEventStreamDesc desc, Event e, long startTimeInEpochSeconds) throws IOException { short currentYear = -1; if(e != null) { if(e instanceof PartionedTime) { currentYear = ((PartionedTime)e).getYear(); } else { currentYear = TimeUtils.computeYearForEpochSeconds(e.getEpochSeconds()); } } else { currentYear = TimeUtils.computeYearForEpochSeconds(startTimeInEpochSeconds); } if(previousYear == -1 || previousYear != currentYear) { if(previousYear != -1) os.write(LineEscaper.NEWLINE_CHAR); // Blank line indicates a new chunk in the event stream; skip if this is the first chunk Builder builder = PayloadInfo.newBuilder() .setPvname(desc.getPvName()) .setType(desc.getArchDBRType().getPBPayloadType()) .setYear(currentYear); desc.mergeInto(builder); PayloadInfo infoWithFields = builder.build(); byte[] headerBytes = LineEscaper.escapeNewLines(infoWithFields.toByteArray()); os.write(headerBytes); os.write(LineEscaper.NEWLINE_CHAR); } return currentYear; } /** * Write a header only - this is sometimes used to communicate the latest copy of the meta-fields (EGU etc) from the engine to the client even if we have no data in the engine. * @param os OutputStream * @param desc RemotableEventStreamDesc * @throws IOException   */ public static void writeHeaderOnly(OutputStream os, RemotableEventStreamDesc desc) throws IOException { Builder builder = PayloadInfo.newBuilder() .setPvname(desc.getPvName()) .setType(desc.getArchDBRType().getPBPayloadType()) .setYear(desc.getYear()); desc.mergeInto(builder); PayloadInfo infoWithFields = builder.build(); byte[] headerBytes = LineEscaper.escapeNewLines(infoWithFields.toByteArray()); os.write(headerBytes); os.write(LineEscaper.NEWLINE_CHAR); } }