diff --git a/README.md b/README.md index 7744436..b277706 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,12 @@ -This package should be divided in two. - -### http - -First one is to map http call to Globs as follow: Given an URLParameter, QueryParameter and HeaderType GlobType ``` -httpServerRegister = new HttpServerRegister("TestServer/1.1"); -bootstrap = ServerBootstrap.bootstrap(); // from apache httpcomponents. +bootstrap = AsyncServerBootstrap.bootstrap().setIOReactorConfig(config); + +final HttpServerRegister httpServerRegister = new HttpServerRegister("EstablishmentServer/0.1"); httpServerRegister.register("/test/{id}/TOTO/{subId}", URLParameter.TYPE) .get(QueryParameter.TYPE, (body, url, queryParameters, header) -> { @@ -22,62 +18,6 @@ httpServerRegister.register("/test/{id}/TOTO/{subId}", URLParameter.TYPE) .withHeaderType(HeaderType.TYPE) ; -httpServerRegister.startAndWaitForStartup(bootstrap); - - -``` - -### etcd - -The second part is about etcd (from google) -It expose an interface to publish Glob and to register for changes on Glob using etcd. It is usefull to propagate -configuration or low change state to/from microservice. (I do not used it to cache data like with a Redis) - -It use the annotation PathIndex_ to create the key from the globs (). -The first part of the key is the name of the GlobType. -It is then possible to listen for all changes on a GlobType or a sub part of the key (in the given order : is is not -possible to listen for only the last part of a key) - -The value can be a json serialisation or a binary serialisation using Globs-bin-serialisation (so only if all field have -the FieldNumber_ annotation) - -``` - Client client = Client.builder().endpoints(ETCD).build(); - - SharedDataAccess etcDSharedDataAccess = EtcDSharedDataAccess.createBin(client); - - CompletableFuture done = new CompletableFuture<>(); - etcDSharedDataAccess.listen(Data1.TYPE, new SharedDataAccess.Listener() { - public void put(Glob glob) { - try { - etcDSharedDataAccess.get(glob.getType(), glob).join(); - done.complete(glob); - } catch (Exception e) { - done.complete(null); - } - } - - public void delete(Glob glob) { - - } - }, Data1.TYPE.instantiate() - .set(Data1.shop, "mg.the-oz.com") - .set(Data1.workerName, "w1") - .set(Data1.num, 1)); - - MutableGlob data = Data1.TYPE.instantiate() - .set(Data1.shop, "mg.the-oz.com") - .set(Data1.workerName, "w1") - .set(Data1.num, 1) - .set(Data1.someData, "blabla"); - - // publish data. - etcDSharedDataAccess.register(data) - .get(1, TimeUnit.MINUTES); - - final Glob join = done.join(); - Assert.assertNotNull(join); - Assert.assertEquals("blabla", join.get(Data1.someData)); - etcDSharedDataAccess.end(); -``` - +GlobHttpApacheBuilder globHttpApacheBuilder = new GlobHttpApacheBuilder(httpServerRegister); +Server serverInstance = globHttpApacheBuilder.startAndWaitForStartup(bootstrap, 0); +server = serverInstance.getServer(); diff --git a/pom.xml b/pom.xml index 52358b3..8643967 100644 --- a/pom.xml +++ b/pom.xml @@ -1,6 +1,5 @@ - + 4.0.0 Globs library used to expose an http api and etcd using globs https://globsframework.org @@ -8,7 +7,7 @@ jar globs-http Generic Lightweight ObjectS http - 4.1.1 + 4.3-SNAPSHOT @@ -36,8 +35,11 @@ + scm:git:git@github.com:globsframework/globs-http + scm:git:git@github.com:globsframework/globs-http https://github.com/globsframework/globs-http - + HEAD + @@ -127,21 +129,10 @@ - - io.etcd - jetcd-core - 0.8.5 - - - org.slf4j - slf4j-api - - - org.globsframework globs - 4.1.1 + 4.2.0 org.globsframework @@ -151,20 +142,20 @@ org.globsframework globs-bin-serialisation - 4.1.1 + 4.3.0 org.apache.httpcomponents.core5 httpcore5 - 5.3.3 + 5.3.4 org.apache.httpcomponents.core5 httpcore5-h2 - 5.3.3 + 5.3.4 @@ -246,7 +237,7 @@ org.globsframework globs - 4.1-SNAPSHOT + 4.2.0 test-jar test diff --git a/src/main/java/org/globsframework/http/DefaultUrlMatcher.java b/src/main/java/org/globsframework/http/DefaultUrlMatcher.java index 7de46a7..95e531b 100644 --- a/src/main/java/org/globsframework/http/DefaultUrlMatcher.java +++ b/src/main/java/org/globsframework/http/DefaultUrlMatcher.java @@ -8,7 +8,7 @@ import java.util.Arrays; -class DefaultUrlMatcher { +public class DefaultUrlMatcher { public static UrlMatcher create(GlobType globType, String fullUrl) { diff --git a/src/main/java/org/globsframework/http/GlobHttpRequestHandlerFactory.java b/src/main/java/org/globsframework/http/GlobHttpRequestHandlerFactory.java index 70195cd..2a1d748 100644 --- a/src/main/java/org/globsframework/http/GlobHttpRequestHandlerFactory.java +++ b/src/main/java/org/globsframework/http/GlobHttpRequestHandlerFactory.java @@ -5,6 +5,6 @@ import org.apache.hc.core5.http.nio.ResponseChannel; import org.apache.hc.core5.http.protocol.HttpContext; -interface GlobHttpRequestHandlerFactory { +public interface GlobHttpRequestHandlerFactory { GlobHttpRequestHandler create(HttpRequest request, EntityDetails entityDetails, ResponseChannel responseChannel, HttpContext context); } diff --git a/src/main/java/org/globsframework/http/HttpOp.java b/src/main/java/org/globsframework/http/HttpOp.java index 1ddc6d9..cf75ff4 100644 --- a/src/main/java/org/globsframework/http/HttpOp.java +++ b/src/main/java/org/globsframework/http/HttpOp.java @@ -1,5 +1,5 @@ package org.globsframework.http; -enum HttpOp { +public enum HttpOp { post, put, patch, delete, get, option } diff --git a/src/main/java/org/globsframework/http/HttpOutputData.java b/src/main/java/org/globsframework/http/HttpOutputData.java index 070ea0a..4aba580 100644 --- a/src/main/java/org/globsframework/http/HttpOutputData.java +++ b/src/main/java/org/globsframework/http/HttpOutputData.java @@ -4,47 +4,41 @@ import java.io.InputStream; -public interface HttpOutputData { - - boolean isGlob(); - - Glob getGlob(); +public sealed interface HttpOutputData permits HttpOutputData.GlobHttpOutputData, HttpOutputData.KnownSizeStreamHttpOutputData { record SizedStream(InputStream stream, long size) {} - SizedStream getStream(); - static HttpOutputData asGlob(Glob glob) { - return new HttpOutputData() { - public boolean isGlob() { - return true; - } + return new GlobHttpOutputData(glob); + } - public Glob getGlob() { - return glob; - } + static HttpOutputData asStream(InputStream data, long size) { + return new KnownSizeStreamHttpOutputData(data, size); + } - public SizedStream getStream() { - return null; - } + final class GlobHttpOutputData implements HttpOutputData { + private final Glob glob; - }; - } + public GlobHttpOutputData(Glob glob) { + this.glob = glob; + } - static HttpOutputData asStream(InputStream data, long size) { - return new HttpOutputData() { - public boolean isGlob() { - return false; - } + public Glob getGlob() { + return glob; + } + } - public Glob getGlob() { - return null; - } + final class KnownSizeStreamHttpOutputData implements HttpOutputData { + private final InputStream data; + private final long size; - public SizedStream getStream() { - return new SizedStream(data, size); - } + public KnownSizeStreamHttpOutputData(InputStream data, long size) { + this.data = data; + this.size = size; + } - }; + public SizedStream getStream() { + return new SizedStream(data, size); + } } } diff --git a/src/main/java/org/globsframework/http/HttpServerRegister.java b/src/main/java/org/globsframework/http/HttpServerRegister.java index 2046a36..3fe41fc 100644 --- a/src/main/java/org/globsframework/http/HttpServerRegister.java +++ b/src/main/java/org/globsframework/http/HttpServerRegister.java @@ -1,48 +1,27 @@ package org.globsframework.http; -import org.apache.hc.core5.function.Supplier; -import org.apache.hc.core5.http.HttpRequestMapper; -import org.apache.hc.core5.http.URIScheme; -import org.apache.hc.core5.http.impl.bootstrap.AsyncServerBootstrap; -import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncServer; -import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler; -import org.apache.hc.core5.http2.impl.nio.bootstrap.H2ServerBootstrap; -import org.apache.hc.core5.reactor.ListenerEndpoint; import org.globsframework.core.metamodel.GlobType; import org.globsframework.core.metamodel.GlobTypeBuilder; import org.globsframework.core.metamodel.GlobTypeBuilderFactory; -import org.globsframework.core.metamodel.annotations.Comment; -import org.globsframework.core.metamodel.fields.*; +import org.globsframework.core.metamodel.fields.StringField; import org.globsframework.core.model.Glob; -import org.globsframework.core.model.MutableGlob; -import org.globsframework.core.utils.Ref; import org.globsframework.core.utils.Strings; -import org.globsframework.http.openapi.model.*; -import org.globsframework.json.GSonUtils; +import org.globsframework.http.openapi.model.GetOpenApiParamType; +import org.globsframework.http.openapi.model.GlobOpenApi; import org.globsframework.json.annottations.IsJsonContent; import org.globsframework.json.annottations.IsJsonContent_; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import java.net.InetSocketAddress; -import java.util.*; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; -import java.util.concurrent.Future; public class HttpServerRegister { - private static final Logger LOGGER = LoggerFactory.getLogger(HttpServerRegister.class); - - private static final String DOUBLE_STR = "double"; - private static final String NUMBER_STR = "number"; - private static final String ARRAY_STR = "array"; - private static final String BIG_DECIMAL_STR = "big-decimal"; - private static final String STRING_STR = "string"; - - private final Map verbMap = new LinkedHashMap<>(); - private final String serverInfo; - private Glob openApiDoc; - private InterceptBuilder interceptBuilder = InterceptBuilder.NULL; + public final Map verbMap = new LinkedHashMap<>(); + public final String serverInfo; + public InterceptBuilder interceptBuilder = InterceptBuilder.NULL; public HttpServerRegister(String serverInfo) { this.serverInfo = serverInfo; @@ -68,515 +47,19 @@ public Verb register(String url, GlobType pathParameters) { return current; } - public void registerOpenApi() { - + public void registerOpenApi(GlobOpenApi openApiDoc) { register("/api", null) .get(GetOpenApiParamType.TYPE, new HttpTreatment() { public CompletableFuture consume(Glob body, Glob pathParameters, Glob queryParameters) throws Exception { String scope = queryParameters == null ? "" : queryParameters.get(GetOpenApiParamType.scope); if (Strings.isNullOrEmpty(scope)) { - return CompletableFuture.completedFuture(openApiDoc); + return CompletableFuture.completedFuture(openApiDoc.getOpenApiDoc()); } - return CompletableFuture.completedFuture(createOpenApiDocByTags(scope)); + return CompletableFuture.completedFuture(openApiDoc.createOpenApiDocByTags(scope)); } }); //.declareReturnType(OpenApiType.TYPE); } - public Glob createOpenApiDocByTags(String tag) { - List paths = new ArrayList<>(); - Arrays.stream(openApiDoc.getOrEmpty(OpenApiType.paths)).forEach(path -> { - boolean isPathSelected = - hasSelectedTag(path, OpenApiPath.get, tag) || - hasSelectedTag(path, OpenApiPath.put, tag) || - hasSelectedTag(path, OpenApiPath.post, tag) || - hasSelectedTag(path, OpenApiPath.delete, tag) || - hasSelectedTag(path, OpenApiPath.patch, tag); - if (isPathSelected) { - paths.add(path); - } - }); - - - return openApiDoc.duplicate().set(OpenApiType.paths, paths.toArray(Glob[]::new)); - } - - private boolean hasSelectedTag(Glob path, GlobField field, String targetScope) { - Glob pathDescription = path.get(field); - if (pathDescription == null) { - return false; - } - - String[] currentScopes = pathDescription.getOrEmpty(OpenApiPathDsc.tags); - return Arrays.asList(currentScopes).contains(targetScope); - } - - public Glob createOpenApiDoc(int port) { - Map schemas = new LinkedHashMap<>(); - List paths = new ArrayList<>(); - for (Map.Entry stringVerbEntry : verbMap.entrySet()) { - createVerbDoc(schemas, paths, stringVerbEntry); - } - - return OpenApiType.TYPE.instantiate() - .set(OpenApiType.openAPIVersion, "3.0.1") - .set(OpenApiType.info, OpenApiInfo.TYPE.instantiate() - .set(OpenApiInfo.description, serverInfo) - .set(OpenApiInfo.title, serverInfo) - .set(OpenApiInfo.version, "1.0") - ) - .set(OpenApiType.components, OpenApiComponents.TYPE.instantiate() - .set(OpenApiComponents.schemas, schemas.values().toArray(Glob[]::new))) - .set(OpenApiType.servers, new Glob[]{OpenApiServers.TYPE.instantiate() - .set(OpenApiServers.url, "http://localhost:" + port)}) - .set(OpenApiType.paths, paths.toArray(Glob[]::new)); - } - - private void createVerbDoc(Map schemas, List paths, Map.Entry stringVerbEntry) { - Verb verb = stringVerbEntry.getValue(); - MutableGlob path = OpenApiPath.TYPE.instantiate(); - paths.add(path); - path.set(OpenApiPath.name, verb.url); - for (HttpOperation operation : stringVerbEntry.getValue().operations) { - createOperationDoc(schemas, verb, path, operation); - - } - } - - private void createOperationDoc(Map schemas, Verb verb, MutableGlob path, HttpOperation operation) { - MutableGlob desc = OpenApiPathDsc.TYPE.instantiate(); - setOperationComment(operation, desc); - List parameters = getOperationPathParameters(schemas, verb); - addOperationQueryParameters(schemas, operation, parameters); - setOperationRequestBody(schemas, operation, desc); - setOperationTags(operation, desc); - setOperationReturnType(schemas, operation, desc); - setPathDescription(path, operation, desc, parameters); - } - - private void setOperationReturnType(Map schemas, HttpOperation operation, MutableGlob desc) { - GlobType returnType = operation.getReturnType(); - if (returnType == null) { - desc.set(OpenApiPathDsc.responses, new Glob[]{OpenApiResponses.TYPE - .instantiate() - .set(OpenApiResponses.description, "None") - .set(OpenApiResponses.code, "200")}); - } else { - desc.set(OpenApiPathDsc.responses, new Glob[]{OpenApiResponses.TYPE.instantiate() - .set(OpenApiResponses.code, "200") - .set(OpenApiResponses.description, - returnType.findOptAnnotation(Comment.UNIQUE_KEY) - .map(Comment.VALUE).orElse("None")) - .set(OpenApiResponses.content, new Glob[]{ - OpenApiBodyMimeType.TYPE.instantiate() - .set(OpenApiBodyMimeType.mimeType, "application/json") - .set(OpenApiBodyMimeType.schema, buildSchema(returnType, schemas))}) - }); - } - } - - private void setOperationComment(HttpOperation operation, MutableGlob desc) { - String comment = operation.getComment(); - if (comment != null) { - desc.set(OpenApiPathDsc.description, comment); - } - } - - private void setOperationTags(HttpOperation operation, MutableGlob desc) { - String[] tags = operation.getTags(); - if (tags != null) { - desc.set(OpenApiPathDsc.tags, tags); - } - } - - private void setOperationRequestBody(Map schemas, HttpOperation operation, MutableGlob desc) { - GlobType bodyType = operation.getBodyType(); - if (bodyType != null) { - desc.set(OpenApiPathDsc.requestBody, OpenApiRequestBody.TYPE.instantiate() - .set(OpenApiRequestBody.content, new Glob[]{OpenApiBodyMimeType.TYPE.instantiate() - .set(OpenApiBodyMimeType.mimeType, "application/json") - .set(OpenApiBodyMimeType.schema, buildSchema(bodyType, schemas))})); - } - } - - private void addOperationQueryParameters(Map schemas, HttpOperation operation, List parameters) { - GlobType queryParamType = operation.getQueryParamType(); - if (queryParamType != null) { - for (Field field : queryParamType.getFields()) { - OpenApiFieldVisitor openApiFieldVisitor = field.safeAccept(new OpenApiFieldVisitor(schemas)); - parameters.add(OpenApiParameter.TYPE.instantiate() - .set(OpenApiParameter.in, "query") - .set(OpenApiParameter.name, field.getName()) - .set(OpenApiParameter.required, true) - .set(OpenApiParameter.schema, openApiFieldVisitor.schema)); - } - } - } - - private List getOperationPathParameters(Map schemas, Verb verb) { - List parameters = new ArrayList<>(); - if (verb.pathParameters != null) { - for (Field field : verb.pathParameters.getFields()) { - OpenApiFieldVisitor apiFieldVisitor = new OpenApiFieldVisitor(schemas); - OpenApiFieldVisitor openApiFieldVisitor = field.safeAccept(apiFieldVisitor); - parameters.add(OpenApiParameter.TYPE.instantiate() - .set(OpenApiParameter.in, "path") - .set(OpenApiParameter.name, field.getName()) - .set(OpenApiParameter.required, true) - .set(OpenApiParameter.schema, openApiFieldVisitor.schema)); - } - } - return parameters; - } - - private void setPathDescription(MutableGlob path, HttpOperation operation, MutableGlob desc, List parameters) { - desc.set(OpenApiPathDsc.parameters, parameters.toArray(Glob[]::new)); - switch (operation.verb()) { - case post: - path.set(OpenApiPath.post, desc); - break; - case put: - path.set(OpenApiPath.put, desc); - break; - case patch: - path.set(OpenApiPath.patch, desc); - break; - case delete: - path.set(OpenApiPath.delete, desc); - break; - case get: - path.set(OpenApiPath.get, desc); - break; - } - } - - private MutableGlob buildSchema(GlobType bodyType, Map schemas) { - if (!schemas.containsKey(bodyType)) { - MutableGlob schema = OpenApiSchemaProperty.TYPE.instantiate(); - schemas.put(bodyType, schema); - schema.set(OpenApiSchemaProperty.name, bodyType.getName()); - schema.set(OpenApiSchemaProperty.type, "object"); - List param = new ArrayList<>(); - for (Field field : bodyType.getFields()) { - param.add(subType(field, schemas)); - } - schema.set(OpenApiSchemaProperty.properties, param.toArray(Glob[]::new)); - } - return OpenApiSchemaProperty.TYPE.instantiate() - .set(OpenApiSchemaProperty.ref, "#/components/schemas/" + bodyType.getName()); - } - - private Glob subType(Field field, Map schemas) { - final Ref p = new Ref<>(); - field.safeAccept(new FieldVisitor.AbstractWithErrorVisitor() { - - @Override - public void visitDouble(DoubleField field) throws Exception { - MutableGlob instantiate = OpenApiSchemaProperty.TYPE.instantiate() - .set(OpenApiSchemaProperty.name, field.getName()) - .set(OpenApiSchemaProperty.format, DOUBLE_STR) - .set(OpenApiSchemaProperty.type, NUMBER_STR); - p.set(instantiate); - } - - @Override - public void visitDoubleArray(DoubleArrayField field) throws Exception { - MutableGlob instantiate = OpenApiSchemaProperty.TYPE.instantiate() - .set(OpenApiSchemaProperty.name, field.getName()) - .set(OpenApiSchemaProperty.type, ARRAY_STR) - .set(OpenApiSchemaProperty.items, - OpenApiSchemaProperty.TYPE.instantiate() - .set(OpenApiSchemaProperty.format, DOUBLE_STR) - .set(OpenApiSchemaProperty.type, NUMBER_STR)); - p.set(instantiate); - } - - @Override - public void visitBigDecimal(BigDecimalField field) throws Exception { - MutableGlob instantiate = OpenApiSchemaProperty.TYPE.instantiate() - .set(OpenApiSchemaProperty.name, field.getName()) - .set(OpenApiSchemaProperty.format, BIG_DECIMAL_STR) - .set(OpenApiSchemaProperty.type, STRING_STR); - p.set(instantiate); - } - - @Override - public void visitBigDecimalArray(BigDecimalArrayField field) throws Exception { - MutableGlob instantiate = OpenApiSchemaProperty.TYPE.instantiate() - .set(OpenApiSchemaProperty.name, field.getName()) - .set(OpenApiSchemaProperty.type, ARRAY_STR) - .set(OpenApiSchemaProperty.items, - OpenApiSchemaProperty.TYPE.instantiate() - .set(OpenApiSchemaProperty.format, BIG_DECIMAL_STR) - .set(OpenApiSchemaProperty.type, STRING_STR)); - p.set(instantiate); - } - - @Override - public void visitInteger(IntegerField field) throws Exception { - MutableGlob instantiate = OpenApiSchemaProperty.TYPE.instantiate() - .set(OpenApiSchemaProperty.name, field.getName()) - .set(OpenApiSchemaProperty.format, "int32") - .set(OpenApiSchemaProperty.type, "integer"); - p.set(instantiate); - } - - @Override - public void visitDate(DateField field) throws Exception { - MutableGlob instantiate = OpenApiSchemaProperty.TYPE.instantiate() - .set(OpenApiSchemaProperty.name, field.getName()) - .set(OpenApiSchemaProperty.format, "date") - .set(OpenApiSchemaProperty.type, STRING_STR); - p.set(instantiate); - } - - @Override - public void visitDateTime(DateTimeField field) throws Exception { - MutableGlob instantiate = OpenApiSchemaProperty.TYPE.instantiate() - .set(OpenApiSchemaProperty.name, field.getName()) - .set(OpenApiSchemaProperty.format, "date-time") - .set(OpenApiSchemaProperty.type, STRING_STR); - p.set(instantiate); - } - - @Override - public void visitString(StringField field) throws Exception { - MutableGlob instantiate = OpenApiSchemaProperty.TYPE.instantiate() - .set(OpenApiSchemaProperty.name, field.getName()) - .set(OpenApiSchemaProperty.type, STRING_STR); - p.set(instantiate); - } - - @Override - public void visitLong(LongField field) throws Exception { - MutableGlob instantiate = OpenApiSchemaProperty.TYPE.instantiate() - .set(OpenApiSchemaProperty.name, field.getName()) - .set(OpenApiSchemaProperty.format, "int64") - .set(OpenApiSchemaProperty.type, "integer"); - p.set(instantiate); - } - - @Override - public void visitLongArray(LongArrayField field) throws Exception { - MutableGlob instantiate = OpenApiSchemaProperty.TYPE.instantiate() - .set(OpenApiSchemaProperty.name, field.getName()) - .set(OpenApiSchemaProperty.type, ARRAY_STR) - .set(OpenApiSchemaProperty.items, - OpenApiSchemaProperty.TYPE.instantiate() - .set(OpenApiSchemaProperty.format, "int64") - .set(OpenApiSchemaProperty.type, "integer")); - p.set(instantiate); - } - - @Override - public void visitIntegerArray(IntegerArrayField field) throws Exception { - MutableGlob instantiate = OpenApiSchemaProperty.TYPE.instantiate() - .set(OpenApiSchemaProperty.name, field.getName()) - .set(OpenApiSchemaProperty.type, ARRAY_STR) - .set(OpenApiSchemaProperty.items, - OpenApiSchemaProperty.TYPE.instantiate() - .set(OpenApiSchemaProperty.format, "int32") - .set(OpenApiSchemaProperty.type, "integer")); - p.set(instantiate); - } - - @Override - public void visitBoolean(BooleanField field) throws Exception { - MutableGlob instantiate = OpenApiSchemaProperty.TYPE.instantiate() - .set(OpenApiSchemaProperty.name, field.getName()) - .set(OpenApiSchemaProperty.type, "boolean"); - p.set(instantiate); - } - - @Override - public void visitBooleanArray(BooleanArrayField field) throws Exception { - MutableGlob instantiate = OpenApiSchemaProperty.TYPE.instantiate() - .set(OpenApiSchemaProperty.name, field.getName()) - .set(OpenApiSchemaProperty.type, ARRAY_STR) - .set(OpenApiSchemaProperty.items, - OpenApiSchemaProperty.TYPE.instantiate() - .set(OpenApiSchemaProperty.type, "boolean")); - p.set(instantiate); - } - - @Override - public void visitStringArray(StringArrayField field) throws Exception { - MutableGlob instantiate = OpenApiSchemaProperty.TYPE.instantiate() - .set(OpenApiSchemaProperty.name, field.getName()) - .set(OpenApiSchemaProperty.type, ARRAY_STR) - .set(OpenApiSchemaProperty.items, - OpenApiSchemaProperty.TYPE.instantiate() - .set(OpenApiSchemaProperty.type, STRING_STR)); - p.set(instantiate); - } - - @Override - public void visitGlob(GlobField field) throws Exception { - MutableGlob ref = buildSchema(field.getTargetType(), schemas); - ref.set(OpenApiSchemaProperty.name, field.getName()); -// .set(OpenApiSchemaProperty.format, "binary") -// .set(OpenApiSchemaProperty.type, "object"); - p.set(ref); - } - - @Override - public void visitUnionGlob(GlobUnionField field) throws Exception { - List sub = extractUnion(field.getTargetTypes()); - - MutableGlob schema = OpenApiSchemaProperty.TYPE.instantiate(); - schema.set(OpenApiSchemaProperty.name, field.getName()); - schema.set(OpenApiSchemaProperty.anyOf, sub.toArray(Glob[]::new)); -// ref.set(OpenApiSchemaProperty.name, field.getName()); -// .set(OpenApiSchemaProperty.format, "binary") -// .set(OpenApiSchemaProperty.type, "object"); - p.set(schema); - } - - private List extractUnion(Collection targetTypes) { - List sub = new ArrayList<>(); - for (GlobType targetType : targetTypes) { - String name = targetType.getName() + "_union"; - var first = schemas.entrySet().stream().filter(e -> e.getKey().getName().equals(name)).findFirst(); - sub.add(first.map(entry -> - OpenApiSchemaProperty.TYPE.instantiate() - .set(OpenApiSchemaProperty.ref, "#/components/schemas/" + entry.getKey().getName())) - .orElseGet(() -> buildSchema( - GlobTypeBuilderFactory.create(name) - .addGlobField(targetType.getName(), Collections.emptyList(), targetType).get(), schemas))); - } - return sub; - } - - public void visitUnionGlobArray(GlobArrayUnionField field) throws Exception { - List sub = extractUnion(field.getTargetTypes()); - MutableGlob ref = OpenApiSchemaProperty.TYPE.instantiate() - .set(OpenApiSchemaProperty.name, field.getName()) - .set(OpenApiSchemaProperty.type, ARRAY_STR) - .set(OpenApiSchemaProperty.items, OpenApiSchemaProperty.TYPE.instantiate() - .set(OpenApiSchemaProperty.anyOf, sub.toArray(Glob[]::new)) - ); - p.set(ref); - } - - public void visitGlobArray(GlobArrayField field) throws Exception { - MutableGlob ref = OpenApiSchemaProperty.TYPE.instantiate() - .set(OpenApiSchemaProperty.name, field.getName()) - .set(OpenApiSchemaProperty.type, ARRAY_STR) - .set(OpenApiSchemaProperty.items, - buildSchema(field.getTargetType(), schemas)); - p.set(ref); - } - - @Override - public void visitBlob(BlobField field) throws Exception { - MutableGlob ref = OpenApiSchemaProperty.TYPE.instantiate() - .set(OpenApiSchemaProperty.name, field.getName()) - .set(OpenApiSchemaProperty.format, "binary") - .set(OpenApiSchemaProperty.type, "object"); - p.set(ref); - } - }); - return p.get(); - } - - - interface BootStratServer { - - void setRequestRouter(final HttpRequestMapper> requestRouter); - - HttpAsyncServer create(); - } - - public HttpAsyncServer init(BootStratServer serverBootstrap) { - - RequestDispatcher requestDispatcher = new RequestDispatcher(serverInfo); - for (Map.Entry stringVerbEntry : verbMap.entrySet()) { - Verb verb = stringVerbEntry.getValue(); - GlobHttpRequestHandlerBuilder globHttpRequestHandler = new GlobHttpRequestHandlerBuilder(serverInfo, verb.complete()); - Collection path = globHttpRequestHandler.createRegExp(); - requestDispatcher.register(path, globHttpRequestHandler); - for (HttpOperation operation : stringVerbEntry.getValue().operations) { - MutableGlob logs = HttpAPIDesc.TYPE.instantiate() - .set(HttpAPIDesc.serverName, serverInfo) - .set(HttpAPIDesc.url, stringVerbEntry.getKey()) - .set(HttpAPIDesc.queryParam, GSonUtils.encodeGlobType(operation.getQueryParamType())) - .set(HttpAPIDesc.body, GSonUtils.encodeGlobType(operation.getBodyType())) - .set(HttpAPIDesc.returnType, GSonUtils.encodeGlobType(operation.getReturnType())) - .set(HttpAPIDesc.comment, operation.getComment()); - LOGGER.info(serverInfo + " Api : {}", GSonUtils.encode(logs, false)); - } - } -// if (Strings.isNotEmpty(serverInfo)) { -// serverBootstrap.setServerInfo(serverInfo); -// } - serverBootstrap.setRequestRouter((request, context) -> - () -> new HttpRequestHttpAsyncServerExchangeTree(requestDispatcher, request, context)); - return serverBootstrap.create(); - } - - public static class Server { - private final HttpAsyncServer server; - private final int port; - - public Server(HttpAsyncServer server, int port) { - this.server = server; - this.port = port; - } - - public int getPort() { - return port; - } - - public HttpAsyncServer getServer() { - return server; - } - } - - public Server startAndWaitForStartup(H2ServerBootstrap bootstrap, int wantedPort) { - HttpAsyncServer server = init(new BootStratServer() { - @Override - public void setRequestRouter(HttpRequestMapper> requestRouter) { - bootstrap.setRequestRouter(requestRouter); - } - - @Override - public HttpAsyncServer create() { - return bootstrap.create(); - } - }); - return initHttpServer(wantedPort, server); - } - - public Server startAndWaitForStartup(AsyncServerBootstrap bootstrap, int wantedPort) { - HttpAsyncServer server = init(new BootStratServer() { - @Override - public void setRequestRouter(HttpRequestMapper> requestRouter) { - bootstrap.setRequestRouter(requestRouter); - } - - @Override - public HttpAsyncServer create() { - return bootstrap.create(); - } - }); - return initHttpServer(wantedPort, server); - } - - private Server initHttpServer(int wantedPort, HttpAsyncServer server) { - try { - server.start(); - Future listen = server.listen(new InetSocketAddress(wantedPort), URIScheme.HTTP); - ListenerEndpoint listenerEndpoint = listen.get(); - InetSocketAddress address = (InetSocketAddress) listenerEndpoint.getAddress(); - int port = address.getPort(); - openApiDoc = createOpenApiDoc(port); - LOGGER.info(serverInfo + " OpenApi doc : {}", GSonUtils.encode(openApiDoc, false)); - return new Server(server, port); - } catch (Exception e) { - String message = serverInfo + " Fail to start server" + serverInfo; - LOGGER.error(message); - throw new RuntimeException(message, e); - } - } public interface InterceptBuilder { InterceptBuilder NULL = httpTreatment -> httpTreatment; @@ -641,67 +124,6 @@ public static class HttpAPIDesc { } - static class StrNode { - private final String serverInfo; - private SubStrNode[] subStrNodes = new SubStrNode[0]; - private SubStrNode[] subWithWildCard = new SubStrNode[0]; - - StrNode(String serverInfo) { - this.serverInfo = serverInfo; - } - - public GlobHttpRequestHandlerFactory createRequestHandler(String[] path, String method, String paramStr, boolean hasBody) { - for (SubStrNode subStrNode : this.subStrNodes) { - if (subStrNode.match(path)) { - return subStrNode.httpRequestHandlerBuilder.create(path, method, paramStr, hasBody); - } - } - return null; - } - - public GlobHttpRequestHandlerFactory findAndCreateRequestHandler(String[] path, String method, String paramStr, boolean hasBody) { - for (SubStrNode subStrNode : this.subWithWildCard) { - if (subStrNode.match(path)) { - return subStrNode.httpRequestHandlerBuilder.create(path, method, paramStr, hasBody); - } - } - return null; - } - - public void register(Collection path, GlobHttpRequestHandlerBuilder globHttpRequestHandler) { - subStrNodes = Arrays.copyOf(subStrNodes, subStrNodes.length + 1); - subStrNodes[subStrNodes.length - 1] = new SubStrNode(path, globHttpRequestHandler); - } - - public void registerWildcard(Collection path, GlobHttpRequestHandlerBuilder globHttpRequestHandler) { - subWithWildCard = Arrays.copyOf(subWithWildCard, subWithWildCard.length + 1); - subWithWildCard[subWithWildCard.length - 1] = new SubStrNode(path, globHttpRequestHandler); - } - } - - static class SubStrNode { - private final String[] path; - private final GlobHttpRequestHandlerBuilder httpRequestHandlerBuilder; - - public SubStrNode(Collection path, GlobHttpRequestHandlerBuilder globHttpRequestHandler) { - this.path = path.toArray(String[]::new); - this.httpRequestHandlerBuilder = globHttpRequestHandler; - } - - boolean match(String[] path) { - String[] strings = this.path; - for (int i = 0, stringsLength = strings.length; i < stringsLength; i++) { - String s = strings[i]; - if (s != null) { - if (!s.equals(path[i])) { - return false; - } - } - } - return true; - } - } - private static class AncapsulateInterceptBuilder implements InterceptBuilder { private final InterceptBuilder interceptBuilder; private final InterceptBuilder builder; @@ -720,123 +142,13 @@ public HttpTreatmentWithHeader create(HttpTreatmentWithHeader httpTreatment) { } } - private class OpenApiFieldVisitor extends FieldVisitor.AbstractWithErrorVisitor { - private Glob schema; - private Map schemas; - - public OpenApiFieldVisitor(Map schemas) { - this.schemas = schemas; - } - - @Override - public void visitInteger(IntegerField field) throws Exception { - createSchema("integer", "int32"); - } - - private void createSchema(String type, String format) { - schema = create(type, format); - } - - private MutableGlob create(String type, String format) { - MutableGlob set = OpenApiSchemaProperty.TYPE.instantiate() - .set(OpenApiSchemaProperty.type, type); - if (format != null) { - set.set(OpenApiSchemaProperty.format, format); - } - return set; - } - - @Override - public void visitDouble(DoubleField field) throws Exception { - createSchema(NUMBER_STR, DOUBLE_STR); - } - - @Override - public void visitString(StringField field) throws Exception { - createSchema(STRING_STR, null); - } - - @Override - public void visitBoolean(BooleanField field) throws Exception { - createSchema("boolean", null); - } - - @Override - public void visitLong(LongField field) throws Exception { - createSchema("integer", "int64"); - } - - @Override - public void visitStringArray(StringArrayField field) throws Exception { - createArray(STRING_STR, null); - } - - @Override - public void visitDoubleArray(DoubleArrayField field) throws Exception { - createArray(NUMBER_STR, DOUBLE_STR); - } - - @Override - public void visitIntegerArray(IntegerArrayField field) throws Exception { - createArray("integer", "int32"); - } - - @Override - public void visitLongArray(LongArrayField field) throws Exception { - createArray("integer", "int64"); - } - - @Override - public void visitDate(DateField field) throws Exception { - createSchema(STRING_STR, "date"); - } - - @Override - public void visitDateTime(DateTimeField field) throws Exception { - createSchema(STRING_STR, "date-time"); - } - - @Override - public void visitBooleanArray(BooleanArrayField field) throws Exception { - createArray("boolean", null); - } - - @Override - public void visitBigDecimal(BigDecimalField field) throws Exception { - createSchema(STRING_STR, BIG_DECIMAL_STR); - } - - @Override - public void visitBigDecimalArray(BigDecimalArrayField field) throws Exception { - createArray(STRING_STR, BIG_DECIMAL_STR); - } - - private void createArray(String type, String format) { - schema = OpenApiSchemaProperty.TYPE.instantiate() - .set(OpenApiSchemaProperty.type, ARRAY_STR) - .set(OpenApiSchemaProperty.items, create(type, format)); - } - - @Override - public void visitGlob(GlobField field) throws Exception { - schema = buildSchema(field.getGlobType(), schemas); - } - - @Override - public void visitGlobArray(GlobArrayField field) throws Exception { - schema = OpenApiSchemaProperty.TYPE.instantiate() - .set(OpenApiSchemaProperty.type, ARRAY_STR) - .set(OpenApiSchemaProperty.items, buildSchema(field.getTargetType(), schemas)); - - } - } public class Verb { - private final String url; - private final GlobType pathParameters; - private final Map headers = new LinkedHashMap<>(); + public final String url; + public final GlobType pathParameters; + public final Map headers = new LinkedHashMap<>(); // TODO: these are scoped - private List operations = new ArrayList<>(); + public List operations = new ArrayList<>(); public Verb(String url, GlobType pathParameters) { @@ -927,7 +239,7 @@ public void addHeader(String name, String value) { headers.put(name, value); } - HttpReceiver complete() { + public HttpReceiver complete() { DefaultHttpReceiver defaultHttpReceiver = new DefaultHttpReceiver(url, pathParameters, operations.toArray(new HttpOperation[0])); headers.forEach(defaultHttpReceiver::addHeader); return defaultHttpReceiver; diff --git a/src/main/java/org/globsframework/http/openapi/model/GlobOpenApi.java b/src/main/java/org/globsframework/http/openapi/model/GlobOpenApi.java new file mode 100644 index 0000000..6cc7642 --- /dev/null +++ b/src/main/java/org/globsframework/http/openapi/model/GlobOpenApi.java @@ -0,0 +1,548 @@ +package org.globsframework.http.openapi.model; + +import org.globsframework.core.metamodel.GlobType; +import org.globsframework.core.metamodel.GlobTypeBuilderFactory; +import org.globsframework.core.metamodel.annotations.Comment; +import org.globsframework.core.metamodel.fields.*; +import org.globsframework.core.model.Glob; +import org.globsframework.core.model.MutableGlob; +import org.globsframework.core.utils.Ref; +import org.globsframework.http.HttpOperation; +import org.globsframework.http.HttpServerRegister; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.*; + +public class GlobOpenApi { + private static final String DOUBLE_STR = "double"; + private static final String NUMBER_STR = "number"; + private static final String ARRAY_STR = "array"; + private static final String BIG_DECIMAL_STR = "big-decimal"; + private static final String STRING_STR = "string"; + private static final Logger log = LoggerFactory.getLogger(GlobOpenApi.class); + private Glob openApiDoc; + private final HttpServerRegister httpServerRegister; + + public GlobOpenApi(HttpServerRegister httpServerRegister) { + this.httpServerRegister = httpServerRegister; + } + + public Glob getOpenApiDoc() { + if (openApiDoc == null) { // if initOpenApiDoc was not called yet + initOpenApiDoc(-1); + } + return openApiDoc; + } + + public Glob createOpenApiDocByTags(String tag) { + List paths = new ArrayList<>(); + Arrays.stream(openApiDoc.getOrEmpty(OpenApiType.paths)).forEach(path -> { + boolean isPathSelected = + hasSelectedTag(path, OpenApiPath.get, tag) || + hasSelectedTag(path, OpenApiPath.put, tag) || + hasSelectedTag(path, OpenApiPath.post, tag) || + hasSelectedTag(path, OpenApiPath.delete, tag) || + hasSelectedTag(path, OpenApiPath.patch, tag); + if (isPathSelected) { + paths.add(path); + } + }); + + + return openApiDoc.duplicate().set(OpenApiType.paths, paths.toArray(Glob[]::new)); + } + + private boolean hasSelectedTag(Glob path, GlobField field, String targetScope) { + Glob pathDescription = path.get(field); + if (pathDescription == null) { + return false; + } + + String[] currentScopes = pathDescription.getOrEmpty(OpenApiPathDsc.tags); + return Arrays.asList(currentScopes).contains(targetScope); + } + + public void initOpenApiDoc(int port) { + Map schemas = new LinkedHashMap<>(); + List paths = new ArrayList<>(); + for (Map.Entry stringVerbEntry : httpServerRegister.verbMap.entrySet()) { + createVerbDoc(schemas, paths, stringVerbEntry); + } + if (openApiDoc != null) { + log.warn("replace previous openApi"); + } + + openApiDoc = OpenApiType.TYPE.instantiate() + .set(OpenApiType.openAPIVersion, "3.0.1") + .set(OpenApiType.info, OpenApiInfo.TYPE.instantiate() + .set(OpenApiInfo.description, httpServerRegister.serverInfo) + .set(OpenApiInfo.title, httpServerRegister.serverInfo) + .set(OpenApiInfo.version, "1.0") + ) + .set(OpenApiType.components, OpenApiComponents.TYPE.instantiate() + .set(OpenApiComponents.schemas, schemas.values().toArray(Glob[]::new))) + .set(OpenApiType.servers, new Glob[]{OpenApiServers.TYPE.instantiate() + .set(OpenApiServers.url, "http://localhost:" + port)}) + .set(OpenApiType.paths, paths.toArray(Glob[]::new)); + } + + private void createVerbDoc(Map schemas, List paths, Map.Entry stringVerbEntry) { + HttpServerRegister.Verb verb = stringVerbEntry.getValue(); + MutableGlob path = OpenApiPath.TYPE.instantiate(); + paths.add(path); + path.set(OpenApiPath.name, verb.url); + for (HttpOperation operation : stringVerbEntry.getValue().operations) { + createOperationDoc(schemas, verb, path, operation); + + } + } + + private void createOperationDoc(Map schemas, HttpServerRegister.Verb verb, MutableGlob path, HttpOperation operation) { + MutableGlob desc = OpenApiPathDsc.TYPE.instantiate(); + setOperationComment(operation, desc); + List parameters = getOperationPathParameters(schemas, verb); + addOperationQueryParameters(schemas, operation, parameters); + setOperationRequestBody(schemas, operation, desc); + setOperationTags(operation, desc); + setOperationReturnType(schemas, operation, desc); + setPathDescription(path, operation, desc, parameters); + } + + private void setOperationReturnType(Map schemas, HttpOperation operation, MutableGlob desc) { + GlobType returnType = operation.getReturnType(); + if (returnType == null) { + desc.set(OpenApiPathDsc.responses, new Glob[]{OpenApiResponses.TYPE + .instantiate() + .set(OpenApiResponses.description, "None") + .set(OpenApiResponses.code, "200")}); + } else { + desc.set(OpenApiPathDsc.responses, new Glob[]{OpenApiResponses.TYPE.instantiate() + .set(OpenApiResponses.code, "200") + .set(OpenApiResponses.description, + returnType.findOptAnnotation(Comment.UNIQUE_KEY) + .map(Comment.VALUE).orElse("None")) + .set(OpenApiResponses.content, new Glob[]{ + OpenApiBodyMimeType.TYPE.instantiate() + .set(OpenApiBodyMimeType.mimeType, "application/json") + .set(OpenApiBodyMimeType.schema, buildSchema(returnType, schemas))}) + }); + } + } + + private void setOperationComment(HttpOperation operation, MutableGlob desc) { + String comment = operation.getComment(); + if (comment != null) { + desc.set(OpenApiPathDsc.description, comment); + } + } + + private void setOperationTags(HttpOperation operation, MutableGlob desc) { + String[] tags = operation.getTags(); + if (tags != null) { + desc.set(OpenApiPathDsc.tags, tags); + } + } + + private void setOperationRequestBody(Map schemas, HttpOperation operation, MutableGlob desc) { + GlobType bodyType = operation.getBodyType(); + if (bodyType != null) { + desc.set(OpenApiPathDsc.requestBody, OpenApiRequestBody.TYPE.instantiate() + .set(OpenApiRequestBody.content, new Glob[]{OpenApiBodyMimeType.TYPE.instantiate() + .set(OpenApiBodyMimeType.mimeType, "application/json") + .set(OpenApiBodyMimeType.schema, buildSchema(bodyType, schemas))})); + } + } + + private void addOperationQueryParameters(Map schemas, HttpOperation operation, List parameters) { + GlobType queryParamType = operation.getQueryParamType(); + if (queryParamType != null) { + for (Field field : queryParamType.getFields()) { + OpenApiFieldVisitor openApiFieldVisitor = field.safeAccept(new OpenApiFieldVisitor(schemas)); + parameters.add(OpenApiParameter.TYPE.instantiate() + .set(OpenApiParameter.in, "query") + .set(OpenApiParameter.name, field.getName()) + .set(OpenApiParameter.required, true) + .set(OpenApiParameter.schema, openApiFieldVisitor.schema)); + } + } + } + + private List getOperationPathParameters(Map schemas, HttpServerRegister.Verb verb) { + List parameters = new ArrayList<>(); + if (verb.pathParameters != null) { + for (Field field : verb.pathParameters.getFields()) { + OpenApiFieldVisitor apiFieldVisitor = new OpenApiFieldVisitor(schemas); + OpenApiFieldVisitor openApiFieldVisitor = field.safeAccept(apiFieldVisitor); + parameters.add(OpenApiParameter.TYPE.instantiate() + .set(OpenApiParameter.in, "path") + .set(OpenApiParameter.name, field.getName()) + .set(OpenApiParameter.required, true) + .set(OpenApiParameter.schema, openApiFieldVisitor.schema)); + } + } + return parameters; + } + + private void setPathDescription(MutableGlob path, HttpOperation operation, MutableGlob desc, List parameters) { + desc.set(OpenApiPathDsc.parameters, parameters.toArray(Glob[]::new)); + switch (operation.verb()) { + case post: + path.set(OpenApiPath.post, desc); + break; + case put: + path.set(OpenApiPath.put, desc); + break; + case patch: + path.set(OpenApiPath.patch, desc); + break; + case delete: + path.set(OpenApiPath.delete, desc); + break; + case get: + path.set(OpenApiPath.get, desc); + break; + } + } + + private MutableGlob buildSchema(GlobType bodyType, Map schemas) { + if (!schemas.containsKey(bodyType)) { + MutableGlob schema = OpenApiSchemaProperty.TYPE.instantiate(); + schemas.put(bodyType, schema); + schema.set(OpenApiSchemaProperty.name, bodyType.getName()); + schema.set(OpenApiSchemaProperty.type, "object"); + List param = new ArrayList<>(); + for (Field field : bodyType.getFields()) { + param.add(subType(field, schemas)); + } + schema.set(OpenApiSchemaProperty.properties, param.toArray(Glob[]::new)); + } + return OpenApiSchemaProperty.TYPE.instantiate() + .set(OpenApiSchemaProperty.ref, "#/components/schemas/" + bodyType.getName()); + } + + private Glob subType(Field field, Map schemas) { + final Ref p = new Ref<>(); + field.safeAccept(new FieldVisitor.AbstractWithErrorVisitor() { + + @Override + public void visitDouble(DoubleField field) throws Exception { + MutableGlob instantiate = OpenApiSchemaProperty.TYPE.instantiate() + .set(OpenApiSchemaProperty.name, field.getName()) + .set(OpenApiSchemaProperty.format, DOUBLE_STR) + .set(OpenApiSchemaProperty.type, NUMBER_STR); + p.set(instantiate); + } + + @Override + public void visitDoubleArray(DoubleArrayField field) throws Exception { + MutableGlob instantiate = OpenApiSchemaProperty.TYPE.instantiate() + .set(OpenApiSchemaProperty.name, field.getName()) + .set(OpenApiSchemaProperty.type, ARRAY_STR) + .set(OpenApiSchemaProperty.items, + OpenApiSchemaProperty.TYPE.instantiate() + .set(OpenApiSchemaProperty.format, DOUBLE_STR) + .set(OpenApiSchemaProperty.type, NUMBER_STR)); + p.set(instantiate); + } + + @Override + public void visitBigDecimal(BigDecimalField field) throws Exception { + MutableGlob instantiate = OpenApiSchemaProperty.TYPE.instantiate() + .set(OpenApiSchemaProperty.name, field.getName()) + .set(OpenApiSchemaProperty.format, BIG_DECIMAL_STR) + .set(OpenApiSchemaProperty.type, STRING_STR); + p.set(instantiate); + } + + @Override + public void visitBigDecimalArray(BigDecimalArrayField field) throws Exception { + MutableGlob instantiate = OpenApiSchemaProperty.TYPE.instantiate() + .set(OpenApiSchemaProperty.name, field.getName()) + .set(OpenApiSchemaProperty.type, ARRAY_STR) + .set(OpenApiSchemaProperty.items, + OpenApiSchemaProperty.TYPE.instantiate() + .set(OpenApiSchemaProperty.format, BIG_DECIMAL_STR) + .set(OpenApiSchemaProperty.type, STRING_STR)); + p.set(instantiate); + } + + @Override + public void visitInteger(IntegerField field) throws Exception { + MutableGlob instantiate = OpenApiSchemaProperty.TYPE.instantiate() + .set(OpenApiSchemaProperty.name, field.getName()) + .set(OpenApiSchemaProperty.format, "int32") + .set(OpenApiSchemaProperty.type, "integer"); + p.set(instantiate); + } + + @Override + public void visitDate(DateField field) throws Exception { + MutableGlob instantiate = OpenApiSchemaProperty.TYPE.instantiate() + .set(OpenApiSchemaProperty.name, field.getName()) + .set(OpenApiSchemaProperty.format, "date") + .set(OpenApiSchemaProperty.type, STRING_STR); + p.set(instantiate); + } + + @Override + public void visitDateTime(DateTimeField field) throws Exception { + MutableGlob instantiate = OpenApiSchemaProperty.TYPE.instantiate() + .set(OpenApiSchemaProperty.name, field.getName()) + .set(OpenApiSchemaProperty.format, "date-time") + .set(OpenApiSchemaProperty.type, STRING_STR); + p.set(instantiate); + } + + @Override + public void visitString(StringField field) throws Exception { + MutableGlob instantiate = OpenApiSchemaProperty.TYPE.instantiate() + .set(OpenApiSchemaProperty.name, field.getName()) + .set(OpenApiSchemaProperty.type, STRING_STR); + p.set(instantiate); + } + + @Override + public void visitLong(LongField field) throws Exception { + MutableGlob instantiate = OpenApiSchemaProperty.TYPE.instantiate() + .set(OpenApiSchemaProperty.name, field.getName()) + .set(OpenApiSchemaProperty.format, "int64") + .set(OpenApiSchemaProperty.type, "integer"); + p.set(instantiate); + } + + @Override + public void visitLongArray(LongArrayField field) throws Exception { + MutableGlob instantiate = OpenApiSchemaProperty.TYPE.instantiate() + .set(OpenApiSchemaProperty.name, field.getName()) + .set(OpenApiSchemaProperty.type, ARRAY_STR) + .set(OpenApiSchemaProperty.items, + OpenApiSchemaProperty.TYPE.instantiate() + .set(OpenApiSchemaProperty.format, "int64") + .set(OpenApiSchemaProperty.type, "integer")); + p.set(instantiate); + } + + @Override + public void visitIntegerArray(IntegerArrayField field) throws Exception { + MutableGlob instantiate = OpenApiSchemaProperty.TYPE.instantiate() + .set(OpenApiSchemaProperty.name, field.getName()) + .set(OpenApiSchemaProperty.type, ARRAY_STR) + .set(OpenApiSchemaProperty.items, + OpenApiSchemaProperty.TYPE.instantiate() + .set(OpenApiSchemaProperty.format, "int32") + .set(OpenApiSchemaProperty.type, "integer")); + p.set(instantiate); + } + + @Override + public void visitBoolean(BooleanField field) throws Exception { + MutableGlob instantiate = OpenApiSchemaProperty.TYPE.instantiate() + .set(OpenApiSchemaProperty.name, field.getName()) + .set(OpenApiSchemaProperty.type, "boolean"); + p.set(instantiate); + } + + @Override + public void visitBooleanArray(BooleanArrayField field) throws Exception { + MutableGlob instantiate = OpenApiSchemaProperty.TYPE.instantiate() + .set(OpenApiSchemaProperty.name, field.getName()) + .set(OpenApiSchemaProperty.type, ARRAY_STR) + .set(OpenApiSchemaProperty.items, + OpenApiSchemaProperty.TYPE.instantiate() + .set(OpenApiSchemaProperty.type, "boolean")); + p.set(instantiate); + } + + @Override + public void visitStringArray(StringArrayField field) throws Exception { + MutableGlob instantiate = OpenApiSchemaProperty.TYPE.instantiate() + .set(OpenApiSchemaProperty.name, field.getName()) + .set(OpenApiSchemaProperty.type, ARRAY_STR) + .set(OpenApiSchemaProperty.items, + OpenApiSchemaProperty.TYPE.instantiate() + .set(OpenApiSchemaProperty.type, STRING_STR)); + p.set(instantiate); + } + + @Override + public void visitGlob(GlobField field) throws Exception { + MutableGlob ref = buildSchema(field.getTargetType(), schemas); + ref.set(OpenApiSchemaProperty.name, field.getName()); +// .set(OpenApiSchemaProperty.format, "binary") +// .set(OpenApiSchemaProperty.type, "object"); + p.set(ref); + } + + @Override + public void visitUnionGlob(GlobUnionField field) throws Exception { + List sub = extractUnion(field.getTargetTypes()); + + MutableGlob schema = OpenApiSchemaProperty.TYPE.instantiate(); + schema.set(OpenApiSchemaProperty.name, field.getName()); + schema.set(OpenApiSchemaProperty.anyOf, sub.toArray(Glob[]::new)); +// ref.set(OpenApiSchemaProperty.name, field.getName()); +// .set(OpenApiSchemaProperty.format, "binary") +// .set(OpenApiSchemaProperty.type, "object"); + p.set(schema); + } + + private List extractUnion(Collection targetTypes) { + List sub = new ArrayList<>(); + for (GlobType targetType : targetTypes) { + String name = targetType.getName() + "_union"; + var first = schemas.entrySet().stream().filter(e -> e.getKey().getName().equals(name)).findFirst(); + sub.add(first.map(entry -> + OpenApiSchemaProperty.TYPE.instantiate() + .set(OpenApiSchemaProperty.ref, "#/components/schemas/" + entry.getKey().getName())) + .orElseGet(() -> buildSchema( + GlobTypeBuilderFactory.create(name) + .addGlobField(targetType.getName(), Collections.emptyList(), targetType).get(), schemas))); + } + return sub; + } + + public void visitUnionGlobArray(GlobArrayUnionField field) throws Exception { + List sub = extractUnion(field.getTargetTypes()); + MutableGlob ref = OpenApiSchemaProperty.TYPE.instantiate() + .set(OpenApiSchemaProperty.name, field.getName()) + .set(OpenApiSchemaProperty.type, ARRAY_STR) + .set(OpenApiSchemaProperty.items, OpenApiSchemaProperty.TYPE.instantiate() + .set(OpenApiSchemaProperty.anyOf, sub.toArray(Glob[]::new)) + ); + p.set(ref); + } + + public void visitGlobArray(GlobArrayField field) throws Exception { + MutableGlob ref = OpenApiSchemaProperty.TYPE.instantiate() + .set(OpenApiSchemaProperty.name, field.getName()) + .set(OpenApiSchemaProperty.type, ARRAY_STR) + .set(OpenApiSchemaProperty.items, + buildSchema(field.getTargetType(), schemas)); + p.set(ref); + } + + @Override + public void visitBlob(BlobField field) throws Exception { + MutableGlob ref = OpenApiSchemaProperty.TYPE.instantiate() + .set(OpenApiSchemaProperty.name, field.getName()) + .set(OpenApiSchemaProperty.format, "binary") + .set(OpenApiSchemaProperty.type, "object"); + p.set(ref); + } + }); + return p.get(); + } + + private class OpenApiFieldVisitor extends FieldVisitor.AbstractWithErrorVisitor { + private Glob schema; + private Map schemas; + + public OpenApiFieldVisitor(Map schemas) { + this.schemas = schemas; + } + + @Override + public void visitInteger(IntegerField field) throws Exception { + createSchema("integer", "int32"); + } + + private void createSchema(String type, String format) { + schema = create(type, format); + } + + private MutableGlob create(String type, String format) { + MutableGlob set = OpenApiSchemaProperty.TYPE.instantiate() + .set(OpenApiSchemaProperty.type, type); + if (format != null) { + set.set(OpenApiSchemaProperty.format, format); + } + return set; + } + + @Override + public void visitDouble(DoubleField field) throws Exception { + createSchema(NUMBER_STR, DOUBLE_STR); + } + + @Override + public void visitString(StringField field) throws Exception { + createSchema(STRING_STR, null); + } + + @Override + public void visitBoolean(BooleanField field) throws Exception { + createSchema("boolean", null); + } + + @Override + public void visitLong(LongField field) throws Exception { + createSchema("integer", "int64"); + } + + @Override + public void visitStringArray(StringArrayField field) throws Exception { + createArray(STRING_STR, null); + } + + @Override + public void visitDoubleArray(DoubleArrayField field) throws Exception { + createArray(NUMBER_STR, DOUBLE_STR); + } + + @Override + public void visitIntegerArray(IntegerArrayField field) throws Exception { + createArray("integer", "int32"); + } + + @Override + public void visitLongArray(LongArrayField field) throws Exception { + createArray("integer", "int64"); + } + + @Override + public void visitDate(DateField field) throws Exception { + createSchema(STRING_STR, "date"); + } + + @Override + public void visitDateTime(DateTimeField field) throws Exception { + createSchema(STRING_STR, "date-time"); + } + + @Override + public void visitBooleanArray(BooleanArrayField field) throws Exception { + createArray("boolean", null); + } + + @Override + public void visitBigDecimal(BigDecimalField field) throws Exception { + createSchema(STRING_STR, BIG_DECIMAL_STR); + } + + @Override + public void visitBigDecimalArray(BigDecimalArrayField field) throws Exception { + createArray(STRING_STR, BIG_DECIMAL_STR); + } + + private void createArray(String type, String format) { + schema = OpenApiSchemaProperty.TYPE.instantiate() + .set(OpenApiSchemaProperty.type, ARRAY_STR) + .set(OpenApiSchemaProperty.items, create(type, format)); + } + + @Override + public void visitGlob(GlobField field) throws Exception { + schema = buildSchema(field.getGlobType(), schemas); + } + + @Override + public void visitGlobArray(GlobArrayField field) throws Exception { + schema = OpenApiSchemaProperty.TYPE.instantiate() + .set(OpenApiSchemaProperty.type, ARRAY_STR) + .set(OpenApiSchemaProperty.items, buildSchema(field.getTargetType(), schemas)); + + } + } + +} diff --git a/src/main/java/org/globsframework/http/server/apache/BootStratServer.java b/src/main/java/org/globsframework/http/server/apache/BootStratServer.java new file mode 100644 index 0000000..3fe7d8e --- /dev/null +++ b/src/main/java/org/globsframework/http/server/apache/BootStratServer.java @@ -0,0 +1,13 @@ +package org.globsframework.http.server.apache; + +import org.apache.hc.core5.function.Supplier; +import org.apache.hc.core5.http.HttpRequestMapper; +import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncServer; +import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler; + +public interface BootStratServer { + + void setRequestRouter(final HttpRequestMapper> requestRouter); + + HttpAsyncServer create(); +} diff --git a/src/main/java/org/globsframework/http/DefaultGlobHttpRequestHandler.java b/src/main/java/org/globsframework/http/server/apache/DefaultGlobHttpRequestHandler.java similarity index 92% rename from src/main/java/org/globsframework/http/DefaultGlobHttpRequestHandler.java rename to src/main/java/org/globsframework/http/server/apache/DefaultGlobHttpRequestHandler.java index 61334af..fb58afc 100644 --- a/src/main/java/org/globsframework/http/DefaultGlobHttpRequestHandler.java +++ b/src/main/java/org/globsframework/http/server/apache/DefaultGlobHttpRequestHandler.java @@ -1,4 +1,4 @@ -package org.globsframework.http; +package org.globsframework.http.server.apache; import org.apache.commons.fileupload.MultipartStream; import org.apache.hc.core5.http.HttpException; @@ -10,13 +10,11 @@ import org.apache.hc.core5.http.nio.ResponseChannel; import org.apache.hc.core5.http.protocol.HttpContext; import org.globsframework.core.metamodel.GlobType; -import org.globsframework.core.metamodel.fields.Field; -import org.globsframework.core.metamodel.fields.GlobArrayField; -import org.globsframework.core.metamodel.fields.GlobField; -import org.globsframework.core.metamodel.fields.IntegerField; +import org.globsframework.core.metamodel.fields.*; import org.globsframework.core.model.Glob; import org.globsframework.core.model.MutableGlob; import org.globsframework.core.utils.ReusableByteArrayOutputStream; +import org.globsframework.http.*; import org.globsframework.http.model.HttpBodyData; import org.globsframework.http.model.HttpGlobResponse; import org.globsframework.http.model.StatusCode; @@ -35,7 +33,7 @@ import static java.nio.charset.StandardCharsets.UTF_8; -class DefaultGlobHttpRequestHandler implements GlobHttpRequestHandler { +public class DefaultGlobHttpRequestHandler implements GlobHttpRequestHandler { private static final Logger LOGGER = LoggerFactory.getLogger("org.globsframework.http.DefaultGlobHttpRequestHandler"); public static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; private final HttpOperation operation; @@ -46,6 +44,7 @@ class DefaultGlobHttpRequestHandler implements GlobHttpRequestHandler { private final ResponseChannel responseChannel; private final HttpContext context; private final Glob header; + private final Header[] requestHeaders; private DataToSendProvider stream; private MultiByteArrayInputStream multiByteArrayInputStream; private long responseSize; @@ -60,8 +59,9 @@ public DefaultGlobHttpRequestHandler(HttpOperation operation, Glob urlGlob, Glob this.requestEntityDetails = requestEntityDetails; this.responseChannel = responseChannel; this.context = context; + requestHeaders = request.getHeaders(); GlobType headerType = operation.getHeaderType(); - this.header = headerType != null ? parseHeader(headerType, request.getHeaders()) : null; + this.header = headerType != null ? parseHeader(headerType, requestHeaders) : null; } private Glob parseHeader(GlobType headerType, Header[] allHeaders) { @@ -70,7 +70,18 @@ private Glob parseHeader(GlobType headerType, Header[] allHeaders) { final String name = allHeader.getName(); final Field field = headerType.findField(name); if (field != null) { - instance.set(field.asStringField(), allHeader.getValue()); + if (field instanceof StringField) { + instance.set(field.asStringField(), allHeader.getValue()); + } + else if (field instanceof IntegerField) { + instance.set(field.asIntegerField(), Integer.parseInt(allHeader.getValue())); + } + else if (field instanceof LongField) { + instance.set(field.asLongField(), Long.parseLong(allHeader.getValue())); + } + else if (field instanceof BooleanField) { + instance.set(field.asBooleanField(), Boolean.parseBoolean(allHeader.getValue())); + } } } return instance; @@ -149,8 +160,8 @@ private void callHandler(HttpInputData inputData) { manageException(throwable); } } else if (httpOutputData != null) { - if (httpOutputData.isGlob()) { - Glob glob = httpOutputData.getGlob(); + if (httpOutputData instanceof HttpOutputData.GlobHttpOutputData outputData) { + Glob glob = outputData.getGlob(); if (glob == null) { send204(); return; @@ -165,7 +176,7 @@ private void callHandler(HttpInputData inputData) { } MultiBufferOutputStream out = new MultiBufferOutputStream(); OutputStreamWriter streamWriter = new OutputStreamWriter(out); - GSonUtils.encode(streamWriter, httpOutputData.getGlob(), false); + GSonUtils.encode(streamWriter, outputData.getGlob(), false); try { streamWriter.close(); } catch (IOException e) { @@ -174,8 +185,8 @@ private void callHandler(HttpInputData inputData) { List data = out.data(); stream = () -> data.isEmpty() ? null : data.remove(0); responseSize = out.size(); - } else { - HttpOutputData.SizedStream data = httpOutputData.getStream(); + } else if (httpOutputData instanceof HttpOutputData.KnownSizeStreamHttpOutputData outputData) { + HttpOutputData.SizedStream data = outputData.getStream(); if (data == null || data.size() == 0L) { send204(); return; @@ -184,7 +195,7 @@ private void callHandler(HttpInputData inputData) { byte[] buffer = new byte[8192]; // can be reused public ByteBuffer nextBufferToSend() { - int read = 0; + int read; try { read = data.stream().read(buffer); } catch (IOException e) { @@ -328,7 +339,7 @@ interface DataToSendProvider { ByteBuffer nextBufferToSend(); } - // synchronized because call at sendResponse but can also be called but listen port on io write allowed. + // synchronized because call at sendResponse but can also be called buy listen thread on io write allowed. public synchronized void produceResponse(DataStreamChannel channel) throws IOException { if (currentResponseBuffer != null && currentResponseBuffer.hasRemaining()) { channel.write(currentResponseBuffer); diff --git a/src/main/java/org/globsframework/http/server/apache/GlobHttpApacheBuilder.java b/src/main/java/org/globsframework/http/server/apache/GlobHttpApacheBuilder.java new file mode 100644 index 0000000..da9099a --- /dev/null +++ b/src/main/java/org/globsframework/http/server/apache/GlobHttpApacheBuilder.java @@ -0,0 +1,104 @@ +package org.globsframework.http.server.apache; + +import org.apache.hc.core5.function.Supplier; +import org.apache.hc.core5.http.HttpRequestMapper; +import org.apache.hc.core5.http.URIScheme; +import org.apache.hc.core5.http.impl.bootstrap.AsyncServerBootstrap; +import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncServer; +import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler; +import org.apache.hc.core5.http2.impl.nio.bootstrap.H2ServerBootstrap; +import org.apache.hc.core5.reactor.ListenerEndpoint; +import org.globsframework.core.model.MutableGlob; +import org.globsframework.http.HttpOperation; +import org.globsframework.http.HttpServerRegister; +import org.globsframework.json.GSonUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.net.InetSocketAddress; +import java.util.Collection; +import java.util.Map; +import java.util.concurrent.Future; + +public class GlobHttpApacheBuilder { + private static final Logger LOGGER = LoggerFactory.getLogger(GlobHttpApacheBuilder.class); + private final HttpServerRegister httpServerRegister; + + public GlobHttpApacheBuilder(HttpServerRegister httpServerRegister) { + this.httpServerRegister = httpServerRegister; + } + + private HttpAsyncServer init(BootStratServer serverBootstrap) { + RequestDispatcher requestDispatcher = new RequestDispatcher(httpServerRegister.serverInfo); + for (Map.Entry stringVerbEntry : httpServerRegister.verbMap.entrySet()) { + HttpServerRegister.Verb verb = stringVerbEntry.getValue(); + GlobHttpRequestHandlerBuilder globHttpRequestHandler = new GlobHttpRequestHandlerBuilder(httpServerRegister.serverInfo, verb.complete()); + Collection path = globHttpRequestHandler.createRegExp(); + requestDispatcher.register(path, globHttpRequestHandler); + + for (HttpOperation operation : stringVerbEntry.getValue().operations) { + MutableGlob logs = HttpServerRegister.HttpAPIDesc.TYPE.instantiate() + .set(HttpServerRegister.HttpAPIDesc.serverName, httpServerRegister.serverInfo) + .set(HttpServerRegister.HttpAPIDesc.url, stringVerbEntry.getKey()) + .set(HttpServerRegister.HttpAPIDesc.queryParam, GSonUtils.encodeGlobType(operation.getQueryParamType())) + .set(HttpServerRegister.HttpAPIDesc.body, GSonUtils.encodeGlobType(operation.getBodyType())) + .set(HttpServerRegister.HttpAPIDesc.returnType, GSonUtils.encodeGlobType(operation.getReturnType())) + .set(HttpServerRegister.HttpAPIDesc.comment, operation.getComment()); + LOGGER.info(httpServerRegister.serverInfo + " Api : {}", GSonUtils.encode(logs, false)); + } + } +// if (Strings.isNotEmpty(serverInfo)) { +// serverBootstrap.setServerInfo(serverInfo); +// } + serverBootstrap.setRequestRouter((request, context) -> + () -> new HttpRequestHttpAsyncServerExchangeTree(requestDispatcher, request, context)); + return serverBootstrap.create(); + } + + public Server startAndWaitForStartup(H2ServerBootstrap bootstrap, int wantedPort) { + HttpAsyncServer server = init(new BootStratServer() { + @Override + public void setRequestRouter(HttpRequestMapper> requestRouter) { + bootstrap.setRequestRouter(requestRouter); + } + + @Override + public HttpAsyncServer create() { + return bootstrap.create(); + } + }); + return initHttpServer(wantedPort, server); + } + + public Server startAndWaitForStartup(AsyncServerBootstrap bootstrap, int wantedPort) { + HttpAsyncServer server = init(new BootStratServer() { + @Override + public void setRequestRouter(HttpRequestMapper> requestRouter) { + bootstrap.setRequestRouter(requestRouter); + } + + @Override + public HttpAsyncServer create() { + return bootstrap.create(); + } + }); + return initHttpServer(wantedPort, server); + } + + private Server initHttpServer(int wantedPort, HttpAsyncServer server) { + try { + server.start(); + Future listen = server.listen(new InetSocketAddress(wantedPort), URIScheme.HTTP); + ListenerEndpoint listenerEndpoint = listen.get(); + InetSocketAddress address = (InetSocketAddress) listenerEndpoint.getAddress(); + int port = address.getPort(); + LOGGER.info(httpServerRegister.serverInfo); + return new Server(server, port); + } catch (Exception e) { + String message = " Fail to start server" + httpServerRegister.serverInfo; + LOGGER.error(message); + throw new RuntimeException(message, e); + } + } + +} diff --git a/src/main/java/org/globsframework/http/GlobHttpRequestHandlerBuilder.java b/src/main/java/org/globsframework/http/server/apache/GlobHttpRequestHandlerBuilder.java similarity index 99% rename from src/main/java/org/globsframework/http/GlobHttpRequestHandlerBuilder.java rename to src/main/java/org/globsframework/http/server/apache/GlobHttpRequestHandlerBuilder.java index 9586aa1..5796eeb 100644 --- a/src/main/java/org/globsframework/http/GlobHttpRequestHandlerBuilder.java +++ b/src/main/java/org/globsframework/http/server/apache/GlobHttpRequestHandlerBuilder.java @@ -1,4 +1,4 @@ -package org.globsframework.http; +package org.globsframework.http.server.apache; import org.apache.hc.client5.http.async.methods.SimpleHttpResponse; import org.apache.hc.client5.http.classic.methods.*; @@ -18,6 +18,7 @@ import org.globsframework.core.model.Glob; import org.globsframework.core.model.MutableGlob; import org.globsframework.core.utils.Strings; +import org.globsframework.http.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/src/main/java/org/globsframework/http/HttpRequestHttpAsyncServerExchangeTree.java b/src/main/java/org/globsframework/http/server/apache/HttpRequestHttpAsyncServerExchangeTree.java similarity index 95% rename from src/main/java/org/globsframework/http/HttpRequestHttpAsyncServerExchangeTree.java rename to src/main/java/org/globsframework/http/server/apache/HttpRequestHttpAsyncServerExchangeTree.java index cd46509..ada8999 100644 --- a/src/main/java/org/globsframework/http/HttpRequestHttpAsyncServerExchangeTree.java +++ b/src/main/java/org/globsframework/http/server/apache/HttpRequestHttpAsyncServerExchangeTree.java @@ -1,4 +1,4 @@ -package org.globsframework.http; +package org.globsframework.http.server.apache; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.Header; @@ -10,6 +10,8 @@ import org.apache.hc.core5.http.nio.DataStreamChannel; import org.apache.hc.core5.http.nio.ResponseChannel; import org.apache.hc.core5.http.protocol.HttpContext; +import org.globsframework.http.GlobHttpRequestHandler; +import org.globsframework.http.GlobHttpRequestHandlerFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/src/main/java/org/globsframework/http/RequestDispatcher.java b/src/main/java/org/globsframework/http/server/apache/RequestDispatcher.java similarity index 91% rename from src/main/java/org/globsframework/http/RequestDispatcher.java rename to src/main/java/org/globsframework/http/server/apache/RequestDispatcher.java index 70cf36a..cacdcb3 100644 --- a/src/main/java/org/globsframework/http/RequestDispatcher.java +++ b/src/main/java/org/globsframework/http/server/apache/RequestDispatcher.java @@ -1,11 +1,13 @@ -package org.globsframework.http; +package org.globsframework.http.server.apache; + +import org.globsframework.http.GlobHttpRequestHandlerFactory; import java.util.Arrays; import java.util.Collection; -class RequestDispatcher { +public class RequestDispatcher { private final String serverInfo; - private HttpServerRegister.StrNode[] nodes = new HttpServerRegister.StrNode[0]; + private StrNode[] nodes = new StrNode[0]; public RequestDispatcher(String serverInfo) { this.serverInfo = serverInfo; @@ -54,7 +56,7 @@ public void register(Collection path, GlobHttpRequestHandlerBuilder glob if (length <= path.size()) { nodes = Arrays.copyOf(nodes, path.size() + 1); for (; length < nodes.length; length++) { - nodes[length] = new HttpServerRegister.StrNode(serverInfo); + nodes[length] = new StrNode(); } } if (globHttpRequestHandler.hasWildcardAtEnd()) { diff --git a/src/main/java/org/globsframework/http/server/apache/Server.java b/src/main/java/org/globsframework/http/server/apache/Server.java new file mode 100644 index 0000000..0a3f660 --- /dev/null +++ b/src/main/java/org/globsframework/http/server/apache/Server.java @@ -0,0 +1,21 @@ +package org.globsframework.http.server.apache; + +import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncServer; + +public class Server { + private final HttpAsyncServer server; + private final int port; + + public Server(HttpAsyncServer server, int port) { + this.server = server; + this.port = port; + } + + public int getPort() { + return port; + } + + public HttpAsyncServer getServer() { + return server; + } +} diff --git a/src/main/java/org/globsframework/http/server/apache/StrNode.java b/src/main/java/org/globsframework/http/server/apache/StrNode.java new file mode 100644 index 0000000..6c0cb64 --- /dev/null +++ b/src/main/java/org/globsframework/http/server/apache/StrNode.java @@ -0,0 +1,39 @@ +package org.globsframework.http.server.apache; + +import org.globsframework.http.GlobHttpRequestHandlerFactory; + +import java.util.Arrays; +import java.util.Collection; + +public class StrNode { + private SubStrNode[] subStrNodes = new SubStrNode[0]; + private SubStrNode[] subWithWildCard = new SubStrNode[0]; + + public GlobHttpRequestHandlerFactory createRequestHandler(String[] path, String method, String paramStr, boolean hasBody) { + for (SubStrNode subStrNode : this.subStrNodes) { + if (subStrNode.match(path)) { + return subStrNode.httpRequestHandlerBuilder.create(path, method, paramStr, hasBody); + } + } + return null; + } + + public GlobHttpRequestHandlerFactory findAndCreateRequestHandler(String[] path, String method, String paramStr, boolean hasBody) { + for (SubStrNode subStrNode : this.subWithWildCard) { + if (subStrNode.match(path)) { + return subStrNode.httpRequestHandlerBuilder.create(path, method, paramStr, hasBody); + } + } + return null; + } + + public void register(Collection path, GlobHttpRequestHandlerBuilder globHttpRequestHandler) { + subStrNodes = Arrays.copyOf(subStrNodes, subStrNodes.length + 1); + subStrNodes[subStrNodes.length - 1] = new SubStrNode(path, globHttpRequestHandler); + } + + public void registerWildcard(Collection path, GlobHttpRequestHandlerBuilder globHttpRequestHandler) { + subWithWildCard = Arrays.copyOf(subWithWildCard, subWithWildCard.length + 1); + subWithWildCard[subWithWildCard.length - 1] = new SubStrNode(path, globHttpRequestHandler); + } +} diff --git a/src/main/java/org/globsframework/http/server/apache/SubStrNode.java b/src/main/java/org/globsframework/http/server/apache/SubStrNode.java new file mode 100644 index 0000000..1e22ffb --- /dev/null +++ b/src/main/java/org/globsframework/http/server/apache/SubStrNode.java @@ -0,0 +1,26 @@ +package org.globsframework.http.server.apache; + +import java.util.Collection; + +public class SubStrNode { + private final String[] path; + final GlobHttpRequestHandlerBuilder httpRequestHandlerBuilder; + + public SubStrNode(Collection path, GlobHttpRequestHandlerBuilder globHttpRequestHandler) { + this.path = path.toArray(String[]::new); + this.httpRequestHandlerBuilder = globHttpRequestHandler; + } + + boolean match(String[] path) { + String[] strings = this.path; + for (int i = 0, stringsLength = strings.length; i < stringsLength; i++) { + String s = strings[i]; + if (s != null) { + if (!s.equals(path[i])) { + return false; + } + } + } + return true; + } +} diff --git a/src/main/java/org/globsframework/shared/EtcDSharedDataAccess.java b/src/main/java/org/globsframework/shared/EtcDSharedDataAccess.java deleted file mode 100644 index 866bb2f..0000000 --- a/src/main/java/org/globsframework/shared/EtcDSharedDataAccess.java +++ /dev/null @@ -1,496 +0,0 @@ -package org.globsframework.shared; - -import io.etcd.jetcd.*; -import io.etcd.jetcd.election.CampaignResponse; -import io.etcd.jetcd.election.LeaderKey; -import io.etcd.jetcd.election.LeaderResponse; -import io.etcd.jetcd.kv.GetResponse; -import io.etcd.jetcd.kv.PutResponse; -import io.etcd.jetcd.lease.LeaseGrantResponse; -import io.etcd.jetcd.options.GetOption; -import io.etcd.jetcd.options.PutOption; -import io.etcd.jetcd.options.WatchOption; -import io.etcd.jetcd.watch.WatchEvent; -import org.globsframework.core.metamodel.GlobType; -import org.globsframework.core.metamodel.GlobTypeResolver; -import org.globsframework.core.metamodel.fields.Field; -import org.globsframework.core.model.FieldValues; -import org.globsframework.core.model.Glob; -import org.globsframework.core.utils.Utils; -import org.globsframework.json.GSonUtils; -import org.globsframework.serialisation.BinReader; -import org.globsframework.serialisation.BinReaderFactory; -import org.globsframework.serialisation.BinWriterFactory; -import org.globsframework.serialisation.glob.GlobBinReader; -import org.globsframework.shared.model.PathIndex; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.InputStreamReader; -import java.nio.charset.StandardCharsets; -import java.time.Duration; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.List; -import java.util.Optional; -import java.util.concurrent.*; - -public class EtcDSharedDataAccess implements SharedDataAccess { - private final static Logger LOGGER = LoggerFactory.getLogger(EtcDSharedDataAccess.class); - private final Client client; - private final Watch watchClient; - private final KV kv; - private final Lease leaseClient; - private final GlobSerializer serializer; - private final GlobDeserializer deserializer; - private final String prefix; - private final String separator; - private final Election electionClient; - private final ScheduledExecutorService scheduledExecutorService; - private final ExecutorService executorService; - - private EtcDSharedDataAccess(Client client, GlobSerializer serializer, GlobDeserializer deserializer, String prefix, String separator) { - this.client = client; - kv = client.getKVClient(); - watchClient = client.getWatchClient(); - leaseClient = client.getLeaseClient(); - electionClient = client.getElectionClient(); - this.serializer = serializer; - this.deserializer = deserializer; - this.prefix = prefix; - this.separator = separator; - scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(); - executorService = Executors.newCachedThreadPool(); - } - - public static SharedDataAccess createJson(Client client) { - return createJson(client, null); - } - - public static SharedDataAccess createJson(Client client, String prefix) { - return createJson(client, prefix, "/"); - } - - public static SharedDataAccess createJson(Client client, String prefix, String separator) { - GlobSerializer serializer = glob -> { - String encode = GSonUtils.encode(glob, true); - return encode.getBytes(StandardCharsets.UTF_8); - }; - GlobDeserializer deserializer = resolvers -> data -> Optional.ofNullable( - GSonUtils.decode(new InputStreamReader(new ByteArrayInputStream(data), StandardCharsets.UTF_8), resolvers)); - return new EtcDSharedDataAccess(client, serializer, deserializer, prefix, separator); - } - - public static SharedDataAccess createBin(Client client) { - return createBin(client, null); - } - - public static SharedDataAccess createBin(Client client, String prefix) { - return createBin(client, prefix, "/"); - } - - public static SharedDataAccess createBin(Client client, String prefix, String separator) { - final BinWriterFactory binWriterFactory = BinWriterFactory.create(); - final BinReaderFactory binReaderFactory = BinReaderFactory.create(); - GlobSerializer serializer = glob -> { - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - binWriterFactory.create(outputStream).write(glob); - return outputStream.toByteArray(); - }; - GlobDeserializer deserializer = resolvers -> { - BinReader globBinReader = binReaderFactory.createGlobBinReader(resolvers); - return data -> globBinReader.read(new ByteArrayInputStream(data)); - }; - return new EtcDSharedDataAccess(client, serializer, deserializer, prefix, separator); - } - - public static String extractPath(String prefix, FieldValues glob, GlobType type, String separator) { - List orderedField = type.streamFields().filter(field -> field.hasAnnotation(PathIndex.KEY)) - .sorted(Comparator.comparing(field1 -> field1.getAnnotation(PathIndex.KEY).get(PathIndex.index))) - .toList(); - StringBuilder builder = new StringBuilder(); - if (prefix != null) { - builder.append(separator).append(prefix); - } - builder.append(separator).append(type.getName()); - int countUnset = 0; - for (Field field : orderedField) { - if (glob.isSet(field)) { - for (int i = 0; i < countUnset; i++) { - builder.append(separator).append("null"); - } - countUnset = 0; - builder.append(separator).append(glob.getValue(field)); - } else { - countUnset++; - } - } - return builder.toString(); - } - - public CompletableFuture register(Glob glob) { - GlobType type = glob.getType(); - String path = extractPath(prefix, glob, type, separator); - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("register " + path); - } - CompletableFuture put = kv.put(ByteSequence.from(path, StandardCharsets.UTF_8), ByteSequence.from(serializer.write(glob))); - return put.thenApply(putResponse -> null); - } - - public CompletableFuture register(Glob glob, UnLeaser unLeaser) { - GlobType type = glob.getType(); - String path = extractPath(prefix, glob, type, separator); - - ByteSequence k = ByteSequence.from(path, StandardCharsets.UTF_8); - ByteSequence v = ByteSequence.from(serializer.write(glob)); - - return kv.put(k, v, PutOption.builder().withLeaseId(unLeaser.getLeaseId()).build()).thenApply(putResponse -> null); - } - - public CompletableFuture registerWithLease(Glob glob, Duration duration) { - GlobType type = glob.getType(); - String path = extractPath(prefix, glob, type, separator); - - ByteSequence k = ByteSequence.from(path, StandardCharsets.UTF_8); - ByteSequence v = ByteSequence.from(serializer.write(glob)); - - return leaseClient.grant(duration.toSeconds()) - .thenApply(LeaseGrantResponse::getID) - .thenCompose(leaseId -> { - LOGGER.info("register " + path + " with lease id" + leaseId); - return kv.put(k, v, PutOption.builder().withLeaseId(leaseId).build()) - .thenApply(putResponse -> new UnLeaser() { - public void touch() { - LOGGER.debug("Touch call on " + leaseId); - leaseClient.keepAliveOnce(leaseId); - } - - public long getLeaseId() { - return leaseId; - } - - public void end() { - - } - }); - }); - } - - public CompletableFuture createLease(Duration duration) { - return leaseClient.grant(duration.toSeconds()) - .thenApply(LeaseGrantResponse::getID) - .thenApply(leaseId -> { - LOGGER.info("lease " + leaseId + " created."); - return leaseId; - }) - .thenApply(leaseId -> new UnLeaser() { - public void touch() { - LOGGER.info("Touch call on " + leaseId); - leaseClient.keepAliveOnce(leaseId); - } - - public long getLeaseId() { - return leaseId; - } - - public void end() { - - } - }); - } - - public CompletableFuture createAutoLease(Duration duration) { - return createLease(duration) - .thenApply(unLeaser -> { - ScheduledFuture scheduledFuture = scheduledExecutorService.scheduleAtFixedRate(unLeaser::touch, - duration.toMillis() / 2, duration.toMillis() / 2, TimeUnit.MILLISECONDS); - return new UnLeaser() { - public void touch() { - unLeaser.touch(); - } - - public long getLeaseId() { - return unLeaser.getLeaseId(); - } - - public void end() { - scheduledFuture.cancel(false); - } - }; - }); - } - - public CompletableFuture> get(GlobType type, FieldValues path) { - CompletableFuture getResponseCompletableFuture = kv.get(ByteSequence.from(extractPath(prefix, path, type, separator), StandardCharsets.UTF_8)); - return getResponseCompletableFuture.thenApply(getResponse -> { - List kvs = getResponse.getKvs(); - if (kvs.isEmpty()) { - return Optional.empty(); - } - if (kvs.size() > 1) { - LOGGER.warn("Many value return " + kvs.size()); - } - ByteSequence value = kvs.get(0).getValue(); - return deserializer.with(GlobTypeResolver.from(type)).read(value.getBytes()); - }); - } - - public CompletableFuture> getUnder(GlobType type, FieldValues path) { - return getUnderWithRevision(type, path).thenApplyAsync(ResultAndRevision::data, executorService); - } - - private CompletableFuture getUnderWithRevision(GlobType type, FieldValues path) { - CompletableFuture getResponseCompletableFuture = - kv.get(ByteSequence.from(extractPath(prefix, path, type, separator), StandardCharsets.UTF_8), GetOption.builder().isPrefix(true).build()); - CompletableFuture completableFuture = getResponseCompletableFuture.thenApply(getResponse -> { - List kvs = getResponse.getKvs(); - long revision = getResponse.getHeader().getRevision(); - if (kvs.isEmpty()) { - return new ResultAndRevision(List.of(), revision); - } - List data = new ArrayList<>(); - for (KeyValue keyValue : kvs) { - ByteSequence value = keyValue.getValue(); - deserializer.with(GlobTypeResolver.from(type)).read(value.getBytes()) - .ifPresent(data::add); - } - return new ResultAndRevision(data, revision); - }); - completableFuture.exceptionally(throwable -> { - LOGGER.error("Exception thrown", throwable); - return null; - }); - return completableFuture; - } - - public CompletableFuture getAndListenUnder(GlobType type, FieldValues path, InitialLoad pastData, Listener newData) { - return getUnderWithRevision(type, path) - .thenComposeAsync(resultAndRevision -> { - try { - return pastData.accept(resultAndRevision.data) - .thenApply(unused -> resultAndRevision); - } catch (Exception e) { - LOGGER.error("Ignored exception : ", e); - return CompletableFuture.completedFuture(resultAndRevision); - } - }, executorService) - .thenApply(resultAndRevision -> resultAndRevision.revision + 1) - .thenApply(revision -> listenUnder(type, newData, path, revision)); - } - - public ListenerCtrl listen(GlobType type, Listener listener, FieldValues orderedPath) { - Listener logListener = new LoggerListener(listener); - GlobDeserializer.Deserializer globBinReader = deserializer.with(GlobTypeResolver.from(type)); - watchClient.watch(ByteSequence.from(extractPath(prefix, orderedPath, type, separator), StandardCharsets.UTF_8), - WatchOption.builder() - .withPrevKV(true) - .build(), - watchResponse -> executorService.submit(() -> { - try { - for (WatchEvent event : watchResponse.getEvents()) { - if (event.getEventType() == WatchEvent.EventType.DELETE) { - globBinReader.read(event.getPrevKV().getValue().getBytes()) - .ifPresent(logListener::delete); - } else if (event.getEventType() == WatchEvent.EventType.PUT) { - globBinReader.read(event.getKeyValue().getValue().getBytes()) - .ifPresent(logListener::put); - } else { - LOGGER.warn("event not unrecognized"); - } - } - } catch (Exception e) { - LOGGER.error("Exception in watch callback", e); - } - })); - return new ListenerCtrl() { - public void close() { - LOGGER.info("Close call on " + orderedPath); - watchClient.close(); - } - }; - } - - public ListenerCtrl listenUnder(GlobType type, Listener listener, FieldValues orderedPath) { - return listenUnder(type, listener, orderedPath, -1); - } - - public ListenerCtrl listenUnder(GlobType type, Listener listener, FieldValues orderedPath, long startAtRevision) { - LOGGER.info("listenUnder " + orderedPath); - Listener logListener = new LoggerListener(listener); - GlobDeserializer.Deserializer globBinReader = deserializer.with(GlobTypeResolver.from(type)); - WatchOption.Builder option = WatchOption.builder() - .withPrevKV(true) - .isPrefix(true); - if (startAtRevision != -1) { - option.withRevision(startAtRevision); - } - Watch.Watcher watch = watchClient.watch(ByteSequence.from(extractPath(prefix, orderedPath, type, separator), StandardCharsets.UTF_8), option.build(), - watchResponse -> executorService.submit(() -> { - try { - for (WatchEvent event : watchResponse.getEvents()) { - if (event.getEventType() == WatchEvent.EventType.DELETE) { - globBinReader.read(event.getPrevKV().getValue().getBytes()) - .ifPresent(logListener::delete); - } else if (event.getEventType() == WatchEvent.EventType.PUT) { - globBinReader.read(event.getKeyValue().getValue().getBytes()) - .ifPresent(logListener::put); - } else { - LOGGER.warn("event not unrecognized"); - } - } - } catch (Exception e) { - LOGGER.error("Exception in watch callback", e); - } - })); - return () -> { - LOGGER.info("Close call on " + orderedPath); - watch.close(); - }; - } - - public CompletableFuture delete(GlobType type, FieldValues values) { - String source = extractPath(prefix, values, type, separator); - LOGGER.info("Delete call on " + source); - return kv.delete(ByteSequence.from(source, StandardCharsets.UTF_8)) - .whenComplete((deleteResponse, throwable) -> { - if (throwable != null) { - LOGGER.error("delete on error " + type.getName() + " => " + source, throwable); - } - }) - .thenAccept(deleteResponse -> { - }); - } - - public CompletableFuture registerForLeaderShip(Glob glob, LeaderListener listener) { - CompletableFuture grant = leaseClient.grant(1); - String key = extractPath(prefix, glob, glob.getType(), separator); - byte[] value = serializer.write(glob); - return grant.thenApply(leaseGrantResponse -> { - long leaseId = leaseGrantResponse.getID(); - LOGGER.info(key + " registered with leaseId " + leaseId); - ScheduledFuture scheduledFuture = scheduledExecutorService.scheduleAtFixedRate(() -> leaseClient.keepAliveOnce(leaseId), - 500, 700, TimeUnit.MILLISECONDS); - return new ListenerAndLeaderOperation(ByteSequence.from(key, StandardCharsets.UTF_8), ByteSequence.from(value), - leaseId, listener, scheduledFuture); - }); - } - - public void end() { - LOGGER.info("etcd end"); - client.close(); - scheduledExecutorService.shutdown(); - executorService.shutdown(); - } - - interface GlobSerializer { - byte[] write(Glob glob); - } - - interface GlobDeserializer { - Deserializer with(GlobTypeResolver resolvers); - - interface Deserializer { - Optional read(byte[] data); - } - } - - record ResultAndRevision(List data, long revision) { - } - - private static class LoggerListener implements Listener { - private final Listener listener; - - public LoggerListener(Listener listener) { - this.listener = listener; - } - - public void put(Glob glob) { - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Receive put " + GSonUtils.encode(glob, true)); - } - try { - listener.put(glob); - } catch (Exception e) { - LOGGER.error("Got exception", e); - } - } - - public void delete(Glob glob) { - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Receive delete " + GSonUtils.encode(glob, true)); - } - try { - listener.delete(glob); - } catch (Exception e) { - LOGGER.error("Got exception", e); - } - } - } - - private class ListenerAndLeaderOperation implements LeaderOperation, Election.Listener { - private final ByteSequence electionName; - private final ByteSequence value; - private final long leaseId; - private final LeaderListener listener; - private final ScheduledFuture scheduledFuture; - private CompletableFuture campaign; - private CompletableFuture leaderKeyCompletableFuture; - - public ListenerAndLeaderOperation(ByteSequence electionName, ByteSequence value, long leaseId, LeaderListener listener, ScheduledFuture scheduledFuture) { - this.electionName = electionName; - this.value = value; - this.leaseId = leaseId; - this.listener = listener; - this.scheduledFuture = scheduledFuture; - } - - void init() { - electionClient.observe(electionName, this); - campaign = electionClient.campaign(electionName, leaseId, value); - leaderKeyCompletableFuture = campaign.thenApply(campaignResponse -> { - LOGGER.info("I am the leader for " + electionName); - listener.youAreTheLeader(); - return campaignResponse.getLeader(); - }); - } - - synchronized public void releaseMyLeaderShip() { - LOGGER.info("release wanted on " + electionName); - if (leaderKeyCompletableFuture.isDone()) { - listener.youAreNotTheLeaderAnyMore(); - electionClient.resign(leaderKeyCompletableFuture.join()); - Utils.sleep(1000); //force wait to allow a new leader different then this. - init(); - } - } - - synchronized public void shutDown() { - LOGGER.info("shutDown wanted on " + electionName); - releaseMyLeaderShip(); - scheduledFuture.cancel(false); - leaseClient.revoke(leaseId); - } - - public void onNext(LeaderResponse response) { - LOGGER.debug("onNext call on " + electionName); - if (!response.getKv().getValue().equals(value)) { - LOGGER.info("Force release "); - releaseMyLeaderShip(); - } else { - LOGGER.debug("Same leader."); - } - } - - synchronized public void onError(Throwable throwable) { - LOGGER.error("onError call on " + electionName, throwable); - releaseMyLeaderShip(); - } - - public void onCompleted() { - LOGGER.info("onCompleted call on " + electionName); - } - } -} diff --git a/src/main/java/org/globsframework/shared/InMemorySharedDataAccess.java b/src/main/java/org/globsframework/shared/InMemorySharedDataAccess.java deleted file mode 100644 index dbd6a58..0000000 --- a/src/main/java/org/globsframework/shared/InMemorySharedDataAccess.java +++ /dev/null @@ -1,235 +0,0 @@ -package org.globsframework.shared; - -import org.globsframework.core.metamodel.GlobType; -import org.globsframework.core.model.FieldValues; -import org.globsframework.core.model.Glob; -import org.globsframework.json.GSonUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.time.Duration; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.*; -import java.util.concurrent.atomic.AtomicLong; - -public class InMemorySharedDataAccess implements SharedDataAccess { - private static final Logger LOGGER = LoggerFactory.getLogger(InMemorySharedDataAccess.class); - private final Map paths = new ConcurrentHashMap<>(); - private final List listeners = new ArrayList<>(); - private final ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(); - private final AtomicLong id = new AtomicLong(0); - private final Map leasers = new ConcurrentHashMap<>(); - private final String prefix; - - public InMemorySharedDataAccess() { - this(null); - } - - public InMemorySharedDataAccess(String prefix) { - this.prefix = prefix; - } - - public static class SimpleListener { - private final Listener listener; - private final String path; - private final boolean allUnder; - - public SimpleListener(Listener listener, String path, boolean allUnder) { - this.listener = listener; - this.path = path; - this.allUnder = allUnder; - } - - public void putOn(String path, Glob glob) { - if (allUnder) { - if (path.startsWith(this.path)) { - listener.put(glob); - } - } else { - if (path.equals(this.path)) { - listener.put(glob); - } - } - } - - public void delete(String path, Glob glob) { - if (allUnder) { - if (path.startsWith(this.path)) { - listener.delete(glob); - } - } else { - if (path.equals(this.path)) { - listener.delete(glob); - } - } - } - } - - public CompletableFuture register(Glob glob) { - String path = EtcDSharedDataAccess.extractPath(prefix, glob, glob.getType(), "/"); - paths.put(path, glob.duplicate()); - for (SimpleListener listener : listeners) { - listener.putOn(path, glob); - } - return CompletableFuture.completedFuture(null); - } - - public CompletableFuture register(Glob glob, UnLeaser unLeaser) { - register(glob); - leasers.get(unLeaser.getLeaseId()).add(glob); - return CompletableFuture.completedFuture(null); - } - - public CompletableFuture registerWithLease(Glob glob, Duration duration) { - register(glob); - long key = id.incrementAndGet(); - InMemoryUnLeaser value = new InMemoryUnLeaser(key, duration, true); - value.add(glob); - leasers.put(key, value); - return CompletableFuture.completedFuture(value); - } - - public CompletableFuture createLease(Duration duration) { - long key = id.incrementAndGet(); - InMemoryUnLeaser value = new InMemoryUnLeaser(key, duration, false); - leasers.put(key, value); - return CompletableFuture.completedFuture(value); - } - - public CompletableFuture createAutoLease(Duration duration) { - long key = id.incrementAndGet(); - InMemoryUnLeaser value = new InMemoryUnLeaser(key, duration, true); - leasers.put(key, value); - return CompletableFuture.completedFuture(value); - } - - public UnLeaser getUnleaser(long leaseId) { - return leasers.get(leaseId); - } - - public CompletableFuture> get(GlobType type, FieldValues path) { - String p = EtcDSharedDataAccess.extractPath(prefix, path, type, "/"); - return CompletableFuture.completedFuture(Optional.ofNullable(paths.get(p))); - } - - public CompletableFuture> getUnder(GlobType type, FieldValues path) { - String p = EtcDSharedDataAccess.extractPath(prefix, path, type, "/"); - List globs = new ArrayList<>(); - for (Map.Entry stringGlobEntry : paths.entrySet()) { - if (stringGlobEntry.getKey().startsWith(p)) { - globs.add(stringGlobEntry.getValue()); - } - } - return CompletableFuture.completedFuture(globs); - } - - public CompletableFuture getAndListenUnder(GlobType type, FieldValues path, InitialLoad pastData, Listener newData) { - CompletableFuture> under = getUnder(type, path); - CompletableFuture listenerCtrlCompletableFuture = CompletableFuture.completedFuture(listenUnder(type, newData)); - return under.thenCompose(pastData::accept) - .exceptionally(throwable -> { - LOGGER.error("unexpected exception", throwable); - return null; - }) - .thenCompose(unused -> listenerCtrlCompletableFuture); - } - - public ListenerCtrl listen(GlobType type, Listener listener, FieldValues orderedPath) { - String p = EtcDSharedDataAccess.extractPath(prefix, orderedPath, type, "/"); - SimpleListener e = new SimpleListener(listener, p, false); - listeners.add(e); - return () -> listeners.remove(e); - } - - public ListenerCtrl listenUnder(GlobType type, Listener listener, FieldValues orderedPath) { - String p = EtcDSharedDataAccess.extractPath(prefix, orderedPath, type, "/"); - SimpleListener e = new SimpleListener(listener, p, true); - listeners.add(e); - return () -> listeners.remove(e); - } - - public CompletableFuture delete(GlobType type, FieldValues values) { - String p = EtcDSharedDataAccess.extractPath(prefix, values, type, "/"); - Glob glob = paths.remove(p); - if (glob != null) { - for (SimpleListener listener : listeners) { - listener.delete(p, glob); - } - return CompletableFuture.completedFuture(null); - } else { - return CompletableFuture.failedFuture(new RuntimeException(p + " not found.")); - } - } - - @Override - public CompletableFuture registerForLeaderShip(Glob glob, LeaderListener listener) { - listener.youAreTheLeader(); - return CompletableFuture.completedFuture(new LeaderOperation() { - @Override - public void releaseMyLeaderShip() { - - } - - @Override - public void shutDown() { - - } - }); - } - - public void end() { - - } - - private class InMemoryUnLeaser implements UnLeaser, Callable { - private ScheduledFuture schedule; - private Duration duration; - private List globs = new ArrayList<>(); - long id; - private final boolean autoLease; - - public InMemoryUnLeaser(long id, Duration duration, boolean autoLease) { - this.id = id; - this.autoLease = autoLease; - if (!autoLease) { - this.schedule = scheduledExecutorService.schedule(this, duration.toSeconds(), TimeUnit.SECONDS); - } - this.duration = duration; - } - - public void add(Glob glob) { - globs.add(glob); - } - - public void touch() { - if (autoLease) { - return; - } - schedule.cancel(false); - schedule = scheduledExecutorService.schedule(this, duration.toSeconds(), TimeUnit.SECONDS); - } - - public long getLeaseId() { - return id; - } - - public void end() { - if (schedule != null) { - schedule.cancel(false); - } - call(); - } - - public Void call() { - for (Glob glob : globs) { - LOGGER.info("timeout deleting " + GSonUtils.encode(glob, true)); - delete(glob.getType(), glob); - leasers.remove(id); - } - return null; - } - } -} diff --git a/src/main/java/org/globsframework/shared/SharedDataAccess.java b/src/main/java/org/globsframework/shared/SharedDataAccess.java deleted file mode 100644 index ac2a9fd..0000000 --- a/src/main/java/org/globsframework/shared/SharedDataAccess.java +++ /dev/null @@ -1,96 +0,0 @@ -package org.globsframework.shared; - -import org.globsframework.core.metamodel.GlobType; -import org.globsframework.core.model.FieldValues; -import org.globsframework.core.model.Glob; - -import java.time.Duration; -import java.util.List; -import java.util.Optional; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.TimeUnit; -import java.util.function.Consumer; - -public interface SharedDataAccess { - - CompletableFuture register(Glob glob); - - CompletableFuture register(Glob glob, UnLeaser unLeaser); - - default CompletableFuture registerWithLease(Glob glob, int timeOut, TimeUnit unit) { - return registerWithLease(glob, Duration.ofSeconds(unit.toSeconds(timeOut))); - } - - CompletableFuture registerWithLease(Glob glob, Duration duration); - - CompletableFuture createLease(Duration duration); - - CompletableFuture createAutoLease(Duration duration); - -// CompletableFuture registerWithAutoLease(Glob glob, int timeOut, TimeUnit unit); - - CompletableFuture> get(GlobType type, FieldValues path); - - CompletableFuture> getUnder(GlobType type, FieldValues path); - - default CompletableFuture getAndListenUnder(GlobType type, FieldValues path, Consumer> pastData, Listener newData) { - return getAndListenUnder(type, path, globs -> { - pastData.accept(globs); - return CompletableFuture.completedFuture(null); - }, newData); - } - - CompletableFuture getAndListenUnder(GlobType type, FieldValues path, InitialLoad pastData, Listener newData); - - default ListenerCtrl listen(GlobType type, Listener listener) { - return listen(type, listener, FieldValues.EMPTY); - } - - default ListenerCtrl listenUnder(GlobType type, Listener listener) { - return listenUnder(type, listener, FieldValues.EMPTY); - } - - ListenerCtrl listen(GlobType type, Listener listener, FieldValues orderedPath); - - ListenerCtrl listenUnder(GlobType type, Listener listener, FieldValues orderedPath); - - CompletableFuture delete(GlobType type, FieldValues values); - - CompletableFuture registerForLeaderShip(Glob glob, LeaderListener listener); - - interface LeaderOperation { - void releaseMyLeaderShip(); - - void shutDown(); - } - - interface LeaderListener { - void youAreTheLeader(); - - void youAreNotTheLeaderAnyMore(); - } - - interface InitialLoad { - CompletableFuture accept(List globs); - } - - interface ListenerCtrl { - void close(); - } - - interface UnLeaser { - void touch(); - - long getLeaseId(); - - void end(); - } - - interface Listener { - void put(Glob glob); - - void delete(Glob glob); - } - - void end(); -} diff --git a/src/main/java/org/globsframework/shared/model/PathIndex.java b/src/main/java/org/globsframework/shared/model/PathIndex.java deleted file mode 100644 index 157fd17..0000000 --- a/src/main/java/org/globsframework/shared/model/PathIndex.java +++ /dev/null @@ -1,33 +0,0 @@ -package org.globsframework.shared.model; - -import org.globsframework.core.metamodel.GlobType; -import org.globsframework.core.metamodel.GlobTypeBuilder; -import org.globsframework.core.metamodel.GlobTypeBuilderFactory; -import org.globsframework.core.metamodel.annotations.GlobCreateFromAnnotation; -import org.globsframework.core.metamodel.annotations.InitUniqueKey; -import org.globsframework.core.metamodel.fields.IntegerField; -import org.globsframework.core.model.Key; -import org.globsframework.core.model.KeyBuilder; - -public class PathIndex { - public static final GlobType TYPE; - - public static final IntegerField index; - - @InitUniqueKey - public static final Key KEY; - - static { - GlobTypeBuilder typeBuilder = GlobTypeBuilderFactory.create("PathIndex"); - TYPE = typeBuilder.unCompleteType(); - index = typeBuilder.declareIntegerField("index"); - typeBuilder.complete(); - typeBuilder.register(GlobCreateFromAnnotation.class, annotation -> PathIndex.TYPE.instantiate() - .set(PathIndex.index, ((PathIndex_) annotation).value())); - KEY = KeyBuilder.newEmptyKey(TYPE); -// GlobTypeLoaderFactory.create(PathIndex.class) -// .register(GlobCreateFromAnnotation.class, annotation -> PathIndex.TYPE.instantiate() -// .set(PathIndex.index, ((PathIndex_) annotation).value())) -// .load(); - } -} diff --git a/src/main/java/org/globsframework/shared/model/PathIndex_.java b/src/main/java/org/globsframework/shared/model/PathIndex_.java deleted file mode 100644 index f32c4f7..0000000 --- a/src/main/java/org/globsframework/shared/model/PathIndex_.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.globsframework.shared.model; - -import org.globsframework.core.metamodel.GlobType; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.Target; - -import static java.lang.annotation.RetentionPolicy.RUNTIME; - -@Retention(RUNTIME) -@Target({ElementType.FIELD}) -public @interface PathIndex_ { - int value(); - - GlobType TYPE = PathIndex.TYPE; -} diff --git a/src/test/java/org/globsframework/http/GlobHttpRequestHandlerTest.java b/src/test/java/org/globsframework/http/GlobHttpRequestHandlerTest.java index b4397c1..9c7343a 100644 --- a/src/test/java/org/globsframework/http/GlobHttpRequestHandlerTest.java +++ b/src/test/java/org/globsframework/http/GlobHttpRequestHandlerTest.java @@ -2,7 +2,6 @@ import org.apache.hc.client5.http.classic.methods.*; import org.apache.hc.client5.http.entity.DecompressingEntity; -import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; import org.apache.hc.client5.http.impl.classic.HttpClients; @@ -11,11 +10,7 @@ import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncServer; import org.apache.hc.core5.http.io.entity.EntityUtils; import org.apache.hc.core5.http.io.entity.StringEntity; -import org.apache.hc.core5.http.nio.AsyncRequestConsumer; -import org.apache.hc.core5.http.nio.AsyncServerRequestHandler; -import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.reactor.IOReactorConfig; -import org.apache.hc.core5.reactor.ListenerEndpoint; import org.apache.hc.core5.util.TimeValue; import org.globsframework.core.metamodel.GlobType; import org.globsframework.core.metamodel.GlobTypeLoaderFactory; @@ -29,7 +24,10 @@ import org.globsframework.http.model.HttpGlobResponse_; import org.globsframework.http.model.StatusCode_; import org.globsframework.http.openapi.model.GetOpenApiParamType; +import org.globsframework.http.openapi.model.GlobOpenApi; import org.globsframework.http.openapi.model.OpenApiType; +import org.globsframework.http.server.apache.GlobHttpApacheBuilder; +import org.globsframework.http.server.apache.Server; import org.globsframework.json.GSonUtils; import org.globsframework.json.annottations.JsonHideValue_; import org.junit.*; @@ -39,10 +37,6 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; -import java.net.InetSocketAddress; -import java.net.SocketAddress; -import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; import java.util.concurrent.*; public class GlobHttpRequestHandlerTest { @@ -52,7 +46,7 @@ public class GlobHttpRequestHandlerTest { private HttpAsyncServer server; private int port; private HttpServerRegister httpServerRegister; - + private GlobOpenApi globOpenApi; private BlockingQueue> pairs; @Before @@ -67,6 +61,7 @@ public void init() { .setIOReactorConfig(config); httpServerRegister = new HttpServerRegister("TestServer/1.1"); + globOpenApi = new GlobOpenApi(httpServerRegister); pairs = new LinkedBlockingDeque<>(); } @@ -183,7 +178,8 @@ public void name() throws IOException, InterruptedException, ParseException { startServer(); - Glob openApiDoc = httpServerRegister.createOpenApiDoc(port); + GlobOpenApi globOpenApi = new GlobOpenApi(httpServerRegister); + Glob openApiDoc = globOpenApi.getOpenApiDoc(); String encode = GSonUtils.encode(openApiDoc, false); System.out.println(encode); @@ -569,12 +565,11 @@ public void openApiScope() throws IOException, InterruptedException { .get(QueryParameter.TYPE, (body, url, queryParameters) -> null) .declareTags(new String[]{"test-scope"}); - httpServerRegister.registerOpenApi(); + httpServerRegister.registerOpenApi(globOpenApi); startServer(); - Glob openApiDoc = httpServerRegister.createOpenApiDoc(port); - String encode = GSonUtils.encode(openApiDoc, false); + String encode = GSonUtils.encode(globOpenApi.getOpenApiDoc(), false); System.out.println(encode); CloseableHttpClient httpclient = HttpClients.createDefault(); @@ -616,9 +611,11 @@ public void xmlInOut() throws IOException { } private void startServer() { - HttpServerRegister.Server httpserverintegerpair = httpServerRegister.startAndWaitForStartup(bootstrap, 0); - server = httpserverintegerpair.getServer(); - port = httpserverintegerpair.getPort(); + GlobHttpApacheBuilder globHttpApacheBuilder = new GlobHttpApacheBuilder(httpServerRegister); + Server serverInstance = globHttpApacheBuilder.startAndWaitForStartup(bootstrap, 0); + server = serverInstance.getServer(); + port = serverInstance.getPort(); + this.globOpenApi.initOpenApiDoc(port); System.out.println("port:" + port); } diff --git a/src/test/java/org/globsframework/shared/EtcDSharedDataAccessTest.java b/src/test/java/org/globsframework/shared/EtcDSharedDataAccessTest.java deleted file mode 100644 index 28ceeb2..0000000 --- a/src/test/java/org/globsframework/shared/EtcDSharedDataAccessTest.java +++ /dev/null @@ -1,522 +0,0 @@ -package org.globsframework.shared; - -import com.google.protobuf.ByteString; -import io.etcd.jetcd.ByteSequence; -import io.etcd.jetcd.Client; -import io.etcd.jetcd.KV; -import io.etcd.jetcd.options.DeleteOption; -import org.globsframework.core.metamodel.GlobType; -import org.globsframework.core.metamodel.GlobTypeLoaderFactory; -import org.globsframework.core.metamodel.fields.IntegerField; -import org.globsframework.core.metamodel.fields.StringField; -import org.globsframework.core.model.FieldValues; -import org.globsframework.core.model.FieldValuesBuilder; -import org.globsframework.core.model.Glob; -import org.globsframework.core.model.MutableGlob; -import org.globsframework.serialisation.model.FieldNumber_; -import org.globsframework.shared.model.PathIndex_; -import org.junit.*; - -import java.time.Duration; -import java.util.List; -import java.util.Optional; -import java.util.concurrent.*; -import java.util.function.Consumer; - -public class EtcDSharedDataAccessTest { - public static final String[] ETCD = new String[]{"http://localhost:2379"}; //, "http://localhost:4001" - - @Before - public void setUp() throws Exception { - Client client = Client.builder().endpoints(ETCD).build(); - deleteAll(client); - client.close(); - } - - @After - public void tearDown() throws Exception { - Client client = Client.builder().endpoints(ETCD).build(); - deleteAll(client); - client.close(); - } - - @Test - @Ignore("integration test to be filtered later") - public void tryLockGetInListener() throws ExecutionException, InterruptedException, TimeoutException { - Client client = Client.builder().endpoints(ETCD).build(); - - SharedDataAccess etcDSharedDataAccess = EtcDSharedDataAccess.createBin(client); - - CompletableFuture done = new CompletableFuture<>(); - etcDSharedDataAccess.listen(Data1.TYPE, new SharedDataAccess.Listener() { - public void put(Glob glob) { - try { - etcDSharedDataAccess.get(glob.getType(), glob).join(); - done.complete(glob); - } catch (Exception e) { - done.complete(null); - } - } - - public void delete(Glob glob) { - - } - }, Data1.TYPE.instantiate() - .set(Data1.shop, "mg.the-oz.com") - .set(Data1.workerName, "w1") - .set(Data1.num, 1)); - - MutableGlob data = Data1.TYPE.instantiate() - .set(Data1.shop, "mg.the-oz.com") - .set(Data1.workerName, "w1") - .set(Data1.num, 1) - .set(Data1.someData, "blabla"); - - // publish data. - etcDSharedDataAccess.register(data) - .get(1, TimeUnit.MINUTES); - - final Glob join = done.join(); - Assert.assertNotNull(join); - Assert.assertEquals("blabla", join.get(Data1.someData)); - etcDSharedDataAccess.end(); - } - - @Test - @Ignore("integration test to be filtered later") - public void throwAErrorExceptionIn() throws ExecutionException, InterruptedException, TimeoutException { - Client client = Client.builder().endpoints(ETCD).build(); - - SharedDataAccess etcDSharedDataAccess = EtcDSharedDataAccess.createBin(client); - - MutableGlob data = Data1.TYPE.instantiate() - .set(Data1.shop, "mg.the-oz.com") - .set(Data1.workerName, "w1") - .set(Data1.num, 1) - .set(Data1.someData, "blabla"); - - etcDSharedDataAccess.register(data) - .get(1, TimeUnit.MINUTES); - BlockingQueue added = new LinkedBlockingDeque<>(); - try { - SharedDataAccess.ListenerCtrl listenerCtrl1 = etcDSharedDataAccess.getAndListenUnder(Data1.TYPE, FieldValues.EMPTY, new Consumer>() { - public void accept(List globs) { - throw new RuntimeException("violent error"); - } - }, new SharedDataAccess.Listener() { - public void put(Glob glob) { - added.add(glob); - } - - public void delete(Glob glob) { - } - }).join(); - } catch (CompletionException e) { - Assert.assertEquals("java.lang.RuntimeException: violent error", e.getMessage()); - } - - - Optional glob = etcDSharedDataAccess.get(Data1.TYPE, data) - .get(1, TimeUnit.MINUTES); - Assert.assertTrue(glob.isPresent()); - MutableGlob data2 = Data1.TYPE.instantiate() - .set(Data1.shop, "mg.the-oz.com") - .set(Data1.workerName, "w2"); - - etcDSharedDataAccess.register(data) - .get(1, TimeUnit.MINUTES); - Glob re = added.poll(4, TimeUnit.SECONDS); - Assert.assertNotNull(re); - } - - @Test - @Ignore("integration test to be filtered later") - public void getUnderAndListenWithEmpty() throws ExecutionException, InterruptedException, TimeoutException { - Client client = Client.builder().endpoints(ETCD).build(); - - SharedDataAccess etcDSharedDataAccess = EtcDSharedDataAccess.createBin(client); - - MutableGlob data = Data1.TYPE.instantiate() - .set(Data1.shop, "mg.the-oz.com") - .set(Data1.workerName, "w1") - .set(Data1.num, 1) - .set(Data1.someData, "blabla"); - - etcDSharedDataAccess.register(data) - .get(1, TimeUnit.MINUTES); - etcDSharedDataAccess.delete(Data1.TYPE, data); - - BlockingQueue puts = new ArrayBlockingQueue<>(10); - BlockingQueue deletes = new ArrayBlockingQueue<>(10); - CompletableFuture> res = new CompletableFuture<>(); - SharedDataAccess.ListenerCtrl listenerCtrl = etcDSharedDataAccess.getAndListenUnder(Data1.TYPE, FieldValues.EMPTY, new Consumer>() { - public void accept(List globs) { - etcDSharedDataAccess.register(data.duplicate().set(Data1.num, 3)); - res.complete(globs); - } - }, new SharedDataAccess.Listener() { - public void put(Glob glob) { - puts.add(glob); - } - - public void delete(Glob glob) { - deletes.add(glob); - } - }).get(); - - List globs = res.get(10, TimeUnit.SECONDS); - Assert.assertEquals(0, deletes.size()); - Assert.assertEquals(0, globs.size()); -// Assert.assertEquals(1, globs.get(0).get(Data1.num).intValue()); - - data.set(Data1.num, 2); - etcDSharedDataAccess.register(data) - .get(1, TimeUnit.MINUTES); - - Glob poll = puts.poll(10, TimeUnit.SECONDS); - Assert.assertNotNull(poll); - Assert.assertEquals(3, poll.get(Data1.num).intValue()); - Glob poll1 = puts.poll(10, TimeUnit.SECONDS); - Assert.assertNotNull(poll1); - Assert.assertEquals(2, poll1.get(Data1.num).intValue()); - listenerCtrl.close(); - etcDSharedDataAccess.end(); -// deleteAll(client); -// client.close(); - } - - - @Test - @Ignore("integration test to be filtered later") - public void getUnderAndListen() throws ExecutionException, InterruptedException, TimeoutException { - Client client = Client.builder().endpoints(ETCD).build(); - - SharedDataAccess etcDSharedDataAccess = EtcDSharedDataAccess.createBin(client); - - MutableGlob data = Data1.TYPE.instantiate() - .set(Data1.shop, "mg.the-oz.com") - .set(Data1.workerName, "w1") - .set(Data1.num, 1) - .set(Data1.someData, "blabla"); - - etcDSharedDataAccess.register(data) - .get(1, TimeUnit.MINUTES); - - BlockingQueue puts = new ArrayBlockingQueue<>(10); - BlockingQueue deletes = new ArrayBlockingQueue<>(10); - CompletableFuture> res = new CompletableFuture<>(); - SharedDataAccess.ListenerCtrl listenerCtrl = etcDSharedDataAccess.getAndListenUnder(Data1.TYPE, FieldValues.EMPTY, new Consumer>() { - public void accept(List globs) { - try { - etcDSharedDataAccess.register(data.duplicate().set(Data1.num, 3)).get(); - } catch (InterruptedException e) { - e.printStackTrace(); - } catch (ExecutionException e) { - e.printStackTrace(); - } - res.complete(globs); - } - }, new SharedDataAccess.Listener() { - public void put(Glob glob) { - puts.add(glob); - } - - public void delete(Glob glob) { - deletes.add(glob); - } - }).get(); - - List globs = res.get(10, TimeUnit.SECONDS); - Assert.assertEquals(1, globs.size()); - Assert.assertEquals(1, globs.get(0).get(Data1.num).intValue()); - - data.set(Data1.num, 2); - etcDSharedDataAccess.register(data) - .get(1, TimeUnit.MINUTES); - - Glob poll = puts.poll(10, TimeUnit.SECONDS); - Assert.assertNotNull(poll); - Assert.assertEquals(3, poll.get(Data1.num).intValue()); - Glob poll1 = puts.poll(10, TimeUnit.SECONDS); - Assert.assertNotNull(poll1); - Assert.assertEquals(2, poll1.get(Data1.num).intValue()); - listenerCtrl.close(); - deleteAll(client); - etcDSharedDataAccess.end(); - } - - private void deleteAll(Client client) throws InterruptedException, ExecutionException { - KV kvClient = client.getKVClient(); - kvClient.delete(ByteSequence.from(ByteString.copyFromUtf8("/")), DeleteOption.newBuilder() - .isPrefix(true).build()).get(); - } - - @Test - @Ignore("integration test to be filtered later") - public void testNameBin() throws ExecutionException, InterruptedException, TimeoutException { - Client clientDelete = Client.builder().endpoints(ETCD).build(); - Client client = Client.builder().endpoints(ETCD).build(); - Client clientRead = Client.builder().endpoints(ETCD).build(); - - checkPutGet(EtcDSharedDataAccess.createBin(client), EtcDSharedDataAccess.createBin(clientRead)); - deleteAll(clientDelete); - clientDelete.close(); - } - - @Test - @Ignore("integration test to be filtered later") - public void testNameJson() throws ExecutionException, InterruptedException, TimeoutException { - Client clientDelete = Client.builder().endpoints(ETCD).build(); - Client client = Client.builder().endpoints(ETCD).build(); - Client clientRead = Client.builder().endpoints(ETCD).build(); - checkPutGet(EtcDSharedDataAccess.createJson(client), EtcDSharedDataAccess.createJson(clientRead)); - deleteAll(clientDelete); - clientDelete.close(); - } - - private void checkPutGet(SharedDataAccess etcDSharedDataAccess, SharedDataAccess sharedDataAccessRead) throws InterruptedException, ExecutionException, TimeoutException { - SharedDataAccess.ListenerCtrl listenerCtrl = null; - SharedDataAccess.ListenerCtrl listenerCtrl2 = null; - SharedDataAccess.ListenerCtrl listenerCtrl1 = null; - - try { - MutableGlob data = Data1.TYPE.instantiate() - .set(Data1.shop, "mg.the-oz.com") - .set(Data1.workerName, "w1") - .set(Data1.num, 1) - .set(Data1.someData, "blabla"); - - BlockingQueue puts = new ArrayBlockingQueue<>(10); - BlockingQueue deletes = new ArrayBlockingQueue<>(10); - listenerCtrl = sharedDataAccessRead.listenUnder(Data1.TYPE, new SharedDataAccess.Listener() { - public void put(Glob glob) { - System.out.println("EtcDSharedDataAccessTest.put "); - puts.add(glob); - } - - public void delete(Glob glob) { - deletes.add(glob); - } - }); - - BlockingQueue puts2 = new ArrayBlockingQueue<>(10); - BlockingQueue deletes2 = new ArrayBlockingQueue<>(10); - listenerCtrl2 = sharedDataAccessRead.listenUnder(Data2.TYPE, new SharedDataAccess.Listener() { - public void put(Glob glob) { - puts2.add(glob); - } - - public void delete(Glob glob) { - deletes2.add(glob); - } - }); - - - etcDSharedDataAccess.register(data) - .get(1, TimeUnit.MINUTES); - - etcDSharedDataAccess.register(Data2.TYPE.instantiate()) - .get(1, TimeUnit.MINUTES); - - Assert.assertEquals("blabla", etcDSharedDataAccess.get(Data1.TYPE, FieldValuesBuilder.init() - .set(Data1.shop, "mg.the-oz.com") - .set(Data1.workerName, "w1") - .set(Data1.num, 1) - .get()) - .get(10, TimeUnit.SECONDS) - .orElseThrow().get(Data1.someData)); - - Assert.assertNotNull(puts.poll(10, TimeUnit.SECONDS)); - - Assert.assertNotNull(puts2.poll(10, TimeUnit.SECONDS)); - - data.set(Data1.num, 2); - listenerCtrl1 = sharedDataAccessRead.listen(Data1.TYPE, new SharedDataAccess.Listener() { - public void put(Glob glob) { - puts.add(glob); - } - - public void delete(Glob glob) { - deletes.add(glob); - } - }, data); - - etcDSharedDataAccess.registerWithLease(data, 1, TimeUnit.MINUTES); - - Assert.assertNotNull(puts.poll(10, TimeUnit.SECONDS)); - - List actual = etcDSharedDataAccess.getUnder(Data1.TYPE, FieldValuesBuilder.init() - .set(Data1.shop, "mg.the-oz.com") - .set(Data1.workerName, "w1") - .get()) - .get(10, TimeUnit.SECONDS); - Assert.assertEquals(2, actual.size()); - - etcDSharedDataAccess.delete(Data1.TYPE, actual.get(0)); - etcDSharedDataAccess.delete(Data1.TYPE, actual.get(1)); - - Assert.assertNotNull(deletes.poll(10, TimeUnit.SECONDS)); - } finally { - if (listenerCtrl != null) { - listenerCtrl.close(); - } - if (listenerCtrl1 != null) { - listenerCtrl1.close(); - } - if (listenerCtrl2 != null) { - listenerCtrl2.close(); - } - etcDSharedDataAccess.end(); - sharedDataAccessRead.end(); - } - - } - - - @Test - @Ignore("integration test to be filtered later") - public void testLeaseBin() throws ExecutionException, InterruptedException, TimeoutException { - Client client = Client.builder().endpoints(ETCD).build(); - try { - SharedDataAccess etcDSharedDataAccess = EtcDSharedDataAccess.createBin(client); - checkLease(etcDSharedDataAccess); - etcDSharedDataAccess.end(); - } finally { - Client clientDelete = Client.builder().endpoints(ETCD).build(); - deleteAll(clientDelete); - clientDelete.close(); - } - } - - @Test - @Ignore("integration test to be filtered later") - public void testLeaseJson() throws ExecutionException, InterruptedException, TimeoutException { - Client client = Client.builder().endpoints(ETCD).build(); - try { - SharedDataAccess etcDSharedDataAccess = EtcDSharedDataAccess.createJson(client); - checkLease(etcDSharedDataAccess); - } finally { - Client clientDelete = Client.builder().endpoints(ETCD).build(); - deleteAll(clientDelete); - clientDelete.close(); - } - } - - @Test - @Ignore - public void autoLease() throws ExecutionException, InterruptedException { - MutableGlob data = Data1.TYPE.instantiate() - .set(Data1.shop, "mg.the-oz.com") - .set(Data1.workerName, "w1") - .set(Data1.num, 1) - .set(Data1.someData, "blabla"); - Client client = Client.builder().endpoints(ETCD).build(); - try { - SharedDataAccess etcDSharedDataAccess = EtcDSharedDataAccess.createJson(client); - SharedDataAccess.UnLeaser unLeaser = etcDSharedDataAccess.createAutoLease(Duration.ofSeconds(1)).get(1, TimeUnit.SECONDS); - etcDSharedDataAccess.register(data, unLeaser); - Thread.sleep(2000); - Assert.assertEquals("blabla", etcDSharedDataAccess.get(Data1.TYPE, data) - .get(1, TimeUnit.SECONDS) - .orElseThrow().get(Data1.someData)); - unLeaser.end(); - Thread.sleep(3000); - List globs = etcDSharedDataAccess.getUnder(Data1.TYPE, FieldValues.EMPTY) - .get(1, TimeUnit.SECONDS); - Assert.assertEquals(0, globs.size()); - etcDSharedDataAccess.end(); - } catch (TimeoutException e) { - e.printStackTrace(); - } finally { - Client clientDelete = Client.builder().endpoints(ETCD).build(); - deleteAll(clientDelete); - clientDelete.close(); - } - } - - private void checkLease(SharedDataAccess etcDSharedDataAccess) throws InterruptedException, ExecutionException, TimeoutException { - MutableGlob data = Data1.TYPE.instantiate() - .set(Data1.shop, "mg.the-oz.com") - .set(Data1.workerName, "w1") - .set(Data1.num, 1) - .set(Data1.someData, "blabla"); - - etcDSharedDataAccess.registerWithLease(data, 3, TimeUnit.SECONDS) - .get(1, TimeUnit.MINUTES); - Assert.assertEquals("blabla", etcDSharedDataAccess.get(Data1.TYPE, FieldValuesBuilder.init() - .set(Data1.shop, "mg.the-oz.com") - .set(Data1.workerName, "w1") - .set(Data1.num, 1) - .get()) - .get(1, TimeUnit.MINUTES) - .orElseThrow().get(Data1.someData)); - Thread.sleep(4000); - Assert.assertTrue(etcDSharedDataAccess.get(Data1.TYPE, FieldValuesBuilder.init() - .set(Data1.shop, "mg.the-oz.com") - .set(Data1.workerName, "w1") - .set(Data1.num, 1) - .get()).get(1, TimeUnit.MINUTES).isEmpty()); - - SharedDataAccess.UnLeaser unLeaser = etcDSharedDataAccess.registerWithLease(data, 3, TimeUnit.SECONDS) - .get(1, TimeUnit.MINUTES); - Thread.sleep(1000); - unLeaser.touch(); - Thread.sleep(1000); - unLeaser.touch(); - Thread.sleep(1000); - unLeaser.touch(); - Thread.sleep(1000); - unLeaser.touch(); - Assert.assertEquals("blabla", etcDSharedDataAccess.get(Data1.TYPE, FieldValuesBuilder.init() - .set(Data1.shop, "mg.the-oz.com") - .set(Data1.workerName, "w1") - .set(Data1.num, 1) - .get()) - .get(1, TimeUnit.MINUTES) - .orElseThrow().get(Data1.someData)); - } - - public static class Data1 { - public static GlobType TYPE; - @FieldNumber_(1) - @PathIndex_(1) - public static StringField shop; - - @FieldNumber_(2) - @PathIndex_(2) - public static StringField workerName; - - @FieldNumber_(3) - @PathIndex_(3) - public static IntegerField num; - - @FieldNumber_(4) - public static StringField someData; - - static { - GlobTypeLoaderFactory.create(Data1.class).load(); - } - } - - public static class Data2 { - public static GlobType TYPE; - @FieldNumber_(1) - @PathIndex_(1) - public static StringField shop; - - @FieldNumber_(2) - @PathIndex_(2) - public static StringField workerName; - - @FieldNumber_(3) - @PathIndex_(3) - public static IntegerField num; - - @FieldNumber_(4) - public static StringField someData; - - static { - GlobTypeLoaderFactory.create(Data2.class).load(); - } - } -}