queryBuilder, IndexSearcher indexSearcher) {
17 | this.queryBuilder = queryBuilder;
18 | this.indexSearcher = indexSearcher;
19 | }
20 |
21 | /**
22 | * For every index segment in the given index, apply the processor to the documents returns by the scorer.
23 | * @param query proprietary DSL query to execute
24 | * @param processor field value collector
25 | * @return processor
26 | */
27 | public P search(T query, P processor) {
28 | final QueryScorer scorer = queryBuilder.build(query);
29 |
30 | try {
31 | for (LeafReaderContext leafCtx : indexSearcher.getIndexReader().leaves()) {
32 | scorer.reset(leafCtx);
33 | processor.reset(leafCtx);
34 |
35 | final DocIdSetIterator docIdIter = scorer.iterator();
36 | if (docIdIter != null) {
37 | int docId = docIdIter.nextDoc();
38 | while (docId != DocIdSetIterator.NO_MORE_DOCS) {
39 | processor.process(docId);
40 | docId = docIdIter.nextDoc();
41 | }
42 | }
43 | }
44 |
45 | return processor;
46 | } catch (Exception e) {
47 | throw new RuntimeException("Failed to execute query: " + query, e);
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/lucenetest/src/test/java/net/ndolgov/lucenetest/search/LuceneFields.java:
--------------------------------------------------------------------------------
1 | package net.ndolgov.lucenetest.search;
2 |
3 | import org.apache.lucene.document.DoubleDocValuesField;
4 | import org.apache.lucene.document.FieldType;
5 | import org.apache.lucene.document.LongField;
6 | import org.apache.lucene.document.NumericDocValuesField;
7 | import org.apache.lucene.index.IndexOptions;
8 |
9 | /**
10 | * Test schema fields
11 | */
12 | public final class LuceneFields {
13 | public static final String INDEXED_FIELD_NAME = "TestIndexed";
14 | public static final String LONG_FIELD_NAME = "TestLong";
15 | public static final String DOUBLE_FIELD_NAME = "TestDouble";
16 |
17 | /** The only indexed field to search by */
18 | public static final LongField indexedField = new LongField(INDEXED_FIELD_NAME, -1, indexedLong());
19 |
20 | public static final NumericDocValuesField longValueField = new NumericDocValuesField(LONG_FIELD_NAME, -1);
21 |
22 | public static final NumericDocValuesField doubleValueField = new DoubleDocValuesField(DOUBLE_FIELD_NAME, -1);
23 |
24 |
25 | public static FieldType indexedLong() {
26 | final FieldType type = new FieldType();
27 | type.setTokenized(false);
28 | type.setOmitNorms(true);
29 | type.setStored(true);
30 | type.setIndexOptions(IndexOptions.DOCS);
31 | type.setNumericType(FieldType.NumericType.LONG);
32 | type.freeze();
33 | return type;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/parquettest/src/main/java/net/ndolgov/parquettest/ColumnHeader.java:
--------------------------------------------------------------------------------
1 | package net.ndolgov.parquettest;
2 |
3 | public interface ColumnHeader {
4 | /** @return the field name used for this column */
5 | String name();
6 |
7 | /** @return the type of this column */
8 | ColumnType type();
9 |
10 | enum ColumnType {
11 | LONG, DOUBLE;
12 | }
13 | }
--------------------------------------------------------------------------------
/parquettest/src/main/java/net/ndolgov/parquettest/GenericParquetReader.java:
--------------------------------------------------------------------------------
1 | package net.ndolgov.parquettest;
2 |
3 | import org.apache.hadoop.fs.Path;
4 | import org.apache.log4j.Logger;
5 | import org.apache.log4j.LogManager;
6 | import org.apache.parquet.filter2.compat.FilterCompat;
7 | import org.apache.parquet.hadoop.ParquetReader;
8 | import org.apache.parquet.hadoop.api.ReadSupport;
9 |
10 | /**
11 | *
12 | */
13 | public final class GenericParquetReader {
14 | private static final Logger logger = LogManager.getLogger(GenericParquetReader.class);
15 | private final ParquetReader reader;
16 | private final String path;
17 |
18 | public GenericParquetReader(ReadSupport support, String path) {
19 | this.path = path;
20 |
21 | try {
22 | reader = ParquetReader.builder(support, new Path(path)).build();
23 | } catch (Exception e) {
24 | throw new RuntimeException("Failed to open Parquet file: " + path, e);
25 | }
26 | }
27 |
28 | public GenericParquetReader(ReadSupport support, String path, FilterCompat.Filter filter) {
29 | this.path = path;
30 |
31 | try {
32 | reader = ParquetReader.builder(support, new Path(path)).withFilter(filter).build();
33 | } catch (Exception e) {
34 | throw new RuntimeException("Failed to open Parquet file: " + path, e);
35 | }
36 | }
37 |
38 | public T read() {
39 | try {
40 | return reader.read();
41 | } catch (Exception e) {
42 | throw new RuntimeException("Failed to read next record from Parquet file: " + path, e);
43 | }
44 | }
45 |
46 | public void close() {
47 | try {
48 | reader.close();
49 | } catch (Exception e) {
50 | logger.warn("Failed to close Parquet file: " + path + " because of: " + e.getMessage());
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/parquettest/src/main/java/net/ndolgov/parquettest/LongColumnHeader.java:
--------------------------------------------------------------------------------
1 | package net.ndolgov.parquettest;
2 |
3 | public final class LongColumnHeader implements ColumnHeader {
4 | private final String name;
5 |
6 | public LongColumnHeader(String name) {
7 | this.name = name;
8 | }
9 |
10 | @Override
11 | public String name() {
12 | return name;
13 | }
14 |
15 | @Override
16 | public ColumnType type() {
17 | return ColumnType.LONG;
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/parquettest/src/main/java/net/ndolgov/parquettest/ParquetLoggerOverride.java:
--------------------------------------------------------------------------------
1 | package net.ndolgov.parquettest;
2 |
3 | import java.io.InputStream;
4 | import java.util.logging.FileHandler;
5 | import java.util.logging.Handler;
6 | import java.util.logging.Level;
7 | import java.util.logging.LogManager;
8 | import java.util.logging.Logger;
9 |
10 | /**
11 | * See https://issues.apache.org/jira/browse/SPARK-4412 and
12 | * http://stackoverflow.com/questions/805701/load-java-util-logging-config-file-for-default-initialization
13 | */
14 | public class ParquetLoggerOverride {
15 | public static void fixParquetJUL() {
16 | try (final InputStream inputStream = ParquetLoggerOverride.class.getResourceAsStream("/logging.properties")) {
17 | LogManager.getLogManager().readConfiguration(inputStream);
18 |
19 | // trigger static initialization
20 | Class.forName(org.apache.parquet.Log.class.getName());
21 |
22 | // make sure it will NOT write to console
23 | final Logger parquetLog = Logger.getLogger(org.apache.parquet.Log.class.getPackage().getName());
24 | for (Handler h : parquetLog.getHandlers()) {
25 | parquetLog.removeHandler(h);
26 | }
27 | parquetLog.setUseParentHandlers(true);
28 | parquetLog.setLevel(Level.INFO);
29 |
30 | // redirect to file
31 | final FileHandler toFile = new FileHandler();
32 | parquetLog.addHandler(toFile);
33 | } catch (final Exception e) {
34 | throw new IllegalArgumentException("Could not load default logging.properties file");
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/parquettest/src/main/java/net/ndolgov/parquettest/Record.java:
--------------------------------------------------------------------------------
1 | package net.ndolgov.parquettest;
2 |
3 | /**
4 | * Record format: | rowId:long | metricId:long | time:long | value:long |
5 | */
6 | public final class Record {
7 | public static final long NULL = -1;
8 |
9 | public long id;
10 |
11 | public long metric;
12 |
13 | public long time;
14 |
15 | public long value;
16 |
17 | public Record(long id, long metric, long time, long value) {
18 | this.id = id;
19 | this.metric = metric;
20 | this.time = time;
21 | this.value = value;
22 | }
23 |
24 | public long getLong(int index) {
25 | switch (index) {
26 | case 0 : return id;
27 | case 1 : return metric;
28 | case 2 : return time;
29 | case 3 : return value;
30 | default: throw new IllegalArgumentException("Unexpected column index: " + index);
31 | }
32 | }
33 |
34 | public void setLong(int index, long newValue) {
35 | switch (index) {
36 | case 0 : id = newValue; break;
37 | case 1 : metric = newValue; break;
38 | case 2 : time = newValue; break;
39 | case 3 : value = newValue; break;
40 | default: throw new IllegalArgumentException("Unexpected column index: " + index);
41 | }
42 | }
43 |
44 | public boolean isNull(int index) {
45 | return getLong(index) == Record.NULL;
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/parquettest/src/main/java/net/ndolgov/parquettest/RecordFields.java:
--------------------------------------------------------------------------------
1 | package net.ndolgov.parquettest;
2 |
3 | /**
4 | * Supported record fields
5 | */
6 | public enum RecordFields {
7 | ROW_ID {
8 | @Override
9 | public String columnName() {
10 | return "ROWID";
11 | }
12 |
13 | @Override
14 | public int index() {
15 | return 0;
16 | }
17 | },
18 | METRIC {
19 | @Override
20 | public String columnName() {
21 | return "METRIC";
22 | }
23 |
24 | @Override
25 | public int index() {
26 | return 1;
27 | }
28 | },
29 | TIME {
30 | @Override
31 | public String columnName() {
32 | return "TIME";
33 | }
34 |
35 | @Override
36 | public int index() {
37 | return 2;
38 | }
39 | },
40 | VALUE {
41 | @Override
42 | public String columnName() {
43 | return "VALUE";
44 | }
45 |
46 | @Override
47 | public int index() {
48 | return 3;
49 | }
50 | };
51 |
52 | public abstract String columnName();
53 |
54 | public abstract int index();
55 | }
56 |
--------------------------------------------------------------------------------
/parquettest/src/main/java/net/ndolgov/parquettest/RecordFileUtil.java:
--------------------------------------------------------------------------------
1 | package net.ndolgov.parquettest;
2 |
3 | import org.apache.hadoop.fs.Path;
4 |
5 | import java.io.IOException;
6 | import java.util.List;
7 | import java.util.Map;
8 |
9 | import static com.google.common.collect.Lists.newArrayList;
10 |
11 | /**
12 | * Parquet file operations
13 | */
14 | public final class RecordFileUtil {
15 | public static void createParquetFile(String path, List rows, Map metadata) throws IOException {
16 | final RecordParquetWriter writer = new RecordParquetWriter(
17 | new Path(path),
18 | newArrayList(
19 | new LongColumnHeader(RecordFields.ROW_ID.columnName()),
20 | new LongColumnHeader(RecordFields.METRIC.columnName()),
21 | new LongColumnHeader(RecordFields.TIME.columnName()),
22 | new LongColumnHeader(RecordFields.VALUE.columnName())),
23 | metadata);
24 |
25 | try {
26 | rows.forEach(row -> {
27 | try {
28 | writer.write(row);
29 | } catch (IOException e) {
30 | throw new IllegalArgumentException(e);
31 | }
32 | });
33 | } finally {
34 | writer.close();
35 | }
36 |
37 | }
38 |
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/parquettest/src/main/java/net/ndolgov/parquettest/RecordParquetWriter.java:
--------------------------------------------------------------------------------
1 | package net.ndolgov.parquettest;
2 |
3 | import org.apache.hadoop.fs.Path;
4 | import org.apache.parquet.hadoop.ParquetWriter;
5 | import org.apache.parquet.hadoop.metadata.CompressionCodecName;
6 |
7 | import java.io.IOException;
8 | import java.util.List;
9 | import java.util.Map;
10 |
11 | /**
12 | * Write a dataset of {@link Record} instances to a Parquet file.
13 | */
14 | public final class RecordParquetWriter extends ParquetWriter {
15 | private static final int DEFAULT_PAGE_SIZE = 512 * 1024; // 500K
16 | private static final int DEFAULT_BLOCK_SIZE = 128 * 1024 * 1024; // 128M
17 |
18 | public RecordParquetWriter(Path file, List headers, CompressionCodecName compressionCodecName, int blockSize, int pageSize, Map metadata) throws IOException {
19 | super(file, new RecordWriteSupport(headers, metadata), compressionCodecName, blockSize, pageSize);
20 | }
21 |
22 | /**
23 | * Create a new {@link Record} writer. Default compression is no compression.
24 | *
25 | * @param path the path name to write to (e.g. "file:///var/tmp/file.par")
26 | * @param headers column headers that represent the data schema
27 | * @param metadata custom metadata to attach to the newly created file
28 | * @throws IOException
29 | */
30 | public RecordParquetWriter(Path path, List headers, Map metadata) throws IOException {
31 | this(path, headers, CompressionCodecName.UNCOMPRESSED, DEFAULT_BLOCK_SIZE, DEFAULT_PAGE_SIZE, metadata);
32 | }
33 | }
34 |
35 |
--------------------------------------------------------------------------------
/parquettest/src/main/java/net/ndolgov/parquettest/RecordWriteSupport.java:
--------------------------------------------------------------------------------
1 | package net.ndolgov.parquettest;
2 |
3 | import org.apache.hadoop.conf.Configuration;
4 | import org.apache.parquet.hadoop.api.WriteSupport;
5 | import org.apache.parquet.io.api.RecordConsumer;
6 |
7 | import java.util.List;
8 | import java.util.Map;
9 |
10 | public final class RecordWriteSupport extends WriteSupport {
11 | static final String ROW_COUNT = "ROW_COUNT";
12 |
13 | private final List headers;
14 |
15 | private final Map metadata;
16 |
17 | private RecordConsumer recordConsumer;
18 |
19 | private WriterFactory.Writer writer;
20 |
21 | private long rowCount;
22 |
23 | public RecordWriteSupport(List headers, Map metadata) {
24 | this.headers = headers;
25 | this.metadata = metadata;
26 | }
27 |
28 | @Override
29 | public WriteContext init(Configuration configuration) {
30 | return new WriteContext(ToParquet.from(headers), metadata);
31 | }
32 |
33 | @Override
34 | public void prepareForWrite(RecordConsumer consumer) {
35 | this.recordConsumer = consumer;
36 | this.writer = WriterFactory.create(headers, consumer);
37 | }
38 |
39 | @Override
40 | public void write(Record record) {
41 | recordConsumer.startMessage();
42 |
43 | try {
44 | writer.write(record);
45 | } catch (RuntimeException e) {
46 | throw new RuntimeException("Could not write record: " + record, e);
47 | }
48 |
49 | recordConsumer.endMessage();
50 | rowCount++;
51 | }
52 |
53 | @Override
54 | public FinalizedWriteContext finalizeWrite() {
55 | metadata.put(ROW_COUNT, String.valueOf(rowCount));
56 | return new FinalizedWriteContext(metadata);
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/parquettest/src/main/java/net/ndolgov/parquettest/ToParquet.java:
--------------------------------------------------------------------------------
1 | package net.ndolgov.parquettest;
2 |
3 | import org.apache.parquet.schema.MessageType;
4 | import org.apache.parquet.schema.PrimitiveType.PrimitiveTypeName;
5 | import org.apache.parquet.schema.Type;
6 | import org.apache.parquet.schema.Types;
7 | import org.apache.parquet.schema.Types.Builder;
8 | import org.apache.parquet.schema.Types.GroupBuilder;
9 |
10 | import java.util.List;
11 |
12 | public final class ToParquet {
13 | public static final String SCHEMA_NAME = "NET.NDOLGOV.PARQUETTEST";
14 |
15 | /**
16 | * @param headers column headers
17 | * @return create a Parquet schema from a sequence of types columns
18 | */
19 | public static MessageType from(List headers) {
20 | return from(Types.buildMessage(), headers).named(SCHEMA_NAME);
21 | }
22 |
23 | private static GroupBuilder from(GroupBuilder groupBuilder, List headers) {
24 | GroupBuilder builder = groupBuilder;
25 |
26 | for (ColumnHeader header : headers) {
27 | builder = addField(header, builder).named(header.name()); // no ids because headers are created sequentially
28 | }
29 |
30 | return builder;
31 | }
32 |
33 | private static Builder extends Builder, GroupBuilder>, GroupBuilder> addField(ColumnHeader header, GroupBuilder builder) {
34 | switch (header.type()) {
35 | case LONG:
36 | return builder.primitive(PrimitiveTypeName.INT64, Type.Repetition.REQUIRED); // fully qualified tuples only
37 |
38 | case DOUBLE:
39 | return builder.primitive(PrimitiveTypeName.DOUBLE, Type.Repetition.OPTIONAL); // not all metrics are expected in every row
40 |
41 | default:
42 | throw new IllegalArgumentException("Unexpected header type: " + header.type());
43 | }
44 | }
45 |
46 | private ToParquet() {
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/parquettest/src/main/java/net/ndolgov/parquettest/package-info.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Example of writing a simple record type to a Parquet file without Avro/PBs
3 | *
4 | * Heavily inspired by https://github.com/apache/parquet-mr/tree/master/parquet-protobuf
5 | */
6 | package net.ndolgov.parquettest;
--------------------------------------------------------------------------------
/parquettest/src/test/java/net/ndolgov/parquettest/FilterByValue.java:
--------------------------------------------------------------------------------
1 | package net.ndolgov.parquettest;
2 |
3 | import org.apache.parquet.filter2.predicate.UserDefinedPredicate;
4 |
5 | import java.io.Serializable;
6 |
7 | /**
8 | * Accept only rows with matching value
9 | */
10 | final class FilterByValue extends UserDefinedPredicate implements Serializable {
11 | private final long value;
12 |
13 | public FilterByValue(long value) {
14 | this.value = value;
15 | }
16 |
17 | @Override
18 | public boolean keep(Long value) {
19 | return value == this.value;
20 | }
21 |
22 | @Override
23 | public boolean canDrop(org.apache.parquet.filter2.predicate.Statistics statistics) {
24 | return (value < statistics.getMin()) || (statistics.getMax() < value);
25 | }
26 |
27 | @Override
28 | public boolean inverseCanDrop(org.apache.parquet.filter2.predicate.Statistics statistics) {
29 | return !canDrop(statistics);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/parquettest/src/test/resources/log4j.xml:
--------------------------------------------------------------------------------
1 |
2 | ] >
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/parquettest/src/test/resources/logging.properties:
--------------------------------------------------------------------------------
1 | # Logging
2 | handlers = java.util.logging.FileHandler
3 |
4 | # File Logging
5 | java.util.logging.FileHandler.pattern = target/parquet.log
6 | java.util.logging.FileHandler.formatter = java.util.logging.SimpleFormatter
7 | java.util.logging.FileHandler.level = FINE
--------------------------------------------------------------------------------
/querydsl/pom.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 | 4.0.0
6 |
7 | net.ndolgov.querydsl
8 | querydsl
9 | 1.0.0-SNAPSHOT
10 | pom
11 | Query DSL parser examples
12 |
13 |
14 | querydsl-antlr
15 | querydsl-dsl
16 | querydsl-fastparse
17 | querydsl-parboiled
18 | querydsl-parquet
19 |
20 |
21 |
22 | 4.6
23 | 1.1.7
24 | 1.7.0
25 | 1.6.4
26 | 6.8.8
27 | 2.3.2
28 | UTF-8
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/querydsl/querydsl-antlr/src/main/antlr4/imports/Common.g4:
--------------------------------------------------------------------------------
1 | lexer grammar Common;
2 |
3 | FILEPATH : ('/' | LETTER) (LETTER | DIGIT | '-' | '_' | '/' | '.')+ ;
4 |
5 | //QUOTED_FILEPATH : QUOTE FILEPATH QUOTE;
6 |
7 | QUOTED_ID : QUOTE ID QUOTE;
8 |
9 | ID : LETTER (LETTER | '_' | DIGIT)*;
10 |
11 | INT : DIGIT+ ;
12 |
13 | fragment DIGIT : '0'..'9';
14 |
15 | fragment LETTER : ('a'..'z' | 'A'..'Z') ;
16 |
17 | WS : (' ' | '\r' | '\t' | '\n')+ -> skip ;
18 |
19 | QUOTE : '\'';
--------------------------------------------------------------------------------
/querydsl/querydsl-antlr/src/main/antlr4/net/ndolgov/querydsl/antlr/action/QueryDsl.g4:
--------------------------------------------------------------------------------
1 | grammar QueryDsl;
2 |
3 | import Common;
4 |
5 | @parser::header {
6 | import net.ndolgov.querydsl.ast.expression.PredicateExpr;
7 | }
8 |
9 | @parser::members {
10 | private AstBuilder builder;
11 |
12 | public T parseWithAstBuilder(T builder) throws RecognitionException {
13 | this.builder = builder;
14 | query();
15 | return builder;
16 | }
17 | }
18 |
19 | query : selectExpr fromExpr whereExpr;
20 |
21 | selectExpr : 'select' ('*' | metricExpr);
22 |
23 | metricExpr : INT {builder.onMetricId($INT.text);} (',' INT)* {builder.onMetricId($INT.text);} ;
24 |
25 | fromExpr : 'from' QUOTE FILEPATH QUOTE {builder.onFromFilePath($FILEPATH.text);} ;
26 |
27 | whereExpr : 'where' pred = conditionExpr {builder.onPredicate($pred.cond);} ;
28 |
29 | conditionExpr returns [PredicateExpr cond] :
30 | pred = longEqExpr { $cond = $pred.cond; } |
31 | left = nestedCondition '&&' right = nestedCondition {$cond = builder.onAnd($left.cond, $right.cond);} |
32 | left = nestedCondition '||' right = nestedCondition {$cond = builder.onOr($left.cond, $right.cond);} ;
33 |
34 | nestedCondition returns [PredicateExpr cond] : '(' pred = conditionExpr ')' {$cond = $pred.cond;} ;
35 |
36 | longEqExpr returns [PredicateExpr cond] : QUOTED_ID '=' INT {$cond = builder.onAttrEqLong($QUOTED_ID.text, $INT.text);} ;
37 |
--------------------------------------------------------------------------------
/querydsl/querydsl-antlr/src/main/antlr4/net/ndolgov/querydsl/antlr/listener/ParquetDsl.g4:
--------------------------------------------------------------------------------
1 | grammar ParquetDsl;
2 |
3 | import Common;
4 |
5 | query : selectExpr fromExpr whereExpr;
6 |
7 | selectExpr : 'select' ('*' | metricExpr);
8 |
9 | metricExpr : INT (',' INT)*;
10 |
11 | fromExpr : 'from' QUOTE FILEPATH QUOTE;
12 |
13 | whereExpr : 'where' conditionExpr;
14 |
15 | conditionExpr :
16 | longEqExpr # LongEq |
17 | nestedCondition '&&' nestedCondition # And |
18 | nestedCondition '||' nestedCondition # Or ;
19 |
20 | nestedCondition : '(' conditionExpr ')';
21 |
22 | longEqExpr : QUOTED_ID '=' INT;
--------------------------------------------------------------------------------
/querydsl/querydsl-antlr/src/main/java/net/ndolgov/querydsl/antlr/action/AntlrActionDslParser.java:
--------------------------------------------------------------------------------
1 | package net.ndolgov.querydsl.antlr.action;
2 |
3 | import net.ndolgov.querydsl.ast.DslQuery;
4 | import net.ndolgov.querydsl.parser.DslParser;
5 | import org.antlr.v4.runtime.ANTLRInputStream;
6 | import org.antlr.v4.runtime.CommonTokenStream;
7 |
8 | /**
9 | * ANTLR4-based DSL parser that uses grammar actions directly (instead of being a parse tree walker listener)
10 | */
11 | public final class AntlrActionDslParser implements DslParser {
12 | @Override
13 | public DslQuery parse(String query) {
14 | try {
15 | return parser(query).parseWithAstBuilder(new AstBuilderImpl()).buildAst();
16 | } catch (Exception e) {
17 | throw new IllegalArgumentException("Failed to parse: " + query, e);
18 | }
19 | }
20 |
21 | private static QueryDslParser parser(String query) {
22 | return new QueryDslParser(
23 | new CommonTokenStream(
24 | new QueryDslLexer(
25 | new ANTLRInputStream(query))));
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/querydsl/querydsl-antlr/src/main/java/net/ndolgov/querydsl/antlr/action/AstBuilder.java:
--------------------------------------------------------------------------------
1 | package net.ndolgov.querydsl.antlr.action;
2 |
3 | import net.ndolgov.querydsl.ast.DslQuery;
4 | import net.ndolgov.querydsl.ast.expression.PredicateExpr;
5 |
6 | /**
7 | * Query AST builder that is notified by grammar actions about found elements.
8 | */
9 | interface AstBuilder {
10 | void onMetricId(String longAsStr);
11 |
12 | void onFromFilePath(String path);
13 |
14 | PredicateExpr onAttrEqLong(String quotedAttrname, String longAsStr);
15 |
16 | PredicateExpr onAnd(PredicateExpr left, PredicateExpr right);
17 |
18 | PredicateExpr onOr(PredicateExpr left, PredicateExpr right);
19 |
20 | void onPredicate(PredicateExpr predicate);
21 |
22 | /**
23 | * @return the root of the AST
24 | */
25 | DslQuery buildAst();
26 | }
27 |
--------------------------------------------------------------------------------
/querydsl/querydsl-antlr/src/main/java/net/ndolgov/querydsl/antlr/listener/AntlrListenerDslParser.java:
--------------------------------------------------------------------------------
1 | package net.ndolgov.querydsl.antlr.listener;
2 |
3 | import net.ndolgov.querydsl.parser.DslParser;
4 | import net.ndolgov.querydsl.ast.DslQuery;
5 | import org.antlr.v4.runtime.ANTLRInputStream;
6 | import org.antlr.v4.runtime.CommonTokenStream;
7 | import org.antlr.v4.runtime.tree.ParseTreeWalker;
8 |
9 | /**
10 | * ANTLR4-based DSL parser implementation that implements parse tree listener interface (and has no Java code in the grammar)
11 | */
12 | public final class AntlrListenerDslParser implements DslParser {
13 | @Override
14 | public DslQuery parse(String query) {
15 | try {
16 | final AstBuildingListener listener = new AstBuildingListener();
17 | new ParseTreeWalker().walk(listener, parser(query).query());
18 | return listener.buildAst();
19 | } catch (Exception e) {
20 | throw new IllegalArgumentException("Failed to parse: " + query, e);
21 | }
22 | }
23 |
24 | private static ParquetDslParser parser(String query) {
25 | final ParquetDslParser parser = new ParquetDslParser(
26 | new CommonTokenStream(
27 | new ParquetDslLexer(
28 | new ANTLRInputStream(query))));
29 |
30 | parser.setBuildParseTree(true);
31 | return parser;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/querydsl/querydsl-dsl/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | 4.0.0
7 |
8 |
9 | net.ndolgov.querydsl
10 | querydsl
11 | 1.0.0-SNAPSHOT
12 |
13 |
14 | querydsl-dsl
15 | 1.0.0-SNAPSHOT
16 | jar
17 | Query AST and Parser API
18 |
19 |
20 |
21 | org.slf4j
22 | slf4j-api
23 | ${slf4j.version}
24 |
25 |
26 |
27 | org.slf4j
28 | slf4j-log4j12
29 | ${slf4j.version}
30 | runtime
31 |
32 |
33 |
34 |
35 |
36 |
37 | org.apache.maven.plugins
38 | maven-compiler-plugin
39 | ${maven.compiler.plugin.version}
40 |
41 | 1.8
42 | 1.8
43 |
44 |
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/querydsl/querydsl-dsl/src/main/java/net/ndolgov/querydsl/ast/AstNode.java:
--------------------------------------------------------------------------------
1 | package net.ndolgov.querydsl.ast;
2 |
3 | /**
4 | * Heterogeneous AST node
5 | */
6 | public interface AstNode {
7 | }
8 |
--------------------------------------------------------------------------------
/querydsl/querydsl-dsl/src/main/java/net/ndolgov/querydsl/ast/DslQuery.java:
--------------------------------------------------------------------------------
1 | package net.ndolgov.querydsl.ast;
2 |
3 | /**
4 | * The root of a query AST
5 | */
6 | public final class DslQuery implements AstNode {
7 | public final Select selectNode;
8 | public final From fromNode;
9 | public final Where whereNode;
10 |
11 | public DslQuery(Select selectNode, From fromNode, Where whereNode) {
12 | this.whereNode = whereNode;
13 | this.selectNode = selectNode;
14 | this.fromNode = fromNode;
15 | }
16 |
17 | @Override
18 | public String toString() {
19 | return selectNode + " " + fromNode + " " + whereNode;
20 | }
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/querydsl/querydsl-dsl/src/main/java/net/ndolgov/querydsl/ast/From.java:
--------------------------------------------------------------------------------
1 | package net.ndolgov.querydsl.ast;
2 |
3 | /**
4 | * "/home/tmp/file.par"
5 | */
6 | public final class From implements AstNode {
7 | public final String path;
8 |
9 | public From(String path) {
10 | this.path = path;
11 | }
12 |
13 | @Override
14 | public String toString() {
15 | return " FROM " + path;
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/querydsl/querydsl-dsl/src/main/java/net/ndolgov/querydsl/ast/Projection.java:
--------------------------------------------------------------------------------
1 | package net.ndolgov.querydsl.ast;
2 |
3 | /**
4 | * "123456"
5 | */
6 | public final class Projection implements AstNode {
7 | public final long metricId;
8 |
9 | public Projection(long metricId) {
10 | this.metricId = metricId;
11 | }
12 |
13 | @Override
14 | public String toString() {
15 | return String.valueOf(metricId);
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/querydsl/querydsl-dsl/src/main/java/net/ndolgov/querydsl/ast/Select.java:
--------------------------------------------------------------------------------
1 | package net.ndolgov.querydsl.ast;
2 |
3 | import java.util.List;
4 |
5 | /**
6 | * "SELECT '*' | projection*"
7 | */
8 | public final class Select implements AstNode {
9 | public final List projections;
10 |
11 | // no projections means select all
12 | public Select(List projections) {
13 | this.projections = projections;
14 | }
15 |
16 | @Override
17 | public String toString() {
18 | return "SELECT " + (all() ? "*" : projections);
19 | }
20 |
21 | public boolean all() {
22 | return projections.isEmpty();
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/querydsl/querydsl-dsl/src/main/java/net/ndolgov/querydsl/ast/Where.java:
--------------------------------------------------------------------------------
1 | package net.ndolgov.querydsl.ast;
2 |
3 | import net.ndolgov.querydsl.ast.expression.PredicateExpr;
4 |
5 | /**
6 | * "WHERE predicate"
7 | */
8 | public final class Where implements AstNode {
9 | public final PredicateExpr predicate;
10 |
11 | public Where(PredicateExpr predicate) {
12 | this.predicate = predicate;
13 | }
14 |
15 | @Override
16 | public String toString() {
17 | return " WHERE " + predicate;
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/querydsl/querydsl-dsl/src/main/java/net/ndolgov/querydsl/ast/expression/AttrEqLong.java:
--------------------------------------------------------------------------------
1 | package net.ndolgov.querydsl.ast.expression;
2 |
3 | /**
4 | * Long constant equality operator
5 | */
6 | public final class AttrEqLong implements PredicateExpr {
7 | public final String attrName;
8 | public final long value;
9 |
10 | public AttrEqLong(String attrName, long value) {
11 | this.attrName = attrName;
12 | this.value = value;
13 | }
14 |
15 | @Override
16 | public > E accept(V visitor) {
17 | return visitor.visitAttrEqLong(this);
18 | }
19 |
20 | @Override
21 | public String toString() {
22 | return "(" + attrName + " = " + value + ")";
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/querydsl/querydsl-dsl/src/main/java/net/ndolgov/querydsl/ast/expression/BinaryExpr.java:
--------------------------------------------------------------------------------
1 | package net.ndolgov.querydsl.ast.expression;
2 |
3 | /**
4 | * "predicate1 && predicate2"
5 | * todo binary vs n-ary
6 | */
7 | public final class BinaryExpr implements PredicateExpr {
8 | public final PredicateExpr left;
9 | public final PredicateExpr right;
10 | public final Op operator;
11 |
12 | public BinaryExpr(PredicateExpr left, PredicateExpr right, Op operator) {
13 | this.left = left;
14 | this.right = right;
15 | this.operator = operator;
16 | }
17 |
18 | @Override
19 | public > E accept(V visitor) {
20 | return visitor.visitBinaryExpr(this);
21 | }
22 |
23 | @Override
24 | public final String toString() {
25 | return "(" + left.toString() + " " + operator.toString() + " " + right.toString() + ")";
26 | }
27 |
28 | public enum Op {
29 | EQ,
30 | AND, OR
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/querydsl/querydsl-dsl/src/main/java/net/ndolgov/querydsl/ast/expression/NoOpExpr.java:
--------------------------------------------------------------------------------
1 | package net.ndolgov.querydsl.ast.expression;
2 |
3 | /**
4 | *
5 | */
6 | public final class NoOpExpr implements PredicateExpr {
7 | public static final PredicateExpr INSTANCE = new NoOpExpr();
8 |
9 | @Override
10 | public > E accept(V visitor) {
11 | return null;
12 | }
13 |
14 | private NoOpExpr() {
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/querydsl/querydsl-dsl/src/main/java/net/ndolgov/querydsl/ast/expression/PredicateExpr.java:
--------------------------------------------------------------------------------
1 | package net.ndolgov.querydsl.ast.expression;
2 |
3 | /**
4 | * Predicate expression type
5 | */
6 | public interface PredicateExpr {
7 | > E accept(V visitor);
8 |
9 | interface ExprVisitor {
10 | E visitAttrEqLong(AttrEqLong expr);
11 |
12 | E visitBinaryExpr(BinaryExpr expr);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/querydsl/querydsl-dsl/src/main/java/net/ndolgov/querydsl/parser/DslParser.java:
--------------------------------------------------------------------------------
1 | package net.ndolgov.querydsl.parser;
2 |
3 | import net.ndolgov.querydsl.ast.DslQuery;
4 |
5 | /**
6 | * Generic DSL parser API
7 | */
8 | public interface DslParser {
9 | /**
10 | * @param query query expression
11 | * @return the AST corresponding to a given query string
12 | */
13 | DslQuery parse(String query);
14 | }
15 |
--------------------------------------------------------------------------------
/querydsl/querydsl-dsl/src/main/java/net/ndolgov/querydsl/parser/Tokens.java:
--------------------------------------------------------------------------------
1 | package net.ndolgov.querydsl.parser;
2 |
3 | /**
4 | * Tokens and keywords used in the query DSL
5 | */
6 | public final class Tokens {
7 | public static final String SELECT = "select";
8 | public static final String FROM = "from";
9 | public static final String WHERE = "where";
10 |
11 | public static final String AND = "&&";
12 | public static final String OR = "||";
13 | public static final String EQUALS = "=";
14 |
15 | public static final char LPAREN = '(';
16 | public static final char RPAREN = ')';
17 |
18 | public static final char LQUOTE = '\'';
19 | public static final char RQUOTE = '\'';
20 |
21 | public static final char ASTERISK = '*';
22 | }
23 |
--------------------------------------------------------------------------------
/querydsl/querydsl-fastparse/build.sbt:
--------------------------------------------------------------------------------
1 | val fastparse_version = "1.0.0"
2 | val scalatest_version = "3.0.4"
3 | val scala_version = "2.12.3"
4 |
5 | val querydsl_fastparse_id = "querydsl-fastparse"
6 |
7 | lazy val root = Project(id = querydsl_fastparse_id, base = file(".") ).
8 | settings(
9 | scalaVersion := scala_version,
10 | scalacOptions ++= Seq("-deprecation", "-Xfatal-warnings")
11 | ).
12 | settings(
13 | name := querydsl_fastparse_id,
14 | organization := "net.ndolgov.querydsl",
15 | version := "1.0.0-SNAPSHOT"
16 | ).
17 | settings(
18 | libraryDependencies ++= Seq(
19 | "org.scalatest" %% "scalatest" % scalatest_version % Test,
20 | "com.lihaoyi" %% "fastparse" % fastparse_version,
21 | "net.ndolgov.querydsl" % "querydsl-dsl" % "1.0.0-SNAPSHOT"
22 | )
23 | ).
24 | settings(
25 | resolvers += "Local Maven" at Path.userHome.asFile.toURI.toURL + ".m2/repository"
26 | )
27 |
28 |
29 |
--------------------------------------------------------------------------------
/querydsl/querydsl-fastparse/project/build.properties:
--------------------------------------------------------------------------------
1 | sbt.version=0.13.15
2 |
--------------------------------------------------------------------------------
/querydsl/querydsl-fastparse/src/main/scala/net/ndolgov/querydsl/fastparse/FastparseDslParser.scala:
--------------------------------------------------------------------------------
1 | package net.ndolgov.querydsl.fastparse
2 |
3 | import net.ndolgov.querydsl.ast.DslQuery
4 | import net.ndolgov.querydsl.parser.DslParser
5 |
6 | /** Fastparse-based DSL parser implementation */
7 | private final class FastparseDslParser(private val parser: FastparseParser) extends DslParser {
8 | override def parse(query: String): DslQuery = parser.parse(query) match {
9 | case Right(ast) =>
10 | ast
11 |
12 | case Left(e) =>
13 | //print(e.extra.traced.trace.mkString) // for syntax debugging
14 | throw new RuntimeException(e.msg);
15 | }
16 | }
17 |
18 | object FastparseDslParser {
19 | def apply() : DslParser = new FastparseDslParser(new FastparseParser())
20 | }
21 |
--------------------------------------------------------------------------------
/querydsl/querydsl-parboiled/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | 4.0.0
7 |
8 |
9 | net.ndolgov.querydsl
10 | querydsl
11 | 1.0.0-SNAPSHOT
12 |
13 |
14 | querydsl-parboiled
15 | 1.0.0-SNAPSHOT
16 | jar
17 | Parboiled query parser
18 |
19 |
20 |
21 | net.ndolgov.querydsl
22 | querydsl-dsl
23 | ${project.version}
24 |
25 |
26 |
27 | org.parboiled
28 | parboiled-java
29 | ${parboiled.version}
30 |
31 |
32 |
33 | org.slf4j
34 | slf4j-api
35 | ${slf4j.version}
36 |
37 |
38 |
39 | org.slf4j
40 | slf4j-log4j12
41 | ${slf4j.version}
42 | runtime
43 |
44 |
45 |
46 | org.testng
47 | testng
48 | ${testng.version}
49 | test
50 |
51 |
52 |
53 |
54 |
55 |
56 | org.apache.maven.plugins
57 | maven-compiler-plugin
58 | ${maven.compiler.plugin.version}
59 |
60 | 1.8
61 | 1.8
62 |
63 |
64 |
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/querydsl/querydsl-parboiled/src/main/java/net/ndolgov/querydsl/parboiled/ParboiledDslParser.java:
--------------------------------------------------------------------------------
1 | package net.ndolgov.querydsl.parboiled;
2 |
3 | import net.ndolgov.querydsl.parser.DslParser;
4 | import net.ndolgov.querydsl.ast.DslQuery;
5 | import org.parboiled.Parboiled;
6 | import org.parboiled.parserunners.RecoveringParseRunner;
7 |
8 | /**
9 | * Parboiled-based DSL parser implementation
10 | */
11 | public final class ParboiledDslParser implements DslParser {
12 | private final RecoveringParseRunner runner;
13 |
14 | public ParboiledDslParser() {
15 | runner = new RecoveringParseRunner<>(Parboiled.createParser(ParboiledParser.class).DslQuery());
16 | }
17 |
18 | @Override
19 | public DslQuery parse(String query) {
20 | try {
21 | return runner.run(query).resultValue;
22 | } finally {
23 | //System.out.println(runner.getLog()); // for syntax debugging, TracingParseRunner
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/querydsl/querydsl-parboiled/src/test/resources/log4j.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/querydsl/querydsl-parquet/src/main/java/net/ndolgov/querydsl/parquet/PredicateExprs.java:
--------------------------------------------------------------------------------
1 | package net.ndolgov.querydsl.parquet;
2 |
3 | import net.ndolgov.parquettest.RecordFields;
4 | import net.ndolgov.querydsl.ast.expression.AttrEqLong;
5 | import net.ndolgov.querydsl.ast.expression.BinaryExpr;
6 | import net.ndolgov.querydsl.ast.expression.NoOpExpr;
7 | import net.ndolgov.querydsl.ast.expression.PredicateExpr;
8 |
9 | /**
10 | * Parquet file predicate expression helpers
11 | */
12 | final class PredicateExprs {
13 | public static boolean isNoOp(PredicateExpr expr) {
14 | return expr == NoOpExpr.INSTANCE;
15 | }
16 |
17 | /**
18 | * @return expression that will pass through rows matching all child expressions
19 | */
20 | public static PredicateExpr conjunction(PredicateExpr left, PredicateExpr right) {
21 | return new BinaryExpr(left, right, BinaryExpr.Op.AND);
22 | }
23 |
24 | /**
25 | * @return expression that will pass through rows matching any of the two child expressions
26 | */
27 | public static PredicateExpr disjunction(PredicateExpr left, PredicateExpr right) {
28 | return new BinaryExpr(left, right, BinaryExpr.Op.OR);
29 | }
30 |
31 | /**
32 | * @return expression that will pass through rows with attr values equal to a given constant
33 | */
34 | public static PredicateExpr columnEq(RecordFields attr, long attrValue) {
35 | return new AttrEqLong(attr.columnName(), attrValue);
36 | }
37 |
38 | /**
39 | * @return expression that will force reading input file with no filter (and so won't incur additional cost)
40 | */
41 | public static PredicateExpr noOpPredicate() {
42 | return NoOpExpr.INSTANCE;
43 | }
44 | }
--------------------------------------------------------------------------------
/querydsl/querydsl-parquet/src/main/java/net/ndolgov/querydsl/parquet/ToParquetFilter.java:
--------------------------------------------------------------------------------
1 | package net.ndolgov.querydsl.parquet;
2 |
3 | import net.ndolgov.querydsl.ast.expression.AttrEqLong;
4 | import net.ndolgov.querydsl.ast.expression.BinaryExpr;
5 | import net.ndolgov.querydsl.ast.expression.PredicateExpr;
6 | import org.apache.parquet.filter2.compat.FilterCompat;
7 | import org.apache.parquet.filter2.predicate.FilterPredicate;
8 |
9 | import static net.ndolgov.querydsl.parquet.PredicateExprs.isNoOp;
10 | import static org.apache.parquet.filter2.predicate.FilterApi.and;
11 | import static org.apache.parquet.filter2.predicate.FilterApi.eq;
12 | import static org.apache.parquet.filter2.predicate.FilterApi.longColumn;
13 | import static org.apache.parquet.filter2.predicate.FilterApi.or;
14 |
15 | /**
16 | * Compile predicate expression tree into a Parquet filter
17 | */
18 | final class ToParquetFilter {
19 | /**
20 | * @param expr filter expression
21 | * @return Parquet filter corresponding to the filter expression
22 | */
23 | public static FilterCompat.Filter transform(PredicateExpr expr) {
24 | if (isNoOp(expr)) {
25 | return FilterCompat.NOOP;
26 | }
27 |
28 | return FilterCompat.get(toPredicate(expr));
29 | }
30 |
31 | private static FilterPredicate toPredicate(PredicateExpr expr) {
32 | return expr.accept(new PredicateExpr.ExprVisitor() {
33 | @Override
34 | public FilterPredicate visitAttrEqLong(AttrEqLong expr) {
35 | return eq(longColumn(expr.attrName), expr.value);
36 | }
37 |
38 | @Override
39 | public FilterPredicate visitBinaryExpr(BinaryExpr expr) {
40 | final FilterPredicate left = toPredicate(expr.left);
41 | final FilterPredicate right = toPredicate(expr.right);
42 |
43 | switch (expr.operator) {
44 | case AND:
45 | return and(left, right);
46 |
47 | case OR:
48 | return or(left, right);
49 |
50 | default:
51 | throw new IllegalArgumentException("Unexpected op: " + expr.operator);
52 | }
53 | }
54 | });
55 | }
56 |
57 | }
--------------------------------------------------------------------------------
/querydsl/querydsl-parquet/src/test/resources/log4j.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/restgatewaytest/project/build.properties:
--------------------------------------------------------------------------------
1 | sbt.version=1.0.3
2 |
--------------------------------------------------------------------------------
/restgatewaytest/project/protoc.sbt:
--------------------------------------------------------------------------------
1 | addSbtPlugin("com.thesamet" % "sbt-protoc" % "0.99.13")
2 |
3 | resolvers += Resolver.bintrayRepo("beyondthelines", "maven")
4 |
5 | val scalapb_plugin_version = "0.6.7"
6 | val scalapb_gateway_version = "0.0.8"
7 |
8 | libraryDependencies ++= Seq(
9 | "com.trueaccord.scalapb" %% "compilerplugin" % scalapb_plugin_version,
10 | "beyondthelines" %% "grpcgatewaygenerator" % scalapb_gateway_version
11 | )
--------------------------------------------------------------------------------
/restgatewaytest/restgatewaytest-web/src/main/proto/testsvcA.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | package net.ndolgov.restgatewaytest.api;
4 |
5 | option java_multiple_files = false;
6 | option java_package = "net.ndolgov.restgatewaytest.api";
7 | option java_outer_classname = "TestServiceAProto";
8 | option objc_class_prefix = "TS1P";
9 |
10 | import "google/api/annotations.proto";
11 |
12 | service TestServiceA {
13 | rpc Process (TestRequestA) returns (TestResponseA) {
14 | option (google.api.http) = {
15 | post: "/restgateway/test/testservicea"
16 | body: "*"
17 | };
18 | }
19 | }
20 |
21 | message TestRequestA {
22 | int64 requestId = 1;
23 | }
24 |
25 | message TestResponseA {
26 | bool success = 1;
27 | int64 requestId = 2;
28 | string result = 3;
29 | }
--------------------------------------------------------------------------------
/restgatewaytest/restgatewaytest-web/src/main/proto/testsvcB.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | package net.ndolgov.restgatewaytest.api;
4 |
5 | option java_multiple_files = false;
6 | option java_package = "net.ndolgov.restgatewaytest.api";
7 | option java_outer_classname = "TestServiceBProto";
8 | option objc_class_prefix = "TS2P";
9 |
10 | import "google/api/annotations.proto";
11 |
12 | service TestServiceB {
13 | rpc Process (TestRequestB) returns (TestResponseB) {
14 | option (google.api.http) = {
15 | post: "/restgateway/test/testserviceb"
16 | body: "*"
17 | };
18 | }
19 | }
20 |
21 | message TestRequestB {
22 | int64 requestId = 1;
23 | }
24 |
25 | message TestResponseB {
26 | bool success = 1;
27 | int64 requestId = 2;
28 | string result = 3;
29 | }
--------------------------------------------------------------------------------
/restgatewaytest/restgatewaytest-web/src/main/resources/specs/TestsvcAService.yml:
--------------------------------------------------------------------------------
1 | swagger: '2.0'
2 | info:
3 | version: not set
4 | title: 'TestsvcAProto'
5 | description: 'REST API generated from testsvcA.proto'
6 | schemes:
7 | - http
8 | - https
9 | consumes:
10 | - 'application/json'
11 | produces:
12 | - 'application/json'
13 | paths:
14 | /restgateway/test/testservicea:
15 | post:
16 | tags:
17 | - TestServiceA
18 | summary:
19 | 'Process'
20 | description:
21 | 'Generated from net.ndolgov.restgatewaytest.api.TestServiceA.Process'
22 | produces:
23 | ['application/json']
24 | responses:
25 | 200:
26 | description: 'Normal response'
27 | schema:
28 | $ref: "#/definitions/TestResponseA"
29 | parameters:
30 | - in: 'body'
31 | name: body
32 | schema:
33 | $ref: "#/definitions/TestRequestA"
34 | definitions:
35 | TestRequestA:
36 | type: object
37 | properties:
38 | requestId:
39 | type: integer
40 | format: int64
41 | TestResponseA:
42 | type: object
43 | properties:
44 | success:
45 | type: boolean
46 | requestId:
47 | type: integer
48 | format: int64
49 | result:
50 | type: string
--------------------------------------------------------------------------------
/restgatewaytest/restgatewaytest-web/src/main/resources/specs/TestsvcBService.yml:
--------------------------------------------------------------------------------
1 | swagger: '2.0'
2 | info:
3 | version: not set
4 | title: 'TestsvcBProto'
5 | description: 'REST API generated from testsvcB.proto'
6 | schemes:
7 | - http
8 | - https
9 | consumes:
10 | - 'application/json'
11 | produces:
12 | - 'application/json'
13 | paths:
14 | /restgateway/test/testserviceb:
15 | post:
16 | tags:
17 | - TestServiceB
18 | summary:
19 | 'Process'
20 | description:
21 | 'Generated from net.ndolgov.restgatewaytest.api.TestServiceB.Process'
22 | produces:
23 | ['application/json']
24 | responses:
25 | 200:
26 | description: 'Normal response'
27 | schema:
28 | $ref: "#/definitions/TestResponseB"
29 | parameters:
30 | - in: 'body'
31 | name: body
32 | schema:
33 | $ref: "#/definitions/TestRequestB"
34 | definitions:
35 | TestRequestB:
36 | type: object
37 | properties:
38 | requestId:
39 | type: integer
40 | format: int64
41 | TestResponseB:
42 | type: object
43 | properties:
44 | success:
45 | type: boolean
46 | requestId:
47 | type: integer
48 | format: int64
49 | result:
50 | type: string
--------------------------------------------------------------------------------
/restgatewaytest/restgatewaytest-web/src/main/scala/net/ndolgov/restgatewaytest/GatewayServer.scala:
--------------------------------------------------------------------------------
1 | package net.ndolgov.restgatewaytest
2 |
3 | import java.util.concurrent.Executor
4 |
5 | import grpcgateway.handlers.GrpcGatewayHandler
6 | import grpcgateway.server.{GrpcGatewayServer, GrpcGatewayServerBuilder}
7 | import io.grpc.{ManagedChannel, ManagedChannelBuilder}
8 | import org.slf4j.LoggerFactory
9 |
10 | /** REST gateway for a gRPC service instance that can be started and stopped */
11 | trait GatewayServer {
12 | def start(): Unit
13 |
14 | def stop(): Unit
15 | }
16 |
17 | private final class GatewayServerImpl(server: GrpcGatewayServer, port: Int) extends GatewayServer {
18 | private val logger = LoggerFactory.getLogger(classOf[GatewayServer])
19 |
20 | override def start(): Unit = {
21 | try {
22 | server.start()
23 | logger.info("Started " + this)
24 | } catch {
25 | case e: Exception =>
26 | throw new RuntimeException("Could not start server", e)
27 | }
28 | }
29 |
30 | override def stop(): Unit = {
31 | try {
32 | logger.info("Stopping " + this)
33 | server.shutdown()
34 | logger.info("Stopped " + this)
35 | } catch {
36 | case _: Exception =>
37 | logger.warn("Interrupted while shutting down " + this)
38 | }
39 | }
40 |
41 | override def toString: String = "{GatewayServer:port=" + port + "}"
42 | }
43 |
44 | /** Create a Netty-backed REST Gateway for a given gRPC server with the request handlers created by a given factory
45 | * method. Bind the gateway to a given port. Perform request redirection on a given thread pool. */
46 | object GatewayServer {
47 | def apply(serviceHost: String, servicePort: Int,
48 | gatewayPort: Int,
49 | executor: Executor,
50 | toHandlers: (ManagedChannel) => Seq[GrpcGatewayHandler]) : GatewayServer = {
51 | val channel = ManagedChannelBuilder.forAddress(serviceHost, servicePort).usePlaintext(true).executor(executor).build()
52 |
53 | var builder = GrpcGatewayServerBuilder.forPort(gatewayPort)
54 | for (handler <- toHandlers(channel)) {
55 | builder = builder.addService(handler)
56 | }
57 |
58 | new GatewayServerImpl(builder.build(), gatewayPort)
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/restgatewaytest/restgatewaytest-web/src/main/scala/net/ndolgov/restgatewaytest/GrpcServer.scala:
--------------------------------------------------------------------------------
1 | package net.ndolgov.restgatewaytest
2 |
3 | import java.net.InetSocketAddress
4 | import java.util.concurrent.TimeUnit
5 |
6 | import io.grpc.netty.NettyServerBuilder
7 | import io.grpc.{Server, ServerServiceDefinition}
8 | import org.slf4j.LoggerFactory
9 |
10 | import scala.concurrent.ExecutionContext
11 |
12 | /** gRPC service instance that can be started and stopped */
13 | trait GrpcServer {
14 | def start(): Unit
15 |
16 | def stop(timeout: Long): Unit
17 | }
18 |
19 | private final class GrpcServerImpl(server: Server, port: Int) extends GrpcServer {
20 | private val logger = LoggerFactory.getLogger(classOf[GrpcServer])
21 |
22 | override def start(): Unit = {
23 | try {
24 | logger.info("Starting " + this)
25 | server.start
26 | logger.info("Started " + this)
27 | } catch {
28 | case e: Exception =>
29 | throw new RuntimeException("Could not start server", e)
30 | }
31 | }
32 |
33 | override def stop(timeout: Long): Unit = {
34 | try {
35 | logger.info("Stopping " + this)
36 | server.shutdown().awaitTermination(timeout, TimeUnit.MILLISECONDS)
37 | logger.info("Stopped " + this)
38 | } catch {
39 | case _: Exception =>
40 | logger.warn("Interrupted while shutting down " + this)
41 | }
42 | }
43 |
44 | override def toString: String = "{GrpcServer:port=" + port + "}"
45 | }
46 |
47 | /** Create a Netty-backed gRPC service instance with the request handlers created by a given factory method.
48 | * Bind the service to a given "host:port" address. Process requests on a given thread pool. */
49 | object GrpcServer {
50 | def apply(hostname: String,
51 | port: Int,
52 | toServices: (ExecutionContext) => Seq[ServerServiceDefinition])
53 | (implicit ec: ExecutionContext) : GrpcServer = {
54 |
55 | val builder: NettyServerBuilder = NettyServerBuilder.forAddress(new InetSocketAddress(hostname, port))
56 | toServices.apply(ec).foreach(definition => builder.addService(definition))
57 |
58 | new GrpcServerImpl(builder.build(), port)
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/restgatewaytest/restgatewaytest-web/src/main/scala/net/ndolgov/restgatewaytest/TestServiceAImpl.scala:
--------------------------------------------------------------------------------
1 | package net.ndolgov.restgatewaytest
2 |
3 | import net.ndolgov.restgatewaytest.api.testsvcA.{TestRequestA, TestResponseA}
4 | import net.ndolgov.restgatewaytest.api.testsvcA.TestServiceAGrpc.TestServiceA
5 | import org.slf4j.LoggerFactory
6 |
7 | import scala.concurrent.{ExecutionContext, Future}
8 |
9 | class TestServiceAImpl(implicit ec: ExecutionContext) extends TestServiceA {
10 | private val logger = LoggerFactory.getLogger(classOf[TestServiceAImpl])
11 |
12 | override def process(request: TestRequestA): Future[TestResponseA] = {
13 | Future {
14 | logger.info(s"Computing result of $request"); // todo this is where actual time-consuming processing would be
15 | TestResponseA(success = true, request.requestId, "RESULTA")
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/restgatewaytest/restgatewaytest-web/src/main/scala/net/ndolgov/restgatewaytest/TestServiceBImpl.scala:
--------------------------------------------------------------------------------
1 | package net.ndolgov.restgatewaytest
2 |
3 | import net.ndolgov.restgatewaytest.api.testsvcB.{TestRequestB, TestResponseB}
4 | import net.ndolgov.restgatewaytest.api.testsvcB.TestServiceBGrpc.TestServiceB
5 | import org.slf4j.LoggerFactory
6 |
7 | import scala.concurrent.{ExecutionContext, Future}
8 |
9 | class TestServiceBImpl(implicit ec: ExecutionContext) extends TestServiceB {
10 | private val logger = LoggerFactory.getLogger(classOf[TestServiceBImpl])
11 |
12 | override def process(request: TestRequestB): Future[TestResponseB] = {
13 | Future {
14 | logger.info(s"Computing result of $request"); // todo this is where actual time-consuming processing would be
15 | TestResponseB(success = true, request.requestId, "RESULTB")
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/restgatewaytest/restgatewaytest-web/src/test/resources/log4j.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/restgatewaytest/restgatewaytest-web/src/test/scala/net/ndolgov/restgatewaytest/JsonMarshaller.scala:
--------------------------------------------------------------------------------
1 | package net.ndolgov.restgatewaytest
2 |
3 | import java.io.StringWriter
4 |
5 | import com.fasterxml.jackson.databind.ObjectMapper
6 | import com.fasterxml.jackson.module.scala.DefaultScalaModule
7 |
8 | /** Scala wrapper for a popular Java JSON serialization library*/
9 | object JsonMarshaller {
10 | private val marshaller = new ObjectMapper().registerModule(new DefaultScalaModule())
11 |
12 | def toJson[T](value: T): String = {
13 | val writer = new StringWriter
14 | marshaller.writeValue(writer, value)
15 | writer.toString
16 | }
17 |
18 | def fromJson[T](json: String, clazz: Class[T]) : T = {
19 | marshaller.readValue(json, clazz)
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/s3test/src/main/java/net/ndolgov/s3test/FileDownloader.java:
--------------------------------------------------------------------------------
1 | package net.ndolgov.s3test;
2 |
3 | import java.io.File;
4 |
5 | /**
6 | * Download a file from S3 storage
7 | */
8 | public interface FileDownloader {
9 | /**
10 | * @param localFile local file to upload
11 | * @param remotePath relative path (counting from some assumed root) in S3 storage
12 | * @param callback observer to notify about upload status
13 | */
14 | void download(File localFile, String remotePath, TransferCallback callback);
15 | }
16 |
--------------------------------------------------------------------------------
/s3test/src/main/java/net/ndolgov/s3test/FileHandler.java:
--------------------------------------------------------------------------------
1 | package net.ndolgov.s3test;
2 |
3 | /**
4 | * Downloader-side handler of newly detected S3 storage files
5 | */
6 | public interface FileHandler {
7 | /**
8 | * Handle a file found in the remote storage at a given path
9 | * @param remotePath S3 storage path
10 | * @param callback a means of reporting request status asynchronously
11 | */
12 | void handle(String remotePath, TransferCallback callback);
13 | }
14 |
--------------------------------------------------------------------------------
/s3test/src/main/java/net/ndolgov/s3test/FileUploader.java:
--------------------------------------------------------------------------------
1 | package net.ndolgov.s3test;
2 |
3 | import java.io.File;
4 |
5 | /**
6 | * Upload a file to S3 storage
7 | */
8 | public interface FileUploader {
9 | /**
10 | * @param localFile local file to upload
11 | * @param remotePath relative path (counting from some assumed root) in S3 storage
12 | * @param callback observer to notify about upload status
13 | */
14 | void upload(File localFile, String remotePath, TransferCallback callback);
15 | }
16 |
--------------------------------------------------------------------------------
/s3test/src/main/java/net/ndolgov/s3test/S3ClientMBean.java:
--------------------------------------------------------------------------------
1 | package net.ndolgov.s3test;
2 |
3 | /**
4 | * S3 client JMX end point
5 | */
6 | public interface S3ClientMBean {
7 | /**
8 | * @return S3 bucket
9 | */
10 | String getBucket();
11 |
12 | /**
13 | * @return a prefix path automatically inserted between the bucket name and a relative path to a file
14 | */
15 | String getPathPrefix();
16 | }
17 |
--------------------------------------------------------------------------------
/s3test/src/main/java/net/ndolgov/s3test/S3Destination.java:
--------------------------------------------------------------------------------
1 | package net.ndolgov.s3test;
2 |
3 | /**
4 | * A means of having a common S3 parent path for multiple files
5 | */
6 | public interface S3Destination {
7 | /** Our S3 path delimiter */
8 | String DELIMITER = "/";
9 |
10 | /**
11 | * @param namespace AWS namespace
12 | * @return namespace-based prefix to restrict S3 keys for this destination
13 | */
14 | String prefix(String namespace);
15 | }
16 |
--------------------------------------------------------------------------------
/s3test/src/main/java/net/ndolgov/s3test/S3Downloader.java:
--------------------------------------------------------------------------------
1 | package net.ndolgov.s3test;
2 |
3 | import com.amazonaws.services.s3.transfer.Transfer;
4 |
5 | import java.io.File;
6 |
7 | /**
8 | * Download a file from a given "subdirectory" of S3 bucket
9 | */
10 | public final class S3Downloader implements FileDownloader {
11 | private final S3FileTransferClient client;
12 |
13 | public S3Downloader(S3FileTransferClient client) {
14 | this.client = client;
15 | }
16 |
17 | @Override
18 | public void download(File localFile, String remotePath, TransferCallback callback) {
19 | final S3TransferProgressListener listener = new S3TransferProgressListener(remotePath, localFile.getName(), callback);
20 | final Transfer download = client.download(remotePath, localFile, listener);
21 | listener.listenTo(download);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/s3test/src/main/java/net/ndolgov/s3test/S3FileTransferClient.java:
--------------------------------------------------------------------------------
1 | package net.ndolgov.s3test;
2 |
3 | import com.amazonaws.event.ProgressListener;
4 | import com.amazonaws.services.s3.transfer.Download;
5 | import com.amazonaws.services.s3.transfer.Upload;
6 |
7 | import java.io.File;
8 | import java.util.function.Function;
9 |
10 | /**
11 | * Asynchronous transfer operations on files in one S3 bucket
12 | */
13 | public interface S3FileTransferClient {
14 | /**
15 | *
16 | * @param src absolute source file path
17 | * @param key a path in the bucket
18 | * @param listener continuation to run when a final transfer state is reached
19 | * @return Upload request handle
20 | */
21 | Upload upload(File src, String key, ProgressListener listener);
22 |
23 | /**
24 | *
25 | * @param key a path in the bucket
26 | * @param dest local file to download to
27 | * @param listener continuation to run when a final transfer state is reached
28 | * @return Download request handle
29 | */
30 | Download download(String key, File dest, ProgressListener listener);
31 |
32 | /**
33 | * List all object located under the root
34 | * @param consumer consumer of found object keys
35 | */
36 | void list(Function consumer);
37 |
38 | /**
39 | * List all object located directly under the root or a given subdirectory
40 | * @param consumer consumer of found object keys
41 | * @param subDir subdirectory under root (e.g. "some_path") or null to search under root
42 | */
43 | void listOneLevel(Function consumer, String subDir);
44 |
45 | /**
46 | * @param key a path in the bucket
47 | * @return true if there is an object with the given key
48 | */
49 | boolean exists(String key);
50 |
51 | /**
52 | * @param objectKey object key (i.e. absolute path in the bucket)
53 | * @return true if there the object with the given key was deleted
54 | */
55 | boolean delete(String objectKey);
56 | }
57 |
--------------------------------------------------------------------------------
/s3test/src/main/java/net/ndolgov/s3test/S3Uploader.java:
--------------------------------------------------------------------------------
1 | package net.ndolgov.s3test;
2 |
3 | import com.amazonaws.services.s3.transfer.Transfer;
4 |
5 | import java.io.File;
6 |
7 | /**
8 | * Upload a file to a subdirectory of S3 bucket
9 | */
10 | public final class S3Uploader implements FileUploader {
11 | private final S3FileTransferClient client;
12 |
13 | public S3Uploader(S3FileTransferClient client) {
14 | this.client = client;
15 | }
16 |
17 | @Override
18 | public void upload(File localFile, String remotePath, TransferCallback callback) {
19 | final S3TransferProgressListener listener = new S3TransferProgressListener(localFile.getName(), remotePath, callback);
20 | final Transfer upload = client.upload(localFile, remotePath, listener);
21 | listener.listenTo(upload);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/s3test/src/main/java/net/ndolgov/s3test/TransferCallback.java:
--------------------------------------------------------------------------------
1 | package net.ndolgov.s3test;
2 |
3 | /**
4 | * A means of tracking asynchronous file transfer completion
5 | */
6 | public interface TransferCallback {
7 | /**
8 | * Notify about successful file transfer
9 | */
10 | void onSuccess();
11 |
12 | /**
13 | * Notify about file transfer failure
14 | * @param message error message
15 | */
16 | void onFailure(String message);
17 |
18 | /**
19 | * For cases when nobody cares about request status
20 | */
21 | TransferCallback IGNORED = new TransferCallback() {
22 | public void onSuccess() {
23 | }
24 |
25 | public void onFailure(String message) {
26 | }
27 | };
28 | }
29 |
--------------------------------------------------------------------------------
/s3test/src/test/resources/log4j.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/scalapbtest/build.sbt:
--------------------------------------------------------------------------------
1 | import scalapb.compiler.Version.scalapbVersion
2 |
3 | val grpc_version = "1.8.0"
4 | val slf4j_version = "1.6.4"
5 | val scalatest_version = "3.0.4"
6 | val scala_version = "2.12.3"
7 |
8 | val group_id = "net.ndolgov"
9 | val project_version = "1.0.0-SNAPSHOT"
10 |
11 | val scalapbtest_grpc_id = "scalapbtest-grpc"
12 | val scalapbtest_grpc = Project(id = scalapbtest_grpc_id, base = file(scalapbtest_grpc_id)).
13 | settings(
14 | name := scalapbtest_grpc_id,
15 | organization := group_id,
16 | version := project_version
17 | ).
18 | settings(
19 | PB.protoSources in Compile := Seq(sourceDirectory.value / "main/proto"),
20 | PB.targets in Compile := Seq(scalapb.gen() -> (sourceManaged in Compile).value)
21 | ).
22 | settings(
23 | libraryDependencies ++= Seq(
24 | "org.scalatest" %% "scalatest" % scalatest_version % Test,
25 | "com.thesamet.scalapb" %% "scalapb-runtime" % scalapbVersion % "protobuf",
26 | "com.thesamet.scalapb" %% "scalapb-runtime-grpc" % scalapbVersion,
27 | "io.grpc" % "grpc-netty" % grpc_version,
28 | "org.slf4j" % "slf4j-api" % slf4j_version,
29 | "org.slf4j" % "slf4j-log4j12" % slf4j_version,
30 | )
31 | )
32 |
33 | val scalapbtest_root_id = "scalapbtest-root"
34 | val root = Project(id = scalapbtest_root_id, base = file(".") ).
35 | settings(
36 | scalaVersion := scala_version,
37 | scalacOptions ++= Seq("-deprecation", "-Xfatal-warnings")
38 | ).
39 | settings(
40 | name := scalapbtest_root_id,
41 | organization := group_id,
42 | version := project_version
43 | ).
44 | settings(
45 | resolvers += "Local Maven" at Path.userHome.asFile.toURI.toURL + ".m2/repository"
46 | ).
47 | settings(
48 | packageBin := { new File("") },
49 | packageSrc := { new File("") }
50 | ).
51 | aggregate(scalapbtest_grpc)
52 |
53 |
54 |
--------------------------------------------------------------------------------
/scalapbtest/project/build.properties:
--------------------------------------------------------------------------------
1 | sbt.version=1.0.3
2 |
--------------------------------------------------------------------------------
/scalapbtest/project/scalapb.sbt:
--------------------------------------------------------------------------------
1 | addSbtPlugin("com.thesamet" % "sbt-protoc" % "0.99.13")
2 |
3 | libraryDependencies += "com.thesamet.scalapb" %% "compilerplugin" % "0.7.0-rc6"
--------------------------------------------------------------------------------
/scalapbtest/scalapbtest-grpc/src/main/proto/testsvcA.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | package scalapbtest;
4 |
5 | option java_multiple_files = false;
6 | option java_package = "net.ndolgov.scalapbtest.api";
7 | option java_outer_classname = "TestServiceAProto";
8 | option objc_class_prefix = "TS1P";
9 |
10 |
11 | service TestServiceA {
12 | rpc Process (TestRequestA) returns (TestResponseA) {}
13 | }
14 |
15 | message TestRequestA {
16 | int64 requestId = 1;
17 | }
18 |
19 | message TestResponseA {
20 | bool success = 1;
21 | int64 requestId = 2;
22 | string result = 3;
23 | }
--------------------------------------------------------------------------------
/scalapbtest/scalapbtest-grpc/src/main/proto/testsvcB.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | package scalapbtest;
4 |
5 | option java_multiple_files = false;
6 | option java_package = "net.ndolgov.scalapbtest.api";
7 | option java_outer_classname = "TestServiceBProto";
8 | option objc_class_prefix = "TS2P";
9 |
10 |
11 | service TestServiceB {
12 | rpc Process (TestRequestB) returns (TestResponseB) {}
13 | }
14 |
15 | message TestRequestB {
16 | int64 requestId = 1;
17 | }
18 |
19 | message TestResponseB {
20 | bool success = 1;
21 | int64 requestId = 2;
22 | string result = 3;
23 | }
--------------------------------------------------------------------------------
/scalapbtest/scalapbtest-grpc/src/main/scala/net/ndolgov/scalapbtest/GrpcClient.scala:
--------------------------------------------------------------------------------
1 | package net.ndolgov.scalapbtest
2 |
3 | import java.util.concurrent.{Executor, TimeUnit}
4 |
5 | import io.grpc.stub.AbstractStub
6 | import io.grpc.{ManagedChannel, ManagedChannelBuilder}
7 | import scala.concurrent.ExecutionContext
8 |
9 | trait GrpcClient {
10 | def stop(): Unit
11 |
12 | def createClient[A <: AbstractStub[A]](f: (ManagedChannel) => A) : A
13 |
14 | def executionContext() : ExecutionContext
15 | }
16 |
17 | private final class GrpcClientImpl(channel: ManagedChannel, ec: ExecutionContext) extends GrpcClient {
18 | override def createClient[A <: AbstractStub[A]](factory: (ManagedChannel) => A) : A = {
19 | factory.apply(channel)
20 | }
21 |
22 | override def stop(): Unit = channel.shutdown().awaitTermination(5000, TimeUnit.MILLISECONDS)
23 |
24 | override def executionContext(): ExecutionContext = ec
25 | }
26 |
27 | /** Create GRPC transport to a given "host:port" destination */
28 | object GrpcClient {
29 | def apply(hostname: String, port: Int, executor: Executor) : GrpcClient = {
30 | new GrpcClientImpl(
31 | ManagedChannelBuilder.forAddress(hostname, port).usePlaintext(true).executor(executor).build(),
32 | ExecutionContext.fromExecutor(executor))
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/scalapbtest/scalapbtest-grpc/src/main/scala/net/ndolgov/scalapbtest/GrpcServer.scala:
--------------------------------------------------------------------------------
1 | package net.ndolgov.scalapbtest
2 |
3 | import java.net.InetSocketAddress
4 | import java.util.concurrent.TimeUnit
5 |
6 | import io.grpc.netty.NettyServerBuilder
7 | import io.grpc.{Server, ServerServiceDefinition}
8 | import org.slf4j.LoggerFactory
9 |
10 | import scala.concurrent.ExecutionContext
11 |
12 | trait GrpcServer {
13 | def start(): Unit
14 |
15 | def stop(): Unit
16 | }
17 |
18 | private final class GrpcServerImpl(server: Server, port: Int) extends GrpcServer {
19 | private val logger = LoggerFactory.getLogger(classOf[GrpcServer])
20 |
21 | override def start(): Unit = {
22 | try {
23 | server.start
24 | logger.info("Started " + this)
25 | } catch {
26 | case e: Exception =>
27 | throw new RuntimeException("Could not start server", e)
28 | }
29 | }
30 |
31 | override def stop(): Unit = {
32 | try {
33 | logger.info("Stopping " + this)
34 | server.shutdown().awaitTermination(5, TimeUnit.SECONDS)
35 | logger.info("Stopped " + this)
36 | } catch {
37 | case e: Exception =>
38 | logger.warn("Interrupted while shutting down " + this)
39 | }
40 | }
41 |
42 | override def toString: String = "{GrpcServer:port=" + port + "}"
43 | }
44 |
45 | /** Create a GRPC server for given request handlers and bind it to provided "host:port" */
46 | object GrpcServer {
47 | def apply(hostname: String, port: Int, toServices: (ExecutionContext) => Seq[ServerServiceDefinition])
48 | (implicit ec: ExecutionContext) : GrpcServer = {
49 | val builder: NettyServerBuilder = NettyServerBuilder.forAddress(new InetSocketAddress(hostname, port))
50 | toServices.apply(ec).foreach(definition => builder.addService(definition))
51 |
52 | new GrpcServerImpl(builder.build(), port)
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/scalapbtest/scalapbtest-grpc/src/main/scala/net/ndolgov/scalapbtest/TestServiceAImpl.scala:
--------------------------------------------------------------------------------
1 | package net.ndolgov.scalapbtest
2 |
3 | import net.ndolgov.scalapbtest.api.testsvcA.{TestRequestA, TestResponseA}
4 | import net.ndolgov.scalapbtest.api.testsvcA.TestServiceAGrpc.TestServiceA
5 | import org.slf4j.LoggerFactory
6 |
7 | import scala.concurrent.{ExecutionContext, Future}
8 |
9 | class TestServiceAImpl(implicit ec: ExecutionContext) extends TestServiceA {
10 | private val logger = LoggerFactory.getLogger(classOf[TestServiceAImpl])
11 |
12 | override def process(request: TestRequestA): Future[TestResponseA] = {
13 | Future {
14 | logger.info("Computing result"); // todo this is where actual time-consuming processing would be
15 | TestResponseA(success = true, request.requestId, "RESULT")
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/scalapbtest/scalapbtest-grpc/src/main/scala/net/ndolgov/scalapbtest/TestServiceBImpl.scala:
--------------------------------------------------------------------------------
1 | package net.ndolgov.scalapbtest
2 |
3 | import net.ndolgov.scalapbtest.api.testsvcB.{TestRequestB, TestResponseB}
4 | import net.ndolgov.scalapbtest.api.testsvcB.TestServiceBGrpc.TestServiceB
5 | import org.slf4j.LoggerFactory
6 |
7 | import scala.concurrent.{ExecutionContext, Future}
8 |
9 | class TestServiceBImpl(implicit ec: ExecutionContext) extends TestServiceB {
10 | private val logger = LoggerFactory.getLogger(classOf[TestServiceBImpl])
11 |
12 | override def process(request: TestRequestB): Future[TestResponseB] = {
13 | Future {
14 | logger.info("Computing result"); // todo this is where actual time-consuming processing would be
15 | TestResponseB(success = true, request.requestId, "RESULT")
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/scalapbtest/scalapbtest-grpc/src/test/resources/log4j.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/sparkdatasourcetest/src/main/resources/META-INF/services/org.apache.spark.sql.sources.DataSourceRegister:
--------------------------------------------------------------------------------
1 | net.ndolgov.sparkdatasourcetest.sql.DefaultSource
2 |
--------------------------------------------------------------------------------
/sparkdatasourcetest/src/main/scala/net/ndolgov/sparkdatasourcetest/lucene/LuceneFieldReader.scala:
--------------------------------------------------------------------------------
1 | package net.ndolgov.sparkdatasourcetest.lucene
2 |
3 | import net.ndolgov.sparkdatasourcetest.sql.LuceneSchema
4 | import org.apache.spark.sql.sources.Filter
5 | import org.apache.spark.sql.types.{DoubleType, LongType, StructField}
6 |
7 | /**
8 | * Read a field value from the current Lucene document to the current Spark Row
9 | */
10 | trait LuceneFieldReader {
11 | def readLong(value : Long) : Unit = throw new UnsupportedOperationException
12 |
13 | def readDouble(value : Double) : Unit = throw new UnsupportedOperationException
14 | }
15 |
16 | object LuceneFieldReader {
17 |
18 | // todo retrieve fields for (filters - columns) attrs?
19 | def apply(columns: Seq[String], filters: Array[Filter], schema: LuceneSchema, row : Array[Any]) : Array[LuceneFieldReader] = {
20 | val readers : Array[LuceneFieldReader] = Array.ofDim[LuceneFieldReader](columns.length)
21 |
22 | var schemaIndex : Int = 0
23 | var outputIndex : Int = 0
24 | for (field <- schema.sparkSchema()) {
25 | if (columns.contains(field.name)) {
26 | readers(outputIndex) = apply(schema, schemaIndex, outputIndex, row)
27 | outputIndex += 1
28 | }
29 | schemaIndex += 1
30 | }
31 |
32 | readers
33 | }
34 |
35 | private def apply(schema: LuceneSchema, schemaIndex: Int, outputIndex : Int, row: Array[Any]): LuceneFieldReader = {
36 | val sparkField: StructField = schema.sparkField(schemaIndex)
37 |
38 | sparkField.dataType match {
39 | case LongType => new LongReader(outputIndex, row)
40 | case DoubleType => new DoubleReader(outputIndex, row)
41 | case _ => throw new IllegalArgumentException("Unsupported field type: " + sparkField.dataType);
42 | }
43 | }
44 |
45 | private final class LongReader(index : Int, row: Array[Any]) extends LuceneFieldReader {
46 | override def readLong(value : Long) : Unit = {
47 | row(index) = value
48 | }
49 | }
50 |
51 | private final class DoubleReader(index : Int, row: Array[Any]) extends LuceneFieldReader {
52 | override def readDouble(value : Double) : Unit = {
53 | row(index) = value
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/sparkdatasourcetest/src/main/scala/net/ndolgov/sparkdatasourcetest/lucene/LuceneFieldWriter.scala:
--------------------------------------------------------------------------------
1 | package net.ndolgov.sparkdatasourcetest.lucene
2 |
3 | import net.ndolgov.sparkdatasourcetest.sql.LuceneSchema
4 | import org.apache.lucene.document.Document
5 | import org.apache.spark.sql.Row
6 | import org.apache.spark.sql.types.{DoubleType, LongType}
7 |
8 | /**
9 | * Write a Spark Row to a Lucene index as a document
10 | */
11 | trait LuceneFieldWriter {
12 | def write(row : Row)
13 | }
14 |
15 | object LuceneFieldWriter {
16 | /**
17 | * @param schema document schema
18 | * @param document document instance to reuse
19 | * @return a new writer
20 | */
21 | def apply(schema : LuceneSchema, document : Document) : LuceneFieldWriter = {
22 | val writers : Array[LuceneFieldWriter] = Array.ofDim[LuceneFieldWriter](schema.size)
23 |
24 | var index : Int = 0
25 | for (field <- schema.sparkSchema()) {
26 | writers(index) = apply(schema, index, document)
27 | index += 1
28 | }
29 |
30 | new RowWriter(writers, document)
31 | }
32 |
33 | private def apply(schema : LuceneSchema, index : Int, document : Document) : LuceneFieldWriter = {
34 | val field : LuceneDocumentField = LuceneFieldFactory(schema, index)
35 |
36 | schema.sparkFieldType(index) match {
37 | case LongType => new LongFieldWriter(index, field, document)
38 | case DoubleType => new DoubleFieldWriter(index, field, document)
39 | case _ => throw new IllegalArgumentException("Unsupported field type: " + field);
40 | }
41 | }
42 |
43 | private final class RowWriter(writers : Seq[LuceneFieldWriter], document : Document) extends LuceneFieldWriter {
44 | override def write(row: Row): Unit = {
45 | writers.foreach((writer: LuceneFieldWriter) => writer.write(row))
46 | }
47 | }
48 |
49 | private final class LongFieldWriter(index: Int, field: LuceneDocumentField, document : Document) extends LuceneFieldWriter {
50 | override def write(row: Row): Unit = {
51 | if (!row.isNullAt(index)) {
52 | field.addTo(document)
53 | field.setLongValue(row.getLong(index))
54 | }
55 | }
56 | }
57 |
58 | private final class DoubleFieldWriter(index: Int, field: LuceneDocumentField, document: Document) extends LuceneFieldWriter {
59 | override def write(row: Row): Unit = {
60 | if (!row.isNullAt(index)) {
61 | field.addTo(document)
62 | field.setDoubleValue(row.getDouble(index))
63 | }
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/sparkdatasourcetest/src/main/scala/net/ndolgov/sparkdatasourcetest/lucene/StoredFieldVisitorQuery.scala:
--------------------------------------------------------------------------------
1 | package net.ndolgov.sparkdatasourcetest.lucene
2 |
3 | import java.io.IOException
4 |
5 | import org.apache.lucene.index.{LeafReaderContext, StoredFieldVisitor}
6 | import org.apache.lucene.queries.{CustomScoreProvider, CustomScoreQuery}
7 | import org.apache.lucene.search.Query
8 |
9 | /**
10 | * Custom Lucene query using a visitor to collect values from matching Lucene documents
11 | */
12 | final class StoredFieldVisitorQuery(val subQuery: Query, val processor : LuceneDocumentProcessor) extends CustomScoreQuery(subQuery) {
13 | @throws[IOException]
14 | override def getCustomScoreProvider(context : LeafReaderContext) : CustomScoreProvider = {
15 | new Provider(context, processor)
16 | }
17 | }
18 |
19 | trait LuceneDocumentProcessor {
20 | /** @return Lucene field visitor to apply to all matching documents */
21 | def visitor() : StoredFieldVisitor
22 |
23 | /** Process Lucene document fields gathered by the [[visitor]] from the last seen document */
24 | def onDocument()
25 | }
26 |
27 | @Override
28 | private final class Provider(val leafCtx : LeafReaderContext, val processor : LuceneDocumentProcessor) extends CustomScoreProvider(leafCtx) {
29 | val DEFAULT_SCORE: Int = 0
30 | val visitor : StoredFieldVisitor = processor.visitor()
31 |
32 | override def customScore(docId: Int, subQueryScore: Float, valSrcScore: Float): Float = {
33 | leafCtx.reader().document(docId, visitor)
34 | processor.onDocument()
35 |
36 | DEFAULT_SCORE
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/sparkdatasourcetest/src/main/scala/net/ndolgov/sparkdatasourcetest/sql/LuceneRelation.scala:
--------------------------------------------------------------------------------
1 | package net.ndolgov.sparkdatasourcetest.sql
2 |
3 | import org.apache.spark.rdd.RDD
4 | import org.apache.spark.sql.sources.{BaseRelation, InsertableRelation, Filter, PrunedFilteredScan}
5 | import org.apache.spark.sql.types.StructType
6 | import org.apache.spark.sql.{Dataset, Row, SaveMode, SQLContext}
7 |
8 | /**
9 | * Searchable Lucene-based data storage of some application-specific scope (e.g. corresponding to a particular version of
10 | * a tenant's dataset; the client would be responsible for mapping (tenantId,version) pair to a relationDir)
11 | *
12 | * @param relationDir root location for all storage partitions
13 | * @param userSchema user-provided schema
14 | * @param sqlContext Spark context
15 | */
16 | class LuceneRelation(relationDir: String, userSchema: LuceneSchema = null)(@transient val sqlContext: SQLContext)
17 | extends BaseRelation with PrunedFilteredScan with InsertableRelation {
18 |
19 | private val rddSchema : LuceneSchema = getSchema
20 |
21 | private val rdd : LuceneRDD = LuceneRDD(sqlContext.sparkContext, relationDir, rddSchema)
22 |
23 | private def getSchema: LuceneSchema = {
24 | if (userSchema == null) {
25 | LuceneSchema.open(LuceneRDD.schemaFilePath(relationDir).toUri.toString)
26 | } else {
27 | userSchema
28 | }
29 | }
30 |
31 | override def schema: StructType = rddSchema.sparkSchema()
32 |
33 | override def unhandledFilters(filters: Array[Filter]): Array[Filter] = rddSchema.unhandledFilters(filters)
34 |
35 | override def buildScan(requiredColumns: Array[String], filters: Array[Filter]): RDD[Row] = {
36 | rdd.pruneAndFilter(requiredColumns, filters)
37 | }
38 |
39 | override def insert(df: Dataset[Row], overwrite: Boolean): Unit = {
40 | df.
41 | write.
42 | format(LuceneDataSource.SHORT_NAME).
43 | option(LuceneDataSource.PATH, relationDir).
44 | mode(if (overwrite) SaveMode.Overwrite else SaveMode.Append).
45 | save()
46 | }
47 | }
48 |
49 |
--------------------------------------------------------------------------------
/sparkdatasourcetest/src/main/scala/net/ndolgov/sparkdatasourcetest/sql/package.scala:
--------------------------------------------------------------------------------
1 | package net.ndolgov.sparkdatasourcetest
2 |
3 | import org.apache.spark.sql.{Dataset, Row, SQLContext}
4 |
5 | /**
6 | * Extend Spark API with LuceneRDD support
7 | */
8 | package object sql {
9 |
10 | /**
11 | * Extend SQLContext API
12 | */
13 | implicit class LuceneSqlContext(sqlContext : SQLContext) {
14 | /**
15 | * Load data from the Lucene-based storage at a given location to a data frame
16 | * @param path data storage location
17 | */
18 | def luceneTable(path: String): Unit = {
19 | sqlContext.baseRelationToDataFrame(new LuceneRelation(path)(sqlContext))
20 | }
21 | }
22 |
23 | /**
24 | * Extend DataFrame API
25 | */
26 | implicit class LuceneDataFrame(df: Dataset[Row]) {
27 | /**
28 | * Save a data frame to the Lucene-based storage
29 | * @param path storage location for the given dataset
30 | * @param luceneSchema data schema
31 | */
32 | def saveAsLuceneIndex(path : String, luceneSchema : String): Unit = {
33 | LuceneRDD(df, luceneSchema).save(path)
34 | }
35 |
36 | /**
37 | * @return the number of rows in the data frame counted in a more efficient way than the default one (that requires
38 | * the entire dataset to be moved to the driver before counting)
39 | */
40 | def countRows(): Long = {
41 | LuceneRDD(df).count()
42 | }
43 | }
44 | }
--------------------------------------------------------------------------------
/sparkdatasourcetest/src/test/scala/net/ndolgov/sparkdatasourcetest/sql/LuceneSchemaTestSuit.scala:
--------------------------------------------------------------------------------
1 | package net.ndolgov.sparkdatasourcetest.sql
2 |
3 | import java.io.File
4 |
5 | import org.apache.spark.sql.types.StructType
6 | import org.scalatest.{Assertions, FlatSpec}
7 |
8 | final class LuceneSchemaTestSuit extends FlatSpec with Assertions {
9 | "A schema written to a file" should "be read back" in {
10 | val sparkSchema : StructType = LuceneDataSourceTestEnv.defaultSchema
11 | val luceneSchema : Array[FieldType] = Array[FieldType](FieldType.INDEXED, FieldType.QUERYABLE, FieldType.STORED)
12 | val original = LuceneSchema(sparkSchema, FieldType.toString(luceneSchema))
13 |
14 | val filePath: String = "target/testschema" + System.currentTimeMillis() + ".txt"
15 | LuceneSchema.save(original, filePath)
16 |
17 | val retrieved = LuceneSchema.open(filePath)
18 |
19 | assert(retrieved.size == 3)
20 | for (i <- 0 to 2) {
21 | assert(retrieved.sparkFieldType(i) == sparkSchema.fields(i).dataType)
22 | assert(retrieved.luceneFieldType(i) == luceneSchema(i))
23 | }
24 |
25 | new File(filePath).delete()
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/sparkdatasourcev2test/README.md:
--------------------------------------------------------------------------------
1 | #### Spark Data Source V2 example
2 |
3 | This example is intended to demonstrate typical interactions with the Data Source API V2.
4 |
5 | Lucene indices are used for persistent storage to make the challenge of integration realistic while still simple.
6 |
7 | The local file system is currently used to simplify the code.
8 |
9 | When writing data
10 | * store each partition as an individual Lucene index in a subdirectory of the same local directory shared by all executors.
11 |
12 | When reading data
13 | * push predicates down to the Lucene engine as a [BooleanQuery](https://lucene.apache.org/core/7_2_1/core/org/apache/lucene/search/BooleanQuery.html)
14 | * prune columns to retrieve only explicitly requested Lucene document fields
15 |
16 | ##### Running locally
17 |
18 | * ```mvn clean test``` to build the data source and execute a few test queries against it
--------------------------------------------------------------------------------
/sparkdatasourcev2test/src/main/resources/META-INF/services/org.apache.spark.sql.sources.DataSourceRegister:
--------------------------------------------------------------------------------
1 | net.ndolgov.sparkdatasourcetest.connector.LuceneDataSourceV2
2 |
--------------------------------------------------------------------------------
/sparkdatasourcev2test/src/main/scala/net/ndolgov/sparkdatasourcetest/connector/FilePaths.scala:
--------------------------------------------------------------------------------
1 | package net.ndolgov.sparkdatasourcetest.connector
2 |
3 | import net.ndolgov.sparkdatasourcetest.lucene.LuceneSchema
4 | import org.apache.hadoop.fs.Path
5 |
6 | object FilePaths {
7 | private val PART : String = "/part-"
8 |
9 | /**
10 | * @param rddDir RDD root path
11 | * @param index RDD partition index
12 | * @return RDD partition path
13 | */
14 | def partitionPath(rddDir: String, index : Long) : Path = new Path(partitionDir(rddDir, index))
15 |
16 | /**
17 | * @param rddDir RDD root path
18 | * @param index RDD partition index
19 | * @return RDD partition path
20 | */
21 | def partitionDir(rddDir: String, index : Long) : String = rddDir + PART + "%05d".format(index)
22 |
23 | /**
24 | * @param rddDir RDD root path
25 | * @return schema file path
26 | */
27 | def schemaFilePath(rddDir: String) : Path = new Path(rddDir + "/" + LuceneSchema.SCHEMA_FILE_NAME)
28 | }
29 |
--------------------------------------------------------------------------------
/sparkdatasourcev2test/src/main/scala/net/ndolgov/sparkdatasourcetest/connector/FileUtils.scala:
--------------------------------------------------------------------------------
1 | package net.ndolgov.sparkdatasourcetest.connector
2 |
3 | import java.io.{File, FileFilter}
4 |
5 | /** For simplicity, avoid using apache commons-style dependencies */
6 | object FileUtils {
7 | def mkDir(path: String): File = {
8 | val dir = new File(path)
9 | if (dir.mkdir()) dir else throw new RuntimeException("Could not create dir: " + path)
10 | }
11 |
12 | def deleteRecursively(file: File): Boolean = {
13 | if (file.isDirectory) {
14 | file.listFiles().forall(file => deleteRecursively(file))
15 | } else {
16 | if (file.exists) file.delete() else true
17 | }
18 | }
19 |
20 | def listSubDirs(path: String): Array[String] = {
21 | val dir = new File(path)
22 |
23 | if (dir.isDirectory) {
24 | dir.listFiles(new FileFilter {
25 | override def accept(subDir: File): Boolean = subDir.getName.startsWith("part-")
26 | }).map((subDir: File) => subDir.getAbsolutePath)
27 | } else {
28 | throw new RuntimeException("Not a dir: " + path)
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/sparkdatasourcev2test/src/main/scala/net/ndolgov/sparkdatasourcetest/connector/LuceneDataSourceV2.scala:
--------------------------------------------------------------------------------
1 | package net.ndolgov.sparkdatasourcetest.connector
2 |
3 | import java.util.Optional
4 |
5 | import net.ndolgov.sparkdatasourcetest.lucene.LuceneSchema
6 | import org.apache.spark.sql.SaveMode
7 | import org.apache.spark.sql.sources.DataSourceRegister
8 | import org.apache.spark.sql.sources.v2.reader.DataSourceReader
9 | import org.apache.spark.sql.sources.v2.writer.DataSourceWriter
10 | import org.apache.spark.sql.sources.v2.{DataSourceV2, DataSourceOptions, ReadSupport, WriteSupport}
11 | import org.apache.spark.sql.types.StructType
12 |
13 | final class LuceneDataSourceV2 extends DataSourceV2 with ReadSupport with WriteSupport with DataSourceRegister {
14 | import LuceneDataSourceV2._
15 |
16 | override def createReader(options: DataSourceOptions): DataSourceReader =
17 | LuceneDataSourceV2Reader(path(options))
18 |
19 | override def createWriter(jobId: String, schema: StructType, mode: SaveMode, options: DataSourceOptions): Optional[DataSourceWriter] =
20 | Optional.of(
21 | LuceneDataSourceV2Writer(
22 | path(options),
23 | LuceneSchema(schema, luceneSchema(options))))
24 |
25 | override def shortName(): String = SHORT_NAME
26 | }
27 |
28 | object LuceneDataSourceV2 {
29 | /** alternative/long format name for when "META-INF.services" trick is not used */
30 | val FORMAT : String = "net.ndolgov.sparkdatasourcev2test.sql"
31 |
32 | /** The default/short format name, usage example: "df.write.format(LuceneDataSource.SHORT_NAME)" */
33 | val SHORT_NAME : String = "LuceneDataSourceV2"
34 |
35 | /** The root directory (presumably in a DFS) for Lucene data storage item. IRL would be based on tenantId/data version/etc */
36 | val PATH : String = "path"
37 |
38 | /** The Lucene schema that describes which columns are indexed and/or stored */
39 | val LUCENE_SCHEMA : String = "lucene.schema"
40 |
41 | private def path(options: DataSourceOptions): String = {
42 | stringArg(PATH, options)
43 | }
44 |
45 | private def luceneSchema(options: DataSourceOptions): String = {
46 | stringArg(LUCENE_SCHEMA, options)
47 | }
48 |
49 | private def stringArg(key : String, options: DataSourceOptions): String = {
50 | val mayBy = options.get(key)
51 | if (mayBy.isPresent) mayBy.get() else throw new IllegalArgumentException("Option is missing: " + key)
52 | }
53 | }
54 |
55 |
--------------------------------------------------------------------------------
/sparkdatasourcev2test/src/main/scala/net/ndolgov/sparkdatasourcetest/connector/LuceneDataSourceV2Writer.scala:
--------------------------------------------------------------------------------
1 | package net.ndolgov.sparkdatasourcetest.connector
2 |
3 | import net.ndolgov.sparkdatasourcetest.lucene.{LuceneIndexWriter, LuceneSchema}
4 | import org.apache.spark.sql.catalyst.InternalRow
5 | import org.apache.spark.sql.sources.v2.writer.{DataSourceWriter, DataWriter, DataWriterFactory, WriterCommitMessage}
6 |
7 | /** Lucene data source write path */
8 | private final class LuceneDataSourceV2Writer(path: String, schema: LuceneSchema) extends DataSourceWriter {
9 | override def createWriterFactory(): DataWriterFactory[InternalRow] = {
10 | FileUtils.mkDir(path)
11 | new LuceneDataWriterFactory(path, schema)
12 | }
13 |
14 | override def commit(messages: Array[WriterCommitMessage]): Unit = {
15 | // the same schema for all the partitions
16 | LuceneSchema.save(schema, FilePaths.schemaFilePath(path).toUri.toString)
17 | }
18 |
19 | override def abort(messages: Array[WriterCommitMessage]): Unit = {
20 | // todo delete partition dirs?
21 | }
22 | }
23 |
24 | private final class LuceneDataWriterFactory(rddDir: String, schema: LuceneSchema) extends DataWriterFactory[InternalRow] {
25 | override def createDataWriter(partitionId: Int, taskId: Long, epochId: Long): DataWriter[InternalRow] =
26 | new LuceneDataWriter(FilePaths.partitionDir(rddDir, partitionId), schema) //todo taskId in file paths
27 | }
28 |
29 | private final class LuceneDataWriter(partitionDir: String, schema: LuceneSchema) extends DataWriter[InternalRow] {
30 | private val rddDir = FileUtils.mkDir(partitionDir)
31 |
32 | private val writer = LuceneIndexWriter(partitionDir, schema)
33 |
34 | override def write(row: InternalRow): Unit = writer.write(row)
35 |
36 | override def commit(): WriterCommitMessage = {
37 | close()
38 | LuceneWriterCommitMessage()
39 | }
40 |
41 | override def abort(): Unit = {
42 | close()
43 | FileUtils.deleteRecursively(rddDir)
44 | }
45 |
46 | private def close(): Unit = writer.close()
47 | }
48 |
49 | case class LuceneWriterCommitMessage() extends WriterCommitMessage
50 |
51 | object LuceneDataSourceV2Writer {
52 | def apply(path: String, schema: LuceneSchema) : DataSourceWriter = new LuceneDataSourceV2Writer(path, schema)
53 | }
54 |
--------------------------------------------------------------------------------
/sparkdatasourcev2test/src/main/scala/net/ndolgov/sparkdatasourcetest/lucene/LuceneFieldReader.scala:
--------------------------------------------------------------------------------
1 | package net.ndolgov.sparkdatasourcetest.lucene
2 |
3 | import org.apache.spark.sql.sources.Filter
4 | import org.apache.spark.sql.types.{DoubleType, LongType, StructField}
5 |
6 | /**
7 | * Read a field value from the current Lucene document to the current Spark Row
8 | */
9 | trait LuceneFieldReader {
10 | def readLong(value : Long) : Unit = throw new UnsupportedOperationException
11 |
12 | def readDouble(value : Double) : Unit = throw new UnsupportedOperationException
13 | }
14 |
15 | object LuceneFieldReader {
16 |
17 | // todo retrieve fields for (filters - columns) attrs?
18 | def apply(columns: Seq[String], filters: Array[Filter], schema: LuceneSchema, row : Array[Any]) : Array[LuceneFieldReader] = {
19 | val readers : Array[LuceneFieldReader] = Array.ofDim[LuceneFieldReader](columns.length)
20 |
21 | var schemaIndex : Int = 0
22 | var outputIndex : Int = 0
23 | for (field <- schema.sparkSchema()) {
24 | if (columns.contains(field.name)) {
25 | readers(outputIndex) = apply(field, outputIndex, row)
26 | outputIndex += 1
27 | }
28 | schemaIndex += 1
29 | }
30 |
31 | readers
32 | }
33 |
34 | private def apply(sparkField: StructField, outputIndex : Int, row: Array[Any]): LuceneFieldReader = {
35 | sparkField.dataType match {
36 | case LongType => new LongReader(outputIndex, row)
37 | case DoubleType => new DoubleReader(outputIndex, row)
38 | case _ => throw new IllegalArgumentException("Unsupported field type: " + sparkField.dataType);
39 | }
40 | }
41 |
42 | private final class LongReader(index : Int, row: Array[Any]) extends LuceneFieldReader {
43 | override def readLong(value : Long) : Unit = {
44 | row(index) = value
45 | }
46 | }
47 |
48 | private final class DoubleReader(index : Int, row: Array[Any]) extends LuceneFieldReader {
49 | override def readDouble(value : Double) : Unit = {
50 | row(index) = value
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/sparkdatasourcev2test/src/main/scala/net/ndolgov/sparkdatasourcetest/lucene/LuceneFieldWriter.scala:
--------------------------------------------------------------------------------
1 | package net.ndolgov.sparkdatasourcetest.lucene
2 |
3 | import org.apache.lucene.document.Document
4 | import org.apache.spark.sql.catalyst.InternalRow
5 | import org.apache.spark.sql.types.{DoubleType, LongType}
6 |
7 | /**
8 | * Write a Spark Row to a Lucene index as a document
9 | */
10 | trait LuceneFieldWriter {
11 | def write(row : InternalRow)
12 | }
13 |
14 | private final class RowWriter(writers : Seq[LuceneFieldWriter], document : Document) extends LuceneFieldWriter {
15 | override def write(row: InternalRow): Unit = {
16 | writers.foreach((writer: LuceneFieldWriter) => writer.write(row))
17 | }
18 | }
19 |
20 | private final class LongFieldWriter(index: Int, field: LuceneDocumentField, document : Document) extends LuceneFieldWriter {
21 | override def write(row: InternalRow): Unit = {
22 | if (!row.isNullAt(index)) {
23 | field.addTo(document)
24 | field.setLongValue(row.getLong(index))
25 | }
26 | }
27 | }
28 |
29 | private final class DoubleFieldWriter(index: Int, field: LuceneDocumentField, document: Document) extends LuceneFieldWriter {
30 | override def write(row: InternalRow): Unit = {
31 | if (!row.isNullAt(index)) {
32 | field.addTo(document)
33 | field.setDoubleValue(row.getDouble(index))
34 | }
35 | }
36 | }
37 |
38 | object LuceneFieldWriter {
39 | /**
40 | * @param schema document schema
41 | * @param document document instance to reuse
42 | * @return a new writer
43 | */
44 | def apply(schema : LuceneSchema, document : Document) : LuceneFieldWriter = {
45 | val writers : Array[LuceneFieldWriter] = Array.ofDim[LuceneFieldWriter](schema.size)
46 |
47 | var index : Int = 0
48 | for (_ <- schema.sparkSchema()) {
49 | writers(index) = apply(schema, index, document)
50 | index += 1
51 | }
52 |
53 | new RowWriter(writers, document)
54 | }
55 |
56 | private def apply(schema : LuceneSchema, index : Int, document : Document) : LuceneFieldWriter = {
57 | val field : LuceneDocumentField = LuceneFieldFactory(schema, index)
58 |
59 | schema.sparkFieldType(index) match {
60 | case LongType => new LongFieldWriter(index, field, document)
61 | case DoubleType => new DoubleFieldWriter(index, field, document)
62 | case _ => throw new IllegalArgumentException("Unsupported field type: " + field);
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/sparkdatasourcev2test/src/main/scala/net/ndolgov/sparkdatasourcetest/lucene/LuceneIndexWriter.scala:
--------------------------------------------------------------------------------
1 | package net.ndolgov.sparkdatasourcetest.lucene
2 |
3 | import java.io.File
4 |
5 | import org.apache.lucene.analysis.core.KeywordAnalyzer
6 | import org.apache.lucene.document.Document
7 | import org.apache.lucene.index.{IndexWriter, IndexWriterConfig}
8 | import org.apache.lucene.store.{Directory, MMapDirectory}
9 | import org.apache.spark.sql.catalyst.InternalRow
10 | import org.slf4j.{Logger, LoggerFactory}
11 |
12 | /**
13 | * Write a sequence of Rows conforming to a provided schema into a new Lucene index at a given location
14 | */
15 | object LuceneIndexWriter {
16 | val logger: Logger = LoggerFactory.getLogger(LuceneIndexWriter.getClass)
17 |
18 | def apply(indexDir: String, schema: LuceneSchema): LuceneIndexWriter = {
19 | logger.info("Creating Lucene index in: " + indexDir)
20 |
21 | val directory: Directory = new MMapDirectory(new File(indexDir).toPath)
22 | val indexWriter = new IndexWriter(directory, new IndexWriterConfig(new KeywordAnalyzer))
23 |
24 | val document = new Document()
25 | val fieldWriter = LuceneFieldWriter(schema, document)
26 |
27 | new LuceneIndexWriterImpl(indexWriter, fieldWriter, directory, document)
28 | }
29 | }
30 |
31 | trait LuceneIndexWriter {
32 | def write(row: InternalRow)
33 |
34 | def close()
35 | }
36 |
37 | /**
38 | * Write a new fixed-schema Lucene document for every given row
39 | */
40 | private final class LuceneIndexWriterImpl(indexWriter : IndexWriter,
41 | fieldWriter : LuceneFieldWriter,
42 | directory: Directory,
43 | document : Document) extends LuceneIndexWriter {
44 | import LuceneIndexWriter.logger
45 |
46 | def write(row: InternalRow) : Unit = {
47 | document.clear()
48 |
49 | fieldWriter.write(row)
50 | indexWriter.addDocument(document)
51 | }
52 |
53 | override def close(): Unit = {
54 | try {
55 | indexWriter.close()
56 | } catch {
57 | case _: Exception => logger.warn("Could not close index writer")
58 | }
59 |
60 | try {
61 | directory.close()
62 | } catch {
63 | case _: Exception => logger.warn("Could not close index directory")
64 | }
65 |
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/sparkdatasourcev2test/src/main/scala/net/ndolgov/sparkdatasourcetest/lucene/StoredFieldVisitorQuery.scala:
--------------------------------------------------------------------------------
1 | package net.ndolgov.sparkdatasourcetest.lucene
2 |
3 | import java.io.IOException
4 |
5 | import org.apache.lucene.index.{LeafReaderContext, StoredFieldVisitor}
6 | import org.apache.lucene.queries.{CustomScoreProvider, CustomScoreQuery}
7 | import org.apache.lucene.search.Query
8 |
9 | /**
10 | * Custom Lucene query using a visitor to collect values from matching Lucene documents
11 | */
12 | final class StoredFieldVisitorQuery(val subQuery: Query, val processor : LuceneDocumentProcessor) extends CustomScoreQuery(subQuery) {
13 | @throws[IOException]
14 | override def getCustomScoreProvider(context : LeafReaderContext) : CustomScoreProvider = {
15 | new Provider(context, processor)
16 | }
17 | }
18 |
19 | trait LuceneDocumentProcessor {
20 | /** @return Lucene field visitor to apply to all matching documents */
21 | def visitor() : StoredFieldVisitor
22 |
23 | /** Process Lucene document fields gathered by the [[visitor]] from the last seen document */
24 | def onDocument()
25 | }
26 |
27 | @Override
28 | private final class Provider(val leafCtx : LeafReaderContext, val processor : LuceneDocumentProcessor) extends CustomScoreProvider(leafCtx) {
29 | val DEFAULT_SCORE: Int = 0
30 | val visitor : StoredFieldVisitor = processor.visitor()
31 |
32 | override def customScore(docId: Int, subQueryScore: Float, valSrcScore: Float): Float = {
33 | leafCtx.reader().document(docId, visitor)
34 | processor.onDocument()
35 |
36 | DEFAULT_SCORE
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/sparkdatasourcev2test/src/test/resources/log4j.xml:
--------------------------------------------------------------------------------
1 |
2 | ] >
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/sparkdatasourcev2test/src/test/scala/net/ndolgov/sparkdatasourcetest/connector/LuceneSchemaTestSuit.scala:
--------------------------------------------------------------------------------
1 | package net.ndolgov.sparkdatasourcetest.connector
2 |
3 | import java.io.File
4 |
5 | import net.ndolgov.sparkdatasourcetest.connector.LuceneDataSourceTestEnv.DocumentField
6 | import net.ndolgov.sparkdatasourcetest.lucene.{FieldType, LuceneField, LuceneSchema}
7 | import org.apache.spark.sql.types.StructType
8 | import org.scalatest.{Assertions, FlatSpec}
9 |
10 | final class LuceneSchemaTestSuit extends FlatSpec with Assertions {
11 | "A schema written to a file" should "be read back" in {
12 | val METRIC = DocumentField.METRIC.name
13 | val TIME = DocumentField.TIME.name
14 | val VALUE = DocumentField.VALUE.name
15 |
16 | val sparkSchema : StructType = LuceneDataSourceTestEnv.defaultSchema
17 |
18 | val luceneSchema : Array[LuceneField] = Array[LuceneField](
19 | LuceneField(METRIC, FieldType.QUERYABLE),
20 | LuceneField(TIME, FieldType.INDEXED),
21 | LuceneField(VALUE, FieldType.STORED))
22 |
23 | val original = LuceneSchema(sparkSchema, LuceneField.toString(luceneSchema))
24 |
25 | val filePath: String = "target/testschema" + System.currentTimeMillis() + ".txt"
26 | LuceneSchema.save(original, filePath)
27 |
28 | val retrieved = LuceneSchema.open(filePath)
29 |
30 | assert(retrieved.size == 3)
31 | for (i <- 0 to 2) {
32 | assert(retrieved.sparkField(i) == sparkSchema.fields(i))
33 | assert(retrieved.luceneField(i) == luceneSchema(i))
34 | }
35 |
36 | new File(filePath).delete()
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/sqstest/src/main/java/net/ndolgov/sqstest/AsyncSqsClient.java:
--------------------------------------------------------------------------------
1 | package net.ndolgov.sqstest;
2 |
3 | import com.amazonaws.services.sqs.model.Message;
4 |
5 | import java.util.List;
6 | import java.util.function.Function;
7 |
8 | public interface AsyncSqsClient {
9 | void receive(String queueUrl, int maxMessages, int visibilityTimeout, Function, Void> handler);
10 |
11 | void delete(String queueUrl, String handle);
12 |
13 | void renew(String queueUrl, String handle, int visibilityTimeout, Function handler);
14 |
15 | void close();
16 |
17 | interface AsyncSqsClientCallback {
18 | void onSuccess(String handle);
19 |
20 | void onFailure(String message);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/sqstest/src/main/java/net/ndolgov/sqstest/ConcurrentMapMessageRepository.java:
--------------------------------------------------------------------------------
1 | package net.ndolgov.sqstest;
2 |
3 | import java.util.concurrent.ConcurrentHashMap;
4 | import java.util.concurrent.ConcurrentMap;
5 |
6 | public final class ConcurrentMapMessageRepository implements MessageRepository {
7 | private final ConcurrentMap handleToQueue;
8 |
9 | public ConcurrentMapMessageRepository() {
10 | this.handleToQueue = new ConcurrentHashMap<>(64);
11 | }
12 |
13 | @Override
14 | public String get(String handle) {
15 | return handleToQueue.get(handle);
16 | }
17 |
18 | @Override
19 | public String remove(String handle) {
20 | return handleToQueue.remove(handle);
21 | }
22 |
23 | @Override
24 | public String put(String handle, String queueUrl) {
25 | handleToQueue.put(handle, queueUrl);
26 | return queueUrl;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/sqstest/src/main/java/net/ndolgov/sqstest/Handler.java:
--------------------------------------------------------------------------------
1 | package net.ndolgov.sqstest;
2 |
3 | import net.ndolgov.sqstest.AsyncSqsClient.AsyncSqsClientCallback;
4 |
5 | public interface Handler {
6 | /**
7 | * Process a message asynchronously on a thread other than the one calling this method
8 | * @param message message
9 | * @param callback message processing status listener
10 | */
11 | void handle(String message, AsyncSqsClientCallback callback);
12 |
13 | /**
14 | * @return how many messages this handler can process in one batch
15 | */
16 | default int getRemainingCapacity() {
17 | return 1;
18 | }
19 |
20 | /**
21 | * @return AWS SQS visibility timeout for this handler, [sec]
22 | */
23 | default int getVisibilityTimeout() {
24 | return 300;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/sqstest/src/main/java/net/ndolgov/sqstest/MessageHandler.java:
--------------------------------------------------------------------------------
1 | package net.ndolgov.sqstest;
2 |
3 | import net.ndolgov.sqstest.AsyncSqsClient.AsyncSqsClientCallback;
4 |
5 | import java.util.concurrent.ExecutorService;
6 | import java.util.concurrent.atomic.AtomicInteger;
7 |
8 | public final class MessageHandler implements Handler {
9 | private final ExecutorService executor;
10 |
11 | private final AtomicInteger jobInProgressCounter = new AtomicInteger();
12 |
13 | private final int visibilityTimeout;
14 |
15 | private final int totalCapacity;
16 |
17 | public MessageHandler(int visibilityTimeout, ExecutorService executor) {
18 | this.visibilityTimeout = visibilityTimeout;
19 | this.executor = executor;
20 |
21 | this.totalCapacity = Runtime.getRuntime().availableProcessors();
22 | }
23 |
24 | @Override
25 | public void handle(String message, AsyncSqsClientCallback callback) {
26 | jobInProgressCounter.incrementAndGet();
27 |
28 | executor.submit((Runnable) () -> {
29 | try {
30 | final long startedAt = System.currentTimeMillis();
31 |
32 | // todo process message
33 |
34 | callback.onSuccess("");
35 | } catch (Exception e) {
36 | callback.onFailure(e.getMessage());
37 | } finally {
38 | jobInProgressCounter.decrementAndGet();
39 | }
40 | });
41 | }
42 |
43 | @Override
44 | public int getRemainingCapacity() {
45 | return Math.max(totalCapacity - jobInProgressCounter.get(), 0);
46 | }
47 |
48 | @Override
49 | public int getVisibilityTimeout() {
50 | return visibilityTimeout;
51 | }
52 |
53 | }
54 |
--------------------------------------------------------------------------------
/sqstest/src/main/java/net/ndolgov/sqstest/MessageRepository.java:
--------------------------------------------------------------------------------
1 | package net.ndolgov.sqstest;
2 |
3 | /**
4 | * Remember all the messages currently being processed
5 | */
6 | public interface MessageRepository {
7 | String get(String handle);
8 |
9 | String remove(String handle);
10 |
11 | String put(String handle, String queueUrl);
12 | }
13 |
--------------------------------------------------------------------------------
/sqstest/src/main/java/net/ndolgov/sqstest/SqsQueuePoller.java:
--------------------------------------------------------------------------------
1 | package net.ndolgov.sqstest;
2 |
3 | import com.amazonaws.services.sqs.model.Message;
4 |
5 | public final class SqsQueuePoller {
6 | private static final double SAFETY_MARGIN = 0.9;
7 | private final AsyncSqsClient sqsClient;
8 | private final MessageRepository repository;
9 | private final VisibilityTimeoutTracker tracker;
10 |
11 | public SqsQueuePoller(AsyncSqsClient sqsClient, MessageRepository repository, VisibilityTimeoutTracker tracker) {
12 | this.sqsClient = sqsClient;
13 | this.repository = repository;
14 | this.tracker = tracker;
15 | }
16 |
17 | public void poll(String queueUrl, Handler handler) {
18 | final int maxMessages = handler.getRemainingCapacity();
19 | if (maxMessages > 0) {
20 | sqsClient.receive(queueUrl, maxMessages, handler.getVisibilityTimeout(), messages -> {
21 | for (Message message : messages) {
22 | handle(queueUrl, message, handler);
23 | }
24 |
25 | return null;
26 | });
27 | }
28 | }
29 |
30 | private void handle(String queueUrl, Message message, Handler handler) {
31 | final String handle = message.getReceiptHandle();
32 |
33 | repository.put(message.getReceiptHandle(), queueUrl);
34 |
35 | tracker.track(handle, (int) (handler.getVisibilityTimeout() * SAFETY_MARGIN));
36 |
37 | handler.handle(message.getBody(), new AsyncSqsClient.AsyncSqsClientCallback() {
38 | @Override
39 | public void onSuccess(String handle) {
40 | sqsClient.delete(repository.remove(handle), handle);
41 | }
42 |
43 | @Override
44 | public void onFailure(String handle) {
45 | sqsClient.delete(repository.remove(handle), handle);
46 | }
47 | });
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/sqstest/src/main/java/net/ndolgov/sqstest/VisibilityTimeoutTracker.java:
--------------------------------------------------------------------------------
1 | package net.ndolgov.sqstest;
2 |
3 | import java.util.concurrent.Future;
4 |
5 | /**
6 | * Periodically reset visibility timeout for message still being processed
7 | */
8 | public interface VisibilityTimeoutTracker {
9 | /**
10 | * @param handle message handle
11 | * @param timeout the next visibility timeout
12 | * @return the job to run after a given timeout
13 | */
14 | Future> track(String handle, int timeout);
15 | }
16 |
--------------------------------------------------------------------------------
/sqstest/src/main/java/net/ndolgov/sqstest/VisibilityTimeoutTrackerImpl.java:
--------------------------------------------------------------------------------
1 | package net.ndolgov.sqstest;
2 |
3 | import org.slf4j.Logger;
4 |
5 | import java.util.concurrent.Future;
6 | import java.util.concurrent.ScheduledExecutorService;
7 | import java.util.concurrent.TimeUnit;
8 |
9 | public final class VisibilityTimeoutTrackerImpl implements VisibilityTimeoutTracker {
10 | private final Logger logger;
11 |
12 | private final ScheduledExecutorService scheduler;
13 |
14 | private final MessageRepository repository;
15 |
16 | private final AsyncSqsClient sqsClient;
17 |
18 | public VisibilityTimeoutTrackerImpl(Logger logger, MessageRepository repository, AsyncSqsClient sqsClient, ScheduledExecutorService scheduler) {
19 | this.logger = logger;
20 | this.repository = repository;
21 | this.sqsClient = sqsClient;
22 | this.scheduler = scheduler;
23 | }
24 |
25 | @Override
26 | public Future> track(String handle, int timeout) {
27 | return scheduler.schedule(() -> {
28 | logger.info("Extending visibility of message: " + handle + " by seconds: " + timeout);
29 |
30 | final String queueUrl = repository.get(handle);
31 | if (queueUrl == null) {
32 | logger.info("Message was already processed: " + handle);
33 | } else {
34 | sqsClient.renew(queueUrl, handle, timeout, unit -> {
35 | track(handle, timeout);
36 | return null;
37 | });
38 | }
39 | },
40 | timeout,
41 | TimeUnit.SECONDS);
42 | }
43 | }
--------------------------------------------------------------------------------
/sqstest/src/test/java/net/ndolgov/sqstest/SqsQueuePollerTest.java:
--------------------------------------------------------------------------------
1 | package net.ndolgov.sqstest;
2 |
3 | import org.slf4j.Logger;
4 | import org.slf4j.LoggerFactory;
5 | import org.testng.annotations.Test;
6 |
7 | import static java.util.concurrent.Executors.newSingleThreadExecutor;
8 | import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
9 |
10 | /**
11 | * Only an example of wiring up, the test itself will fail
12 | */
13 | public final class SqsQueuePollerTest {
14 | private static final Logger logger = LoggerFactory.getLogger(SqsQueuePollerTest.class);
15 | private static final int VISIBILITY_TUMEOUT = 900;
16 | private static final String QUEUE_URL = "http://sqs.us-east-1.amazonaws.com/123456789012/queue2";
17 |
18 | @Test
19 | public void testOneQueuePollingSetup() {
20 | final AsyncSqsClientImpl sqsClient = new AsyncSqsClientImpl(logger, VISIBILITY_TUMEOUT);
21 | final ConcurrentMapMessageRepository repository = new ConcurrentMapMessageRepository();
22 |
23 | final SqsQueuePoller poller = new SqsQueuePoller(
24 | sqsClient,
25 | repository,
26 | new VisibilityTimeoutTrackerImpl(logger, repository, sqsClient, newSingleThreadScheduledExecutor()));
27 |
28 | poller.poll(
29 | QUEUE_URL,
30 | new MessageHandler(VISIBILITY_TUMEOUT, newSingleThreadExecutor()));
31 |
32 | sqsClient.close();
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/thriftrpctest/src/main/java/net/ndolgov/thriftrpctest/ClientFactory.java:
--------------------------------------------------------------------------------
1 | package net.ndolgov.thriftrpctest;
2 |
3 | /**
4 | * Asynchronous Thrift service client factory
5 | */
6 | public interface ClientFactory {
7 | /**
8 | * @param definition service definition to create client for
9 | * @param hostname server host name (IRL obtained from some discovery service)
10 | * @param Thrift-generated service interface type
11 | * @return newly created service client
12 | */
13 | T create(ServiceDefinition definition, String hostname);
14 |
15 | void close();
16 | }
17 |
--------------------------------------------------------------------------------
/thriftrpctest/src/main/java/net/ndolgov/thriftrpctest/HandlerFactory.java:
--------------------------------------------------------------------------------
1 | package net.ndolgov.thriftrpctest;
2 |
3 | /**
4 | * Asynchronous Thrift service server-side handler factory
5 | */
6 | public interface HandlerFactory {
7 | /**
8 | * @param definition service definition to create server-side handler for
9 | * @param Thrift-generated service interface type
10 | * @return newly created service request handler
11 | */
12 | T handler(ServiceDefinition definition);
13 | }
14 |
--------------------------------------------------------------------------------
/thriftrpctest/src/main/java/net/ndolgov/thriftrpctest/MultiplexedClientFactory.java:
--------------------------------------------------------------------------------
1 | package net.ndolgov.thriftrpctest;
2 |
3 | import org.apache.thrift.async.TAsyncClientManager;
4 | import org.apache.thrift.protocol.TBinaryProtocol;
5 | import org.apache.thrift.protocol.TMultiplexedProtocol;
6 | import org.apache.thrift.protocol.TProtocol;
7 | import org.apache.thrift.protocol.TProtocolFactory;
8 | import org.apache.thrift.transport.TNonblockingSocket;
9 | import org.apache.thrift.transport.TTransport;
10 |
11 | /**
12 | * Establish a connection to a given service instance
13 | */
14 | public final class MultiplexedClientFactory implements ClientFactory {
15 | private final TAsyncClientManager manager;
16 | private final TProtocolFactory factory;
17 | private final int port;
18 |
19 | public MultiplexedClientFactory(int port) {
20 | this.port = port;
21 |
22 | factory = new TBinaryProtocol.Factory();
23 |
24 | try {
25 | manager = new TAsyncClientManager();
26 | } catch (Exception e) {
27 | throw new IllegalArgumentException("Could not create client manager", e);
28 | }
29 | }
30 |
31 | @Override
32 | public T create(ServiceDefinition definition, String hostname) {
33 | try {
34 | final TProtocolFactory pfactory = new TMultiplexedProtocolFactory(factory, definition.getName()); // todo cache?
35 | return (T) definition.clientFactory(pfactory, manager).getAsyncClient(new TNonblockingSocket(hostname, port));
36 | } catch (Exception e) {
37 | throw new RuntimeException("Could not create client to: " + hostname + " for service: " + definition, e);
38 | }
39 | }
40 |
41 | @Override
42 | public void close() {
43 | manager.stop();
44 | }
45 |
46 | private final static class TMultiplexedProtocolFactory implements TProtocolFactory {
47 | private final TProtocolFactory factory;
48 | private final String name;
49 |
50 | public TMultiplexedProtocolFactory(TProtocolFactory factory, String name) {
51 | this.factory = factory;
52 | this.name = name;
53 | }
54 |
55 | public TProtocol getProtocol(TTransport transport) {
56 | return new TMultiplexedProtocol(factory.getProtocol(transport), name);
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/thriftrpctest/src/test/java/net/ndolgov/thriftrpctest/Handler.java:
--------------------------------------------------------------------------------
1 | package net.ndolgov.thriftrpctest;
2 |
3 | import net.ndolgov.thriftrpctest.api.TestRequest;
4 | import net.ndolgov.thriftrpctest.api.TestResponse;
5 | import net.ndolgov.thriftrpctest.api.TestService;
6 |
7 | import org.apache.thrift.async.AsyncMethodCallback;
8 | import org.slf4j.Logger;
9 | import org.slf4j.LoggerFactory;
10 |
11 | import java.util.concurrent.CompletableFuture;
12 | import java.util.concurrent.Executor;
13 |
14 | /**
15 | * Asynchronous RPC call handler illustrating how to process requests on a dedicated thread pool and
16 | * reply asynchronously once the future is finished.
17 | */
18 | public final class Handler implements TestService.AsyncIface {
19 | private static final Logger logger = LoggerFactory.getLogger(Handler.class);
20 |
21 | public static final String RESULT = "RESULT";
22 |
23 | private final Executor executor;
24 |
25 | public Handler(Executor executor) {
26 | this.executor = executor;
27 | }
28 |
29 | @Override
30 | public void process(TestRequest request, AsyncMethodCallback callback) {
31 | logger.info("Processing: " + request);
32 |
33 | final CompletableFuture future = CompletableFuture.supplyAsync(
34 | () -> {
35 | return "RESULT"; // todo this is where actual time-consuming processing would be
36 | },
37 | executor);
38 |
39 | future.whenComplete((result, e) -> {
40 | if (e == null) {
41 | callback.onComplete(new TestResponse().setSuccess(true).setResult(result).setRequestId(request.getRequestId()));
42 | } else {
43 | callback.onComplete(new TestResponse().setSuccess(false).setRequestId(request.getRequestId()));
44 | }
45 | });
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/thriftrpctest/src/test/java/net/ndolgov/thriftrpctest/Handler2.java:
--------------------------------------------------------------------------------
1 | package net.ndolgov.thriftrpctest;
2 |
3 | import net.ndolgov.thriftrpctest.api.TestRequest2;
4 | import net.ndolgov.thriftrpctest.api.TestResponse2;
5 | import net.ndolgov.thriftrpctest.api.TestService2;
6 | import org.apache.thrift.async.AsyncMethodCallback;
7 | import org.slf4j.Logger;
8 | import org.slf4j.LoggerFactory;
9 |
10 | import java.util.concurrent.CompletableFuture;
11 | import java.util.concurrent.Executor;
12 |
13 | /**
14 | * Asynchronous RPC call handler illustrating how to process requests on a dedicated thread pool and
15 | * reply asynchronously once the future is finished.
16 | */
17 | public final class Handler2 implements TestService2.AsyncIface {
18 | private static final Logger logger = LoggerFactory.getLogger(Handler2.class);
19 |
20 | public static final String RESULT = "RESULT2";
21 |
22 | private final Executor executor;
23 |
24 | public Handler2(Executor executor) {
25 | this.executor = executor;
26 | }
27 |
28 | @Override
29 | public void process(TestRequest2 request, AsyncMethodCallback callback) {
30 | logger.info("Processing: " + request);
31 |
32 | final CompletableFuture future = CompletableFuture.supplyAsync(
33 | () -> {
34 | return "RESULT2"; // todo this is where actual time-consuming processing would be
35 | },
36 | executor);
37 |
38 | future.whenComplete((result, e) -> {
39 | if (e == null) {
40 | callback.onComplete(new TestResponse2().setSuccess(true).setResult(result).setRequestId(request.getRequestId()));
41 | } else {
42 | callback.onComplete(new TestResponse2().setSuccess(false).setRequestId(request.getRequestId()));
43 | }
44 | });
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/thriftrpctest/src/test/resources/log4j.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/thriftrpctest/src/test/thrift/testsvc.thrift:
--------------------------------------------------------------------------------
1 | namespace java net.ndolgov.thriftrpctest.api
2 |
3 | struct TestRequest {
4 | 1: required i64 requestId
5 | }
6 |
7 | struct TestResponse {
8 | 1: required bool success
9 | 2: required i64 requestId
10 | 3: string result
11 | }
12 |
13 | service TestService {
14 | TestResponse process(TestRequest request)
15 | }
--------------------------------------------------------------------------------
/thriftrpctest/src/test/thrift/testsvc2.thrift:
--------------------------------------------------------------------------------
1 | namespace java net.ndolgov.thriftrpctest.api
2 |
3 | struct TestRequest2 {
4 | 1: required i64 requestId
5 | }
6 |
7 | struct TestResponse2 {
8 | 1: required bool success
9 | 2: required i64 requestId
10 | 3: string result
11 | }
12 |
13 | service TestService2 {
14 | TestResponse2 process(TestRequest2 request)
15 | }
--------------------------------------------------------------------------------
/timeseriescompressiontest/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | 4.0.0
7 |
8 | net.ndolgov
9 | timeseriescompressiontest
10 | 1.0.0-SNAPSHOT
11 | jar
12 | Time Series compression test
13 |
14 |
15 | 6.8.8
16 |
17 |
18 |
19 |
20 | org.testng
21 | testng
22 | ${testng.version}
23 | test
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/timeseriescompressiontest/src/test/java/net/ndolgov/timeseriescompression/CompressedTimeSeriesTest.java:
--------------------------------------------------------------------------------
1 | package net.ndolgov.timeseriescompression;
2 |
3 | import org.testng.annotations.Test;
4 |
5 | import static org.testng.Assert.assertEquals;
6 |
7 | public final class CompressedTimeSeriesTest {
8 | @Test
9 | public void testEmpty() {
10 | read(new byte[0], 0, new long[0], new double[0]);
11 | }
12 |
13 | @Test
14 | public void testMultipleDataPoints() {
15 | final int dpCount = 6;
16 | final byte[] buffer = new byte[16 * dpCount];
17 |
18 | final CompressedTimeSeries timeSeries = new CompressedTimeSeries(buffer);
19 | final long[] originalTimestamps = {0, 60, 120, 180, 240, 300};
20 | final double[] originalValues = {5.0, 6.0, 7.0, 7.0, 8.0, 0.3333};
21 |
22 | for (int i = 0; i < originalValues.length; i++) {
23 | append(timeSeries, originalTimestamps[i], originalValues[i]);
24 | }
25 |
26 | final long[] retrievedTimes = new long[dpCount];
27 | final double[] retrievedValues = new double[dpCount];
28 | read(buffer, dpCount, retrievedTimes, retrievedValues);
29 |
30 | for (int i = 0; i < dpCount; i++) {
31 | assertEquals(originalTimestamps[i], retrievedTimes[i]);
32 | assertEquals(originalValues[i], retrievedValues[i]);
33 | }
34 | }
35 |
36 | private static void append(CompressedTimeSeries stream, long time, double value) {
37 | if (!stream.append(time, value)) {
38 | throw new IllegalArgumentException("timestamp:" + time);
39 | }
40 | }
41 |
42 | private static void read(byte[] data, int count, long[] times, double[] values) {
43 | if (count == 0) {
44 | return;
45 | }
46 |
47 | final CompressedTimeSeries timeSeries = new CompressedTimeSeries(data);
48 |
49 | times[0] = timeSeries.readFirstTimeStamp();
50 | values[0] = timeSeries.readNextValue();
51 | int readSoFar = 1;
52 |
53 | while (readSoFar < count) {
54 | times[readSoFar] = timeSeries.readNextTimestamp();
55 | values[readSoFar] = timeSeries.readNextValue();
56 | readSoFar++;
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------