mirror of
https://github.com/xpipe-io/xpipe.git
synced 2024-11-22 07:30:24 +00:00
More work on proxies
This commit is contained in:
parent
803ff2ccf2
commit
74691c5a03
30 changed files with 442 additions and 213 deletions
|
@ -15,6 +15,7 @@ import io.xpipe.core.store.ShellStore;
|
||||||
import io.xpipe.core.util.JacksonMapper;
|
import io.xpipe.core.util.JacksonMapper;
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.Getter;
|
||||||
import lombok.Value;
|
import lombok.Value;
|
||||||
import lombok.extern.jackson.Jacksonized;
|
import lombok.extern.jackson.Jacksonized;
|
||||||
|
|
||||||
|
@ -31,7 +32,7 @@ import static io.xpipe.beacon.BeaconConfig.BODY_SEPARATOR;
|
||||||
public class BeaconClient implements AutoCloseable {
|
public class BeaconClient implements AutoCloseable {
|
||||||
|
|
||||||
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
|
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
|
||||||
public static abstract class ClientInformation {
|
public abstract static class ClientInformation {
|
||||||
|
|
||||||
public final CliClientInformation cli() {
|
public final CliClientInformation cli() {
|
||||||
return (CliClientInformation) this;
|
return (CliClientInformation) this;
|
||||||
|
@ -55,6 +56,19 @@ public class BeaconClient implements AutoCloseable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JsonTypeName("reachableCheck")
|
||||||
|
@Value
|
||||||
|
@Builder
|
||||||
|
@Jacksonized
|
||||||
|
@EqualsAndHashCode(callSuper = false)
|
||||||
|
public static class ReachableCheckInformation extends ClientInformation {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toDisplayString() {
|
||||||
|
return "Reachable check";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@JsonTypeName("gateway")
|
@JsonTypeName("gateway")
|
||||||
@Value
|
@Value
|
||||||
@Builder
|
@Builder
|
||||||
|
@ -86,12 +100,14 @@ public class BeaconClient implements AutoCloseable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private final Closeable closeable;
|
@Getter
|
||||||
|
private final Closeable base;
|
||||||
|
|
||||||
private final InputStream in;
|
private final InputStream in;
|
||||||
private final OutputStream out;
|
private final OutputStream out;
|
||||||
|
|
||||||
private BeaconClient(Closeable closeable, InputStream in, OutputStream out) {
|
private BeaconClient(Closeable base, InputStream in, OutputStream out) {
|
||||||
this.closeable = closeable;
|
this.base = base;
|
||||||
this.in = in;
|
this.in = in;
|
||||||
this.out = out;
|
this.out = out;
|
||||||
}
|
}
|
||||||
|
@ -105,11 +121,33 @@ public class BeaconClient implements AutoCloseable {
|
||||||
|
|
||||||
public static BeaconClient connectProxy(ShellStore proxy) throws Exception {
|
public static BeaconClient connectProxy(ShellStore proxy) throws Exception {
|
||||||
var control = proxy.create().start();
|
var control = proxy.create().start();
|
||||||
var command = control.command("xpipe beacon").start();
|
var command = control.command("xpipe beacon --raw").start();
|
||||||
command.discardErr();
|
command.discardErr();
|
||||||
return new BeaconClient(() -> {
|
return new BeaconClient(command, command.getStdout(), command.getStdin()) {
|
||||||
command.close();
|
@Override
|
||||||
}, command.getStdout(), command.getStdin());
|
public <T extends ResponseMessage> T receiveResponse()
|
||||||
|
throws ConnectorException, ClientException, ServerException {
|
||||||
|
try {
|
||||||
|
sendEOF();
|
||||||
|
getRawOutputStream().close();
|
||||||
|
} catch (IOException ex) {
|
||||||
|
throw new ConnectorException(ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.receiveResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws ConnectorException {
|
||||||
|
try {
|
||||||
|
getRawInputStream().readAllBytes();
|
||||||
|
} catch (IOException ex) {
|
||||||
|
throw new ConnectorException(ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
super.close();
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Optional<BeaconClient> tryConnect(ClientInformation information) {
|
public static Optional<BeaconClient> tryConnect(ClientInformation information) {
|
||||||
|
@ -122,7 +160,7 @@ public class BeaconClient implements AutoCloseable {
|
||||||
|
|
||||||
public void close() throws ConnectorException {
|
public void close() throws ConnectorException {
|
||||||
try {
|
try {
|
||||||
closeable.close();
|
base.close();
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
throw new ConnectorException("Couldn't close client", ex);
|
throw new ConnectorException("Couldn't close client", ex);
|
||||||
}
|
}
|
||||||
|
@ -170,6 +208,13 @@ public class BeaconClient implements AutoCloseable {
|
||||||
sendObject(msg);
|
sendObject(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void sendEOF() throws ConnectorException {
|
||||||
|
try (OutputStream ignored = BeaconFormat.writeBlocks(out)) {
|
||||||
|
} catch (IOException ex) {
|
||||||
|
throw new ConnectorException("Couldn't write to socket", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void sendObject(JsonNode node) throws ConnectorException {
|
public void sendObject(JsonNode node) throws ConnectorException {
|
||||||
var writer = new StringWriter();
|
var writer = new StringWriter();
|
||||||
var mapper = JacksonMapper.newMapper();
|
var mapper = JacksonMapper.newMapper();
|
||||||
|
|
|
@ -10,6 +10,6 @@ public class BeaconJacksonModule extends SimpleModule {
|
||||||
context.registerSubtypes(
|
context.registerSubtypes(
|
||||||
new NamedType(BeaconClient.ApiClientInformation.class),
|
new NamedType(BeaconClient.ApiClientInformation.class),
|
||||||
new NamedType(BeaconClient.CliClientInformation.class),
|
new NamedType(BeaconClient.CliClientInformation.class),
|
||||||
new NamedType(BeaconClient.GatewayClientInformation.class));
|
new NamedType(BeaconClient.ReachableCheckInformation.class));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
139
beacon/src/main/java/io/xpipe/beacon/BeaconProxyImpl.java
Normal file
139
beacon/src/main/java/io/xpipe/beacon/BeaconProxyImpl.java
Normal file
|
@ -0,0 +1,139 @@
|
||||||
|
package io.xpipe.beacon;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
|
||||||
|
import io.xpipe.beacon.exchange.ProxyFunctionExchange;
|
||||||
|
import io.xpipe.beacon.exchange.ProxyReadConnectionExchange;
|
||||||
|
import io.xpipe.beacon.exchange.ProxyWriteConnectionExchange;
|
||||||
|
import io.xpipe.core.impl.InputStreamStore;
|
||||||
|
import io.xpipe.core.impl.OutputStreamStore;
|
||||||
|
import io.xpipe.core.source.DataSource;
|
||||||
|
import io.xpipe.core.source.DataSourceConnection;
|
||||||
|
import io.xpipe.core.source.DataSourceReadConnection;
|
||||||
|
import io.xpipe.core.source.WriteMode;
|
||||||
|
import io.xpipe.core.store.ShellStore;
|
||||||
|
import io.xpipe.core.util.JacksonMapper;
|
||||||
|
import io.xpipe.core.util.ProxyFunction;
|
||||||
|
import io.xpipe.core.util.ProxyProvider;
|
||||||
|
import io.xpipe.core.util.Proxyable;
|
||||||
|
import lombok.SneakyThrows;
|
||||||
|
|
||||||
|
import java.io.FilterInputStream;
|
||||||
|
import java.io.FilterOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
public class BeaconProxyImpl extends ProxyProvider {
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
private static DataSource<?> downstreamTransform(DataSource<?> input, ShellStore proxy) {
|
||||||
|
var proxyNode = JacksonMapper.newMapper().valueToTree(proxy);
|
||||||
|
var inputNode = JacksonMapper.newMapper().valueToTree(input);
|
||||||
|
var localNode = JacksonMapper.newMapper().valueToTree(ShellStore.local());
|
||||||
|
replace(inputNode, node -> node.equals(proxyNode) ? Optional.of(localNode) : Optional.empty());
|
||||||
|
return JacksonMapper.newMapper().treeToValue(inputNode, DataSource.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static JsonNode replace(JsonNode node, Function<JsonNode, Optional<JsonNode>> function) {
|
||||||
|
var value = function.apply(node);
|
||||||
|
if (value.isPresent()) {
|
||||||
|
return value.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!node.isObject()) {
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
var replacement = JsonNodeFactory.instance.objectNode();
|
||||||
|
var iterator = node.fields();
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
var stringJsonNodeEntry = iterator.next();
|
||||||
|
var resolved = function.apply(stringJsonNodeEntry.getValue()).orElse(stringJsonNodeEntry.getValue());
|
||||||
|
replacement.set(stringJsonNodeEntry.getKey(), resolved);
|
||||||
|
}
|
||||||
|
return replacement;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ShellStore getProxy(Object base) {
|
||||||
|
var proxy = base instanceof Proxyable p ? p.getProxy() : null;
|
||||||
|
return ShellStore.isLocal(proxy) ? (BeaconConfig.localProxy() ? proxy : null) : proxy;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isRemote(Object base) {
|
||||||
|
if (base == null) {
|
||||||
|
throw new IllegalArgumentException("Proxy base is null");
|
||||||
|
}
|
||||||
|
|
||||||
|
return getProxy(base) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T extends DataSourceReadConnection> T createRemoteReadConnection(DataSource<?> source, ShellStore proxy) throws Exception {
|
||||||
|
var downstream = downstreamTransform(source, proxy);
|
||||||
|
|
||||||
|
BeaconClient client = null;
|
||||||
|
try {
|
||||||
|
client = BeaconClient.connectProxy(proxy);
|
||||||
|
client.sendRequest(ProxyReadConnectionExchange.Request.builder()
|
||||||
|
.source(downstream)
|
||||||
|
.build());
|
||||||
|
client.receiveResponse();
|
||||||
|
BeaconClient finalClient = client;
|
||||||
|
var inputStream = new FilterInputStream(finalClient.receiveBody()) {
|
||||||
|
@Override
|
||||||
|
@SneakyThrows
|
||||||
|
public void close() throws IOException {
|
||||||
|
super.close();
|
||||||
|
finalClient.close();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var inputSource = DataSource.createInternalDataSource(source.getType(), new InputStreamStore(inputStream));
|
||||||
|
return (T) inputSource.openReadConnection();
|
||||||
|
} catch (Exception ex) {
|
||||||
|
if (client != null) client.close();
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T extends DataSourceConnection> T createRemoteWriteConnection(DataSource<?> source, WriteMode mode, ShellStore proxy) throws Exception {
|
||||||
|
var downstream = downstreamTransform(source, proxy);
|
||||||
|
|
||||||
|
BeaconClient client = null;
|
||||||
|
try {
|
||||||
|
client = BeaconClient.connectProxy(proxy);
|
||||||
|
client.sendRequest(ProxyWriteConnectionExchange.Request.builder()
|
||||||
|
.source(downstream)
|
||||||
|
.build());
|
||||||
|
BeaconClient finalClient = client;
|
||||||
|
var outputStream = new FilterOutputStream(client.sendBody()) {
|
||||||
|
@Override
|
||||||
|
@SneakyThrows
|
||||||
|
public void close() throws IOException {
|
||||||
|
super.close();
|
||||||
|
finalClient.receiveResponse();
|
||||||
|
finalClient.close();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var outputSource = DataSource.createInternalDataSource(source.getType(), new OutputStreamStore(outputStream));
|
||||||
|
return (T) outputSource.openWriteConnection(mode);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
if (client != null) client.close();
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SneakyThrows
|
||||||
|
public ProxyFunction call(ProxyFunction func, ShellStore proxy) {
|
||||||
|
try (var client = BeaconClient.connectProxy(proxy)) {
|
||||||
|
client.sendRequest(
|
||||||
|
ProxyFunctionExchange.Request.builder().function(func).build());
|
||||||
|
ProxyFunctionExchange.Response response = client.receiveResponse();
|
||||||
|
return response.getFunction();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,7 +18,8 @@ import java.nio.file.Path;
|
||||||
public class BeaconServer {
|
public class BeaconServer {
|
||||||
|
|
||||||
public static boolean isRunning() {
|
public static boolean isRunning() {
|
||||||
try (var ignored = BeaconClient.connect(null)) {
|
try (var ignored = BeaconClient.connect(
|
||||||
|
BeaconClient.ReachableCheckInformation.builder().build())) {
|
||||||
return true;
|
return true;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -1,21 +0,0 @@
|
||||||
package io.xpipe.beacon;
|
|
||||||
|
|
||||||
import io.xpipe.core.store.ShellStore;
|
|
||||||
|
|
||||||
public interface Proxyable {
|
|
||||||
|
|
||||||
public static ShellStore getProxy(Object base) {
|
|
||||||
var proxy = base instanceof Proxyable p ? p.getProxy() : null;
|
|
||||||
return ShellStore.isLocal(proxy) ? (BeaconConfig.localProxy() ? proxy : null) : proxy;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isRemote(Object base) {
|
|
||||||
if (base == null) {
|
|
||||||
throw new IllegalArgumentException("Proxy base is null");
|
|
||||||
}
|
|
||||||
|
|
||||||
return base instanceof Proxyable p && !ShellStore.isLocal(p.getProxy());
|
|
||||||
}
|
|
||||||
|
|
||||||
ShellStore getProxy();
|
|
||||||
}
|
|
|
@ -2,18 +2,18 @@ package io.xpipe.beacon.exchange;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||||
import io.xpipe.beacon.NamedFunction;
|
import io.xpipe.core.util.ProxyFunction;
|
||||||
import io.xpipe.beacon.RequestMessage;
|
import io.xpipe.beacon.RequestMessage;
|
||||||
import io.xpipe.beacon.ResponseMessage;
|
import io.xpipe.beacon.ResponseMessage;
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
import lombok.Value;
|
import lombok.Value;
|
||||||
import lombok.extern.jackson.Jacksonized;
|
import lombok.extern.jackson.Jacksonized;
|
||||||
|
|
||||||
public class NamedFunctionExchange implements MessageExchange {
|
public class ProxyFunctionExchange implements MessageExchange {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getId() {
|
public String getId() {
|
||||||
return "namedFunction";
|
return "proxyFunction";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Jacksonized
|
@Jacksonized
|
||||||
|
@ -21,9 +21,9 @@ public class NamedFunctionExchange implements MessageExchange {
|
||||||
@Value
|
@Value
|
||||||
public static class Request implements RequestMessage {
|
public static class Request implements RequestMessage {
|
||||||
|
|
||||||
@JsonSerialize(using = NamedFunction.Serializer.class, as = NamedFunction.class)
|
@JsonSerialize(using = ProxyFunction.Serializer.class, as = ProxyFunction.class)
|
||||||
@JsonDeserialize(using = NamedFunction.Deserializer.class, as = NamedFunction.class)
|
@JsonDeserialize(using = ProxyFunction.Deserializer.class, as = ProxyFunction.class)
|
||||||
NamedFunction<?> function;
|
ProxyFunction function;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Jacksonized
|
@Jacksonized
|
||||||
|
@ -31,6 +31,8 @@ public class NamedFunctionExchange implements MessageExchange {
|
||||||
@Value
|
@Value
|
||||||
public static class Response implements ResponseMessage {
|
public static class Response implements ResponseMessage {
|
||||||
|
|
||||||
Object returnValue;
|
@JsonSerialize(using = ProxyFunction.Serializer.class, as = ProxyFunction.class)
|
||||||
|
@JsonDeserialize(using = ProxyFunction.Deserializer.class, as = ProxyFunction.class)
|
||||||
|
ProxyFunction function;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
package io.xpipe.beacon.exchange;
|
||||||
|
|
||||||
|
import io.xpipe.beacon.RequestMessage;
|
||||||
|
import io.xpipe.beacon.ResponseMessage;
|
||||||
|
import io.xpipe.core.source.DataSource;
|
||||||
|
import io.xpipe.core.source.WriteMode;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.NonNull;
|
||||||
|
import lombok.Value;
|
||||||
|
import lombok.extern.jackson.Jacksonized;
|
||||||
|
|
||||||
|
public class ProxyWriteConnectionExchange implements MessageExchange {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return "proxyWriteConnection";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Jacksonized
|
||||||
|
@Builder
|
||||||
|
@Value
|
||||||
|
public static class Request implements RequestMessage {
|
||||||
|
@NonNull DataSource<?> source;
|
||||||
|
@NonNull WriteMode mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Jacksonized
|
||||||
|
@Builder
|
||||||
|
@Value
|
||||||
|
public static class Response implements ResponseMessage {
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,10 +1,13 @@
|
||||||
import com.fasterxml.jackson.databind.Module;
|
import com.fasterxml.jackson.databind.Module;
|
||||||
import io.xpipe.beacon.BeaconJacksonModule;
|
import io.xpipe.beacon.BeaconJacksonModule;
|
||||||
|
import io.xpipe.beacon.BeaconProxyImpl;
|
||||||
|
import io.xpipe.core.util.ProxyFunction;
|
||||||
import io.xpipe.beacon.exchange.*;
|
import io.xpipe.beacon.exchange.*;
|
||||||
import io.xpipe.beacon.exchange.api.QueryRawDataExchange;
|
import io.xpipe.beacon.exchange.api.QueryRawDataExchange;
|
||||||
import io.xpipe.beacon.exchange.api.QueryTableDataExchange;
|
import io.xpipe.beacon.exchange.api.QueryTableDataExchange;
|
||||||
import io.xpipe.beacon.exchange.api.QueryTextDataExchange;
|
import io.xpipe.beacon.exchange.api.QueryTextDataExchange;
|
||||||
import io.xpipe.beacon.exchange.cli.*;
|
import io.xpipe.beacon.exchange.cli.*;
|
||||||
|
import io.xpipe.core.util.ProxyProvider;
|
||||||
|
|
||||||
module io.xpipe.beacon {
|
module io.xpipe.beacon {
|
||||||
exports io.xpipe.beacon;
|
exports io.xpipe.beacon;
|
||||||
|
@ -25,8 +28,9 @@ module io.xpipe.beacon {
|
||||||
requires static lombok;
|
requires static lombok;
|
||||||
|
|
||||||
uses MessageExchange;
|
uses MessageExchange;
|
||||||
uses io.xpipe.beacon.NamedFunction;
|
uses ProxyFunction;
|
||||||
|
|
||||||
|
provides ProxyProvider with BeaconProxyImpl;
|
||||||
provides Module with BeaconJacksonModule;
|
provides Module with BeaconJacksonModule;
|
||||||
provides io.xpipe.beacon.exchange.MessageExchange with
|
provides io.xpipe.beacon.exchange.MessageExchange with
|
||||||
ForwardExchange,
|
ForwardExchange,
|
||||||
|
@ -37,7 +41,8 @@ module io.xpipe.beacon {
|
||||||
ListCollectionsExchange,
|
ListCollectionsExchange,
|
||||||
ListEntriesExchange,
|
ListEntriesExchange,
|
||||||
ModeExchange,
|
ModeExchange,
|
||||||
NamedFunctionExchange,
|
ProxyWriteConnectionExchange,
|
||||||
|
ProxyFunctionExchange,
|
||||||
StatusExchange,
|
StatusExchange,
|
||||||
StopExchange,
|
StopExchange,
|
||||||
RenameStoreExchange,
|
RenameStoreExchange,
|
||||||
|
|
|
@ -17,6 +17,7 @@ dependencies {
|
||||||
api group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: "2.13.0"
|
api group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: "2.13.0"
|
||||||
implementation group: 'com.fasterxml.jackson.module', name: 'jackson-module-parameter-names', version: "2.13.0"
|
implementation group: 'com.fasterxml.jackson.module', name: 'jackson-module-parameter-names', version: "2.13.0"
|
||||||
implementation group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-jsr310', version: "2.13.0"
|
implementation group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-jsr310', version: "2.13.0"
|
||||||
|
implementation group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-jdk8', version: "2.13.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
version = file('../misc/version').text
|
version = file('../misc/version').text
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
package io.xpipe.core.impl;
|
package io.xpipe.core.impl;
|
||||||
|
|
||||||
|
import io.xpipe.core.store.DataFlow;
|
||||||
import io.xpipe.core.store.StreamDataStore;
|
import io.xpipe.core.store.StreamDataStore;
|
||||||
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A data store that is only represented by an InputStream.
|
* A data store that is only represented by an InputStream.
|
||||||
* This can be useful for development.
|
|
||||||
*/
|
*/
|
||||||
public class InputStreamStore implements StreamDataStore {
|
public class InputStreamStore implements StreamDataStore {
|
||||||
|
|
||||||
|
@ -21,6 +21,11 @@ public class InputStreamStore implements StreamDataStore {
|
||||||
return in;
|
return in;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DataFlow getFlow() {
|
||||||
|
return DataFlow.INPUT;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean canOpen() {
|
public boolean canOpen() {
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -12,10 +12,10 @@ public abstract class LocalProcessControlProvider {
|
||||||
INSTANCE = layer != null
|
INSTANCE = layer != null
|
||||||
? ServiceLoader.load(layer, LocalProcessControlProvider.class)
|
? ServiceLoader.load(layer, LocalProcessControlProvider.class)
|
||||||
.findFirst()
|
.findFirst()
|
||||||
.orElseThrow()
|
.orElse(null)
|
||||||
: ServiceLoader.load(LocalProcessControlProvider.class)
|
: ServiceLoader.load(LocalProcessControlProvider.class)
|
||||||
.findFirst()
|
.findFirst()
|
||||||
.orElseThrow();
|
.orElse(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ShellProcessControl create() {
|
public static ShellProcessControl create() {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package io.xpipe.core.impl;
|
package io.xpipe.core.impl;
|
||||||
|
|
||||||
|
import io.xpipe.core.store.DataFlow;
|
||||||
import io.xpipe.core.store.StreamDataStore;
|
import io.xpipe.core.store.StreamDataStore;
|
||||||
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
@ -13,6 +14,11 @@ public class OutputStreamStore implements StreamDataStore {
|
||||||
this.out = out;
|
this.out = out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DataFlow getFlow() {
|
||||||
|
return DataFlow.OUTPUT;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public InputStream openInput() throws Exception {
|
public InputStream openInput() throws Exception {
|
||||||
throw new UnsupportedOperationException("No input available");
|
throw new UnsupportedOperationException("No input available");
|
||||||
|
|
|
@ -29,15 +29,17 @@ public class PreservingWriteConnection implements DataSourceConnection {
|
||||||
if (source.getStore().canOpen()) {
|
if (source.getStore().canOpen()) {
|
||||||
try (var in = source.openReadConnection();
|
try (var in = source.openReadConnection();
|
||||||
var out = nativeSource.openWriteConnection(WriteMode.REPLACE)) {
|
var out = nativeSource.openWriteConnection(WriteMode.REPLACE)) {
|
||||||
|
in.init();
|
||||||
|
out.init();
|
||||||
in.forward(out);
|
in.forward(out);
|
||||||
}
|
}
|
||||||
;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
connection.init();
|
connection.init();
|
||||||
if (source.getStore().canOpen()) {
|
if (source.getStore().canOpen()) {
|
||||||
|
|
||||||
try (var in = nativeSource.openReadConnection()) {
|
try (var in = nativeSource.openReadConnection()) {
|
||||||
|
in.init();
|
||||||
in.forward(connection);
|
in.forward(connection);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package io.xpipe.core.process;
|
package io.xpipe.core.process;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
@ -7,7 +8,7 @@ import java.nio.charset.Charset;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public interface ProcessControl extends AutoCloseable {
|
public interface ProcessControl extends Closeable, AutoCloseable {
|
||||||
|
|
||||||
static String join(List<String> command) {
|
static String join(List<String> command) {
|
||||||
return command.stream().map(s -> s.contains(" ") ? "\"" + s + "\"" : s).collect(Collectors.joining(" "));
|
return command.stream().map(s -> s.contains(" ") ? "\"" + s + "\"" : s).collect(Collectors.joining(" "));
|
||||||
|
|
|
@ -12,6 +12,11 @@ public abstract class CollectionDataSource<DS extends DataStore> extends DataSou
|
||||||
@Singular
|
@Singular
|
||||||
private final Map<String, String> preferredProviders;
|
private final Map<String, String> preferredProviders;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DataSourceType getType() {
|
||||||
|
return DataSourceType.COLLECTION;
|
||||||
|
}
|
||||||
|
|
||||||
public CollectionDataSource<DS> annotate(String file, String provider) {
|
public CollectionDataSource<DS> annotate(String file, String provider) {
|
||||||
preferredProviders.put(file, provider);
|
preferredProviders.put(file, provider);
|
||||||
return this;
|
return this;
|
||||||
|
@ -22,18 +27,12 @@ public abstract class CollectionDataSource<DS extends DataStore> extends DataSou
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public final DataSourceInfo determineInfo() throws Exception {
|
|
||||||
try (var con = openReadConnection()) {
|
|
||||||
var c = (int) con.listEntries().count();
|
|
||||||
return new DataSourceInfo.Collection(c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public final CollectionReadConnection openReadConnection() throws Exception {
|
public final CollectionReadConnection openReadConnection() throws Exception {
|
||||||
var con = newReadConnection();
|
if (!isComplete()) {
|
||||||
con.init();
|
throw new UnsupportedOperationException();
|
||||||
return con;
|
}
|
||||||
|
|
||||||
|
return newReadConnection();
|
||||||
}
|
}
|
||||||
|
|
||||||
public final CollectionWriteConnection openWriteConnection(WriteMode mode) throws Exception {
|
public final CollectionWriteConnection openWriteConnection(WriteMode mode) throws Exception {
|
||||||
|
@ -42,7 +41,6 @@ public abstract class CollectionDataSource<DS extends DataStore> extends DataSou
|
||||||
throw new UnsupportedOperationException(mode.getId());
|
throw new UnsupportedOperationException(mode.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
con.init();
|
|
||||||
return con;
|
return con;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -123,13 +123,6 @@ public abstract class DataSource<DS extends DataStore> extends JacksonizedValue
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Determines the data source info.
|
|
||||||
* This is usually called only once on data source
|
|
||||||
* creation as this process might be expensive.
|
|
||||||
*/
|
|
||||||
public abstract DataSourceInfo determineInfo() throws Exception;
|
|
||||||
|
|
||||||
public DataSourceReadConnection openReadConnection() throws Exception {
|
public DataSourceReadConnection openReadConnection() throws Exception {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
@ -141,4 +134,6 @@ public abstract class DataSource<DS extends DataStore> extends JacksonizedValue
|
||||||
public DS getStore() {
|
public DS getStore() {
|
||||||
return store;
|
return store;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public abstract DataSourceType getType();
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,19 +9,17 @@ public abstract class RawDataSource<DS extends DataStore> extends DataSource<DS>
|
||||||
private static final int MAX_BYTES_READ = 100000;
|
private static final int MAX_BYTES_READ = 100000;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final DataSourceInfo determineInfo() throws Exception {
|
public DataSourceType getType() {
|
||||||
try (var con = openReadConnection()) {
|
return DataSourceType.RAW;
|
||||||
var b = con.readBytes(MAX_BYTES_READ);
|
|
||||||
int usedCount = b.length == MAX_BYTES_READ ? -1 : b.length;
|
|
||||||
return new DataSourceInfo.Raw(usedCount);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final RawReadConnection openReadConnection() throws Exception {
|
public final RawReadConnection openReadConnection() throws Exception {
|
||||||
var con = newReadConnection();
|
if (!isComplete()) {
|
||||||
con.init();
|
throw new UnsupportedOperationException();
|
||||||
return con;
|
}
|
||||||
|
|
||||||
|
return newReadConnection();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -31,7 +29,6 @@ public abstract class RawDataSource<DS extends DataStore> extends DataSource<DS>
|
||||||
throw new UnsupportedOperationException(mode.getId());
|
throw new UnsupportedOperationException(mode.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
con.init();
|
|
||||||
return con;
|
return con;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,11 @@ import lombok.experimental.SuperBuilder;
|
||||||
@SuperBuilder
|
@SuperBuilder
|
||||||
public abstract class StructureDataSource<DS extends DataStore> extends DataSource<DS> {
|
public abstract class StructureDataSource<DS extends DataStore> extends DataSource<DS> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DataSourceType getType() {
|
||||||
|
return DataSourceType.STRUCTURE;
|
||||||
|
}
|
||||||
|
|
||||||
private int countEntries(DataStructureNode n) {
|
private int countEntries(DataStructureNode n) {
|
||||||
if (n.isValue()) {
|
if (n.isValue()) {
|
||||||
return 1;
|
return 1;
|
||||||
|
@ -19,19 +24,12 @@ public abstract class StructureDataSource<DS extends DataStore> extends DataSour
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public final DataSourceInfo determineInfo() throws Exception {
|
|
||||||
try (var con = openReadConnection()) {
|
|
||||||
var n = con.read();
|
|
||||||
var c = countEntries(n);
|
|
||||||
return new DataSourceInfo.Structure(c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public final StructureReadConnection openReadConnection() throws Exception {
|
public final StructureReadConnection openReadConnection() throws Exception {
|
||||||
var con = newReadConnection();
|
if (!isComplete()) {
|
||||||
con.init();
|
throw new UnsupportedOperationException();
|
||||||
return con;
|
}
|
||||||
|
|
||||||
|
return newReadConnection();
|
||||||
}
|
}
|
||||||
|
|
||||||
public final StructureWriteConnection openWriteConnection(WriteMode mode) throws Exception {
|
public final StructureWriteConnection openWriteConnection(WriteMode mode) throws Exception {
|
||||||
|
@ -40,7 +38,6 @@ public abstract class StructureDataSource<DS extends DataStore> extends DataSour
|
||||||
throw new UnsupportedOperationException(mode.getId());
|
throw new UnsupportedOperationException(mode.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
con.init();
|
|
||||||
return con;
|
return con;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,10 @@ package io.xpipe.core.source;
|
||||||
import io.xpipe.core.data.type.TupleType;
|
import io.xpipe.core.data.type.TupleType;
|
||||||
import io.xpipe.core.impl.PreservingTableWriteConnection;
|
import io.xpipe.core.impl.PreservingTableWriteConnection;
|
||||||
import io.xpipe.core.store.DataStore;
|
import io.xpipe.core.store.DataStore;
|
||||||
|
import io.xpipe.core.util.ProxyProvider;
|
||||||
|
import io.xpipe.core.util.SimpleProxyFunction;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.SneakyThrows;
|
||||||
import lombok.experimental.SuperBuilder;
|
import lombok.experimental.SuperBuilder;
|
||||||
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
@ -24,34 +28,47 @@ public abstract class TableDataSource<DS extends DataStore> extends DataSource<D
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final DataSourceInfo determineInfo() throws Exception {
|
public DataSourceType getType() {
|
||||||
if (!getFlow().hasInput() || !getStore().canOpen()) {
|
return DataSourceType.TABLE;
|
||||||
return new DataSourceInfo.Table(null, -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
checkComplete();
|
|
||||||
} catch (Exception e) {
|
|
||||||
return new DataSourceInfo.Table(null, -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
try (var con = openReadConnection()) {
|
|
||||||
var dataType = con.getDataType();
|
|
||||||
var rowCount = con.getRowCount();
|
|
||||||
return new DataSourceInfo.Table(dataType, rowCount.orElse(-1));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public final TableReadConnection openReadConnection() throws Exception {
|
public final TableReadConnection openReadConnection() throws Exception {
|
||||||
try {
|
if (!isComplete()) {
|
||||||
checkComplete();
|
|
||||||
} catch (Exception e) {
|
|
||||||
return TableReadConnection.empty();
|
return TableReadConnection.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
var con = newReadConnection();
|
var proxy = ProxyProvider.get().getProxy(this);
|
||||||
con.init();
|
if (proxy != null) {
|
||||||
return con;
|
return ProxyProvider.get().createRemoteReadConnection(this, proxy);
|
||||||
|
}
|
||||||
|
|
||||||
|
return newReadConnection();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NoArgsConstructor
|
||||||
|
private static class CreateMappingFunction extends SimpleProxyFunction<TableMapping> {
|
||||||
|
|
||||||
|
private TableDataSource<?> source;
|
||||||
|
private TupleType type;
|
||||||
|
|
||||||
|
public CreateMappingFunction(TableDataSource<?> source, TupleType type) {
|
||||||
|
this.source = source;
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
private TableMapping mapping;
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
public void callLocal() {
|
||||||
|
try (TableWriteConnection w = source.openWriteConnection(WriteMode.REPLACE)) {
|
||||||
|
w.init();
|
||||||
|
mapping = w.createMapping(type).orElse(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public final Optional<TableMapping> createMapping(TupleType inputType) throws Exception {
|
||||||
|
return Optional.ofNullable(new CreateMappingFunction(this, inputType).callAndGet());
|
||||||
}
|
}
|
||||||
|
|
||||||
public final TableWriteConnection openWriteConnection(WriteMode mode) throws Exception {
|
public final TableWriteConnection openWriteConnection(WriteMode mode) throws Exception {
|
||||||
|
@ -60,7 +77,11 @@ public abstract class TableDataSource<DS extends DataStore> extends DataSource<D
|
||||||
throw new UnsupportedOperationException(mode.getId());
|
throw new UnsupportedOperationException(mode.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
con.init();
|
var proxy = ProxyProvider.get().getProxy(this);
|
||||||
|
if (proxy != null) {
|
||||||
|
return ProxyProvider.get().createRemoteWriteConnection(this, mode, proxy);
|
||||||
|
}
|
||||||
|
|
||||||
return con;
|
return con;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package io.xpipe.core.source;
|
package io.xpipe.core.source;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||||
import io.xpipe.core.data.type.TupleType;
|
import io.xpipe.core.data.type.TupleType;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@ -63,6 +64,7 @@ public class TableMapping {
|
||||||
|
|
||||||
protected final Integer[] columMap;
|
protected final Integer[] columMap;
|
||||||
|
|
||||||
|
@JsonCreator
|
||||||
public TableMapping(TupleType inputType, TupleType outputType, Integer[] columMap) {
|
public TableMapping(TupleType inputType, TupleType outputType, Integer[] columMap) {
|
||||||
this.inputType = inputType;
|
this.inputType = inputType;
|
||||||
this.outputType = outputType;
|
this.outputType = outputType;
|
||||||
|
|
|
@ -4,46 +4,36 @@ import io.xpipe.core.impl.PreservingTextWriteConnection;
|
||||||
import io.xpipe.core.store.DataStore;
|
import io.xpipe.core.store.DataStore;
|
||||||
import lombok.experimental.SuperBuilder;
|
import lombok.experimental.SuperBuilder;
|
||||||
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
|
|
||||||
@SuperBuilder
|
@SuperBuilder
|
||||||
public abstract class TextDataSource<DS extends DataStore> extends DataSource<DS> {
|
public abstract class TextDataSource<DS extends DataStore> extends DataSource<DS> {
|
||||||
|
|
||||||
private static final int MAX_LINE_READ = 1000;
|
private static final int MAX_LINE_READ = 1000;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final DataSourceInfo determineInfo() throws Exception {
|
public DataSourceType getType() {
|
||||||
if (!getStore().canOpen()) {
|
return DataSourceType.TEXT;
|
||||||
return new DataSourceInfo.Text(-1, -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
try (var con = openReadConnection()) {
|
|
||||||
AtomicInteger lineCount = new AtomicInteger();
|
|
||||||
AtomicInteger charCount = new AtomicInteger();
|
|
||||||
con.lines().limit(MAX_LINE_READ).forEach(s -> {
|
|
||||||
lineCount.getAndIncrement();
|
|
||||||
charCount.addAndGet(s.length());
|
|
||||||
});
|
|
||||||
boolean limitHit = lineCount.get() == MAX_LINE_READ;
|
|
||||||
return new DataSourceInfo.Text(limitHit ? -1 : charCount.get(), limitHit ? -1 : lineCount.get());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final TextReadConnection openReadConnection() throws Exception {
|
public final TextReadConnection openReadConnection() throws Exception {
|
||||||
var con = newReadConnection();
|
if (!isComplete()) {
|
||||||
con.init();
|
throw new UnsupportedOperationException();
|
||||||
return con;
|
}
|
||||||
|
|
||||||
|
return newReadConnection();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final TextWriteConnection openWriteConnection(WriteMode mode) throws Exception {
|
public final TextWriteConnection openWriteConnection(WriteMode mode) throws Exception {
|
||||||
var con = newWriteConnection(mode);
|
var con = newWriteConnection(mode);
|
||||||
con.init();
|
if (con == null) {
|
||||||
|
throw new UnsupportedOperationException(mode.getId());
|
||||||
|
}
|
||||||
|
|
||||||
return con;
|
return con;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected TextWriteConnection newWriteConnection(WriteMode mode) {
|
protected TextWriteConnection newWriteConnection(WriteMode mode) {
|
||||||
if (mode.equals(WriteMode.PREPEND)) {
|
if (mode.equals(WriteMode.PREPEND)) {
|
||||||
return new PreservingTextWriteConnection(this, newWriteConnection(WriteMode.REPLACE), false);
|
return new PreservingTextWriteConnection(this, newWriteConnection(WriteMode.REPLACE), false);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package io.xpipe.beacon;
|
package io.xpipe.core.util;
|
||||||
|
|
||||||
import com.fasterxml.jackson.core.JacksonException;
|
import com.fasterxml.jackson.core.JacksonException;
|
||||||
import com.fasterxml.jackson.core.JsonGenerator;
|
import com.fasterxml.jackson.core.JsonGenerator;
|
||||||
|
@ -9,8 +9,6 @@ import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
|
||||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||||
import com.fasterxml.jackson.databind.node.TextNode;
|
import com.fasterxml.jackson.databind.node.TextNode;
|
||||||
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
|
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
|
||||||
import io.xpipe.beacon.exchange.NamedFunctionExchange;
|
|
||||||
import io.xpipe.core.util.JacksonMapper;
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
|
|
||||||
|
@ -18,7 +16,7 @@ import java.io.IOException;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
public abstract class NamedFunction<T> {
|
public abstract class ProxyFunction {
|
||||||
|
|
||||||
private static ModuleLayer layer;
|
private static ModuleLayer layer;
|
||||||
|
|
||||||
|
@ -26,14 +24,14 @@ public abstract class NamedFunction<T> {
|
||||||
layer = l;
|
layer = l;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Serializer extends StdSerializer<NamedFunction> {
|
public static class Serializer extends StdSerializer<ProxyFunction> {
|
||||||
|
|
||||||
protected Serializer() {
|
protected Serializer() {
|
||||||
super(NamedFunction.class);
|
super(ProxyFunction.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void serialize(NamedFunction value, JsonGenerator gen, SerializerProvider provider) throws IOException {
|
public void serialize(ProxyFunction value, JsonGenerator gen, SerializerProvider provider) throws IOException {
|
||||||
var node = (ObjectNode) JacksonMapper.getDefault().valueToTree(value);
|
var node = (ObjectNode) JacksonMapper.getDefault().valueToTree(value);
|
||||||
node.set("module", new TextNode(value.getClass().getModule().getName()));
|
node.set("module", new TextNode(value.getClass().getModule().getName()));
|
||||||
node.set("class", new TextNode(value.getClass().getName()));
|
node.set("class", new TextNode(value.getClass().getName()));
|
||||||
|
@ -41,41 +39,36 @@ public abstract class NamedFunction<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Deserializer extends StdDeserializer<NamedFunction<?>> {
|
public static class Deserializer extends StdDeserializer<ProxyFunction> {
|
||||||
|
|
||||||
protected Deserializer() {
|
protected Deserializer() {
|
||||||
super(NamedFunction.class);
|
super(ProxyFunction.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
public NamedFunction<?> deserialize(JsonParser p, DeserializationContext ctxt)
|
public ProxyFunction deserialize(JsonParser p, DeserializationContext ctxt)
|
||||||
throws IOException, JacksonException {
|
throws IOException, JacksonException {
|
||||||
var tree = (ObjectNode) JacksonMapper.getDefault().readTree(p);
|
var tree = (ObjectNode) JacksonMapper.getDefault().readTree(p);
|
||||||
var moduleReference = tree.remove("module").asText();
|
var moduleReference = tree.remove("module").asText();
|
||||||
var classReference = tree.remove("class").asText();
|
var classReference = tree.remove("class").asText();
|
||||||
var module = layer.findModule(moduleReference).orElseThrow();
|
var module = layer.findModule(moduleReference).orElseThrow();
|
||||||
var targetClass = Class.forName(module, classReference);
|
var targetClass = Class.forName(module, classReference);
|
||||||
|
|
||||||
if (targetClass == null) {
|
if (targetClass == null) {
|
||||||
throw new IllegalArgumentException("Named function class not found: " + classReference);
|
throw new IllegalArgumentException("Named function class not found: " + classReference);
|
||||||
}
|
}
|
||||||
|
return (ProxyFunction) JacksonMapper.getDefault().treeToValue(tree, targetClass);
|
||||||
return (NamedFunction<?>) JacksonMapper.getDefault().treeToValue(tree, targetClass);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
public T call() {
|
public ProxyFunction callAndCopy() {
|
||||||
var proxyStore = Proxyable.getProxy(getProxyBase());
|
var proxyStore = ProxyProvider.get().getProxy(getProxyBase());
|
||||||
if (proxyStore != null) {
|
if (proxyStore != null) {
|
||||||
var client = BeaconClient.connectProxy(proxyStore);
|
return ProxyProvider.get().call(this, proxyStore);
|
||||||
client.sendRequest(
|
|
||||||
NamedFunctionExchange.Request.builder().function(this).build());
|
|
||||||
NamedFunctionExchange.Response response = client.receiveResponse();
|
|
||||||
return (T) response.getReturnValue();
|
|
||||||
} else {
|
} else {
|
||||||
return callLocal();
|
callLocal();
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,5 +79,5 @@ public abstract class NamedFunction<T> {
|
||||||
return first.get(this);
|
return first.get(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract T callLocal();
|
public abstract void callLocal();
|
||||||
}
|
}
|
36
core/src/main/java/io/xpipe/core/util/ProxyProvider.java
Normal file
36
core/src/main/java/io/xpipe/core/util/ProxyProvider.java
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
package io.xpipe.core.util;
|
||||||
|
|
||||||
|
import io.xpipe.core.source.DataSource;
|
||||||
|
import io.xpipe.core.source.DataSourceConnection;
|
||||||
|
import io.xpipe.core.source.DataSourceReadConnection;
|
||||||
|
import io.xpipe.core.source.WriteMode;
|
||||||
|
import io.xpipe.core.store.ShellStore;
|
||||||
|
|
||||||
|
import java.util.ServiceLoader;
|
||||||
|
|
||||||
|
public abstract class ProxyProvider {
|
||||||
|
|
||||||
|
private static ProxyProvider INSTANCE;
|
||||||
|
|
||||||
|
public static ProxyProvider get() {
|
||||||
|
if (INSTANCE == null) {
|
||||||
|
INSTANCE = ServiceLoader.load(ModuleLayer.boot(), ProxyProvider.class)
|
||||||
|
.findFirst()
|
||||||
|
.orElseThrow();
|
||||||
|
}
|
||||||
|
|
||||||
|
return INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract ShellStore getProxy(Object base);
|
||||||
|
|
||||||
|
public abstract boolean isRemote(Object base);
|
||||||
|
|
||||||
|
public abstract <T extends DataSourceReadConnection> T createRemoteReadConnection(
|
||||||
|
DataSource<?> source, ShellStore proxy) throws Exception;
|
||||||
|
|
||||||
|
public abstract <T extends DataSourceConnection> T createRemoteWriteConnection(
|
||||||
|
DataSource<?> source, WriteMode mode, ShellStore proxy) throws Exception;
|
||||||
|
|
||||||
|
public abstract ProxyFunction call(ProxyFunction func, ShellStore proxy);
|
||||||
|
}
|
8
core/src/main/java/io/xpipe/core/util/Proxyable.java
Normal file
8
core/src/main/java/io/xpipe/core/util/Proxyable.java
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
package io.xpipe.core.util;
|
||||||
|
|
||||||
|
import io.xpipe.core.store.ShellStore;
|
||||||
|
|
||||||
|
public interface Proxyable {
|
||||||
|
|
||||||
|
ShellStore getProxy();
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
package io.xpipe.core.util;
|
||||||
|
|
||||||
|
import lombok.SneakyThrows;
|
||||||
|
|
||||||
|
public abstract class SimpleProxyFunction<T> extends ProxyFunction {
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
public T getResult() {
|
||||||
|
var fields = getClass().getDeclaredFields();
|
||||||
|
var last = fields[fields.length - 1];
|
||||||
|
last.setAccessible(true);
|
||||||
|
return (T) last.get(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
public T callAndGet() {
|
||||||
|
var result = callAndCopy();
|
||||||
|
return ((SimpleProxyFunction<T>) result).getResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -25,6 +25,7 @@ open module io.xpipe.core {
|
||||||
uses com.fasterxml.jackson.databind.Module;
|
uses com.fasterxml.jackson.databind.Module;
|
||||||
uses io.xpipe.core.source.WriteMode;
|
uses io.xpipe.core.source.WriteMode;
|
||||||
uses LocalProcessControlProvider;
|
uses LocalProcessControlProvider;
|
||||||
|
uses io.xpipe.core.util.ProxyProvider;
|
||||||
|
|
||||||
provides WriteMode with WriteMode.Replace, WriteMode.Append, WriteMode.Prepend;
|
provides WriteMode with WriteMode.Replace, WriteMode.Append, WriteMode.Prepend;
|
||||||
provides com.fasterxml.jackson.databind.Module with
|
provides com.fasterxml.jackson.databind.Module with
|
||||||
|
|
|
@ -1,75 +1,23 @@
|
||||||
package io.xpipe.extension;
|
package io.xpipe.extension;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
|
||||||
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
|
|
||||||
import io.xpipe.api.connector.XPipeApiConnection;
|
|
||||||
import io.xpipe.beacon.exchange.ProxyReadConnectionExchange;
|
|
||||||
import io.xpipe.core.impl.FileNames;
|
import io.xpipe.core.impl.FileNames;
|
||||||
import io.xpipe.core.impl.InputStreamStore;
|
|
||||||
import io.xpipe.core.process.ShellProcessControl;
|
import io.xpipe.core.process.ShellProcessControl;
|
||||||
import io.xpipe.core.source.DataSource;
|
|
||||||
import io.xpipe.core.source.DataSourceReadConnection;
|
|
||||||
import io.xpipe.core.store.ShellStore;
|
import io.xpipe.core.store.ShellStore;
|
||||||
import io.xpipe.core.util.JacksonMapper;
|
|
||||||
import io.xpipe.core.util.XPipeInstallation;
|
import io.xpipe.core.util.XPipeInstallation;
|
||||||
import io.xpipe.extension.util.XPipeDaemon;
|
import io.xpipe.extension.util.XPipeDaemon;
|
||||||
import lombok.SneakyThrows;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.function.Function;
|
|
||||||
|
|
||||||
public class XPipeProxy {
|
public class XPipeProxy {
|
||||||
|
|
||||||
@SneakyThrows
|
|
||||||
private static DataSource<?> downstreamTransform(DataSource<?> input, ShellStore proxy) {
|
|
||||||
var proxyNode = JacksonMapper.newMapper().valueToTree(proxy);
|
|
||||||
var inputNode = JacksonMapper.newMapper().valueToTree(input);
|
|
||||||
var localNode = JacksonMapper.newMapper().valueToTree(ShellStore.local());
|
|
||||||
replace(inputNode, node -> node.equals(proxyNode) ? Optional.of(localNode) : Optional.empty());
|
|
||||||
return JacksonMapper.newMapper().treeToValue(inputNode, DataSource.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static JsonNode replace(JsonNode node, Function<JsonNode, Optional<JsonNode>> function) {
|
|
||||||
var value = function.apply(node);
|
|
||||||
if (value.isPresent()) {
|
|
||||||
return value.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!node.isObject()) {
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
|
|
||||||
var replacement = JsonNodeFactory.instance.objectNode();
|
|
||||||
var iterator = node.fields();
|
|
||||||
while (iterator.hasNext()) {
|
|
||||||
var stringJsonNodeEntry = iterator.next();
|
|
||||||
var resolved = function.apply(stringJsonNodeEntry.getValue()).orElse(stringJsonNodeEntry.getValue());
|
|
||||||
replacement.set(stringJsonNodeEntry.getKey(), resolved);
|
|
||||||
}
|
|
||||||
return replacement;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <T extends DataSourceReadConnection> T remoteReadConnection(DataSource<?> source, ShellStore proxy) {
|
|
||||||
var downstream = downstreamTransform(source, proxy);
|
|
||||||
return (T) XPipeApiConnection.execute(con -> {
|
|
||||||
con.sendRequest(ProxyReadConnectionExchange.Request.builder()
|
|
||||||
.source(downstream)
|
|
||||||
.build());
|
|
||||||
con.receiveResponse();
|
|
||||||
var inputSource = DataSource.createInternalDataSource(
|
|
||||||
source.determineInfo().getType(), new InputStreamStore(con.receiveBody()));
|
|
||||||
return inputSource.openReadConnection();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void checkSupport(ShellStore store) throws Exception {
|
public static void checkSupport(ShellStore store) throws Exception {
|
||||||
var version = XPipeDaemon.getInstance().getVersion();
|
var version = XPipeDaemon.getInstance().getVersion();
|
||||||
try (ShellProcessControl s = store.create().start()) {
|
try (ShellProcessControl s = store.create().start()) {
|
||||||
var defaultInstallationExecutable = FileNames.join(
|
var defaultInstallationExecutable = FileNames.join(
|
||||||
XPipeInstallation.getDefaultInstallationBasePath(s),
|
XPipeInstallation.getDefaultInstallationBasePath(s),
|
||||||
XPipeInstallation.getDaemonExecutablePath(s.getOsType()));
|
XPipeInstallation.getDaemonExecutablePath(s.getOsType()));
|
||||||
if (!s.executeBooleanSimpleCommand(s.getShellType().createFileExistsCommand(defaultInstallationExecutable))) {
|
if (!s.executeBooleanSimpleCommand(
|
||||||
|
s.getShellType().createFileExistsCommand(defaultInstallationExecutable))) {
|
||||||
throw new IOException(I18n.get("noInstallationFound"));
|
throw new IOException(I18n.get("noInstallationFound"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package io.xpipe.extension;
|
package io.xpipe.extension;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.jsontype.NamedType;
|
import com.fasterxml.jackson.databind.jsontype.NamedType;
|
||||||
import io.xpipe.beacon.NamedFunction;
|
import io.xpipe.core.util.ProxyFunction;
|
||||||
import io.xpipe.core.impl.LocalProcessControlProvider;
|
import io.xpipe.core.impl.LocalProcessControlProvider;
|
||||||
import io.xpipe.core.util.JacksonMapper;
|
import io.xpipe.core.util.JacksonMapper;
|
||||||
import io.xpipe.extension.event.TrackEvent;
|
import io.xpipe.extension.event.TrackEvent;
|
||||||
|
@ -36,7 +36,7 @@ public class XPipeServiceProviders {
|
||||||
|
|
||||||
SupportedApplicationProviders.loadAll(layer);
|
SupportedApplicationProviders.loadAll(layer);
|
||||||
PrefsProviders.init(layer);
|
PrefsProviders.init(layer);
|
||||||
NamedFunction.init(layer);
|
ProxyFunction.init(layer);
|
||||||
TrackEvent.info("Finished loading extension providers");
|
TrackEvent.info("Finished loading extension providers");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,6 +37,10 @@ public class TrackEvent {
|
||||||
return builder().type("info").message(message);
|
return builder().type("info").message(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static TrackEventBuilder withInfo(String category, String message) {
|
||||||
|
return builder().category(category).type("info").message(message);
|
||||||
|
}
|
||||||
|
|
||||||
public static TrackEventBuilder withWarn(String category, String message) {
|
public static TrackEventBuilder withWarn(String category, String message) {
|
||||||
return builder().category(category).type("warn").message(message);
|
return builder().category(category).type("warn").message(message);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import io.xpipe.beacon.NamedFunction;
|
import io.xpipe.core.util.ProxyFunction;
|
||||||
import io.xpipe.extension.DataSourceProvider;
|
import io.xpipe.extension.DataSourceProvider;
|
||||||
import io.xpipe.extension.DataStoreActionProvider;
|
import io.xpipe.extension.DataStoreActionProvider;
|
||||||
import io.xpipe.extension.SupportedApplicationProvider;
|
import io.xpipe.extension.SupportedApplicationProvider;
|
||||||
|
@ -41,5 +41,5 @@ open module io.xpipe.extension {
|
||||||
uses XPipeDaemon;
|
uses XPipeDaemon;
|
||||||
uses io.xpipe.extension.Cache;
|
uses io.xpipe.extension.Cache;
|
||||||
uses io.xpipe.extension.DataSourceActionProvider;
|
uses io.xpipe.extension.DataSourceActionProvider;
|
||||||
uses NamedFunction;
|
uses ProxyFunction;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue