/** * This file is protected by Copyright. * Please refer to the COPYRIGHT file distributed with this source distribution. * * This file is part of REDHAWK IDE. * * All rights reserved. This program and the accompanying materials are made available under * the terms of the Eclipse Public License v1.0 which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html. * */ package nxm.redhawk.prim; import gov.redhawk.bulkio.util.BulkIOType; import gov.redhawk.bulkio.util.BulkIOUtilActivator; import gov.redhawk.sca.util.Debug; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.net.URLEncoder; import nxm.redhawk.lib.RedhawkOptActivator; import nxm.redhawk.prim.data.BulkIOReceiver; import nxm.sys.inc.Commandable; import nxm.sys.lib.BaseFile; import nxm.sys.lib.Convert; import nxm.sys.lib.Data; import nxm.sys.lib.DataFile; import nxm.sys.lib.FileName; import nxm.sys.lib.Midas; import nxm.sys.lib.MidasException; import nxm.sys.lib.Time; import org.eclipse.core.runtime.CoreException; import BULKIO.PrecisionUTCTime; import BULKIO.StreamSRI; /** * This class connects to the specified CORBA host and receives data and writes * it to a file. Generics are used to receive all types of data(Scalar/Complex, * Char/Short/Long/Float/Double). NOTE: The class can switch between receiving * Scalar and Complex, but not the actual data type. This functionality has not * been tested(the plot may not recover). */ public class corbareceiver extends CorbaPrimitive implements IMidasDataWriter { //SUPPRESS CHECKSTYLE ClassName /** Name of IDL argument * @since 8.0 */ public static final String A_IDL = "IDL"; /** * @since 8.0 */ public static final String NAME = "CORBARECEIVER"; /** * @since 8.0 */ public static final String A_FILE = "FILE"; /** * Name of frame size argument * @since 10.0 */ public static final String A_FRAMESIZE = "FRAMESIZE"; /** * OVERRIDE FRAME SIZE (from StreamSRI) attribute * @since 10.0 */ public static final String A_OVERRIDE_SRI_SUBSIZE = "OVERRIDE_SRI_SUBSIZE"; /** * Name of switch to set to false to NOT block pushPacket/write(..) when pipe doesn't have enough room, * which will cause that pushPacket data to get drop * @since 10.0 */ public static final String SW_BLOCKING = "/BLOCKING"; /** * Name of switch to grab number of milliseconds to wait for SRI during open() * @since 10.0 */ public static final String SW_WAIT = "/WAIT"; /** * Name of switch to enable/disable increasing/growing output file's pipe size when * incoming data packet size is larger than it. * @since 10.2 */ public static final String SW_GROW_PIPE = "/GROWPIPE"; /** * Name of switch to set pipe size multiplier based on incoming data packet size (when larger than current pipe size). * @since 10.2 */ public static final String SW_PS_MULTIPLIER = "/PSMULT"; /** treat dataOctet as 8-bit unsigned integer (this will upcast format type to 16-bit signed integer to hold value). * @since 10.2 */ public static final String SW_TREAT_OCTET_AS_UNSIGNED = "/UNSIGNEDOCTET"; /** max seconds allowed since UTC (J1950) */ private static final double MAX_UTC_WSEC = Time.MAX_INPUT_WSEC + Time.J1950TOJ1970; private static final double MIN_UTC_WSEC = Time.J1950TOJ1970; /** sleep interval (ms) for {@link #SW_WAIT}. */ private static final int SLEEP_INTERVAL_MS = 100; private static final Debug TRACE_LOGGER = new Debug(RedhawkOptActivator.ID, corbareceiver.class.getSimpleName()); /** the output file to write to */ private volatile DataFile outputFile = null; private FileName fileName; /** The configured SRI */ private volatile StreamSRI currentSri; private BulkIOReceiver receiver; private Integer origFrameSizeArg = null; /** default frame size for 1000 stream or custom frame size if {@link #overrideSRISubSize} is true. */ private int frameSizeAttribute; /** override frame size specified in SRI with {@link #frameSizeAttribute}. */ private boolean overrideSRISubSize; /** block if output pipe is full. */ private boolean blocking; /** true if 1000 stream (SRI.subsize <= 0); false for 2000 stream (SRI.subsize > 0). */ private boolean is1000FromSRI = true; /** override SRI's sample rate (1/xdelta for 1000, 1/ydelta for 2000). */ private boolean overrideSRISampleRate = false; /** custom sample rate to use for output file/pipe. zero for none. */ private double sampleRate = 0.0; private boolean connected; private String portIor; private BulkIOType type; /** allow increasing the pipe size when pushPacket data is larger than it. */ private boolean canGrowPipe; /** multiplier of data packet size for setting pipe size (on {@link #outputFile} if it is less than data size. */ private int pipeSizeMultiplier; private int newPipeSize; /** * @since 8.0 */ public static String encodeIDL(final String idl) { try { return URLEncoder.encode(idl, "UTF-8"); } catch (final UnsupportedEncodingException e) { return idl; } } /** * @since 8.0 */ public static String decodeIDL(final String idl) { try { return URLDecoder.decode(idl, "UTF-8"); } catch (final UnsupportedEncodingException e) { return idl; } } @Override public synchronized int open() { // MA is the argument list this.fileName = this.MA.getFileName(A_FILE); final String encoded_idl = this.MA.getS(A_IDL, null); this.frameSizeAttribute = this.MA.getL(A_FRAMESIZE, 0); this.overrideSRISubSize = this.MA.getState(A_OVERRIDE_SRI_SUBSIZE, false); this.blocking = this.MA.getState(SW_BLOCKING, false); boolean unsignedOctet = MA.getState(SW_TREAT_OCTET_AS_UNSIGNED); this.canGrowPipe = MA.getState(SW_GROW_PIPE, true); setPipeSizeMultiplier(MA.getL(SW_PS_MULTIPLIER, 4)); BulkIOType newType = BulkIOType.getType(corbareceiver.decodeIDL(encoded_idl)); this.type = newType; if (this.receiver == null) { this.receiver = new BulkIOReceiver(this, newType, unsignedOctet, null); } if (origFrameSizeArg == null) { origFrameSizeArg = this.frameSizeAttribute; } int ret = super.open(); // This check fails if we weren't able to connect to the ORB if ((ret == Commandable.NORMAL)) { // Check if we were able to connect to the Port if (!initConnections()) { return Commandable.ABORT; // ABORT, since we are unable to connect to the Port } } final double maxWaitForSRI = MA.getD(SW_WAIT, 1000); // in milliseconds int numLoops = (int) (maxWaitForSRI / SLEEP_INTERVAL_MS); // 0 or positive values will effect timeout if (maxWaitForSRI < 0) { numLoops = Integer.MAX_VALUE; // negative wait is effectively infinity } while (this.currentSri == null && (numLoops-- > 0)) { try { wait(SLEEP_INTERVAL_MS); } catch (InterruptedException e) { break; } } this.outputFile = createOutputFile(this.currentSri, this.receiver); this.outputFile.open(); TRACE_LOGGER.exitingMethod(); return ret; } @Override public int process() { if (newPipeSize > 0) { setPipeSize(newPipeSize); newPipeSize = 0; } return NOOP; } @Override public synchronized int close() { TRACE_LOGGER.enteringMethod(); DataFile localOutputFile = this.outputFile; this.outputFile = null; if (localOutputFile != null) { localOutputFile.close(); } if (this.state != Commandable.RESTART) { //Avoid hanging the UI if the CORBA call to the component fails to return super.close(); this.currentSri = null; } return Commandable.NORMAL; } /** * This initializes the ORB, gets the destination CF::Resource and connects * to the specified port. This will retry MAX_RETRIES times to complete the * connection sequence. * @since 8.0 */ private boolean initConnections() { if (!connected) { try { String portIor2 = getPortIOR(); portIor = portIor2; BulkIOType type2 = type; BulkIOReceiver receiver2 = receiver; if (portIor2 != null && type2 != null && receiver2 != null) { BulkIOUtilActivator.getBulkIOPortConnectionManager().connect(portIor2, type2, receiver2); } else { return false; } connected = true; } catch (CoreException e) { throw new MidasException("Failed to connect port", e); } } return connected; } @Override protected void shutdown() { super.shutdown(); final String portIor2 = portIor; final BulkIOType type2 = type; final BulkIOReceiver receiver2 = receiver; if (portIor2 != null && type2 != null && receiver2 != null) { BulkIOUtilActivator.getBulkIOPortConnectionManager().disconnect(portIor2, type2, receiver2); } connected = false; } /** * This will determine if we should continue processing the packet. To * process, the endOfStream must be false and we must have previously * received SRI. * * @param endOfStream true if the received stream has closed * @return true if we should process the data */ private synchronized boolean shouldProcessPacket(final boolean endOfStream, final byte type) { if (this.state != Commandable.PROCESS || isStateChanged() || this.currentSri == null || this.outputFile == null) { return false; } return true; } private DataFile createDefaultOutputFile() { final String format = "S" + receiver.getMidasType(); DataFile newOutputFile = new DataFile(this.MA.cmd, fileName, "2000", format, BaseFile.OUTPUT); newOutputFile.setFrameSize(frameSizeAttribute); // <-- this call will not convert 1000 to 2000 return newOutputFile; } private DataFile createOutputFile(final StreamSRI sri, final BulkIOReceiver receiver) { if (sri == null) { return createDefaultOutputFile(); } this.is1000FromSRI = (sri.subsize <= 0); // true for 1000 stream, false for 2000 stream int outputFramesize = sri.subsize; if (this.is1000FromSRI) { outputFramesize = this.frameSizeAttribute; // frame 1000 stream with our default frame size } else if (this.overrideSRISubSize && this.frameSizeAttribute > 0) { outputFramesize = this.frameSizeAttribute; } outputFramesize = Math.max(outputFramesize, 1); final String fileType = (outputFramesize > 0) ? "2000" : "1000"; // SUPPRESS CHECKSTYLE AvoidInline final String format = ((sri.mode == 0) ? "S" : "C") + receiver.getMidasType(); // SUPPRESS CHECKSTYLE AvoidInline final DataFile newOutputFile = new DataFile(this.MA.cmd, fileName, fileType, format, BaseFile.OUTPUT); if (outputFramesize > 0) { newOutputFile.setFrameSize(outputFramesize); } double sampleDelta = 0; // zero will be ignored (not used) if (isOverrideSRISampleRate() && this.sampleRate != 0) { sampleDelta = 1 / this.sampleRate; } final boolean overrideSampleDelta = this.overrideSRISampleRate && (sampleDelta != 0); double xdelta = 1.0; if (overrideSampleDelta && this.is1000FromSRI) { xdelta = sampleDelta; } else if (sri.xdelta > 0 && sri.xdelta <= Double.MAX_VALUE) { // ignore <= 0, Infinity and NaN xdelta = sri.xdelta; } newOutputFile.setXStart(sri.xstart); newOutputFile.setXDelta(xdelta); newOutputFile.setXUnits(sri.xunits); double ydelta = 1.0; if (overrideSampleDelta && !this.is1000FromSRI) { ydelta = sampleDelta; } else if (sri.ydelta > 0 && sri.ydelta <= Double.MAX_VALUE) { // ignore <= 0, Infinity and NaN ydelta = sri.ydelta; } newOutputFile.setYStart(sri.ystart); newOutputFile.setYDelta(ydelta); newOutputFile.setYUnits(sri.yunits); return newOutputFile; } private static boolean isRestartNeed4SriChange(final StreamSRI sri, final StreamSRI lastSRI) { boolean shouldRestart = false; if (lastSRI != null) { // blocking, hversion, keywords, and streamID changes do not require a restart shouldRestart |= (lastSRI.mode != sri.mode); shouldRestart |= (lastSRI.subsize != sri.subsize); shouldRestart |= (lastSRI.xdelta != sri.xdelta); shouldRestart |= (lastSRI.xstart != sri.xstart); shouldRestart |= (lastSRI.xunits != sri.xunits); shouldRestart |= (lastSRI.ydelta != sri.ydelta); shouldRestart |= (lastSRI.ystart != sri.ystart); shouldRestart |= (lastSRI.yunits != sri.yunits); } else { shouldRestart = true; } return shouldRestart; } /** * @since 10.0 * @noreference This method is not intended to be referenced by clients. * @deprecated since 10.1 use {@link #setStreamSri(String, StreamSRI, StreamSRI)} */ @Deprecated public void setStreamSri(final StreamSRI sri) { } /** * @since 10.2 * @noreference This method is not intended to be referenced by clients. */ @Override public synchronized void setStreamSri(String streamID, StreamSRI oldSri, StreamSRI newSri) { TRACE_LOGGER.message("{0}: setStreamSri to {1} id={2} blocking={3}", getID(), newSri, newSri.streamID, newSri.blocking); this.currentSri = newSri; sendMessage("STREAMSRI", 1, this.currentSri); if (state == Commandable.PROCESS) { if (isRestartNeed4SriChange(newSri, oldSri)) { doRestart(); } } else if (state == Commandable.OPEN) { notifyAll(); } } private synchronized void doRestart() { TRACE_LOGGER.message("{0}: scheduling restart... {1}", getID(), this.outputFile); setState(Commandable.RESTART); } /** * @noreference This method is not intended to be referenced by clients. * @since 8.0 * @deprecated since 10.1 use {@link #write(Object, int, byte, boolean, PrecisionUTCTime, String)} */ @Deprecated public void write(final Object dataArray, final int size, final byte type, final boolean endOfStream, PrecisionUTCTime time) { write(dataArray, size, type, endOfStream, time, null); } /** * This plots an arbitrary typed array of data. Type checking is performed * by System.arraycopy(). The type of 'list' must match 'type'. * * @param dataArray the array of data to plot * @param size the length of the data in the array * @param type the NeXtMidas type of the data to plot (eg. Data.FLOAT) * @since 10.2 * @noreference This method is not intended to be referenced by clients. */ @Override public void write(final Object dataArray, final int size, final byte type, final boolean endOfStream, PrecisionUTCTime time, final String streamId) { if (!shouldProcessPacket(endOfStream, type)) { return; } final DataFile localOutputFile = this.outputFile; if (localOutputFile == null || !localOutputFile.isOpen) { return; } final int bufferSize = Data.getBPS(type) * size; // size of data to write in bytes if (this.canGrowPipe && localOutputFile.getPipeSize() < bufferSize) { // increase pipe size if allowed (ticket #1554) if (TRACE_LOGGER.enabled) { TRACE_LOGGER.message("{0}: scheduling pipe size increase from {1} to ({2} x {3}) bytes for {4}", getID(), localOutputFile.getPipeSize(), this.pipeSizeMultiplier, bufferSize, this.outputFile); } this.newPipeSize = bufferSize * this.pipeSizeMultiplier; // this change will be picked up in the process() method } if (!blocking) { // === non-blocking option enabled === if (M.pipeMode == Midas.PPAUSE) { // 1. pipe is PAUSED TRACE_LOGGER.message("Dropping packet b/c pipe is PAUSED"); return; // 1b. drop packet, as write would block } if (!this.canGrowPipe && localOutputFile.getPipeSize() < bufferSize) { // PASS - let packet through even though this might block, otherwise no data will ever be written } else if (localOutputFile.getResource().avail() < bufferSize) { // 2. avail buffer is less than data/packet size // Time.sleep(0.01); // <-- provide slight back-pressure so we don't spin CPU for Component that does NOT throttle data TRACE_LOGGER.message("Dropping packet b/c not enough avail buffer without blocking write"); return; // 2b. drop packet since write would block } } if (time != null && time.twsec <= MAX_UTC_WSEC && time.twsec >= MIN_UTC_WSEC && time.tfsec <= MAX_UTC_WSEC && time.tfsec >= MIN_UTC_WSEC) { Time midasTime = new Time(time.twsec + Time.J1970TOJ1950, time.tfsec); localOutputFile.setTimeAt(midasTime); } byte[] byteBuffer = new byte[bufferSize]; Convert.ja2bb(dataArray, 0, type, byteBuffer, 0, localOutputFile.dataType, size); localOutputFile.write(byteBuffer, 0, byteBuffer.length); return; } /** * @return the overrideSRISubSize * @since 10.0 */ public boolean isOverrideSRISubSize() { return overrideSRISubSize; } /** * @param overrideSRISubSize true to override the SRI SubSize (frame size) with {@link #getFrameSize()}. * @see #setFrameSize(int) * @since 10.0 */ public void setOverrideSRISubSize(boolean overrideSRISubSize) { if (this.overrideSRISubSize != overrideSRISubSize) { this.overrideSRISubSize = overrideSRISubSize; MA.put(A_OVERRIDE_SRI_SUBSIZE, "" + overrideSRISubSize); final StreamSRI sri = this.currentSri; if ((sri != null) && (this.frameSizeAttribute != sri.subsize)) { doRestart(); // restart since specified subsize is different than in SRI } } } /** * @return the frame size to use * @since 10.0 */ public int getFrameSize() { return frameSizeAttribute; } /** * @param set the custom frame size (SubSize) to use when {@link #overrideSRISubSize} is true.; * use -1 to reset to original frame size argument * @see #setOverrideSRISubSize(boolean) * @since 10.0 */ public void setFrameSize(int frameSizeAttribute) { if (frameSizeAttribute == -1) { frameSizeAttribute = origFrameSizeArg; } if (this.frameSizeAttribute != frameSizeAttribute) { this.frameSizeAttribute = frameSizeAttribute; MA.put(A_FRAMESIZE, "" + frameSizeAttribute); if (this.overrideSRISubSize) { doRestart(); // restart since specified subsize is different than in SRI } } } /** true if custom sample rate differs from SRI's sample rate, otherwise false. */ private boolean isSampleRateDifferent() { final StreamSRI sri = this.currentSri; if ((sri != null) && (this.sampleRate != 0)) { // cannot have zero sample rate final double delta = 1.0 / this.sampleRate; final boolean xdDiffers = this.is1000FromSRI && delta != sri.xdelta; final boolean ydDiffers = !this.is1000FromSRI && delta != sri.ydelta; if (xdDiffers || ydDiffers) { return true; } } return false; } /** * @return the overrideSRISampleRate * @since 10.0 */ public boolean isOverrideSRISampleRate() { return overrideSRISampleRate; } /** * @param overrideSRISampleRate the overrideSRISampleRate to set * @since 10.0 */ public void setOverrideSRISampleRate(boolean overrideSRISampleRate) { if (this.overrideSRISampleRate != overrideSRISampleRate) { this.overrideSRISampleRate = overrideSRISampleRate; if (isSampleRateDifferent()) { doRestart(); // restart since specified sample rate is different than in SRI } } } /** * @return the current custom sample rate to override in SRI. * @since 10.0 */ public double getSampleRate() { return sampleRate; } /** * @param newSRate the custom sample rate to override in SRI * (1/xdelta for 1000 stream; 1/ydelta for 2000 stream). * @since 10.0 */ public void setSampleRate(double newSRate) { if (this.sampleRate != newSRate) { this.sampleRate = newSRate; if (this.overrideSRISampleRate && isSampleRateDifferent()) { doRestart(); // restart since specified sample rate is different than in SRI } } } /** * @return the blocking option * @since 10.0 */ public boolean isBlocking() { return blocking; } /** * @param blocking the blocking option to set * @since 10.0 */ public void setBlocking(boolean blocking) { this.blocking = blocking; MA.put(SW_BLOCKING, "" + blocking); } /** * @since 10.2 */ public boolean isCanGrowPipe() { return canGrowPipe; } /** * @since 10.2 */ public void setCanGrowPipe(boolean newValue) { this.canGrowPipe = newValue; MA.put(SW_GROW_PIPE, "" + newValue); } /** * @since 10.2 */ public int getPipeSizeMultiplier() { return pipeSizeMultiplier; } /** * @param newValue new pipe size multiplier (MUST be >= 1) * @since 10.2 */ public void setPipeSizeMultiplier(int newValue) { if (newValue <= 0) { throw new IllegalArgumentException("data size to pipe size multiplier must be greater than 0"); } this.pipeSizeMultiplier = newValue; MA.put(SW_PS_MULTIPLIER, "" + newValue); } /** @since 10.2*/ public int getPipeSize() { int retval = 0; DataFile df = this.outputFile; if (df != null) { retval = df.getPipeSize(); } return retval; } /** * Change output file's pipe size (immediately) * @param newValue new pipe size for output data file/pipe (in bytes) * @since 10.2 */ public synchronized void setPipeSize(int newValue) { if (newValue <= 0) { throw new IllegalArgumentException("pipe size (bytes) must be greater than 0"); } DataFile df = this.outputFile; if (df != null && df.getPipeSize() != newValue) { TRACE_LOGGER.message("{0}: changing pipe size from {1} to {2} bytes for {3}", getID(), df.getPipeSize(), newValue, df); // boolean changePipeMode = M.pipeMode == Midas.PRUN; // if (changePipeMode) { // M.pipeMode = Midas.PPAUSE; // change pipe mode to PAUSE to prevent DataFile.setPipeSize(..) from sleeping 0.2 sec // } df.setPipeSize(newValue); // if (changePipeMode) { // M.pipeMode = Midas.PRUN; // restore pipe mode back to RUN // } } } }