org.diirt.datasource.PVReaderConfiguration Maven / Gradle / Ivy
Show all versions of datasource Show documentation
/**
* Copyright (C) 2010-14 diirt developers. See COPYRIGHT.TXT
* All rights reserved. Use is subject to license terms. See LICENSE.TXT
*/
package org.diirt.datasource;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
import org.diirt.datasource.expression.DesiredRateExpression;
import static org.diirt.util.concurrent.Executors.*;
import org.diirt.util.time.TimeDuration;
/**
* An expression used to set the final parameters on how the pv expression
* should be read.
*
* @param the type of the expression
* @author carcassi
*/
public class PVReaderConfiguration extends CommonConfiguration {
@Override
public PVReaderConfiguration from(DataSource dataSource) {
super.from(dataSource);
return this;
}
@Override
public PVReaderConfiguration notifyOn(Executor onThread) {
super.notifyOn(onThread);
return this;
}
/**
* Sets a timeout for no values received.
*
* For more details, consult {@link #timeout(org.diirt.util.time.TimeDuration, java.lang.String) }.
*
* @param timeout the duration of the timeout; can't be null
* @return this expression
*/
@Override
public PVReaderConfiguration timeout(TimeDuration timeout) {
super.timeout(timeout);
return this;
}
/**
* Sets a timeout for no values received with the given message.
*
* If no value is received before the given time, a {@link TimeoutException}
* is notified through the listener. Note the difference: the timeout is
* not on the connection but on the value itself. This allows to use timeouts
* when creating combined expressions that can produce data even if not
* all elements have values. For single channels, this means that if the
* channel is connected, but no value has been processed, a timeout
* exception is still sent.
*
* @param timeout the duration of the timeout; can't be null
* @param timeoutMessage the message for the reported timeout
* @return this expression
*/
@Override
public PVReaderConfiguration timeout(TimeDuration timeout, String timeoutMessage) {
super.timeout(timeout, timeoutMessage);
return this;
}
private final DesiredRateExpression aggregatedPVExpression;
private final List> readListeners = new ArrayList<>();
private ExceptionHandler exceptionHandler;
private TimeDuration maxRate;
PVReaderImpl pv;
ReadFunction aggregatedFunction;
PVReaderConfiguration(DesiredRateExpression aggregatedPVExpression) {
this.aggregatedPVExpression = aggregatedPVExpression;
}
/**
* Adds a listener notified for any reader event (values, connection and errors).
*
* Registering a listener here guarantees that no event is ever missed.
*
* @param listener the listener to register
* @return this expression
*/
public PVReaderConfiguration readListener(PVReaderListener super T> listener) {
@SuppressWarnings("unchecked")
PVReaderListener convertedListener = (PVReaderListener) listener;
readListeners.add(convertedListener);
return this;
}
/**
* Forwards exception to the given exception handler. No thread switch
* is done, so the handler is notified on the thread where the exception
* was thrown.
*
* Giving a custom exception handler will disable the default handler,
* so {@link PVReader#lastException() } is no longer set and no notification
* is done.
*
* @param exceptionHandler an exception handler
* @return this
*/
public PVReaderConfiguration routeExceptionsTo(ExceptionHandler exceptionHandler) {
if (this.exceptionHandler != null) {
throw new IllegalArgumentException("Exception handler already set");
}
this.exceptionHandler = ExceptionHandler.safeHandler(exceptionHandler);
return this;
}
/**
* Sets the rate of scan of the expression and creates the actual {@link PVReader}
* object that can be monitored through listeners.
*
* @param rate the minimum time distance (i.e. the maximum rate) between two different notifications
* @return the PVReader
*/
public PVReader maxRate(TimeDuration rate) {
maxRateAndValidate(rate);
preparePvReader();
PVDirector director = prepareDirector(this);
prepareDecoupler(director, this);
return pv;
}
void maxRateAndValidate(TimeDuration rate) {
this.maxRate = rate;
validateReaderConfiguration();
}
static PVDirector prepareDirector(PVReaderConfiguration readConfiguration) {
PVDirector director = new PVDirector<>(readConfiguration.pv, readConfiguration.aggregatedFunction, PVManager.getReadScannerExecutorService(),
readConfiguration.notificationExecutor, readConfiguration.dataSource, readConfiguration.exceptionHandler);
if (readConfiguration.timeout != null) {
if (readConfiguration.timeoutMessage == null)
readConfiguration.timeoutMessage = "Read timeout";
director.readTimeout(readConfiguration.timeout, readConfiguration.timeoutMessage);
}
return director;
}
static void prepareDecoupler(PVDirector director, PVReaderConfiguration readConfiguration) {
ScannerParameters scannerParameters = new ScannerParameters()
.readerDirector(director)
.scannerExecutor(PVManager.getReadScannerExecutorService())
.maxDuration(readConfiguration.maxRate);
if (readConfiguration.aggregatedFunction instanceof Collector || readConfiguration.aggregatedFunction instanceof ValueCache) {
scannerParameters.type(ScannerParameters.Type.PASSIVE);
} else {
scannerParameters.type(ScannerParameters.Type.ACTIVE);
}
SourceDesiredRateDecoupler rateDecoupler = scannerParameters.build();
readConfiguration.pv.setDirector(director);
director.setScanner(rateDecoupler);
director.connectReadExpression(readConfiguration.aggregatedPVExpression);
rateDecoupler.start();
}
private void validateReaderConfiguration() {
if (maxRate.getSec() < 0 && maxRate.getNanoSec() < 5000000) {
throw new IllegalArgumentException("Current implementation limits the rate to >5ms or <200Hz (requested " + maxRate + "s)");
}
checkDataSourceAndThreadSwitch();
}
void preparePvReader() {
pv = new PVReaderImpl<>(aggregatedPVExpression.getName(), localThread() == notificationExecutor);
for (PVReaderListener pVReaderListener : readListeners) {
pv.addPVReaderListener(pVReaderListener);
}
aggregatedFunction = aggregatedPVExpression.getFunction();
}
}