/******************************************************************************* * 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 edu.stanford.slac.archiverappliance.PBOverHTTP; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Constructor; import java.sql.Timestamp; import java.util.Iterator; import org.apache.log4j.Logger; import org.epics.archiverappliance.ByteArray; import org.epics.archiverappliance.Event; import org.epics.archiverappliance.data.DBRTimeEvent; import org.epics.archiverappliance.retrieval.RemotableEventStreamDesc; import org.epics.archiverappliance.retrieval.client.RetrievalEventProcessor; import edu.stanford.slac.archiverappliance.PB.EPICSEvent.PayloadInfo; import edu.stanford.slac.archiverappliance.PB.data.DBR2PBTypeMapping; import edu.stanford.slac.archiverappliance.PB.utils.LineEscaper; /** * The iterator for the InputStreamBackedEventStream * @author mshankar * */ public class InputStreamBackedEventStreamIterator implements Iterator<Event> { private static Logger logger = Logger.getLogger(InputStreamBackedEventStreamIterator.class.getName()); private static int BUFFER_SIZE=10*1024; private InputStream is = null; private int linenumber = 0; private RetrievalEventProcessor retrievalEventProcessor; private RemotableEventStreamDesc currentEventStreamDesc; InputStreamBackedEventStreamIterator(InputStream is, Timestamp startTime) { this.is = is; buf = new byte[BUFFER_SIZE]; readNextBatch(); } private byte[] nextLine = null; private short year; private Constructor<? extends DBRTimeEvent> unmarshallingConstructor; @Override public boolean hasNext() { nextLine = readLine(); if(nextLine == null) return false; while(nextLine.length <= 1) { logger.info("Detected PB header. Length of transition line is " + nextLine.length); // We have an empty line. // Per the protocol, the next line should be a header line. try { byte[] payloadLine = LineEscaper.unescapeNewLines(this.readLine()); PayloadInfo info = PayloadInfo.parseFrom(payloadLine); RemotableEventStreamDesc desc = new RemotableEventStreamDesc(info.getPvname(), info); this.setCurrentEventStreamDesc(desc); // The previous line should take care of transitions between year partitions. // We still need to detect PV transitions in the stream and issue events // This is done as part of the setCurrentEventStreamDesc itself } catch(Exception ex) { logger.error("Exception processing PB header info " + linenumber, ex); return false; } // The line we just read was a header line. // This method needs to leave this iterator in a state where we have the line for the next event // So, prepare for the next event by reading another line. nextLine = readLine(); if(nextLine == null) return false; } return true; } void setCurrentEventStreamDesc(RemotableEventStreamDesc newEventStreamDesc) { assert(newEventStreamDesc != null); // Check to see if we are processing a new PV. // In case this is the first PV, the currentEventStreamDesc will be null. boolean processingnewPV = false; if(currentEventStreamDesc == null) { processingnewPV = true; } else if (!newEventStreamDesc.getPvName().equals(currentEventStreamDesc.getPvName())) { processingnewPV = true; logger.info("Switching to data for PV " + newEventStreamDesc.getPvName()); } else { processingnewPV = false; logger.debug("Still in the same PV " + currentEventStreamDesc.getPvName()); } currentEventStreamDesc = newEventStreamDesc; unmarshallingConstructor = DBR2PBTypeMapping.getPBClassFor(currentEventStreamDesc.getArchDBRType()).getUnmarshallingFromByteArrayConstructor(); year = currentEventStreamDesc.getYear(); if(processingnewPV) { // Issue an event if(retrievalEventProcessor != null) retrievalEventProcessor.newPVOnStream(currentEventStreamDesc); } } @Override public Event next() { try { assert(unmarshallingConstructor != null); assert(year != 0); return (Event) unmarshallingConstructor.newInstance(year, new ByteArray(nextLine)); } catch (Exception ex) { logger.error("Exception creating event object processing line " + linenumber, ex); return null; } } @Override public void remove() { } private byte[] buf = null; private long bytesRead = 0; private int currentReadPosition = 0; byte[] readLine() { ByteArrayOutputStream out = new ByteArrayOutputStream(); while(true) { while(currentReadPosition < bytesRead) { byte b = buf[currentReadPosition++]; if(b == LineEscaper.NEWLINE_CHAR) { linenumber++; return out.toByteArray(); } else { out.write(b); } } readNextBatch(); if(bytesRead <= 0) { // End of file reached. if(out.size() > 0) { linenumber++; return out.toByteArray(); } else { return null; } } } } private void readNextBatch() { try { bytesRead = is.read(buf); currentReadPosition = 0; }catch(IOException ex) { logger.fatal("Exception reading next line ", ex); // Probably not kosher but this is needed for the units tests to fail. throw new RuntimeException(ex); } } public void setRetrievalEventProcessor(RetrievalEventProcessor retrievalEventProcessor) { this.retrievalEventProcessor = retrievalEventProcessor; } }