/******************************************************************************* * 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.retrieval.mimeresponses; import java.io.IOException; import java.io.OutputStream; import java.sql.Timestamp; import java.util.HashMap; import java.util.LinkedList; import java.util.Map; import org.apache.log4j.Logger; import org.epics.archiverappliance.ByteArray; import org.epics.archiverappliance.Event; import org.epics.archiverappliance.EventStream; import org.epics.archiverappliance.EventStreamDesc; 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; import edu.stanford.slac.archiverappliance.PB.EPICSEvent.FieldValue; import edu.stanford.slac.archiverappliance.PB.EPICSEvent.PayloadInfo; import edu.stanford.slac.archiverappliance.PB.EPICSEvent.PayloadInfo.Builder; import edu.stanford.slac.archiverappliance.PB.utils.LineEscaper; /** * Mimeresponse for PB over HTTP. * @author mshankar * */ public class PBRAWResponse implements MimeResponse { private static final int MERGEDEDUP_BUFFER_EVENTCOUNT = 2; private static Logger logger = Logger.getLogger(PBRAWResponse.class.getName()); private OutputStream os = null; boolean firstChunk = true; long eventsWritten = 0; private Builder headerToBeSentLater = null; private String pvName = null; private short previouslySentYear = 0; @Override public void setOutputStream(OutputStream os) { this.os = os; this.firstChunk = true; } @Override public void processingPV(String pv, Timestamp start, Timestamp end, EventStreamDesc streamDesc) { // We don't need this data for the raw response. this.pvName = pv; } public void swicthingToStream(EventStream strm) { // The merge dedup consumer has this nasty habit of buffering the first two events and sending them post stream to satisfy various constraints. // We have to treat these two events as special as the year that the message header has may not be the one that the event refers to. if(eventsWritten > MERGEDEDUP_BUFFER_EVENTCOUNT) { writeHeader(strm); } else { headerToBeSentLater = buildHeader(strm); // Write the header anyways to make the chunking code happy. writeHeader(headerToBeSentLater); } } @Override public void consumeEvent(Event e) throws Exception { if(eventsWritten <= MERGEDEDUP_BUFFER_EVENTCOUNT) { if(headerToBeSentLater == null) { String msg = "headerToBeSentLater is null when processing pv " + pvName; logger.error(msg); throw new IOException(msg); } try { short eventYear = TimeUtils.convertToYearSecondTimestamp(e.getEventTimeStamp()).getYear(); if(eventYear != previouslySentYear) { logger.debug("Writing header as part of event for year " + eventYear); headerToBeSentLater.setYear(eventYear); writeHeader(headerToBeSentLater); previouslySentYear = eventYear; logger.debug("Done writing header as part of event for year " + eventYear); } } catch(Throwable t) { logger.error("Writing header as part of event", t); } } ByteArray val = e.getRawForm(); os.write(val.data, val.off, val.len); os.write(LineEscaper.NEWLINE_CHAR); eventsWritten++; } @Override public void close() { try { os.close(); os = null; } catch(Exception t) {} } private void writeHeader(EventStream strm) { Builder builder = buildHeader(strm); writeHeader(builder); } private void writeHeader(Builder builder) { try { if(firstChunk) { // If this is the first chunk we do not need to add a new line firstChunk = false; } else { os.write(LineEscaper.NEWLINE_CHAR); } byte[] headerBytes = LineEscaper.escapeNewLines(builder.build().toByteArray()); os.write(headerBytes); os.write(LineEscaper.NEWLINE_CHAR); } catch(Exception ex) { if(ex != null && ex.toString() != null && ex.toString().contains("ClientAbortException")) { // We check for ClientAbortException etc this way to avoid including tomcat jars in the build path. logger.debug("Exception writing header in the raw response", ex); } else { logger.error("Exception writing header in the raw response --> " + ex.toString(), ex); } } } private static Builder buildHeader(EventStream strm) { RemotableEventStreamDesc desc = ((RemotableOverRaw)strm).getDescription(); Builder builder = PayloadInfo.newBuilder() .setPvname(desc.getPvName()) .setType(desc.getArchDBRType().getPBPayloadType()) .setYear(desc.getYear()) .setElementCount(desc.getElementCount()); Map<String, String> headers = desc.getHeaders(); if(!headers.isEmpty()) { LinkedList<FieldValue> fieldValuesList = new LinkedList<FieldValue>(); for(String fieldName : headers.keySet()) { String fieldValue = headers.get(fieldName); if(fieldValue != null && !fieldValue.isEmpty()) { fieldValuesList.add(EPICSEvent.FieldValue.newBuilder().setName(fieldName).setVal(fieldValue).build()); } } builder.addAllHeaders(fieldValuesList); } return builder; } @Override public HashMap<String, String> getExtraHeaders() { return null; } }