Refactor exchanges, add dialog APIs, add store providers

This commit is contained in:
Christopher Schnick 2022-06-12 02:40:28 +02:00
parent f4f9c1d978
commit 696568d5bc
50 changed files with 791 additions and 206 deletions

View file

@ -13,7 +13,7 @@ import lombok.extern.jackson.Jacksonized;
/**
* Performs an edit for a data source.
*/
public class EditExecuteExchange implements MessageExchange<EditExecuteExchange.Request, EditExecuteExchange.Response> {
public class EditExecuteExchange implements MessageExchange {
@Override
public String getId() {

View file

@ -12,23 +12,13 @@ import lombok.extern.jackson.Jacksonized;
/**
* Requests to edit a data source.
*/
public class EditPreparationExchange implements MessageExchange<EditPreparationExchange.Request, EditPreparationExchange.Response> {
public class EditPreparationExchange implements MessageExchange {
@Override
public String getId() {
return "editPreparation";
}
@Override
public Class<EditPreparationExchange.Request> getRequestClass() {
return EditPreparationExchange.Request.class;
}
@Override
public Class<EditPreparationExchange.Response> getResponseClass() {
return EditPreparationExchange.Response.class;
}
@Jacksonized
@Builder
@Value

View file

@ -1,13 +1,11 @@
package io.xpipe.beacon.exchange;
import io.xpipe.beacon.message.RequestMessage;
import io.xpipe.beacon.message.ResponseMessage;
import lombok.SneakyThrows;
/**
* A message exchange scheme that implements a certain functionality.
*/
public interface MessageExchange<RQ extends RequestMessage, RP extends ResponseMessage> {
public interface MessageExchange {
/**
* The unique id of this exchange that will be included in the messages.
@ -19,10 +17,10 @@ public interface MessageExchange<RQ extends RequestMessage, RP extends ResponseM
*/
@SneakyThrows
@SuppressWarnings("unchecked")
default Class<RQ> getRequestClass() {
default Class<?> getRequestClass() {
var c = getClass().getSuperclass();
var name = (MessageExchange.class.isAssignableFrom(c) ? c : getClass()).getName() + "$Request";
return (Class<RQ>) Class.forName(name);
return Class.forName(name);
}
/**
@ -30,9 +28,9 @@ public interface MessageExchange<RQ extends RequestMessage, RP extends ResponseM
*/
@SneakyThrows
@SuppressWarnings("unchecked")
default Class<RP> getResponseClass() {
default Class<?> getResponseClass() {
var c = getClass().getSuperclass();
var name = (MessageExchange.class.isAssignableFrom(c) ? c : getClass()).getName() + "$Response";
return (Class<RP>) Class.forName(name);
return Class.forName(name);
}
}

View file

@ -10,37 +10,34 @@ import java.util.stream.Collectors;
public class MessageExchanges {
private static Set<MessageExchange<?,?>> ALL;
private static Set<MessageExchange> ALL;
private static void loadAll() {
if (ALL == null) {
ALL = ServiceLoader.load(MessageExchange.class).stream()
.map(s -> (MessageExchange<?,?>) s.get()).collect(Collectors.toSet());
.map(s -> (MessageExchange) s.get()).collect(Collectors.toSet());
}
}
@SuppressWarnings("unchecked")
public static <RQ extends RequestMessage, RP extends ResponseMessage> Optional<MessageExchange<RQ, RP>> byId(String name) {
public static <RQ extends RequestMessage, RP extends ResponseMessage> Optional<MessageExchange> byId(String name) {
loadAll();
var r = ALL.stream().filter(d -> d.getId().equals(name)).findAny();
return Optional.ofNullable((MessageExchange<RQ, RP>) r.orElse(null));
return Optional.ofNullable((MessageExchange) r.orElse(null));
}
@SuppressWarnings("unchecked")
public static <RQ extends RequestMessage, RP extends ResponseMessage> Optional<MessageExchange<RQ, RP>> byRequest(RQ req) {
public static <RQ extends RequestMessage, RP extends ResponseMessage> Optional<MessageExchange> byRequest(RQ req) {
loadAll();
var r = ALL.stream().filter(d -> d.getRequestClass().equals(req.getClass())).findAny();
return Optional.ofNullable((MessageExchange<RQ, RP>) r.orElse(null));
return r;
}
@SuppressWarnings("unchecked")
public static <RQ extends RequestMessage, RP extends ResponseMessage> Optional<MessageExchange<RQ, RP>> byResponse(RP rep) {
public static <RQ extends RequestMessage, RP extends ResponseMessage> Optional<MessageExchange> byResponse(RP rep) {
loadAll();
var r = ALL.stream().filter(d -> d.getResponseClass().equals(rep.getClass())).findAny();
return Optional.ofNullable((MessageExchange<RQ, RP>) r.orElse(null));
return r;
}
public static Set<MessageExchange<?,?>> getAll() {
public static Set<MessageExchange> getAll() {
loadAll();
return ALL;
}

View file

@ -12,23 +12,13 @@ import lombok.extern.jackson.Jacksonized;
/**
* Queries general information about a data source.
*/
public class QueryDataSourceExchange implements MessageExchange<QueryDataSourceExchange.Request, QueryDataSourceExchange.Response> {
public class QueryDataSourceExchange implements MessageExchange {
@Override
public String getId() {
return "queryDataSource";
}
@Override
public Class<QueryDataSourceExchange.Request> getRequestClass() {
return QueryDataSourceExchange.Request.class;
}
@Override
public Class<QueryDataSourceExchange.Response> getResponseClass() {
return QueryDataSourceExchange.Response.class;
}
@Jacksonized
@Builder
@Value

View file

@ -13,23 +13,13 @@ import lombok.extern.jackson.Jacksonized;
/**
* Sends stream-based data to a daemon.
*/
public class ReadExecuteExchange implements MessageExchange<ReadExecuteExchange.Request, ReadExecuteExchange.Response> {
public class ReadExecuteExchange implements MessageExchange {
@Override
public String getId() {
return "readExecute";
}
@Override
public Class<ReadExecuteExchange.Request> getRequestClass() {
return ReadExecuteExchange.Request.class;
}
@Override
public Class<ReadExecuteExchange.Response> getResponseClass() {
return ReadExecuteExchange.Response.class;
}
@Jacksonized
@Builder
@Value

View file

@ -3,7 +3,7 @@ package io.xpipe.beacon.exchange;
import io.xpipe.beacon.message.RequestMessage;
import io.xpipe.beacon.message.ResponseMessage;
import io.xpipe.core.source.DataSourceConfigInstance;
import io.xpipe.core.store.StreamDataStore;
import io.xpipe.core.store.DataStore;
import lombok.Builder;
import lombok.NonNull;
import lombok.Value;
@ -12,23 +12,13 @@ import lombok.extern.jackson.Jacksonized;
/**
* Prepares a client to send stream-based data to a daemon.
*/
public class ReadPreparationExchange implements MessageExchange<ReadPreparationExchange.Request, ReadPreparationExchange.Response> {
public class ReadPreparationExchange implements MessageExchange {
@Override
public String getId() {
return "readPreparation";
}
@Override
public Class<ReadPreparationExchange.Request> getRequestClass() {
return ReadPreparationExchange.Request.class;
}
@Override
public Class<ReadPreparationExchange.Response> getResponseClass() {
return ReadPreparationExchange.Response.class;
}
@Jacksonized
@Builder
@Value
@ -36,7 +26,7 @@ public class ReadPreparationExchange implements MessageExchange<ReadPreparationE
String provider;
@NonNull
StreamDataStore store;
DataStore store;
boolean configureAll;
}

View file

@ -7,7 +7,7 @@ import lombok.NonNull;
import lombok.Value;
import lombok.extern.jackson.Jacksonized;
public class RemoveCollectionExchange implements MessageExchange<RemoveCollectionExchange.Request, RemoveCollectionExchange.Response> {
public class RemoveCollectionExchange implements MessageExchange {
@Override
public String getId() {

View file

@ -9,7 +9,7 @@ import lombok.NonNull;
import lombok.Value;
import lombok.extern.jackson.Jacksonized;
public class RemoveEntryExchange implements MessageExchange<RemoveEntryExchange.Request, RemoveEntryExchange.Response> {
public class RemoveEntryExchange implements MessageExchange {
@Override
public String getId() {

View file

@ -7,7 +7,7 @@ import lombok.NonNull;
import lombok.Value;
import lombok.extern.jackson.Jacksonized;
public class RenameCollectionExchange implements MessageExchange<RenameCollectionExchange.Request, RenameCollectionExchange.Response> {
public class RenameCollectionExchange implements MessageExchange {
@Override
public String getId() {

View file

@ -9,7 +9,7 @@ import lombok.NonNull;
import lombok.Value;
import lombok.extern.jackson.Jacksonized;
public class RenameEntryExchange implements MessageExchange<RenameEntryExchange.Request, RenameEntryExchange.Response> {
public class RenameEntryExchange implements MessageExchange {
@Override
public String getId() {

View file

@ -9,23 +9,13 @@ import lombok.extern.jackson.Jacksonized;
/**
* Requests the daemon to stop.
*/
public class StopExchange implements MessageExchange<StopExchange.Request, StopExchange.Response> {
public class StopExchange implements MessageExchange {
@Override
public String getId() {
return "stop";
}
@Override
public Class<StopExchange.Request> getRequestClass() {
return StopExchange.Request.class;
}
@Override
public Class<StopExchange.Response> getResponseClass() {
return StopExchange.Response.class;
}
@Jacksonized
@Builder
@Value

View file

@ -10,23 +10,13 @@ import lombok.extern.jackson.Jacksonized;
/**
* Stores a stream of data in a storage.
*/
public class StoreStreamExchange implements MessageExchange<StoreStreamExchange.Request, StoreStreamExchange.Response> {
public class StoreStreamExchange implements MessageExchange {
@Override
public String getId() {
return "storeStream";
}
@Override
public Class<StoreStreamExchange.Request> getRequestClass() {
return StoreStreamExchange.Request.class;
}
@Override
public Class<StoreStreamExchange.Response> getResponseClass() {
return StoreStreamExchange.Response.class;
}
@Jacksonized
@Builder
@Value

View file

@ -9,7 +9,7 @@ import lombok.NonNull;
import lombok.Value;
import lombok.extern.jackson.Jacksonized;
public class QueryRawDataExchange implements MessageExchange<QueryRawDataExchange.Request, QueryRawDataExchange.Response> {
public class QueryRawDataExchange implements MessageExchange {
@Override
public String getId() {

View file

@ -12,23 +12,13 @@ import lombok.extern.jackson.Jacksonized;
/**
* Queries data of a table data source in the xpbt format.
*/
public class QueryTableDataExchange implements MessageExchange<QueryTableDataExchange.Request, QueryTableDataExchange.Response> {
public class QueryTableDataExchange implements MessageExchange {
@Override
public String getId() {
return "queryTableData";
}
@Override
public Class<QueryTableDataExchange.Request> getRequestClass() {
return QueryTableDataExchange.Request.class;
}
@Override
public Class<QueryTableDataExchange.Response> getResponseClass() {
return QueryTableDataExchange.Response.class;
}
@Jacksonized
@Builder
@Value

View file

@ -9,7 +9,7 @@ import lombok.NonNull;
import lombok.Value;
import lombok.extern.jackson.Jacksonized;
public class QueryTextDataExchange implements MessageExchange<QueryTextDataExchange.Request, QueryTextDataExchange.Response> {
public class QueryTextDataExchange implements MessageExchange {
@Override
public String getId() {

View file

@ -12,7 +12,7 @@ import lombok.NonNull;
import lombok.Value;
import lombok.extern.jackson.Jacksonized;
public class ConvertExchange implements MessageExchange<ConvertExchange.Request, ConvertExchange.Response> {
public class ConvertExchange implements MessageExchange {
@Override
public String getId() {

View file

@ -3,12 +3,14 @@ package io.xpipe.beacon.exchange.cli;
import io.xpipe.beacon.exchange.MessageExchange;
import io.xpipe.beacon.message.RequestMessage;
import io.xpipe.beacon.message.ResponseMessage;
import io.xpipe.core.source.DataSourceConfigInstance;
import io.xpipe.core.config.DialogElement;
import lombok.Builder;
import lombok.Value;
import lombok.extern.jackson.Jacksonized;
public class DialogExchange implements MessageExchange<DialogExchange.Request, DialogExchange.Response> {
import java.util.UUID;
public class DialogExchange implements MessageExchange {
@Override
public String getId() {
@ -29,8 +31,7 @@ public class DialogExchange implements MessageExchange<DialogExchange.Request, D
@Builder
@Value
public static class Request implements RequestMessage {
DataSourceConfigInstance instance;
String key;
UUID dialogKey;
String value;
}
@ -38,6 +39,7 @@ public class DialogExchange implements MessageExchange<DialogExchange.Request, D
@Builder
@Value
public static class Response implements ResponseMessage {
DialogElement element;
String errorMsg;
}
}

View file

@ -10,23 +10,13 @@ import lombok.extern.jackson.Jacksonized;
import java.util.List;
public class ListCollectionsExchange implements MessageExchange<ListCollectionsExchange.Request, ListCollectionsExchange.Response> {
public class ListCollectionsExchange implements MessageExchange {
@Override
public String getId() {
return "listCollections";
}
@Override
public Class<Request> getRequestClass() {
return Request.class;
}
@Override
public Class<Response> getResponseClass() {
return Response.class;
}
@Jacksonized
@Builder
@Value

View file

@ -10,23 +10,13 @@ import lombok.extern.jackson.Jacksonized;
import java.util.List;
public class ListEntriesExchange implements MessageExchange<ListEntriesExchange.Request, ListEntriesExchange.Response> {
public class ListEntriesExchange implements MessageExchange {
@Override
public String getId() {
return "listEntries";
}
@Override
public Class<Request> getRequestClass() {
return Request.class;
}
@Override
public Class<Response> getResponseClass() {
return Response.class;
}
@Jacksonized
@Builder
@Value

View file

@ -8,23 +8,13 @@ import lombok.NonNull;
import lombok.Value;
import lombok.extern.jackson.Jacksonized;
public class ModeExchange implements MessageExchange<ModeExchange.Request, ModeExchange.Response> {
public class ModeExchange implements MessageExchange {
@Override
public String getId() {
return "mode";
}
@Override
public Class<ModeExchange.Request> getRequestClass() {
return ModeExchange.Request.class;
}
@Override
public Class<ModeExchange.Response> getResponseClass() {
return ModeExchange.Response.class;
}
@Jacksonized
@Builder
@Value

View file

@ -13,7 +13,7 @@ import lombok.extern.jackson.Jacksonized;
import java.util.List;
import java.util.Map;
public class ProviderListExchange implements MessageExchange<ProviderListExchange.Request, ProviderListExchange.Response> {
public class ProviderListExchange implements MessageExchange {
@Override
public String getId() {

View file

@ -9,23 +9,13 @@ import lombok.NonNull;
import lombok.Value;
import lombok.extern.jackson.Jacksonized;
public class SelectExchange implements MessageExchange<SelectExchange.Request, SelectExchange.Response> {
public class SelectExchange implements MessageExchange {
@Override
public String getId() {
return "select";
}
@Override
public Class<SelectExchange.Request> getRequestClass() {
return SelectExchange.Request.class;
}
@Override
public Class<SelectExchange.Response> getResponseClass() {
return SelectExchange.Response.class;
}
@Jacksonized
@Builder
@Value

View file

@ -7,23 +7,13 @@ import lombok.Builder;
import lombok.Value;
import lombok.extern.jackson.Jacksonized;
public class StatusExchange implements MessageExchange<StatusExchange.Request, StatusExchange.Response> {
public class StatusExchange implements MessageExchange {
@Override
public String getId() {
return "status";
}
@Override
public Class<Request> getRequestClass() {
return Request.class;
}
@Override
public Class<Response> getResponseClass() {
return Response.class;
}
@Jacksonized
@Builder
@Value

View file

@ -0,0 +1,36 @@
package io.xpipe.beacon.exchange.cli;
import io.xpipe.beacon.exchange.MessageExchange;
import io.xpipe.beacon.message.RequestMessage;
import io.xpipe.beacon.message.ResponseMessage;
import io.xpipe.core.config.DialogElement;
import lombok.Builder;
import lombok.NonNull;
import lombok.Value;
import lombok.extern.jackson.Jacksonized;
import java.util.UUID;
public class StoreAddExchange implements MessageExchange {
@Override
public String getId() {
return "storeAdd";
}
@Jacksonized
@Builder
@Value
public static class Request implements RequestMessage {
@NonNull
String input;
}
@Jacksonized
@Builder
@Value
public static class Response implements ResponseMessage {
UUID dialogKey;
DialogElement dialogElement;
}
}

View file

@ -7,23 +7,13 @@ import lombok.Builder;
import lombok.Value;
import lombok.extern.jackson.Jacksonized;
public class VersionExchange implements MessageExchange<VersionExchange.Request, VersionExchange.Response> {
public class VersionExchange implements MessageExchange {
@Override
public String getId() {
return "version";
}
@Override
public Class<VersionExchange.Request> getRequestClass() {
return VersionExchange.Request.class;
}
@Override
public Class<VersionExchange.Response> getResponseClass() {
return VersionExchange.Response.class;
}
@lombok.extern.jackson.Jacksonized
@lombok.Builder
@lombok.Value

View file

@ -13,7 +13,7 @@ import lombok.extern.jackson.Jacksonized;
/**
* Output the data source contents.
*/
public class WriteExecuteExchange implements MessageExchange<WriteExecuteExchange.Request, WriteExecuteExchange.Response> {
public class WriteExecuteExchange implements MessageExchange {
@Override
public String getId() {

View file

@ -14,7 +14,7 @@ import lombok.extern.jackson.Jacksonized;
/**
* Prepares a client to output the data source contents.
*/
public class WritePreparationExchange implements MessageExchange<WritePreparationExchange.Request, WritePreparationExchange.Response> {
public class WritePreparationExchange implements MessageExchange {
@Override
public String getId() {

View file

@ -29,6 +29,7 @@ module io.xpipe.beacon {
ModeExchange,
StatusExchange,
StopExchange,
StoreAddExchange,
WritePreparationExchange,
WriteExecuteExchange,
SelectExchange,

View file

@ -0,0 +1,21 @@
package io.xpipe.core.config;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonTypeName;
import lombok.Getter;
@JsonTypeName("query")
@Getter
public class BaseQueryElement extends DialogElement {
private final String description;
private final boolean required;
protected String value;
@JsonCreator
public BaseQueryElement(String description, boolean required, String value) {
this.description = description;
this.required = required;
this.value = value;
}
}

View file

@ -0,0 +1,67 @@
package io.xpipe.core.config;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonTypeName;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Value;
import lombok.extern.jackson.Jacksonized;
import java.util.List;
@JsonTypeName("choice")
public class ChoiceElement extends DialogElement {
private final List<Element> elements;
private int selected;
@Override
public boolean apply(String value) {
if (value == null) {
return true;
}
if (value.length() != 1) {
return true;
}
var c = value.charAt(0);
if (Character.isDigit(c)) {
selected = Integer.parseInt(value) - 1;
return true;
}
for (int i = 0; i < elements.size(); i++) {
if (elements.get(i).getCharacter() != null && elements.get(i).getCharacter().equals(c)) {
selected = i;
return true;
}
}
return false;
}
@Value
@Builder
@Jacksonized
@AllArgsConstructor
public static class Element {
Character character;
String description;
}
@JsonCreator
public ChoiceElement(List<Element> elements, int selected) {
this.elements = elements;
this.selected = selected;
}
public List<Element> getElements() {
return elements;
}
public int getSelected() {
return selected;
}
}

View file

@ -17,10 +17,10 @@ public class ConfigParameter {
}
@JsonIgnore
ConfigConverter<?> converter;
QueryConverter<?> converter;
@SuppressWarnings("unchecked")
public <T> ConfigConverter<T> getConverter() {
return (ConfigConverter<T>) converter;
public <T> QueryConverter<T> getConverter() {
return (QueryConverter<T>) converter;
}
}

View file

@ -0,0 +1,176 @@
package io.xpipe.core.config;
import java.util.function.Function;
import java.util.function.Supplier;
public abstract class Dialog {
private static class Sequence extends Dialog {
private int index = 0;
private final DialogElement[] es;
public Sequence(DialogElement... es) {
this.es = es;
}
@Override
public DialogElement start() {
index = 0;
return es[0];
}
@Override
public DialogElement receive(String answer) {
if (es[index].apply(answer)) {
if (index == es.length - 1) {
complete();
return null;
} else {
return es[++index];
}
}
return es[index];
}
}
public static Dialog chain(DialogElement... es) {
return new Dialog.Sequence(es);
}
public static Dialog chain(Dialog... ds) {
return new Dialog() {
private int current = 0;
@Override
public DialogElement start() {
current = 0;
return ds[0].start();
}
@Override
public DialogElement receive(String answer) {
DialogElement currentElement = ds[current].receive(answer);
if (currentElement == null) {
ds[current].complete();
if (current == ds.length - 1) {
complete();
return null;
} else {
return ds[++current].start();
}
}
return currentElement;
}
};
}
public static Dialog of(DialogElement e) {
return new Dialog() {
@Override
public DialogElement start() {
return e;
}
@Override
public DialogElement receive(String answer) {
if (e.apply(answer)) {
complete();
return null;
}
return e;
}
};
}
public static Dialog retryIf(Dialog d, Supplier<String> msg) {
return new Dialog() {
private boolean retry;
@Override
public DialogElement start() {
return d.start();
}
@Override
public DialogElement receive(String answer) {
if (retry) {
retry = false;
return d.start();
}
var next = d.receive(answer);
if (next == null) {
var s = msg.get();
if (s != null) {
retry = true;
return new HeaderElement(s);
}
}
return next;
}
}.evaluateTo(d.onCompletion);
}
public static Dialog choice(ChoiceElement choice, Function<Integer, Dialog> c) {
return new Dialog() {
private Dialog choiceMade;
@Override
public DialogElement start() {
choiceMade = null;
return choice;
}
@Override
public DialogElement receive(String answer) {
if (choiceMade != null) {
var r = choiceMade.receive(answer);
if (r == null) {
complete();
}
return r;
}
if (choice.apply(answer)) {
choiceMade = c.apply(choice.getSelected());
return choiceMade.start();
}
return choice;
}
};
}
private Object eval;
private Supplier<?> onCompletion;
public abstract DialogElement start();
public Dialog evaluateTo(Supplier<?> s) {
onCompletion = s;
return this;
}
@SuppressWarnings("unchecked")
public <T> T getResult() {
return (T) eval;
}
public void complete() {
if (onCompletion != null) {
eval = onCompletion.get();
}
}
public abstract DialogElement receive(String answer);
}

View file

@ -0,0 +1,25 @@
package io.xpipe.core.config;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import lombok.EqualsAndHashCode;
import java.util.UUID;
@EqualsAndHashCode
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
public abstract class DialogElement {
protected String id;
public DialogElement() {
this.id = UUID.randomUUID().toString();
}
public boolean apply(String value) {
throw new UnsupportedOperationException();
}
public String getId() {
return id;
}
}

View file

@ -0,0 +1,24 @@
package io.xpipe.core.config;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonTypeName;
@JsonTypeName("header")
public class HeaderElement extends DialogElement {
protected String header;
@JsonCreator
public HeaderElement(String header) {
this.header = header;
}
@Override
public boolean apply(String value) {
return true;
}
public String getHeader() {
return header;
}
}

View file

@ -2,9 +2,9 @@ package io.xpipe.core.config;
import java.nio.charset.Charset;
public abstract class ConfigConverter<T> {
public abstract class QueryConverter<T> {
public static final ConfigConverter<Charset> CHARSET = new ConfigConverter<Charset>() {
public static final QueryConverter<Charset> CHARSET = new QueryConverter<Charset>() {
@Override
protected Charset fromString(String s) {
return Charset.forName(s);
@ -16,7 +16,7 @@ public abstract class ConfigConverter<T> {
}
};
public static final ConfigConverter<String> STRING = new ConfigConverter<String>() {
public static final QueryConverter<String> STRING = new QueryConverter<String>() {
@Override
protected String fromString(String s) {
return s;
@ -28,7 +28,19 @@ public abstract class ConfigConverter<T> {
}
};
public static final ConfigConverter<Character> CHARACTER = new ConfigConverter<Character>() {
public static final QueryConverter<Integer> INTEGER = new QueryConverter<Integer>() {
@Override
protected Integer fromString(String s) {
return Integer.parseInt(s);
}
@Override
protected String toString(Integer value) {
return value.toString();
}
};
public static final QueryConverter<Character> CHARACTER = new QueryConverter<Character>() {
@Override
protected Character fromString(String s) {
if (s.length() != 1) {
@ -44,7 +56,7 @@ public abstract class ConfigConverter<T> {
}
};
public static final ConfigConverter<Boolean> BOOLEAN = new ConfigConverter<Boolean>() {
public static final QueryConverter<Boolean> BOOLEAN = new QueryConverter<Boolean>() {
@Override
protected Boolean fromString(String s) {
if (s.equalsIgnoreCase("y") || s.equalsIgnoreCase("yes") || s.equalsIgnoreCase("true")) {

View file

@ -0,0 +1,47 @@
package io.xpipe.core.config;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
@JsonSerialize(as = BaseQueryElement.class)
public class QueryElement extends BaseQueryElement {
@JsonIgnore
private final QueryConverter<?> converter;
public QueryElement(String description, boolean required, String value, QueryConverter<?> converter) {
super(description, required, value);
this.converter = converter;
}
public QueryElement(String description, boolean required, Object value, QueryConverter<?> converter) {
super(description, required, value != null ? value.toString() : null);
this.converter = converter;
}
@Override
public boolean apply(String value) {
if (value == null && this.value != null) {
if (isRequired() && this.value == null) {
return false;
}
this.value = null;
return true;
}
try {
converter.convertFromString(value);
} catch (Exception ex) {
return false;
}
this.value = value;
return true;
}
@SuppressWarnings("unchecked")
public <T> T getConvertedValue() {
return (T) converter.convertFromString(value);
}
}

View file

@ -38,8 +38,12 @@ public abstract class DataSource<DS extends DataStore> {
return c;
}
public boolean isComplete() {
return true;
protected boolean supportsRead() {
return false;
}
protected boolean supportsWrite() {
return false;
}
/**
@ -65,9 +69,13 @@ public abstract class DataSource<DS extends DataStore> {
*/
public abstract DataSourceInfo determineInfo() throws Exception;
public abstract DataSourceReadConnection openReadConnection() throws Exception;
public DataSourceReadConnection openReadConnection() throws Exception {
throw new UnsupportedOperationException();
}
public abstract DataSourceConnection openWriteConnection() throws Exception;
public DataSourceConnection openWriteConnection() throws Exception {
throw new UnsupportedOperationException();
}
public DataSourceConnection openAppendingWriteConnection() throws Exception {
throw new UnsupportedOperationException("Appending write is not supported");

View file

@ -0,0 +1,94 @@
package io.xpipe.core.source;
import io.xpipe.core.data.node.*;
import io.xpipe.core.data.type.TupleType;
import io.xpipe.core.data.type.ValueType;
import io.xpipe.core.store.JdbcStore;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Collections;
@Data
@EqualsAndHashCode(callSuper = true)
public abstract class JdbcQuerySource extends TableDataSource<JdbcStore> {
JdbcStore store;
protected abstract String createQuery();
public Connection createConnection() throws SQLException {
return store.createConnection();
}
@Override
protected boolean supportsRead() {
return true;
}
@Override
protected TableReadConnection newReadConnection() {
return new TableReadConnection() {
private Connection connection;
private Statement statement;
private TupleType dataType;
private ResultSet resultSet;
@Override
public void init() throws Exception {
connection = createConnection();
statement = connection.createStatement();
resultSet = statement.executeQuery(createQuery());
var meta = resultSet.getMetaData();
var names = new ArrayList<String>();
for (int i = 0; i < meta.getColumnCount(); i++) {
names.add(meta.getColumnName(i + 1));
}
dataType = TupleType.of(names, Collections.nCopies(names.size(), ValueType.of()));
}
@Override
public void close() throws Exception {
statement.close();
connection.close();
}
@Override
public TupleType getDataType() {
return dataType;
}
@Override
public int getRowCount() throws Exception {
return resultSet.getFetchSize();
}
@Override
public void withRows(DataStructureNodeAcceptor<TupleNode> lineAcceptor) throws Exception {
while (resultSet.next()) {
var vals = new ArrayList<DataStructureNode>();
for (int i = 0; i < dataType.getSize(); i++) {
vals.add(ValueNode.of(resultSet.getString(i)));
}
var node = TupleNode.of(dataType.getNames(), vals);
if (!lineAcceptor.accept(node)) {
break;
}
}
}
@Override
public ArrayNode readRows(int maxLines) throws Exception {
return null;
}
};
}
}

View file

@ -18,7 +18,7 @@ public interface TableReadConnection extends DataSourceReadConnection {
public static TableReadConnection empty() {
return new TableReadConnection() {
@Override
public TupleType getDataType() throws Exception {
public TupleType getDataType() {
return TupleType.empty();
}
@ -42,7 +42,7 @@ public interface TableReadConnection extends DataSourceReadConnection {
/**
* Returns the data type of the table data.
*/
TupleType getDataType() throws Exception;
TupleType getDataType();
/**
* Returns the amount of rows to be read or -1 if the amount is unknown.

View file

@ -0,0 +1,19 @@
package io.xpipe.core.store;
import com.fasterxml.jackson.annotation.JsonTypeName;
import lombok.Value;
import java.io.InputStream;
@Value
@JsonTypeName("commandInput")
public class CommandInputStore implements StreamDataStore {
String cmd;
@Override
public InputStream openInput() throws Exception {
var proc = Runtime.getRuntime().exec(cmd);
return proc.getInputStream();
}
}

View file

@ -0,0 +1,52 @@
package io.xpipe.core.store;
import com.fasterxml.jackson.annotation.JsonTypeName;
import lombok.Value;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.Map;
import java.util.Optional;
@Value
@JsonTypeName("httpRequest")
public class HttpRequestStore implements StreamDataStore {
public static boolean isHttpRequest(String s) {
return s.startsWith("http:") || s.startsWith("https:");
}
public static Optional<HttpRequestStore> fromString(String s) {
try {
var uri = new URI(s);
return Optional.of(new HttpRequestStore(uri, Map.of()));
} catch (URISyntaxException e) {
return Optional.empty();
}
}
URI uri;
Map<String, String> headers;
@Override
public InputStream openInput() throws Exception {
var b = HttpRequest.newBuilder().uri(uri);
headers.forEach(b::setHeader);
var req = b.GET().build();
var client = HttpClient.newHttpClient();
var res = client.send(req, HttpResponse.BodyHandlers.ofByteArray());
return new ByteArrayInputStream(res.body());
}
@Override
public boolean exists() {
return false;
}
}

View file

@ -0,0 +1,34 @@
package io.xpipe.core.store;
import lombok.*;
import lombok.experimental.FieldDefaults;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;
@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
@Getter
@EqualsAndHashCode
@ToString
@AllArgsConstructor
public abstract class JdbcStore implements DataStore {
String hostname;
int port;
public void checkConnect() throws Exception {
try (Connection con = createConnection()) {
return;
}
}
public Connection createConnection() throws SQLException {
return DriverManager.getConnection(toUrl(), toProperties());
}
public abstract String toUrl();
public abstract Properties toProperties();
}

View file

@ -49,7 +49,9 @@ public interface StreamDataStore extends DataStore {
throw new UnsupportedOperationException("Can't open store output");
}
boolean exists();
default boolean exists() {
return false;
}
default boolean persistent() {
return false;

View file

@ -11,6 +11,9 @@ import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.jsontype.NamedType;
import com.fasterxml.jackson.databind.module.SimpleModule;
import io.xpipe.core.config.BaseQueryElement;
import io.xpipe.core.config.ChoiceElement;
import io.xpipe.core.config.HeaderElement;
import io.xpipe.core.data.type.ArrayType;
import io.xpipe.core.data.type.TupleType;
import io.xpipe.core.data.type.ValueType;
@ -18,6 +21,7 @@ import io.xpipe.core.data.type.WildcardType;
import io.xpipe.core.source.DataSourceInfo;
import io.xpipe.core.source.DataSourceReference;
import io.xpipe.core.store.CollectionEntryDataStore;
import io.xpipe.core.store.HttpRequestStore;
import io.xpipe.core.store.LocalDirectoryDataStore;
import io.xpipe.core.store.LocalFileDataStore;
@ -33,6 +37,7 @@ public class CoreJacksonModule extends SimpleModule {
new NamedType(LocalFileDataStore.class),
new NamedType(LocalDirectoryDataStore.class),
new NamedType(CollectionEntryDataStore.class),
new NamedType(HttpRequestStore.class),
new NamedType(ValueType.class),
new NamedType(TupleType.class),
new NamedType(ArrayType.class),
@ -41,7 +46,10 @@ public class CoreJacksonModule extends SimpleModule {
new NamedType(DataSourceInfo.Structure.class),
new NamedType(DataSourceInfo.Text.class),
new NamedType(DataSourceInfo.Collection.class),
new NamedType(DataSourceInfo.Raw.class)
new NamedType(DataSourceInfo.Raw.class),
new NamedType(BaseQueryElement.class),
new NamedType(ChoiceElement.class),
new NamedType(HeaderElement.class)
);
addSerializer(Charset.class, new CharsetSerializer());

View file

@ -22,7 +22,9 @@ module io.xpipe.core {
requires com.fasterxml.jackson.core;
requires com.fasterxml.jackson.databind;
requires java.net.http;
requires static lombok;
requires java.sql;
uses com.fasterxml.jackson.databind.Module;
provides com.fasterxml.jackson.databind.Module with CoreJacksonModule;

View file

@ -1,7 +1,7 @@
package io.xpipe.extension;
import io.xpipe.charsetter.NewLine;
import io.xpipe.core.config.ConfigConverter;
import io.xpipe.core.config.QueryConverter;
import io.xpipe.core.config.ConfigParameter;
import io.xpipe.core.source.DataSource;
import io.xpipe.core.source.DataSourceType;
@ -9,7 +9,6 @@ import io.xpipe.core.store.DataStore;
import javafx.beans.property.Property;
import javafx.scene.layout.Region;
import lombok.SneakyThrows;
import lombok.Value;
import java.util.Arrays;
import java.util.List;
@ -93,13 +92,6 @@ public interface DataSourceProvider<T extends DataSource<?>> {
interface ConfigProvider<T extends DataSource<?>> {
@Value
static class Parameter {
ConfigParameter parameter;
Object currentValue;
boolean required;
}
static <T extends DataSource<?>> ConfigProvider<T> empty(List<String> names, Function<DataStore, T> func) {
return new ConfigProvider<>() {
@Override
@ -124,9 +116,9 @@ public interface DataSourceProvider<T extends DataSource<?>> {
}
ConfigParameter CHARSET = new ConfigParameter(
"charset", ConfigConverter.CHARSET);
"charset", QueryConverter.CHARSET);
public static final ConfigConverter<NewLine> NEW_LINE_CONVERTER = new ConfigConverter<NewLine>() {
public static final QueryConverter<NewLine> NEW_LINE_CONVERTER = new QueryConverter<NewLine>() {
@Override
protected NewLine fromString(String s) {
return NewLine.id(s);

View file

@ -0,0 +1,46 @@
package io.xpipe.extension;
import io.xpipe.core.config.Dialog;
import java.net.URL;
import java.util.List;
public interface DataStoreProvider {
default void init() throws Exception {
}
default String i18n(String key) {
return I18n.get(getId() + "." + key);
}
default String i18nKey(String key) {
return getId() + "." + key;
}
default String getDisplayName() {
return i18n("displayName");
}
default String getDisplayDescription() {
return i18n("displayDescription");
}
default String getDisplayIconFileName() {
return getId() + ":icon.png";
}
default Dialog dialogForURL(URL url) {
return null;
}
Dialog defaultDialog();
List<String> getPossibleNames();
default String getId() {
var n = getClass().getPackageName();
var i = n.lastIndexOf('.');
return i != -1 ? n.substring(i + 1) : n;
}
}

View file

@ -0,0 +1,51 @@
package io.xpipe.extension;
import io.xpipe.core.config.Dialog;
import io.xpipe.extension.event.ErrorEvent;
import java.net.URL;
import java.util.Objects;
import java.util.Optional;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.stream.Collectors;
public class DataStoreProviders {
private static Set<DataStoreProvider> ALL;
public static void init(ModuleLayer layer) {
if (ALL == null) {
ALL = ServiceLoader.load(layer, DataStoreProvider.class).stream()
.map(p -> (DataStoreProvider) p.get()).collect(Collectors.toSet());
ALL.forEach(p -> {
try {
p.init();
} catch (Exception e) {
ErrorEvent.fromThrowable(e).handle();
}
});
}
}
public static Optional<DataStoreProvider> byName(String name) {
if (ALL == null) {
throw new IllegalStateException("Not initialized");
}
return ALL.stream().filter(d -> d.getPossibleNames().stream()
.anyMatch(s -> s.equalsIgnoreCase(name))).findAny();
}
public static Optional<Dialog> byURL(URL url) {
if (ALL == null) {
throw new IllegalStateException("Not initialized");
}
return ALL.stream().map(d -> d.dialogForURL(url)).filter(Objects::nonNull).findAny();
}
public static Set<DataStoreProvider> getAll() {
return ALL;
}
}

View file

@ -35,4 +35,5 @@ module io.xpipe.extension {
uses io.xpipe.extension.I18n;
uses io.xpipe.extension.event.EventHandler;
uses io.xpipe.extension.prefs.PrefsProvider;
uses io.xpipe.extension.DataStoreProvider;
}