diff --git a/commons/pom.xml b/commons/pom.xml
index 8342690c..786ca4d1 100644
--- a/commons/pom.xml
+++ b/commons/pom.xml
@@ -4,7 +4,7 @@
io.polyapi
parent-pom
- 0.2.6-SNAPSHOT
+ 0.3.0-SNAPSHOT
../parent-pom
diff --git a/commons/src/main/java/io/polyapi/commons/api/error/PolyApiExecutionException.java b/commons/src/main/java/io/polyapi/commons/api/error/PolyApiExecutionException.java
new file mode 100644
index 00000000..dc6a349e
--- /dev/null
+++ b/commons/src/main/java/io/polyapi/commons/api/error/PolyApiExecutionException.java
@@ -0,0 +1,38 @@
+package io.polyapi.commons.api.error;
+
+/**
+ * Parent exception for all exceptions that want to be handled during the execution of a Poly function.
+ * Requires a status code that will be returned to the user in case an exception happens.
+ */
+public abstract class PolyApiExecutionException extends PolyApiException {
+
+ /**
+ * See {@link PolyApiException#PolyApiException()}
+ */
+ public PolyApiExecutionException() {
+ super();
+ }
+
+ /**
+ * See {@link PolyApiException#PolyApiException(String)}
+ */
+ public PolyApiExecutionException(String message) {
+ super(message);
+ }
+
+ /**
+ * See {@link PolyApiException#PolyApiException(String, Throwable)}
+ */
+ public PolyApiExecutionException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ /**
+ * See {@link PolyApiException#PolyApiException(Throwable)}
+ */
+ public PolyApiExecutionException(Throwable cause) {
+ super(cause);
+ }
+
+ public abstract int getStatusCode();
+}
diff --git a/commons/src/main/java/io/polyapi/commons/api/error/http/HttpResponseException.java b/commons/src/main/java/io/polyapi/commons/api/error/http/HttpResponseException.java
index 6a657927..d555bffb 100644
--- a/commons/src/main/java/io/polyapi/commons/api/error/http/HttpResponseException.java
+++ b/commons/src/main/java/io/polyapi/commons/api/error/http/HttpResponseException.java
@@ -1,15 +1,17 @@
package io.polyapi.commons.api.error.http;
-import io.polyapi.commons.api.error.PolyApiException;
+import io.polyapi.commons.api.error.PolyApiExecutionException;
import io.polyapi.commons.api.http.Response;
import lombok.Getter;
+import java.util.Optional;
+
/**
* Parent of exceptions thrown when the response from an HTTP request is different than 2XX.
* This class contains an instance of the {@link Response} returned.
*/
@Getter
-public class HttpResponseException extends PolyApiException {
+public class HttpResponseException extends PolyApiExecutionException {
private final Response response;
@@ -23,4 +25,9 @@ public HttpResponseException(String message, Response response) {
super(message);
this.response = response;
}
+
+ @Override
+ public int getStatusCode() {
+ return Optional.ofNullable(response).map(Response::statusCode).orElse(500);
+ }
}
diff --git a/commons/src/main/java/io/polyapi/commons/api/error/http/UnexpectedInformationalResponseException.java b/commons/src/main/java/io/polyapi/commons/api/error/http/UnexpectedInformationalResponseException.java
index 090acdc0..1123864c 100644
--- a/commons/src/main/java/io/polyapi/commons/api/error/http/UnexpectedInformationalResponseException.java
+++ b/commons/src/main/java/io/polyapi/commons/api/error/http/UnexpectedInformationalResponseException.java
@@ -9,7 +9,7 @@
*/
public class UnexpectedInformationalResponseException extends HttpResponseException {
- public UnexpectedInformationalResponseException(Response response) {
- super(format("An unexpected informational response was received. Status code: %s.", response.statusCode()), response);
- }
+ public UnexpectedInformationalResponseException(Response response) {
+ super(format("An unexpected informational response was received. Status code: %s.", response.statusCode()), response);
+ }
}
diff --git a/commons/src/main/java/io/polyapi/commons/api/model/FunctionType.java b/commons/src/main/java/io/polyapi/commons/api/model/FunctionType.java
new file mode 100644
index 00000000..37b0f65c
--- /dev/null
+++ b/commons/src/main/java/io/polyapi/commons/api/model/FunctionType.java
@@ -0,0 +1,8 @@
+package io.polyapi.commons.api.model;
+
+/**
+ * Type of a Poly function.
+ */
+public enum FunctionType {
+ SERVER, CLIENT;
+}
diff --git a/commons/src/main/java/io/polyapi/commons/api/model/PolyFunction.java b/commons/src/main/java/io/polyapi/commons/api/model/PolyFunction.java
new file mode 100644
index 00000000..c766f188
--- /dev/null
+++ b/commons/src/main/java/io/polyapi/commons/api/model/PolyFunction.java
@@ -0,0 +1,24 @@
+package io.polyapi.commons.api.model;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.Boolean.FALSE;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+/**
+ * Annotation that marks a specific method as a Poly function.
+ * When uploading functions,
+ */
+@Target(METHOD)
+@Retention(RUNTIME)
+public @interface PolyFunction {
+ FunctionType type() default FunctionType.SERVER;
+
+ String context() default "";
+
+ String name() default "";
+
+ boolean isDeployable() default true;
+}
diff --git a/commons/src/main/java/io/polyapi/commons/api/model/PolyGeneratedClass.java b/commons/src/main/java/io/polyapi/commons/api/model/PolyGeneratedClass.java
new file mode 100644
index 00000000..2b5e5fe6
--- /dev/null
+++ b/commons/src/main/java/io/polyapi/commons/api/model/PolyGeneratedClass.java
@@ -0,0 +1,16 @@
+package io.polyapi.commons.api.model;
+
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+/**
+ * Annotation that marks a class as generated by Poly.
+ */
+@Target(TYPE)
+@Retention(RUNTIME)
+public @interface PolyGeneratedClass {
+}
diff --git a/commons/src/main/java/io/polyapi/commons/api/model/RequiredDependencies.java b/commons/src/main/java/io/polyapi/commons/api/model/RequiredDependencies.java
new file mode 100644
index 00000000..443ea36f
--- /dev/null
+++ b/commons/src/main/java/io/polyapi/commons/api/model/RequiredDependencies.java
@@ -0,0 +1,13 @@
+package io.polyapi.commons.api.model;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+@Target(METHOD)
+@Retention(RUNTIME)
+public @interface RequiredDependencies {
+ RequiredDependency[] value();
+}
diff --git a/commons/src/main/java/io/polyapi/commons/api/model/RequiredDependency.java b/commons/src/main/java/io/polyapi/commons/api/model/RequiredDependency.java
new file mode 100644
index 00000000..b5a7f1fa
--- /dev/null
+++ b/commons/src/main/java/io/polyapi/commons/api/model/RequiredDependency.java
@@ -0,0 +1,21 @@
+package io.polyapi.commons.api.model;
+
+
+import java.lang.annotation.Repeatable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+@Target(METHOD)
+@Retention(RUNTIME)
+@Repeatable(RequiredDependencies.class)
+public @interface RequiredDependency {
+
+ String groupId() default "";
+
+ String artifactId() default ".*";
+
+ String version() default ".*";
+}
diff --git a/commons/src/main/java/io/polyapi/commons/internal/http/HardcodedTokenProvider.java b/commons/src/main/java/io/polyapi/commons/internal/http/HardcodedTokenProvider.java
index b89de9d0..ce49f1fc 100644
--- a/commons/src/main/java/io/polyapi/commons/internal/http/HardcodedTokenProvider.java
+++ b/commons/src/main/java/io/polyapi/commons/internal/http/HardcodedTokenProvider.java
@@ -4,8 +4,6 @@
/**
* {@link TokenProvider} that always return the same set token.
- *
- * @deprecated This class should be replaced for one that obtains the token dynamically and refreshes it.
*/
public class HardcodedTokenProvider implements TokenProvider {
diff --git a/commons/src/main/java/io/polyapi/commons/internal/invocation/PolyFunction.java b/commons/src/main/java/io/polyapi/commons/internal/invocation/PolyFunction.java
deleted file mode 100644
index 1984ec4f..00000000
--- a/commons/src/main/java/io/polyapi/commons/internal/invocation/PolyFunction.java
+++ /dev/null
@@ -1,6 +0,0 @@
-package io.polyapi.commons.internal.invocation;
-
-public @interface PolyFunction {
-
- String value() default "execute";
-}
diff --git a/library/pom.xml b/library/pom.xml
index 4fcb9e66..94aae300 100644
--- a/library/pom.xml
+++ b/library/pom.xml
@@ -4,7 +4,7 @@
io.polyapi
parent-pom
- 0.2.6-SNAPSHOT
+ 0.3.0-SNAPSHOT
../parent-pom
library
diff --git a/parent-pom/pom.xml b/parent-pom/pom.xml
index 61290fb0..07da6bd9 100644
--- a/parent-pom/pom.xml
+++ b/parent-pom/pom.xml
@@ -3,7 +3,7 @@
4.0.0
io.polyapi
parent-pom
- 0.2.6-SNAPSHOT
+ 0.3.0-SNAPSHOT
pom
Poly API Java parent POM
https://polyapi.io
diff --git a/polyapi-maven-plugin/pom.xml b/polyapi-maven-plugin/pom.xml
index 25829243..58217532 100644
--- a/polyapi-maven-plugin/pom.xml
+++ b/polyapi-maven-plugin/pom.xml
@@ -4,7 +4,7 @@
io.polyapi
parent-pom
- 0.2.6-SNAPSHOT
+ 0.3.0-SNAPSHOT
../parent-pom
polyapi-maven-plugin
@@ -29,6 +29,11 @@
+
+ org.reflections
+ reflections
+ 0.10.2
+
org.apache.maven
maven-plugin-api
diff --git a/polyapi-maven-plugin/src/main/java/io/polyapi/plugin/error/deploy/DeploymentWrapperException.java b/polyapi-maven-plugin/src/main/java/io/polyapi/plugin/error/deploy/DeploymentWrapperException.java
new file mode 100644
index 00000000..08903b98
--- /dev/null
+++ b/polyapi-maven-plugin/src/main/java/io/polyapi/plugin/error/deploy/DeploymentWrapperException.java
@@ -0,0 +1,17 @@
+package io.polyapi.plugin.error.deploy;
+
+import io.polyapi.commons.api.error.PolyApiException;
+import io.polyapi.plugin.error.PolyApiMavenPluginException;
+
+import java.util.Collection;
+
+/**
+ * Exception that wraps all the other exceptions thrown when deploying functions.
+ */
+public class DeploymentWrapperException extends PolyApiMavenPluginException {
+
+ public DeploymentWrapperException(Collection extends Throwable> suppressedExceptions) {
+ super("Exceptions occurred while deploying functions.");
+ suppressedExceptions.forEach(this::addSuppressed);
+ }
+}
diff --git a/polyapi-maven-plugin/src/main/java/io/polyapi/plugin/error/validation/InvalidPropertyException.java b/polyapi-maven-plugin/src/main/java/io/polyapi/plugin/error/validation/InvalidPropertyException.java
new file mode 100644
index 00000000..28dcc606
--- /dev/null
+++ b/polyapi-maven-plugin/src/main/java/io/polyapi/plugin/error/validation/InvalidPropertyException.java
@@ -0,0 +1,11 @@
+package io.polyapi.plugin.error.validation;
+
+import java.lang.reflect.Method;
+
+import static java.lang.String.format;
+
+public class InvalidPropertyException extends ValidationException {
+ public InvalidPropertyException(String propertyName, String propertyValue, Method method, String pattern) {
+ super(propertyName, format("Property '%s' with value '%s' of method '%s' doesn't match pattern '%s'.", "%s", propertyValue, method, pattern));
+ }
+}
diff --git a/polyapi-maven-plugin/src/main/java/io/polyapi/plugin/error/validation/KeywordUseException.java b/polyapi-maven-plugin/src/main/java/io/polyapi/plugin/error/validation/KeywordUseException.java
new file mode 100644
index 00000000..d1acce1d
--- /dev/null
+++ b/polyapi-maven-plugin/src/main/java/io/polyapi/plugin/error/validation/KeywordUseException.java
@@ -0,0 +1,13 @@
+package io.polyapi.plugin.error.validation;
+
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.stream.Collectors;
+
+import static java.lang.String.format;
+
+public class KeywordUseException extends ValidationException {
+ public KeywordUseException(String propertyName, String propertyValue, Method method, String... keywords) {
+ super(propertyName, format("Property '%s' with value '%s' of method '%s' uses Java keywords '%s'. Please rename the context accordingly.", "%s", propertyValue, method, Arrays.stream(keywords).collect(Collectors.joining("', '"))));
+ }
+}
diff --git a/polyapi-maven-plugin/src/main/java/io/polyapi/plugin/error/validation/ValidationException.java b/polyapi-maven-plugin/src/main/java/io/polyapi/plugin/error/validation/ValidationException.java
index 5d62adab..eaf19053 100644
--- a/polyapi-maven-plugin/src/main/java/io/polyapi/plugin/error/validation/ValidationException.java
+++ b/polyapi-maven-plugin/src/main/java/io/polyapi/plugin/error/validation/ValidationException.java
@@ -1,12 +1,14 @@
package io.polyapi.plugin.error.validation;
import io.polyapi.plugin.error.PolyApiMavenPluginException;
+import lombok.Getter;
import static java.lang.String.format;
/**
* Parent class for exceptions thrown for validation purposes. Stores the name of the property being validated and injects it into the message.
*/
+@Getter
public class ValidationException extends PolyApiMavenPluginException {
private final String propertyName;
@@ -33,8 +35,4 @@ public ValidationException(String propertyName, String messageTemplate, Throwabl
super(format(messageTemplate, propertyName), cause);
this.propertyName = propertyName;
}
-
- public String getPropertyName() {
- return propertyName;
- }
}
diff --git a/polyapi-maven-plugin/src/main/java/io/polyapi/plugin/model/function/CodeObject.java b/polyapi-maven-plugin/src/main/java/io/polyapi/plugin/model/function/CodeObject.java
new file mode 100644
index 00000000..9fc7aab0
--- /dev/null
+++ b/polyapi-maven-plugin/src/main/java/io/polyapi/plugin/model/function/CodeObject.java
@@ -0,0 +1,24 @@
+package io.polyapi.plugin.model.function;
+
+
+import io.polyapi.commons.api.model.Visibility;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+import static java.lang.String.format;
+import static java.util.stream.Collectors.joining;
+
+@Getter
+@Setter
+public class CodeObject {
+ private String packageName;
+ private String className;
+ private String methodName;
+ private String params;
+ private String code;
+}
diff --git a/polyapi-maven-plugin/src/main/java/io/polyapi/plugin/model/function/PolyFunction.java b/polyapi-maven-plugin/src/main/java/io/polyapi/plugin/model/function/PolyFunction.java
index b4be1d7f..6d44cd58 100644
--- a/polyapi-maven-plugin/src/main/java/io/polyapi/plugin/model/function/PolyFunction.java
+++ b/polyapi-maven-plugin/src/main/java/io/polyapi/plugin/model/function/PolyFunction.java
@@ -25,6 +25,7 @@ public class PolyFunction {
private String returnType;
private Visibility visibility;
private Boolean logsEnabled;
+ private List requirements;
private Map returnTypeSchema;
private List arguments;
diff --git a/polyapi-maven-plugin/src/main/java/io/polyapi/plugin/model/function/PolyFunctionMetadata.java b/polyapi-maven-plugin/src/main/java/io/polyapi/plugin/model/function/PolyFunctionMetadata.java
new file mode 100644
index 00000000..dbb364ab
--- /dev/null
+++ b/polyapi-maven-plugin/src/main/java/io/polyapi/plugin/model/function/PolyFunctionMetadata.java
@@ -0,0 +1,23 @@
+package io.polyapi.plugin.model.function;
+
+import io.polyapi.commons.api.model.FunctionType;
+
+import java.io.InputStream;
+import java.util.List;
+
+public record PolyFunctionMetadata(String name, String signature, FunctionType type, InputStream sourceCode, String context, List dependencies, Boolean isDeployable) {
+
+ @Override
+ public String toString() {
+ return "PolyFunctionMetadata{" +
+ "name='" + name + '\'' +
+ ", type=" + type +
+ ", context='" + context + '\'' +
+ ", dependencies=" + dependencies +
+ '}';
+ }
+
+ public String getTypedType() {
+ return type.name().toLowerCase();
+ }
+}
diff --git a/polyapi-maven-plugin/src/main/java/io/polyapi/plugin/mojo/AddFunctionMojo.java b/polyapi-maven-plugin/src/main/java/io/polyapi/plugin/mojo/AddFunctionMojo.java
index c1b73355..815498ab 100644
--- a/polyapi-maven-plugin/src/main/java/io/polyapi/plugin/mojo/AddFunctionMojo.java
+++ b/polyapi-maven-plugin/src/main/java/io/polyapi/plugin/mojo/AddFunctionMojo.java
@@ -1,13 +1,10 @@
package io.polyapi.plugin.mojo;
-import io.polyapi.commons.api.http.HttpClient;
-import io.polyapi.commons.api.http.TokenProvider;
-import io.polyapi.commons.api.json.JsonParser;
+import io.polyapi.plugin.error.classloader.QualifiedNameNotFoundException;
import io.polyapi.plugin.model.function.PolyFunction;
import io.polyapi.plugin.model.function.PolyFunctionArgument;
import io.polyapi.plugin.service.JavaParserService;
import io.polyapi.plugin.service.JavaParserServiceImpl;
-import io.polyapi.plugin.service.MavenService;
import io.polyapi.plugin.service.PolyFunctionService;
import io.polyapi.plugin.service.PolyFunctionServiceImpl;
import lombok.Setter;
@@ -16,17 +13,22 @@
import org.slf4j.LoggerFactory;
import java.io.File;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.BiConsumer;
+import static java.lang.String.format;
import static java.util.stream.Collectors.joining;
@Setter
public abstract class AddFunctionMojo extends PolyApiMojo {
private static final Logger logger = LoggerFactory.getLogger(AddFunctionMojo.class);
- @Parameter(property = "functionName", required = true)
+ @Parameter(property = "functionName")
private String functionName;
- @Parameter(property = "file", required = true)
+ @Parameter(property = "file")
private File file;
@Parameter(property = "context")
@@ -35,20 +37,48 @@ public abstract class AddFunctionMojo extends PolyApiMojo {
@Parameter(property = "description")
private String description;
- public void execute(String host, Integer port, TokenProvider tokenProvider, HttpClient httpClient, JsonParser jsonParser, MavenService mavenService) {
- var classLoader = mavenService.getProjectClassLoader();
- logger.debug("Setting up the Java parser service.");
- logger.debug("Setting up class loader for all relevant places in the project.");
- JavaParserService javaParserService = new JavaParserServiceImpl(classLoader, jsonParser);
- logger.debug("Setting up HTTP service to access the Function API in Poly.");
- var functionApiService = new PolyFunctionServiceImpl(host, port, httpClient, jsonParser);
- logger.info("Parsing function {} in file {}.", functionName, file.getAbsolutePath());
- var polyFunction = javaParserService.parseFunction(mavenService.getSourceFolders(), mavenService.getJarSources(), file, functionName, description, context);
- logger.info("Poly function {}({}) parsed.", polyFunction.getName(), polyFunction.getArguments().stream().map(PolyFunctionArgument::getType).collect(joining(", ")));
- logger.info("Deploying function.");
- logger.debug("Target URL is {}.", host);
- deployFunction(polyFunction, functionApiService);
- logger.info("Function deployed successfully.");
+ @Override
+ public void execute(String host, Integer port) {
+ var classLoader = getMavenService().getProjectClassLoader();
+ BiConsumer addFunction = (Method method, File file) -> {
+ logger.debug("Setting up the Java parser service.");
+ logger.debug("Setting up class loader for all relevant places in the project.");
+ JavaParserService javaParserService = new JavaParserServiceImpl(classLoader, getJsonParser());
+ logger.debug("Setting up HTTP service to access the Function API in Poly.");
+ logger.info("Parsing function {} in file {}.", method.getName(), file.getAbsolutePath());
+ var polyFunction = javaParserService.parseFunction(getMavenService().getSourceFolders(), getMavenService().getJarSources(), file, method, description, context);
+ logger.info("Poly function {}({}) parsed.", polyFunction.getName(), polyFunction.getArguments().stream().map(PolyFunctionArgument::getType).collect(joining(", ")));
+ logger.info("Deploying function.");
+ logger.debug("Target URL is {}.", host);
+ deployFunction(polyFunction, new PolyFunctionServiceImpl(host, port, getHttpClient(), getJsonParser()));
+ logger.info("Function deployed successfully.");
+ };
+ if (functionName == null && file == null) {
+ getMavenService().getPolyFunctionMethods().forEach(method -> {
+ Class> declaringClass = method.getDeclaringClass();
+ addFunction.accept(method, new File(format("src/main/java/%s/%s.java", declaringClass.getPackageName().replace(".", "/"), declaringClass.getSimpleName())));
+ });
+ } else {
+ if (functionName == null || file == null) {
+ throw new RuntimeException();// FIXME: Throw the appropriate exception
+ }
+ String path = file.getAbsolutePath();
+ String className = path.substring(path.lastIndexOf("src/main/java/") + 14, path.length() - 5).replace("/", ".");
+ try {
+ Class> clazz = classLoader.loadClass(className);
+ logger.info(clazz.getName());
+ List methods = Arrays.stream(clazz.getDeclaredMethods()).filter(method -> method.getName().equalsIgnoreCase(functionName)).toList();
+ if (methods.size() > 1) {
+ throw new RuntimeException(); // FIXME: Throw the appropriate exception
+ }
+ if (methods.isEmpty()) {
+ throw new RuntimeException(); // FIXME: Throw the appropriate exception.
+ }
+ methods.forEach(method -> addFunction.accept(method, file));
+ } catch (ClassNotFoundException e) {
+ throw new QualifiedNameNotFoundException(className, e);
+ }
+ }
}
protected abstract void deployFunction(PolyFunction function, PolyFunctionService polyFunctionService);
diff --git a/polyapi-maven-plugin/src/main/java/io/polyapi/plugin/mojo/GenerateSourcesMojo.java b/polyapi-maven-plugin/src/main/java/io/polyapi/plugin/mojo/GenerateSourcesMojo.java
index b7766d57..0f33ebeb 100644
--- a/polyapi-maven-plugin/src/main/java/io/polyapi/plugin/mojo/GenerateSourcesMojo.java
+++ b/polyapi-maven-plugin/src/main/java/io/polyapi/plugin/mojo/GenerateSourcesMojo.java
@@ -1,9 +1,6 @@
package io.polyapi.plugin.mojo;
-import io.polyapi.commons.api.http.HttpClient;
-import io.polyapi.commons.api.http.TokenProvider;
-import io.polyapi.commons.api.json.JsonParser;
import io.polyapi.commons.api.service.file.FileService;
import io.polyapi.commons.internal.file.FileServiceImpl;
import io.polyapi.plugin.model.specification.Context;
@@ -11,9 +8,8 @@
import io.polyapi.plugin.model.specification.function.CustomFunctionSpecification;
import io.polyapi.plugin.model.specification.function.FunctionSpecification;
import io.polyapi.plugin.model.specification.variable.ServerVariableSpecification;
-import io.polyapi.plugin.service.schema.JsonSchemaParser;
-import io.polyapi.plugin.service.MavenService;
import io.polyapi.plugin.service.SpecificationServiceImpl;
+import io.polyapi.plugin.service.schema.JsonSchemaParser;
import io.polyapi.plugin.service.template.PolyHandlebars;
import io.polyapi.plugin.service.visitor.CodeGenerationVisitor;
import lombok.Setter;
@@ -47,23 +43,23 @@ public class GenerateSourcesMojo extends PolyApiMojo {
@Override
- public void execute(String host, Integer port, TokenProvider tokenProvider, HttpClient httpClient, JsonParser jsonParser, MavenService mavenService) {
+ public void execute(String host, Integer port) {
this.fileService = new FileServiceImpl(new PolyHandlebars(), overwrite);
this.jsonSchemaParser = new JsonSchemaParser();
- var specifications = new SpecificationServiceImpl(host, port, httpClient, jsonParser).getJsonSpecs();
+ var specifications = new SpecificationServiceImpl(host, port, getHttpClient(), getJsonParser()).getJsonSpecs();
var context = new HashMap();
// @FIXME: Are we sure we want to set this ID at this level?
context.put("clientId", UUID.randomUUID().toString());
context.put("host", host);
context.put("port", port);
- context.put("apiKey", tokenProvider.getToken());
+ context.put("apiKey", getTokenProvider().getToken());
// FIXME: We should remove the ClientInfo class.
context.put("apiBaseUrl", format("%s:%s", host, port));
context.put("packageName", "io.polyapi");
fileService.createClassFileWithDefaultPackage("ClientInfo", "clientInfo", context);
fileService.createFileFromTemplate(new File("target/generated-resources/poly.properties"), "poly.properties", context);
- fileService.createFileWithContent(new File(new File("target/.poly"), "specs.json"), jsonParser.toJsonString(specifications));
+ fileService.createFileWithContent(new File(new File("target/.poly"), "specs.json"), getJsonParser().toJsonString(specifications));
writeContext("Poly", specifications, FunctionSpecification.class);
writeContext("Vari", specifications, ServerVariableSpecification.class);
logger.info("Sources generated correctly.");
diff --git a/polyapi-maven-plugin/src/main/java/io/polyapi/plugin/mojo/PolyApiMojo.java b/polyapi-maven-plugin/src/main/java/io/polyapi/plugin/mojo/PolyApiMojo.java
index 42d0cfe2..f66cb7b6 100644
--- a/polyapi-maven-plugin/src/main/java/io/polyapi/plugin/mojo/PolyApiMojo.java
+++ b/polyapi-maven-plugin/src/main/java/io/polyapi/plugin/mojo/PolyApiMojo.java
@@ -8,7 +8,6 @@
import io.polyapi.commons.internal.http.HardcodedTokenProvider;
import io.polyapi.commons.internal.json.JacksonJsonParser;
import io.polyapi.plugin.error.PolyApiMavenPluginException;
-import io.polyapi.plugin.mojo.validation.Validator;
import io.polyapi.plugin.service.MavenService;
import lombok.Setter;
import okhttp3.OkHttpClient;
@@ -22,10 +21,9 @@
import org.slf4j.LoggerFactory;
import java.io.IOException;
-import java.util.Optional;
-import java.util.function.Consumer;
-import static java.lang.String.format;
+import static io.polyapi.plugin.mojo.validation.Validator.validateNotEmpty;
+import static io.polyapi.plugin.mojo.validation.Validator.validatePortFormat;
import static java.nio.charset.Charset.defaultCharset;
import static java.util.concurrent.TimeUnit.MINUTES;
@@ -44,43 +42,42 @@ public abstract class PolyApiMojo extends AbstractMojo {
@Parameter(property = "apiKey")
private String apiKey;
+ private MavenService mavenService;
+ private TokenProvider tokenProvider;
+ private HttpClient httpClient;
+ private JsonParser jsonParser;
@Override
public void execute() throws MojoExecutionException, MojoFailureException {
try {
logger.debug("Setting up Maven service.");
- MavenService mavenService = Optional.ofNullable(project).map(MavenService::new).orElse(null);
- logger.info(mavenService == null ? "No Maven project." : format("Maven service set. Targeting artifact %s.%s:%s.", project.getGroupId(), project.getArtifactId(), project.getVersion()));
-
- Optional.ofNullable(mavenService).ifPresent(getPropertyFromPlugin("host", host, this::setHost));
- Validator.validateNotEmpty("host", host);
-
- Optional.ofNullable(mavenService).ifPresent(getPropertyFromPlugin("port", port, this::setPort));
- Validator.validatePortFormat("port", port);
-
- Optional.ofNullable(mavenService).ifPresent(getPropertyFromPlugin("apiKey", apiKey, this::setApiKey));
- Validator.validateNotEmpty("apiKey", apiKey);
- TokenProvider tokenProvider = new HardcodedTokenProvider(apiKey);
- execute(host, Integer.valueOf(port), tokenProvider, new DefaultHttpClient(new OkHttpClient.Builder()
- .connectTimeout(10, MINUTES)
- .readTimeout(10, MINUTES)
- .writeTimeout(10, MINUTES)
- .build(),
- tokenProvider),
- new JacksonJsonParser(),
- mavenService);
+ mavenService = new MavenService(project);
+ mavenService.getPropertyFromPlugin("host", host, this::setHost);
+ mavenService.getPropertyFromPlugin("port", port, this::setPort);
+ validatePortFormat("port", port);
+ mavenService.getPropertyFromPlugin("apiKey", apiKey, this::setApiKey);
+ validateNotEmpty("apiKey", apiKey);
+ tokenProvider = new HardcodedTokenProvider(apiKey);
+ httpClient = new DefaultHttpClient(new OkHttpClient.Builder()
+ .connectTimeout(10, MINUTES)
+ .readTimeout(10, MINUTES)
+ .writeTimeout(10, MINUTES)
+ .build(),
+ tokenProvider);
+ jsonParser = new JacksonJsonParser();
+ execute(host, Integer.valueOf(port));
} catch (PolyApiMavenPluginException e) {
logger.error("An exception occurred during the plugin execution.", e);
throw new MojoFailureException(e);
} catch (UnexpectedHttpResponseException e) {
logger.error("An unexpected HTTP response code {} was returned from the server.", e.getResponse().statusCode());
- if (logger.isTraceEnabled()) {
+ //if (logger.isTraceEnabled()) {
try {
- logger.trace("Response from server is: {}", IOUtils.toString(e.getResponse().body(), defaultCharset()));
+ logger.info("Response from server is: {}", IOUtils.toString(e.getResponse().body(), defaultCharset()));
} catch (IOException ex) {
throw new MojoExecutionException(ex);
}
- }
+ //}
throw new MojoFailureException(e);
} catch (RuntimeException e) {
logger.error("An unexpected exception occurred during the plugin execution.", e);
@@ -88,10 +85,21 @@ public void execute() throws MojoExecutionException, MojoFailureException {
}
}
- private Consumer getPropertyFromPlugin(String propertyName, String property, Consumer callback) {
- logger.debug("Retrieving property 'port'.");
- return mavenService -> mavenService.getPropertyFromPlugin(propertyName, property, callback);
+ protected MavenService getMavenService() {
+ return this.mavenService;
+ }
+
+ protected TokenProvider getTokenProvider() {
+ return tokenProvider;
+ }
+
+ protected HttpClient getHttpClient() {
+ return httpClient;
+ }
+
+ protected JsonParser getJsonParser() {
+ return jsonParser;
}
- protected abstract void execute(String host, Integer port, TokenProvider tokenProvider, HttpClient httpClient, JsonParser jsonParser, MavenService mavenService);
+ protected abstract void execute(String host, Integer port);
}
diff --git a/polyapi-maven-plugin/src/main/java/io/polyapi/plugin/mojo/function/DeployFunctionsMojo.java b/polyapi-maven-plugin/src/main/java/io/polyapi/plugin/mojo/function/DeployFunctionsMojo.java
new file mode 100644
index 00000000..fbc27258
--- /dev/null
+++ b/polyapi-maven-plugin/src/main/java/io/polyapi/plugin/mojo/function/DeployFunctionsMojo.java
@@ -0,0 +1,63 @@
+package io.polyapi.plugin.mojo.function;
+
+import io.polyapi.commons.api.error.http.HttpResponseException;
+import io.polyapi.plugin.error.deploy.DeploymentWrapperException;
+import io.polyapi.plugin.model.function.PolyFunctionMetadata;
+import io.polyapi.plugin.mojo.PolyApiMojo;
+import io.polyapi.plugin.service.PolyFunctionService;
+import io.polyapi.plugin.service.PolyFunctionServiceImpl;
+import org.apache.commons.io.IOUtils;
+import org.apache.maven.plugins.annotations.Mojo;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import static org.apache.maven.plugins.annotations.ResolutionScope.COMPILE_PLUS_RUNTIME;
+
+@Mojo(name = "deploy-functions", requiresDependencyResolution = COMPILE_PLUS_RUNTIME)
+public class DeployFunctionsMojo extends PolyApiMojo {
+ private static final Logger logger = LoggerFactory.getLogger(DeployFunctionsMojo.class);
+
+ @Override
+ protected void execute(String host, Integer port) {
+ PolyFunctionService polyFunctionService = new PolyFunctionServiceImpl(host, port, getHttpClient(), getJsonParser(), getMavenService().getProjectClassLoader());
+ logger.info("Initiating the deployment of functions.");
+ Set polyFunctions = getMavenService().scanPolyFunctions();
+ Map exceptions = new HashMap<>();
+ polyFunctions.forEach(polyFunctionMetadata -> {
+ logger.debug("Discovered {} function '{}' on context '{}'.", polyFunctionMetadata.getTypedType(), polyFunctionMetadata.name(), polyFunctionMetadata.context());
+ if (polyFunctionMetadata.isDeployable()) {
+ try {
+ String id = polyFunctionService.deploy(polyFunctionMetadata);
+ logger.info("Deployed {} function '{}' on context '{}' with id '{}'", polyFunctionMetadata.getTypedType(), polyFunctionMetadata.name(), polyFunctionMetadata.context(), id);
+ logger.debug("Function can be accessed at {}:{}/functions/{}/{}", host, port, polyFunctionMetadata.getTypedType(), id);
+ } catch (HttpResponseException e) {
+ exceptions.put(polyFunctionMetadata, e);
+ }
+ } else {
+ logger.warn("Deployment of {} function '{}' on context '{}' skipped.", polyFunctionMetadata.getTypedType(), polyFunctionMetadata.name(), polyFunctionMetadata.context());
+ }
+ });
+ if (exceptions.isEmpty()) {
+ logger.info("Deployment of {} functions complete.", polyFunctions.size());
+ } else {
+ logger.error("{} Errors occurred while deploying a total of {} functions.", exceptions.size(), polyFunctions.stream().filter(PolyFunctionMetadata::isDeployable).count());
+ exceptions.forEach((polyFunctionMetadata, exception) -> {
+ logger.debug("{} occurred while deploying {} function '{}' on context '{}'. Exception message is '{}'.", exception.getClass(), polyFunctionMetadata.getTypedType(), polyFunctionMetadata.name(), polyFunctionMetadata.context(), Optional.ofNullable(exception.getMessage()).orElse("No message"));
+ if (exception instanceof HttpResponseException) {
+ try {
+ logger.info(IOUtils.toString(HttpResponseException.class.cast(exception).getResponse().body()));
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ });
+ throw new DeploymentWrapperException(exceptions.values());
+ }
+ }
+}
diff --git a/polyapi-maven-plugin/src/main/java/io/polyapi/plugin/service/JavaParserService.java b/polyapi-maven-plugin/src/main/java/io/polyapi/plugin/service/JavaParserService.java
index 8980a089..8a1f9107 100644
--- a/polyapi-maven-plugin/src/main/java/io/polyapi/plugin/service/JavaParserService.java
+++ b/polyapi-maven-plugin/src/main/java/io/polyapi/plugin/service/JavaParserService.java
@@ -1,12 +1,16 @@
package io.polyapi.plugin.service;
import io.polyapi.plugin.model.function.PolyFunction;
+import io.polyapi.plugin.model.function.PolyFunctionMetadata;
import java.io.File;
+import java.lang.reflect.Method;
import java.util.List;
public interface JavaParserService {
+ PolyFunction parseFunction(PolyFunctionMetadata polyFunctionMetadata);
- PolyFunction parseFunction(List sourceRoots, List jarPaths, File file, String functionName, String description, String context);
+ @Deprecated
+ PolyFunction parseFunction(List sourceRoots, List jarPaths, File file, Method method, String description, String context);
}
diff --git a/polyapi-maven-plugin/src/main/java/io/polyapi/plugin/service/JavaParserServiceImpl.java b/polyapi-maven-plugin/src/main/java/io/polyapi/plugin/service/JavaParserServiceImpl.java
index 60d24c42..4bb252bd 100644
--- a/polyapi-maven-plugin/src/main/java/io/polyapi/plugin/service/JavaParserServiceImpl.java
+++ b/polyapi-maven-plugin/src/main/java/io/polyapi/plugin/service/JavaParserServiceImpl.java
@@ -3,51 +3,58 @@
import com.fasterxml.jackson.databind.JavaType;
import com.github.javaparser.JavaParser;
import com.github.javaparser.ParserConfiguration;
-import com.github.javaparser.ast.Node;
import com.github.javaparser.ast.NodeList;
+import com.github.javaparser.ast.PackageDeclaration;
+import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
import com.github.javaparser.ast.body.MethodDeclaration;
import com.github.javaparser.ast.body.TypeDeclaration;
+import com.github.javaparser.ast.comments.JavadocComment;
import com.github.javaparser.ast.expr.Expression;
import com.github.javaparser.ast.expr.MethodCallExpr;
+import com.github.javaparser.ast.expr.Name;
import com.github.javaparser.ast.expr.NameExpr;
import com.github.javaparser.ast.stmt.BlockStmt;
import com.github.javaparser.ast.stmt.ExpressionStmt;
import com.github.javaparser.ast.stmt.ReturnStmt;
+import com.github.javaparser.ast.type.Type;
import com.github.javaparser.resolution.declarations.ResolvedParameterDeclaration;
import com.github.javaparser.resolution.types.ResolvedType;
-import com.github.javaparser.resolution.types.ResolvedVoidType;
import com.github.javaparser.symbolsolver.JavaSymbolSolver;
-import com.github.javaparser.symbolsolver.resolution.typesolvers.ClassLoaderTypeSolver;
-import com.github.javaparser.symbolsolver.resolution.typesolvers.CombinedTypeSolver;
-import com.github.javaparser.symbolsolver.resolution.typesolvers.JarTypeSolver;
-import com.github.javaparser.symbolsolver.resolution.typesolvers.JavaParserTypeSolver;
-import com.github.javaparser.symbolsolver.resolution.typesolvers.ReflectionTypeSolver;
+import com.github.javaparser.symbolsolver.resolution.typesolvers.*;
import io.polyapi.commons.api.json.JsonParser;
+import io.polyapi.commons.api.model.PolyGeneratedClass;
import io.polyapi.plugin.error.PolyApiMavenPluginException;
import io.polyapi.plugin.error.classloader.QualifiedNameNotFoundException;
import io.polyapi.plugin.model.TypeData;
+import io.polyapi.plugin.model.function.CodeObject;
import io.polyapi.plugin.model.function.PolyFunction;
import io.polyapi.plugin.model.function.PolyFunctionArgument;
+import io.polyapi.plugin.model.function.PolyFunctionMetadata;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
+import java.lang.reflect.Method;
import java.util.*;
import static com.fasterxml.jackson.databind.type.TypeFactory.defaultInstance;
import static com.github.javaparser.ast.Modifier.Keyword.PUBLIC;
import static java.lang.Character.isUpperCase;
+import static java.lang.String.format;
+import static java.util.stream.Collectors.joining;
import static java.util.stream.IntStream.range;
public class JavaParserServiceImpl implements JavaParserService {
private static final Logger logger = LoggerFactory.getLogger(JavaParserServiceImpl.class);
private final JsonParser jsonParser;
private final ClassLoader classLoader;
+ private final JavaParser javaParser;
public JavaParserServiceImpl(ClassLoader classLoader, JsonParser jsonParser) {
this.classLoader = classLoader;
+ this.javaParser = new JavaParser(new ParserConfiguration().setSymbolResolver(new JavaSymbolSolver(new ClassLoaderTypeSolver(classLoader))));
this.jsonParser = jsonParser;
}
@@ -84,7 +91,73 @@ private java.lang.reflect.Type toMap(ResolvedType type) {
}
@Override
- public PolyFunction parseFunction(List sourceRoots, List jarPaths, File file, String functionName, String description, String context) {
+ public PolyFunction parseFunction(PolyFunctionMetadata polyFunctionMetadata) {
+ var compilationUnit = javaParser.parse(polyFunctionMetadata.sourceCode()).getResult().orElseThrow();
+ return compilationUnit.getTypes().stream()
+ .map(TypeDeclaration::getMethods)
+ .flatMap(List::stream)
+ .filter(methodDeclaration -> format("%s(%s)", methodDeclaration.getName(), methodDeclaration.getSignature().getParameterTypes().stream().map(Type::asString).collect(joining(", "))).equals(polyFunctionMetadata.signature()))
+ .peek(methodDeclaration -> logger.debug("Found matching method declaration: {}", methodDeclaration.getSignature()))
+ .map(methodDeclaration -> {
+ CodeObject codeObject = new CodeObject();
+ codeObject.setPackageName(compilationUnit.getPackageDeclaration().map(PackageDeclaration::getName).map(Name::asString).orElse(""));
+ codeObject.setClassName(compilationUnit.getType(0).getNameAsString());
+ codeObject.setMethodName(methodDeclaration.getNameAsString());
+
+ var resolvedMethodDeclaration = methodDeclaration.resolve();
+ ClassOrInterfaceDeclaration classOrInterfaceDeclaration = compilationUnit.getType(0).asClassOrInterfaceDeclaration()
+ .addAnnotation(PolyGeneratedClass.class);
+ logger.debug("Creating PolyFunction from method declaration: {}", resolvedMethodDeclaration.getName());
+ var function = new PolyFunction();
+ function.setContext(polyFunctionMetadata.context());
+ methodDeclaration.getJavadocComment().map(JavadocComment::asString).ifPresent(function::setDescription);
+ function.setArguments(new ArrayList<>());
+ logger.trace("Parsing return type for {}.", resolvedMethodDeclaration.getName());
+ var typeData = parse(resolvedMethodDeclaration.getReturnType());
+ function.setName(resolvedMethodDeclaration.getName());
+ function.setReturnType(typeData.name());
+ logger.trace("Adding JSon schema to return type.");
+ if (!resolvedMethodDeclaration.getReturnType().isVoid()) {
+ function.setReturnTypeSchema(jsonParser.parseString(typeData.jsonSchema(), defaultInstance().constructMapType(HashMap.class, String.class, Object.class)));
+ }
+ logger.trace("Parsing parameters.");
+ range(0, resolvedMethodDeclaration.getNumberOfParams()).boxed().map(resolvedMethodDeclaration::getParam)
+ .peek(param -> logger.trace(" Parsing parameter {}.", param.getName()))
+ .map(param -> {
+ logger.debug("Adding parameter '{}' to execute method.", param.getName());
+ logger.trace("Converting to PolyFunctionArgument.");
+ var argument = new PolyFunctionArgument();
+ argument.setKey(param.getName());
+ argument.setName(param.getName());
+ var argumentTypeData = parse(param.getType());
+ switch (param.getType().asReferenceType().getQualifiedName()) {
+ case "java.lang.Integer", "java.lang.Long", "java.lang.Number", "java.lang.Double", "java.lang.Float", "java.lang.Short", "java.lang.Byte" ->
+ argument.setType("number");
+ case "java.lang.Boolean" -> argument.setType("boolean");
+ case "java.lang.String", "java.lang.Character" -> argument.setType("string");
+ default -> {
+ argument.setType("object");
+ argument.setTypeSchema(argumentTypeData.jsonSchema());
+ }
+ }
+ return argument;
+ })
+ .forEach(function.getArguments()::add);
+ codeObject.setParams(function.getArguments().stream().map(PolyFunctionArgument::getType).collect(joining(",")));
+ logger.trace("Parsed {} parameters.", function.getArguments().size());
+ codeObject.setCode(compilationUnit.toString());
+ function.setCode(jsonParser.toJsonString(codeObject));
+ logger.info(function.getCode());
+ function.setRequirements(polyFunctionMetadata.dependencies());
+ return function;
+ })
+ .findFirst()
+ .orElse(null);
+ }
+
+ @Deprecated
+ @Override
+ public PolyFunction parseFunction(List sourceRoots, List jarPaths, File file, Method method, String description, String context) {
try {
logger.debug("Setting up a combined type solvers.");
var combinedTypeSolver = new CombinedTypeSolver();
@@ -118,7 +191,7 @@ public PolyFunction parseFunction(List sourceRoots, List jarPaths,
.map(TypeDeclaration::resolve)
.forEach(resolvedCompilationUnit -> {
resolvedCompilationUnit.getDeclaredMethods().stream()
- .filter(methodDeclaration -> methodDeclaration.getName().equals(functionName))
+ .filter(methodDeclaration -> methodDeclaration.getName().equals(method.getName()))
.peek(methodDeclaration -> logger.debug("Found matching method declaration: {}", methodDeclaration.getSignature()))
.forEach(methodDeclaration -> {
logger.debug("Creating PolyFunction from method declaration: {}", methodDeclaration.getName());
@@ -180,13 +253,12 @@ public PolyFunction parseFunction(List sourceRoots, List jarPaths,
});
if (functions.isEmpty()) {
- throw new PolyApiMavenPluginException("No function with name " + functionName + " found in file: " + file.getAbsolutePath());
+ throw new PolyApiMavenPluginException("No function with name " + method.getName() + " found in file: " + file.getAbsolutePath());
} else if (functions.size() > 1) {
- throw new PolyApiMavenPluginException("More than one function with name " + functionName + " found in file: " + file.getAbsolutePath());
+ throw new PolyApiMavenPluginException("More than one function with name " + method.getName() + " found in file: " + file.getAbsolutePath());
}
return functions.get(0);
- } catch (
- FileNotFoundException e) {
+ } catch (FileNotFoundException e) {
throw new PolyApiMavenPluginException("Error parsing file", e);
}
diff --git a/polyapi-maven-plugin/src/main/java/io/polyapi/plugin/service/MavenService.java b/polyapi-maven-plugin/src/main/java/io/polyapi/plugin/service/MavenService.java
index dfcde8c5..6e4ee833 100644
--- a/polyapi-maven-plugin/src/main/java/io/polyapi/plugin/service/MavenService.java
+++ b/polyapi-maven-plugin/src/main/java/io/polyapi/plugin/service/MavenService.java
@@ -1,28 +1,52 @@
package io.polyapi.plugin.service;
+import io.polyapi.commons.api.model.PolyFunction;
+import io.polyapi.commons.api.model.PolyGeneratedClass;
+import io.polyapi.commons.api.model.RequiredDependencies;
+import io.polyapi.commons.api.model.RequiredDependency;
import io.polyapi.plugin.error.PolyApiMavenPluginException;
+import io.polyapi.plugin.error.validation.InvalidPropertyException;
+import io.polyapi.plugin.error.validation.KeywordUseException;
import io.polyapi.plugin.error.validation.PropertyNotFoundException;
+import io.polyapi.plugin.model.function.PolyFunctionMetadata;
import org.apache.maven.artifact.DependencyResolutionRequiredException;
import org.apache.maven.model.Plugin;
import org.apache.maven.model.PluginExecution;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.util.xml.Xpp3Dom;
+import org.reflections.Reflections;
+import org.reflections.util.ConfigurationBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import javax.lang.model.SourceVersion;
import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Parameter;
+import java.lang.reflect.Type;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
-import java.util.List;
-import java.util.Objects;
+import java.util.*;
import java.util.function.Consumer;
+import java.util.regex.Pattern;
import java.util.stream.Stream;
+import static java.lang.String.format;
+import static java.util.function.Predicate.not;
+import static java.util.regex.Pattern.compile;
+import static java.util.stream.Collectors.joining;
+import static java.util.stream.Collectors.toSet;
import static java.util.stream.Stream.concat;
+import static javax.lang.model.SourceVersion.isKeyword;
+import static org.reflections.scanners.Scanners.MethodsAnnotated;
public class MavenService {
private static final Logger logger = LoggerFactory.getLogger(MavenService.class);
+ private static final String FUNCTION_NAME_PATTERN = "^[a-z][\\w$_]+$";
+ private static final String CONTEXT_PATTERN = "^[a-z][\\w$_.]*[\\w$_]$";
private final MavenProject project;
public MavenService(MavenProject project) {
@@ -65,7 +89,7 @@ public String getPropertyFromPlugin(String pluginGroupId, String pluginArtifactI
.orElseThrow(() -> new PropertyNotFoundException(propertyName));
}
- public ClassLoader getProjectClassLoader() {
+ public URLClassLoader getProjectClassLoader() {
try {
return new URLClassLoader(concat(concat(project.getCompileClasspathElements().stream(),
project.getRuntimeClasspathElements().stream()),
@@ -82,14 +106,14 @@ public ClassLoader getProjectClassLoader() {
}
})
.toArray(URL[]::new),
- ClassLoader.getSystemClassLoader());
+ MavenService.class.getClassLoader());
} catch (DependencyResolutionRequiredException e) {
throw new RuntimeException(e);
}
}
public List getSourceFolders() {
- return Stream.concat(project.getCompileSourceRoots().stream(), Stream.of(project.getBasedir() + "/target/generated-sources"))
+ return concat(project.getCompileSourceRoots().stream(), Stream.of(project.getBasedir() + "/target/generated-sources"))
.peek(sourceRoot -> logger.debug(" Retrieving source root '{}'", sourceRoot))
.map(File::new)
.filter(File::exists)
@@ -107,4 +131,85 @@ public List getJarSources() {
throw new PolyApiMavenPluginException(e);
}
}
+
+ public Set scanPolyFunctions() {
+ logger.info("Scanning the project for functions annotated with {}}.", PolyFunction.class.getName());
+ URLClassLoader projectClassLoader = getProjectClassLoader();
+ Reflections reflections = new Reflections(new ConfigurationBuilder()
+ .addClassLoaders(projectClassLoader)
+ .addScanners(MethodsAnnotated)
+ .addUrls(projectClassLoader.getURLs()));
+ logger.debug("Reflections URLS: {}", reflections.getConfiguration().getUrls().size());
+ Set methods = reflections.getMethodsAnnotatedWith(PolyFunction.class);
+ logger.info("Found {} methods to convert.", methods.size());
+
+ List.of(RequiredDependency.class, RequiredDependencies.class).forEach(annotation ->
+ reflections.getMethodsAnnotatedWith(annotation).stream()
+ .filter(not(methods::contains))
+ .forEach(misusedMethod -> {
+ logger.warn("Method {} is annotated with {} but is ignored as it needs to be annotated with {} to be scanned.", misusedMethod, misusedMethod.getAnnotation(annotation).getClass().getSimpleName(), PolyFunction.class.getSimpleName());
+ }));
+ return methods.stream()
+ .filter(not(method -> method.getDeclaringClass().isAnnotationPresent(PolyGeneratedClass.class)))
+ .map(method -> {
+ try {
+ logger.info("Processing method '{}'.", method);
+ PolyFunction polyFunction = method.getAnnotation(PolyFunction.class);
+ logger.debug("Retrieving function name.");
+ String functionName = Optional.ofNullable(polyFunction.name()).filter(not(String::isBlank)).orElseGet(method::getName);
+ if (!functionName.matches(FUNCTION_NAME_PATTERN)) {
+ throw new InvalidPropertyException("functionName", functionName, method, FUNCTION_NAME_PATTERN);
+ }
+ if (isKeyword(functionName.trim())) {
+ throw new KeywordUseException("functionName", functionName, method, functionName);
+ }
+ Class> declaringClass = method.getDeclaringClass();
+ logger.debug("Retrieving context.");
+ String context = Optional.ofNullable(polyFunction.context()).filter(not(String::isEmpty)).orElseGet(declaringClass::getPackageName);
+ if (!context.matches(CONTEXT_PATTERN)) {
+ throw new InvalidPropertyException("context", context, method, CONTEXT_PATTERN);
+ }
+ Optional.of(Arrays.stream(context.split("\\.")).filter(SourceVersion::isKeyword).toArray(String[]::new)).filter(array -> array.length > 0).ifPresent(keywords -> {
+ throw new KeywordUseException("context", context, method, keywords);
+ });
+ logger.debug("Retrieving required dependencies.");
+ Pattern pattern = compile(Optional.of(concat(Optional.ofNullable(method.getAnnotation(RequiredDependencies.class)).map(RequiredDependencies::value).stream().flatMap(Arrays::stream), Optional.ofNullable(method.getAnnotation(RequiredDependency.class)).stream())
+ .map(requiredDependency -> format("%s:%s:%s", requiredDependency.groupId(), requiredDependency.artifactId(), requiredDependency.version()))
+ .collect(joining("|")))
+ .filter(not(String::isEmpty))
+ .orElse("(?=a)b"));
+ logger.debug("Pattern used to match required dependencies is: {}", pattern.pattern());
+ List requiredDependencies = project.getDependencies().stream()
+ .map(dependency -> format("%s:%s:%s", dependency.getGroupId(), dependency.getArtifactId(), dependency.getVersion()))
+ .filter(pattern.asPredicate())
+ .toList();
+ logger.debug("Required dependencies found: {}", requiredDependencies);
+ return new PolyFunctionMetadata(functionName,
+ format("%s(%s)", method.getName(), Arrays.stream(method.getParameters()).map(Parameter::getType).map(Class::getName).collect(joining(", "))),
+ polyFunction.type(),
+ new FileInputStream(format("src/main/java/%s/%s.java",
+ declaringClass.getPackageName().replace(".", "/"),
+ declaringClass.getSimpleName())),
+ context,
+ requiredDependencies,
+ polyFunction.isDeployable());
+ } catch (FileNotFoundException e) {
+ throw new RuntimeException(e); // FIXME: Throw the appropriate exception.
+ }
+ })
+ .collect(toSet());
+ }
+
+ public Set getPolyFunctionMethods() {
+ logger.info("Scanning projects for methods annotated with @PolyFunction.");
+ URLClassLoader projectClassLoader = getProjectClassLoader();
+ Reflections reflections = new Reflections(new ConfigurationBuilder()
+ .addClassLoaders(projectClassLoader)
+ .addScanners(MethodsAnnotated)
+ .addUrls(projectClassLoader.getURLs()));
+ logger.info("Reflections URLS: {}", reflections.getConfiguration().getUrls().size());
+ Set methods = reflections.getMethodsAnnotatedWith(PolyFunction.class);
+ logger.info("Methods: {}", methods.size());
+ return methods;
+ }
}
diff --git a/polyapi-maven-plugin/src/main/java/io/polyapi/plugin/service/PolyFunctionService.java b/polyapi-maven-plugin/src/main/java/io/polyapi/plugin/service/PolyFunctionService.java
index 8870da25..0c2de6b4 100644
--- a/polyapi-maven-plugin/src/main/java/io/polyapi/plugin/service/PolyFunctionService.java
+++ b/polyapi-maven-plugin/src/main/java/io/polyapi/plugin/service/PolyFunctionService.java
@@ -1,10 +1,13 @@
package io.polyapi.plugin.service;
import io.polyapi.plugin.model.function.PolyFunction;
+import io.polyapi.plugin.model.function.PolyFunctionMetadata;
public interface PolyFunctionService {
PolyFunction postServerFunction(PolyFunction polyFunction);
PolyFunction postClientFunction(PolyFunction polyFunction);
+
+ String deploy(PolyFunctionMetadata polyFunctionMetadata);
}
diff --git a/polyapi-maven-plugin/src/main/java/io/polyapi/plugin/service/PolyFunctionServiceImpl.java b/polyapi-maven-plugin/src/main/java/io/polyapi/plugin/service/PolyFunctionServiceImpl.java
index 759da4c3..cca07557 100644
--- a/polyapi-maven-plugin/src/main/java/io/polyapi/plugin/service/PolyFunctionServiceImpl.java
+++ b/polyapi-maven-plugin/src/main/java/io/polyapi/plugin/service/PolyFunctionServiceImpl.java
@@ -4,22 +4,45 @@
import io.polyapi.commons.api.json.JsonParser;
import io.polyapi.commons.api.service.PolyApiService;
import io.polyapi.plugin.model.function.PolyFunction;
+import io.polyapi.plugin.model.function.PolyFunctionMetadata;
+import org.apache.commons.io.IOUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
-import java.util.HashMap;
+import java.io.IOException;
+
+import static java.lang.String.format;
public class PolyFunctionServiceImpl extends PolyApiService implements PolyFunctionService {
+ private static final Logger logger = LoggerFactory.getLogger(PolyFunctionServiceImpl.class);
+ private final JavaParserService javaParserService;
+ @Deprecated
public PolyFunctionServiceImpl(String host, Integer port, HttpClient client, JsonParser jsonParser) {
+ this(host, port, client, jsonParser, null);
+ }
+
+ public PolyFunctionServiceImpl(String host, Integer port, HttpClient client, JsonParser jsonParser, ClassLoader classLoader) {
super(host, port, client, jsonParser);
+ this.javaParserService = new JavaParserServiceImpl(classLoader, jsonParser);
}
@Override
public PolyFunction postServerFunction(PolyFunction polyFunction) {
- return post("functions/server", new HashMap<>(), new HashMap<>(), polyFunction, PolyFunction.class);
+ return post("functions/server", polyFunction, PolyFunction.class);
}
@Override
public PolyFunction postClientFunction(PolyFunction polyFunction) {
- return post("functions/client", new HashMap<>(), new HashMap<>(), polyFunction, PolyFunction.class);
+ return post("functions/client", polyFunction, PolyFunction.class);
+ }
+
+ @Override
+ public String deploy(PolyFunctionMetadata polyFunctionMetadata) {
+ logger.info("Deploying {} function '{}' on context '{}'.", polyFunctionMetadata.getTypedType(), polyFunctionMetadata.name(), polyFunctionMetadata.context());
+ PolyFunction function = post(format("functions/%s", polyFunctionMetadata.getTypedType()), javaParserService.parseFunction(polyFunctionMetadata), PolyFunction.class);
+ logger.info("{} -> {}", polyFunctionMetadata, function.getCode());
+ logger.info("Deployment of {} function '{}' on context'{}' successful.", polyFunctionMetadata.getTypedType(), polyFunctionMetadata.name(), polyFunctionMetadata.context());
+ return function.getId();
}
}
diff --git a/pom.xml b/pom.xml
index e72b9409..1be01209 100644
--- a/pom.xml
+++ b/pom.xml
@@ -3,7 +3,7 @@
4.0.0
io.polyapi
polyapi-java
- 0.2.6-SNAPSHOT
+ 0.3.0-SNAPSHOT
pom
parent-pom