Merge legacy dataforge and migrate to git
This commit is contained in:
commit
c4164f2681
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
build/
|
||||
.gradle/
|
||||
.idea/
|
||||
out/
|
9
README.md
Normal file
9
README.md
Normal file
@ -0,0 +1,9 @@
|
||||
# Purpose #
|
||||
|
||||
This repository contains tools and utilities for [Trotsk nu-mass](http://www.inr.ru/~trdat/) experiment.
|
||||
|
||||
# Set-up #
|
||||
|
||||
This project build using [DataForge](http://www.inr.ru/~nozik/dataforge/) framework. Currently in order to compile numass tools, one need to download dataforge gradle project [here](https://bitbucket.org/Altavir/dataforge). If both projects (numass and dataforge) are in the same directory, everything will work out of the box, otherwise, one needs to edit `gradle.properties` file in the root of numass project and set `dataforgePath` to the relative path of dataforge directory.
|
||||
|
||||
It is intended to fix this problem with public maven repository later.
|
65
build.gradle
Normal file
65
build.gradle
Normal file
@ -0,0 +1,65 @@
|
||||
buildscript {
|
||||
ext.kotlin_version = "1.4.30"
|
||||
repositories {
|
||||
mavenCentral()
|
||||
jcenter()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
}
|
||||
}
|
||||
|
||||
plugins{
|
||||
id 'org.openjfx.javafxplugin' version '0.0.9' apply false
|
||||
}
|
||||
|
||||
allprojects {
|
||||
apply plugin: 'java'
|
||||
apply plugin: "org.jetbrains.kotlin.jvm"
|
||||
|
||||
group = 'inr.numass'
|
||||
version = '1.0.0'
|
||||
|
||||
[compileJava, compileTestJava]*.options*.encoding = 'UTF-8'
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
jcenter()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
||||
compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
||||
compile 'org.jetbrains:annotations:16.0.2'
|
||||
testImplementation group: 'junit', name: 'junit', version: '4.+'
|
||||
|
||||
//Spock dependencies. To be removed later
|
||||
testCompile 'org.codehaus.groovy:groovy-all:2.5.+'
|
||||
testCompile "org.spockframework:spock-core:1.2-groovy-2.5"
|
||||
}
|
||||
|
||||
compileKotlin {
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
javaParameters = true
|
||||
freeCompilerArgs += [
|
||||
'-Xjvm-default=enable',
|
||||
"-progressive",
|
||||
"-Xuse-experimental=kotlin.Experimental"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
compileTestKotlin {
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
javaParameters = true
|
||||
freeCompilerArgs += [
|
||||
'-Xjvm-default=enable',
|
||||
"-progressive",
|
||||
"-Xuse-experimental=kotlin.Experimental"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
10
dataforge-control/build.gradle
Normal file
10
dataforge-control/build.gradle
Normal file
@ -0,0 +1,10 @@
|
||||
|
||||
description = 'dataforge-control'
|
||||
|
||||
dependencies {
|
||||
// Adding dependencies here will add the dependencies to each subproject.
|
||||
compile project(':dataforge-core')
|
||||
//TODO consider removing storage dependency
|
||||
compile project(':dataforge-storage')
|
||||
compile 'org.scream3r:jssc:2.8.0'
|
||||
}
|
BIN
dataforge-control/doc/States.vsdx
Normal file
BIN
dataforge-control/doc/States.vsdx
Normal file
Binary file not shown.
@ -0,0 +1,22 @@
|
||||
package hep.dataforge.control;
|
||||
|
||||
import hep.dataforge.control.devices.Device;
|
||||
import hep.dataforge.io.envelopes.Envelope;
|
||||
import hep.dataforge.meta.Meta;
|
||||
|
||||
/**
|
||||
* Created by darksnake on 11-Oct-16.
|
||||
*/
|
||||
public class ControlUtils {
|
||||
public static String getDeviceType(Meta meta){
|
||||
return meta.getString("type");
|
||||
}
|
||||
|
||||
public static String getDeviceName(Meta meta){
|
||||
return meta.getString("name","");
|
||||
}
|
||||
|
||||
public static Envelope getDefaultDeviceResponse(Device device, Envelope request){
|
||||
throw new UnsupportedOperationException("Not implemented");
|
||||
}
|
||||
}
|
@ -0,0 +1,100 @@
|
||||
/*
|
||||
* Copyright 2015 Alexander Nozik.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package hep.dataforge.control.collectors;
|
||||
|
||||
import hep.dataforge.tables.ValuesListener;
|
||||
import hep.dataforge.utils.DateTimeUtils;
|
||||
import hep.dataforge.values.Value;
|
||||
import hep.dataforge.values.ValueFactory;
|
||||
import hep.dataforge.values.ValueMap;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* A class to dynamically collect measurements from multi-channel devices and
|
||||
* bundle them into DataPoints. The collect method is called when all values are
|
||||
* present.
|
||||
*
|
||||
* @author Alexander Nozik
|
||||
*/
|
||||
public class PointCollector implements ValueCollector {
|
||||
|
||||
private final List<String> names;
|
||||
private final ValuesListener consumer;
|
||||
private final Map<String, Value> valueMap = new ConcurrentHashMap<>();
|
||||
//TODO make time averaging?
|
||||
|
||||
public PointCollector(ValuesListener consumer, Collection<String> names) {
|
||||
this.names = new ArrayList<>(names);
|
||||
this.consumer = consumer;
|
||||
}
|
||||
|
||||
public PointCollector(ValuesListener consumer, String... names) {
|
||||
this.names = Arrays.asList(names);
|
||||
this.consumer = consumer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(String name, Value value) {
|
||||
valueMap.put(name, value);
|
||||
if (valueMap.keySet().containsAll(names)) {
|
||||
collect();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(String name, Object value) {
|
||||
valueMap.put(name, ValueFactory.of(value));
|
||||
if (valueMap.keySet().containsAll(names)) {
|
||||
collect();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Could be used to force collect even if not all values are present
|
||||
*/
|
||||
@Override
|
||||
public void collect() {
|
||||
collect(DateTimeUtils.now());
|
||||
}
|
||||
|
||||
public synchronized void collect(Instant time) {
|
||||
ValueMap.Builder point = new ValueMap.Builder();
|
||||
|
||||
point.putValue("timestamp", time);
|
||||
valueMap.entrySet().forEach((entry) -> {
|
||||
point.putValue(entry.getKey(), entry.getValue());
|
||||
});
|
||||
|
||||
// filling all missing values with nulls
|
||||
names.stream().filter((name) -> (!point.build().hasValue(name))).forEach((name) -> {
|
||||
point.putValue(name, ValueFactory.NULL);
|
||||
});
|
||||
|
||||
consumer.accept(point.build());
|
||||
valueMap.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
valueMap.clear();
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,117 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package hep.dataforge.control.collectors;
|
||||
|
||||
import hep.dataforge.utils.DateTimeUtils;
|
||||
import hep.dataforge.values.Value;
|
||||
import hep.dataforge.values.ValueFactory;
|
||||
import hep.dataforge.values.ValueMap;
|
||||
import hep.dataforge.values.Values;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* An averaging DataPoint collector that starts timer on first put operation and
|
||||
* forces collection when timer expires. If there are few Values with same time
|
||||
* during this period, they are averaged.
|
||||
*
|
||||
* @author <a href="mailto:altavir@gmail.com">Alexander Nozik</a>
|
||||
*/
|
||||
public class RegularPointCollector implements ValueCollector {
|
||||
|
||||
private final Map<String, List<Value>> values = new ConcurrentHashMap<>();
|
||||
private final Consumer<Values> consumer;
|
||||
private final Duration duration;
|
||||
private Instant startTime;
|
||||
/**
|
||||
* The names that must be in the dataPoint
|
||||
*/
|
||||
private List<String> names = new ArrayList<>();
|
||||
private Timer timer;
|
||||
|
||||
public RegularPointCollector(Duration duration, Consumer<Values> consumer) {
|
||||
this.consumer = consumer;
|
||||
this.duration = duration;
|
||||
}
|
||||
|
||||
public RegularPointCollector(Duration duration, Collection<String> names, Consumer<Values> consumer) {
|
||||
this(duration, consumer);
|
||||
this.names = new ArrayList<>(names);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void collect() {
|
||||
collect(DateTimeUtils.now());
|
||||
}
|
||||
|
||||
public synchronized void collect(Instant time) {
|
||||
if(!values.isEmpty()) {
|
||||
ValueMap.Builder point = new ValueMap.Builder();
|
||||
|
||||
Instant average = Instant.ofEpochMilli((time.toEpochMilli() + startTime.toEpochMilli()) / 2);
|
||||
|
||||
point.putValue("timestamp", average);
|
||||
|
||||
for (Map.Entry<String, List<Value>> entry : values.entrySet()) {
|
||||
point.putValue(entry.getKey(), entry.getValue().stream().mapToDouble(Value::getDouble).sum() / entry.getValue().size());
|
||||
}
|
||||
|
||||
// filling all missing values with nulls
|
||||
for (String name : names) {
|
||||
if (!point.build().hasValue(name)) {
|
||||
point.putValue(name, ValueFactory.NULL);
|
||||
}
|
||||
}
|
||||
|
||||
startTime = null;
|
||||
values.clear();
|
||||
consumer.accept(point.build());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void put(String name, Value value) {
|
||||
if (startTime == null) {
|
||||
startTime = DateTimeUtils.now();
|
||||
timer = new Timer();
|
||||
timer.schedule(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
collect();
|
||||
}
|
||||
}, duration.toMillis());
|
||||
}
|
||||
|
||||
if (!values.containsKey(name)) {
|
||||
values.put(name, new ArrayList<>());
|
||||
}
|
||||
values.get(name).add(value);
|
||||
}
|
||||
|
||||
private void cancel() {
|
||||
if (timer != null && startTime != null) {
|
||||
timer.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
values.clear();
|
||||
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
cancel();
|
||||
clear();
|
||||
startTime = null;
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package hep.dataforge.control.collectors;
|
||||
|
||||
import hep.dataforge.values.Value;
|
||||
import hep.dataforge.values.ValueFactory;
|
||||
|
||||
/**
|
||||
* A collector of values which listens to some input values until condition
|
||||
* satisfied then pushes the result to external listener.
|
||||
*
|
||||
* @author <a href="mailto:altavir@gmail.com">Alexander Nozik</a>
|
||||
*/
|
||||
public interface ValueCollector {
|
||||
|
||||
void put(String name, Value value);
|
||||
|
||||
default void put(String name, Object value) {
|
||||
put(name, ValueFactory.of(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Send current cached result to listener. Could be used to force collect
|
||||
* even if not all values are present.
|
||||
*/
|
||||
void collect();
|
||||
|
||||
/**
|
||||
* Clear currently collected data
|
||||
*/
|
||||
void clear();
|
||||
|
||||
}
|
@ -0,0 +1,157 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package hep.dataforge.control.measurements;
|
||||
|
||||
import hep.dataforge.exceptions.MeasurementException;
|
||||
import hep.dataforge.utils.DateTimeUtils;
|
||||
import kotlin.Pair;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* A boilerplate code for measurements
|
||||
*
|
||||
* @author Alexander Nozik
|
||||
*/
|
||||
@Deprecated
|
||||
public abstract class AbstractMeasurement<T> implements Measurement<T> {
|
||||
|
||||
// protected final ReferenceRegistry<MeasurementListener<T>> listeners = new ReferenceRegistry<>();
|
||||
protected Pair<T, Instant> lastResult;
|
||||
protected Throwable exception;
|
||||
private MeasurementState state;
|
||||
|
||||
protected MeasurementState getMeasurementState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
protected void setMeasurementState(MeasurementState state) {
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call after measurement started
|
||||
*/
|
||||
protected void afterStart() {
|
||||
setMeasurementState(MeasurementState.PENDING);
|
||||
notifyListeners(it -> it.onMeasurementStarted(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Call after measurement stopped
|
||||
*/
|
||||
protected void afterStop() {
|
||||
setMeasurementState(MeasurementState.FINISHED);
|
||||
notifyListeners(it -> it.onMeasurementFinished(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset measurement to initial state
|
||||
*/
|
||||
protected void afterPause() {
|
||||
setMeasurementState(MeasurementState.OK);
|
||||
notifyListeners(it -> it.onMeasurementFinished(this));
|
||||
}
|
||||
|
||||
protected synchronized void onError(String message, Throwable error) {
|
||||
LoggerFactory.getLogger(getClass()).error("Measurement failed with error: " + message, error);
|
||||
setMeasurementState(MeasurementState.FAILED);
|
||||
this.exception = error;
|
||||
notify();
|
||||
notifyListeners(it -> it.onMeasurementFailed(this, error));
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal method to notify measurement complete. Uses current system time
|
||||
*
|
||||
* @param result
|
||||
*/
|
||||
protected final void result(T result) {
|
||||
result(result, DateTimeUtils.now());
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal method to notify measurement complete
|
||||
*
|
||||
* @param result
|
||||
*/
|
||||
protected synchronized void result(T result, Instant time) {
|
||||
this.lastResult = new Pair<>(result, time);
|
||||
setMeasurementState(MeasurementState.OK);
|
||||
notify();
|
||||
notifyListeners(it -> it.onMeasurementResult(this, result, time));
|
||||
}
|
||||
|
||||
protected void updateProgress(double progress) {
|
||||
notifyListeners(it -> it.onMeasurementProgress(this, progress));
|
||||
}
|
||||
|
||||
protected void updateMessage(String message) {
|
||||
notifyListeners(it -> it.onMeasurementProgress(this, message));
|
||||
}
|
||||
|
||||
protected final void notifyListeners(Consumer<MeasurementListener> consumer) {
|
||||
getDevice().forEachConnection(MeasurementListener.class, consumer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFinished() {
|
||||
return state == MeasurementState.FINISHED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStarted() {
|
||||
return state == MeasurementState.PENDING || state == MeasurementState.OK;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Throwable getError() {
|
||||
return this.exception;
|
||||
}
|
||||
|
||||
protected synchronized Pair<T, Instant> get() throws MeasurementException {
|
||||
if (getMeasurementState() == MeasurementState.INIT) {
|
||||
start();
|
||||
LoggerFactory.getLogger(getClass()).debug("Measurement not started. Starting");
|
||||
}
|
||||
while (state == MeasurementState.PENDING) {
|
||||
try {
|
||||
//Wait for result could cause deadlock if called in main thread
|
||||
wait();
|
||||
} catch (InterruptedException ex) {
|
||||
throw new MeasurementException(ex);
|
||||
}
|
||||
}
|
||||
if (this.lastResult != null) {
|
||||
return this.lastResult;
|
||||
} else if (state == MeasurementState.FAILED) {
|
||||
throw new MeasurementException(getError());
|
||||
} else {
|
||||
throw new MeasurementException("Measurement failed for unknown reason");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Instant getTime() throws MeasurementException {
|
||||
return get().getSecond();
|
||||
}
|
||||
|
||||
@Override
|
||||
public T getResult() throws MeasurementException {
|
||||
return get().getFirst();
|
||||
}
|
||||
|
||||
protected enum MeasurementState {
|
||||
INIT, //Measurement not started
|
||||
PENDING, // Measurement in process
|
||||
OK, // Last measurement complete, next is planned
|
||||
FAILED, // Last measurement failed
|
||||
FINISHED, // Measurement finished or stopped
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package hep.dataforge.control.measurements;
|
||||
|
||||
import hep.dataforge.control.devices.Device;
|
||||
import hep.dataforge.exceptions.MeasurementException;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
/**
|
||||
* A general representation of ongoing or completed measurement. Could be
|
||||
* regular.
|
||||
*
|
||||
* @author Alexander Nozik
|
||||
*/
|
||||
@Deprecated
|
||||
public interface Measurement<T> {
|
||||
|
||||
Device getDevice();
|
||||
|
||||
/**
|
||||
* Begin the measurement
|
||||
*/
|
||||
void start();
|
||||
|
||||
/**
|
||||
* Stop the measurement
|
||||
*
|
||||
* @param force force stop if measurement in progress
|
||||
* @throws MeasurementException
|
||||
*/
|
||||
boolean stop(boolean force) throws MeasurementException;
|
||||
|
||||
/**
|
||||
* Measurement is started
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
boolean isStarted();
|
||||
|
||||
/**
|
||||
* Measurement is complete or stopped and could be recycled
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
boolean isFinished();
|
||||
|
||||
/**
|
||||
* Get the time of the last measurement
|
||||
*
|
||||
* @return
|
||||
* @throws MeasurementException
|
||||
*/
|
||||
Instant getTime() throws MeasurementException;
|
||||
|
||||
/**
|
||||
* Get last measurement result or wait for measurement to complete and
|
||||
* return its result. Synchronous call.
|
||||
*
|
||||
* @return
|
||||
* @throws MeasurementException
|
||||
*/
|
||||
T getResult() throws MeasurementException;
|
||||
|
||||
/**
|
||||
* Last thrown exception. Null if no exceptions are thrown
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
Throwable getError();
|
||||
}
|
@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright 2015 Alexander Nozik.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package hep.dataforge.control.measurements;
|
||||
|
||||
import hep.dataforge.exceptions.MeasurementException;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
/**
|
||||
* A listener for device measurements
|
||||
*
|
||||
* @author Alexander Nozik
|
||||
*/
|
||||
@Deprecated
|
||||
public interface MeasurementListener {
|
||||
|
||||
/**
|
||||
* Measurement started. Ignored by default
|
||||
* @param measurement
|
||||
*/
|
||||
default void onMeasurementStarted(Measurement<?> measurement){
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Measurement stopped. Ignored by default
|
||||
* @param measurement
|
||||
*/
|
||||
default void onMeasurementFinished(Measurement<?> measurement){
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Measurement result obtained
|
||||
* @param measurement
|
||||
* @param result
|
||||
*/
|
||||
void onMeasurementResult(Measurement<?> measurement, Object result, Instant time);
|
||||
|
||||
/**
|
||||
* Measurement failed with exception
|
||||
* @param measurement
|
||||
* @param exception
|
||||
*/
|
||||
void onMeasurementFailed(Measurement<?> measurement, Throwable exception);
|
||||
|
||||
/**
|
||||
* Measurement failed with message
|
||||
* @param measurement
|
||||
* @param message
|
||||
*/
|
||||
default void onMeasurementFailed(Measurement<?> measurement, String message){
|
||||
onMeasurementFailed(measurement, new MeasurementException(message));
|
||||
}
|
||||
|
||||
/**
|
||||
* Measurement progress updated. Ignored by default
|
||||
* @param measurement
|
||||
* @param progress
|
||||
*/
|
||||
default void onMeasurementProgress(Measurement<?> measurement, double progress) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Measurement progress message updated. Ignored by default
|
||||
* @param measurement
|
||||
* @param message
|
||||
*/
|
||||
default void onMeasurementProgress(Measurement<?> measurement, String message) {
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package hep.dataforge.control.measurements;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Alexander Nozik
|
||||
*/
|
||||
@Deprecated
|
||||
public abstract class RegularMeasurement<T> extends SimpleMeasurement<T> {
|
||||
|
||||
private boolean stopFlag = false;
|
||||
|
||||
@Override
|
||||
protected void finishTask() {
|
||||
if (stopFlag || (stopOnError() && getMeasurementState() == MeasurementState.FAILED)) {
|
||||
afterStop();
|
||||
} else {
|
||||
startTask();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean stop(boolean force) {
|
||||
if (isFinished()) {
|
||||
return false;
|
||||
} else if (force) {
|
||||
return interruptTask(force);
|
||||
} else {
|
||||
stopFlag = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean stopOnError() {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected abstract Duration getDelay();
|
||||
|
||||
}
|
@ -0,0 +1,142 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package hep.dataforge.control.measurements;
|
||||
|
||||
import hep.dataforge.exceptions.MeasurementException;
|
||||
import hep.dataforge.utils.DateTimeUtils;
|
||||
import kotlin.Pair;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.concurrent.FutureTask;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* A simple one-time measurement wrapping FutureTask. Could be restarted
|
||||
*
|
||||
* @author Alexander Nozik
|
||||
*/
|
||||
@Deprecated
|
||||
public abstract class SimpleMeasurement<T> extends AbstractMeasurement<T> {
|
||||
|
||||
private FutureTask<Pair<T, Instant>> task;
|
||||
|
||||
/**
|
||||
* invalidate current task. New task will be created on next getFuture call.
|
||||
* This method does not guarantee that task is finished when it is cleared
|
||||
*/
|
||||
private void clearTask() {
|
||||
task = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform synchronous measurement
|
||||
*
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
protected abstract T doMeasure() throws Exception;
|
||||
|
||||
@Override
|
||||
public synchronized void start() {
|
||||
//PENDING do we need executor here?
|
||||
//Executors.newSingleThreadExecutor().submit(getTask());
|
||||
if (!isStarted()) {
|
||||
afterStart();
|
||||
startTask();
|
||||
} else {
|
||||
LoggerFactory.getLogger(getClass()).warn("Alredy started");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized boolean stop(boolean force) {
|
||||
if (isStarted()) {
|
||||
afterStop();
|
||||
return interruptTask(force);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean interruptTask(boolean force) {
|
||||
if (task != null) {
|
||||
if (task.isCancelled() || task.isDone()) {
|
||||
task = null;
|
||||
return true;
|
||||
} else {
|
||||
return task.cancel(force);
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
protected ThreadGroup getThreadGroup() {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected Duration getMeasurementTimeout() {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected String getThreadName() {
|
||||
return "measurement thread";
|
||||
}
|
||||
|
||||
protected void startTask() {
|
||||
Runnable process = () -> {
|
||||
Pair<T, Instant> res;
|
||||
try {
|
||||
Duration timeout = getMeasurementTimeout();
|
||||
task = buildTask();
|
||||
task.run();
|
||||
if (timeout == null) {
|
||||
res = task.get();
|
||||
} else {
|
||||
res = task.get(getMeasurementTimeout().toMillis(), TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
|
||||
if (res != null) {
|
||||
result(res.getFirst(), res.getSecond());
|
||||
} else {
|
||||
throw new MeasurementException("Empty result");
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
onError("failed to start measurement task", ex);
|
||||
}
|
||||
clearTask();
|
||||
finishTask();
|
||||
};
|
||||
new Thread(getThreadGroup(), process, getThreadName()).start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset measurement task and notify listeners
|
||||
*/
|
||||
protected void finishTask() {
|
||||
afterStop();
|
||||
}
|
||||
|
||||
private FutureTask<Pair<T, Instant>> buildTask() {
|
||||
return new FutureTask<>(() -> {
|
||||
try {
|
||||
T res = doMeasure();
|
||||
if (res == null) {
|
||||
return null;
|
||||
}
|
||||
Instant time = DateTimeUtils.now();
|
||||
return new Pair<>(res, time);
|
||||
} catch (Exception ex) {
|
||||
onError("failed to report measurement results", ex);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 2015 Alexander Nozik.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package hep.dataforge.exceptions;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Alexander Nozik
|
||||
*/
|
||||
public class ControlException extends Exception {
|
||||
|
||||
/**
|
||||
* Creates a new instance of <code>ControlException</code> without detail
|
||||
* message.
|
||||
*/
|
||||
public ControlException() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an instance of <code>ControlException</code> with the
|
||||
* specified detail message.
|
||||
*
|
||||
* @param msg the detail message.
|
||||
*/
|
||||
public ControlException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
|
||||
public ControlException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public ControlException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
protected ControlException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
|
||||
super(message, cause, enableSuppression, writableStackTrace);
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2015 Alexander Nozik.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package hep.dataforge.exceptions;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Alexander Nozik
|
||||
*/
|
||||
public class MeasurementException extends ControlException {
|
||||
|
||||
/**
|
||||
* Creates a new instance of <code>MeasurementException</code> without
|
||||
* detail message.
|
||||
*/
|
||||
public MeasurementException() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an instance of <code>MeasurementException</code> with the
|
||||
* specified detail message.
|
||||
*
|
||||
* @param msg the detail message.
|
||||
*/
|
||||
public MeasurementException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
|
||||
public MeasurementException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public MeasurementException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2015 Alexander Nozik.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package hep.dataforge.exceptions;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Alexander Nozik
|
||||
*/
|
||||
public class MeasurementInterruptedException extends MeasurementException {
|
||||
|
||||
/**
|
||||
* Creates a new instance of <code>MeasurementInterruptedException</code>
|
||||
* without detail message.
|
||||
*/
|
||||
public MeasurementInterruptedException() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an instance of <code>MeasurementInterruptedException</code>
|
||||
* with the specified detail message.
|
||||
*
|
||||
* @param msg the detail message.
|
||||
*/
|
||||
public MeasurementInterruptedException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
|
||||
public MeasurementInterruptedException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public MeasurementInterruptedException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2015 Alexander Nozik.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package hep.dataforge.exceptions;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Alexander Nozik
|
||||
*/
|
||||
public class MeasurementNotReadyException extends MeasurementException {
|
||||
|
||||
/**
|
||||
* Creates a new instance of <code>MeasurementNotReadyException</code>
|
||||
* without detail message.
|
||||
*/
|
||||
public MeasurementNotReadyException() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an instance of <code>MeasurementNotReadyException</code> with
|
||||
* the specified detail message.
|
||||
*
|
||||
* @param msg the detail message.
|
||||
*/
|
||||
public MeasurementNotReadyException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2015 Alexander Nozik.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package hep.dataforge.exceptions;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Alexander Nozik
|
||||
*/
|
||||
public class MeasurementTimeoutException extends MeasurementException {
|
||||
|
||||
/**
|
||||
* Creates a new instance of <code>MeasurementTimeoutException</code>
|
||||
* without detail message.
|
||||
*/
|
||||
public MeasurementTimeoutException() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an instance of <code>MeasurementTimeoutException</code> with
|
||||
* the specified detail message.
|
||||
*
|
||||
* @param msg the detail message.
|
||||
*/
|
||||
public MeasurementTimeoutException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 2015 Alexander Nozik.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package hep.dataforge.exceptions;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Alexander Nozik
|
||||
*/
|
||||
public class PortException extends ControlException {
|
||||
|
||||
/**
|
||||
* Creates a new instance of <code>PortException</code> without detail
|
||||
* message.
|
||||
*/
|
||||
public PortException() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an instance of <code>PortException</code> with the specified
|
||||
* detail message.
|
||||
*
|
||||
* @param msg the detail message.
|
||||
*/
|
||||
public PortException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
|
||||
public PortException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public PortException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright 2015 Alexander Nozik.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package hep.dataforge.exceptions;
|
||||
|
||||
public class PortLockException extends RuntimeException {
|
||||
|
||||
public PortLockException() {
|
||||
}
|
||||
|
||||
public PortLockException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,130 @@
|
||||
/*
|
||||
* Copyright 2018 Alexander Nozik.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package hep.dataforge.control
|
||||
|
||||
import hep.dataforge.connections.Connection
|
||||
import hep.dataforge.context.*
|
||||
import hep.dataforge.control.devices.Device
|
||||
import hep.dataforge.control.devices.DeviceFactory
|
||||
import hep.dataforge.control.devices.DeviceHub
|
||||
import hep.dataforge.exceptions.ControlException
|
||||
import hep.dataforge.meta.Meta
|
||||
import hep.dataforge.names.Name
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* A plugin for creating and using different devices
|
||||
* Created by darksnake on 11-Oct-16.
|
||||
*/
|
||||
@PluginDef(name = "devices", info = "Management plugin for devices an their interaction")
|
||||
class DeviceManager : BasicPlugin(), DeviceHub {
|
||||
|
||||
/**
|
||||
* Registered devices
|
||||
*/
|
||||
private val _devices = HashMap<Name, Device>()
|
||||
|
||||
/**
|
||||
* the list of top level devices
|
||||
*/
|
||||
val devices: Collection<Device> = _devices.values
|
||||
|
||||
override val deviceNames: List<Name>
|
||||
get() = _devices.entries.flatMap { entry ->
|
||||
if (entry.value is DeviceHub) {
|
||||
(entry.value as DeviceHub).deviceNames.map { it -> entry.key.plus(it) }
|
||||
} else {
|
||||
listOf(entry.key)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun add(device: Device) {
|
||||
val name = Name.ofSingle(device.name)
|
||||
if (_devices.containsKey(name)) {
|
||||
logger.warn("Replacing existing device in device manager!")
|
||||
remove(name)
|
||||
}
|
||||
_devices[name] = device
|
||||
}
|
||||
|
||||
fun remove(name: Name) {
|
||||
Optional.ofNullable(this._devices.remove(name)).ifPresent { it ->
|
||||
try {
|
||||
it.shutdown()
|
||||
} catch (e: ControlException) {
|
||||
logger.error("Failed to stop the device: " + it.name, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun buildDevice(deviceMeta: Meta): Device {
|
||||
val factory = context
|
||||
.findService(DeviceFactory::class.java) { it.type == ControlUtils.getDeviceType(deviceMeta) }
|
||||
?: throw RuntimeException("Can't find factory for given device type")
|
||||
val device = factory.build(context, deviceMeta)
|
||||
|
||||
deviceMeta.getMetaList("connection").forEach { connectionMeta -> device.connectionHelper.connect(context, connectionMeta) }
|
||||
|
||||
add(device)
|
||||
return device
|
||||
}
|
||||
|
||||
override fun optDevice(name: Name): Optional<Device> {
|
||||
return when {
|
||||
name.isEmpty() -> throw IllegalArgumentException("Can't provide a device with zero name")
|
||||
name.length == 1 -> Optional.ofNullable(_devices[name])
|
||||
else -> Optional.ofNullable(_devices[name.first]).flatMap { hub ->
|
||||
if (hub is DeviceHub) {
|
||||
(hub as DeviceHub).optDevice(name.cutFirst())
|
||||
} else {
|
||||
Optional.empty()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun detach() {
|
||||
_devices.values.forEach { it ->
|
||||
try {
|
||||
it.shutdown()
|
||||
} catch (e: ControlException) {
|
||||
logger.error("Failed to stop the device: " + it.name, e)
|
||||
}
|
||||
}
|
||||
super.detach()
|
||||
}
|
||||
|
||||
override fun connectAll(connection: Connection, vararg roles: String) {
|
||||
this._devices.values.forEach { device -> device.connect(connection, *roles) }
|
||||
}
|
||||
|
||||
override fun connectAll(context: Context, meta: Meta) {
|
||||
this._devices.values.forEach { device -> device.connectionHelper.connect(context, meta) }
|
||||
}
|
||||
|
||||
class Factory : PluginFactory() {
|
||||
|
||||
override val type: Class<out Plugin>
|
||||
get() = DeviceManager::class.java
|
||||
|
||||
override fun build(meta: Meta): Plugin {
|
||||
return DeviceManager()
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package hep.dataforge.control.connections
|
||||
|
||||
import hep.dataforge.connections.Connection
|
||||
import hep.dataforge.control.devices.Device
|
||||
|
||||
abstract class DeviceConnection : Connection {
|
||||
|
||||
private var _device: Device? = null
|
||||
val device: Device
|
||||
get() = _device ?: throw RuntimeException("Connection closed")
|
||||
|
||||
override fun isOpen(): Boolean {
|
||||
return this._device != null
|
||||
}
|
||||
|
||||
override fun open(device: Any) {
|
||||
this._device = Device::class.java.cast(device)
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
this._device = null
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package hep.dataforge.control.connections
|
||||
|
||||
import hep.dataforge.connections.Connection
|
||||
import hep.dataforge.context.Context
|
||||
import hep.dataforge.context.ContextAware
|
||||
import hep.dataforge.context.launch
|
||||
import hep.dataforge.storage.tables.MutableTableLoader
|
||||
import hep.dataforge.tables.ValuesListener
|
||||
import hep.dataforge.values.Values
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Alexander Nozik
|
||||
*/
|
||||
class LoaderConnection(private val loader: MutableTableLoader) : Connection, ValuesListener, ContextAware {
|
||||
|
||||
override val context: Context
|
||||
get() = loader.context
|
||||
|
||||
override fun accept(point: Values) {
|
||||
launch {
|
||||
loader.append(point)
|
||||
}
|
||||
}
|
||||
|
||||
override fun isOpen(): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun open(`object`: Any) {
|
||||
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
override fun close() {
|
||||
loader.close()
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package hep.dataforge.control.connections
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Alexander Nozik
|
||||
*/
|
||||
object Roles {
|
||||
const val DEVICE_LISTENER_ROLE = "deviceListener"
|
||||
const val MEASUREMENT_LISTENER_ROLE = "measurementListener"
|
||||
const val STORAGE_ROLE = "storage"
|
||||
const val VIEW_ROLE = "view"
|
||||
}
|
@ -0,0 +1,199 @@
|
||||
/*
|
||||
* Copyright 2017 Alexander Nozik.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package hep.dataforge.control.devices
|
||||
|
||||
import hep.dataforge.connections.Connection
|
||||
import hep.dataforge.connections.ConnectionHelper
|
||||
import hep.dataforge.context.Context
|
||||
import hep.dataforge.context.Global
|
||||
import hep.dataforge.control.connections.Roles
|
||||
import hep.dataforge.control.devices.Device.Companion.INITIALIZED_STATE
|
||||
import hep.dataforge.description.ValueDef
|
||||
import hep.dataforge.events.Event
|
||||
import hep.dataforge.events.EventHandler
|
||||
import hep.dataforge.exceptions.ControlException
|
||||
import hep.dataforge.listAnnotations
|
||||
import hep.dataforge.meta.Meta
|
||||
import hep.dataforge.meta.MetaHolder
|
||||
import hep.dataforge.names.AnonymousNotAlowed
|
||||
import hep.dataforge.states.*
|
||||
import hep.dataforge.values.ValueType
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.launch
|
||||
import java.time.Duration
|
||||
import java.util.concurrent.*
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* State has two components: physical and logical. If logical state does not
|
||||
* coincide with physical, it should be invalidated and automatically updated on
|
||||
* next request.
|
||||
*
|
||||
*
|
||||
* @author Alexander Nozik
|
||||
*/
|
||||
@AnonymousNotAlowed
|
||||
@StateDef(
|
||||
value = ValueDef(
|
||||
key = INITIALIZED_STATE,
|
||||
type = [ValueType.BOOLEAN],
|
||||
def = "false",
|
||||
info = "Initialization state of the device"
|
||||
), writable = true
|
||||
)
|
||||
abstract class AbstractDevice(override final val context: Context = Global, meta: Meta) : MetaHolder(meta), Device {
|
||||
|
||||
final override val states = StateHolder()
|
||||
|
||||
val initializedState: ValueState = valueState(INITIALIZED_STATE) { old, value ->
|
||||
if (old != value) {
|
||||
if (value.boolean) {
|
||||
init()
|
||||
} else {
|
||||
shutdown()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialization state
|
||||
*/
|
||||
val initialized by initializedState.booleanDelegate
|
||||
|
||||
private var stateListenerJob: Job? = null
|
||||
|
||||
private val _connectionHelper: ConnectionHelper by lazy { ConnectionHelper(this) }
|
||||
|
||||
override fun getConnectionHelper(): ConnectionHelper {
|
||||
return _connectionHelper
|
||||
}
|
||||
|
||||
/**
|
||||
* A single thread executor for this device. All state changes and similar work must be done on this thread.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
protected val executor: ScheduledExecutorService = Executors.newSingleThreadScheduledExecutor { r ->
|
||||
val res = Thread(r)
|
||||
res.name = "device::$name"
|
||||
res.priority = Thread.MAX_PRIORITY
|
||||
res.isDaemon = true
|
||||
res
|
||||
}
|
||||
|
||||
init {
|
||||
//initialize states
|
||||
javaClass.listAnnotations(StateDef::class.java, true).forEach {
|
||||
states.init(ValueState(it))
|
||||
}
|
||||
javaClass.listAnnotations(MetaStateDef::class.java, true).forEach {
|
||||
states.init(MetaState(it))
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(ControlException::class)
|
||||
override fun init() {
|
||||
logger.info("Initializing device '{}'...", name)
|
||||
states.update(INITIALIZED_STATE, true)
|
||||
stateListenerJob = context.launch {
|
||||
val flow = states.changes()
|
||||
flow.collect {
|
||||
onStateChange(it.first, it.second)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(ControlException::class)
|
||||
override fun shutdown() {
|
||||
logger.info("Shutting down device '{}'...", name)
|
||||
forEachConnection(Connection::class.java) { c ->
|
||||
try {
|
||||
c.close()
|
||||
} catch (e: Exception) {
|
||||
logger.error("Failed to close connection", e)
|
||||
}
|
||||
}
|
||||
states.update(INITIALIZED_STATE, false)
|
||||
stateListenerJob?.cancel()
|
||||
executor.shutdown()
|
||||
}
|
||||
|
||||
|
||||
override val name: String
|
||||
get() = meta.getString("name", type)
|
||||
|
||||
protected fun runOnDeviceThread(runnable: () -> Unit): Future<*> {
|
||||
return executor.submit(runnable)
|
||||
}
|
||||
|
||||
protected fun <T> callOnDeviceThread(callable: () -> T): Future<T> {
|
||||
return executor.submit(callable)
|
||||
}
|
||||
|
||||
protected fun scheduleOnDeviceThread(delay: Duration, runnable: () -> Unit): ScheduledFuture<*> {
|
||||
return executor.schedule(runnable, delay.toMillis(), TimeUnit.MILLISECONDS)
|
||||
}
|
||||
|
||||
protected fun repeatOnDeviceThread(
|
||||
interval: Duration,
|
||||
delay: Duration = Duration.ZERO,
|
||||
runnable: () -> Unit
|
||||
): ScheduledFuture<*> {
|
||||
return executor.scheduleWithFixedDelay(runnable, delay.toMillis(), interval.toMillis(), TimeUnit.MILLISECONDS)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Override to apply custom internal reaction of state change
|
||||
*/
|
||||
protected open fun onStateChange(stateName: String, value: Any) {
|
||||
forEachConnection(Roles.DEVICE_LISTENER_ROLE, DeviceListener::class.java) {
|
||||
it.notifyStateChanged(this, stateName, value)
|
||||
}
|
||||
}
|
||||
|
||||
override val type: String
|
||||
get() = meta.getString("type", "unknown")
|
||||
|
||||
protected fun updateState(stateName: String, value: Any?) {
|
||||
states.update(stateName, value)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
val Device.initialized: Boolean
|
||||
get() {
|
||||
return if (this is AbstractDevice) {
|
||||
this.initialized
|
||||
} else {
|
||||
this.states
|
||||
.filter { it.name == INITIALIZED_STATE }
|
||||
.filterIsInstance(ValueState::class.java).firstOrNull()?.value?.boolean ?: false
|
||||
}
|
||||
}
|
||||
|
||||
fun Device.notifyError(message: String, error: Throwable? = null) {
|
||||
logger.error(message, error)
|
||||
forEachConnection(DeviceListener::class.java) {
|
||||
it.evaluateDeviceException(this, message, error)
|
||||
}
|
||||
}
|
||||
|
||||
fun Device.dispatchEvent(event: Event) {
|
||||
forEachConnection(EventHandler::class.java) { it -> it.pushEvent(event) }
|
||||
}
|
@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Copyright 2018 Alexander Nozik.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package hep.dataforge.control.devices
|
||||
|
||||
import hep.dataforge.Named
|
||||
import hep.dataforge.connections.AutoConnectible
|
||||
import hep.dataforge.connections.Connection.EVENT_HANDLER_ROLE
|
||||
import hep.dataforge.connections.Connection.LOGGER_ROLE
|
||||
import hep.dataforge.connections.RoleDef
|
||||
import hep.dataforge.connections.RoleDefs
|
||||
import hep.dataforge.context.ContextAware
|
||||
import hep.dataforge.control.connections.Roles.DEVICE_LISTENER_ROLE
|
||||
import hep.dataforge.control.connections.Roles.VIEW_ROLE
|
||||
import hep.dataforge.events.EventHandler
|
||||
import hep.dataforge.exceptions.ControlException
|
||||
import hep.dataforge.meta.Metoid
|
||||
import hep.dataforge.states.Stateful
|
||||
import org.slf4j.Logger
|
||||
|
||||
|
||||
/**
|
||||
* The Device is general abstract representation of any physical or virtual
|
||||
* apparatus that can interface with data acquisition and control system.
|
||||
*
|
||||
*
|
||||
* The device has following important features:
|
||||
*
|
||||
*
|
||||
* *
|
||||
* **States:** each device has a number of states that could be
|
||||
* accessed by `getState` method. States could be either stored as some
|
||||
* internal variables or calculated on demand. States calculation is
|
||||
* synchronous!
|
||||
*
|
||||
* *
|
||||
* **Listeners:** some external class which listens device state
|
||||
* changes and events. By default listeners are represented by weak references
|
||||
* so they could be finalized any time if not used.
|
||||
* *
|
||||
* **Connections:** any external device connectors which are used
|
||||
* by device. The difference between listener and connection is that device is
|
||||
* obligated to notify all registered listeners about all changes, but
|
||||
* connection is used by device at its own discretion. Also usually only one
|
||||
* connection is used for each single purpose.
|
||||
*
|
||||
*
|
||||
*
|
||||
* @author Alexander Nozik
|
||||
*/
|
||||
@RoleDefs(
|
||||
RoleDef(name = DEVICE_LISTENER_ROLE, objectType = DeviceListener::class, info = "A device listener"),
|
||||
RoleDef(name = LOGGER_ROLE, objectType = Logger::class, unique = true, info = "The logger for this device"),
|
||||
RoleDef(name = EVENT_HANDLER_ROLE, objectType = EventHandler::class, info = "The listener for device events"),
|
||||
RoleDef(name = VIEW_ROLE)
|
||||
)
|
||||
interface Device : AutoConnectible, Metoid, ContextAware, Named, Stateful {
|
||||
|
||||
/**
|
||||
* Device type
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
val type: String
|
||||
|
||||
@JvmDefault
|
||||
override val logger: Logger
|
||||
get() = optConnection(LOGGER_ROLE, Logger::class.java).orElse(context.logger)
|
||||
|
||||
/**
|
||||
* Initialize device and check if it is working but do not start any
|
||||
* measurements or issue commands. Init method could be called only once per
|
||||
* MeasurementDevice object. On second call it throws exception or does
|
||||
* nothing.
|
||||
*
|
||||
* @throws ControlException
|
||||
*/
|
||||
@Throws(ControlException::class)
|
||||
fun init()
|
||||
|
||||
/**
|
||||
* Release all resources locked during init. No further work with device is
|
||||
* possible after shutdown. The init method called after shutdown can cause
|
||||
* exceptions or incorrect work.
|
||||
*
|
||||
* @throws ControlException
|
||||
*/
|
||||
@Throws(ControlException::class)
|
||||
fun shutdown()
|
||||
|
||||
|
||||
companion object {
|
||||
const val INITIALIZED_STATE = "initialized"
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2018 Alexander Nozik.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package hep.dataforge.control.devices
|
||||
|
||||
import hep.dataforge.utils.ContextMetaFactory
|
||||
|
||||
/**
|
||||
* Created by darksnake on 06-May-17.
|
||||
*/
|
||||
interface DeviceFactory : ContextMetaFactory<Device> {
|
||||
/**
|
||||
* The type of the device factory. One factory can supply multiple device classes depending on configuration.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
val type: String
|
||||
}
|
@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright 2018 Alexander Nozik.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package hep.dataforge.control.devices
|
||||
|
||||
import hep.dataforge.connections.Connection
|
||||
import hep.dataforge.context.Context
|
||||
import hep.dataforge.meta.Meta
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.providers.Provider
|
||||
import hep.dataforge.providers.Provides
|
||||
import hep.dataforge.providers.ProvidesNames
|
||||
import java.util.*
|
||||
import java.util.stream.Stream
|
||||
|
||||
/**
|
||||
* A hub containing several devices
|
||||
*/
|
||||
interface DeviceHub : Provider {
|
||||
|
||||
val deviceNames: List<Name>
|
||||
|
||||
fun optDevice(name: Name): Optional<Device>
|
||||
|
||||
@Provides(DEVICE_TARGET)
|
||||
fun optDevice(name: String): Optional<Device> {
|
||||
return optDevice(Name.of(name))
|
||||
}
|
||||
|
||||
@ProvidesNames(DEVICE_TARGET)
|
||||
fun listDevices(): Stream<String> {
|
||||
return deviceNames.stream().map{ it.toString() }
|
||||
}
|
||||
|
||||
fun getDevices(recursive: Boolean): Stream<Device> {
|
||||
return if (recursive) {
|
||||
deviceNames.stream().map { it -> optDevice(it).get() }
|
||||
} else {
|
||||
deviceNames.stream().filter { it -> it.length == 1 }.map { it -> optDevice(it).get() }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a connection to each of child devices
|
||||
*
|
||||
* @param connection
|
||||
* @param roles
|
||||
*/
|
||||
fun connectAll(connection: Connection, vararg roles: String) {
|
||||
deviceNames.stream().filter { it -> it.length == 1 }
|
||||
.map<Optional<Device>>{ this.optDevice(it) }
|
||||
.map<Device>{ it.get() }
|
||||
.forEach { it ->
|
||||
if (it is DeviceHub) {
|
||||
(it as DeviceHub).connectAll(connection, *roles)
|
||||
} else {
|
||||
it.connect(connection, *roles)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun connectAll(context: Context, meta: Meta) {
|
||||
deviceNames.stream().filter { it -> it.length == 1 }
|
||||
.map<Optional<Device>>{ this.optDevice(it) }
|
||||
.map<Device>{ it.get() }
|
||||
.forEach { it ->
|
||||
if (it is DeviceHub) {
|
||||
(it as DeviceHub).connectAll(context, meta)
|
||||
} else {
|
||||
it.connectionHelper.connect(context, meta)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val DEVICE_TARGET = "device"
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2018 Alexander Nozik.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package hep.dataforge.control.devices
|
||||
|
||||
import hep.dataforge.connections.Connection
|
||||
|
||||
/**
|
||||
* A listener that listens to device state change initialization and shut down
|
||||
*
|
||||
* @author Alexander Nozik
|
||||
*/
|
||||
interface DeviceListener: Connection {
|
||||
|
||||
/**
|
||||
* Notify that state of device is changed.
|
||||
*
|
||||
* @param device
|
||||
* @param name the name of the state
|
||||
* @param state
|
||||
*/
|
||||
fun notifyStateChanged(device: Device, name: String, state: Any)
|
||||
|
||||
/**
|
||||
*
|
||||
* @param device
|
||||
* @param message
|
||||
* @param exception
|
||||
*/
|
||||
fun evaluateDeviceException(device: Device, message: String, exception: Throwable?) {
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,208 @@
|
||||
/*
|
||||
* Copyright 2017 Alexander Nozik.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package hep.dataforge.control.devices
|
||||
|
||||
import hep.dataforge.context.Context
|
||||
import hep.dataforge.control.devices.PortSensor.Companion.CONNECTED_STATE
|
||||
import hep.dataforge.control.devices.PortSensor.Companion.DEBUG_STATE
|
||||
import hep.dataforge.control.ports.GenericPortController
|
||||
import hep.dataforge.control.ports.PortFactory
|
||||
import hep.dataforge.description.NodeDef
|
||||
import hep.dataforge.description.ValueDef
|
||||
import hep.dataforge.description.ValueDefs
|
||||
import hep.dataforge.events.EventBuilder
|
||||
import hep.dataforge.exceptions.ControlException
|
||||
import hep.dataforge.meta.Meta
|
||||
import hep.dataforge.nullable
|
||||
import hep.dataforge.states.*
|
||||
import hep.dataforge.useValue
|
||||
import hep.dataforge.values.ValueType.BOOLEAN
|
||||
import hep.dataforge.values.ValueType.NUMBER
|
||||
import java.time.Duration
|
||||
|
||||
/**
|
||||
* A Sensor that uses a Port to obtain data
|
||||
*
|
||||
* @param <T>
|
||||
* @author darksnake
|
||||
*/
|
||||
@StateDefs(
|
||||
StateDef(
|
||||
value = ValueDef(
|
||||
key = CONNECTED_STATE,
|
||||
type = [BOOLEAN],
|
||||
def = "false",
|
||||
info = "The connection state for this device"
|
||||
), writable = true
|
||||
),
|
||||
//StateDef(value = ValueDef(name = PORT_STATE, info = "The name of the port to which this device is connected")),
|
||||
StateDef(
|
||||
value = ValueDef(
|
||||
key = DEBUG_STATE,
|
||||
type = [BOOLEAN],
|
||||
def = "false",
|
||||
info = "If true, then all received phrases would be shown in the log"
|
||||
), writable = true
|
||||
)
|
||||
)
|
||||
@MetaStateDef(
|
||||
value = NodeDef(
|
||||
key = "port",
|
||||
descriptor = "method::hep.dataforge.control.ports.PortFactory.build",
|
||||
info = "Information about port"
|
||||
), writable = true
|
||||
)
|
||||
@ValueDefs(
|
||||
ValueDef(key = "timeout", type = arrayOf(NUMBER), def = "400", info = "A timeout for port response in milliseconds")
|
||||
)
|
||||
abstract class PortSensor(context: Context, meta: Meta) : Sensor(context, meta) {
|
||||
|
||||
private var _connection: GenericPortController? = null
|
||||
protected val connection: GenericPortController
|
||||
get() = _connection ?: throw RuntimeException("Not connected")
|
||||
|
||||
val connected = valueState(CONNECTED_STATE, getter = { connection.port.isOpen }) { old, value ->
|
||||
if (old != value) {
|
||||
logger.info("State 'connect' changed to $value")
|
||||
connect(value.boolean)
|
||||
}
|
||||
update(value)
|
||||
}
|
||||
|
||||
var debug by valueState(DEBUG_STATE) { old, value ->
|
||||
if (old != value) {
|
||||
logger.info("Turning debug mode to $value")
|
||||
setDebugMode(value.boolean)
|
||||
}
|
||||
update(value)
|
||||
}.booleanDelegate
|
||||
|
||||
var port by metaState(PORT_STATE, getter = { connection.port.toMeta() }) { old, value ->
|
||||
if (old != value) {
|
||||
setupConnection(value)
|
||||
}
|
||||
update(value)
|
||||
}.delegate
|
||||
|
||||
private val defaultTimeout: Duration = Duration.ofMillis(meta.getInt("timeout", 400).toLong())
|
||||
|
||||
init {
|
||||
// meta.useMeta(PORT_STATE) {
|
||||
// port = it
|
||||
// }
|
||||
meta.useValue(DEBUG_STATE) {
|
||||
updateState(DEBUG_STATE, it.boolean)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setDebugMode(debugMode: Boolean) {
|
||||
//Add debug listener
|
||||
if (debugMode) {
|
||||
connection.apply {
|
||||
onAnyPhrase("$name[debug]") { phrase -> logger.debug("Device {} received phrase: \n{}", name, phrase) }
|
||||
onError("$name[debug]") { message, error ->
|
||||
logger.error(
|
||||
"Device {} exception: \n{}",
|
||||
name,
|
||||
message,
|
||||
error
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
connection.apply {
|
||||
removePhraseListener("$name[debug]")
|
||||
removeErrorListener("$name[debug]")
|
||||
}
|
||||
}
|
||||
updateState(DEBUG_STATE, debugMode)
|
||||
}
|
||||
|
||||
private fun connect(connected: Boolean) {
|
||||
if (connected) {
|
||||
try {
|
||||
if (_connection == null) {
|
||||
logger.debug("Setting up connection using device meta")
|
||||
val initialPort = meta.optMeta(PORT_STATE).nullable
|
||||
?: meta.optString(PORT_STATE).nullable?.let { PortFactory.nameToMeta(it) }
|
||||
?: Meta.empty()
|
||||
setupConnection(initialPort)
|
||||
}
|
||||
connection.open()
|
||||
this.connected.update(true)
|
||||
} catch (ex: Exception) {
|
||||
notifyError("Failed to open connection", ex)
|
||||
this.connected.update(false)
|
||||
}
|
||||
} else {
|
||||
_connection?.close()
|
||||
_connection = null
|
||||
this.connected.update(false)
|
||||
}
|
||||
}
|
||||
|
||||
protected open fun buildConnection(meta: Meta): GenericPortController {
|
||||
val port = PortFactory.build(meta)
|
||||
return GenericPortController(context, port)
|
||||
}
|
||||
|
||||
private fun setupConnection(portMeta: Meta) {
|
||||
_connection?.close()
|
||||
this._connection = buildConnection(portMeta)
|
||||
setDebugMode(debug)
|
||||
updateState(PORT_STATE, portMeta)
|
||||
}
|
||||
|
||||
@Throws(ControlException::class)
|
||||
override fun shutdown() {
|
||||
super.shutdown()
|
||||
connected.set(false)
|
||||
}
|
||||
|
||||
protected fun sendAndWait(request: String, timeout: Duration = defaultTimeout): String {
|
||||
return connection.sendAndWait(request, timeout) { true }
|
||||
}
|
||||
|
||||
protected fun sendAndWait(
|
||||
request: String,
|
||||
timeout: Duration = defaultTimeout,
|
||||
predicate: (String) -> Boolean
|
||||
): String {
|
||||
return connection.sendAndWait(request, timeout, predicate)
|
||||
}
|
||||
|
||||
protected fun send(message: String) {
|
||||
connection.send(message)
|
||||
dispatchEvent(
|
||||
EventBuilder
|
||||
.make(name)
|
||||
.setMetaValue("request", message)
|
||||
.build()
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val CONNECTED_STATE = "connected"
|
||||
const val PORT_STATE = "port"
|
||||
const val DEBUG_STATE = "debug"
|
||||
}
|
||||
}
|
@ -0,0 +1,259 @@
|
||||
/*
|
||||
* Copyright 2017 Alexander Nozik.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package hep.dataforge.control.devices
|
||||
|
||||
import hep.dataforge.context.Context
|
||||
import hep.dataforge.control.devices.Sensor.Companion.MEASUREMENT_MESSAGE_STATE
|
||||
import hep.dataforge.control.devices.Sensor.Companion.MEASUREMENT_META_STATE
|
||||
import hep.dataforge.control.devices.Sensor.Companion.MEASUREMENT_PROGRESS_STATE
|
||||
import hep.dataforge.control.devices.Sensor.Companion.MEASUREMENT_RESULT_STATE
|
||||
import hep.dataforge.control.devices.Sensor.Companion.MEASUREMENT_STATUS_STATE
|
||||
import hep.dataforge.control.devices.Sensor.Companion.MEASURING_STATE
|
||||
import hep.dataforge.description.NodeDef
|
||||
import hep.dataforge.description.ValueDef
|
||||
import hep.dataforge.meta.Meta
|
||||
import hep.dataforge.meta.MetaMorph
|
||||
import hep.dataforge.meta.buildMeta
|
||||
import hep.dataforge.states.*
|
||||
import hep.dataforge.values.ValueType
|
||||
import kotlinx.coroutines.InternalCoroutinesApi
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.asCoroutineDispatcher
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.time.delay
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
|
||||
/**
|
||||
* A device with which could perform of one-time or regular measurements. Only one measurement is allowed at a time
|
||||
*
|
||||
* @author Alexander Nozik
|
||||
*/
|
||||
@ValueDef(
|
||||
key = "resultBuffer",
|
||||
type = [ValueType.NUMBER],
|
||||
def = "100",
|
||||
info = "The size of the buffer for results of measurements"
|
||||
)
|
||||
@StateDefs(
|
||||
StateDef(
|
||||
value = ValueDef(
|
||||
key = MEASURING_STATE,
|
||||
type = [ValueType.BOOLEAN],
|
||||
info = "Shows if this sensor is actively measuring"
|
||||
), writable = true
|
||||
),
|
||||
StateDef(
|
||||
ValueDef(
|
||||
key = MEASUREMENT_STATUS_STATE,
|
||||
enumeration = Sensor.MeasurementState::class,
|
||||
info = "Shows if this sensor is actively measuring"
|
||||
)
|
||||
),
|
||||
StateDef(ValueDef(key = MEASUREMENT_MESSAGE_STATE, info = "Current message")),
|
||||
StateDef(ValueDef(key = MEASUREMENT_PROGRESS_STATE, type = [ValueType.NUMBER], info = "Current progress"))
|
||||
)
|
||||
@MetaStateDefs(
|
||||
MetaStateDef(
|
||||
value = NodeDef(key = MEASUREMENT_META_STATE, info = "Configuration of current measurement."),
|
||||
writable = true
|
||||
),
|
||||
MetaStateDef(NodeDef(key = MEASUREMENT_RESULT_STATE, info = "The result of the last measurement in Meta form"))
|
||||
)
|
||||
abstract class Sensor(context: Context, meta: Meta) : AbstractDevice(context, meta) {
|
||||
|
||||
private val coroutineContext = executor.asCoroutineDispatcher()
|
||||
|
||||
protected var job: Job? = null
|
||||
|
||||
val resultState = metaState(MEASUREMENT_RESULT_STATE)
|
||||
|
||||
/**
|
||||
* The result of last measurement
|
||||
*/
|
||||
val result: Meta by resultState.delegate
|
||||
|
||||
/**
|
||||
* The error from last measurement
|
||||
*/
|
||||
val error: Meta by metaState(MEASUREMENT_ERROR_STATE).delegate
|
||||
|
||||
/**
|
||||
* Current measurement configuration
|
||||
*/
|
||||
var measurement by metaState(MEASUREMENT_META_STATE) { old: Meta?, value: Meta ->
|
||||
startMeasurement(old, value)
|
||||
update(value)
|
||||
}.delegate
|
||||
|
||||
/**
|
||||
* true if measurement in process
|
||||
*/
|
||||
val measuring = valueState(MEASURING_STATE) { value ->
|
||||
if (value.boolean) {
|
||||
startMeasurement(null, measurement)
|
||||
} else {
|
||||
stopMeasurement()
|
||||
}
|
||||
update(value)
|
||||
}
|
||||
|
||||
/**
|
||||
* Current state of the measurement
|
||||
*/
|
||||
val measurementState by valueState(MEASUREMENT_STATUS_STATE).enumDelegate<MeasurementState>()
|
||||
|
||||
var message by valueState(MEASUREMENT_MESSAGE_STATE).stringDelegate
|
||||
|
||||
var progress by valueState(MEASUREMENT_PROGRESS_STATE).doubleDelegate
|
||||
|
||||
override fun shutdown() {
|
||||
stopMeasurement()
|
||||
super.shutdown()
|
||||
}
|
||||
|
||||
/**
|
||||
* Start measurement with current configuration if it is not in progress
|
||||
*/
|
||||
fun measure() {
|
||||
if (!measuring.booleanValue) {
|
||||
measuring.set(true)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify measurement state changed
|
||||
*/
|
||||
protected fun notifyMeasurementState(state: MeasurementState) {
|
||||
updateState(MEASUREMENT_STATUS_STATE, state.name)
|
||||
when (state) {
|
||||
MeasurementState.NOT_STARTED -> updateState(MEASURING_STATE, false)
|
||||
MeasurementState.STOPPED -> updateState(MEASURING_STATE, false)
|
||||
MeasurementState.IN_PROGRESS -> updateState(MEASURING_STATE, true)
|
||||
MeasurementState.WAITING -> updateState(MEASURING_STATE, true)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set active measurement using given meta
|
||||
* @param oldMeta Meta of previous active measurement. If null no measurement was set
|
||||
* @param newMeta Meta of new measurement. If null, then clear measurement
|
||||
* @return actual meta for new measurement
|
||||
*/
|
||||
protected abstract fun startMeasurement(oldMeta: Meta?, newMeta: Meta)
|
||||
|
||||
/**
|
||||
* stop measurement with given meta
|
||||
*/
|
||||
protected open fun stopMeasurement() {
|
||||
synchronized(this) {
|
||||
job?.cancel()
|
||||
notifyMeasurementState(MeasurementState.STOPPED)
|
||||
}
|
||||
}
|
||||
|
||||
protected fun measurement(action: suspend () -> Unit) {
|
||||
job = context.launch {
|
||||
notifyMeasurementState(MeasurementState.IN_PROGRESS)
|
||||
action.invoke()
|
||||
notifyMeasurementState(MeasurementState.STOPPED)
|
||||
}
|
||||
}
|
||||
|
||||
protected fun scheduleMeasurement(interval: Duration, action: suspend () -> Unit) {
|
||||
job = context.launch {
|
||||
delay(interval)
|
||||
notifyMeasurementState(MeasurementState.IN_PROGRESS)
|
||||
action.invoke()
|
||||
notifyMeasurementState(MeasurementState.STOPPED)
|
||||
}
|
||||
}
|
||||
|
||||
@InternalCoroutinesApi
|
||||
protected fun regularMeasurement(interval: Duration, action: suspend () -> Unit) {
|
||||
job = context.launch {
|
||||
while (true) {
|
||||
notifyMeasurementState(MeasurementState.IN_PROGRESS)
|
||||
action.invoke()
|
||||
notifyMeasurementState(MeasurementState.WAITING)
|
||||
delay(interval)
|
||||
}
|
||||
}.apply {
|
||||
invokeOnCompletion(onCancelling = true, invokeImmediately = true) {
|
||||
notifyMeasurementState(MeasurementState.STOPPED)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected fun notifyResult(value: Any, timestamp: Instant = Instant.now()) {
|
||||
val result = buildMeta("result") {
|
||||
RESULT_TIMESTAMP to timestamp
|
||||
when (value) {
|
||||
is Meta -> setNode(RESULT_VALUE, value)
|
||||
is MetaMorph -> setNode(RESULT_VALUE, value.toMeta())
|
||||
else -> RESULT_VALUE to value
|
||||
}
|
||||
}
|
||||
updateState(MEASUREMENT_RESULT_STATE, result)
|
||||
forEachConnection(SensorListener::class.java){
|
||||
it.reading(this,value)
|
||||
}
|
||||
}
|
||||
|
||||
protected fun notifyError(value: Any, timestamp: Instant = Instant.now()) {
|
||||
val result = buildMeta("error") {
|
||||
RESULT_TIMESTAMP to timestamp
|
||||
if (value is Meta) {
|
||||
setNode(RESULT_VALUE, value)
|
||||
} else {
|
||||
RESULT_VALUE to value
|
||||
}
|
||||
}
|
||||
updateState(MEASUREMENT_ERROR_STATE, result)
|
||||
}
|
||||
|
||||
enum class MeasurementState {
|
||||
NOT_STARTED, // initial state, not started
|
||||
IN_PROGRESS, // in progress
|
||||
WAITING, // waiting on scheduler
|
||||
STOPPED // stopped
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val MEASURING_STATE = "measurement.active"
|
||||
const val MEASUREMENT_STATUS_STATE = "measurement.state"
|
||||
const val MEASUREMENT_META_STATE = "measurement.meta"
|
||||
const val MEASUREMENT_RESULT_STATE = "measurement.result"
|
||||
const val MEASUREMENT_ERROR_STATE = "measurement.error"
|
||||
const val MEASUREMENT_MESSAGE_STATE = "measurement.message"
|
||||
const val MEASUREMENT_PROGRESS_STATE = "measurement.progress"
|
||||
|
||||
const val RESULT_SUCCESS = "success"
|
||||
const val RESULT_TIMESTAMP = "timestamp"
|
||||
const val RESULT_VALUE = "value"
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
interface SensorListener{
|
||||
fun reading(sensor: Sensor, any:Any)
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright 2017 Alexander Nozik.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package hep.dataforge.control.devices
|
||||
|
||||
import hep.dataforge.context.Context
|
||||
import hep.dataforge.meta.Meta
|
||||
import hep.dataforge.meta.MetaBuilder
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
import java.util.*
|
||||
|
||||
private val VIRTUAL_SENSOR_TYPE = "@test"
|
||||
|
||||
private val generator = Random()
|
||||
|
||||
class VirtualSensor(context: Context) : Sensor(context, Meta.empty()) {
|
||||
override fun startMeasurement(oldMeta: Meta?, newMeta: Meta) {
|
||||
if (oldMeta !== newMeta) {
|
||||
val delay = Duration.parse(newMeta.getString("duration", "PT0.2S"))
|
||||
val mean = newMeta.getDouble("mean", 1.0)
|
||||
val sigma = newMeta.getDouble("sigma", 0.1)
|
||||
|
||||
measurement {
|
||||
Thread.sleep(delay.toMillis())
|
||||
val value = generator.nextDouble() * sigma + mean
|
||||
MetaBuilder("result").setValue("value", value).setValue("timestamp", Instant.now())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override val type: String
|
||||
get() {
|
||||
return VIRTUAL_SENSOR_TYPE
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,162 @@
|
||||
/*
|
||||
* Copyright 2017 Alexander Nozik.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package hep.dataforge.control.ports
|
||||
|
||||
import hep.dataforge.exceptions.PortException
|
||||
import hep.dataforge.meta.Meta
|
||||
import hep.dataforge.meta.buildMeta
|
||||
import jssc.SerialPort
|
||||
import jssc.SerialPort.*
|
||||
import jssc.SerialPortEventListener
|
||||
import jssc.SerialPortException
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.IOException
|
||||
|
||||
/**
|
||||
* @author Alexander Nozik
|
||||
*/
|
||||
class ComPort(val address: String, val config: Meta) : Port() {
|
||||
|
||||
override val name: String = if (address.startsWith("com::")) {
|
||||
address
|
||||
} else {
|
||||
"com::$address"
|
||||
}
|
||||
|
||||
// private static final int CHAR_SIZE = 1;
|
||||
// private static final int MAX_SIZE = 50;
|
||||
private val port: SerialPort by lazy {
|
||||
SerialPort(name)
|
||||
}
|
||||
|
||||
private val serialPortListener = SerialPortEventListener { event ->
|
||||
if (event.isRXCHAR) {
|
||||
val chars = event.eventValue
|
||||
try {
|
||||
val bytes = port.readBytes(chars)
|
||||
receive(bytes)
|
||||
} catch (ex: IOException) {
|
||||
throw RuntimeException(ex)
|
||||
} catch (ex: SerialPortException) {
|
||||
throw RuntimeException(ex)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override val isOpen: Boolean
|
||||
get() = port.isOpened
|
||||
|
||||
|
||||
override fun toString(): String {
|
||||
return name
|
||||
}
|
||||
|
||||
@Throws(PortException::class)
|
||||
override fun open() {
|
||||
try {
|
||||
if (!port.isOpened) {
|
||||
port.apply {
|
||||
openPort()
|
||||
val baudRate = config.getInt("baudRate", BAUDRATE_9600)
|
||||
val dataBits = config.getInt("dataBits", DATABITS_8)
|
||||
val stopBits = config.getInt("stopBits", STOPBITS_1)
|
||||
val parity = config.getInt("parity", PARITY_NONE)
|
||||
setParams(baudRate, dataBits, stopBits, parity)
|
||||
addEventListener(serialPortListener)
|
||||
}
|
||||
}
|
||||
} catch (ex: SerialPortException) {
|
||||
throw PortException("Can't open the port", ex)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Throws(PortException::class)
|
||||
fun clearPort() {
|
||||
try {
|
||||
port.purgePort(PURGE_RXCLEAR or PURGE_TXCLEAR)
|
||||
} catch (ex: SerialPortException) {
|
||||
throw PortException(ex)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
override fun close() {
|
||||
port.let {
|
||||
it.removeEventListener()
|
||||
if (it.isOpened) {
|
||||
it.closePort()
|
||||
}
|
||||
}
|
||||
super.close()
|
||||
}
|
||||
|
||||
@Throws(PortException::class)
|
||||
public override fun send(message: ByteArray) {
|
||||
if (!isOpen) {
|
||||
open()
|
||||
}
|
||||
launch {
|
||||
try {
|
||||
logger.debug("SEND: $message")
|
||||
port.writeBytes(message)
|
||||
} catch (ex: SerialPortException) {
|
||||
throw RuntimeException(ex)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun toMeta(): Meta = buildMeta {
|
||||
"type" to "com"
|
||||
"name" to this@ComPort.name
|
||||
"address" to address
|
||||
update(config)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
/**
|
||||
* Construct ComPort with default parameters:
|
||||
*
|
||||
*
|
||||
* Baud rate: 9600
|
||||
*
|
||||
*
|
||||
* Data bits: 8
|
||||
*
|
||||
*
|
||||
* Stop bits: 1
|
||||
*
|
||||
*
|
||||
* Parity: non
|
||||
*
|
||||
* @param portName
|
||||
*/
|
||||
@JvmOverloads
|
||||
fun create(portName: String, baudRate: Int = BAUDRATE_9600, dataBits: Int = DATABITS_8, stopBits: Int = STOPBITS_1, parity: Int = PARITY_NONE): ComPort {
|
||||
return ComPort(portName, buildMeta {
|
||||
setValue("type", "com")
|
||||
putValue("name", portName)
|
||||
putValue("baudRate", baudRate)
|
||||
putValue("dataBits", dataBits)
|
||||
putValue("stopBits", stopBits)
|
||||
putValue("parity", parity)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,329 @@
|
||||
/*
|
||||
* Copyright 2017 Alexander Nozik.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package hep.dataforge.control.ports
|
||||
|
||||
import hep.dataforge.context.Context
|
||||
import hep.dataforge.context.ContextAware
|
||||
import hep.dataforge.context.Global
|
||||
import hep.dataforge.exceptions.ControlException
|
||||
import hep.dataforge.exceptions.PortException
|
||||
import hep.dataforge.utils.ReferenceRegistry
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.nio.charset.Charset
|
||||
import java.time.Duration
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
|
||||
/**
|
||||
* A port controller helper that allows both synchronous and asynchronous operations on port
|
||||
* @property port the port associated with this controller
|
||||
*/
|
||||
open class GenericPortController(
|
||||
override val context: Context,
|
||||
val port: Port,
|
||||
private val phraseCondition: (String) -> Boolean = { it.endsWith("\n") }
|
||||
) : PortController, AutoCloseable, ContextAware {
|
||||
|
||||
|
||||
constructor(context: Context, port: Port, delimiter: String) : this(context, port, { it.endsWith(delimiter) })
|
||||
|
||||
private val waiters = ReferenceRegistry<FuturePhrase>()
|
||||
private val listeners = ReferenceRegistry<PhraseListener>()
|
||||
private val exceptionListeners = ReferenceRegistry<ErrorListener>()
|
||||
private val buffer = ByteArrayOutputStream();
|
||||
|
||||
|
||||
fun open() {
|
||||
try {
|
||||
port.holdBy(this)
|
||||
if (!port.isOpen) {
|
||||
port.open()
|
||||
}
|
||||
} catch (e: PortException) {
|
||||
throw RuntimeException("Can't hold the port $port by generic handler", e)
|
||||
}
|
||||
}
|
||||
|
||||
override val logger: Logger
|
||||
get() = LoggerFactory.getLogger("${context.name}.$port")
|
||||
|
||||
override fun accept(byte: Byte) {
|
||||
synchronized(port) {
|
||||
buffer.write(byte.toInt())
|
||||
val string = buffer.toString("UTF8")
|
||||
if (phraseCondition(string)) {
|
||||
acceptPhrase(string)
|
||||
buffer.reset()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun acceptPhrase(message: String) {
|
||||
waiters.forEach { waiter -> waiter.acceptPhrase(message) }
|
||||
listeners.forEach { listener -> listener.acceptPhrase(message) }
|
||||
}
|
||||
|
||||
override fun error(errorMessage: String, error: Throwable) {
|
||||
exceptionListeners.forEach { it ->
|
||||
context.executors.defaultExecutor.submit {
|
||||
try {
|
||||
it.action(errorMessage, error)
|
||||
} catch (ex: Exception) {
|
||||
logger.error("Failed to execute error listener action", ex)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for next phrase matching condition and return its result
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@JvmOverloads
|
||||
fun next(condition: (String) -> Boolean = { true }): CompletableFuture<String> {
|
||||
//No need for synchronization since ReferenceRegistry is synchronized
|
||||
waiters.removeIf { it.isDone }
|
||||
val res = FuturePhrase(condition)
|
||||
waiters.add(res)
|
||||
return res
|
||||
}
|
||||
|
||||
/**
|
||||
* Get next phrase matching pattern
|
||||
*
|
||||
* @param pattern
|
||||
* @return
|
||||
*/
|
||||
fun next(pattern: String): CompletableFuture<String> {
|
||||
return next { it -> it.matches(pattern.toRegex()) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Block until specific phrase is received
|
||||
*
|
||||
* @param timeout
|
||||
* @param predicate
|
||||
* @return
|
||||
* @throws PortException
|
||||
*/
|
||||
@JvmOverloads
|
||||
fun waitFor(timeout: Duration, predicate: (String) -> Boolean = { true }): String {
|
||||
return next(predicate).get(timeout.toMillis(), TimeUnit.MILLISECONDS)
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook specific reaction to the specific phrase. Whenever it is possible, it is better to use `weakOnPhrase` to avoid memory leaks due to obsolete listeners.
|
||||
*
|
||||
* @param condition
|
||||
* @param action
|
||||
*/
|
||||
fun onPhrase(condition: (String) -> Boolean, owner: Any? = null, action: (String) -> Unit) {
|
||||
val listener = PhraseListener(condition, owner, action)
|
||||
listeners.add(listener)
|
||||
}
|
||||
|
||||
/**
|
||||
* Add weak phrase listener
|
||||
*
|
||||
* @param condition
|
||||
* @param action
|
||||
* @return
|
||||
*/
|
||||
fun weakOnPhrase(condition: (String) -> Boolean, owner: Any? = null, action: (String) -> Unit) {
|
||||
val listener = PhraseListener(condition, owner, action)
|
||||
listeners.add(listener, false)
|
||||
}
|
||||
|
||||
fun weakOnPhrase(pattern: String, owner: Any? = null, action: (String) -> Unit) {
|
||||
weakOnPhrase({ it.matches(pattern.toRegex()) }, owner, action)
|
||||
}
|
||||
|
||||
fun weakOnPhrase(owner: Any? = null, action: (String) -> Unit) {
|
||||
weakOnPhrase({ true }, owner, action)
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a specific phrase listener
|
||||
*
|
||||
* @param listener
|
||||
*/
|
||||
fun removePhraseListener(owner: Any) {
|
||||
this.listeners.removeIf { it.owner == owner }
|
||||
}
|
||||
|
||||
/**
|
||||
* Add action to phrase matching specific pattern
|
||||
*
|
||||
* @param pattern
|
||||
* @param action
|
||||
* @return
|
||||
*/
|
||||
fun onPhrase(pattern: String, owner: Any? = null, action: (String) -> Unit) {
|
||||
onPhrase({ it.matches(pattern.toRegex()) }, owner, action)
|
||||
}
|
||||
|
||||
/**
|
||||
* Add reaction to any phrase
|
||||
*
|
||||
* @param action
|
||||
* @return
|
||||
*/
|
||||
fun onAnyPhrase(owner: Any? = null, action: (String) -> Unit) {
|
||||
onPhrase({ true }, owner, action)
|
||||
}
|
||||
|
||||
/**
|
||||
* Add error listener
|
||||
*
|
||||
* @param listener
|
||||
* @return
|
||||
*/
|
||||
fun onError(owner: Any? = null, listener: (String, Throwable?) -> Unit) {
|
||||
this.exceptionListeners.add(ErrorListener(owner, listener))
|
||||
}
|
||||
|
||||
/**
|
||||
* Add weak error listener
|
||||
*
|
||||
* @param listener
|
||||
* @return
|
||||
*/
|
||||
fun weakOnError(owner: Any? = null, listener: (String, Throwable?) -> Unit) {
|
||||
this.exceptionListeners.add(ErrorListener(owner, listener), false)
|
||||
}
|
||||
|
||||
/**
|
||||
* remove specific error listener
|
||||
*
|
||||
*/
|
||||
fun removeErrorListener(owner: Any) {
|
||||
this.exceptionListeners.removeIf { it.owner == owner }
|
||||
}
|
||||
|
||||
/**
|
||||
* Send async message to port
|
||||
*
|
||||
* @param message
|
||||
*/
|
||||
fun send(message: ByteArray) {
|
||||
try {
|
||||
open()
|
||||
port.send(this, message)
|
||||
} catch (e: PortException) {
|
||||
throw RuntimeException("Failed to send message to port $port")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun send(message: String, charset: Charset = Charsets.US_ASCII) {
|
||||
send(message.toByteArray(charset))
|
||||
}
|
||||
|
||||
/**
|
||||
* Send and return the future with the result
|
||||
*
|
||||
* @param message
|
||||
* @param condition
|
||||
*/
|
||||
fun sendAndGet(message: String, condition: (String) -> Boolean): CompletableFuture<String> {
|
||||
val res = next(condition) // in case of immediate reaction
|
||||
send(message)
|
||||
return res
|
||||
}
|
||||
|
||||
/**
|
||||
* Send and block thread until specific result is obtained. All listeners and reactions work as usual.
|
||||
*
|
||||
* @param message
|
||||
* @param timeout
|
||||
* @param condition
|
||||
* @return
|
||||
*/
|
||||
fun sendAndWait(message: String, timeout: Duration, condition: (String) -> Boolean = { true }): String {
|
||||
return sendAndGet(message, condition).get(timeout.toMillis(), TimeUnit.MILLISECONDS)
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel all pending waiting actions and release the port. Does not close the port
|
||||
*/
|
||||
@Throws(Exception::class)
|
||||
override fun close() {
|
||||
close(Duration.ofMillis(1000))
|
||||
}
|
||||
|
||||
/**
|
||||
* Blocking close operation. Waits at most for timeout to finish all operations and then closes.
|
||||
*
|
||||
* @param timeout
|
||||
*/
|
||||
@Throws(Exception::class)
|
||||
fun close(timeout: Duration) {
|
||||
CompletableFuture.allOf(*waiters.toTypedArray()).get(timeout.toMillis(), TimeUnit.MILLISECONDS)
|
||||
port.releaseBy(this)
|
||||
}
|
||||
|
||||
private inner class FuturePhrase(internal val condition: (String) -> Boolean) : CompletableFuture<String>() {
|
||||
internal fun acceptPhrase(phrase: String) {
|
||||
if (condition(phrase)) {
|
||||
complete(phrase)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private inner class PhraseListener(private val condition: (String) -> Boolean, val owner: Any? = null, private val action: (String) -> Unit) {
|
||||
|
||||
internal fun acceptPhrase(phrase: String) {
|
||||
if (condition(phrase)) {
|
||||
context.executors.defaultExecutor.submit {
|
||||
try {
|
||||
action(phrase)
|
||||
} catch (ex: Exception) {
|
||||
logger.error("Failed to execute hooked action", ex)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private inner class ErrorListener(val owner: Any? = null, val action: (String, Throwable?) -> Unit)
|
||||
|
||||
companion object {
|
||||
|
||||
/**
|
||||
* Use temporary controller to safely send request and receive response
|
||||
*
|
||||
* @param port
|
||||
* @param request
|
||||
* @param timeout
|
||||
* @return
|
||||
* @throws ControlException
|
||||
*/
|
||||
@Throws(ControlException::class)
|
||||
fun sendAndWait(port: Port, request: String, timeout: Duration): String {
|
||||
try {
|
||||
GenericPortController(Global, port).use { controller -> return controller.sendAndWait(request, timeout) { true } }
|
||||
} catch (e: Exception) {
|
||||
throw ControlException("Failed to close the port", e)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,200 @@
|
||||
/*
|
||||
* Copyright 2017 Alexander Nozik.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package hep.dataforge.control.ports
|
||||
|
||||
import hep.dataforge.Named
|
||||
import hep.dataforge.exceptions.PortException
|
||||
import hep.dataforge.exceptions.PortLockException
|
||||
import hep.dataforge.meta.MetaID
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.asCoroutineDispatcher
|
||||
import kotlinx.coroutines.launch
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.time.Duration
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
|
||||
|
||||
/**
|
||||
* The controller which is currently working with this handler. One
|
||||
* controller can simultaneously hold many handlers, but handler could be
|
||||
* held by only one controller.
|
||||
*/
|
||||
interface PortController {
|
||||
|
||||
fun accept(byte: Byte)
|
||||
|
||||
fun accept(bytes: ByteArray){
|
||||
//TODO improve performance using byte buffers
|
||||
bytes.forEach { accept(it) }
|
||||
}
|
||||
|
||||
fun error(errorMessage: String, error: Throwable) {
|
||||
//do nothing
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The universal asynchronous port handler
|
||||
*
|
||||
* @author Alexander Nozik
|
||||
*/
|
||||
abstract class Port : AutoCloseable, MetaID, Named, CoroutineScope {
|
||||
|
||||
private val portLock = ReentrantLock(true)
|
||||
|
||||
private var controller: PortController? = null
|
||||
|
||||
override val coroutineContext = Executors.newSingleThreadExecutor { r ->
|
||||
val res = Thread(r)
|
||||
res.name = "port::$name"
|
||||
res.priority = Thread.MAX_PRIORITY
|
||||
res
|
||||
}.asCoroutineDispatcher()
|
||||
|
||||
protected val logger: Logger by lazy { LoggerFactory.getLogger("port[$name]") }
|
||||
|
||||
abstract val isOpen: Boolean
|
||||
|
||||
private val isLocked: Boolean
|
||||
get() = this.portLock.isLocked
|
||||
|
||||
@Throws(PortException::class)
|
||||
abstract fun open()
|
||||
|
||||
/**
|
||||
* Emergency hold break.
|
||||
*/
|
||||
@Synchronized
|
||||
fun breakHold() {
|
||||
if (isLocked) {
|
||||
logger.warn("Breaking hold on port $name")
|
||||
launch { portLock.unlock() }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An unique ID for this port
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
override fun toString(): String {
|
||||
return name
|
||||
}
|
||||
|
||||
/**
|
||||
* Acquire lock on this instance of port handler with given controller
|
||||
* object. If port is currently locked by another controller, the wait until it is released.
|
||||
* Only the same controller can release the port.
|
||||
*
|
||||
* @param controller
|
||||
* @throws hep.dataforge.exceptions.PortException
|
||||
*/
|
||||
@Throws(PortException::class)
|
||||
fun holdBy(controller: PortController) {
|
||||
if (!isOpen) {
|
||||
open()
|
||||
}
|
||||
|
||||
launch {
|
||||
try {
|
||||
portLock.lockInterruptibly()
|
||||
} catch (ex: InterruptedException) {
|
||||
logger.error("Lock on port {} is broken", toString())
|
||||
throw RuntimeException(ex)
|
||||
}
|
||||
}
|
||||
logger.debug("Locked by {}", controller)
|
||||
this.controller = controller
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Receive a single byte
|
||||
*/
|
||||
fun receive(byte: Byte) {
|
||||
controller?.accept(byte)
|
||||
}
|
||||
|
||||
/**
|
||||
* Receive an array of bytes
|
||||
*/
|
||||
fun receive(bytes: ByteArray) {
|
||||
controller?.accept(bytes)
|
||||
}
|
||||
|
||||
/**
|
||||
* send the message to the port
|
||||
*
|
||||
* @param message
|
||||
* @throws hep.dataforge.exceptions.PortException
|
||||
*/
|
||||
@Throws(PortException::class)
|
||||
protected abstract fun send(message: ByteArray)
|
||||
|
||||
/**
|
||||
* Send the message if the controller is correct
|
||||
*
|
||||
* @param controller
|
||||
* @param message
|
||||
* @throws PortException
|
||||
*/
|
||||
@Throws(PortException::class)
|
||||
fun send(controller: PortController, message: ByteArray) {
|
||||
if (controller === this.controller) {
|
||||
send(message)
|
||||
} else {
|
||||
throw PortException("Port locked by another controller")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Release hold of this portHandler from given controller.
|
||||
*
|
||||
* @param controller
|
||||
* @throws PortLockException in case given holder is not the one that holds
|
||||
* handler
|
||||
*/
|
||||
@Synchronized
|
||||
@Throws(PortLockException::class)
|
||||
fun releaseBy(controller: PortController) {
|
||||
if (isLocked) {
|
||||
if (controller == this.controller) {
|
||||
this.controller = null
|
||||
launch {
|
||||
portLock.unlock()
|
||||
logger.debug("Unlocked by {}", controller)
|
||||
}
|
||||
} else {
|
||||
throw PortLockException("Can't unlock port with wrong controller")
|
||||
}
|
||||
} else {
|
||||
logger.warn("Attempting to release unlocked port")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Throws(Exception::class)
|
||||
override fun close() {
|
||||
coroutineContext.close()
|
||||
}
|
||||
|
||||
class PortTimeoutException(timeout: Duration) : PortException() {
|
||||
override val message: String = String.format("The timeout time of '%s' is exceeded", timeout)
|
||||
}
|
||||
}
|
@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Copyright 2017 Alexander Nozik.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package hep.dataforge.control.ports
|
||||
|
||||
import hep.dataforge.description.ValueDef
|
||||
import hep.dataforge.description.ValueDefs
|
||||
import hep.dataforge.exceptions.ControlException
|
||||
import hep.dataforge.meta.Meta
|
||||
import hep.dataforge.meta.MetaBuilder
|
||||
import hep.dataforge.set
|
||||
import hep.dataforge.utils.MetaFactory
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
*
|
||||
* @author [Alexander Nozik](mailto:altavir@gmail.com)
|
||||
*/
|
||||
object PortFactory : MetaFactory<Port> {
|
||||
|
||||
private val portMap = HashMap<Meta, Port>()
|
||||
|
||||
|
||||
@ValueDefs(
|
||||
ValueDef(key = "type", def = "tcp", info = "The type of the port"),
|
||||
ValueDef(key = "address", required = true, info = "The specific designation of this port according to type"),
|
||||
ValueDef(key = "type", def = "tcp", info = "The type of the port")
|
||||
)
|
||||
override fun build(meta: Meta): Port {
|
||||
val protocol = meta.getString("type", "tcp")
|
||||
val port = when (protocol) {
|
||||
"com" -> {
|
||||
if (meta.hasValue("address")) {
|
||||
ComPort(meta.getString("address"), meta)
|
||||
} else {
|
||||
throw IllegalArgumentException("Not enough information to create a port")
|
||||
}
|
||||
}
|
||||
"tcp" -> {
|
||||
if (meta.hasValue("ip") && meta.hasValue("port")) {
|
||||
TcpPort(meta.getString("ip"), meta.getInt("port"), meta)
|
||||
} else {
|
||||
throw IllegalArgumentException("Not enough information to create a port")
|
||||
}
|
||||
}
|
||||
"virtual" -> buildVirtualPort(meta)
|
||||
else -> throw ControlException("Unknown protocol")
|
||||
}
|
||||
return portMap.getOrPut(port.toMeta()) { port }
|
||||
}
|
||||
|
||||
private fun buildVirtualPort(meta: Meta): Port {
|
||||
val className = meta.getString("class")
|
||||
val theClass = Class.forName(className)
|
||||
return theClass.getDeclaredConstructor(Meta::class.java).newInstance(meta) as Port
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new port or reuse existing one if it is already created
|
||||
* @param portName
|
||||
* @return
|
||||
* @throws ControlException
|
||||
*/
|
||||
fun build(portName: String): Port {
|
||||
return build(nameToMeta(portName))
|
||||
}
|
||||
|
||||
fun nameToMeta(portName: String): Meta {
|
||||
val builder = MetaBuilder("port")
|
||||
.setValue("name", portName)
|
||||
|
||||
val type = portName.substringBefore("::", "com")
|
||||
val address = portName.substringAfter("::")
|
||||
|
||||
builder["type"] = type
|
||||
builder["address"] = address
|
||||
|
||||
if (type == "tcp") {
|
||||
builder["ip"] = address.substringBefore(":")
|
||||
builder["port"] = address.substringAfter(":").toInt()
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
}
|
@ -0,0 +1,147 @@
|
||||
/*
|
||||
* Copyright 2018 Alexander Nozik.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package hep.dataforge.control.ports
|
||||
|
||||
import hep.dataforge.context.Context
|
||||
import hep.dataforge.context.ContextAware
|
||||
import hep.dataforge.control.devices.Device
|
||||
import hep.dataforge.control.devices.dispatchEvent
|
||||
import hep.dataforge.description.ValueDef
|
||||
import hep.dataforge.events.EventBuilder
|
||||
import hep.dataforge.meta.Meta
|
||||
import hep.dataforge.nullable
|
||||
import hep.dataforge.states.*
|
||||
import hep.dataforge.useValue
|
||||
import hep.dataforge.values.ValueType
|
||||
import org.slf4j.Logger
|
||||
import java.time.Duration
|
||||
|
||||
@StateDef(value = ValueDef(key = "connected",type = [ValueType.BOOLEAN], def = "false"), writable = true)
|
||||
class PortHelper(
|
||||
val device: Device,
|
||||
val builder: ((Context, Meta) -> GenericPortController) = { context, meta -> GenericPortController(context, PortFactory.build(meta)) }
|
||||
) : Stateful, ContextAware {
|
||||
override val logger: Logger
|
||||
get() = device.logger
|
||||
|
||||
override val states: StateHolder
|
||||
get() = device.states
|
||||
|
||||
override val context: Context
|
||||
get() = device.context
|
||||
|
||||
|
||||
private val Device.portMeta: Meta
|
||||
get() = meta.optMeta(PORT_STATE).nullable
|
||||
?: device.meta.optValue(PORT_STATE).map {
|
||||
PortFactory.nameToMeta(it.string)
|
||||
}.orElse(Meta.empty())
|
||||
|
||||
var connection: GenericPortController = builder(context, device.portMeta)
|
||||
private set
|
||||
|
||||
val connectedState = valueState(CONNECTED_STATE, getter = { connection.port.isOpen }) { old, value ->
|
||||
if (old != value) {
|
||||
logger.info("State 'connect' changed to $value")
|
||||
if (value.boolean) {
|
||||
connection.open()
|
||||
} else {
|
||||
connection.close()
|
||||
}
|
||||
//connect(value.boolean)
|
||||
}
|
||||
update(value)
|
||||
}
|
||||
|
||||
var connected by connectedState.booleanDelegate
|
||||
|
||||
var debug by valueState(DEBUG_STATE) { old, value ->
|
||||
if (old != value) {
|
||||
logger.info("Turning debug mode to $value")
|
||||
setDebugMode(value.boolean)
|
||||
}
|
||||
update(value)
|
||||
}.booleanDelegate
|
||||
|
||||
var port by metaState(PORT_STATE, getter = { connection.port.toMeta() }) { old, value ->
|
||||
if (old != value) {
|
||||
setDebugMode(false)
|
||||
connectedState.update(false)
|
||||
connection.close()
|
||||
connection = builder(context, value)
|
||||
connection.open()
|
||||
setDebugMode(debug)
|
||||
}
|
||||
update(value)
|
||||
}.delegate
|
||||
|
||||
private val defaultTimeout: Duration = Duration.ofMillis(device.meta.getInt("port.timeout", 400).toLong())
|
||||
|
||||
val name get() = device.name
|
||||
|
||||
init {
|
||||
states.update(PORT_STATE, connection.port.toMeta())
|
||||
device.meta.useValue(DEBUG_STATE) {
|
||||
debug = it.boolean
|
||||
}
|
||||
}
|
||||
|
||||
private fun setDebugMode(debugMode: Boolean) {
|
||||
//Add debug listener
|
||||
if (debugMode) {
|
||||
connection.apply {
|
||||
onAnyPhrase("$name[debug]") { phrase -> logger.debug("Device {} received phrase: \n{}", name, phrase) }
|
||||
onError("$name[debug]") { message, error -> logger.error("Device {} exception: \n{}", name, message, error) }
|
||||
}
|
||||
} else {
|
||||
connection.apply {
|
||||
removePhraseListener("$name[debug]")
|
||||
removeErrorListener("$name[debug]")
|
||||
}
|
||||
}
|
||||
states.update(DEBUG_STATE, debugMode)
|
||||
}
|
||||
|
||||
fun shutdown() {
|
||||
connectedState.set(false)
|
||||
}
|
||||
|
||||
fun sendAndWait(request: String, timeout: Duration = defaultTimeout): String {
|
||||
return connection.sendAndWait(request, timeout) { true }
|
||||
}
|
||||
|
||||
fun sendAndWait(request: String, timeout: Duration = defaultTimeout, predicate: (String) -> Boolean): String {
|
||||
return connection.sendAndWait(request, timeout, predicate)
|
||||
}
|
||||
|
||||
fun send(message: String) {
|
||||
connection.send(message)
|
||||
device.dispatchEvent(
|
||||
EventBuilder
|
||||
.make(device.name)
|
||||
.setMetaValue("request", message)
|
||||
.build()
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val CONNECTED_STATE = "connected"
|
||||
const val PORT_STATE = "port"
|
||||
const val DEBUG_STATE = "debug"
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,121 @@
|
||||
/*
|
||||
* Copyright 2017 Alexander Nozik.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package hep.dataforge.control.ports
|
||||
|
||||
import hep.dataforge.exceptions.PortException
|
||||
import hep.dataforge.meta.Meta
|
||||
import hep.dataforge.meta.buildMeta
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import java.net.InetSocketAddress
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.channels.SocketChannel
|
||||
|
||||
/**
|
||||
* @author Alexander Nozik
|
||||
*/
|
||||
class TcpPort(val ip: String, val port: Int, val config: Meta = Meta.empty()) : Port() {
|
||||
|
||||
private var channel: SocketChannel = SocketChannel.open()
|
||||
|
||||
override val isOpen: Boolean
|
||||
get() = channel.isConnected
|
||||
|
||||
override val name = String.format("tcp::%s:%d", ip, port)
|
||||
|
||||
private var listenerJob: Job? = null
|
||||
|
||||
private fun openChannel(): SocketChannel{
|
||||
return SocketChannel.open(InetSocketAddress(ip, port)).apply {
|
||||
this.configureBlocking(false)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(PortException::class)
|
||||
override fun open() {
|
||||
launch {
|
||||
if (!channel.isConnected && !channel.isConnectionPending) {
|
||||
channel = openChannel()
|
||||
startListener()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
@Throws(Exception::class)
|
||||
override fun close() {
|
||||
launch {
|
||||
if(isOpen) {
|
||||
listenerJob?.cancel()
|
||||
channel.shutdownInput()
|
||||
channel.shutdownOutput()
|
||||
channel.close()
|
||||
super.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun startListener() {
|
||||
listenerJob = launch {
|
||||
val buffer = ByteBuffer.allocate(1024)
|
||||
while (true) {
|
||||
try {
|
||||
//read all content
|
||||
do {
|
||||
val num = channel.read(buffer)
|
||||
if (num > 0) {
|
||||
receive(buffer.toArray(num))
|
||||
}
|
||||
buffer.rewind()
|
||||
} while (num > 0)
|
||||
delay(50)
|
||||
} catch (ex: Exception) {
|
||||
logger.error("Channel read error", ex)
|
||||
logger.info("Reconnecting")
|
||||
channel = openChannel()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(PortException::class)
|
||||
public override fun send(message: ByteArray) {
|
||||
launch {
|
||||
try {
|
||||
channel.write(ByteBuffer.wrap(message))
|
||||
logger.debug("SEND: ${String(message)}")
|
||||
} catch (ex: Exception) {
|
||||
throw RuntimeException(ex)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun toMeta(): Meta = buildMeta {
|
||||
"type" to "tcp"
|
||||
"name" to this@TcpPort.name
|
||||
"ip" to ip
|
||||
"port" to port
|
||||
}
|
||||
}
|
||||
|
||||
fun ByteBuffer.toArray(limit: Int = limit()): ByteArray{
|
||||
rewind()
|
||||
val response = ByteArray(limit)
|
||||
get(response)
|
||||
rewind()
|
||||
return response
|
||||
}
|
@ -0,0 +1,127 @@
|
||||
/*
|
||||
* Copyright 2017 Alexander Nozik.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package hep.dataforge.control.ports
|
||||
|
||||
import hep.dataforge.exceptions.PortException
|
||||
import hep.dataforge.meta.Configurable
|
||||
import hep.dataforge.meta.Configuration
|
||||
import hep.dataforge.meta.Meta
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import java.time.Duration
|
||||
import java.util.concurrent.CopyOnWriteArraySet
|
||||
import java.util.function.Supplier
|
||||
|
||||
/**
|
||||
* @author Alexander Nozik
|
||||
*/
|
||||
abstract class VirtualPort protected constructor(meta: Meta) : Port(), Configurable {
|
||||
|
||||
private val futures = CopyOnWriteArraySet<TaggedFuture>()
|
||||
override var isOpen = false
|
||||
var meta = Configuration(meta)
|
||||
protected open val delimeter = meta.getString("delimenter", "\n")
|
||||
|
||||
@Throws(PortException::class)
|
||||
override fun open() {
|
||||
//scheduler = Executors.newScheduledThreadPool(meta.getInt("numThreads", 4))
|
||||
isOpen = true
|
||||
}
|
||||
|
||||
override fun getConfig(): Configuration {
|
||||
return meta
|
||||
}
|
||||
|
||||
override fun configure(config: Meta): Configurable {
|
||||
meta.update(config)
|
||||
return this
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return meta.getString("id", javaClass.simpleName)
|
||||
}
|
||||
|
||||
@Throws(PortException::class)
|
||||
public override fun send(message: ByteArray) {
|
||||
evaluateRequest(String(message, Charsets.US_ASCII))
|
||||
}
|
||||
|
||||
/**
|
||||
* The device logic here
|
||||
*
|
||||
* @param request
|
||||
*/
|
||||
protected abstract fun evaluateRequest(request: String)
|
||||
|
||||
@Synchronized
|
||||
protected fun clearCompleted() {
|
||||
futures.stream().filter { future -> future.future.isCompleted }.forEach { futures.remove(it) }
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
protected fun cancelByTag(tag: String) {
|
||||
futures.stream().filter { future -> future.hasTag(tag) }.forEach { it.cancel() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Plan the response with given delay
|
||||
*
|
||||
* @param response
|
||||
* @param delay
|
||||
* @param tags
|
||||
*/
|
||||
@Synchronized
|
||||
protected fun planResponse(response: String, delay: Duration, vararg tags: String) {
|
||||
clearCompleted()
|
||||
val future = launch {
|
||||
kotlinx.coroutines.time.delay(delay)
|
||||
receive((response + delimeter).toByteArray())
|
||||
}
|
||||
this.futures.add(TaggedFuture(future, *tags))
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
protected fun planRegularResponse(responseBuilder: Supplier<String>, delay: Duration, period: Duration, vararg tags: String) {
|
||||
clearCompleted()
|
||||
val future = launch {
|
||||
kotlinx.coroutines.time.delay(delay)
|
||||
while (true) {
|
||||
receive((responseBuilder.get() + delimeter).toByteArray())
|
||||
kotlinx.coroutines.time.delay(period)
|
||||
}
|
||||
}
|
||||
this.futures.add(TaggedFuture(future, *tags))
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
override fun close() {
|
||||
futures.clear()
|
||||
isOpen = false
|
||||
super.close()
|
||||
}
|
||||
|
||||
private inner class TaggedFuture(internal val future: Job, vararg tags: String) {
|
||||
internal val tags = setOf(*tags)
|
||||
|
||||
fun hasTag(tag: String): Boolean {
|
||||
return tags.contains(tag)
|
||||
}
|
||||
|
||||
fun cancel() {
|
||||
return future.cancel()
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1 @@
|
||||
hep.dataforge.control.DeviceManager$Factory
|
@ -0,0 +1,73 @@
|
||||
package hep.dataforge.control.ports
|
||||
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import org.junit.After
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import java.net.InetSocketAddress
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.channels.ServerSocketChannel
|
||||
import java.nio.channels.SocketChannel
|
||||
|
||||
|
||||
class TcpPortTest {
|
||||
var job: Job? = null
|
||||
|
||||
@Before
|
||||
fun startServer() {
|
||||
GlobalScope.launch {
|
||||
println("Starting server")
|
||||
val serverSocketChannel = ServerSocketChannel.open()
|
||||
|
||||
serverSocketChannel.socket().bind(InetSocketAddress(9999))
|
||||
|
||||
while (true) {
|
||||
delay(0)
|
||||
println("Accepting client")
|
||||
serverSocketChannel.accept().use {
|
||||
val buffer = ByteBuffer.allocate(1024)
|
||||
val num = it.read(buffer)
|
||||
println("Received $num bytes")
|
||||
buffer.flip()
|
||||
it.write(buffer)
|
||||
buffer.rewind()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@After
|
||||
fun stopServer() {
|
||||
job?.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testClient() {
|
||||
val channel: SocketChannel = SocketChannel.open(InetSocketAddress("localhost", 9999))
|
||||
println("Sending 3 bytes")
|
||||
val request = "ddd".toByteArray()
|
||||
channel.write(ByteBuffer.wrap(request))
|
||||
val buffer = ByteBuffer.allocate(1024)
|
||||
channel.read(buffer)
|
||||
buffer.flip()
|
||||
val response = buffer.toArray()
|
||||
assertEquals(request.size, response.size)
|
||||
assertEquals(String(request), String(response))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testPort() {
|
||||
val port = TcpPort("localhost", 9999)
|
||||
port.holdBy(object : PortController {
|
||||
override fun accept(byte: Byte) {
|
||||
println(byte)
|
||||
}
|
||||
})
|
||||
port.send("ddd".toByteArray())
|
||||
Thread.sleep(500)
|
||||
}
|
||||
}
|
8
dataforge-core/build.gradle
Normal file
8
dataforge-core/build.gradle
Normal file
@ -0,0 +1,8 @@
|
||||
description = 'dataforge-core'
|
||||
|
||||
dependencies {
|
||||
compile 'ch.qos.logback:logback-classic:1.2.3'
|
||||
compile 'org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:1.3.2'
|
||||
compile group: 'org.jetbrains.kotlin', name: 'kotlin-reflect', version: kotlin_version
|
||||
compile group: 'javax.cache', name: 'cache-api', version: '1.1.0'
|
||||
}
|
22
dataforge-core/dataforge-json/build.gradle
Normal file
22
dataforge-core/dataforge-json/build.gradle
Normal file
@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright 2018 Alexander Nozik.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
description = 'json meta type for dataforge'
|
||||
|
||||
dependencies {
|
||||
compile project(":dataforge-core")
|
||||
compile 'com.github.cliftonlabs:json-simple:3.0.2'
|
||||
}
|
@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Copyright 2018 Alexander Nozik.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package hep.dataforge.io
|
||||
|
||||
import com.github.cliftonlabs.json_simple.JsonArray
|
||||
import com.github.cliftonlabs.json_simple.JsonObject
|
||||
import com.github.cliftonlabs.json_simple.Jsoner
|
||||
import hep.dataforge.meta.KMetaBuilder
|
||||
import hep.dataforge.meta.MetaBuilder
|
||||
import hep.dataforge.meta.buildMeta
|
||||
import hep.dataforge.values.LateParseValue
|
||||
import hep.dataforge.values.Value
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.io.InputStreamReader
|
||||
import java.text.ParseException
|
||||
|
||||
/**
|
||||
* Reader for JSON meta
|
||||
*
|
||||
* @author Alexander Nozik
|
||||
*/
|
||||
object JSONMetaReader : MetaStreamReader {
|
||||
|
||||
@Throws(IOException::class, ParseException::class)
|
||||
override fun read(stream: InputStream, length: Long): MetaBuilder {
|
||||
return if (length == 0L) {
|
||||
MetaBuilder("")
|
||||
} else {
|
||||
val json = if (length > 0) {
|
||||
//Read into intermediate buffer
|
||||
val buffer = ByteArray(length.toInt())
|
||||
stream.read(buffer)
|
||||
Jsoner.deserialize(InputStreamReader(ByteArrayInputStream(buffer), Charsets.UTF_8)) as JsonObject
|
||||
} else {
|
||||
Jsoner.deserialize(InputStreamReader(stream, Charsets.UTF_8)) as JsonObject
|
||||
}
|
||||
json.toMeta()
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(ParseException::class)
|
||||
private fun JsonObject.toMeta(): MetaBuilder {
|
||||
return buildMeta {
|
||||
this@toMeta.forEach { key, value -> appendValue(this, key as String, value) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun JsonArray.toListValue(): Value {
|
||||
val list = this.map {value->
|
||||
when (value) {
|
||||
is JsonArray -> value.toListValue()
|
||||
is Number -> Value.of(value)
|
||||
is Boolean -> Value.of(value)
|
||||
is String -> LateParseValue(value)
|
||||
null -> Value.NULL
|
||||
is JsonObject -> throw RuntimeException("Object values inside multidimensional arrays are not allowed")
|
||||
else -> throw Error("Unknown token $value in json")
|
||||
}
|
||||
}
|
||||
return Value.of(list)
|
||||
}
|
||||
|
||||
private fun appendValue(builder: KMetaBuilder, key: String, value: Any?) {
|
||||
when (value) {
|
||||
is JsonObject -> builder.attachNode(value.toMeta().rename(key))
|
||||
is JsonArray -> {
|
||||
value.forEach {
|
||||
if(it is JsonArray){
|
||||
builder.putValue(key, it.toListValue())
|
||||
} else {
|
||||
appendValue(builder, key, it)
|
||||
}
|
||||
}
|
||||
}
|
||||
is Number -> builder.putValue(key, value)
|
||||
is Boolean -> builder.putValue(key, value)
|
||||
is String -> builder.putValue(key, LateParseValue(value))
|
||||
//ignore anything else
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2018 Alexander Nozik.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package hep.dataforge.io
|
||||
|
||||
import hep.dataforge.io.envelopes.MetaType
|
||||
|
||||
class JSONMetaType : MetaType {
|
||||
override val codes: List<Short> = listOf(0x4a53, 1)//JS
|
||||
|
||||
override val name: String = "JSON"
|
||||
|
||||
override val reader: MetaStreamReader = JSONMetaReader
|
||||
|
||||
override val writer: MetaStreamWriter = JSONMetaWriter
|
||||
|
||||
override val fileNameFilter: (String) -> Boolean = { it.toLowerCase().endsWith(".json") }
|
||||
}
|
||||
|
||||
val jsonMetaType = JSONMetaType()
|
@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Copyright 2018 Alexander Nozik.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package hep.dataforge.io
|
||||
|
||||
import com.github.cliftonlabs.json_simple.JsonArray
|
||||
import com.github.cliftonlabs.json_simple.JsonObject
|
||||
import com.github.cliftonlabs.json_simple.Jsoner
|
||||
import hep.dataforge.meta.Meta
|
||||
import hep.dataforge.values.Value
|
||||
import hep.dataforge.values.ValueType
|
||||
import java.io.OutputStream
|
||||
|
||||
/**
|
||||
* A converter from Meta object to JSON character stream
|
||||
*
|
||||
* @author Alexander Nozik
|
||||
*/
|
||||
object JSONMetaWriter : MetaStreamWriter {
|
||||
|
||||
override fun write(stream: OutputStream, meta: Meta) {
|
||||
val json = meta.toJson()
|
||||
val string = Jsoner.prettyPrint(Jsoner.serialize(json))
|
||||
stream.write(string.toByteArray(Charsets.UTF_8))
|
||||
stream.flush()
|
||||
}
|
||||
|
||||
private fun Value.toJson(): Any {
|
||||
return if (list.size == 1) {
|
||||
when (type) {
|
||||
ValueType.NUMBER -> number
|
||||
ValueType.BOOLEAN -> boolean
|
||||
else -> string
|
||||
}
|
||||
} else {
|
||||
JsonArray().apply {
|
||||
list.forEach { add(it.toJson()) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Meta.toJson(): JsonObject {
|
||||
val builder = JsonObject()
|
||||
nodeNames.forEach {
|
||||
val nodes = getMetaList(it)
|
||||
if (nodes.size == 1) {
|
||||
builder[it] = nodes[0].toJson()
|
||||
} else {
|
||||
val array = JsonArray()
|
||||
nodes.forEach { array.add(it.toJson()) }
|
||||
builder[it] = array
|
||||
}
|
||||
}
|
||||
|
||||
valueNames.forEach {
|
||||
builder[it] = getValue(it).toJson()
|
||||
}
|
||||
|
||||
return builder
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//private class JSONWriter : StringWriter() {
|
||||
//
|
||||
// private var indentlevel = 0
|
||||
//
|
||||
// override fun write(c: Int) {
|
||||
// val ch = c.toChar()
|
||||
// if (ch == '[' || ch == '{') {
|
||||
// super.write(c)
|
||||
// super.write("\n")
|
||||
// indentlevel++
|
||||
// writeIndentation()
|
||||
// } else if (ch == ',') {
|
||||
// super.write(c)
|
||||
// super.write("\n")
|
||||
// writeIndentation()
|
||||
// } else if (ch == ']' || ch == '}') {
|
||||
// super.write("\n")
|
||||
// indentlevel--
|
||||
// writeIndentation()
|
||||
// super.write(c)
|
||||
// } else if (ch == ':') {
|
||||
// super.write(c)
|
||||
// super.write(spaceaftercolon)
|
||||
// } else {
|
||||
// super.write(c)
|
||||
// }
|
||||
//
|
||||
// }
|
||||
//
|
||||
// private fun writeIndentation() {
|
||||
// for (i in 0 until indentlevel) {
|
||||
// super.write(indentstring)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// companion object {
|
||||
// internal val indentstring = " " //define as you wish
|
||||
// internal val spaceaftercolon = " " //use "" if you don't want space after colon
|
||||
// }
|
||||
//}
|
||||
|
||||
|
@ -0,0 +1 @@
|
||||
hep.dataforge.io.JSONMetaType
|
@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright 2018 Alexander Nozik.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package hep.dataforge.io
|
||||
|
||||
import hep.dataforge.get
|
||||
import hep.dataforge.meta.buildMeta
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
|
||||
class JSONMetaTypeTest {
|
||||
@Test
|
||||
fun testRead() {
|
||||
val json = """
|
||||
{
|
||||
"a" : 22,
|
||||
"b" : {
|
||||
"c" : [1, 2, [3.1, 3.2]]
|
||||
"d" : "my string value"
|
||||
}
|
||||
}
|
||||
""".trimMargin()
|
||||
val meta = jsonMetaType.reader.readString(json)
|
||||
assertEquals(22, meta["a"].int)
|
||||
assertEquals(3.1, meta["b.c"][2][0].double,0.01)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testWrite(){
|
||||
val meta = buildMeta {
|
||||
"a" to 22
|
||||
"b" to {
|
||||
"c" to listOf(1, 2, listOf(3.1, 3.2))
|
||||
"d" to "my string value"
|
||||
}
|
||||
}
|
||||
val string = jsonMetaType.writer.writeString(meta)
|
||||
println(string)
|
||||
}
|
||||
}
|
4
dataforge-core/docs/descriptors.md
Normal file
4
dataforge-core/docs/descriptors.md
Normal file
@ -0,0 +1,4 @@
|
||||
---
|
||||
title: Descriptors
|
||||
---
|
||||
|
22
dataforge-core/docs/logging.md
Normal file
22
dataforge-core/docs/logging.md
Normal file
@ -0,0 +1,22 @@
|
||||
---
|
||||
title: Logging and History
|
||||
---
|
||||
|
||||
DataForge supports two separate logging system:
|
||||
|
||||
1. Generic logging system based on `slf4j` on JVM and possibly any other native logging on remote platform. It is used
|
||||
for debugging and online information about the process. Logger could be obtained in any point of the program (e.g. via
|
||||
`LoggerFactrory.getLogger(...)` in JVM). Critical elements and all `ContextAware` blocks automatically have a pre-defined
|
||||
logger accessible via `logger` property.
|
||||
|
||||
2. Internal logging utilizes hierarchical structure called `History`. Each `Hisotory` object has a reference to `Chronocle`
|
||||
which stores history entries called `Record`. Also any `History` but the root one which is `Global.history` must have a parent
|
||||
`History`. Any `Record` entry appended to the history is automatically appended to its parent with appropriate trace element
|
||||
which specifies where it entry comes from. One can also attach hooks to any chronicle to be triggered on entry addition.
|
||||
Global history logging behavior is controlled via `Chronicler` context plugin, which is loaded by default into Global context.
|
||||
History ususally could not be created on sight (only from context chronicler) and should be passed to the process explicitly.
|
||||
|
||||
The key difference between logger and history is that logs are intended for debugging and information and are discarded after
|
||||
being read. The history on the other hand is supposed to be the important part of the analysis and should be stored with
|
||||
analysis results after it is complete. It is strongly discouraged to use `History` in a performance-sensitive code. Also
|
||||
it is bad idea to output any sensitive information to log since it could be discarded.
|
@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2015 Alexander Nozik.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package hep.dataforge.actions;
|
||||
|
||||
/**
|
||||
* <p>ActionEvent interface.</p>
|
||||
*
|
||||
* @author Alexander Nozik
|
||||
* @version $Id: $Id
|
||||
*/
|
||||
public interface ActionEvent {
|
||||
/**
|
||||
* <p>source.</p>
|
||||
*
|
||||
* @return a {@link hep.dataforge.actions.Action} object.
|
||||
*/
|
||||
Action source();
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2015 Alexander Nozik.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package hep.dataforge.actions;
|
||||
|
||||
/**
|
||||
* <p>ActionEventListener interface.</p>
|
||||
*
|
||||
* @author Alexander Nozik
|
||||
* @version $Id: $Id
|
||||
*/
|
||||
public interface ActionEventListener {
|
||||
/**
|
||||
* <p>listenTo.</p>
|
||||
*
|
||||
* @param event a {@link hep.dataforge.actions.ActionEvent} object.
|
||||
*/
|
||||
void listenTo(ActionEvent event);
|
||||
}
|
@ -0,0 +1,141 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package hep.dataforge.actions;
|
||||
|
||||
import hep.dataforge.context.BasicPlugin;
|
||||
import hep.dataforge.context.Plugin;
|
||||
import hep.dataforge.context.PluginDef;
|
||||
import hep.dataforge.context.PluginFactory;
|
||||
import hep.dataforge.meta.Meta;
|
||||
import hep.dataforge.providers.Provides;
|
||||
import hep.dataforge.providers.ProvidesNames;
|
||||
import hep.dataforge.tables.ReadPointSetAction;
|
||||
import hep.dataforge.tables.TransformTableAction;
|
||||
import hep.dataforge.utils.Optionals;
|
||||
import hep.dataforge.workspace.tasks.Task;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* A support manager to dynamically load actions and tasks into the context
|
||||
*
|
||||
* @author Alexander Nozik
|
||||
*/
|
||||
@PluginDef(name = "actions", group = "hep.dataforge", info = "A list of available actions and task for given context")
|
||||
public class ActionManager extends BasicPlugin {
|
||||
private final Map<String, Action> actionMap = new HashMap<>();
|
||||
private final Map<String, Task> taskMap = new HashMap<>();
|
||||
|
||||
public ActionManager() {
|
||||
//TODO move somewhere else
|
||||
putAction(TransformTableAction.class);
|
||||
putAction(ReadPointSetAction.class);
|
||||
}
|
||||
|
||||
protected Optional<ActionManager> getParent() {
|
||||
if (getContext().getParent() == null) {
|
||||
return Optional.empty();
|
||||
} else {
|
||||
return getContext().getParent().provide("actions", ActionManager.class);
|
||||
}
|
||||
}
|
||||
|
||||
@Provides(Action.ACTION_TARGET)
|
||||
public Optional<Action> optAction(String name) {
|
||||
return Optionals.either(actionMap.get(name))
|
||||
.or(getParent().flatMap(parent -> parent.optAction(name)))
|
||||
.opt();
|
||||
}
|
||||
|
||||
@Provides(Task.TASK_TARGET)
|
||||
public Optional<Task> optTask(String name) {
|
||||
return Optionals.either(taskMap.get(name))
|
||||
.or(getParent().flatMap(parent -> parent.optTask(name)))
|
||||
.opt();
|
||||
}
|
||||
|
||||
public void put(Action action) {
|
||||
if (actionMap.containsKey(action.getName())) {
|
||||
LoggerFactory.getLogger(getClass()).warn("Duplicate action names in ActionManager.");
|
||||
} else {
|
||||
actionMap.put(action.getName(), action);
|
||||
}
|
||||
}
|
||||
|
||||
public void put(Task task) {
|
||||
if (taskMap.containsKey(task.getName())) {
|
||||
LoggerFactory.getLogger(getClass()).warn("Duplicate task names in ActionManager.");
|
||||
} else {
|
||||
taskMap.put(task.getName(), task);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Put a task into the manager using action construction by reflections. Action must have empty constructor
|
||||
*
|
||||
* @param actionClass a {@link java.lang.Class} object.
|
||||
*/
|
||||
public final void putAction(Class<? extends Action> actionClass) {
|
||||
try {
|
||||
put(actionClass.newInstance());
|
||||
} catch (IllegalAccessException ex) {
|
||||
throw new RuntimeException("Action must have default empty constructor to be registered.");
|
||||
} catch (InstantiationException ex) {
|
||||
throw new RuntimeException("Error while constructing Action", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public final void putTask(Class<? extends Task> taskClass) {
|
||||
try {
|
||||
put(taskClass.getConstructor().newInstance());
|
||||
} catch (IllegalAccessException ex) {
|
||||
throw new RuntimeException("Task must have default empty constructor to be registered.");
|
||||
} catch (Exception ex) {
|
||||
throw new RuntimeException("Error while constructing Task", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stream of all available actions
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@ProvidesNames(Action.ACTION_TARGET)
|
||||
public Stream<String> listActions() {
|
||||
return this.actionMap.keySet().stream();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stream of all available tasks
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@ProvidesNames(Task.TASK_TARGET)
|
||||
public Stream<String> listTasks() {
|
||||
return this.taskMap.keySet().stream();
|
||||
}
|
||||
|
||||
public static class Factory extends PluginFactory {
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public ActionManager build(@NotNull Meta meta) {
|
||||
return new ActionManager();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Class<? extends Plugin> getType() {
|
||||
return ActionManager.class;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2015 Alexander Nozik.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package hep.dataforge.actions;
|
||||
|
||||
import hep.dataforge.data.NamedData;
|
||||
import hep.dataforge.goals.Goal;
|
||||
import hep.dataforge.io.history.Chronicle;
|
||||
import hep.dataforge.meta.Meta;
|
||||
|
||||
/**
|
||||
* The asynchronous result of the action
|
||||
*
|
||||
* @author Alexander Nozik
|
||||
* @param <R>
|
||||
*/
|
||||
public class ActionResult<R> extends NamedData<R> {
|
||||
|
||||
private final Chronicle log;
|
||||
|
||||
public ActionResult(String name, Class<R> type, Goal<R> goal, Meta meta, Chronicle log) {
|
||||
super(name, type, goal, meta);
|
||||
this.log = log;
|
||||
}
|
||||
|
||||
public Chronicle log() {
|
||||
return log;
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package hep.dataforge.actions;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Alexander Nozik
|
||||
*/
|
||||
public interface ActionStateListener {
|
||||
void notifyActionStarted(Action action, Object argument);
|
||||
void notifyActionFinished(Action action, Object result);
|
||||
/**
|
||||
* Notify action progress
|
||||
* @param action
|
||||
* @param progress the value between 0 and 1; negative values are ignored
|
||||
* @param message
|
||||
*/
|
||||
void notifyAcionProgress(Action action, double progress, String message);
|
||||
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package hep.dataforge.actions;
|
||||
|
||||
import hep.dataforge.context.Context;
|
||||
import hep.dataforge.data.DataNode;
|
||||
import hep.dataforge.goals.GeneratorGoal;
|
||||
import hep.dataforge.goals.Goal;
|
||||
import hep.dataforge.io.history.Chronicle;
|
||||
import hep.dataforge.meta.Meta;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* An action that does not take any input data, only generates output. Each
|
||||
* output token is generated separately.
|
||||
*
|
||||
* @author Alexander Nozik
|
||||
*/
|
||||
public abstract class GeneratorAction<R> extends GenericAction<Void, R> {
|
||||
|
||||
public GeneratorAction(@NotNull String name,@NotNull Class<R> outputType) {
|
||||
super(name, Void.class, outputType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataNode<R> run(Context context, DataNode<? extends Void> data, Meta actionMeta) {
|
||||
Chronicle log = context.getHistory().getChronicle(getName());
|
||||
|
||||
Stream<ActionResult<R>> results = nameStream().map(name -> {
|
||||
Goal<R> goal = new GeneratorGoal<>(getExecutorService(context, actionMeta), () -> generateData(name));
|
||||
return new ActionResult<>(name, getOutputType(), goal, generateMeta(name), log);
|
||||
});
|
||||
|
||||
return wrap(resultNodeName(), actionMeta, results);
|
||||
}
|
||||
|
||||
protected abstract Stream<String> nameStream();
|
||||
|
||||
protected abstract Meta generateMeta(String name);
|
||||
|
||||
protected abstract R generateData(String name);
|
||||
|
||||
protected String resultNodeName() {
|
||||
return "";
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright 2015 Alexander Nozik.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package hep.dataforge.actions;
|
||||
|
||||
import hep.dataforge.data.DataNode;
|
||||
import hep.dataforge.data.DataNodeBuilder;
|
||||
import hep.dataforge.data.DataSet;
|
||||
import hep.dataforge.data.NamedData;
|
||||
import hep.dataforge.description.ValueDef;
|
||||
import hep.dataforge.meta.Meta;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* The class to builder groups of content with annotation defined rules
|
||||
*
|
||||
* @author Alexander Nozik
|
||||
*/
|
||||
|
||||
public class GroupBuilder {
|
||||
|
||||
/**
|
||||
* Create grouping rule that creates groups for different values of value
|
||||
* field with name {@code tag}
|
||||
*
|
||||
* @param tag
|
||||
* @param defaultTagValue
|
||||
* @return
|
||||
*/
|
||||
public static GroupRule byValue(final String tag, String defaultTagValue) {
|
||||
return new GroupRule() {
|
||||
@Override
|
||||
public <T> List<DataNode<T>> group(DataNode<T> input) {
|
||||
Map<String, DataNodeBuilder<T>> map = new HashMap<>();
|
||||
|
||||
input.forEach((NamedData<? extends T> data) -> {
|
||||
String tagValue = data.getMeta().getString(tag, defaultTagValue);
|
||||
if (!map.containsKey(tagValue)) {
|
||||
DataNodeBuilder<T> builder = DataSet.Companion.edit(input.getType());
|
||||
builder.setName(tagValue);
|
||||
//builder.setMeta(new MetaBuilder(DEFAULT_META_NAME).putValue("tagValue", tagValue));
|
||||
//PENDING share meta here?
|
||||
map.put(tagValue, builder);
|
||||
}
|
||||
map.get(tagValue).add(data);
|
||||
});
|
||||
|
||||
return map.values().stream().<DataNode<T>>map(DataNodeBuilder::build).collect(Collectors.toList());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@ValueDef(key = "byValue", required = true, info = "The name of annotation value by which grouping should be made")
|
||||
@ValueDef(key = "defaultValue", def = "default", info = "Default value which should be used for content in which the grouping value is not presented")
|
||||
public static GroupRule byMeta(Meta config) {
|
||||
//TODO expand grouping options
|
||||
if (config.hasValue("byValue")) {
|
||||
return byValue(config.getString("byValue"), config.getString("defaultValue", "default"));
|
||||
} else {
|
||||
return Collections::singletonList;
|
||||
}
|
||||
}
|
||||
|
||||
public interface GroupRule {
|
||||
<T> List<DataNode<T>> group(DataNode<T> input);
|
||||
}
|
||||
}
|
@ -0,0 +1,170 @@
|
||||
/*
|
||||
* Copyright 2015 Alexander Nozik.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package hep.dataforge.actions;
|
||||
|
||||
import hep.dataforge.context.Context;
|
||||
import hep.dataforge.data.Data;
|
||||
import hep.dataforge.data.DataNode;
|
||||
import hep.dataforge.data.NamedData;
|
||||
import hep.dataforge.goals.AbstractGoal;
|
||||
import hep.dataforge.goals.Goal;
|
||||
import hep.dataforge.meta.Laminate;
|
||||
import hep.dataforge.meta.Meta;
|
||||
import hep.dataforge.meta.MetaBuilder;
|
||||
import hep.dataforge.names.Name;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Action with multiple input data pieces but single output
|
||||
*
|
||||
* @param <T>
|
||||
* @param <R>
|
||||
* @author Alexander Nozik
|
||||
*/
|
||||
public abstract class ManyToOneAction<T, R> extends GenericAction<T, R> {
|
||||
|
||||
public ManyToOneAction(@NotNull String name, @NotNull Class<T> inputType, @NotNull Class<R> outputType) {
|
||||
super(name, inputType, outputType);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public DataNode<R> run(Context context, DataNode<? extends T> set, Meta actionMeta) {
|
||||
checkInput(set);
|
||||
List<DataNode<T>> groups = buildGroups(context, (DataNode<T>) set, actionMeta);
|
||||
return wrap(getResultName(set.getName(), actionMeta), set.getMeta(), groups.stream().map(group->runGroup(context, group,actionMeta)));
|
||||
}
|
||||
|
||||
public ActionResult<R> runGroup(Context context, DataNode<T> data, Meta actionMeta) {
|
||||
Meta outputMeta = outputMeta(data).build();
|
||||
Goal<R> goal = new ManyToOneGoal(context, data, actionMeta, outputMeta);
|
||||
String resultName = data.getName() == null ? getName() : data.getName();
|
||||
|
||||
return new ActionResult<>(resultName, getOutputType(), goal, outputMeta, context.getHistory().getChronicle(resultName));
|
||||
}
|
||||
|
||||
protected List<DataNode<T>> buildGroups(Context context, DataNode<T> input, Meta actionMeta) {
|
||||
return GroupBuilder.byMeta(inputMeta(context, input.getMeta(), actionMeta)).group(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform actual calculation
|
||||
*
|
||||
* @param nodeName
|
||||
* @param input
|
||||
* @param meta
|
||||
* @return
|
||||
*/
|
||||
protected abstract R execute(Context context, String nodeName, Map<String, T> input, Laminate meta);
|
||||
|
||||
/**
|
||||
* Build output meta for resulting object
|
||||
*
|
||||
* @param input
|
||||
* @return
|
||||
*/
|
||||
protected MetaBuilder outputMeta(DataNode<T> input) {
|
||||
MetaBuilder builder = new MetaBuilder(MetaBuilder.DEFAULT_META_NAME)
|
||||
.putValue("name", input.getName())
|
||||
.putValue("type", input.getType().getName());
|
||||
input.dataStream().forEach((NamedData<? extends T> data) -> {
|
||||
MetaBuilder dataNode = new MetaBuilder("data")
|
||||
.putValue("name", data.getName());
|
||||
if (!data.getType().equals(input.getType())) {
|
||||
dataNode.putValue("type", data.getType().getName());
|
||||
}
|
||||
// if (!data.meta().isEmpty()) {
|
||||
// dataNode.putNode(DataFactory.NODE_META_KEY, data.meta());
|
||||
// }
|
||||
builder.putNode(dataNode);
|
||||
});
|
||||
return builder;
|
||||
}
|
||||
|
||||
/**
|
||||
* An action to be performed before each group evaluation
|
||||
*
|
||||
* @param input
|
||||
*/
|
||||
protected void beforeGroup(Context context, DataNode<? extends T> input) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* An action to be performed after each group evaluation
|
||||
*
|
||||
* @param output
|
||||
*/
|
||||
protected void afterGroup(Context context, String groupName, Meta outputMeta, R output) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Action goal {@code fainOnError()} delegate
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
protected boolean failOnError() {
|
||||
return true;
|
||||
}
|
||||
|
||||
private class ManyToOneGoal extends AbstractGoal<R> {
|
||||
|
||||
private final Context context;
|
||||
private final DataNode<T> data;
|
||||
private final Meta actionMeta;
|
||||
private final Meta outputMeta;
|
||||
|
||||
public ManyToOneGoal(Context context, DataNode<T> data, Meta actionMeta, Meta outputMeta) {
|
||||
super(getExecutorService(context, actionMeta));
|
||||
this.context = context;
|
||||
this.data = data;
|
||||
this.actionMeta = actionMeta;
|
||||
this.outputMeta = outputMeta;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean failOnError() {
|
||||
return ManyToOneAction.this.failOnError();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<Goal<?>> dependencies() {
|
||||
return data.nodeGoal().dependencies();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected R compute() throws Exception {
|
||||
Laminate meta = inputMeta(context, data.getMeta(), actionMeta);
|
||||
Thread.currentThread().setName(Name.Companion.joinString(getThreadName(actionMeta), data.getName()));
|
||||
beforeGroup(context, data);
|
||||
// In this moment, all the data is already calculated
|
||||
Map<String, T> collection = data.dataStream()
|
||||
.filter(Data::isValid) // filter valid data only
|
||||
.collect(Collectors.toMap(NamedData::getName, Data::get));
|
||||
R res = execute(context, data.getName(), collection, meta);
|
||||
afterGroup(context, data.getName(), outputMeta, res);
|
||||
return res;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
package hep.dataforge.actions;
|
||||
|
||||
import hep.dataforge.context.Context;
|
||||
import hep.dataforge.data.Data;
|
||||
import hep.dataforge.data.DataNode;
|
||||
import hep.dataforge.data.DataNodeBuilder;
|
||||
import hep.dataforge.data.DataTree;
|
||||
import hep.dataforge.goals.Goal;
|
||||
import hep.dataforge.goals.PipeGoal;
|
||||
import hep.dataforge.meta.Laminate;
|
||||
import hep.dataforge.meta.Meta;
|
||||
import hep.dataforge.names.Name;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A split action that creates multiple Data from each input element (some input elements could be ignored)
|
||||
* Created by darksnake on 28-Jan-17.
|
||||
*/
|
||||
public abstract class OneToManyAction<T, R> extends GenericAction<T, R> {
|
||||
|
||||
public OneToManyAction(@NotNull String name, @NotNull Class<T> inputType, @NotNull Class<R> outputType) {
|
||||
super(name, inputType, outputType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataNode<R> run(Context context, DataNode<? extends T> data, Meta actionMeta) {
|
||||
checkInput(data);
|
||||
DataNodeBuilder<R> builder = DataTree.Companion.edit(getOutputType());
|
||||
data.forEach(datum -> {
|
||||
String inputName = datum.getName();
|
||||
Laminate inputMeta = new Laminate(datum.getMeta(), actionMeta);
|
||||
Map<String, Meta> metaMap = prepareMeta(context, inputName, inputMeta);
|
||||
metaMap.forEach((outputName, outputMeta) -> {
|
||||
Goal<R> goal = new PipeGoal<>(datum.getGoal(), input -> execute(context, inputName, outputName, input, inputMeta));
|
||||
Data<R> res = new Data<R>(getOutputType(), goal, outputMeta);
|
||||
builder.putData(placement(inputName, outputName), res, false);
|
||||
});
|
||||
});
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* The placement rule for result Data. By default each input element is transformed into a node with
|
||||
*
|
||||
* @param inputName
|
||||
* @param outputName
|
||||
* @return
|
||||
*/
|
||||
protected String placement(String inputName, String outputName) {
|
||||
return Name.Companion.joinString(inputName, outputName);
|
||||
}
|
||||
|
||||
//TODO add node meta
|
||||
|
||||
/**
|
||||
* @param context
|
||||
* @param inputName
|
||||
* @param meta
|
||||
* @return
|
||||
*/
|
||||
protected abstract Map<String, Meta> prepareMeta(Context context, String inputName, Laminate meta);
|
||||
|
||||
protected abstract R execute(Context context, String inputName, String outputName, T input, Laminate meta);
|
||||
|
||||
}
|
@ -0,0 +1,150 @@
|
||||
/*
|
||||
* Copyright 2015 Alexander Nozik.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package hep.dataforge.actions;
|
||||
|
||||
import hep.dataforge.context.Context;
|
||||
import hep.dataforge.context.Global;
|
||||
import hep.dataforge.data.DataNode;
|
||||
import hep.dataforge.data.NamedData;
|
||||
import hep.dataforge.goals.PipeGoal;
|
||||
import hep.dataforge.io.history.Chronicle;
|
||||
import hep.dataforge.meta.Laminate;
|
||||
import hep.dataforge.meta.Meta;
|
||||
import hep.dataforge.names.Name;
|
||||
import kotlin.Pair;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
/**
|
||||
* A template to builder actions that reflect strictly one to one content
|
||||
* transformations
|
||||
*
|
||||
* @param <T>
|
||||
* @param <R>
|
||||
* @author Alexander Nozik
|
||||
* @version $Id: $Id
|
||||
*/
|
||||
public abstract class OneToOneAction<T, R> extends GenericAction<T, R> {
|
||||
|
||||
public OneToOneAction(String name, Class<T> inputType, Class<R> outputType) {
|
||||
super(name,inputType,outputType);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public DataNode<R> run(Context context, DataNode<? extends T> set, Meta actionMeta) {
|
||||
checkInput(set);
|
||||
if (set.isEmpty()) {
|
||||
throw new RuntimeException(getName() + ": Running 1 to 1 action on empty data node");
|
||||
}
|
||||
|
||||
return wrap(
|
||||
set.getName(),
|
||||
set.getMeta(),
|
||||
set.dataStream(true).map(data -> runOne(context, data, actionMeta))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build asynchronous result for single data. Data types separated from
|
||||
* action generics to be able to operate maps instead of raw data
|
||||
*
|
||||
* @param data
|
||||
* @param actionMeta
|
||||
* @return
|
||||
*/
|
||||
protected ActionResult<R> runOne(Context context, NamedData<? extends T> data, Meta actionMeta) {
|
||||
if (!this.getInputType().isAssignableFrom(data.getType())) {
|
||||
throw new RuntimeException(String.format("Type mismatch in action %s. %s expected, but %s recieved",
|
||||
getName(), getInputType().getName(), data.getType().getName()));
|
||||
}
|
||||
|
||||
Pair<String, Meta> resultParamters = outputParameters(context, data, actionMeta);
|
||||
|
||||
Laminate meta = inputMeta(context, data.getMeta(), actionMeta);
|
||||
String resultName = resultParamters.getFirst();
|
||||
Meta outputMeta = resultParamters.getSecond();
|
||||
|
||||
PipeGoal<? extends T, R> goal = new PipeGoal<>(getExecutorService(context, meta), data.getGoal(),
|
||||
input -> {
|
||||
Thread.currentThread().setName(Name.Companion.joinString(getThreadName(actionMeta), resultName));
|
||||
return transform(context, resultName, input, meta);
|
||||
}
|
||||
);
|
||||
return new ActionResult<>(resultName, getOutputType(), goal, outputMeta, context.getHistory().getChronicle(resultName));
|
||||
}
|
||||
|
||||
protected Chronicle getLog(Context context, String dataName) {
|
||||
return context.getHistory().getChronicle(Name.Companion.joinString(dataName, getName()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param name name of the input item
|
||||
* @param input input data
|
||||
* @param inputMeta combined meta for this evaluation. Includes data meta,
|
||||
* group meta and action meta
|
||||
* @return
|
||||
*/
|
||||
private R transform(Context context, String name, T input, Laminate inputMeta) {
|
||||
beforeAction(context, name, input, inputMeta);
|
||||
R res = execute(context, name, input, inputMeta);
|
||||
afterAction(context, name, res, inputMeta);
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method to run action outside of context or execution chain
|
||||
*
|
||||
* @param input
|
||||
* @param metaLayers
|
||||
* @return
|
||||
*/
|
||||
public R simpleRun(T input, Meta... metaLayers) {
|
||||
return transform(Global.INSTANCE, "simpleRun", input, inputMeta(Global.INSTANCE, metaLayers));
|
||||
}
|
||||
|
||||
protected abstract R execute(Context context, String name, T input, Laminate meta);
|
||||
|
||||
/**
|
||||
* Build output meta for given data. This meta is calculated on action call
|
||||
* (no lazy calculations). By default output meta is the same as input data
|
||||
* meta.
|
||||
*
|
||||
* @param actionMeta
|
||||
* @param data
|
||||
* @return
|
||||
*/
|
||||
protected Pair<String, Meta> outputParameters(Context context, NamedData<? extends T> data, Meta actionMeta) {
|
||||
return new Pair<>(getResultName(data.getName(), actionMeta), data.getMeta());
|
||||
}
|
||||
|
||||
protected void afterAction(Context context, String name, R res, Laminate meta) {
|
||||
Logger logger = getLogger(context, meta);
|
||||
if (res == null) {
|
||||
logger.error("Action {} returned 'null' on data {}", getName(), name);
|
||||
throw new RuntimeException("Null result of action");//TODO add emty data to handle this
|
||||
}
|
||||
logger.debug("Action '{}[{}]' is finished", getName(), name);
|
||||
}
|
||||
|
||||
protected void beforeAction(Context context, String name, T datum, Laminate meta) {
|
||||
if (context.getBoolean("actions.reportStart", false)) {
|
||||
report(context, name, "Starting action {} on data with name {} with following configuration: \n\t {}", getName(), name, meta.toString());
|
||||
}
|
||||
getLogger(context, meta).debug("Starting action '{}[{}]'", getName(), name);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2018 Alexander Nozik.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package hep.dataforge.connections;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public interface AutoConnectible extends Connectible {
|
||||
|
||||
ConnectionHelper getConnectionHelper();
|
||||
|
||||
@Override
|
||||
default void connect(Connection connection, String... roles) {
|
||||
getConnectionHelper().connect(connection, roles);
|
||||
}
|
||||
|
||||
@Override
|
||||
default <T> Stream<T> connections(String role, Class<T> type) {
|
||||
return getConnectionHelper().connections(role, type);
|
||||
}
|
||||
|
||||
default <T> Optional<T> optConnection(String role, Class<T> type) {
|
||||
return getConnectionHelper().optConnection(role, type);
|
||||
}
|
||||
|
||||
@Override
|
||||
default void disconnect(Connection connection) {
|
||||
getConnectionHelper().disconnect(connection);
|
||||
}
|
||||
}
|
@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Copyright 2018 Alexander Nozik.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package hep.dataforge.connections;
|
||||
|
||||
import hep.dataforge.UtilsKt;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Something that could be connected
|
||||
*
|
||||
* @author Alexander Nozik
|
||||
*/
|
||||
public interface Connectible {
|
||||
|
||||
/**
|
||||
* Register a new connection with given roles
|
||||
*
|
||||
* @param connection
|
||||
* @param roles
|
||||
*/
|
||||
void connect(Connection connection, String... roles);
|
||||
|
||||
/**
|
||||
* Get a stream of all connections with given role and type. Role could be regexp
|
||||
*
|
||||
* @param role
|
||||
* @param type
|
||||
* @param <T>
|
||||
* @return
|
||||
*/
|
||||
<T> Stream<T> connections(String role, Class<T> type);
|
||||
|
||||
/**
|
||||
* Disconnect given connection
|
||||
*
|
||||
* @param connection
|
||||
*/
|
||||
void disconnect(Connection connection);
|
||||
|
||||
|
||||
/**
|
||||
* For each connection of given class and role. Role may be empty, but type
|
||||
* is mandatory
|
||||
*
|
||||
* @param <T>
|
||||
* @param role
|
||||
* @param type
|
||||
* @param action
|
||||
*/
|
||||
default <T> void forEachConnection(String role, Class<T> type, Consumer<T> action) {
|
||||
connections(role, type).forEach(action);
|
||||
}
|
||||
|
||||
default <T> void forEachConnection(Class<T> type, Consumer<T> action) {
|
||||
forEachConnection(".*", type, action);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A list of all available roles
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
default List<RoleDef> roleDefs() {
|
||||
return UtilsKt.listAnnotations(this.getClass(), RoleDef.class, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a role definition for given name. Null if not found.
|
||||
*
|
||||
* @param name
|
||||
* @return
|
||||
*/
|
||||
default Optional<RoleDef> optRoleDef(String name) {
|
||||
return roleDefs().stream().filter((roleDef) -> roleDef.name().equals(name)).findFirst();
|
||||
}
|
||||
|
||||
/**
|
||||
* A quick way to find if this object accepts connection with given role
|
||||
*
|
||||
* @param name
|
||||
* @return
|
||||
*/
|
||||
default boolean acceptsRole(String name) {
|
||||
return roleDefs().stream().anyMatch((roleDef) -> roleDef.name().equals(name));
|
||||
}
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright 2018 Alexander Nozik.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package hep.dataforge.connections;
|
||||
|
||||
import hep.dataforge.context.Context;
|
||||
import hep.dataforge.meta.Meta;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* A connection which could be applied to object that could receive connection.
|
||||
* Usually connection does not invoke {@code open} method itself, but delegates it to {@code Connectible}
|
||||
*
|
||||
* @author Alexander Nozik
|
||||
*/
|
||||
public interface Connection extends AutoCloseable {
|
||||
|
||||
/**
|
||||
* Create a connection using context connection factory provider if it is possible
|
||||
*
|
||||
* @param context
|
||||
* @param meta
|
||||
* @return
|
||||
*/
|
||||
static Optional<Connection> buildConnection(Connectible obj, Context context, Meta meta) {
|
||||
String type = meta.getString("type");
|
||||
return Optional.ofNullable(context.findService(ConnectionFactory.class, it -> Objects.equals(it.getType(), type)))
|
||||
.map(it -> it.build(obj, context, meta));
|
||||
}
|
||||
|
||||
String LOGGER_ROLE = "log";
|
||||
String EVENT_HANDLER_ROLE = "eventHandler";
|
||||
|
||||
default boolean isOpen() {
|
||||
return true;
|
||||
}
|
||||
|
||||
default void open(Object object) throws Exception {
|
||||
//do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
default void close() throws Exception {
|
||||
//do nothing
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2018 Alexander Nozik.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package hep.dataforge.connections;
|
||||
|
||||
import hep.dataforge.context.Context;
|
||||
import hep.dataforge.meta.Meta;
|
||||
|
||||
/**
|
||||
* A factory SPI class for connections
|
||||
*/
|
||||
public interface ConnectionFactory {
|
||||
String getType();
|
||||
|
||||
/**
|
||||
*
|
||||
* @param obj an object for which this connections is intended
|
||||
* @param context context of the connection (could be different from connectible context)
|
||||
* @param meta configuration for connection
|
||||
* @param <T> type of the connectible
|
||||
* @return
|
||||
*/
|
||||
<T extends Connectible> Connection build(T obj, Context context, Meta meta);
|
||||
}
|
@ -0,0 +1,163 @@
|
||||
/*
|
||||
* Copyright 2018 Alexander Nozik.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package hep.dataforge.connections;
|
||||
|
||||
import hep.dataforge.Named;
|
||||
import hep.dataforge.context.Context;
|
||||
import hep.dataforge.meta.Meta;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* A helper class to manage Connectible objects in the same fashion
|
||||
*/
|
||||
public class ConnectionHelper implements Connectible {
|
||||
//TODO isolate errors inside connections
|
||||
private final Map<Connection, Set<String>> connections = new HashMap<>();
|
||||
private final Connectible caller;
|
||||
private final Logger logger = LoggerFactory.getLogger(getClass());
|
||||
|
||||
public ConnectionHelper(Connectible caller) {
|
||||
this.caller = caller;
|
||||
}
|
||||
|
||||
// public Logger getLogger() {
|
||||
// return logger;
|
||||
// }
|
||||
|
||||
/**
|
||||
* Attach connection
|
||||
*
|
||||
* @param connection
|
||||
* @param roles
|
||||
*/
|
||||
@Override
|
||||
public synchronized void connect(Connection connection, String... roles) {
|
||||
logger.info("Attaching connection {} with roles {}", connection.toString(), String.join(", ", roles));
|
||||
//Checking if connection could serve given roles
|
||||
for (String role : roles) {
|
||||
if (!acceptsRole(role)) {
|
||||
logger.warn("The connectible does not support role {}", role);
|
||||
} else {
|
||||
roleDefs().stream().filter((roleDef) -> roleDef.name().equals(role)).forEach(rd -> {
|
||||
if (!rd.objectType().isInstance(connection)) {
|
||||
logger.error("Connection does not meet type requirement for role {}. Must be {}.",
|
||||
role, rd.objectType().getName());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Set<String> roleSet = new HashSet<>(Arrays.asList(roles));
|
||||
if (this.connections.containsKey(connection)) {
|
||||
//updating roles of existing connection
|
||||
connections.get(connection).addAll(roleSet);
|
||||
} else {
|
||||
this.connections.put(connection, roleSet);
|
||||
}
|
||||
|
||||
try {
|
||||
logger.debug("Opening connection {}", connection.toString());
|
||||
connection.open(caller);
|
||||
} catch (Exception ex) {
|
||||
throw new RuntimeException("Can not open connection", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build connection (or connections if meta has multiple "connection" entries) and connect
|
||||
*
|
||||
* @param context
|
||||
* @param meta
|
||||
*/
|
||||
public void connect(Context context, Meta meta) {
|
||||
if (meta.hasMeta("connection")) {
|
||||
meta.getMetaList("connection").forEach(it -> connect(context, it));
|
||||
} else {
|
||||
String[] roles = meta.getStringArray("role", new String[]{});
|
||||
Connection.buildConnection(caller, context, meta).ifPresent(connection -> connect(connection, roles));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void disconnect(Connection connection) {
|
||||
if (connections.containsKey(connection)) {
|
||||
String conName = Named.Companion.nameOf(connection);
|
||||
try {
|
||||
logger.debug("Closing connection {}", conName);
|
||||
connection.close();
|
||||
} catch (Exception ex) {
|
||||
logger.error("Can not close connection", ex);
|
||||
}
|
||||
logger.info("Detaching connection {}", conName);
|
||||
this.connections.remove(connection);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get a stream of all connections for a given role. Stream could be empty
|
||||
*
|
||||
* @param role
|
||||
* @param type
|
||||
* @param <T>
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public <T> Stream<T> connections(String role, Class<T> type) {
|
||||
return connections.entrySet().stream()
|
||||
.filter(entry -> type.isInstance(entry.getKey()))
|
||||
.filter(entry -> role.isEmpty() || entry.getValue().stream().anyMatch(r -> r.matches(role)))
|
||||
.map(entry -> type.cast(entry.getKey()));
|
||||
}
|
||||
|
||||
public <T> Stream<T> connections(Class<T> type) {
|
||||
return connections.entrySet().stream()
|
||||
.filter(entry -> type.isInstance(entry.getKey()))
|
||||
.map(entry -> type.cast(entry.getKey()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a unique connection or first connection satisfying condition
|
||||
*
|
||||
* @param role
|
||||
* @param type
|
||||
* @param <T>
|
||||
* @return
|
||||
*/
|
||||
public <T> Optional<T> optConnection(String role, Class<T> type) {
|
||||
return connections(role, type).findFirst();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean acceptsRole(String name) {
|
||||
return caller.acceptsRole(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<RoleDef> roleDefs() {
|
||||
return caller.roleDefs();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<RoleDef> optRoleDef(String name) {
|
||||
return caller.optRoleDef(name);
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2018 Alexander Nozik.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package hep.dataforge.connections;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Inherited
|
||||
public @interface ConnectsTo {
|
||||
String type();
|
||||
String[] roles() default {};
|
||||
Class<? extends Connection> cl();
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2018 Alexander Nozik.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package hep.dataforge.connections;
|
||||
|
||||
import hep.dataforge.values.Value;
|
||||
import hep.dataforge.values.ValueFactory;
|
||||
|
||||
/**
|
||||
* Created by darksnake on 25-May-17.
|
||||
*/
|
||||
public interface NamedValueListener {
|
||||
void pushValue(String valueName, Value value);
|
||||
|
||||
default void pushValue(String valueName, Object obj) {
|
||||
pushValue(valueName, ValueFactory.of(obj));
|
||||
}
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright 2018 Alexander Nozik.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package hep.dataforge.connections;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* The role of connection served by this device
|
||||
*
|
||||
* @author Alexander Nozik
|
||||
*/
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Inherited
|
||||
@Repeatable(RoleDefs.class)
|
||||
public @interface RoleDef {
|
||||
/**
|
||||
* Role name
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
String name();
|
||||
|
||||
/**
|
||||
* The type of the object that could play this role
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
Class objectType() default Object.class;
|
||||
|
||||
/**
|
||||
* If true then only one connection of this role is allowed per object
|
||||
* @return
|
||||
*/
|
||||
boolean unique() default false;
|
||||
|
||||
/**
|
||||
* Role description
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
String info() default "";
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2018 Alexander Nozik.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package hep.dataforge.connections;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Alexander Nozik
|
||||
*/
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Inherited
|
||||
public @interface RoleDefs {
|
||||
RoleDef[] value();
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2018 Alexander Nozik.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package hep.dataforge.connections;
|
||||
|
||||
import hep.dataforge.values.Value;
|
||||
import hep.dataforge.values.ValueFactory;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Alexander Nozik
|
||||
*/
|
||||
public interface ValueListener {
|
||||
void pushValue(Value value);
|
||||
|
||||
default void pushValue(Object obj){
|
||||
pushValue(ValueFactory.of(obj));
|
||||
}
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright 2015 Alexander Nozik.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package hep.dataforge.description;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* NodeDef class.</p>
|
||||
*
|
||||
* @author Alexander Nozik
|
||||
* @version $Id: $Id
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Inherited
|
||||
@Repeatable(NodeDefs.class)
|
||||
public @interface NodeDef {
|
||||
|
||||
String key();
|
||||
|
||||
String info() default "";
|
||||
|
||||
boolean multiple() default false;
|
||||
|
||||
boolean required() default false;
|
||||
|
||||
String[] tags() default {};
|
||||
|
||||
/**
|
||||
* A list of child value descriptors
|
||||
*/
|
||||
ValueDef[] values() default {};
|
||||
|
||||
/**
|
||||
* A target class for this node to describe
|
||||
* @return
|
||||
*/
|
||||
Class type() default Object.class;
|
||||
|
||||
/**
|
||||
* The DataForge path to the resource containing the description. Following targets are supported:
|
||||
* <ol>
|
||||
* <li>resource</li>
|
||||
* <li>file</li>
|
||||
* <li>class</li>
|
||||
* <li>method</li>
|
||||
* <li>property</li>
|
||||
* </ol>
|
||||
*
|
||||
* Does not work if [type] is provided
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
String descriptor() default "";
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2015 Alexander Nozik.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package hep.dataforge.description;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
/**
|
||||
* <p>NodeDefs class.</p>
|
||||
*
|
||||
* @author Alexander Nozik
|
||||
* @version $Id: $Id
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Inherited
|
||||
public @interface NodeDefs {
|
||||
NodeDef[] value();
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2015 Alexander Nozik.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package hep.dataforge.description;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* The annotation defining Action name, info, input and output types.
|
||||
*
|
||||
* @author Alexander Nozik
|
||||
* @version $Id: $Id
|
||||
*/
|
||||
@Target({ElementType.TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Inherited
|
||||
public @interface TypedActionDef {
|
||||
|
||||
String name();
|
||||
|
||||
String info() default "";
|
||||
|
||||
Class<?> inputType() default Object.class;
|
||||
|
||||
Class<?> outputType() default Object.class;
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright 2015 Alexander Nozik.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package hep.dataforge.description;
|
||||
|
||||
import hep.dataforge.values.ValueType;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* Декларация параметра аннотации, который исползуется в контенте или методе,
|
||||
* параметром которого является аннотация
|
||||
*
|
||||
* @author Alexander Nozik
|
||||
* @version $Id: $Id
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Inherited
|
||||
@Repeatable(ValueDefs.class)
|
||||
public @interface ValueDef {
|
||||
|
||||
String key();
|
||||
|
||||
ValueType[] type() default {ValueType.STRING};
|
||||
|
||||
boolean multiple() default false;
|
||||
|
||||
String def() default "";
|
||||
|
||||
String info() default "";
|
||||
|
||||
boolean required() default true;
|
||||
|
||||
String[] allowed() default {};
|
||||
|
||||
Class enumeration() default Object.class;
|
||||
|
||||
String[] tags() default {};
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2015 Alexander Nozik.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package hep.dataforge.description;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
/**
|
||||
* <p>ValueDefs class.</p>
|
||||
*
|
||||
* @author Alexander Nozik
|
||||
* @version $Id: $Id
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Inherited
|
||||
public @interface ValueDefs {
|
||||
ValueDef[] value();
|
||||
}
|
96
dataforge-core/src/main/java/hep/dataforge/events/Event.java
Normal file
96
dataforge-core/src/main/java/hep/dataforge/events/Event.java
Normal file
@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright 2015 Alexander Nozik.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package hep.dataforge.events;
|
||||
|
||||
import hep.dataforge.meta.Meta;
|
||||
import hep.dataforge.meta.MetaBuilder;
|
||||
import hep.dataforge.meta.SimpleMetaMorph;
|
||||
import hep.dataforge.utils.DateTimeUtils;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
/**
|
||||
* A metamorph representing framework event
|
||||
*
|
||||
* @author Alexander Nozik
|
||||
*/
|
||||
public class Event extends SimpleMetaMorph {
|
||||
public static final String EVENT_PRIORITY_KEY = "priority";
|
||||
public static final String EVENT_TYPE_KEY = "type";
|
||||
public static final String EVENT_SOURCE_KEY = "sourceTag";
|
||||
public static final String EVENT_TIME_KEY = "time";
|
||||
|
||||
/**
|
||||
* Create an event with given basic parameters and additional meta data. All
|
||||
* values except type could be null or empty
|
||||
*
|
||||
* @param type
|
||||
* @param source
|
||||
* @param priority
|
||||
* @param time
|
||||
* @param additionalMeta
|
||||
*/
|
||||
public static Event make(String type, String source, int priority, Instant time, Meta additionalMeta) {
|
||||
MetaBuilder builder = new MetaBuilder("event");
|
||||
if (additionalMeta != null) {
|
||||
builder.update(additionalMeta.getBuilder());
|
||||
}
|
||||
|
||||
builder.setValue(EVENT_TYPE_KEY, type);
|
||||
|
||||
if (time == null) {
|
||||
time = DateTimeUtils.now();
|
||||
}
|
||||
builder.setValue(EVENT_TIME_KEY, time);
|
||||
if (source != null && !source.isEmpty()) {
|
||||
builder.setValue(EVENT_SOURCE_KEY, source);
|
||||
}
|
||||
if (priority != 0) {
|
||||
builder.setValue(EVENT_PRIORITY_KEY, priority);
|
||||
}
|
||||
return new Event(builder.build());
|
||||
}
|
||||
|
||||
//TODO add source context to event?
|
||||
|
||||
public Event(Meta meta) {
|
||||
super(meta);
|
||||
}
|
||||
|
||||
public int priority() {
|
||||
return getMeta().getInt(EVENT_PRIORITY_KEY, 0);
|
||||
}
|
||||
|
||||
public String type() {
|
||||
return getMeta().getString(EVENT_TYPE_KEY);
|
||||
}
|
||||
|
||||
public String sourceTag() {
|
||||
return getMeta().getString(EVENT_SOURCE_KEY, "");
|
||||
}
|
||||
|
||||
public Instant time() {
|
||||
return getMeta().getValue(EVENT_TIME_KEY).getTime();
|
||||
}
|
||||
|
||||
// /**
|
||||
// * get event string representation (header) to write in logs
|
||||
// *
|
||||
// * @return
|
||||
// */
|
||||
// @Override
|
||||
// String toString();
|
||||
}
|
@ -0,0 +1,97 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package hep.dataforge.events;
|
||||
|
||||
import hep.dataforge.meta.Meta;
|
||||
import hep.dataforge.meta.MetaBuilder;
|
||||
import hep.dataforge.utils.DateTimeUtils;
|
||||
import hep.dataforge.utils.GenericBuilder;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static hep.dataforge.events.Event.*;
|
||||
|
||||
/**
|
||||
* Th builder class for events
|
||||
*
|
||||
* @author Alexander Nozik
|
||||
*/
|
||||
public abstract class EventBuilder<E extends EventBuilder> implements GenericBuilder<Event, E> {
|
||||
|
||||
protected final MetaBuilder builder = new MetaBuilder("event");
|
||||
private final Map<String, Object> objectMap = new HashMap<>();
|
||||
|
||||
protected EventBuilder(String type) {
|
||||
this.builder.setValue(Event.EVENT_TYPE_KEY, type);
|
||||
}
|
||||
|
||||
public static EventBuilder make(String type) {
|
||||
return new ConcreteEventBuilder(type);
|
||||
}
|
||||
|
||||
public EventBuilder setTime(Instant time) {
|
||||
builder.setValue(EVENT_TIME_KEY, time);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set time of the event to current time
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public EventBuilder setTime() {
|
||||
builder.setValue(EVENT_TIME_KEY, DateTimeUtils.now());
|
||||
return this;
|
||||
}
|
||||
|
||||
public EventBuilder setSource(String source) {
|
||||
builder.setValue(EVENT_SOURCE_KEY, source);
|
||||
return this;
|
||||
}
|
||||
|
||||
public EventBuilder setPriority(int priority) {
|
||||
builder.setValue(EVENT_PRIORITY_KEY, priority);
|
||||
return this;
|
||||
}
|
||||
|
||||
public EventBuilder setMetaNode(String nodeName, Meta... nodes) {
|
||||
builder.setNode(nodeName, nodes);
|
||||
return this;
|
||||
}
|
||||
|
||||
public EventBuilder setMetaValue(String valueName, Object value) {
|
||||
builder.setValue(valueName, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Meta buildEventMeta() {
|
||||
if (!builder.hasValue(EVENT_TIME_KEY)) {
|
||||
setTime();
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Event build() {
|
||||
return new Event(buildEventMeta());
|
||||
}
|
||||
|
||||
private static class ConcreteEventBuilder extends EventBuilder<EventBuilder> {
|
||||
|
||||
public ConcreteEventBuilder(String type) {
|
||||
super(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventBuilder self() {
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright 2015 Alexander Nozik.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package hep.dataforge.events;
|
||||
|
||||
/**
|
||||
* A handler for events
|
||||
* @author Alexander Nozik
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface EventHandler{
|
||||
boolean pushEvent(Event event);
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright 2015 Alexander Nozik.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package hep.dataforge.events;
|
||||
|
||||
/**
|
||||
* An interface marking an object that can dispatch messages (send it to named
|
||||
* responder)
|
||||
*
|
||||
* @author Alexander Nozik
|
||||
*/
|
||||
public interface EventTransmitter {
|
||||
|
||||
/**
|
||||
* Send message and return true if message is successfully sent
|
||||
* @param address
|
||||
* @param event
|
||||
* @return
|
||||
*/
|
||||
boolean send(String address, Event event);
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2015 Alexander Nozik.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package hep.dataforge.exceptions;
|
||||
|
||||
/**
|
||||
* <p>AnonymousNotAlowedException class.</p>
|
||||
*
|
||||
* @author Alexander Nozik
|
||||
* @version $Id: $Id
|
||||
*/
|
||||
public class AnonymousNotAlowedException extends NamingException {
|
||||
|
||||
/**
|
||||
* Creates a new instance of <code>AnonymousNotAlowedException</code>
|
||||
* without detail message.
|
||||
*/
|
||||
public AnonymousNotAlowedException() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an instance of <code>AnonymousNotAlowedException</code> with
|
||||
* the specified detail message.
|
||||
*
|
||||
* @param msg the detail message.
|
||||
*/
|
||||
public AnonymousNotAlowedException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2015 Alexander Nozik.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package hep.dataforge.exceptions;
|
||||
|
||||
/**
|
||||
* <p>ChainPathNotSupportedException class.</p>
|
||||
*
|
||||
* @author Alexander Nozik
|
||||
* @version $Id: $Id
|
||||
*/
|
||||
public class ChainPathNotSupportedException extends NamingException {
|
||||
|
||||
/**
|
||||
* Creates a new instance of <code>ChainPathNotSupportedException</code>
|
||||
* without detail message.
|
||||
*/
|
||||
public ChainPathNotSupportedException(){
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an instance of <code>ChainPathNotSupportedException</code>
|
||||
* with the specified detail message.
|
||||
*
|
||||
* @param msg the detail message.
|
||||
*/
|
||||
public ChainPathNotSupportedException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright 2015 Alexander Nozik.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package hep.dataforge.exceptions;
|
||||
|
||||
import hep.dataforge.meta.Meta;
|
||||
|
||||
/**
|
||||
* Ошибка парсинга аннотации. В общем случае не обрабатывается
|
||||
*
|
||||
* @author Alexander Nozik
|
||||
* @version $Id: $Id
|
||||
*/
|
||||
public class ConfigurationException extends RuntimeException {
|
||||
|
||||
Meta an;
|
||||
|
||||
/**
|
||||
* Creates a new instance of <code>AnnotationParseException</code> without
|
||||
* detail message.
|
||||
*
|
||||
* @param source a {@link hep.dataforge.meta.Meta} object.
|
||||
*/
|
||||
public ConfigurationException(Meta source) {
|
||||
this.an = source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an instance of <code>AnnotationParseException</code> with the
|
||||
* specified detail message.
|
||||
*
|
||||
* @param source a {@link hep.dataforge.meta.Meta} object.
|
||||
* @param msg the detail message.
|
||||
*/
|
||||
public ConfigurationException(Meta source, String msg) {
|
||||
super(msg);
|
||||
this.an = source;
|
||||
}
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 2015 Alexander Nozik.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package hep.dataforge.exceptions;
|
||||
|
||||
/**
|
||||
* <p>ContentException class.</p>
|
||||
*
|
||||
* @author Alexander Nozik
|
||||
* @version $Id: $Id
|
||||
*/
|
||||
public class ContentException extends RuntimeException {
|
||||
|
||||
/**
|
||||
* Creates a new instance of <code>ContentException</code> without detail
|
||||
* message.
|
||||
*/
|
||||
public ContentException() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an instance of <code>ContentException</code> with the
|
||||
* specified detail message.
|
||||
*
|
||||
* @param msg the detail message.
|
||||
*/
|
||||
public ContentException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Constructor for ContentException.</p>
|
||||
*
|
||||
* @param string a {@link java.lang.String} object.
|
||||
* @param thrwbl a {@link java.lang.Throwable} object.
|
||||
*/
|
||||
public ContentException(String string, Throwable thrwbl) {
|
||||
super(string, thrwbl);
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package hep.dataforge.exceptions;
|
||||
|
||||
import hep.dataforge.Named;
|
||||
|
||||
public class ContextLockException extends RuntimeException {
|
||||
private final Object locker;
|
||||
|
||||
public ContextLockException(Object locker) {
|
||||
this.locker = locker;
|
||||
}
|
||||
|
||||
public ContextLockException() {
|
||||
this.locker = null;
|
||||
}
|
||||
|
||||
private String getObjectName() {
|
||||
if (locker instanceof Named) {
|
||||
return locker.getClass().getSimpleName() + ":" + ((Named) locker).getName();
|
||||
} else {
|
||||
return locker.getClass().getSimpleName();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
if (locker == null) {
|
||||
return "Context is locked";
|
||||
} else {
|
||||
return "Context is locked by " + getObjectName();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2015 Alexander Nozik.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package hep.dataforge.exceptions;
|
||||
|
||||
/**
|
||||
* <p>DataFormatException class.</p>
|
||||
*
|
||||
* @author Alexander Nozik
|
||||
* @version $Id: $Id
|
||||
*/
|
||||
public class DataFormatException extends NamingException {
|
||||
|
||||
/**
|
||||
* Creates a new instance of <code>DataSetFormatException</code> without
|
||||
* detail message.
|
||||
*/
|
||||
public DataFormatException() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an instance of <code>DataSetFormatException</code> with the
|
||||
* specified detail message.
|
||||
*
|
||||
* @param msg the detail message.
|
||||
*/
|
||||
public DataFormatException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2015 Alexander Nozik.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package hep.dataforge.exceptions;
|
||||
|
||||
/**
|
||||
* <p>DescriptionNotFoundException class.</p>
|
||||
*
|
||||
* @author Alexander Nozik
|
||||
* @version $Id: $Id
|
||||
*/
|
||||
public class DescriptionNotFoundException extends DescriptorException {
|
||||
|
||||
/**
|
||||
* Creates a new instance of <code>DescriptionNotFoundException</code>
|
||||
* without detail message.
|
||||
*/
|
||||
public DescriptionNotFoundException() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an instance of <code>DescriptionNotFoundException</code> with
|
||||
* the specified detail message.
|
||||
*
|
||||
* @param msg the detail message.
|
||||
*/
|
||||
public DescriptionNotFoundException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2015 Alexander Nozik.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package hep.dataforge.exceptions;
|
||||
|
||||
/**
|
||||
* <p>DescriptorException class.</p>
|
||||
*
|
||||
* @author Alexander Nozik
|
||||
* @version $Id: $Id
|
||||
*/
|
||||
public class DescriptorException extends RuntimeException {
|
||||
|
||||
/**
|
||||
* Creates a new instance of <code>DescriptorException</code> without detail
|
||||
* message.
|
||||
*/
|
||||
public DescriptorException() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an instance of <code>DescriptorException</code> with the
|
||||
* specified detail message.
|
||||
*
|
||||
* @param msg the detail message.
|
||||
*/
|
||||
public DescriptorException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2015 Alexander Nozik.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package hep.dataforge.exceptions;
|
||||
|
||||
/**
|
||||
* <p>DuplicateDescriptionException class.</p>
|
||||
*
|
||||
* @author Alexander Nozik
|
||||
* @version $Id: $Id
|
||||
*/
|
||||
public class DuplicateDescriptionException extends DescriptorException {
|
||||
|
||||
private final String name;
|
||||
private final boolean forElement;
|
||||
|
||||
public DuplicateDescriptionException(String name, boolean forElement) {
|
||||
this.name = name;
|
||||
this.forElement = forElement;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
if(forElement){
|
||||
return String.format("Duplicate description for element %s", name);
|
||||
} else {
|
||||
return String.format("Duplicate description for parameter %s", name);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2015 Alexander Nozik.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package hep.dataforge.exceptions;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author darksnake
|
||||
*/
|
||||
public class EnvelopeFormatException extends RuntimeException {
|
||||
|
||||
/**
|
||||
* Creates a new instance of <code>EnvelopeFormatException</code> without
|
||||
* detail message.
|
||||
*/
|
||||
public EnvelopeFormatException() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an instance of <code>EnvelopeFormatException</code> with the
|
||||
* specified detail message.
|
||||
*
|
||||
* @param msg the detail message.
|
||||
*/
|
||||
public EnvelopeFormatException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
|
||||
public EnvelopeFormatException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package hep.dataforge.exceptions;
|
||||
|
||||
import hep.dataforge.meta.Meta;
|
||||
|
||||
/**
|
||||
* An exception thrown in case message target is not found by current {@code Responder}
|
||||
* @author Alexander Nozik
|
||||
*/
|
||||
public class EnvelopeTargetNotFoundException extends RuntimeException {
|
||||
|
||||
private String targetType;
|
||||
private String targetName;
|
||||
private Meta targetMeta;
|
||||
|
||||
public EnvelopeTargetNotFoundException(String targetName) {
|
||||
this.targetName = targetName;
|
||||
}
|
||||
|
||||
public EnvelopeTargetNotFoundException(String targetType, String targetName) {
|
||||
this.targetType = targetType;
|
||||
this.targetName = targetName;
|
||||
}
|
||||
|
||||
public EnvelopeTargetNotFoundException(Meta targetMeta) {
|
||||
this.targetMeta = targetMeta;
|
||||
}
|
||||
|
||||
public EnvelopeTargetNotFoundException(String targetName, Meta targetMeta) {
|
||||
this.targetName = targetName;
|
||||
this.targetMeta = targetMeta;
|
||||
}
|
||||
|
||||
public EnvelopeTargetNotFoundException(String targetType, String targetName, Meta targetMeta) {
|
||||
this.targetType = targetType;
|
||||
this.targetName = targetName;
|
||||
this.targetMeta = targetMeta;
|
||||
}
|
||||
|
||||
public String getTargetType() {
|
||||
return targetType;
|
||||
}
|
||||
|
||||
public String getTargetName() {
|
||||
return targetName;
|
||||
}
|
||||
|
||||
public Meta getTargetMeta() {
|
||||
return targetMeta;
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright 2015 Alexander Nozik.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package hep.dataforge.exceptions;
|
||||
|
||||
/**
|
||||
* <p>NameNotFoundException class.</p>
|
||||
*
|
||||
* @author Alexander Nozik
|
||||
* @version $Id: $Id
|
||||
*/
|
||||
public class NameNotFoundException extends NamingException {
|
||||
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* Creates a new instance of
|
||||
* <code>NameNotFoundException</code> without detail message.
|
||||
*/
|
||||
public NameNotFoundException() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an instance of
|
||||
* <code>NameNotFoundException</code> with the specified detail message.
|
||||
*
|
||||
* @param name a {@link java.lang.String} object.
|
||||
*/
|
||||
public NameNotFoundException(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Constructor for NameNotFoundException.</p>
|
||||
*
|
||||
* @param name a {@link java.lang.String} object.
|
||||
* @param msg a {@link java.lang.String} object.
|
||||
*/
|
||||
public NameNotFoundException(String name, String msg) {
|
||||
super(msg);
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return super.getMessage() + buildMessage();
|
||||
}
|
||||
|
||||
protected String buildMessage() {
|
||||
return " The element with name \"" + getName() + "\" is not found.";
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Getter for the field <code>name</code>.</p>
|
||||
*
|
||||
* @return a {@link java.lang.String} object.
|
||||
*/
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2015 Alexander Nozik.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package hep.dataforge.exceptions;
|
||||
|
||||
/**
|
||||
* <p>NamingException class.</p>
|
||||
*
|
||||
* @author Alexander Nozik
|
||||
* @version $Id: $Id
|
||||
*/
|
||||
public class NamingException extends RuntimeException {
|
||||
|
||||
/**
|
||||
* Creates a new instance of <code>NewException</code> without detail
|
||||
* message.
|
||||
*/
|
||||
public NamingException() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an instance of <code>NewException</code> with the specified
|
||||
* detail message.
|
||||
*
|
||||
* @param msg the detail message.
|
||||
*/
|
||||
public NamingException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
package hep.dataforge.exceptions;
|
||||
|
||||
/**
|
||||
* Created by darksnake on 12-Nov-16.
|
||||
*/
|
||||
public class NonEmptyMetaMorphException extends IllegalStateException {
|
||||
private Class<?> type;
|
||||
|
||||
public NonEmptyMetaMorphException(Class<?> type) {
|
||||
super(String.format("Can not update non-empty MetaMorph for class '%s'", type.getSimpleName()));
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package hep.dataforge.exceptions;
|
||||
|
||||
import hep.dataforge.connections.Connection;
|
||||
|
||||
/**
|
||||
* An exception thrown when the request is sent to the closed connection
|
||||
*
|
||||
* @author Alexander Nozik
|
||||
*/
|
||||
public class NotConnectedException extends Exception {
|
||||
|
||||
Connection connection;
|
||||
|
||||
public NotConnectedException(Connection connection) {
|
||||
this.connection = connection;
|
||||
}
|
||||
|
||||
public Connection getConnection() {
|
||||
return connection;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return "The connection is not open";
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2015 Alexander Nozik.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package hep.dataforge.exceptions;
|
||||
|
||||
/**
|
||||
* This exception is used when some parameters or functions are not defined by
|
||||
* user.
|
||||
*
|
||||
* @author Alexander Nozik
|
||||
* @version $Id: $Id
|
||||
*/
|
||||
public class NotDefinedException extends IllegalStateException {
|
||||
|
||||
/**
|
||||
* Creates a new instance of
|
||||
* <code>NotDefinedException</code> without detail message.
|
||||
*/
|
||||
public NotDefinedException() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an instance of
|
||||
* <code>NotDefinedException</code> with the specified detail message.
|
||||
*
|
||||
* @param msg the detail message.
|
||||
*/
|
||||
public NotDefinedException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2015 Alexander Nozik.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package hep.dataforge.exceptions;
|
||||
|
||||
/**
|
||||
* Ошибка синтаксиса пути
|
||||
*
|
||||
* @author Alexander Nozik
|
||||
* @version $Id: $Id
|
||||
*/
|
||||
public class PathSyntaxException extends NamingException {
|
||||
|
||||
/**
|
||||
* Creates a new instance of <code>PathSyntaxException</code> without detail
|
||||
* message.
|
||||
*/
|
||||
public PathSyntaxException() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an instance of <code>PathSyntaxException</code> with the
|
||||
* specified detail message.
|
||||
*
|
||||
* @param msg the detail message.
|
||||
*/
|
||||
public PathSyntaxException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2015 Alexander Nozik.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package hep.dataforge.exceptions;
|
||||
|
||||
/**
|
||||
* <p>TargetNotProvidedException class.</p>
|
||||
*
|
||||
* @author Alexander Nozik
|
||||
* @version $Id: $Id
|
||||
*/
|
||||
public class TargetNotProvidedException extends NameNotFoundException {
|
||||
|
||||
/**
|
||||
* Creates a new instance of <code>TargetNotProvided</code> without detail
|
||||
* message.
|
||||
*/
|
||||
public TargetNotProvidedException(String target) {
|
||||
super(target);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an instance of <code>TargetNotProvided</code> with the
|
||||
* specified detail message.
|
||||
*
|
||||
* @param msg the detail message.
|
||||
*/
|
||||
public TargetNotProvidedException(String msg, String target) {
|
||||
super(msg, target);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String buildMessage() {
|
||||
return "The target \"" + getName() + "\" is not provided.";
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright 2015 Alexander Nozik.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package hep.dataforge.exceptions;
|
||||
|
||||
import hep.dataforge.values.Value;
|
||||
import hep.dataforge.values.ValueType;
|
||||
|
||||
/**
|
||||
* <p>ValueConversionException class.</p>
|
||||
*
|
||||
* @author Alexander Nozik
|
||||
* @version $Id: $Id
|
||||
*/
|
||||
public class ValueConversionException extends RuntimeException {
|
||||
|
||||
Value value;
|
||||
ValueType to;
|
||||
|
||||
|
||||
/**
|
||||
* <p>Constructor for ValueConversionException.</p>
|
||||
*
|
||||
* @param value a {@link hep.dataforge.values.Value} object.
|
||||
* @param to a {@link hep.dataforge.values.ValueType} object.
|
||||
*/
|
||||
public ValueConversionException(Value value, ValueType to) {
|
||||
this.value = value;
|
||||
this.to = to;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return String.format("Failed to convert value '%s' of type %s to %s", value.getString(), value.getType(), to.name());
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,148 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package hep.dataforge.goals;
|
||||
|
||||
import hep.dataforge.utils.ReferenceRegistry;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ForkJoinPool;
|
||||
|
||||
/**
|
||||
* @author Alexander Nozik
|
||||
*/
|
||||
public abstract class AbstractGoal<T> implements Goal<T> {
|
||||
|
||||
private final ReferenceRegistry<GoalListener<T>> listeners = new ReferenceRegistry<>();
|
||||
|
||||
|
||||
private final Executor executor;
|
||||
protected final GoalResult result = new GoalResult();
|
||||
private CompletableFuture<?> computation;
|
||||
private Thread thread;
|
||||
|
||||
public AbstractGoal(Executor executor) {
|
||||
this.executor = executor;
|
||||
}
|
||||
|
||||
public AbstractGoal() {
|
||||
this.executor = ForkJoinPool.commonPool();
|
||||
}
|
||||
|
||||
protected Logger getLogger() {
|
||||
return LoggerFactory.getLogger(getClass());
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void run() {
|
||||
if (!isRunning()) {
|
||||
//start all dependencies so they will occupy threads
|
||||
computation = CompletableFuture
|
||||
.allOf(dependencies()
|
||||
.map(dep -> {
|
||||
dep.run();//starting all dependencies
|
||||
return dep.asCompletableFuture();
|
||||
})
|
||||
.toArray(CompletableFuture[]::new))
|
||||
.whenCompleteAsync((res, err) -> {
|
||||
if (err != null) {
|
||||
getLogger().error("One of goal dependencies failed with exception", err);
|
||||
if (failOnError()) {
|
||||
this.result.completeExceptionally(err);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
thread = Thread.currentThread();
|
||||
//trigger start hooks
|
||||
listeners.forEach(GoalListener::onGoalStart);
|
||||
T r = compute();
|
||||
//triggering result hooks
|
||||
listeners.forEach(listener -> listener.onGoalComplete(r));
|
||||
this.result.complete(r);
|
||||
} catch (Exception ex) {
|
||||
//trigger exception hooks
|
||||
getLogger().error("Exception during goal execution", ex);
|
||||
listeners.forEach(listener -> listener.onGoalFailed(ex));
|
||||
this.result.completeExceptionally(ex);
|
||||
} finally {
|
||||
thread = null;
|
||||
}
|
||||
}, executor);
|
||||
}
|
||||
}
|
||||
|
||||
public Executor getExecutor() {
|
||||
return executor;
|
||||
}
|
||||
|
||||
protected abstract T compute() throws Exception;
|
||||
|
||||
/**
|
||||
* If true the goal will result in error if any of dependencies throws exception.
|
||||
* Otherwise it will be calculated event if some of dependencies are failed.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
protected boolean failOnError() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Abort internal goals process without canceling result. Use with
|
||||
* care
|
||||
*/
|
||||
protected void abort() {
|
||||
if (isRunning()) {
|
||||
if (this.computation != null) {
|
||||
this.computation.cancel(true);
|
||||
}
|
||||
if (thread != null) {
|
||||
thread.interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isRunning() {
|
||||
return this.result.isDone() || this.computation != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Abort current goals if it is in progress and set result. Useful for
|
||||
* caching purposes.
|
||||
*
|
||||
* @param result
|
||||
*/
|
||||
public final synchronized boolean complete(T result) {
|
||||
abort();
|
||||
return this.result.complete(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerListener(GoalListener<T> listener) {
|
||||
listeners.add(listener,true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<T> asCompletableFuture() {
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
protected class GoalResult extends CompletableFuture<T> {
|
||||
|
||||
@Override
|
||||
public boolean cancel(boolean mayInterruptIfRunning) {
|
||||
if (mayInterruptIfRunning) {
|
||||
abort();
|
||||
}
|
||||
return super.cancel(mayInterruptIfRunning);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user