├── .travis.yml ├── gradle ├── .gradle │ └── 2.1 │ │ └── taskArtifacts │ │ ├── cache.properties │ │ ├── fileHashes.bin │ │ ├── fileSnapshots.bin │ │ ├── taskArtifacts.bin │ │ ├── cache.properties.lock │ │ └── outputFileStates.bin ├── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── cyngn.gradle ├── gradlew.bat └── gradlew ├── .gitignore ├── .gitreview ├── src ├── main │ └── java │ │ └── com │ │ └── cyngn │ │ └── exovert │ │ ├── generate │ │ ├── server │ │ │ ├── rest │ │ │ │ ├── types │ │ │ │ │ ├── Request.java │ │ │ │ │ ├── Response.java │ │ │ │ │ ├── Validation.java │ │ │ │ │ ├── EnumValue.java │ │ │ │ │ ├── EnumType.java │ │ │ │ │ ├── LengthValidation.java │ │ │ │ │ ├── DataTypeGroup.java │ │ │ │ │ ├── ClassType.java │ │ │ │ │ ├── Api.java │ │ │ │ │ └── Field.java │ │ │ │ ├── GenerationContext.java │ │ │ │ ├── DataTypeSpec.java │ │ │ │ ├── InterfaceSpec.java │ │ │ │ ├── utils │ │ │ │ │ ├── Constants.java │ │ │ │ │ └── RestGeneratorHelper.java │ │ │ │ ├── TypeMap.java │ │ │ │ ├── RestServerGenerator.java │ │ │ │ ├── TypeGenerator.java │ │ │ │ ├── CommonRestGenerator.java │ │ │ │ ├── TypeMapImpl.java │ │ │ │ ├── TypeParser.java │ │ │ │ └── RestClientGenerator.java │ │ │ ├── config │ │ │ │ ├── ConfTemplate.java │ │ │ │ └── LogbackTemplate.java │ │ │ └── ServerGenerator.java │ │ ├── entity │ │ │ ├── TypeResult.java │ │ │ ├── UDTGenerator.java │ │ │ ├── TableGenerator.java │ │ │ └── EntityGeneratorHelper.java │ │ ├── project │ │ │ ├── ProjectGenerator.java │ │ │ └── GradleTemplate.java │ │ └── storage │ │ │ ├── AccessorGenerator.java │ │ │ └── DalGenerator.java │ │ ├── util │ │ ├── VertxRef.java │ │ ├── GeneratorHelper.java │ │ ├── Udt.java │ │ ├── Disk.java │ │ └── MetaData.java │ │ ├── RestCreator.java │ │ └── CrudCreator.java └── test │ └── java │ └── com │ └── cyngn │ └── exovert │ └── generate │ └── server │ └── model │ ├── TypeMapImplTest.java │ ├── JacksonTest.java │ ├── utils │ ├── api.json │ └── RestGeneratorHelperTest.java │ └── TypeParserTest.java ├── samples ├── api-with-defaults.json ├── types.json └── api.json ├── gradlew ├── README.md └── LICENSE /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: 3 | - oraclejdk8 4 | -------------------------------------------------------------------------------- /gradle/.gradle/2.1/taskArtifacts/cache.properties: -------------------------------------------------------------------------------- 1 | #Fri Apr 17 22:29:02 PDT 2015 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | .idea 3 | *.iml 4 | *.ipr 5 | build 6 | tmp 7 | *.DS_Store 8 | -------------------------------------------------------------------------------- /.gitreview: -------------------------------------------------------------------------------- 1 | [gerrit] 2 | host=review.cyanogenmod.org 3 | port=29418 4 | project=cyngn/exovert 5 | defaultbranch=master 6 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/exovert/master/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/exovert/master/gradle/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/.gradle/2.1/taskArtifacts/fileHashes.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/exovert/master/gradle/.gradle/2.1/taskArtifacts/fileHashes.bin -------------------------------------------------------------------------------- /gradle/.gradle/2.1/taskArtifacts/fileSnapshots.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/exovert/master/gradle/.gradle/2.1/taskArtifacts/fileSnapshots.bin -------------------------------------------------------------------------------- /gradle/.gradle/2.1/taskArtifacts/taskArtifacts.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/exovert/master/gradle/.gradle/2.1/taskArtifacts/taskArtifacts.bin -------------------------------------------------------------------------------- /gradle/.gradle/2.1/taskArtifacts/cache.properties.lock: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/exovert/master/gradle/.gradle/2.1/taskArtifacts/cache.properties.lock -------------------------------------------------------------------------------- /gradle/.gradle/2.1/taskArtifacts/outputFileStates.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/angebagui/exovert/master/gradle/.gradle/2.1/taskArtifacts/outputFileStates.bin -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Sep 09 12:20:21 PDT 2015 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.0-all.zip 7 | -------------------------------------------------------------------------------- /gradle/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Apr 17 22:29:02 PDT 2015 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.1-bin.zip 7 | -------------------------------------------------------------------------------- /src/main/java/com/cyngn/exovert/generate/server/rest/types/Request.java: -------------------------------------------------------------------------------- 1 | package com.cyngn.exovert.generate.server.rest.types; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * Type specification for API request object 9 | * 10 | * @author asarda@cyngn.com (Ajay Sarda) 9/8/15. 11 | */ 12 | public class Request { 13 | /** 14 | * List of fields 15 | */ 16 | @JsonProperty("fields") 17 | public List fields; 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/cyngn/exovert/generate/server/rest/types/Response.java: -------------------------------------------------------------------------------- 1 | package com.cyngn.exovert.generate.server.rest.types; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * Type Specification for Response object 9 | * 10 | * @author asarda@cyngn.com (Ajay Sarda) 9/8/15. 11 | */ 12 | 13 | public class Response { 14 | /** 15 | * List of fields 16 | */ 17 | @JsonProperty("fields") 18 | public List fields; 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/cyngn/exovert/generate/server/rest/types/Validation.java: -------------------------------------------------------------------------------- 1 | package com.cyngn.exovert.generate.server.rest.types; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | 5 | /** 6 | * Class to holds different types of validation that can be 7 | * applied for Type 8 | * 9 | * @author asarda@cyngn.com (Ajay Sarda) 9/8/15. 10 | */ 11 | public class Validation { 12 | /** 13 | * length validation 14 | */ 15 | @JsonProperty 16 | public LengthValidation length; 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/cyngn/exovert/generate/entity/TypeResult.java: -------------------------------------------------------------------------------- 1 | package com.cyngn.exovert.generate.entity; 2 | 3 | import com.squareup.javapoet.TypeName; 4 | 5 | /** 6 | * The result of a type decomposition. 7 | * 8 | * @author truelove@cyngn.com (Jeremy Truelove) 8/28/15 9 | */ 10 | class TypeResult { 11 | public TypeName type; 12 | public boolean hasFrozenType; 13 | 14 | public TypeResult(TypeName type, boolean hasFrozenType) { 15 | this.type = type; 16 | this.hasFrozenType = hasFrozenType; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/cyngn/exovert/generate/server/rest/types/EnumValue.java: -------------------------------------------------------------------------------- 1 | package com.cyngn.exovert.generate.server.rest.types; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | 5 | /** 6 | * Type for enum value 7 | * 8 | * {@link EnumType} contains the list of {@link EnumValue} 9 | * 10 | * @author asarda@cyngn.com (Ajay Sarda) 9/14/15. 11 | */ 12 | public class EnumValue { 13 | 14 | /** 15 | * Enum name 16 | */ 17 | @JsonProperty 18 | public String name; 19 | 20 | /** 21 | * Enum value 22 | */ 23 | @JsonProperty 24 | public String value; 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/cyngn/exovert/generate/server/rest/GenerationContext.java: -------------------------------------------------------------------------------- 1 | package com.cyngn.exovert.generate.server.rest; 2 | 3 | import com.cyngn.exovert.generate.server.rest.utils.RestGeneratorHelper; 4 | 5 | /** 6 | * Stores context metadata around generation run 7 | * 8 | * @author asarda@cyngn.com (Ajay Sarda) 9/14/15. 9 | */ 10 | class GenerationContext { 11 | public TypeMap typeMap; 12 | 13 | // setting default context 14 | public boolean preview = false; 15 | 16 | public String outputDirectory = 17 | RestGeneratorHelper.getGeneratedSourceDirectory(); 18 | 19 | public String specFilePath = "api.json"; 20 | 21 | public boolean client; 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/cyngn/exovert/generate/server/rest/DataTypeSpec.java: -------------------------------------------------------------------------------- 1 | package com.cyngn.exovert.generate.server.rest; 2 | 3 | import com.cyngn.exovert.generate.server.rest.types.DataTypeGroup; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * Specifications for data types 10 | * 11 | * @author asarda@cyngn.com (Ajay Sarda) 9/17/15. 12 | */ 13 | public class DataTypeSpec { 14 | 15 | /** 16 | * List of {@link DataTypeGroup} 17 | * 18 | * {@link DataTypeGroup} represents the set of datatypes in package namespace. 19 | */ 20 | @JsonProperty("data_type_groups") 21 | public List dataTypeGroups; 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/cyngn/exovert/generate/server/rest/types/EnumType.java: -------------------------------------------------------------------------------- 1 | package com.cyngn.exovert.generate.server.rest.types; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | 5 | import java.util.Set; 6 | 7 | /** 8 | * Specification Class for Enumerated type. 9 | * 10 | * Contains list of {@link EnumValue} 11 | * 12 | * @author asarda@cyngn.com (Ajay Sarda) 9/14/15. 13 | */ 14 | public class EnumType { 15 | 16 | /** 17 | * Name of enum class 18 | */ 19 | @JsonProperty 20 | public String name; 21 | 22 | /** 23 | * Set of enum values 24 | */ 25 | @JsonProperty 26 | public Set values; 27 | 28 | /** 29 | * Enum type description 30 | */ 31 | @JsonProperty 32 | public String description; 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/cyngn/exovert/util/VertxRef.java: -------------------------------------------------------------------------------- 1 | package com.cyngn.exovert.util; 2 | 3 | import io.vertx.core.Vertx; 4 | 5 | import java.util.concurrent.atomic.AtomicBoolean; 6 | 7 | /** 8 | * Util to get access to the current vert.x context. 9 | * 10 | * @author truelove@cyngn.com (Jeremy Truelove) 8/28/15 11 | */ 12 | public class VertxRef { 13 | public static final VertxRef instance = new VertxRef(); 14 | private AtomicBoolean initialized; 15 | private Vertx vertx; 16 | 17 | private VertxRef(){ 18 | initialized = new AtomicBoolean(false); 19 | } 20 | 21 | public synchronized void init(Vertx vertx) { 22 | if(initialized.compareAndSet(false, true)) { 23 | this.vertx = vertx; 24 | } 25 | } 26 | 27 | public Vertx get() { return vertx; } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/cyngn/exovert/generate/server/rest/types/LengthValidation.java: -------------------------------------------------------------------------------- 1 | package com.cyngn.exovert.generate.server.rest.types; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | 5 | /** 6 | * Range validation on length of the objects. 7 | * 8 | * This can be used to validate the length of the string, 9 | * domain of the integer, batch size etc for request fields. 10 | * 11 | * @author asarda@cyngn.com (Ajay Sarda) 9/8/15. 12 | */ 13 | public class LengthValidation { 14 | 15 | /** 16 | * Minimum length. 17 | * 18 | * For Strings, this can be set to non-zero to define mandate for 19 | * non-empty strings 20 | */ 21 | @JsonProperty 22 | public int min; 23 | 24 | /** 25 | * Maximum length. 26 | */ 27 | @JsonProperty 28 | public int max; 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/cyngn/exovert/generate/server/config/ConfTemplate.java: -------------------------------------------------------------------------------- 1 | package com.cyngn.exovert.generate.server.config; 2 | 3 | /** 4 | * sample conf.json template file, long term need to use a better templating solution. 5 | * 6 | * @author truelove@cyngn.com (Jeremy Truelove) 9/10/15 7 | */ 8 | public class ConfTemplate { 9 | public final static String TEMPLATE = "{\n" + 10 | " \"port\" : 8080,\n" + 11 | " \"cassandra\": {\n" + 12 | " \"seeds\": [\"localhost\"],\n" + 13 | " \"reconnect\": {\n" + 14 | " \"name\": \"exponential\",\n" + 15 | " \"base_delay\": 1000,\n" + 16 | " \"max_delay\": 10000\n" + 17 | " }\n" + 18 | " }\n" + 19 | "}"; 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/cyngn/exovert/generate/server/rest/types/DataTypeGroup.java: -------------------------------------------------------------------------------- 1 | package com.cyngn.exovert.generate.server.rest.types; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * Group type for generating {@link EnumType} and {@link ClassType} in 9 | * package namespace aka grouping of types at package namespace. 10 | * 11 | * @author asarda@cyngn.com (Ajay Sarda) 9/17/15. 12 | */ 13 | public class DataTypeGroup { 14 | /** 15 | * package namespace 16 | */ 17 | @JsonProperty 18 | public String namespace; 19 | 20 | /** 21 | * List of enumerated types 22 | */ 23 | @JsonProperty("enum_types") 24 | public List enumTypes; 25 | 26 | /** 27 | * List of class types 28 | */ 29 | @JsonProperty("class_types") 30 | public List classTypes; 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/cyngn/exovert/generate/project/ProjectGenerator.java: -------------------------------------------------------------------------------- 1 | package com.cyngn.exovert.generate.project; 2 | 3 | import com.cyngn.exovert.util.Disk; 4 | import com.cyngn.exovert.util.MetaData; 5 | 6 | import java.io.IOException; 7 | 8 | /** 9 | * Handle generating project gradle files. 10 | * 11 | * @author truelove@cyngn.com (Jeremy Truelove) 9/9/15 12 | */ 13 | public class ProjectGenerator { 14 | 15 | /** 16 | * Handles generating project gradle files 17 | * 18 | * @param projectName the name of the project 19 | * @throws IOException thrown if can't write to disk 20 | */ 21 | public static void generate(String projectName) throws IOException { 22 | String namespace = MetaData.instance.getNamespace(); 23 | String file = String.format(GradleTemplate.TEMPLATE, namespace, projectName, namespace + ".Server"); 24 | 25 | Disk.outputFile(file, "build.gradle"); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/cyngn/exovert/generate/server/rest/types/ClassType.java: -------------------------------------------------------------------------------- 1 | package com.cyngn.exovert.generate.server.rest.types; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * Specification type for Class. 9 | * 10 | * @author asarda@cyngn.com (Ajay Sarda) 9/14/15. 11 | */ 12 | public class ClassType { 13 | 14 | /** 15 | * Class Name 16 | */ 17 | @JsonProperty 18 | public String name; 19 | 20 | /** 21 | * Fields of the class 22 | */ 23 | @JsonProperty("fields") 24 | public List fields; 25 | 26 | /** 27 | * Whether class is immutable or not 28 | */ 29 | @JsonProperty 30 | public boolean immutable; 31 | 32 | /** 33 | * Should include json annotations or not 34 | */ 35 | @JsonProperty("json_annotations") 36 | public boolean jsonAnnotations; 37 | 38 | /** 39 | * Class Type documentation 40 | */ 41 | @JsonProperty 42 | public String description; 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/cyngn/exovert/generate/server/config/LogbackTemplate.java: -------------------------------------------------------------------------------- 1 | package com.cyngn.exovert.generate.server.config; 2 | 3 | /** 4 | * logback.xml template file, long term need to use a better templating solution. 5 | * 6 | * @author truelove@cyngn.com (Jeremy Truelove) 9/10/15 7 | */ 8 | public class LogbackTemplate { 9 | 10 | public final static String TEMPLATE = "\n" + 11 | " \n" + 12 | " \n" + 14 | " \n" + 15 | " %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n\n" + 16 | " \n" + 17 | " \n" + 18 | "\n" + 19 | " \n" + 20 | " \n" + 21 | " \n" + 22 | ""; 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/cyngn/exovert/generate/server/rest/types/Api.java: -------------------------------------------------------------------------------- 1 | package com.cyngn.exovert.generate.server.rest.types; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | 5 | /** 6 | * Type specification for API 7 | * 8 | * @author asarda@cyngn.com (Ajay Sarda) 9/8/15. 9 | */ 10 | public class Api { 11 | 12 | /** 13 | * API Name 14 | */ 15 | @JsonProperty 16 | public String name; 17 | 18 | /** 19 | * API description 20 | */ 21 | @JsonProperty 22 | public String description; 23 | 24 | /** 25 | * Routing path for the api 26 | */ 27 | @JsonProperty 28 | public String path; 29 | 30 | /** 31 | * {@link io.vertx.core.http.HttpMethod} associated with the API 32 | * 33 | * Currently supported http_methods are GET, POST, DELETE 34 | */ 35 | @JsonProperty("http_method") 36 | public String httpMethod; 37 | 38 | /** 39 | * Request object 40 | */ 41 | @JsonProperty 42 | public Request request; 43 | 44 | /** 45 | * Response object 46 | */ 47 | @JsonProperty 48 | public Response response; 49 | } -------------------------------------------------------------------------------- /src/main/java/com/cyngn/exovert/util/GeneratorHelper.java: -------------------------------------------------------------------------------- 1 | package com.cyngn.exovert.util; 2 | 3 | import com.squareup.javapoet.ClassName; 4 | import com.squareup.javapoet.FieldSpec; 5 | import org.joda.time.DateTime; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | import javax.lang.model.element.Modifier; 10 | 11 | /** 12 | * Helper class for all generators 13 | * 14 | * @author truelove@cyngn.com (Jeremy Truelove) 9/1/15 15 | */ 16 | public class GeneratorHelper { 17 | public static FieldSpec getLogger(String nameSpace, String className) { 18 | return FieldSpec.builder(Logger.class, "logger", Modifier.FINAL, Modifier.STATIC, Modifier.PRIVATE) 19 | .initializer("$T.getLogger($T.class)", LoggerFactory.class, ClassName.get(nameSpace, className)).build(); 20 | } 21 | 22 | public static String getJavaDocHeader(String text, DateTime updatedTime) { 23 | return "GENERATED CODE DO NOT MODIFY - last updated: " + updatedTime + "\n" + 24 | "generated by exovert - https://github.com/cyngn/exovert\n\n" + (text != null ? text + "\n" : ""); 25 | } 26 | 27 | public static String getJavaDocHeader(String text) { 28 | return getJavaDocHeader(text, DateTime.now()); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/cyngn/exovert/generate/server/rest/types/Field.java: -------------------------------------------------------------------------------- 1 | package com.cyngn.exovert.generate.server.rest.types; 2 | 3 | import com.cyngn.exovert.generate.server.rest.TypeMap; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | 6 | /** 7 | * Type specification for field. Field is contained within 8 | * {@link Request}, {@link Response} and {@link Response} 9 | * 10 | * @author asarda@cyngn.com (Ajay Sarda) 9/8/15. 11 | */ 12 | public class Field { 13 | /** 14 | * Field name 15 | */ 16 | @JsonProperty 17 | public String name; 18 | 19 | /** 20 | * Type of the field. 21 | * 22 | * The type gets mapped to Java type by {@link TypeMap} 23 | */ 24 | @JsonProperty 25 | public String type; 26 | 27 | /** 28 | * Whether required or not. By default, fields are optional. 29 | */ 30 | @JsonProperty 31 | public boolean required; 32 | 33 | /** 34 | * Default value 35 | */ 36 | @JsonProperty("default_value") 37 | public String defaultValue; 38 | 39 | /** 40 | * Validation associated with the field. 41 | */ 42 | @JsonProperty 43 | public Validation validation; 44 | 45 | /** 46 | * Enum type description 47 | */ 48 | @JsonProperty 49 | public String description; 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/cyngn/exovert/generate/server/rest/InterfaceSpec.java: -------------------------------------------------------------------------------- 1 | package com.cyngn.exovert.generate.server.rest; 2 | 3 | import com.cyngn.exovert.generate.server.rest.types.Api; 4 | import com.cyngn.exovert.generate.server.rest.types.DataTypeGroup; 5 | import com.fasterxml.jackson.annotation.JsonProperty; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * Client/Server Interface for Specification types for APIs, 11 | * 12 | * request, response, request data types, response data types 13 | * 14 | * @author asarda@cyngn.com (Ajay Sarda) 9/8/15. 15 | */ 16 | public class InterfaceSpec { 17 | /** 18 | * Server name 19 | */ 20 | @JsonProperty 21 | public String name; 22 | 23 | /** 24 | * Media type 25 | */ 26 | @JsonProperty("media_type") 27 | public String mediaType; 28 | 29 | /** 30 | * package namespace 31 | */ 32 | @JsonProperty 33 | public String namespace; 34 | 35 | /** 36 | * List of {@link Api} 37 | */ 38 | @JsonProperty 39 | public List apis; 40 | 41 | /** 42 | * List of {@link DataTypeGroup} 43 | * 44 | * {@link DataTypeGroup} represents the set of datatypes in package namespace. 45 | */ 46 | @JsonProperty("data_type_groups") 47 | public List dataTypeGroups; 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/com/cyngn/exovert/util/Udt.java: -------------------------------------------------------------------------------- 1 | package com.cyngn.exovert.util; 2 | 3 | import com.datastax.driver.core.DataType; 4 | import com.datastax.driver.core.KeyspaceMetadata; 5 | import com.datastax.driver.core.UserType; 6 | import com.google.common.base.CaseFormat; 7 | import org.apache.commons.lang.StringUtils; 8 | 9 | import java.util.concurrent.atomic.AtomicBoolean; 10 | 11 | /** 12 | * Util functions related to UDTs. 13 | * 14 | * @author truelove@cyngn.com (Jeremy Truelove) 8/28/15 15 | */ 16 | public class Udt { 17 | public static final Udt instance = new Udt(); 18 | private KeyspaceMetadata keyspaceMetadata; 19 | private AtomicBoolean initialized; 20 | 21 | private Udt(){ 22 | initialized = new AtomicBoolean(false); 23 | } 24 | 25 | public synchronized void init(KeyspaceMetadata keyspaceMetadata) { 26 | if(initialized.compareAndSet(false, true)) { 27 | this.keyspaceMetadata = keyspaceMetadata; 28 | } 29 | } 30 | 31 | public boolean isUdt(String name) { 32 | boolean is = false; 33 | for(UserType type : keyspaceMetadata.getUserTypes()) { 34 | if (name.toLowerCase().equals(type.getTypeName().toLowerCase())) { 35 | is = true; 36 | break; 37 | } 38 | } 39 | return is; 40 | } 41 | 42 | public boolean isUdt(DataType type) { 43 | return StringUtils.equalsIgnoreCase("udt", type.getName().name()); 44 | } 45 | 46 | public String getUdtClassName(String udt) { 47 | return CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, udt); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /gradle/cyngn.gradle: -------------------------------------------------------------------------------- 1 | // Handles updating the 'version' field in the gradle.properties file 2 | task updateVersion << { 3 | if (!project.hasProperty("newVersion")) { 4 | throw new InvalidUserDataException("You must specify a 'newVersion' parameter with updateVersion task, " + 5 | "ie -PnewVersion=1.0.9-foo") 6 | } 7 | 8 | println "updating version to " + newVersion 9 | project.version = newVersion 10 | 11 | ant.propertyfile( 12 | file: "gradle.properties") { 13 | entry( key: "version", value: project.version) 14 | } 15 | } 16 | 17 | // Handles updating the 'version' field in the gradle.properties with a snapshot tag 18 | task makeSnapshot << { 19 | if (project.version.contains("-SNAPSHOT")) { 20 | println "project.version : " + project.version + " is already in snapshot form" 21 | return 22 | } 23 | println "updating version to " + project.version + "-SNAPSHOT" 24 | 25 | project.version = project.version + "-SNAPSHOT" 26 | ant.propertyfile( 27 | file: "gradle.properties") { 28 | entry( key: "version", value: project.version) 29 | } 30 | } 31 | 32 | // commit an updated 33 | task commitNewVersion << { 34 | if (!project.hasProperty("commitBranch")) { 35 | throw new InvalidUserDataException("You must specify a 'commitBranch' parameter with commitNewVersion task, " + 36 | "ie -PcommitBranch=master") 37 | } 38 | 39 | exec { 40 | commandLine 'git', 'add', 'gradle.properties' 41 | } 42 | exec { 43 | commandLine 'git', 'commit', '-m', 'Teamcity updating to version: ' + project.version 44 | } 45 | exec { 46 | commandLine 'git', 'push', 'gerrit', commitBranch 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/test/java/com/cyngn/exovert/generate/server/model/TypeMapImplTest.java: -------------------------------------------------------------------------------- 1 | package com.cyngn.exovert.generate.server.model; 2 | 3 | import com.cyngn.exovert.generate.server.rest.TypeMap; 4 | import com.google.common.base.CaseFormat; 5 | import com.squareup.javapoet.ClassName; 6 | import com.squareup.javapoet.CodeBlock; 7 | import org.junit.Rule; 8 | import org.junit.Test; 9 | import org.junit.rules.ExpectedException; 10 | 11 | import static org.junit.Assert.assertEquals; 12 | 13 | /** 14 | * @author asarda@cyngn.com (Ajay Sarda) 9/18/15. 15 | */ 16 | public class TypeMapImplTest { 17 | 18 | @Rule 19 | public final ExpectedException exception = ExpectedException.none(); 20 | 21 | @Test 22 | public void testRegisterType(){ 23 | TypeMap typeMap = TypeMap.create(); 24 | typeMap.registerType("T", ClassName.get("", "T")); 25 | 26 | assertEquals("T", typeMap.getTypeName("T").toString()); 27 | } 28 | 29 | @Test 30 | public void testAlreadyRegister(){ 31 | TypeMap typeMap = TypeMap.create(); 32 | typeMap.registerType("T", ClassName.get("", "T")); 33 | 34 | exception.expect(IllegalArgumentException.class); 35 | typeMap.registerType("T", ClassName.get("", "T")); 36 | } 37 | 38 | @Test 39 | public void testTypeConverter() { 40 | TypeMap typeMap = TypeMap.create(); 41 | 42 | CodeBlock cb = typeMap.getTypeConverter("String", 43 | CodeBlock.builder().add("request.getParam($S)", CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, "name")).build()); 44 | 45 | assertEquals("request.getParam(\"name\")", cb.toString()); 46 | 47 | cb = typeMap.getTypeConverter("Long", 48 | CodeBlock.builder().add("request.getParam($S)", CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, "value")).build()); 49 | 50 | assertEquals("java.lang.Long.parseLong(request.getParam(\"value\"))", cb.toString()); 51 | } 52 | 53 | } -------------------------------------------------------------------------------- /src/test/java/com/cyngn/exovert/generate/server/model/JacksonTest.java: -------------------------------------------------------------------------------- 1 | package com.cyngn.exovert.generate.server.model; 2 | 3 | import com.cyngn.vertx.web.JsonUtil; 4 | import com.fasterxml.jackson.annotation.JsonIgnore; 5 | import com.fasterxml.jackson.annotation.JsonProperty; 6 | import com.fasterxml.jackson.databind.DeserializationFeature; 7 | import org.junit.Before; 8 | import org.junit.Test; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * @author andy@cyngn.com (Andy Mast) 1/4/16 14 | */ 15 | public class JacksonTest { 16 | 17 | @Test 18 | public void testFooParsed() { 19 | String json = "{\n" + 20 | " \"fieldRequired\": \"required\",\n" + 21 | " \"fieldOptional\": \"present\"\n" + 22 | "}"; 23 | 24 | Foo foo = JsonUtil.parseJsonToObject(json, Foo.class); 25 | assertNotNull(foo); 26 | assertNotNull(foo.fieldRequired); 27 | assertNotNull(foo.fieldOptional); 28 | } 29 | 30 | @Test 31 | public void testOptionalParsedWithNull() { 32 | String json = "{\n" + 33 | " \"fieldRequired\": \"required\",\n" + 34 | " \"fieldOptional\": null\n" + 35 | "}"; 36 | 37 | Foo foo = JsonUtil.parseJsonToObject(json, Foo.class); 38 | assertNotNull(foo); 39 | assertNotNull(foo.fieldRequired); 40 | assertNull(foo.fieldOptional); 41 | } 42 | 43 | @Test 44 | public void testFooParsedWithMissingField() { 45 | String json = "{\n" + 46 | " \"fieldRequired\": \"required\"\n" + 47 | "}"; 48 | 49 | Foo foo = JsonUtil.parseJsonToObject(json, Foo.class); 50 | assertNotNull(foo); 51 | assertNotNull(foo.fieldRequired); 52 | assertNull(foo.fieldOptional); 53 | } 54 | 55 | public static class Foo { 56 | @JsonProperty("fieldRequired") 57 | public String fieldRequired; 58 | 59 | @JsonProperty("fieldOptional") 60 | public String fieldOptional; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/test/java/com/cyngn/exovert/generate/server/model/utils/api.json: -------------------------------------------------------------------------------- 1 | { 2 | "namespace" : "com.cyngn.beer", 3 | "apis" : [ 4 | { 5 | "name": "get", 6 | "description": "API to retrieve beer", 7 | "path": "/get", 8 | "http_method": "GET", 9 | "request": { 10 | "fields": [ 11 | { 12 | "name": "name", 13 | "type": "String", 14 | "required": true, 15 | "validation": { 16 | "length" : { 17 | "min" : 1, 18 | "max" : 1000 19 | } 20 | } 21 | } 22 | ] 23 | }, 24 | "response": { 25 | "fields": [ 26 | { 27 | "name": "name", 28 | "type": "String", 29 | "required": true 30 | }, 31 | { 32 | "name": "quantity_in_ounces", 33 | "type": "Integer", 34 | "required": true 35 | }, 36 | { 37 | "name": "price", 38 | "type": "Double", 39 | "required": true 40 | }, 41 | { 42 | "name": "notes", 43 | "type": "String", 44 | "required": false 45 | } 46 | ] 47 | } 48 | } 49 | ], 50 | "data_type_groups":[ 51 | { 52 | "namespace" : "com.cyngn.beer.external", 53 | "enum_types": [ 54 | { 55 | "name" : "beer_type", 56 | "description": "Enumerated type for different types of beer", 57 | "values":[ 58 | { 59 | "name" : "ALCOHOLIC", 60 | "value" : "alcoholic" 61 | }, 62 | { 63 | "name" : "NON_ALCOHOLIC", 64 | "value" : "non_alcoholic" 65 | } 66 | ] 67 | } 68 | ], 69 | "class_types": [ 70 | { 71 | "name" : "type_has_string", 72 | "description": "Example to show Type with String", 73 | "immutable" : false, 74 | "fields": [ 75 | { 76 | "name": "name", 77 | "type": "String" 78 | } 79 | ] 80 | } 81 | ] 82 | } 83 | ] 84 | } 85 | -------------------------------------------------------------------------------- /src/main/java/com/cyngn/exovert/generate/server/rest/utils/Constants.java: -------------------------------------------------------------------------------- 1 | package com.cyngn.exovert.generate.server.rest.utils; 2 | 3 | import com.cyngn.exovert.generate.server.rest.RestServerGenerator; 4 | 5 | /** 6 | * Contains constants for {@link RestServerGenerator} 7 | * 8 | * @author asarda@cyngn.com (Ajay Sarda) 9/10/15. 9 | */ 10 | public class Constants { 11 | // directory constants 12 | public static final String GENERATED_SRC_DIRECTORY = "generated-src"; 13 | public static final String BUILD_DIRECTORY = "build"; 14 | public static final String INDENTATION_SPACES = " "; 15 | 16 | //package name constants 17 | public static final String TYPES_PACKAGE_SUFFIX = "types"; 18 | public static final String API_PACKAGE_SUFFIX = "api"; 19 | 20 | // class name constants 21 | public static final String REQUEST_CLASS_SUFFIX = "Request"; 22 | public static final String RESPONSE_CLASS_SUFFIX = "Response"; 23 | public static final String API_NAME_PREFIX = "Abstract"; 24 | public static final String API_NAME_SUFFIX = "Api"; 25 | public static final String BUILDER_CLASS_NAME = "Builder"; 26 | public static final String BUILDER_CLASS_SUFFIX = "." + BUILDER_CLASS_NAME; 27 | 28 | // method name constants 29 | public static final String VALIDATE = "validate"; 30 | public static final String SET_METHOD_PREFIX = "set"; 31 | public static final String GET_METHOD_PREFIX = "get"; 32 | public static final String HANDLE_GET = "handleGet"; 33 | public static final String HANDLE_POST = "handlePost"; 34 | public static final String HANDLE_DELETE = "handleDelete"; 35 | 36 | //field name constants 37 | public static final String SUPPORTED_API_FIELD = "supportedApi"; 38 | public static final String API_PATH = "_API_PATH"; 39 | 40 | // http constants 41 | public static final String HTTP_METHOD_GET = "GET"; 42 | public static final String HTTP_METHOD_POST = "POST"; 43 | public static final String HTTP_METHOD_DELETE = "DELETE"; 44 | 45 | // client constants 46 | public static final String CLIENT_SUFFIX = "Client"; 47 | public static final String SERVICE_CLIENT_FIELD_NAME = "serviceClient"; 48 | public static final String CALL_PREFIX = "call"; 49 | public static final String API_NAME_CONSTANT_SUFFIX = "_API"; 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/cyngn/exovert/generate/server/rest/TypeMap.java: -------------------------------------------------------------------------------- 1 | package com.cyngn.exovert.generate.server.rest; 2 | 3 | import com.squareup.javapoet.CodeBlock; 4 | import com.squareup.javapoet.TypeName; 5 | 6 | /** 7 | * Defines the mapping of types defined in api configuration json to 8 | * {@link TypeName} 9 | * 10 | * @author asarda@cyngn.com (Ajay Sarda) on 9/8/15. 11 | */ 12 | public interface TypeMap { 13 | 14 | /** 15 | * Gets the {@link TypeMap} object for the {@code type} 16 | * 17 | * {@code type} could be primitive types (Integer, Boolean), reference types (String) 18 | * or user defined types (MyType) 19 | * 20 | * TODO: support collections 21 | * 22 | * @param type - type string 23 | * @return {@link TypeName} 24 | */ 25 | TypeName getTypeName(String type); 26 | 27 | /** 28 | * Registers the type and its type name with TypeMap. 29 | * 30 | * This can be used by callers to register any custom types and classes and use it 31 | * 32 | * @param type - type string 33 | * @param typeName - type name 34 | */ 35 | void registerType(String type, TypeName typeName); 36 | 37 | /** 38 | * Registers the type and its type name with TypeMap. 39 | *

40 | * This can be used by callers to register any custom types and classes and use it 41 | * 42 | * @param type - type string 43 | * @param typeName - type name 44 | * @param isEnum - whether it is enum or not 45 | */ 46 | void registerType(String type, TypeName typeName, boolean isEnum); 47 | 48 | /** 49 | * Checks if the given type is enumerated or not 50 | * 51 | * @param type - type string 52 | * @return true if type is enumerated type, otherwise false 53 | */ 54 | boolean isEnumeratedType(String type); 55 | 56 | /** 57 | * Get the type conversion code block to convert {@link String} 58 | * to type 59 | * @param type - type String 60 | * @param cb - code block 61 | * @return - returns CodeBlock decorated with conversion code. 62 | */ 63 | CodeBlock getTypeConverter(String type, CodeBlock cb); 64 | 65 | /** 66 | * Returns the default implementation of {@link TypeMap} 67 | * 68 | * @return {@link TypeName} 69 | */ 70 | static TypeMap create() { 71 | return new TypeMapImpl(); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /gradle/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /samples/api-with-defaults.json: -------------------------------------------------------------------------------- 1 | { 2 | "namespace": "com.cyngn.sample", 3 | "apis": [ 4 | { 5 | "name": "sample", 6 | "documentation": "Sample API", 7 | "path": "/sample", 8 | "http_method": "POST", 9 | "request": { 10 | "fields": [ 11 | { 12 | "name": "double_value", 13 | "type": "double", 14 | "required": false, 15 | "default_value": "1.0" 16 | }, 17 | { 18 | "name": "short_value", 19 | "type": "short", 20 | "required": false, 21 | "default_value": "5" 22 | }, 23 | { 24 | "name": "string_value", 25 | "type": "string", 26 | "required": false, 27 | "default_value": "These are default notes" 28 | }, 29 | { 30 | "name": "integer_value", 31 | "type": "integer", 32 | "required": false, 33 | "default_value": "5000" 34 | }, 35 | { 36 | "name": "long_value", 37 | "type": "long", 38 | "required": false, 39 | "default_value": "5000" 40 | }, 41 | { 42 | "name": "float_value", 43 | "type": "float", 44 | "required": false, 45 | "default_value": "5.75" 46 | }, 47 | { 48 | "name": "character_value", 49 | "type": "character", 50 | "required": false, 51 | "default_value": "A" 52 | }, 53 | { 54 | "name": "date_value", 55 | "type": "date", 56 | "required": false, 57 | "default_value": "09/30/2015" 58 | }, 59 | { 60 | "name": "enum_type", 61 | "type": "enum_type", 62 | "required": false, 63 | "default_value": "alcoholic" 64 | } 65 | ] 66 | } 67 | } 68 | ], 69 | "data_type_groups":[ 70 | { 71 | "namespace" : "com.cyngn.sample.external", 72 | "enum_types": [ 73 | { 74 | "name": "enum_type", 75 | "documentation": "Enumerated type for different types of beer", 76 | "values": [ 77 | { 78 | "name": "VALUE_ONE", 79 | "value": "value_one" 80 | }, 81 | { 82 | "name": "VALUE_TWO", 83 | "value": "value_two" 84 | } 85 | ] 86 | } 87 | ], 88 | "class_types": [ 89 | { 90 | "name": "type_has_string", 91 | "documentation": "Example to show Type with String", 92 | "immutable": false, 93 | "fields": [ 94 | { 95 | "name": "name", 96 | "type": "string" 97 | } 98 | ] 99 | } 100 | ] 101 | } 102 | ] 103 | } -------------------------------------------------------------------------------- /src/main/java/com/cyngn/exovert/util/Disk.java: -------------------------------------------------------------------------------- 1 | package com.cyngn.exovert.util; 2 | 3 | import com.squareup.javapoet.JavaFile; 4 | import io.vertx.core.Vertx; 5 | import io.vertx.core.buffer.Buffer; 6 | import org.apache.commons.lang.StringUtils; 7 | 8 | import java.io.IOException; 9 | import java.nio.file.Path; 10 | import java.nio.file.Paths; 11 | 12 | /** 13 | * Wrapper for interacting with disk and outputting data. 14 | * 15 | * @author truelove@cyngn.com (Jeremy Truelove) 8/28/15 16 | */ 17 | public class Disk { 18 | /** 19 | * Outputs a generated JavaFile 20 | * @param file the file to output 21 | * @throws IOException if write to file fails 22 | */ 23 | public static void outputFile(JavaFile file) throws IOException { 24 | String javaFile = file.typeSpec.name + ".java"; 25 | if (!isPreview()) { 26 | String path = MetaData.instance.getOutDir() + "/src/main/java/" + 27 | StringUtils.replace(file.packageName, ".", "/"); 28 | String fileName = path + "/" + javaFile; 29 | 30 | writeFile(fileName, file.toString()); 31 | } else { 32 | System.out.println("\nFile: " + javaFile + "\n"); 33 | file.writeTo(System.out); 34 | } 35 | } 36 | 37 | /** 38 | * Outputs a generated JavaFile 39 | * @param fileData the file to output 40 | * @param fileName the name of the file 41 | * @throws IOException if write to file fails 42 | */ 43 | public static void outputFile(String fileData, String fileName) throws IOException { 44 | if (!isPreview()) { 45 | 46 | String path = MetaData.instance.getOutDir(); 47 | String fullPath = path + "/" + fileName; 48 | 49 | writeFile(fullPath, fileData); 50 | } else { 51 | System.out.println("\nFile: " + fileName + "\n"); 52 | System.out.println(fileData); 53 | } 54 | } 55 | 56 | /** 57 | * Write the file out. 58 | * 59 | * @param path the path to write to 60 | * @param fileContents the file data 61 | */ 62 | private static void writeFile(String path, String fileContents) { 63 | Vertx vertx = VertxRef.instance.get(); 64 | Path filePath = Paths.get(path); 65 | Path dirPath = filePath.getParent(); 66 | if(!vertx.fileSystem().existsBlocking(dirPath.toString())) { 67 | vertx.fileSystem().mkdirsBlocking(dirPath.toString()); 68 | } 69 | 70 | System.out.println("Outputting file to path: " + path); 71 | 72 | VertxRef.instance.get().fileSystem().writeFile(path, Buffer.buffer(fileContents), result -> { 73 | if(result.failed()) { 74 | System.out.println("Failed to create file: " + path + ", ex: " + result.cause()); 75 | } 76 | }); 77 | } 78 | 79 | /** 80 | * @return Is the tool running in preview mode? 81 | */ 82 | public static boolean isPreview() { 83 | return StringUtils.isEmpty(MetaData.instance.getOutDir()); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/com/cyngn/exovert/generate/entity/UDTGenerator.java: -------------------------------------------------------------------------------- 1 | package com.cyngn.exovert.generate.entity; 2 | 3 | import com.cyngn.exovert.util.Disk; 4 | import com.cyngn.exovert.util.GeneratorHelper; 5 | import com.cyngn.exovert.util.MetaData; 6 | import com.datastax.driver.core.DataType; 7 | import com.datastax.driver.core.UserType; 8 | import com.datastax.driver.mapping.annotations.UDT; 9 | import com.google.common.base.CaseFormat; 10 | import com.squareup.javapoet.AnnotationSpec; 11 | import com.squareup.javapoet.JavaFile; 12 | import com.squareup.javapoet.TypeSpec; 13 | 14 | import javax.lang.model.element.Modifier; 15 | import java.io.IOException; 16 | import java.util.ArrayList; 17 | import java.util.Collection; 18 | import java.util.List; 19 | 20 | /** 21 | * Creates Udt classes based on the cassandra schema. 22 | * 23 | * @author truelove@cyngn.com (Jeremy Truelove) 8/27/15 24 | */ 25 | public class UDTGenerator { 26 | 27 | /** 28 | * Kicks off table generation. 29 | * 30 | * @param userTypes the cassandra Udt meta data 31 | * @throws IOException if write to file fails 32 | */ 33 | public static void generate(Collection userTypes) throws IOException { 34 | String namespaceToUse = MetaData.instance.getUdtNamespace(); 35 | 36 | for (UserType userType : userTypes) { 37 | String rawName = userType.getTypeName(); 38 | String name = CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, rawName); 39 | 40 | TypeSpec.Builder udtClassBuilder = TypeSpec.classBuilder(name) 41 | .addModifiers(Modifier.PUBLIC) 42 | .addAnnotation(getUDTAnnotation(userType.getKeyspace(), rawName)); 43 | 44 | addFields(udtClassBuilder, userType, name); 45 | 46 | udtClassBuilder.addJavadoc(GeneratorHelper.getJavaDocHeader("UDT class for Cassandra - " + rawName, MetaData.instance.getUpdateTime())); 47 | 48 | JavaFile javaFile = JavaFile.builder(namespaceToUse, udtClassBuilder.build()).build(); 49 | 50 | Disk.outputFile(javaFile); 51 | } 52 | } 53 | 54 | /** 55 | * Add fields to the class spec. 56 | */ 57 | private static void addFields(TypeSpec.Builder builder, UserType userType, String className) { 58 | 59 | List fields = new ArrayList<>(); 60 | for (String field : userType.getFieldNames()) { 61 | DataType type = userType.getFieldType(field); 62 | builder.addField(EntityGeneratorHelper.getFieldSpec(field, type, true)); 63 | builder.addMethod(EntityGeneratorHelper.getSetter(field, type)); 64 | builder.addMethod(EntityGeneratorHelper.getGetter(field, type)); 65 | 66 | fields.add(CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, field)); 67 | } 68 | 69 | builder.addMethod(EntityGeneratorHelper.getToString(fields, className)); 70 | } 71 | 72 | /** 73 | * Add UDT annotation to class. 74 | */ 75 | private static AnnotationSpec getUDTAnnotation(String keyspace, String udtName) { 76 | return AnnotationSpec.builder(UDT.class).addMember("keyspace", "$S", keyspace).addMember("name", "$S", udtName).build(); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/com/cyngn/exovert/generate/server/rest/RestServerGenerator.java: -------------------------------------------------------------------------------- 1 | package com.cyngn.exovert.generate.server.rest; 2 | 3 | import com.cyngn.exovert.generate.server.rest.types.Api; 4 | import com.cyngn.exovert.generate.server.rest.utils.RestGeneratorHelper; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | 8 | /** 9 | * Generates Server interface 10 | * 11 | * Interface includes 12 | * API classes, 13 | * Request, Response classes (with setters, getters and validations) 14 | * 15 | * @author asarda@cyngn.com (Ajay Sarda) 9/8/15. 16 | */ 17 | public class RestServerGenerator { 18 | private static final Logger logger = LoggerFactory.getLogger(RestServerGenerator.class); 19 | 20 | private GenerationContext context; 21 | private ClassGenerator classGenerator; 22 | private MethodGenerator methodGenerator; 23 | private CommonRestGenerator commonRestGenerator; 24 | 25 | private RestServerGenerator(GenerationContext context) { 26 | this.context = context; 27 | this.context.typeMap = TypeMap.create(); 28 | this.methodGenerator = new MethodGenerator(this.context); 29 | this.classGenerator = new ClassGenerator(this.context, this.methodGenerator); 30 | this.commonRestGenerator = new CommonRestGenerator(this.context); 31 | } 32 | 33 | /** 34 | * Generates Request, Response and API classes. 35 | * 36 | * @throws Exception on generation failure 37 | */ 38 | public void generate() throws Exception { 39 | logger.info("Generating the REST server ..."); 40 | 41 | InterfaceSpec spec = RestGeneratorHelper.loadSpecFromFile(context.specFilePath); 42 | 43 | // generate common types between server and client 44 | commonRestGenerator.generate(spec); 45 | 46 | for (Api api : spec.apis) { 47 | // add api class for server 48 | commonRestGenerator.generateClassFromTypespec( 49 | RestGeneratorHelper.getApiNamespace(spec.namespace), 50 | classGenerator.getApiTypeSpec(api, spec.namespace)); 51 | } 52 | } 53 | 54 | /** 55 | * Get the instance to {@link RestServerGenerator.Builder} 56 | * 57 | * @return {@link RestServerGenerator.Builder} 58 | */ 59 | public static Builder newBuilder() { 60 | return new Builder(); 61 | } 62 | 63 | /** 64 | * Fluent Builder class to build {@link RestServerGenerator} 65 | */ 66 | public static class Builder { 67 | private GenerationContext context; 68 | 69 | public Builder() { 70 | context = new GenerationContext(); 71 | } 72 | 73 | public Builder withIsPreview(boolean isPreview) { 74 | context.preview = isPreview; 75 | return this; 76 | } 77 | 78 | public Builder withOutputDirectory(String outputDirectory) { 79 | context.outputDirectory = outputDirectory; 80 | return this; 81 | } 82 | 83 | public Builder withSpecFilePath(String specFilePath) { 84 | context.specFilePath = specFilePath; 85 | return this; 86 | } 87 | 88 | public RestServerGenerator build() { 89 | return new RestServerGenerator(context); 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /samples/types.json: -------------------------------------------------------------------------------- 1 | { 2 | "data_type_groups":[ 3 | { 4 | "namespace" : "com.cyngn.types.examples.one", 5 | "data_types" : { 6 | "enum_types": [ 7 | { 8 | "name": "beer_type", 9 | "description": "Enumerated type for different types of beer", 10 | "values": [ 11 | { 12 | "name": "ALCOHOLIC", 13 | "value": "alcoholic" 14 | }, 15 | { 16 | "name": "NON_ALCOHOLIC", 17 | "value": "non_alcoholic" 18 | } 19 | ] 20 | } 21 | ], 22 | "class_types": [ 23 | { 24 | "name": "type_has_string", 25 | "description": "Example to show Type with String", 26 | "immutable": false, 27 | "fields": [ 28 | { 29 | "name": "name", 30 | "type": "string", 31 | "default_value": "This is default value" 32 | } 33 | ] 34 | } 35 | ] 36 | } 37 | }, 38 | { 39 | "namespace" : "com.cyngn.types.examples.two", 40 | "data_types" : { 41 | "enum_types": [ 42 | { 43 | "name": "beer_type", 44 | "description": "Enumerated type for different types of beer", 45 | "values": [ 46 | { 47 | "name": "ALCOHOLIC", 48 | "value": "alcoholic" 49 | }, 50 | { 51 | "name": "NON_ALCOHOLIC", 52 | "value": "non_alcoholic" 53 | } 54 | ] 55 | } 56 | ], 57 | "class_types": [ 58 | { 59 | "name" : "type_has_immutable_string", 60 | "description": "Example to show Immutable type with string", 61 | "immutable" : true, 62 | "fields": [ 63 | { 64 | "name": "names", 65 | "type": "list" 66 | } 67 | ] 68 | }, 69 | { 70 | "name" : "type_has_string_with_json_annotations", 71 | "description": "Example to show Type annotated with Json annotations", 72 | "immutable" : false, 73 | "json_annotations" : true, 74 | "fields": [ 75 | { 76 | "name": "names", 77 | "type": "list" 78 | } 79 | ] 80 | }, 81 | { 82 | "name" : "type_has_map", 83 | "description": "Example to show Type with map field", 84 | "immutable" : false, 85 | "fields": [ 86 | { 87 | "name": "names", 88 | "type": "map" 89 | } 90 | ] 91 | }, 92 | { 93 | "name" : "type_has_list", 94 | "description" : "Example to show Type with list field", 95 | "immutable" : false, 96 | "fields": [ 97 | { 98 | "name": "names", 99 | "type": "list" 100 | } 101 | ] 102 | } 103 | ] 104 | } 105 | } 106 | ] 107 | } -------------------------------------------------------------------------------- /src/main/java/com/cyngn/exovert/util/MetaData.java: -------------------------------------------------------------------------------- 1 | package com.cyngn.exovert.util; 2 | 3 | import com.datastax.driver.core.CodecRegistry; 4 | import com.datastax.driver.core.UserType; 5 | import com.englishtown.vertx.cassandra.CassandraSession; 6 | import com.fasterxml.jackson.annotation.JsonProperty; 7 | import com.squareup.javapoet.AnnotationSpec; 8 | import com.squareup.javapoet.ClassName; 9 | import org.apache.commons.lang.StringUtils; 10 | import org.joda.time.DateTime; 11 | import org.joda.time.DateTimeZone; 12 | 13 | import java.util.concurrent.atomic.AtomicBoolean; 14 | 15 | /** 16 | * Util class for getting access to common meta data at run time. 17 | * 18 | * @author truelove@cyngn.com (Jeremy Truelove) 8/28/15 19 | */ 20 | public class MetaData { 21 | public static final MetaData instance = new MetaData(); 22 | private AtomicBoolean initialized; 23 | private String namespace; 24 | private String keyspace; 25 | private String outDir; 26 | private DateTime updateTime; 27 | private String prefix; 28 | 29 | private static final String DEFAULT_API_PREFIX = "/api/v1/"; 30 | private CodecRegistry codecRegistry; 31 | 32 | private MetaData(){ 33 | initialized = new AtomicBoolean(false); 34 | } 35 | 36 | public synchronized void init(String namespace, String keyspace, String outDir, String restPrefix, CassandraSession session) { 37 | if(initialized.compareAndSet(false, true)) { 38 | this.namespace = namespace; 39 | this.keyspace = keyspace; 40 | this.outDir = outDir; 41 | updateTime = DateTime.now(DateTimeZone.UTC); 42 | 43 | if (StringUtils.isNotEmpty(restPrefix)) { prefix = restPrefix + (restPrefix.endsWith("/") ? "" : "/"); 44 | } else { prefix = DEFAULT_API_PREFIX; } 45 | 46 | codecRegistry = session.getCluster().getConfiguration().getCodecRegistry(); 47 | } 48 | } 49 | 50 | public String getNamespace() { return namespace; } 51 | public String getKeyspace() { return keyspace; } 52 | public String getOutDir() { return outDir; } 53 | public String getUdtNamespace() { return StringUtils.join(new String[]{namespace, "storage", "cassandra", "udt"}, '.'); } 54 | public String getTableNamespace() { return StringUtils.join(new String[]{namespace, "storage", "cassandra", "table"}, '.'); } 55 | public String getDalNamespace() { return StringUtils.join(new String[]{namespace, "storage", "cassandra", "dal"}, '.'); } 56 | public String getRestNamespace() { return StringUtils.join(new String[]{namespace, "rest"}, '.'); } 57 | public String getRestPrefix() { return prefix; } 58 | public DateTime getUpdateTime() { return updateTime; } 59 | public CodecRegistry getCodecRegistry() { return codecRegistry; } 60 | 61 | public static boolean isSnakeCase(String str) { 62 | return str.contains("_"); 63 | } 64 | 65 | public static AnnotationSpec getJsonAnnotation(String field) { 66 | AnnotationSpec.Builder builder = AnnotationSpec.builder(JsonProperty.class); 67 | if(isSnakeCase(field)) { 68 | builder.addMember("value", "$S", field); 69 | } 70 | 71 | return builder.build(); 72 | } 73 | 74 | public static ClassName getClassNameForUdt(UserType type) { 75 | return ClassName.get(MetaData.instance.getUdtNamespace(), Udt.instance.getUdtClassName(type.getTypeName())); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/com/cyngn/exovert/generate/project/GradleTemplate.java: -------------------------------------------------------------------------------- 1 | package com.cyngn.exovert.generate.project; 2 | 3 | /** 4 | * build.gradle template file, long term need to use a better templating solution. 5 | * 6 | * @author truelove@cyngn.com (Jeremy Truelove) 9/9/15 7 | */ 8 | public class GradleTemplate { 9 | public static String TEMPLATE = "buildscript {\n" + 10 | " repositories { jcenter() }\n" + 11 | " dependencies {\n" + 12 | " classpath 'com.github.jengelman.gradle.plugins:shadow:1.1.1'\n" + 13 | " }\n" + 14 | "}\n" + 15 | "\n" + 16 | "apply plugin: 'java'\n" + 17 | "apply plugin: 'com.github.johnrengelman.shadow'\n" + 18 | "\n" + 19 | "version = '0.1.0'\n" + 20 | "group = '%s'\n" + 21 | "archivesBaseName = '%s'\n" + 22 | "\n" + 23 | "if (!JavaVersion.current().java8Compatible) {\n" + 24 | " throw new IllegalStateException('''A Haiku:\n" + 25 | " | This needs Java 8,\n" + 26 | " | You are using something else,\n" + 27 | " | Refresh. Try again.'''.stripMargin())\n" + 28 | "}\n" + 29 | "\n" + 30 | "repositories {\n" + 31 | " mavenCentral()\n" + 32 | " maven { url = 'http://oss.sonatype.org/content/repositories/snapshots/' }\n" + 33 | " maven { url = 'http://oss.sonatype.org/content/repositories/releases/' }\n" + 34 | "}\n" + 35 | "\n" + 36 | "dependencies {\n" + 37 | " compile 'io.vertx:vertx-core:3.2.0'\n" + 38 | " compile \"joda-time:joda-time:2.4\"\n" + 39 | " compile \"com.google.guava:guava:18.0\"\n" + 40 | " compile \"commons-lang:commons-lang:2.6\"\n" + 41 | " compile \"net.sf.jopt-simple:jopt-simple:4.9\"\n" + 42 | " compile \"com.cyngn.vertx:vertx-util:0.5.4\"\n" + 43 | " compile \"com.englishtown.vertx:vertx-cassandra:3.2.0\"\n" + 44 | " compile \"com.englishtown.vertx:vertx-cassandra-mapping:3.2.0\"\n" + 45 | " compile \"ch.qos.logback:logback-classic:1.0.13\"\n" + 46 | " compile \"ch.qos.logback:logback-core:1.0.13\"\n" + 47 | " compile \"io.vertx:vertx-codegen:3.2.0\"\n" + 48 | " testCompile \"junit:junit:4.11\"\n" + 49 | " testCompile \"io.vertx:vertx-unit:3.2.0\"\n" + 50 | "}\n" + 51 | "\n" + 52 | "task wrapper(type: Wrapper) {\n" + 53 | " gradleVersion = '2.5'\n" + 54 | "}\n" + 55 | "\n" + 56 | "task release() << {}\n" + 57 | "\n" + 58 | "gradle.taskGraph.whenReady {taskGraph ->\n" + 59 | " if (!taskGraph.hasTask(release)) {\n" + 60 | " version += '-SNAPSHOT'\n" + 61 | " }\n" + 62 | "}\n" + 63 | "\n" + 64 | "task sourcesJar(type: Jar) {\n" + 65 | " classifier = 'sources'\n" + 66 | " from sourceSets.main.allSource\n" + 67 | "}\n" + 68 | "\n" + 69 | "artifacts {\n" + 70 | " archives sourcesJar\n" + 71 | "}\n" + 72 | "\n" + 73 | "shadowJar {\n" + 74 | " classifier = 'fat'\n" + 75 | " manifest {\n" + 76 | " attributes 'Main-Class': 'io.vertx.core.Starter'\n" + 77 | " attributes 'Main-Verticle': '%s'\n" + 78 | " }\n" + 79 | " mergeServiceFiles {\n" + 80 | " include 'META-INF/services/io.vertx.core.spi.VerticleFactory'\n" + 81 | " }\n" + 82 | "}"; 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/com/cyngn/exovert/generate/entity/TableGenerator.java: -------------------------------------------------------------------------------- 1 | package com.cyngn.exovert.generate.entity; 2 | 3 | import com.cyngn.exovert.util.Disk; 4 | import com.cyngn.exovert.util.GeneratorHelper; 5 | import com.cyngn.exovert.util.MetaData; 6 | import com.datastax.driver.core.ColumnMetadata; 7 | import com.datastax.driver.core.DataType; 8 | import com.datastax.driver.core.TableMetadata; 9 | import com.datastax.driver.mapping.annotations.Table; 10 | import com.google.common.base.CaseFormat; 11 | import com.squareup.javapoet.AnnotationSpec; 12 | import com.squareup.javapoet.JavaFile; 13 | import com.squareup.javapoet.TypeSpec; 14 | 15 | import javax.lang.model.element.Modifier; 16 | import java.io.IOException; 17 | import java.util.ArrayList; 18 | import java.util.Collection; 19 | import java.util.HashMap; 20 | import java.util.List; 21 | import java.util.Map; 22 | 23 | /** 24 | * Creates Table classes based on the cassandra schema. 25 | * 26 | * @author truelove@cyngn.com (Jeremy Truelove) 8/27/15 27 | */ 28 | public class TableGenerator { 29 | 30 | /** 31 | * Kicks off table generation. 32 | * 33 | * @param tables the cassandra table meta data 34 | * @throws IOException if write to file fails 35 | */ 36 | public static void generate(Collection tables) throws IOException { 37 | String namespaceToUse = MetaData.instance.getTableNamespace(); 38 | 39 | for (TableMetadata table : tables) { 40 | String rawName = table.getName(); 41 | String name = CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, rawName); 42 | 43 | TypeSpec.Builder tableClassBuilder = TypeSpec.classBuilder(name) 44 | .addModifiers(Modifier.PUBLIC) 45 | .addAnnotation(getTableAnnotation(table.getKeyspace().getName(), rawName)); 46 | 47 | addFields(tableClassBuilder, table, name); 48 | 49 | tableClassBuilder.addJavadoc(GeneratorHelper.getJavaDocHeader("Table class for Cassandra - " + rawName, MetaData.instance.getUpdateTime())); 50 | 51 | JavaFile javaFile = JavaFile.builder(namespaceToUse, tableClassBuilder.build()).build(); 52 | 53 | Disk.outputFile(javaFile); 54 | } 55 | } 56 | 57 | /** 58 | * Add fields to the class spec. 59 | */ 60 | private static void addFields(TypeSpec.Builder builder, TableMetadata tableMetadata, String className) { 61 | Map partitionKeys = getMapOfKeys(tableMetadata.getPartitionKey()); 62 | Map clusteringKeys = getMapOfKeys(tableMetadata.getClusteringColumns()); 63 | 64 | List fields = new ArrayList<>(); 65 | for (ColumnMetadata column : tableMetadata.getColumns()) { 66 | DataType type = column.getType(); 67 | String name = column.getName(); 68 | 69 | List extraAnnotations = new ArrayList<>(); 70 | if(partitionKeys.containsKey(name)) { 71 | extraAnnotations.add(EntityGeneratorHelper.getPartitionKeyAnnotation(partitionKeys.get(name))); 72 | } 73 | 74 | if(clusteringKeys.containsKey(name)) { 75 | extraAnnotations.add(EntityGeneratorHelper.getClusteringAnnotation(clusteringKeys.get(name))); 76 | } 77 | 78 | builder.addField(EntityGeneratorHelper.getFieldSpec(name, type, false, extraAnnotations)); 79 | builder.addMethod(EntityGeneratorHelper.getSetter(name, type)); 80 | builder.addMethod(EntityGeneratorHelper.getGetter(name, type)); 81 | fields.add(CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, name)); 82 | } 83 | 84 | builder.addMethod(EntityGeneratorHelper.getToString(fields, className)); 85 | } 86 | 87 | /** 88 | * Map out the keys and their positions. 89 | * @param keys the keys to map 90 | * @return map of the keys at their position in the key chain 91 | */ 92 | private static Map getMapOfKeys(List keys) { 93 | Map partitionKeys = new HashMap<>(); 94 | int count = 0; 95 | for(ColumnMetadata columnMetadata : keys) { 96 | partitionKeys.put(columnMetadata.getName(), count); 97 | count++; 98 | } 99 | return partitionKeys; 100 | } 101 | 102 | /** 103 | * Add Table annotation to class. 104 | */ 105 | private static AnnotationSpec getTableAnnotation(String keyspace, String name) { 106 | return AnnotationSpec.builder(Table.class).addMember("keyspace", "$S", keyspace) 107 | .addMember("name", "$S", name).build(); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/main/java/com/cyngn/exovert/generate/server/rest/TypeGenerator.java: -------------------------------------------------------------------------------- 1 | package com.cyngn.exovert.generate.server.rest; 2 | 3 | import com.cyngn.exovert.generate.server.rest.types.ClassType; 4 | import com.cyngn.exovert.generate.server.rest.types.DataTypeGroup; 5 | import com.cyngn.exovert.generate.server.rest.types.EnumType; 6 | import com.cyngn.exovert.generate.server.rest.utils.Constants; 7 | import com.cyngn.exovert.generate.server.rest.utils.RestGeneratorHelper; 8 | import com.cyngn.exovert.util.GeneratorHelper; 9 | import com.squareup.javapoet.JavaFile; 10 | import com.squareup.javapoet.TypeSpec; 11 | import org.slf4j.Logger; 12 | import org.slf4j.LoggerFactory; 13 | 14 | import java.io.IOException; 15 | 16 | /** 17 | * Generates the types. 18 | * 19 | * @author asarda@cyngn.com (Ajay Sarda) 9/17/15. 20 | */ 21 | public class TypeGenerator { 22 | private static final Logger logger = LoggerFactory.getLogger(TypeGenerator.class); 23 | private GenerationContext context; 24 | private ClassGenerator classGenerator; 25 | private MethodGenerator methodGenerator; 26 | 27 | public TypeGenerator(GenerationContext context) { 28 | this.context = context; 29 | this.methodGenerator = new MethodGenerator(this.context); 30 | this.classGenerator = new ClassGenerator(this.context, this.methodGenerator); 31 | } 32 | 33 | /** 34 | * Generates Types 35 | * 36 | * @throws Exception on generation failure 37 | */ 38 | public void generate() throws Exception { 39 | logger.info("Generating the types..."); 40 | 41 | DataTypeSpec spec = RestGeneratorHelper.loadTypeSpecFromFile(context.specFilePath); 42 | 43 | // generate for data type group 44 | for (DataTypeGroup dataTypeGroup : spec.dataTypeGroups) { 45 | // recreate the type map on every group to avoid collision across packages. 46 | context.typeMap = TypeMap.create(); 47 | 48 | // generate enums 49 | if (dataTypeGroup.enumTypes != null) { 50 | for (EnumType enumType : dataTypeGroup.enumTypes) { 51 | 52 | TypeSpec typeSpec = classGenerator 53 | .getEnumTypeSpec(RestGeneratorHelper.getTypesNamespace(dataTypeGroup.namespace), enumType); 54 | 55 | generateClassFromTypespec(RestGeneratorHelper.getTypesNamespace(dataTypeGroup.namespace), typeSpec); 56 | } 57 | } 58 | 59 | // generate class types 60 | if (dataTypeGroup.classTypes != null) { 61 | for (ClassType classType : dataTypeGroup.classTypes) { 62 | TypeSpec typeSpec = 63 | classGenerator.getTypeSpecBuilder( 64 | RestGeneratorHelper.getTypesNamespace(dataTypeGroup.namespace), 65 | RestGeneratorHelper.getTypeName(classType.name), 66 | classType.fields, classType.immutable, classType.jsonAnnotations) 67 | .addJavadoc(GeneratorHelper.getJavaDocHeader(classType.description)).build(); 68 | 69 | generateClassFromTypespec(RestGeneratorHelper.getTypesNamespace(dataTypeGroup.namespace), typeSpec); 70 | } 71 | } 72 | } 73 | } 74 | 75 | public void generateClassFromTypespec(String namespace, TypeSpec typeSpec) throws IOException { 76 | JavaFile javaFile = JavaFile.builder(namespace, typeSpec) 77 | .indent(Constants.INDENTATION_SPACES).build(); 78 | 79 | logger.info(String.format("Generating package name:%s, class:%s", namespace, typeSpec.name)); 80 | 81 | if (this.context.preview) { 82 | javaFile.writeTo(System.out); 83 | } else { 84 | javaFile.writeTo(RestGeneratorHelper.getDirectoryPath(context.outputDirectory)); 85 | } 86 | } 87 | 88 | /** 89 | * Get the instance to {@link RestServerGenerator.Builder} 90 | * 91 | * @return {@link RestServerGenerator.Builder} 92 | */ 93 | public static Builder newBuilder() { 94 | return new Builder(); 95 | } 96 | 97 | /** 98 | * Fluent Builder class to build {@link RestServerGenerator} 99 | */ 100 | public static class Builder { 101 | private GenerationContext context; 102 | 103 | public Builder() { 104 | context = new GenerationContext(); 105 | } 106 | 107 | public Builder withIsPreview(boolean isPreview) { 108 | context.preview = isPreview; 109 | return this; 110 | } 111 | 112 | public Builder withOutputDirectory(String outputDirectory) { 113 | context.outputDirectory = outputDirectory; 114 | return this; 115 | } 116 | 117 | public Builder withSpecFilePath(String specFilePath) { 118 | context.specFilePath = specFilePath; 119 | return this; 120 | } 121 | 122 | public TypeGenerator build() { 123 | return new TypeGenerator(context); 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/main/java/com/cyngn/exovert/generate/server/rest/CommonRestGenerator.java: -------------------------------------------------------------------------------- 1 | package com.cyngn.exovert.generate.server.rest; 2 | 3 | import com.cyngn.exovert.generate.server.rest.types.Api; 4 | import com.cyngn.exovert.generate.server.rest.types.ClassType; 5 | import com.cyngn.exovert.generate.server.rest.types.DataTypeGroup; 6 | import com.cyngn.exovert.generate.server.rest.types.EnumType; 7 | import com.cyngn.exovert.generate.server.rest.utils.Constants; 8 | import com.cyngn.exovert.generate.server.rest.utils.RestGeneratorHelper; 9 | import com.cyngn.exovert.util.GeneratorHelper; 10 | import com.google.common.base.Preconditions; 11 | import com.squareup.javapoet.JavaFile; 12 | import com.squareup.javapoet.TypeSpec; 13 | import org.slf4j.Logger; 14 | import org.slf4j.LoggerFactory; 15 | 16 | import java.io.IOException; 17 | 18 | /** 19 | * Generator to generate common types between 20 | * Server and Client 21 | * 22 | * @author asarda@cyngn.com (Ajay Sarda) 9/17/15. 23 | */ 24 | class CommonRestGenerator { 25 | private static final Logger logger = LoggerFactory.getLogger(CommonRestGenerator.class); 26 | private GenerationContext context; 27 | private ClassGenerator classGenerator; 28 | private MethodGenerator methodGenerator; 29 | 30 | public CommonRestGenerator(GenerationContext context) { 31 | this.context = context; 32 | this.methodGenerator = new MethodGenerator(this.context); 33 | this.classGenerator = new ClassGenerator(this.context, this.methodGenerator); 34 | } 35 | 36 | /** 37 | * Generates Request, Response classes, types 38 | * 39 | * @throws Exception on generation failure 40 | */ 41 | public void generate(InterfaceSpec spec) throws Exception { 42 | Preconditions.checkArgument(spec != null, "Interface specification cannot be null"); 43 | 44 | // generate types 45 | if (spec.dataTypeGroups != null) { 46 | 47 | // generate for data type group 48 | for (DataTypeGroup dataTypeGroup : spec.dataTypeGroups) { 49 | // generate enums 50 | if (dataTypeGroup.enumTypes != null) { 51 | for (EnumType enumType : dataTypeGroup.enumTypes) { 52 | String namespace = getPackageNamespace(context, dataTypeGroup.namespace); 53 | TypeSpec typeSpec = classGenerator.getEnumTypeSpec(namespace, enumType); 54 | generateClassFromTypespec(namespace, typeSpec); 55 | } 56 | } 57 | 58 | // generate class types 59 | if (dataTypeGroup.classTypes != null) { 60 | for (ClassType classType : dataTypeGroup.classTypes) { 61 | String namespace = getPackageNamespace(context, dataTypeGroup.namespace); 62 | TypeSpec typeSpec = 63 | classGenerator.getTypeSpecBuilder( 64 | namespace, 65 | RestGeneratorHelper.getTypeName(classType.name), 66 | classType.fields, classType.immutable, classType.jsonAnnotations) 67 | .addJavadoc(GeneratorHelper.getJavaDocHeader(classType.description)).build(); 68 | 69 | generateClassFromTypespec(namespace, typeSpec); 70 | } 71 | } 72 | } 73 | } 74 | 75 | for (Api api : spec.apis) { 76 | String namespace = getPackageNamespace(context, RestGeneratorHelper.getTypesNamespace(spec.namespace)); 77 | // generate request 78 | if(api.request != null) { 79 | TypeSpec typeSpec = classGenerator.getRequestTypeSpec(namespace, api); 80 | generateClassFromTypespec(namespace, typeSpec); 81 | } 82 | 83 | // generate response 84 | if(api.response != null) { 85 | // generate response 86 | TypeSpec typeSpec = classGenerator.getResponseTypeSpec( 87 | namespace, 88 | RestGeneratorHelper.getResponseObjectName(api.name), 89 | api.response.fields); 90 | 91 | generateClassFromTypespec(namespace, typeSpec); 92 | } 93 | } 94 | } 95 | 96 | /** 97 | * Dumps the class from TypeSpec 98 | * 99 | * @param namespace - package namespace 100 | * @param typeSpec - TypeSpec 101 | * @throws IOException - on write failure. 102 | */ 103 | public void generateClassFromTypespec(String namespace, TypeSpec typeSpec) throws IOException { 104 | JavaFile javaFile = JavaFile.builder(namespace, typeSpec) 105 | .indent(Constants.INDENTATION_SPACES).build(); 106 | 107 | logger.info(String.format("Generating package name:%s, class:%s", namespace, typeSpec.name)); 108 | 109 | if (this.context.preview) { 110 | javaFile.writeTo(System.out); 111 | } else { 112 | javaFile.writeTo(RestGeneratorHelper.getDirectoryPath(context.outputDirectory)); 113 | } 114 | } 115 | 116 | public String getPackageNamespace(GenerationContext context, String namespace) { 117 | if (context.client) { 118 | return namespace + ".client"; 119 | } 120 | return namespace; 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /gradle/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | [![Build Status](https://travis-ci.org/cyngn/exovert.svg?branch=master)](https://travis-ci.org/cyngn/exovert) 3 | 4 | # exovert 5 | Is a tool that is designed to help free developers up to focus on developing services and not building the boiler plate glue code often involved with accessing a DB, creating POJOs, building CRUD interfaces. It also helps in creating REST server and client classes, or creating Java classes with setters, getters, equals/hashCode/toString, builder and Json annotations. 6 | 7 | The tool is focused around using Cassandra for storage and Vert.x as the primary service framework. More DB options could be added based on demand at a later time. 8 | 9 | Currently tool has two generators - CrudGenerator and RestGenerator 10 | 11 | CrudGenerator works by reading your schema from the DB then generating the entity classes (ie Table and UDT objects), then building a DAL for them, then the REST interface. 12 | 13 | CrudGenerator Support 14 | 15 | * entity classes 16 | * DAL 17 | * CRUD REST interface, including list APIs 18 | * Simple Server 19 | * metrics support (coming soon) 20 | * cache support (coming later) 21 | 22 | RestGenerator works by reading the specification file from the command line and generates either server or client or type classes depending on the command line option. 23 | 24 | RestGenerator Support 25 | 26 | * REST API classes for GET, POST and DELETE 27 | * Validations for API inputs 28 | * Request and Response classes for REST APIs 29 | * Java classes with setters, getters, equals/hashCode/toString, builder and Json annotations 30 | * Option to generate Immutable or Mutable classes 31 | * Request and Response classes for REST clients 32 | * REST client builder with service endpoint, timeouts, retries (coming later) 33 | 34 | ## Getting Started 35 | 36 | Get the command line tool. 37 | 38 | ```xml 39 | 40 | com.cyngn.vertx 41 | exovert 42 | 0.2.0 43 | 44 | ``` 45 | 46 | ## Running CrudCreator 47 | 48 | ```bash 49 | $ build/bin/CrudCreator 50 | ``` 51 | ``` 52 | Option Description 53 | ------ ----------- 54 | -c, --create create the basic service infrastructure 55 | -d, --db the db host to connect to 56 | --help shows this message 57 | -k, --keyspace the keyspace to read from 58 | -n, --namespace the namespace to create java classes in 59 | -o, --out the output dir to place files in 60 | -p, --preview output all the java files to the 61 | console, don't create files 62 | -r, --rest generate the REST API for the scheme 63 | ``` 64 | 65 | **Example Preview Run** 66 | ```bash 67 | java -jar exovert-0.1.0-fat.jar --preview -k test_keyspace -db localhost -n com.test 68 | ``` 69 | 70 | **Example Run** 71 | ```bash 72 | java -jar exovert-0.1.0-fat.jar --create -k test_keyspace -db localhost -n com.test -o src/java/generated 73 | ``` 74 | 75 | ### Example Cassandra Scheme 76 | 77 | As an example [ChronoServer Cassandra Schema](https://github.com/cyngn/ChronoServer/blob/master/db/scheme.cql) 78 | 79 | ```cql 80 | // schema script 81 | CREATE TYPE IF NOT EXISTS chrono.url_package ( 82 | method varchar, 83 | urls set 84 | ); 85 | 86 | CREATE TABLE IF NOT EXISTS chrono.test_batch ( 87 | name varchar, 88 | url_packages list>, 89 | created timestamp, 90 | PRIMARY KEY (name) 91 | ); 92 | 93 | CREATE TYPE IF NOT EXISTS chrono.measurement ( 94 | url varchar, 95 | time_in_milli bigint 96 | ); 97 | 98 | CREATE TABLE IF NOT EXISTS chrono.payload ( 99 | unit varchar, 100 | size bigint, 101 | data varchar, 102 | PRIMARY KEY (unit, size) 103 | ); 104 | 105 | CREATE TABLE IF NOT EXISTS chrono.upload_data ( 106 | test_batch varchar, 107 | unit varchar, 108 | size bigint, 109 | data varchar, 110 | created timestamp, 111 | PRIMARY KEY (test_batch, unit, size, created) 112 | ); 113 | 114 | CREATE TABLE IF NOT EXISTS chrono.report ( 115 | batch_name varchar, 116 | mode varchar, 117 | device_id varchar, 118 | mobile_carrier varchar, 119 | mobile_rssi varchar, 120 | wifi_state varchar, 121 | wifi_rssi varchar, 122 | gps_coordinates varchar, 123 | tag varchar, 124 | mobile_network_class varchar, 125 | mobile_network_type varchar, 126 | client_ip varchar, 127 | created timestamp, 128 | measurements list>, 129 | PRIMARY KEY (batch_name, device_id, created) 130 | ); 131 | ``` 132 | 133 | ### CRUD Generated Code Example 134 | 135 | see [CRUD_README.md](https://github.com/cyngn/exovert/blob/master/src/main/java/com/cyngn/exovert/generate/CRUD_README.md) for the output code sample. 136 | 137 | ## Running RestGenerator 138 | 139 | ```bash 140 | $ build/bin/RestCreator 141 | ``` 142 | ``` 143 | --client create the client files on disk 144 | -f, --spec specification file (default: api.json) 145 | --help shows this message 146 | -o, --out the output dir in which to place files 147 | (default: build/generated-src) 148 | -p, --preview output all the java files to the 149 | console, don't create files 150 | --server create the server files on disk 151 | --types create the type files on disk 152 | ``` 153 | 154 | **Example Preview Run to create server** 155 | ```bash 156 | build/bin/RestGenerator --preview --server --spec api.json 157 | ``` 158 | For sample api json file, look at [samples](https://github.com/cyngn/exovert/blob/master/samples/) 159 | 160 | **Example Run to create server** 161 | ```bash 162 | build/bin/RestGenerator --server --spec api.json --out build/generated-src 163 | ``` 164 | ### REST Generated Code Example 165 | Examples are at [REST_README.md](https://github.com/cyngn/exovert/blob/master/src/main/java/com/cyngn/exovert/generate/server/rest/README.md) 166 | 167 | ### Thanks 168 | Especially to the [Java Poet Creators](https://github.com/square/javapoet) for making such a great and easy to use code generation library. 169 | -------------------------------------------------------------------------------- /src/test/java/com/cyngn/exovert/generate/server/model/utils/RestGeneratorHelperTest.java: -------------------------------------------------------------------------------- 1 | package com.cyngn.exovert.generate.server.model.utils; 2 | 3 | import com.cyngn.exovert.generate.server.rest.InterfaceSpec; 4 | import com.cyngn.exovert.generate.server.rest.TypeMap; 5 | import com.cyngn.exovert.generate.server.rest.utils.RestGeneratorHelper; 6 | import org.junit.Test; 7 | 8 | import static org.junit.Assert.assertEquals; 9 | 10 | /** 11 | * @author asarda@cyngn.com (Ajay Sarda) 9/18/15. 12 | */ 13 | public class RestGeneratorHelperTest { 14 | 15 | @Test 16 | public void testGetApiName() { 17 | assertEquals("AbstractCreateBeerApi", RestGeneratorHelper.getApiName("create_beer")); 18 | assertEquals("AbstractCreateBeerApi", RestGeneratorHelper.getApiName("Create_beer")); 19 | assertEquals("AbstractCreateBeerApi", RestGeneratorHelper.getApiName("create_Beer")); 20 | assertEquals("AbstractCreateBeerApi", RestGeneratorHelper.getApiName("Create_Beer")); 21 | assertEquals("AbstractCreatebeerApi", RestGeneratorHelper.getApiName("CreateBeer")); 22 | 23 | } 24 | 25 | @Test 26 | public void testGetRequestObjectName() { 27 | assertEquals("CreateRequest", RestGeneratorHelper.getRequestObjectName("create")); 28 | assertEquals("CreateBeerRequest", RestGeneratorHelper.getRequestObjectName("create_beer")); 29 | } 30 | 31 | @Test 32 | public void testGetRequestVariableName() { 33 | assertEquals("createRequest", RestGeneratorHelper.getRequestVariableName("create")); 34 | assertEquals("createBeerRequest", RestGeneratorHelper.getRequestVariableName("create_beer")); 35 | } 36 | 37 | @Test 38 | public void testGetSetMethodName() { 39 | assertEquals("setName", RestGeneratorHelper.getSetMethodName("name")); 40 | assertEquals("setBeerType", RestGeneratorHelper.getSetMethodName("beerType")); 41 | } 42 | 43 | @Test 44 | public void testGetGetMethodName() { 45 | assertEquals("getName", RestGeneratorHelper.getGetMethodName("name")); 46 | assertEquals("getBeerType", RestGeneratorHelper.getGetMethodName("beerType")); 47 | } 48 | 49 | @Test 50 | public void testGetFieldName() { 51 | assertEquals("name", RestGeneratorHelper.getFieldName("name")); 52 | assertEquals("beerType", RestGeneratorHelper.getFieldName("beer_type")); 53 | assertEquals("beerType", RestGeneratorHelper.getFieldName("beer_Type")); 54 | assertEquals("beerType", RestGeneratorHelper.getFieldName("Beer_Type")); 55 | 56 | //TODO: fix this 57 | //assertEquals("beerType", RestGeneratorHelper.getFieldName("beerType")); 58 | } 59 | 60 | @Test 61 | public void testGetTypeName() { 62 | TypeMap typeMap = TypeMap.create(); 63 | assertEquals("Integer", RestGeneratorHelper.getTypeName("Integer")); 64 | assertEquals("String", RestGeneratorHelper.getTypeName("String")); 65 | assertEquals("List", RestGeneratorHelper.getTypeName("List")); 66 | assertEquals("List", RestGeneratorHelper.getTypeName("list")); 67 | assertEquals("List", RestGeneratorHelper.getTypeName("List")); 68 | assertEquals("List", RestGeneratorHelper.getTypeName("list")); 69 | assertEquals("Set", RestGeneratorHelper.getTypeName("Set")); 70 | assertEquals("Set", RestGeneratorHelper.getTypeName("set")); 71 | assertEquals("Set", RestGeneratorHelper.getTypeName("Set")); 72 | assertEquals("Set", RestGeneratorHelper.getTypeName("set")); 73 | assertEquals("Map", RestGeneratorHelper.getTypeName("Map< String , String>")); 74 | assertEquals("Map", RestGeneratorHelper.getTypeName("Map< beer_type , string>")); 75 | assertEquals("Map", RestGeneratorHelper.getTypeName("Map< beer_type , beer_type>")); 76 | assertEquals("Map,String>", RestGeneratorHelper.getTypeName("map, string>")); 77 | assertEquals("List>", RestGeneratorHelper.getTypeName("list>")); 78 | assertEquals("Map,String>,String>", 79 | RestGeneratorHelper.getTypeName("map,string>,string>")); 80 | } 81 | 82 | @Test 83 | public void testGetBuilderTypeName() { 84 | assertEquals("CreateBeerRequest.Builder", RestGeneratorHelper.getBuilderTypeName("CreateBeerRequest")); 85 | } 86 | 87 | @Test 88 | public void testGetBuilderVariableName() { 89 | assertEquals("builder", RestGeneratorHelper.getBuilderVariableName()); 90 | } 91 | 92 | @Test 93 | public void testGetHandlerName() { 94 | assertEquals("handleGet", RestGeneratorHelper.getHandlerName("get")); 95 | assertEquals("handleGet", RestGeneratorHelper.getHandlerName("Get")); 96 | assertEquals("handleGet", RestGeneratorHelper.getHandlerName("GET")); 97 | assertEquals("handlePost", RestGeneratorHelper.getHandlerName("post")); 98 | assertEquals("handlePost", RestGeneratorHelper.getHandlerName("Post")); 99 | assertEquals("handlePost", RestGeneratorHelper.getHandlerName("POST")); 100 | assertEquals("handleDelete", RestGeneratorHelper.getHandlerName("delete")); 101 | assertEquals("handleDelete", RestGeneratorHelper.getHandlerName("Delete")); 102 | assertEquals("handleDelete", RestGeneratorHelper.getHandlerName("DELETE")); 103 | } 104 | 105 | @Test 106 | public void testLoadTypeSpecFromFile() throws Exception { 107 | // find better way of doing this 108 | InterfaceSpec spec = RestGeneratorHelper.loadSpecFromFile("src/test/java/com/cyngn/exovert/generate/server/model/utils/api.json"); 109 | 110 | assertEquals(1, spec.apis.size()); 111 | 112 | assertEquals(1, spec.dataTypeGroups.get(0).enumTypes.size()); 113 | assertEquals(1, spec.dataTypeGroups.get(0).classTypes.size()); 114 | } 115 | } -------------------------------------------------------------------------------- /src/test/java/com/cyngn/exovert/generate/server/model/TypeParserTest.java: -------------------------------------------------------------------------------- 1 | package com.cyngn.exovert.generate.server.model; 2 | 3 | import com.cyngn.exovert.generate.server.rest.TypeMap; 4 | import com.cyngn.exovert.generate.server.rest.TypeParser; 5 | import com.squareup.javapoet.ClassName; 6 | import com.squareup.javapoet.TypeName; 7 | import static org.junit.Assert.*; 8 | import org.junit.Rule; 9 | import org.junit.Test; 10 | import org.junit.rules.ExpectedException; 11 | 12 | /** 13 | * Tests {@link TypeParser} 14 | * 15 | * @author asarda@cyngn.com (Ajay Sarda) 9/18/15. 16 | */ 17 | public class TypeParserTest { 18 | 19 | @Rule 20 | public final ExpectedException exception = ExpectedException.none(); 21 | 22 | @Test 23 | public void testIsList() { 24 | assertTrue(TypeParser.isList("List")); 25 | assertTrue(TypeParser.isList("List< String >")); 26 | assertFalse(TypeParser.isList("NotAList")); 27 | assertFalse(TypeParser.isList("String")); 28 | } 29 | 30 | @Test 31 | public void testGetListType() { 32 | assertEquals("String", TypeParser.getListType("List")); 33 | assertEquals("T", TypeParser.getListType("List")); 34 | assertEquals("string", TypeParser.getListType("List")); 35 | assertEquals("string", TypeParser.getListType("list")); 36 | assertEquals("String", TypeParser.getListType("list")); 37 | 38 | assertEquals("T", TypeParser.getListType("list")); 39 | 40 | exception.expect(IllegalArgumentException.class); 41 | TypeParser.getListType("NotAList"); 42 | 43 | exception.expect(IllegalArgumentException.class); 44 | TypeParser.getListType("NotAList<>"); 45 | } 46 | 47 | @Test 48 | public void testGetSetType() { 49 | assertEquals("String", TypeParser.getSetType("Set")); 50 | assertEquals("T", TypeParser.getSetType("Set")); 51 | assertEquals("string", TypeParser.getSetType("Set")); 52 | assertEquals("string", TypeParser.getSetType("set")); 53 | assertEquals("String", TypeParser.getSetType("set")); 54 | 55 | assertEquals("T", TypeParser.getSetType("set")); 56 | 57 | exception.expect(IllegalArgumentException.class); 58 | TypeParser.getSetType("NotASet"); 59 | 60 | exception.expect(IllegalArgumentException.class); 61 | TypeParser.getSetType("NotASet<>"); 62 | } 63 | 64 | @Test 65 | public void testIsMap() { 66 | assertTrue(TypeParser.isMap("Map")); 67 | assertTrue(TypeParser.isMap("Map< String, String>")); 68 | assertTrue(TypeParser.isMap("Map< String , String >")); 69 | assertTrue(TypeParser.isMap("Map< K , V >")); 70 | assertTrue(TypeParser.isMap("Map< K , V >")); 71 | 72 | // TODO: fix this 73 | //Assert.assertFalse(TypeParser.isMap("Map<>")); 74 | } 75 | 76 | @Test 77 | public void testGetMapKeyType() { 78 | assertEquals("String", TypeParser.getMapKeyType("Map")); 79 | assertEquals("K", TypeParser.getMapKeyType("Map")); 80 | assertEquals("Map", TypeParser.getMapKeyType("Map,String>")); 81 | 82 | exception.expect(IllegalArgumentException.class); 83 | TypeParser.getMapKeyType("Map<>"); 84 | 85 | exception.expect(IllegalArgumentException.class); 86 | TypeParser.getMapKeyType("Map"); 87 | 88 | exception.expect(IllegalArgumentException.class); 89 | TypeParser.getMapKeyType("Map"); 90 | 91 | exception.expect(IllegalArgumentException.class); 92 | TypeParser.getMapKeyType("Map<,>"); 93 | } 94 | 95 | @Test 96 | public void testGetMapValueType() { 97 | assertEquals("String", TypeParser.getMapValueType("Map")); 98 | assertEquals("V", TypeParser.getMapValueType("Map")); 99 | 100 | exception.expect(IllegalArgumentException.class); 101 | TypeParser.getMapKeyType("Map<>"); 102 | 103 | exception.expect(IllegalArgumentException.class); 104 | TypeParser.getMapKeyType("Map"); 105 | 106 | exception.expect(IllegalArgumentException.class); 107 | TypeParser.getMapKeyType("Map"); 108 | 109 | exception.expect(IllegalArgumentException.class); 110 | TypeParser.getMapKeyType("Map<,>"); 111 | } 112 | 113 | @Test 114 | public void testParse() { 115 | TypeMap typeMap = TypeMap.create(); 116 | TypeName typeName = TypeParser.parse("List", typeMap); 117 | 118 | assertEquals("java.util.List", typeName.toString()); 119 | 120 | typeMap.registerType("T", ClassName.get("", "T")); 121 | 122 | typeName = TypeParser.parse("List", typeMap); 123 | assertEquals("java.util.List", typeName.toString()); 124 | 125 | typeName = TypeParser.parse("List>", typeMap); 126 | assertEquals("java.util.List>", typeName.toString()); 127 | 128 | typeName = TypeParser.parse("Set", typeMap); 129 | assertEquals("java.util.Set", typeName.toString()); 130 | 131 | typeName = TypeParser.parse("Set>", typeMap); 132 | assertEquals("java.util.Set>", typeName.toString()); 133 | 134 | typeName = TypeParser.parse("Map", typeMap); 135 | 136 | assertEquals("java.util.Map", typeName.toString()); 137 | 138 | typeMap.registerType("K", ClassName.get("", "K")); 139 | typeMap.registerType("V", ClassName.get("", "V")); 140 | 141 | typeName = TypeParser.parse("Map", typeMap); 142 | 143 | assertEquals("java.util.Map", typeName.toString()); 144 | 145 | typeName = TypeParser.parse("Map, V>", typeMap); 146 | assertEquals("java.util.Map, V>", typeName.toString()); 147 | 148 | typeName = TypeParser.parse("Map,V>, V>", typeMap); 149 | assertEquals("java.util.Map, V>, V>", typeName.toString()); 150 | 151 | } 152 | } -------------------------------------------------------------------------------- /src/main/java/com/cyngn/exovert/RestCreator.java: -------------------------------------------------------------------------------- 1 | package com.cyngn.exovert; 2 | 3 | import com.cyngn.exovert.generate.server.rest.RestClientGenerator; 4 | import com.cyngn.exovert.generate.server.rest.RestServerGenerator; 5 | import com.cyngn.exovert.generate.server.rest.TypeGenerator; 6 | import com.cyngn.vertx.async.Action; 7 | import joptsimple.OptionParser; 8 | import joptsimple.OptionSet; 9 | import joptsimple.OptionSpec; 10 | 11 | import java.io.IOException; 12 | import java.util.HashMap; 13 | import java.util.Map; 14 | 15 | import static java.util.Arrays.asList; 16 | 17 | /** 18 | * Runs the generation for 19 | * Server and Clients 20 | * 21 | * @author asarda@cyngn.com (Ajay Sarda) 9/16/15. 22 | */ 23 | public class RestCreator { 24 | 25 | // command line params 26 | private OptionSpec preview; 27 | private OptionSpec server; 28 | private OptionSpec client; 29 | private OptionSpec types; 30 | 31 | private OptionSpec out; 32 | private OptionSpec spec; 33 | private OptionSpec help; 34 | 35 | private OptionParser parser; 36 | 37 | private Map, Action> actions; 38 | 39 | private OptionSet optionSet; 40 | 41 | private RestCreator() throws Exception { 42 | parser = getParser(); 43 | 44 | actions = new HashMap<>(); 45 | actions.put(server, this::createServer); 46 | actions.put(client, this::createClient); 47 | actions.put(types, this::createTypes); 48 | } 49 | 50 | private void createServer() { 51 | try { 52 | RestServerGenerator.Builder builder = RestServerGenerator.newBuilder(); 53 | if (optionSet.has(preview)) { 54 | builder.withIsPreview(true); 55 | } else { 56 | if (optionSet.has(out)) { 57 | builder.withOutputDirectory(out.value(optionSet)); 58 | } 59 | } 60 | 61 | if(optionSet.has(spec)) { 62 | builder.withSpecFilePath(spec.value(optionSet)); 63 | } 64 | 65 | // generate the server code 66 | builder.build().generate(); 67 | } catch (Exception e) { 68 | e.printStackTrace(); 69 | } 70 | } 71 | 72 | private void createClient() { 73 | try { 74 | RestClientGenerator.Builder builder = RestClientGenerator.newBuilder(); 75 | if (optionSet.has(preview)) { 76 | builder.withIsPreview(true); 77 | } else if (optionSet.has(out)) { 78 | builder.withOutputDirectory(out.value(optionSet)); 79 | } 80 | if(optionSet.has(spec)) { 81 | builder.withSpecFilePath(spec.value(optionSet)); 82 | } 83 | 84 | builder.build().generate(); 85 | 86 | } catch (Exception e) { 87 | e.printStackTrace(); 88 | } 89 | } 90 | 91 | private void createTypes() { 92 | try { 93 | TypeGenerator.Builder builder = TypeGenerator.newBuilder(); 94 | if (optionSet.has(preview)) { 95 | builder.withIsPreview(true); 96 | } else if (optionSet.has(out)) { 97 | builder.withOutputDirectory(out.value(optionSet)); 98 | } 99 | 100 | if(optionSet.has(spec)) { 101 | builder.withSpecFilePath(spec.value(optionSet)); 102 | } 103 | 104 | builder.build().generate(); 105 | 106 | } catch (Exception e) { 107 | e.printStackTrace(); 108 | } 109 | } 110 | 111 | /** 112 | * Handle parsing the command line args and directing the app path 113 | */ 114 | private void run(String [] args) throws IOException { 115 | optionSet = parser.parse(args); 116 | 117 | OptionSpec invalidSelection = null; 118 | Map.Entry, Action> spec = null; 119 | 120 | // figure out which primary action they selected and make sure they didn't choose two of them 121 | for (Map.Entry, Action> entry : actions.entrySet()) { 122 | if (optionSet.has(entry.getKey())) { 123 | if (spec != null) { 124 | invalidSelection = entry.getKey(); 125 | break; 126 | } 127 | spec = entry; 128 | } 129 | } 130 | 131 | if (invalidSelection != null) { 132 | System.out.println("Competing options selected - option1: " + spec.getKey() + " option2: " + 133 | invalidSelection + "\n"); 134 | parser.printHelpOn(System.out); 135 | System.exit(-1); 136 | } else if (spec != null) { 137 | final Action handler = spec.getValue(); 138 | handler.callback(); 139 | System.exit(0); 140 | } else { 141 | parser.printHelpOn(System.out); 142 | System.exit(0); 143 | } 144 | } 145 | 146 | /** 147 | * Builds the command line parser 148 | */ 149 | private OptionParser getParser() { 150 | OptionParser parser = new OptionParser(); 151 | server = parser.acceptsAll(asList("server"), "create the server files on disk"); 152 | client = parser.acceptsAll(asList("client"), "create the client files on disk"); 153 | types = parser.acceptsAll(asList("types"), "create the type files on disk"); 154 | 155 | preview = parser.acceptsAll(asList("preview", "p"), "output all the java files to the console, don't create files"); 156 | 157 | out = parser.acceptsAll(asList("out", "o"), "the output dir in which to place files") 158 | .withRequiredArg() 159 | .defaultsTo("build/generated-src") 160 | .ofType(String.class); 161 | 162 | spec = parser.acceptsAll(asList("spec", "f"), "specification file") 163 | .withRequiredArg() 164 | .defaultsTo("api.json") 165 | .ofType(String.class); 166 | 167 | help = parser.accepts("help", "shows this message").forHelp(); 168 | return parser; 169 | } 170 | 171 | /** 172 | * Entry point. 173 | * @param args command-line args 174 | * @throws Exception runtime errors 175 | */ 176 | public static void main(String [] args) throws Exception { 177 | new RestCreator().run(args); 178 | } 179 | 180 | } 181 | -------------------------------------------------------------------------------- /src/main/java/com/cyngn/exovert/generate/server/rest/TypeMapImpl.java: -------------------------------------------------------------------------------- 1 | package com.cyngn.exovert.generate.server.rest; 2 | 3 | import com.google.common.base.Preconditions; 4 | import com.squareup.javapoet.ClassName; 5 | import com.squareup.javapoet.CodeBlock; 6 | import com.squareup.javapoet.TypeName; 7 | import org.apache.commons.lang.StringUtils; 8 | 9 | import java.util.HashMap; 10 | import java.util.HashSet; 11 | import java.util.Map; 12 | import java.util.Set; 13 | import java.util.function.Function; 14 | 15 | /** 16 | * Implementation of {@link TypeMap} 17 | * 18 | * Coupled with {@link TypeName} to keep knowledge of types that are fed into 19 | * code generation. 20 | * 21 | * @author asarda@cyngn.com (Ajay Sarda) 9/8/15. 22 | */ 23 | class TypeMapImpl implements TypeMap { 24 | 25 | private Map typeToClassMapping = new HashMap<>(); 26 | private Map> typeConverterMapping = new HashMap<>(); 27 | private Set enumeratedTypes = new HashSet<>(); 28 | 29 | private void bootstrap() { 30 | typeToClassMapping.put("Object", TypeName.OBJECT); 31 | typeToClassMapping.put("Void", ClassName.get("java.lang", "Void")); 32 | typeToClassMapping.put("Boolean", ClassName.get("java.lang", "Boolean")); 33 | typeToClassMapping.put("Byte", ClassName.get("java.lang", "Byte")); 34 | typeToClassMapping.put("Short", ClassName.get("java.lang", "Short")); 35 | typeToClassMapping.put("Integer", ClassName.get("java.lang", "Integer")); 36 | typeToClassMapping.put("Long", ClassName.get("java.lang", "Long")); 37 | typeToClassMapping.put("Character", ClassName.get("java.lang", "Character")); 38 | typeToClassMapping.put("Float", ClassName.get("java.lang", "Float")); 39 | typeToClassMapping.put("Double", ClassName.get("java.lang", "Double")); 40 | typeToClassMapping.put("UUID", ClassName.get("java.util", "UUID")); 41 | 42 | typeToClassMapping.put("Date", ClassName.get("org.joda.time", "DateTime")); 43 | typeToClassMapping.put("String", ClassName.get("java.lang", "String")); 44 | typeToClassMapping.put("List", ClassName.get("java.util", "List")); 45 | typeToClassMapping.put("Map", ClassName.get("java.util", "Map")); 46 | typeToClassMapping.put("Set", ClassName.get("java.util", "Set")); 47 | 48 | //converters for type 49 | // for Boolean 50 | typeConverterMapping.put("Boolean", cb -> 51 | CodeBlock.builder().add("$T.parseBoolean(", typeToClassMapping.get("Boolean")).add(cb).add(")").build()); 52 | 53 | // for Byte 54 | typeConverterMapping.put("Byte", cb -> 55 | CodeBlock.builder().add("$T.parseByte(", typeToClassMapping.get("Byte")).add(cb).add(")").build()); 56 | 57 | // for Short 58 | typeConverterMapping.put("Short", cb -> 59 | CodeBlock.builder().add("$T.parseShort(", typeToClassMapping.get("Short")).add(cb).add(")").build()); 60 | 61 | // for Integer 62 | typeConverterMapping.put("Integer", cb -> 63 | CodeBlock.builder().add("$T.parseInt(", typeToClassMapping.get("Integer")).add(cb).add(")").build()); 64 | 65 | // for Float 66 | typeConverterMapping.put("Float", cb -> 67 | CodeBlock.builder().add("$T.parseFloat(", typeToClassMapping.get("Float")).add(cb).add(")").build()); 68 | 69 | // for Double 70 | typeConverterMapping.put("Double", cb -> 71 | CodeBlock.builder().add("$T.parseDouble(", typeToClassMapping.get("Double")).add(cb).add(")").build()); 72 | 73 | // for Long 74 | typeConverterMapping.put("Long", cb -> 75 | CodeBlock.builder().add("$T.parseLong(", typeToClassMapping.get("Long")).add(cb).add(")").build()); 76 | 77 | // for Character 78 | typeConverterMapping.put("Character", cb -> 79 | CodeBlock.builder().add(cb+".charAt(0)").add(")").build()); 80 | 81 | // for Date 82 | typeConverterMapping.put("Date", cb -> 83 | CodeBlock.builder().add("new $T(", typeToClassMapping.get("Date")).add(cb).add(").toDate()").build()); 84 | 85 | // for String 86 | typeConverterMapping.put("String", cb -> 87 | CodeBlock.builder().add("$L", cb).build()); 88 | 89 | // for UUID 90 | typeConverterMapping.put("UUID", cb -> 91 | CodeBlock.builder().add("$T.fromString($L", typeToClassMapping.get("UUID"), cb).add(")").build()); 92 | 93 | //Conversion for map and list will be tricky, unless we define our own serialization and use it across the board. 94 | } 95 | 96 | public TypeMapImpl() { 97 | bootstrap(); 98 | } 99 | 100 | @Override 101 | public TypeName getTypeName(String type) { 102 | Preconditions.checkArgument(StringUtils.isNotEmpty(type), "type cannot be empty or null"); 103 | 104 | TypeName typeName = typeToClassMapping.get(type); 105 | if (typeName == null) { 106 | throw new IllegalArgumentException("Invalid type: " + type); 107 | } 108 | return typeName; 109 | } 110 | 111 | @Override 112 | public void registerType(String type, TypeName typeName) { 113 | registerType(type, typeName, false); 114 | } 115 | 116 | @Override 117 | public void registerType(String type, TypeName typeName, boolean isEnum) { 118 | Preconditions.checkArgument(StringUtils.isNotEmpty(type), "type cannot be empty or null"); 119 | Preconditions.checkArgument(typeName != null, "TypeName cannot be empty or null"); 120 | 121 | if (typeToClassMapping.containsKey(type)) { 122 | throw new IllegalArgumentException(String.format("Type:%s already registered", type)); 123 | } 124 | 125 | typeToClassMapping.put(type, typeName); 126 | 127 | if (isEnum) { 128 | typeConverterMapping.put(type, cb -> 129 | CodeBlock.builder().add("$T.fromValue(", typeToClassMapping.get(type)).add(cb).add(")").build()); 130 | enumeratedTypes.add(type); 131 | } 132 | } 133 | 134 | @Override 135 | public boolean isEnumeratedType(String type) { 136 | return enumeratedTypes.contains(type); 137 | } 138 | 139 | @Override 140 | public CodeBlock getTypeConverter(String type, CodeBlock cb) { 141 | Preconditions.checkArgument(StringUtils.isNotEmpty(type), "type cannot be empty or null"); 142 | Preconditions.checkArgument(cb != null, "cb == null"); 143 | 144 | if (!typeConverterMapping.containsKey(type)) { 145 | throw new IllegalArgumentException("No converter found for type: " + type + " for code block: " + cb); 146 | } 147 | return typeConverterMapping.get(type).apply(cb); 148 | } 149 | } 150 | 151 | 152 | -------------------------------------------------------------------------------- /samples/api.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "BeerService", 3 | "namespace" : "com.cyngn.beer", 4 | "apis" : [ 5 | { 6 | "name": "create", 7 | "description": "API to create beer", 8 | "path": "/create", 9 | "http_method": "POST", 10 | "request": { 11 | "fields": [ 12 | { 13 | "name": "name", 14 | "description" : "name of the beer", 15 | "type": "string", 16 | "required": true, 17 | "validation": { 18 | "length" : { 19 | "min" : 1, 20 | "max" : 1000 21 | } 22 | } 23 | }, 24 | { 25 | "name": "quantity_in_ounces", 26 | "type": "integer", 27 | "required": true 28 | }, 29 | { 30 | "name": "price", 31 | "type": "double", 32 | "required": true 33 | }, 34 | { 35 | "name": "notes", 36 | "type": "string", 37 | "required": false, 38 | "default_value" : "These are default notes" 39 | }, 40 | { 41 | "name": "beer_type", 42 | "type": "beer_type", 43 | "required": false, 44 | "default_value" : "alcoholic" 45 | } 46 | ] 47 | } 48 | }, 49 | { 50 | "name": "get_beer", 51 | "description": "API to retrieve beer", 52 | "path": "/get", 53 | "http_method": "GET", 54 | "request": { 55 | "fields": [ 56 | { 57 | "name": "name", 58 | "type": "string", 59 | "required": true, 60 | "validation": { 61 | "length" : { 62 | "min" : 1, 63 | "max" : 1000 64 | } 65 | } 66 | } 67 | ] 68 | }, 69 | "response": { 70 | "fields": [ 71 | { 72 | "name": "name", 73 | "type": "string", 74 | "required": true 75 | }, 76 | { 77 | "name": "quantity_in_ounces", 78 | "type": "integer", 79 | "required": true 80 | }, 81 | { 82 | "name": "price", 83 | "type": "double", 84 | "required": true 85 | }, 86 | { 87 | "name": "notes", 88 | "type": "string", 89 | "required": false 90 | } 91 | ] 92 | } 93 | }, 94 | { 95 | "name": "delete", 96 | "description": "API to remove beer", 97 | "path": "/delete", 98 | "http_method": "DELETE", 99 | "request": { 100 | "fields": [ 101 | { 102 | "name": "name", 103 | "type": "string", 104 | "required": true, 105 | "validation": { 106 | "length" : { 107 | "min" : 1, 108 | "max" : 1000 109 | } 110 | } 111 | } 112 | ] 113 | } 114 | }, 115 | { 116 | "name": "ping", 117 | "description": "API to ping the server", 118 | "path": "/ping", 119 | "http_method": "POST" 120 | } 121 | ], 122 | "data_type_groups":[ 123 | { 124 | "namespace" : "com.cyngn.beer.external", 125 | "enum_types": [ 126 | { 127 | "name": "beer_type", 128 | "description": "Enumerated type for different types of beer", 129 | "values": [ 130 | { 131 | "name": "ALCOHOLIC", 132 | "value": "alcoholic" 133 | }, 134 | { 135 | "name": "NON_ALCOHOLIC", 136 | "value": "non_alcoholic" 137 | } 138 | ] 139 | } 140 | ], 141 | "class_types": [ 142 | { 143 | "name": "type_has_string", 144 | "description": "Example to show Type with String", 145 | "immutable": false, 146 | "fields": [ 147 | { 148 | "name": "name", 149 | "type": "string", 150 | "default_value": "This is default value" 151 | } 152 | ] 153 | } 154 | ] 155 | }, 156 | { 157 | "namespace" : "com.cyngn.beer.internal", 158 | "class_types": [ 159 | { 160 | "name" : "type_has_immutable_string", 161 | "description": "Example to show Immutable type with string", 162 | "immutable" : true, 163 | "fields": [ 164 | { 165 | "name": "names", 166 | "type": "list" 167 | } 168 | ] 169 | }, 170 | { 171 | "name" : "type_has_string_with_json_annotations", 172 | "description": "Example to show Type annotated with Json annotations", 173 | "immutable" : false, 174 | "json_annotations" : true, 175 | "fields": [ 176 | { 177 | "name": "names", 178 | "type": "list" 179 | } 180 | ] 181 | }, 182 | { 183 | "name" : "type_has_map", 184 | "description": "Example to show Type with map field", 185 | "immutable" : false, 186 | "fields": [ 187 | { 188 | "name": "names", 189 | "type": "map" 190 | } 191 | ] 192 | }, 193 | { 194 | "name" : "type_has_list", 195 | "description" : "Example to show Type with list field", 196 | "immutable" : false, 197 | "fields": [ 198 | { 199 | "name": "names", 200 | "type": "list" 201 | } 202 | ] 203 | }, 204 | { 205 | "name" : "type_has_map_of_map_and_string", 206 | "description" : "Example to show Type with map of map field", 207 | "immutable" : false, 208 | "fields": [ 209 | { 210 | "name": "names", 211 | "type": "map,string>" 212 | } 213 | ] 214 | }, 215 | { 216 | "name" : "type_has_map_of_list", 217 | "description": "Example to show Type with map containing list as value", 218 | "immutable" : false, 219 | "fields": [ 220 | { 221 | "name": "names", 222 | "type": "map>" 223 | } 224 | ] 225 | }, 226 | { 227 | "name" : "type_has_list_of_map", 228 | "description": "Example to show Type with list containing map", 229 | "immutable" : false, 230 | "fields": [ 231 | { 232 | "name": "names", 233 | "type": "list>" 234 | } 235 | ] 236 | }, 237 | { 238 | "name" : "type_has_list_of_list", 239 | "description": "Example to show Type with list containing list", 240 | "immutable" : false, 241 | "fields": [ 242 | { 243 | "name": "names", 244 | "type": "list>" 245 | } 246 | ] 247 | } 248 | ] 249 | } 250 | ] 251 | } 252 | -------------------------------------------------------------------------------- /src/main/java/com/cyngn/exovert/generate/server/rest/utils/RestGeneratorHelper.java: -------------------------------------------------------------------------------- 1 | package com.cyngn.exovert.generate.server.rest.utils; 2 | 3 | import com.cyngn.exovert.generate.server.rest.DataTypeSpec; 4 | import com.cyngn.exovert.generate.server.rest.InterfaceSpec; 5 | import com.cyngn.exovert.generate.server.rest.RestServerGenerator; 6 | import com.cyngn.exovert.generate.server.rest.TypeParser; 7 | import com.fasterxml.jackson.databind.ObjectMapper; 8 | import com.google.common.base.CaseFormat; 9 | import com.google.common.collect.ImmutableMap; 10 | import com.google.common.collect.ImmutableSet; 11 | import org.slf4j.Logger; 12 | import org.slf4j.LoggerFactory; 13 | 14 | import java.io.File; 15 | import java.io.FileNotFoundException; 16 | import java.io.IOException; 17 | import java.nio.file.FileSystems; 18 | import java.nio.file.Path; 19 | import java.util.Map; 20 | import java.util.Scanner; 21 | import java.util.Set; 22 | 23 | /** 24 | * Rest generator Helper methods for {@link RestServerGenerator} 25 | * 26 | * @author asarda@cyngn.com (Ajay Sarda) 9/10/15. 27 | */ 28 | public class RestGeneratorHelper { 29 | private static final Logger logger = LoggerFactory.getLogger(RestGeneratorHelper.class); 30 | private static ObjectMapper mapper = new ObjectMapper(); 31 | 32 | private static Map handlesMap = ImmutableMap.of( 33 | Constants.HTTP_METHOD_GET, Constants.HANDLE_GET, Constants.HTTP_METHOD_POST, Constants.HANDLE_POST, 34 | Constants.HTTP_METHOD_DELETE, Constants.HANDLE_DELETE); 35 | 36 | private static Set supportedHttpMethods = ImmutableSet.of( 37 | Constants.HTTP_METHOD_GET, Constants.HTTP_METHOD_POST, Constants.HTTP_METHOD_DELETE); 38 | 39 | public static String getGeneratedSourceDirectory() { 40 | return Constants.BUILD_DIRECTORY + "/" + Constants.GENERATED_SRC_DIRECTORY; 41 | } 42 | 43 | public static Path getDirectoryPath(String path) { 44 | return FileSystems.getDefault().getPath(path); 45 | } 46 | 47 | public static String getTypesNamespace(String namespace) { 48 | return namespace + "." + Constants.TYPES_PACKAGE_SUFFIX; 49 | } 50 | 51 | public static String getApiNamespace(String namespace) { 52 | return namespace + "." + Constants.API_PACKAGE_SUFFIX; 53 | } 54 | 55 | private static String getLowerCamelCaseFromSnakeCase(String str) { 56 | return CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, str.toLowerCase()); 57 | } 58 | 59 | private static String getUpperCamelCaseFromSnakeCase(String str) { 60 | return CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, str.toLowerCase()); 61 | } 62 | 63 | public static String getApiName(String api) { 64 | return Constants.API_NAME_PREFIX + getUpperCamelCaseFromSnakeCase(api) 65 | + Constants.API_NAME_SUFFIX; 66 | } 67 | 68 | public static String getApiConstantName(String api) { 69 | return api.toUpperCase().concat(Constants.API_NAME_CONSTANT_SUFFIX); 70 | } 71 | 72 | public static String getRequestObjectName(String apiName) { 73 | return CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_CAMEL, getUpperCamelCaseFromSnakeCase(apiName) + Constants.REQUEST_CLASS_SUFFIX); 74 | } 75 | 76 | public static String getRequestVariableName(String apiName) { 77 | return CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_CAMEL, RestGeneratorHelper.getRequestObjectName(apiName)); 78 | } 79 | 80 | public static String getResponseObjectName(String apiName) { 81 | return CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_CAMEL, getUpperCamelCaseFromSnakeCase(apiName) + Constants.RESPONSE_CLASS_SUFFIX); 82 | } 83 | 84 | public static String getSetMethodName(String fieldName) { 85 | //fieldname is always normalized to lowerCamel 86 | return Constants.SET_METHOD_PREFIX + CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_CAMEL, fieldName); 87 | } 88 | 89 | public static String getGetMethodName(String fieldName) { 90 | //fieldname is always normalized to lowerCamel 91 | return Constants.GET_METHOD_PREFIX + CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_CAMEL, fieldName); 92 | } 93 | 94 | public static String getFieldName(String fieldName) { 95 | return CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, fieldName.toLowerCase()); 96 | } 97 | 98 | public static String getTypeNameString(String typeName) { 99 | return CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, typeName.toLowerCase()); 100 | } 101 | 102 | public static String getTypeName(String name) { 103 | if (TypeParser.isList(name)) { 104 | return String.format("List<%s>", getTypeName(TypeParser.getListType(name))); 105 | } else if (TypeParser.isSet(name)) { 106 | return String.format("Set<%s>", getTypeName(TypeParser.getSetType(name))); 107 | } else if (TypeParser.isMap(name)){ 108 | return String.format("Map<%s,%s>", getTypeName(TypeParser.getMapKeyType(name)), 109 | getTypeName(TypeParser.getMapValueType(name))); 110 | } else { 111 | return getUpperCamelCaseFromSnakeCase(name); 112 | } 113 | } 114 | 115 | public static String getBuilderTypeName(String name){ 116 | return name + Constants.BUILDER_CLASS_SUFFIX; 117 | } 118 | 119 | public static String getBuilderVariableName(){ 120 | return Constants.BUILDER_CLASS_NAME.toLowerCase(); 121 | } 122 | 123 | public static String getHandlerName(String httpMethod) { 124 | String handlerName = handlesMap.get(httpMethod.toUpperCase()); 125 | if (handlerName == null) { 126 | throw new IllegalArgumentException("Invalid or unsupported httpMethod: " + httpMethod); 127 | } 128 | return handlerName; 129 | } 130 | 131 | public static String getHttpMethod(String httpMethod) { 132 | if (supportedHttpMethods.contains(httpMethod.toUpperCase())) { 133 | return httpMethod.toUpperCase(); 134 | } else { 135 | throw new IllegalArgumentException("Invalid or unsupported httpMethod: " + httpMethod); 136 | } 137 | } 138 | 139 | public static String getServiceClientName(String serviceName) { 140 | return serviceName + Constants.CLIENT_SUFFIX; 141 | } 142 | 143 | public static String getCallApiMethodName(String apiName) { 144 | return Constants.CALL_PREFIX + getUpperCamelCaseFromSnakeCase(apiName); 145 | } 146 | 147 | public static InterfaceSpec loadSpecFromFile(String filename) throws Exception { 148 | InterfaceSpec spec; 149 | 150 | try (Scanner scanner = new Scanner(new File(filename)).useDelimiter("\\A")) { 151 | String sconf = scanner.next(); 152 | try { 153 | spec = mapper.readValue(sconf, InterfaceSpec.class); 154 | logger.info("Successfully loaded specification file " + filename); 155 | } catch (IOException e) { 156 | logger.error("Api definition file " + sconf + " does not contain a valid JSON object"); 157 | throw e; 158 | } 159 | } catch (FileNotFoundException e) { 160 | logger.error("No api definition file found", e); 161 | throw e; 162 | } 163 | return spec; 164 | } 165 | 166 | public static DataTypeSpec loadTypeSpecFromFile(String filename) throws Exception { 167 | DataTypeSpec spec; 168 | 169 | try (Scanner scanner = new Scanner(new File(filename)).useDelimiter("\\A")) { 170 | String sconf = scanner.next(); 171 | try { 172 | spec = mapper.readValue(sconf, DataTypeSpec.class); 173 | logger.info("Successfully loaded types file " + filename); 174 | } catch (IOException e) { 175 | logger.error("Type definition file " + sconf + " does not contain a valid JSON object"); 176 | throw e; 177 | } 178 | } catch (FileNotFoundException e) { 179 | logger.error("No type definition file found", e); 180 | throw e; 181 | } 182 | return spec; 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /src/main/java/com/cyngn/exovert/generate/storage/AccessorGenerator.java: -------------------------------------------------------------------------------- 1 | package com.cyngn.exovert.generate.storage; 2 | 3 | import com.cyngn.exovert.generate.entity.EntityGeneratorHelper; 4 | import com.cyngn.exovert.util.Disk; 5 | import com.cyngn.exovert.util.GeneratorHelper; 6 | import com.cyngn.exovert.util.MetaData; 7 | import com.cyngn.exovert.util.Udt; 8 | import com.datastax.driver.core.ColumnMetadata; 9 | import com.datastax.driver.core.TableMetadata; 10 | import com.datastax.driver.mapping.Result; 11 | import com.datastax.driver.mapping.annotations.Accessor; 12 | import com.datastax.driver.mapping.annotations.Param; 13 | import com.datastax.driver.mapping.annotations.Query; 14 | import com.google.common.base.CaseFormat; 15 | import com.google.common.util.concurrent.ListenableFuture; 16 | import com.squareup.javapoet.AnnotationSpec; 17 | import com.squareup.javapoet.ClassName; 18 | import com.squareup.javapoet.JavaFile; 19 | import com.squareup.javapoet.MethodSpec; 20 | import com.squareup.javapoet.ParameterSpec; 21 | import com.squareup.javapoet.ParameterizedTypeName; 22 | import com.squareup.javapoet.TypeSpec; 23 | 24 | import javax.lang.model.element.Modifier; 25 | import java.io.IOException; 26 | import java.util.ArrayList; 27 | import java.util.Collection; 28 | import java.util.List; 29 | 30 | /** 31 | * Handles generating a Cassandra accessor class. 32 | * 33 | * @author truelove@cyngn.com (Jeremy Truelove) 12/3/15 34 | */ 35 | public class AccessorGenerator { 36 | 37 | /** 38 | * Kicks off DAL generation. 39 | * @param tables the cassandra table meta data 40 | * @throws IOException if write to file fails 41 | */ 42 | public static void generate(Collection tables) throws IOException { 43 | String namespaceToUse = MetaData.instance.getDalNamespace(); 44 | 45 | for (TableMetadata table : tables) { 46 | String rawName = CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, table.getName()); 47 | String name = rawName + "Accessor"; 48 | 49 | ClassName entityTable = ClassName.get(MetaData.instance.getTableNamespace(), rawName); 50 | 51 | TypeSpec.Builder accessorBuilder = TypeSpec.interfaceBuilder(name) 52 | .addModifiers(Modifier.PUBLIC); 53 | 54 | accessorBuilder.addAnnotation(Accessor.class); 55 | 56 | List pKeyColumns = table.getPrimaryKey(); 57 | List partitionKeyColumns = table.getPartitionKey(); 58 | 59 | int partitionKeys = partitionKeyColumns.size(); 60 | // if there is only 1 key or there's just the partition key without clustering keys 61 | if (isSingleValueKeyedTable(table)) { 62 | accessorBuilder.addMethod(generateAll(table, entityTable)); 63 | } else { 64 | // if there are clustering keys after the partition key we need to start at the partition key 65 | int count = partitionKeys > 1 ? partitionKeys : 1; 66 | while (count < pKeyColumns.size()) { 67 | accessorBuilder.addMethod(generateSpecificGet(table, entityTable, count)); 68 | count++; 69 | } 70 | } 71 | 72 | accessorBuilder.addJavadoc(GeneratorHelper.getJavaDocHeader("Accessor for Cassandra entity - {@link " + 73 | ClassName.get(MetaData.instance.getTableNamespace(), rawName) + "}", MetaData.instance.getUpdateTime())); 74 | 75 | JavaFile javaFile = JavaFile.builder(namespaceToUse, accessorBuilder.build()).build(); 76 | 77 | Disk.outputFile(javaFile); 78 | } 79 | } 80 | 81 | public static boolean isSingleValueKeyedTable(TableMetadata table) { 82 | int primaryKeyCount = table.getPrimaryKey().size(); 83 | int partitionKeyCount = table.getPartitionKey().size(); 84 | return primaryKeyCount == 1 || primaryKeyCount == partitionKeyCount; 85 | } 86 | 87 | public static List> getParametersForAccessors(TableMetadata table) { 88 | List> methodParamPermutations = new ArrayList<>(); 89 | List pKeys = table.getPrimaryKey(); 90 | int partitionKeys = table.getPartitionKey().size(); 91 | int primaryKeys = pKeys.size(); 92 | 93 | // if there are clustering keys after the partition key we need to start at the partition key 94 | int count = partitionKeys > 1 ? partitionKeys : 1; 95 | while (count < primaryKeys) { 96 | methodParamPermutations.add(getParameters(table, count)); 97 | count++; 98 | } 99 | 100 | return methodParamPermutations; 101 | } 102 | 103 | private static List getParameters(TableMetadata table, int desiredColumns) { 104 | List columns = table.getPrimaryKey(); 105 | List fields = new ArrayList<>(); 106 | for(int i = 0; i < desiredColumns; i++) { 107 | ColumnMetadata column = columns.get(i); 108 | fields.add(getSpec(column)); 109 | } 110 | 111 | return fields; 112 | } 113 | 114 | private static MethodSpec generateAll(TableMetadata table, ClassName entityTable) { 115 | String query = getBaseQuery(table); 116 | 117 | return MethodSpec.methodBuilder("getAll") 118 | .addModifiers(Modifier.ABSTRACT).addModifiers(Modifier.PUBLIC) 119 | .returns(ParameterizedTypeName.get(ClassName.get(ListenableFuture.class), 120 | ParameterizedTypeName.get(ClassName.get(Result.class), entityTable))) 121 | .addAnnotation(AnnotationSpec.builder(Query.class).addMember("value", "$S", query).build()) 122 | .build(); 123 | } 124 | 125 | private static MethodSpec generateSpecificGet(TableMetadata table, ClassName entityTable, int desiredColumns) { 126 | String query = getBaseQuery(table) + " WHERE "; 127 | MethodSpec.Builder builder = MethodSpec.methodBuilder("getAll"); 128 | 129 | List columns = table.getPrimaryKey(); 130 | for(int i = 0; i < desiredColumns; i++) { 131 | ColumnMetadata column = columns.get(i); 132 | String name = column.getName(); 133 | String newClause = name + "=:" + name; 134 | if(i != 0) { 135 | newClause = " AND " + newClause; 136 | } 137 | query += newClause; 138 | builder.addParameter(getSpec(column, true)); 139 | } 140 | 141 | return builder.addModifiers(Modifier.ABSTRACT).addModifiers(Modifier.PUBLIC) 142 | .returns(ParameterizedTypeName.get(ClassName.get(ListenableFuture.class), 143 | ParameterizedTypeName.get(ClassName.get(Result.class), entityTable))) 144 | .addAnnotation(AnnotationSpec.builder(Query.class).addMember("value", "$S", query).build()) 145 | .build(); 146 | 147 | } 148 | 149 | private static ParameterSpec getSpec(ColumnMetadata column) { 150 | return getSpec(column, false); 151 | } 152 | 153 | private static ParameterSpec getSpec(ColumnMetadata column, boolean addAnnotation) { 154 | String name = column.getName(); 155 | String paramName = CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, name); 156 | ParameterSpec.Builder param; 157 | 158 | if (Udt.instance.isUdt(column.getType())) { 159 | throw new IllegalArgumentException("We don't currently support UDT primary keys in the query string, field: " 160 | + column.getName()); 161 | } else { 162 | param = ParameterSpec.builder(EntityGeneratorHelper.getRawType(column.getType()), paramName); 163 | } 164 | 165 | if(addAnnotation) { 166 | param.addAnnotation(AnnotationSpec.builder(Param.class).addMember("value", "$S", name).build()); 167 | } 168 | 169 | return param.addModifiers(Modifier.FINAL).build(); 170 | } 171 | 172 | private static String getBaseQuery(TableMetadata table) { 173 | return String.format("SELECT * FROM %s.%s", table.getKeyspace().getName(), table.getName().toLowerCase()); 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /src/main/java/com/cyngn/exovert/generate/server/rest/TypeParser.java: -------------------------------------------------------------------------------- 1 | package com.cyngn.exovert.generate.server.rest; 2 | 3 | import com.cyngn.exovert.generate.server.rest.utils.RestGeneratorHelper; 4 | import com.google.common.base.Preconditions; 5 | import com.squareup.javapoet.ClassName; 6 | import com.squareup.javapoet.ParameterizedTypeName; 7 | import com.squareup.javapoet.TypeName; 8 | import org.apache.commons.lang.StringUtils; 9 | 10 | import java.util.regex.Matcher; 11 | import java.util.regex.Pattern; 12 | 13 | /** 14 | * Type parser to parse 15 | * 16 | * Primitives, references, list, map from type string 17 | * 18 | * Employs {@link TypeMap} to look up type. 19 | * 20 | * @author asarda@cyngn.com (Ajay Sarda) 9/17/15. 21 | */ 22 | public class TypeParser { 23 | 24 | // Pattern for map< string, string > 25 | private static final String MAP_PATTERN = "map<\\s*(\\S+)\\s*,\\s*(\\S+)\\s*>"; 26 | // Pattern for list< string > 27 | private static final String LIST_PATTERN = "list<\\s*(\\S+)\\s*>"; 28 | // Pattern for set< string > 29 | private static final String SET_PATTERN = "set<\\s*(\\S+)\\s*>"; 30 | 31 | /** 32 | * Parses the type string and return the TypeName for it 33 | * 34 | * @param type - type string 35 | * @param typeMap - type mapping 36 | * @return {@link TypeName} 37 | */ 38 | public static TypeName parse(String type, TypeMap typeMap) { 39 | Preconditions.checkArgument(StringUtils.isNotEmpty(type), "type cannot be empty or null"); 40 | Preconditions.checkArgument(typeMap != null, "typeMap == null"); 41 | 42 | if (isList(type)) { 43 | return parseList(type, typeMap); 44 | } else if (isSet(type)) { 45 | return parseSet(type, typeMap); 46 | } else if (isMap(type)) { 47 | return parseMap(type, typeMap); 48 | } else { 49 | return typeMap.getTypeName(type); 50 | } 51 | } 52 | 53 | private static TypeName parseList(String type, TypeMap typeMap) { 54 | ClassName list = (ClassName) typeMap.getTypeName("List"); 55 | 56 | TypeName elementTypeName; 57 | String elementType = getListType(type); 58 | 59 | if (isCollection(elementType)) { 60 | elementTypeName = parse(elementType, typeMap); 61 | } else { 62 | elementTypeName = typeMap.getTypeName(elementType); 63 | } 64 | 65 | return ParameterizedTypeName.get(list, elementTypeName); 66 | } 67 | 68 | private static TypeName parseSet(String type, TypeMap typeMap) { 69 | ClassName set = (ClassName) typeMap.getTypeName("Set"); 70 | 71 | TypeName elementTypeName; 72 | String elementType = RestGeneratorHelper.getTypeNameString(getSetType(type)); 73 | 74 | if (isCollection(elementType)) { 75 | elementTypeName = parse(elementType, typeMap); 76 | } else { 77 | elementTypeName = typeMap.getTypeName(elementType); 78 | } 79 | 80 | return ParameterizedTypeName.get(set, elementTypeName); 81 | } 82 | 83 | private static TypeName parseMap(String type, TypeMap typeMap) { 84 | ClassName map = (ClassName) typeMap.getTypeName("Map"); 85 | 86 | String keyType = getMapKeyType(type); 87 | String valueType = getMapValueType(type); 88 | TypeName keyTypeName; 89 | TypeName valueTypeName; 90 | 91 | if (isCollection(keyType)) { 92 | keyTypeName = parse(keyType, typeMap); 93 | } else { 94 | keyTypeName = typeMap.getTypeName(keyType); 95 | } 96 | 97 | if (isCollection(valueType)) { 98 | valueTypeName = parse(valueType, typeMap); 99 | } else { 100 | valueTypeName = typeMap.getTypeName(valueType); 101 | } 102 | 103 | return ParameterizedTypeName.get(map, 104 | keyTypeName, 105 | valueTypeName); 106 | } 107 | 108 | private static boolean isCollection(String type) { 109 | return isList(type) || isSet(type) || isMap(type); 110 | } 111 | 112 | /** 113 | * Checks if the given type string is list type 114 | * @param type - type string 115 | * @return true if type is list type 116 | */ 117 | public static boolean isList(String type) { 118 | return type.toLowerCase().startsWith("list<"); 119 | } 120 | 121 | /** 122 | * Checks if the given type string is set type 123 | * @param type - type string 124 | * @return true if type is set type 125 | */ 126 | public static boolean isSet(String type) { 127 | return type.toLowerCase().startsWith("set<"); 128 | } 129 | 130 | /** 131 | * Checks if the given type string is map type 132 | * @param type - type string 133 | * @return true if type is map type 134 | */ 135 | public static boolean isMap(String type) { 136 | return type.toLowerCase().startsWith("map<"); 137 | } 138 | 139 | /** 140 | * Gets the element type in list type 141 | * @param type - type string 142 | * @return T if the type is List<T> 143 | */ 144 | public static String getListType(String type) { 145 | Preconditions.checkArgument(StringUtils.isNotEmpty(type), "type cannot be empty or null"); 146 | 147 | if (isList(type)){ 148 | Pattern pattern = Pattern.compile(LIST_PATTERN, Pattern.CASE_INSENSITIVE); 149 | Matcher m = pattern.matcher(type); 150 | if (m.find()) { 151 | return m.group(1); 152 | } else { 153 | throw new IllegalArgumentException("Invalid element type in list"); 154 | } 155 | } 156 | throw new IllegalArgumentException("Type: " + type + " is not a list"); 157 | } 158 | 159 | /** 160 | * Gets the element type in set type 161 | * @param type - type string 162 | * @return T if the type is Set<K,V> 163 | */ 164 | public static String getSetType(String type) { 165 | Preconditions.checkArgument(StringUtils.isNotEmpty(type), "type cannot be empty or null"); 166 | 167 | if (isSet(type)){ 168 | Pattern pattern = Pattern.compile(SET_PATTERN, Pattern.CASE_INSENSITIVE); 169 | Matcher m = pattern.matcher(type); 170 | if (m.find()) { 171 | return m.group(1); 172 | } else { 173 | throw new IllegalArgumentException("Invalid element type in set"); 174 | } 175 | } 176 | throw new IllegalArgumentException("Type: " + type + " is not a set"); 177 | } 178 | 179 | /** 180 | * Gets the key type in map type 181 | * @param type - type string 182 | * @return K if the type is Map<K,V> 183 | */ 184 | public static String getMapKeyType(String type) { 185 | Preconditions.checkArgument(StringUtils.isNotEmpty(type), "type cannot be empty or null"); 186 | 187 | if (isMap(type)){ 188 | Pattern pattern = Pattern.compile(MAP_PATTERN, Pattern.CASE_INSENSITIVE); 189 | Matcher m = pattern.matcher(type); 190 | if (m.find()) { 191 | return m.group(1); 192 | } else { 193 | throw new IllegalArgumentException("Invalid types in map: " + type); 194 | } 195 | } 196 | throw new IllegalArgumentException("Type: " + type + " is not a map"); 197 | } 198 | 199 | /** 200 | * Gets the value type in map type 201 | * @param type - type String 202 | * @return V if the type is Map<K,V> 203 | */ 204 | public static String getMapValueType(String type) { 205 | Preconditions.checkArgument(StringUtils.isNotEmpty(type), "type cannot be empty or null"); 206 | 207 | if (isMap(type)){ 208 | Pattern pattern = Pattern.compile(MAP_PATTERN, Pattern.CASE_INSENSITIVE); 209 | Matcher m = pattern.matcher(type); 210 | if (m.find()) { 211 | return m.group(2); 212 | } else { 213 | throw new IllegalArgumentException("Invalid types in map: " + type); 214 | } 215 | } 216 | throw new IllegalArgumentException("Type: " + type + " is not a map"); 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /src/main/java/com/cyngn/exovert/CrudCreator.java: -------------------------------------------------------------------------------- 1 | package com.cyngn.exovert; 2 | 3 | import com.cyngn.exovert.generate.entity.TableGenerator; 4 | import com.cyngn.exovert.generate.entity.UDTGenerator; 5 | import com.cyngn.exovert.generate.project.ProjectGenerator; 6 | import com.cyngn.exovert.generate.rest.RestGenerator; 7 | import com.cyngn.exovert.generate.server.ServerGenerator; 8 | import com.cyngn.exovert.generate.storage.AccessorGenerator; 9 | import com.cyngn.exovert.generate.storage.DalGenerator; 10 | import com.cyngn.exovert.util.MetaData; 11 | import com.cyngn.exovert.util.Udt; 12 | import com.cyngn.exovert.util.VertxRef; 13 | import com.cyngn.vertx.async.Action; 14 | import com.cyngn.vertx.async.promise.Promise; 15 | import com.datastax.driver.core.Cluster; 16 | import com.datastax.driver.core.KeyspaceMetadata; 17 | import com.datastax.driver.mapping.annotations.Accessor; 18 | import com.englishtown.vertx.cassandra.impl.DefaultCassandraSession; 19 | import com.englishtown.vertx.cassandra.impl.JsonCassandraConfigurator; 20 | import io.vertx.core.Vertx; 21 | import io.vertx.core.json.JsonArray; 22 | import io.vertx.core.json.JsonObject; 23 | import joptsimple.OptionParser; 24 | import joptsimple.OptionSet; 25 | import joptsimple.OptionSpec; 26 | 27 | import java.io.IOException; 28 | import java.util.Arrays; 29 | import java.util.HashMap; 30 | import java.util.Map; 31 | import java.util.function.Consumer; 32 | 33 | import static java.util.Arrays.asList; 34 | 35 | /** 36 | * Runs the generation tool. 37 | * 38 | * @author truelove@cyngn.com (Jeremy Truelove) 5/15/15 39 | */ 40 | public class CrudCreator { 41 | 42 | private static Vertx vertx; 43 | 44 | // command line params 45 | private OptionSpec namespace; 46 | private OptionSpec db; 47 | private OptionSpec keyspace; 48 | private OptionSpec out; 49 | private OptionSpec preview; 50 | private OptionSpec create; 51 | private OptionSpec server; 52 | private OptionSpec rest; 53 | private OptionSpec gradle; 54 | private OptionSpec name; 55 | private OptionSpec help; 56 | 57 | // the primary commands users can select 58 | private Map, Action> actions; 59 | private OptionParser parser; 60 | 61 | private DefaultCassandraSession session; 62 | private OptionSet optionSet; 63 | 64 | private CrudCreator() { 65 | vertx = Vertx.vertx(); 66 | parser = getParser(); 67 | 68 | actions = new HashMap<>(); 69 | actions.put(create, this::create); 70 | actions.put(preview, this::preview); 71 | actions.put(help, () -> { 72 | try { 73 | parser.printHelpOn(System.out); 74 | } catch (Exception ex) { 75 | } 76 | }); 77 | } 78 | 79 | private void create() { 80 | init(false); 81 | execute(session.getCluster().getMetadata().getKeyspace(MetaData.instance.getKeyspace())); 82 | } 83 | 84 | private void preview() { 85 | init(true); 86 | execute(session.getCluster().getMetadata().getKeyspace(MetaData.instance.getKeyspace())); 87 | } 88 | 89 | private void execute(KeyspaceMetadata ksm) { 90 | try { 91 | UDTGenerator.generate(ksm.getUserTypes()); 92 | TableGenerator.generate(ksm.getTables()); 93 | AccessorGenerator.generate(ksm.getTables()); 94 | DalGenerator.generate(ksm.getTables()); 95 | if(optionSet.has(rest) || optionSet.has(server)) { RestGenerator.generate(ksm.getTables()); } 96 | if(optionSet.has(server)) { ServerGenerator.generate(ksm.getTables()); } 97 | if(optionSet.has(gradle)) { ProjectGenerator.generate(name.value(optionSet)); } 98 | } catch (IOException ex) { 99 | System.out.println("Generation failed: ex " + ex.getMessage()); 100 | ex.printStackTrace(); 101 | } 102 | } 103 | 104 | private void init(boolean isPreview) { 105 | String outDir = out.value(optionSet); 106 | String keySpace = keyspace.value(optionSet); 107 | String nameSpace = namespace.value(optionSet); 108 | String restPrefix = rest.value(optionSet); 109 | 110 | KeyspaceMetadata ksm = session.getCluster().getMetadata().getKeyspace(keySpace); 111 | 112 | Udt.instance.init(ksm); 113 | MetaData.instance.init(nameSpace, keySpace, !isPreview ? outDir : null, restPrefix, session); 114 | VertxRef.instance.init(vertx); 115 | } 116 | 117 | /** 118 | * Setup DB connections. 119 | */ 120 | private void initCassandra(Consumer onComplete) { 121 | String dbHost = db.value(optionSet); 122 | 123 | JsonObject config = new JsonObject() 124 | .put("seeds", new JsonArray(Arrays.asList(dbHost))) 125 | .put("query", new JsonObject().put("consistency", "LOCAL_QUORUM")) 126 | .put("reconnect", new JsonObject().put("name", "exponential").put("base_delay", 1000).put("max_delay", 1000)); 127 | 128 | System.out.println("Cassandra config to use:" + config.encode()); 129 | 130 | JsonCassandraConfigurator configurator = new JsonCassandraConfigurator(config); 131 | session = new DefaultCassandraSession(Cluster.builder(), configurator, vertx); 132 | session.onReady(result -> onComplete.accept(true)); 133 | } 134 | 135 | /** 136 | * Builds the command line parser 137 | */ 138 | private OptionParser getParser() { 139 | OptionParser parser = new OptionParser(); 140 | create = parser.acceptsAll(asList("create", "c"), "create the files on disk"); 141 | preview = parser.acceptsAll(asList("preview", "p"), "output all the java files to the console, don't create files"); 142 | name = parser.acceptsAll(asList("name"), "the optional project name") 143 | .requiredIf(gradle) 144 | .withRequiredArg() 145 | .ofType(String.class); 146 | gradle = parser.acceptsAll(asList("gradle", "g"), "create a starter gradle file"); 147 | keyspace = parser.acceptsAll(asList("keyspace", "k"), "the keyspace from which to read") 148 | .requiredIf(create, preview) 149 | .withRequiredArg() 150 | .ofType(String.class); 151 | namespace = parser.acceptsAll(asList("namespace", "n"), "the namespace for generated java classes") 152 | .requiredIf(create, preview) 153 | .withRequiredArg() 154 | .ofType(String.class); 155 | db = parser.acceptsAll(asList("db", "d"), "the db host that has the keyspace") 156 | .withRequiredArg() 157 | .defaultsTo("localhost") 158 | .ofType(String.class); 159 | out = parser.acceptsAll(asList("out", "o"), "the output dir in which to place files") 160 | .withRequiredArg() 161 | .defaultsTo("./tmp") 162 | .ofType(String.class); 163 | rest = parser.acceptsAll(asList("rest", "r"), "generate the REST API for the scheme") 164 | .withOptionalArg() 165 | .ofType(String.class); 166 | server = parser.acceptsAll(asList("server", "s"), "generate a simple server, implies the --rest option also"); 167 | help = parser.accepts("help", "shows this message").forHelp(); 168 | return parser; 169 | } 170 | 171 | /** 172 | * Handle parsing the command line args and directing the app path 173 | */ 174 | private void run(String [] args) throws IOException { 175 | optionSet = parser.parse(args); 176 | 177 | OptionSpec invalidSelection = null; 178 | Map.Entry, Action> spec = null; 179 | 180 | // figure out which primary action they selected and make sure they didn't choose two of them 181 | for(Map.Entry, Action> entry: actions.entrySet()) { 182 | if(optionSet.has(entry.getKey())) { 183 | if(spec != null) { 184 | invalidSelection = entry.getKey(); 185 | break; 186 | } 187 | spec = entry; 188 | } 189 | } 190 | 191 | if(invalidSelection != null) { 192 | System.out.println("Competing options selected - option1: " + spec.getKey() + " option2: " + 193 | invalidSelection + "\n"); 194 | parser.printHelpOn(System.out); 195 | System.exit(-1); 196 | } else if (spec != null) { 197 | final Action handler = spec.getValue(); 198 | // hand everything off to vertx from here 199 | vertx.runOnContext((Void) -> { 200 | Promise.newInstance(vertx).then((context,onComplete) -> 201 | initCassandra(success -> onComplete.accept(success))) 202 | .except(context -> System.out.println("failed, ex: " + context.getString(Promise.CONTEXT_FAILURE_KEY))) 203 | .done(context -> { handler.callback(); System.exit(0); }) 204 | .eval(); 205 | }); 206 | } else { 207 | parser.printHelpOn(System.out); 208 | System.exit(0); 209 | } 210 | } 211 | 212 | /** 213 | * Entry point. 214 | * @param args command-line args 215 | * @throws Exception runtime errors 216 | */ 217 | public static void main(String [] args) throws Exception { 218 | new CrudCreator().run(args); 219 | 220 | // keep the app alive 221 | System.in.read(); 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /src/main/java/com/cyngn/exovert/generate/server/rest/RestClientGenerator.java: -------------------------------------------------------------------------------- 1 | package com.cyngn.exovert.generate.server.rest; 2 | 3 | import com.cyngn.exovert.generate.server.rest.types.Api; 4 | import com.cyngn.exovert.generate.server.rest.utils.Constants; 5 | import com.cyngn.exovert.generate.server.rest.utils.RestGeneratorHelper; 6 | import com.cyngn.vertx.client.ServiceClient; 7 | import com.cyngn.vertx.web.JsonUtil; 8 | import com.google.common.base.Preconditions; 9 | import com.squareup.javapoet.ClassName; 10 | import com.squareup.javapoet.FieldSpec; 11 | import com.squareup.javapoet.MethodSpec; 12 | import com.squareup.javapoet.ParameterizedTypeName; 13 | import com.squareup.javapoet.TypeSpec; 14 | import io.vertx.core.Handler; 15 | import io.vertx.core.http.HttpClientResponse; 16 | import io.vertx.core.http.HttpMethod; 17 | import org.slf4j.Logger; 18 | import org.slf4j.LoggerFactory; 19 | 20 | import javax.lang.model.element.Modifier; 21 | 22 | /** 23 | * Generates the REST Client. 24 | * 25 | * Generated artifacts include Request, Response types. 26 | * 27 | * @author asarda@cyngn.com (Ajay Sarda) 9/16/15. 28 | */ 29 | public class RestClientGenerator { 30 | 31 | private static final Logger logger = LoggerFactory.getLogger(RestClientGenerator.class); 32 | private GenerationContext context; 33 | private CommonRestGenerator commonRestGenerator; 34 | 35 | private RestClientGenerator(GenerationContext context) { 36 | this.context = context; 37 | this.context.typeMap = TypeMap.create(); 38 | this.commonRestGenerator = new CommonRestGenerator(this.context); 39 | } 40 | 41 | /** 42 | * Generates Request, Response classes, types 43 | * 44 | * @throws Exception on generation failure 45 | */ 46 | public void generate() throws Exception { 47 | logger.info("Generating the REST client..."); 48 | 49 | InterfaceSpec spec = RestGeneratorHelper.loadSpecFromFile(context.specFilePath); 50 | 51 | this.context.client = true; 52 | 53 | // generate common types between server and client 54 | commonRestGenerator.generate(spec); 55 | 56 | commonRestGenerator.generateClassFromTypespec( 57 | commonRestGenerator.getPackageNamespace(context, RestGeneratorHelper.getApiNamespace(spec.namespace)), 58 | getServiceClientSpec(spec)); 59 | } 60 | 61 | /** 62 | * Generates type spec for service client 63 | * 64 | * @param spec - Interface specification 65 | * @return {@link TypeSpec} for the service client 66 | */ 67 | public TypeSpec getServiceClientSpec(InterfaceSpec spec) { 68 | Preconditions.checkArgument(spec != null, "spec == null"); 69 | Preconditions.checkArgument(spec.name != null, "Name field missing in specification"); 70 | 71 | TypeSpec.Builder serviceClientBuilder = TypeSpec.classBuilder(RestGeneratorHelper.getServiceClientName(spec.name)) 72 | .addModifiers(Modifier.PUBLIC); 73 | 74 | // add field 75 | serviceClientBuilder.addField(FieldSpec.builder(ServiceClient.class, Constants.SERVICE_CLIENT_FIELD_NAME, 76 | Modifier.PROTECTED).build()); 77 | 78 | // add field for each apis 79 | for (Api api : spec.apis) { 80 | serviceClientBuilder.addField(FieldSpec.builder(String.class, RestGeneratorHelper.getApiConstantName(api.name), 81 | Modifier.PUBLIC, Modifier.FINAL, Modifier.STATIC).initializer("$S", api.name).build()); 82 | } 83 | 84 | // add constructor 85 | MethodSpec.Builder constructorSpec = MethodSpec.constructorBuilder() 86 | .addModifiers(Modifier.PUBLIC); 87 | 88 | // add service client for now 89 | constructorSpec.addParameter(ServiceClient.class, Constants.SERVICE_CLIENT_FIELD_NAME) 90 | .addStatement("this.$1N = $1N", Constants.SERVICE_CLIENT_FIELD_NAME); 91 | 92 | serviceClientBuilder.addMethod(constructorSpec.build()); 93 | 94 | for (Api api : spec.apis) { 95 | // for each api we generate methods 96 | MethodSpec.Builder methodSpecBuilder = MethodSpec.methodBuilder( 97 | RestGeneratorHelper.getCallApiMethodName(api.name)); 98 | 99 | methodSpecBuilder.addModifiers(Modifier.PUBLIC); 100 | methodSpecBuilder.addJavadoc(api.description + "\n"); 101 | 102 | if (api.request != null) { 103 | methodSpecBuilder.addParameter(ClassName.get( 104 | commonRestGenerator.getPackageNamespace(context, RestGeneratorHelper.getTypesNamespace(spec.namespace)), 105 | RestGeneratorHelper.getRequestObjectName(api.name)) 106 | , "request"); 107 | 108 | } 109 | methodSpecBuilder.addParameter(ParameterizedTypeName.get(Handler.class, HttpClientResponse.class), 110 | "responseHandler"); 111 | 112 | methodSpecBuilder.addParameter(ParameterizedTypeName.get(Handler.class, Throwable.class), 113 | "exceptionHandler"); 114 | 115 | if (api.request != null) { 116 | methodSpecBuilder.addStatement("byte[] payload = $T.getJsonForObject(request).getBytes()", JsonUtil.class); 117 | } 118 | 119 | // api.path will vary depending on httpmethod 120 | String httpMethod = RestGeneratorHelper.getHttpMethod(api.httpMethod); 121 | 122 | if (httpMethod.equals(Constants.HTTP_METHOD_GET) 123 | || httpMethod.equals(Constants.HTTP_METHOD_DELETE) ) { 124 | if (api.request != null) { 125 | methodSpecBuilder.addStatement("long timeout = serviceClient.getTimeout($S)", api.name); 126 | methodSpecBuilder.beginControlFlow(" if (timeout > 0L)"); 127 | methodSpecBuilder.addStatement("serviceClient.call($T.$L, $S + $L, payload, timeout, responseHandler, exceptionHandler)", 128 | HttpMethod.class, httpMethod, api.path, "request.getQueryString()"); 129 | methodSpecBuilder.nextControlFlow("else"); 130 | methodSpecBuilder.addStatement("serviceClient.call($T.$L, $S + $L, payload, responseHandler, exceptionHandler)", 131 | HttpMethod.class, httpMethod, api.path, "request.getQueryString()"); 132 | methodSpecBuilder.endControlFlow(); 133 | } else { 134 | methodSpecBuilder.addStatement("long timeout = serviceClient.getTimeout($S)", api.name); 135 | methodSpecBuilder.beginControlFlow(" if (timeout > 0L)"); 136 | methodSpecBuilder.addStatement("serviceClient.call($T.$L, $S + $L, timeout, responseHandler, exceptionHandler)", 137 | HttpMethod.class, httpMethod, api.path, "request.getQueryString()"); 138 | methodSpecBuilder.nextControlFlow("else"); 139 | methodSpecBuilder.addStatement("serviceClient.call($T.$L, $S + $L, responseHandler, exceptionHandler)", 140 | HttpMethod.class, httpMethod, api.path, "request.getQueryString()"); 141 | methodSpecBuilder.endControlFlow(); 142 | } 143 | } else { 144 | if (api.request != null) { 145 | methodSpecBuilder.addStatement("long timeout = serviceClient.getTimeout($S)", api.name); 146 | methodSpecBuilder.beginControlFlow(" if (timeout > 0L)"); 147 | methodSpecBuilder.addStatement("serviceClient.call($T.$L, $S, payload, timeout, responseHandler, exceptionHandler)", 148 | HttpMethod.class, httpMethod, api.path); 149 | methodSpecBuilder.nextControlFlow("else"); 150 | methodSpecBuilder.addStatement("serviceClient.call($T.$L, $S, payload, responseHandler, exceptionHandler)", 151 | HttpMethod.class, httpMethod, api.path); 152 | methodSpecBuilder.endControlFlow(); 153 | } else { 154 | methodSpecBuilder.addStatement("long timeout = serviceClient.getTimeout($S)", api.name); 155 | methodSpecBuilder.beginControlFlow(" if (timeout > 0L)"); 156 | methodSpecBuilder.addStatement("serviceClient.call($T.$L, $S, timeout, responseHandler, exceptionHandler)", 157 | HttpMethod.class, httpMethod, api.path); 158 | methodSpecBuilder.nextControlFlow("else"); 159 | methodSpecBuilder.addStatement("serviceClient.call($T.$L, $S, responseHandler, exceptionHandler)", 160 | HttpMethod.class, httpMethod, api.path); 161 | methodSpecBuilder.endControlFlow(); 162 | } 163 | } 164 | serviceClientBuilder.addMethod(methodSpecBuilder.build()); 165 | } 166 | 167 | return serviceClientBuilder.build(); 168 | } 169 | 170 | /** 171 | * Get the instance to {@link RestClientGenerator.Builder} 172 | * 173 | * @return {@link RestClientGenerator.Builder} 174 | */ 175 | public static Builder newBuilder() { 176 | return new Builder(); 177 | } 178 | 179 | /** 180 | * Fluent Builder class to build {@link RestClientGenerator} 181 | */ 182 | public static class Builder { 183 | private GenerationContext context; 184 | 185 | public Builder() { 186 | context = new GenerationContext(); 187 | } 188 | 189 | public Builder withIsPreview(boolean isPreview) { 190 | context.preview = isPreview; 191 | return this; 192 | } 193 | 194 | public Builder withOutputDirectory(String outputDirectory) { 195 | context.outputDirectory = outputDirectory; 196 | return this; 197 | } 198 | 199 | public Builder withSpecFilePath(String specFilePath) { 200 | context.specFilePath = specFilePath; 201 | return this; 202 | } 203 | 204 | public RestClientGenerator build() { 205 | return new RestClientGenerator(context); 206 | } 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /src/main/java/com/cyngn/exovert/generate/server/ServerGenerator.java: -------------------------------------------------------------------------------- 1 | package com.cyngn.exovert.generate.server; 2 | 3 | import com.cyngn.exovert.generate.server.config.ConfTemplate; 4 | import com.cyngn.exovert.generate.server.config.LogbackTemplate; 5 | import com.cyngn.exovert.util.Disk; 6 | import com.cyngn.exovert.util.GeneratorHelper; 7 | import com.cyngn.exovert.util.MetaData; 8 | import com.cyngn.vertx.web.RestApi; 9 | import com.cyngn.vertx.web.RouterTools; 10 | import com.datastax.driver.core.Cluster; 11 | import com.datastax.driver.core.TableMetadata; 12 | import com.englishtown.vertx.cassandra.impl.DefaultCassandraSession; 13 | import com.englishtown.vertx.cassandra.impl.JsonCassandraConfigurator; 14 | import com.google.common.base.CaseFormat; 15 | import com.google.common.collect.Lists; 16 | import com.squareup.javapoet.ClassName; 17 | import com.squareup.javapoet.CodeBlock; 18 | import com.squareup.javapoet.FieldSpec; 19 | import com.squareup.javapoet.JavaFile; 20 | import com.squareup.javapoet.MethodSpec; 21 | import com.squareup.javapoet.ParameterizedTypeName; 22 | import com.squareup.javapoet.TypeSpec; 23 | import io.vertx.core.AbstractVerticle; 24 | import io.vertx.core.Future; 25 | import io.vertx.core.http.HttpServer; 26 | import io.vertx.core.json.JsonObject; 27 | import io.vertx.core.shareddata.LocalMap; 28 | import io.vertx.ext.web.Router; 29 | import io.vertx.ext.web.handler.LoggerHandler; 30 | 31 | import javax.lang.model.element.Modifier; 32 | import java.io.IOException; 33 | import java.net.InetAddress; 34 | import java.net.UnknownHostException; 35 | import java.util.Collection; 36 | import java.util.List; 37 | 38 | /** 39 | * Generates a simple CRUD Server. 40 | * 41 | * @author truelove@cyngn.com (Jeremy Truelove) 9/9/15 42 | */ 43 | public class ServerGenerator { 44 | private static String INITIALIZER_THREAD_FIELD = "INITIALIZER_THREAD_KEY"; 45 | private static String SHARED_DATA_FIELD = "SHARED_DATA_KEY"; 46 | 47 | /** 48 | * Kicks off simple server generation. 49 | * @param tables the cassandra table meta data 50 | * @throws IOException thrown if we fail to write out to disk 51 | */ 52 | public static void generate(Collection tables) throws IOException { 53 | String namespaceToUse = MetaData.instance.getNamespace(); 54 | 55 | TypeSpec.Builder serverBuilder = TypeSpec.classBuilder("Server") 56 | .addModifiers(Modifier.PUBLIC); 57 | 58 | serverBuilder.superclass(AbstractVerticle.class); 59 | 60 | addMemberVars(namespaceToUse, serverBuilder); 61 | 62 | serverBuilder.addMethod(getStartupMethod()); 63 | serverBuilder.addMethod(getIsInitializerFunc()); 64 | serverBuilder.addMethod(getBuildApi(tables)); 65 | serverBuilder.addMethod(getStartServer()); 66 | serverBuilder.addMethod(getStopMethod()); 67 | 68 | serverBuilder.addJavadoc(GeneratorHelper.getJavaDocHeader("Simple server that registers all {@link " + 69 | ClassName.get(RestApi.class) + "} for CRUD operations." + 70 | "\n\nto build: ./gradlew clean shadowJar\n" + 71 | "to run: java -jar build/libs/[project-name]-fat.jar -conf [your_conf.json]", MetaData.instance.getUpdateTime())); 72 | 73 | JavaFile javaFile = JavaFile.builder(namespaceToUse, serverBuilder.build()).build(); 74 | 75 | Disk.outputFile(javaFile); 76 | 77 | //setup the logback file the server needs to run 78 | Disk.outputFile(LogbackTemplate.TEMPLATE, "src/main/resources/logback.xml"); 79 | // output a default conf file 80 | Disk.outputFile(ConfTemplate.TEMPLATE, "conf.json"); 81 | } 82 | 83 | private static MethodSpec getStartupMethod() { 84 | return MethodSpec.methodBuilder("start") 85 | .addAnnotation(Override.class) 86 | .addModifiers(Modifier.PUBLIC) 87 | .addParameter(ParameterizedTypeName.get(Future.class, Void.class), "startedResult", Modifier.FINAL) 88 | .addStatement("$T config = config()", JsonObject.class) 89 | .addCode("\nif(!config.containsKey($S)) { stop(); }\n\n", "cassandra") 90 | .addStatement("$N = vertx.sharedData().getLocalMap($N)", "sharedData", SHARED_DATA_FIELD) 91 | .addStatement("$N.putIfAbsent($N, Thread.currentThread().getId())", "sharedData", 92 | INITIALIZER_THREAD_FIELD) 93 | .addStatement("$N = new $T($T.builder(), new $T(vertx), vertx)", "session", 94 | DefaultCassandraSession.class, Cluster.class, JsonCassandraConfigurator.class) 95 | .addStatement("$N = config.getInteger($S, 80)", "port", "port") 96 | .beginControlFlow("\nif(isInitializerThread())") 97 | .beginControlFlow("try") 98 | .addStatement("logger.info($S, $T.getLocalHost().getHostAddress(), $L)", 99 | "Starting up server... on ip: {} port: {}", InetAddress.class, "port") 100 | .nextControlFlow("catch($T ex)", UnknownHostException.class) 101 | .addStatement("logger.error($S, ex)", "Failed to get host ip address, ex: ") 102 | .addStatement("stop()") 103 | .endControlFlow() 104 | .endControlFlow() 105 | .addCode("\n") 106 | .addStatement("startServer()") 107 | .addStatement("startedResult.complete()") 108 | .build(); 109 | } 110 | 111 | private static MethodSpec getStopMethod() { 112 | return MethodSpec.methodBuilder("stop") 113 | .addAnnotation(Override.class) 114 | .addModifiers(Modifier.PUBLIC) 115 | .addStatement("logger.info($S)", "Stopping the server.") 116 | .beginControlFlow("try") 117 | .addCode("if(server != null) { server.close(); }\n") 118 | .nextControlFlow("finally") 119 | .addCode("//make sure only one thread tries to shutdown.\n") 120 | .addStatement("Long shutdownThreadId = sharedData.putIfAbsent($S, $T.currentThread().getId())", 121 | "shutdown", Thread.class) 122 | .beginControlFlow("if(shutdownThreadId == null)") 123 | .addCode("vertx.close(event ->").beginControlFlow("") 124 | .addStatement("logger.info($S)", "Vertx shutdown") 125 | .addStatement("System.exit(-1)") 126 | .endControlFlow(")") 127 | .endControlFlow() 128 | .endControlFlow() 129 | .build(); 130 | } 131 | 132 | private static MethodSpec getIsInitializerFunc() { 133 | return MethodSpec.methodBuilder("isInitializerThread") 134 | .returns(boolean.class) 135 | .addModifiers(Modifier.PUBLIC) 136 | .addStatement("return sharedData.get($N) == $T.currentThread().getId()", INITIALIZER_THREAD_FIELD, 137 | Thread.class).build(); 138 | } 139 | 140 | private static void addMemberVars(String namespace, TypeSpec.Builder builder) { 141 | builder.addField(GeneratorHelper.getLogger(namespace, "Server")); 142 | builder.addField(ParameterizedTypeName.get(LocalMap.class, String.class, Long.class), "sharedData", 143 | Modifier.PRIVATE); 144 | builder.addField(FieldSpec.builder(String.class, SHARED_DATA_FIELD, Modifier.PRIVATE, Modifier.STATIC, 145 | Modifier.FINAL) 146 | .initializer("$S", "shared_data").build()); 147 | builder.addField(FieldSpec.builder(String.class, INITIALIZER_THREAD_FIELD, Modifier.PRIVATE, Modifier.STATIC, 148 | Modifier.FINAL) 149 | .initializer("$S", "initializer_thread").build()); 150 | builder.addField(HttpServer.class, "server", Modifier.PRIVATE); 151 | builder.addField(DefaultCassandraSession.class, "session", Modifier.PRIVATE); 152 | builder.addField(int.class, "port", Modifier.PRIVATE); 153 | } 154 | 155 | private static MethodSpec getStartServer() { 156 | return MethodSpec.methodBuilder("startServer") 157 | .addModifiers(Modifier.PRIVATE) 158 | .addStatement("$N = vertx.createHttpServer()", "server") 159 | .addStatement("$T router = $T.router(vertx)", Router.class, Router.class) 160 | .addStatement("buildApi(router)") 161 | .addStatement("server.requestHandler(router::accept)") 162 | .addCode("\nserver.listen(port, $S, event -> ", "0.0.0.0") 163 | .beginControlFlow("") 164 | .beginControlFlow("if(event.failed())") 165 | .addStatement("logger.error($S, event.cause())", "Failed to start server, error: ") 166 | .addStatement("stop()") 167 | .nextControlFlow("else") 168 | .addStatement("logger.info($S, Thread.currentThread().getId())", "Thread: {} starting to handle request") 169 | .endControlFlow() 170 | .endControlFlow(")") 171 | .build(); 172 | } 173 | 174 | private static MethodSpec getBuildApi(Collection tables) { 175 | MethodSpec.Builder builder = MethodSpec.methodBuilder("buildApi") 176 | .addParameter(Router.class, "router") 177 | .addModifiers(Modifier.PRIVATE) 178 | .addStatement("$T.registerRootHandlers(router, $T.create())", RouterTools.class, 179 | LoggerHandler.class) 180 | .addCode("\n") 181 | .addCode("$T<$T> apis = $T.newArrayList(", List.class, RestApi.class, Lists.class); 182 | 183 | CodeBlock.Builder block = CodeBlock.builder(); 184 | block.indent(); 185 | boolean first = true; 186 | for(TableMetadata table : tables) { 187 | String rootClass = CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, table.getName()); 188 | ClassName apiClass = ClassName.get(MetaData.instance.getRestNamespace(), rootClass + "Api"); 189 | ClassName dalClass = ClassName.get(MetaData.instance.getDalNamespace(), rootClass + "Dal"); 190 | 191 | if (first) { 192 | block.add("\nnew $T(new $T(session))", apiClass, dalClass); 193 | first = false; 194 | } else { block.add(",\nnew $T(new $T(session))", apiClass, dalClass); } 195 | } 196 | block.unindent(); 197 | builder.addCode(block.build()) 198 | .addCode("\n") 199 | .addStatement(")") 200 | .beginControlFlow("\nfor($T api: apis)", RestApi.class) 201 | .addStatement("api.init(router)") 202 | .addCode("if(isInitializerThread()) {api.outputApi(logger);}\n") 203 | .endControlFlow(); 204 | 205 | return builder.build(); 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /src/main/java/com/cyngn/exovert/generate/entity/EntityGeneratorHelper.java: -------------------------------------------------------------------------------- 1 | package com.cyngn.exovert.generate.entity; 2 | 3 | import com.cyngn.exovert.util.MetaData; 4 | import com.cyngn.exovert.util.Udt; 5 | import com.datastax.driver.core.ColumnMetadata; 6 | import com.datastax.driver.core.DataType; 7 | import com.datastax.driver.core.TableMetadata; 8 | import com.datastax.driver.core.UserType; 9 | import com.datastax.driver.mapping.annotations.ClusteringColumn; 10 | import com.datastax.driver.mapping.annotations.Column; 11 | import com.datastax.driver.mapping.annotations.Field; 12 | import com.datastax.driver.mapping.annotations.FrozenKey; 13 | import com.datastax.driver.mapping.annotations.FrozenValue; 14 | import com.datastax.driver.mapping.annotations.PartitionKey; 15 | import com.google.common.base.CaseFormat; 16 | import com.google.common.reflect.TypeToken; 17 | import com.squareup.javapoet.AnnotationSpec; 18 | import com.squareup.javapoet.ClassName; 19 | import com.squareup.javapoet.FieldSpec; 20 | import com.squareup.javapoet.MethodSpec; 21 | import com.squareup.javapoet.ParameterizedTypeName; 22 | import com.squareup.javapoet.TypeName; 23 | 24 | import javax.lang.model.element.Modifier; 25 | import java.util.ArrayList; 26 | import java.util.Date; 27 | import java.util.List; 28 | import java.util.stream.Collectors; 29 | 30 | /** 31 | * Shared functions for generating entity code. 32 | * 33 | * @author truelove@cyngn.com (Jeremy Truelove) 8/28/15 34 | */ 35 | public class EntityGeneratorHelper { 36 | 37 | /** 38 | * Handle getting the class names for parameterized types. 39 | * 40 | * @param type the cassandra data type to extract from 41 | * @return the parameterized type result 42 | */ 43 | public static TypeResult getClassWithTypes(DataType type) { 44 | ClassName outer = getRawType(type); 45 | 46 | List generics = new ArrayList<>(); 47 | boolean hasFrozenType = false; 48 | for(DataType genericType : type.getTypeArguments()) { 49 | if(Udt.instance.isUdt(genericType)) { 50 | generics.add(MetaData.getClassNameForUdt((UserType) genericType)); 51 | if(genericType.isFrozen()) { 52 | hasFrozenType = true; 53 | } 54 | } else { 55 | generics.add(getRawType(genericType).box()); 56 | } 57 | } 58 | return new TypeResult(ParameterizedTypeName.get(outer, generics.toArray(new TypeName[generics.size()])), hasFrozenType); 59 | } 60 | 61 | /** 62 | * Gets the custom type class name in string form if the DataType passed in is a Cassandra CustomType, i.e. UDT 63 | * 64 | * @param type the DataType to check 65 | * @return the custom type class name or null if the DataType isn't a custom type 66 | */ 67 | public static String getCustomTypeName(DataType type) { 68 | String customTypeName = null; 69 | if (type instanceof DataType.CustomType) { 70 | customTypeName = ((DataType.CustomType) type).getCustomTypeClassName(); 71 | } 72 | 73 | return customTypeName; 74 | } 75 | 76 | /** 77 | * Get the raw java type of a Cassandra DataStax driver type 78 | * 79 | * @param type the column type off the java driver 80 | * @return the java poet ClassName representation of a java type 81 | */ 82 | public static ClassName getRawType(DataType type) { 83 | TypeToken typeToken = MetaData.instance.getCodecRegistry().codecFor(type).getJavaType(); 84 | 85 | ClassName className = ClassName.get(typeToken.getRawType()); 86 | // instead of Date LocalDate gets returned now by the new codec system 87 | if("LocalDate".equals(className.simpleName())) { className = ClassName.get(Date.class); } 88 | 89 | return className; 90 | } 91 | 92 | /** 93 | * Get a setter spec for a entity field. 94 | * 95 | * @param field the field name 96 | * @param type the cassandra field type 97 | * @return the setter method spec 98 | */ 99 | public static MethodSpec getSetter(String field, DataType type) { 100 | String methodRoot = CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, field); 101 | String paramName = CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, field); 102 | MethodSpec.Builder spec; 103 | 104 | if (type.getTypeArguments().size() == 0) { 105 | if(Udt.instance.isUdt(type)) { 106 | spec = MethodSpec.methodBuilder("set" + methodRoot).addParameter(MetaData.getClassNameForUdt((UserType) type), paramName); 107 | } else { 108 | spec = MethodSpec.methodBuilder("set" + methodRoot).addParameter(getRawType(type), paramName); 109 | } 110 | } else { 111 | TypeResult result = getClassWithTypes(type); 112 | spec = MethodSpec.methodBuilder("set" + methodRoot).addParameter(result.type, paramName); 113 | } 114 | spec.addModifiers(Modifier.PUBLIC).addStatement("this.$L = $L", paramName, paramName); 115 | 116 | return spec.build(); 117 | } 118 | 119 | /** 120 | * Get a getter spec for a entity field. 121 | * 122 | * @param field the field name 123 | * @param type the cassandra field type 124 | * @return the getter method spec 125 | */ 126 | public static MethodSpec getGetter(String field, DataType type) { 127 | String methodRoot = CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, field); 128 | String paramName = CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, field); 129 | MethodSpec.Builder spec; 130 | 131 | if (type.getTypeArguments().size() == 0) { 132 | if(Udt.instance.isUdt(type)) { 133 | spec = MethodSpec.methodBuilder("get" + methodRoot).returns(MetaData.getClassNameForUdt((UserType) type)); 134 | } else { 135 | spec = MethodSpec.methodBuilder("get" + methodRoot).returns(getRawType(type)); 136 | } 137 | } else { 138 | TypeResult result = getClassWithTypes(type); 139 | spec = MethodSpec.methodBuilder("get" + methodRoot).returns(result.type); 140 | } 141 | spec.addModifiers(Modifier.PUBLIC).addStatement("return $L", paramName); 142 | 143 | return spec.build(); 144 | } 145 | 146 | /** 147 | * Get a FieldSpec for an entity field. 148 | * 149 | * @param field the field name 150 | * @param type the field type 151 | * @param isUdtClass is this a UDT entity? 152 | * @return the FieldSpec representing the cassandra field 153 | */ 154 | public static FieldSpec getFieldSpec(String field, DataType type, boolean isUdtClass) { 155 | return getFieldSpec(field, type, isUdtClass, new ArrayList<>()); 156 | } 157 | 158 | /** 159 | * Get a FieldSpec for an entity field. 160 | * 161 | * @param field the field name 162 | * @param type the field type 163 | * @param isUdtClass is this a UDT entity? 164 | * @param extraAnnotations additional annotations to put on the field 165 | * @return the FieldSpec representing the cassandra field 166 | */ 167 | public static FieldSpec getFieldSpec(String field, DataType type, boolean isUdtClass, 168 | List extraAnnotations) { 169 | String fieldName = CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, field); 170 | FieldSpec.Builder spec; 171 | 172 | boolean hasFrozen = type.isFrozen(); 173 | if (type.getTypeArguments().size() == 0) { 174 | if(Udt.instance.isUdt(type)) { 175 | spec = FieldSpec.builder(MetaData.getClassNameForUdt((UserType)type), fieldName, Modifier.PUBLIC); 176 | } else { 177 | spec = FieldSpec.builder(getRawType(type), fieldName, Modifier.PUBLIC); 178 | } 179 | } else { 180 | TypeResult result = getClassWithTypes(type); 181 | hasFrozen |= result.hasFrozenType; 182 | spec = FieldSpec.builder(result.type, fieldName, Modifier.PUBLIC); 183 | } 184 | 185 | if(hasFrozen) { spec.addAnnotation(getFrozenValueAnnotation()); } 186 | if (isUdtClass) { spec.addAnnotation(getFieldAnnotation(field)); } 187 | else { spec.addAnnotation(getColumnAnnotation(field)); } 188 | spec.addAnnotation(MetaData.getJsonAnnotation(field)); 189 | 190 | for(AnnotationSpec annotationSpec : extraAnnotations) { 191 | spec.addAnnotation(annotationSpec); 192 | } 193 | 194 | return spec.build(); 195 | } 196 | 197 | /** 198 | * Get the Column annotation for a table field. 199 | * @param field the field name to put the annotation on 200 | * @return the annotation 201 | */ 202 | public static AnnotationSpec getColumnAnnotation(String field) { 203 | AnnotationSpec.Builder builder = AnnotationSpec.builder(Column.class); 204 | if(MetaData.isSnakeCase(field)) { 205 | builder.addMember("value", "name = $S", field); 206 | } 207 | return builder.build(); 208 | } 209 | 210 | /** 211 | * Get the Field annotation for a UDT field. 212 | * @param field the field name to put the annotation on 213 | * @return the annotation 214 | */ 215 | public static AnnotationSpec getFieldAnnotation(String field) { 216 | AnnotationSpec.Builder builder = AnnotationSpec.builder(Field.class); 217 | if(MetaData.isSnakeCase(field)) { 218 | builder.addMember("value", "name = $S", field); 219 | } 220 | return builder.build(); 221 | } 222 | 223 | /** 224 | * Get a FrozenKey annotation for a field. 225 | * @return the annotation 226 | */ 227 | public static AnnotationSpec getFrozenKeyAnnotation() { 228 | return AnnotationSpec.builder(FrozenKey.class).build(); 229 | } 230 | 231 | /** 232 | * Get a FrozenValue annotation for a field. 233 | * @return the annotation 234 | */ 235 | public static AnnotationSpec getFrozenValueAnnotation() { 236 | return AnnotationSpec.builder(FrozenValue.class).build(); 237 | } 238 | 239 | /** 240 | * Get the PartitionKey annotation for a Table field. 241 | * @param position the order of the field in the partition key 242 | * @return the annotation 243 | */ 244 | public static AnnotationSpec getPartitionKeyAnnotation(int position) { 245 | return AnnotationSpec.builder(PartitionKey.class).addMember("value", "$L", position).build(); 246 | } 247 | 248 | /** 249 | * Get the ClusteringColumn annotation for a Table field. 250 | * @param position the order of the field in the partition key 251 | * @return the annotation 252 | */ 253 | public static AnnotationSpec getClusteringAnnotation(int position) { 254 | return AnnotationSpec.builder(ClusteringColumn.class).addMember("value", "$L", position).build(); 255 | } 256 | 257 | /** 258 | * Given a list of class fields and the class name generate a 'toString' method. 259 | * @param fields the class fields 260 | * @param className the class name 261 | * @return a new toString method 262 | */ 263 | public static MethodSpec getToString(List fields, String className) { 264 | MethodSpec.Builder builder = MethodSpec.methodBuilder("toString") 265 | .addAnnotation(Override.class) 266 | .returns(String.class) 267 | .addModifiers(Modifier.PUBLIC); 268 | 269 | builder.addCode("return $S $L\n", className + "{", "+"); 270 | boolean first = true; 271 | for (String field : fields) { 272 | String fieldStr = field + "="; 273 | if(!first) { 274 | fieldStr = ", " + fieldStr; 275 | } else { 276 | first = false; 277 | } 278 | 279 | builder.addCode("$S + $N +\n", fieldStr, field); 280 | } 281 | builder.addStatement("$S", "}"); 282 | return builder.build(); 283 | } 284 | 285 | /** 286 | * Get the primary key for a Cassandra table 287 | * 288 | * @param table the Cassandra table 289 | * @return the names in order of the primary key fields 290 | */ 291 | public static List getPrimaryKey(TableMetadata table) { 292 | return table.getPrimaryKey().stream().map(ColumnMetadata::getName).collect(Collectors.toList()); 293 | } 294 | } 295 | -------------------------------------------------------------------------------- /src/main/java/com/cyngn/exovert/generate/storage/DalGenerator.java: -------------------------------------------------------------------------------- 1 | package com.cyngn.exovert.generate.storage; 2 | 3 | import com.cyngn.exovert.util.Disk; 4 | import com.cyngn.exovert.util.GeneratorHelper; 5 | import com.cyngn.exovert.util.MetaData; 6 | import com.cyngn.vertx.async.ResultContext; 7 | import com.datastax.driver.core.TableMetadata; 8 | import com.datastax.driver.mapping.MappingManager; 9 | import com.datastax.driver.mapping.Result; 10 | import com.englishtown.vertx.cassandra.CassandraSession; 11 | import com.englishtown.vertx.cassandra.FutureUtils; 12 | import com.englishtown.vertx.cassandra.mapping.VertxMapper; 13 | import com.englishtown.vertx.cassandra.mapping.VertxMappingManager; 14 | import com.englishtown.vertx.cassandra.mapping.impl.DefaultVertxMappingManager; 15 | import com.google.common.base.CaseFormat; 16 | import com.google.common.util.concurrent.FutureCallback; 17 | import com.google.common.util.concurrent.ListenableFuture; 18 | import com.squareup.javapoet.ClassName; 19 | import com.squareup.javapoet.FieldSpec; 20 | import com.squareup.javapoet.JavaFile; 21 | import com.squareup.javapoet.MethodSpec; 22 | import com.squareup.javapoet.ParameterSpec; 23 | import com.squareup.javapoet.ParameterizedTypeName; 24 | import com.squareup.javapoet.TypeName; 25 | import com.squareup.javapoet.TypeSpec; 26 | import com.squareup.javapoet.TypeVariableName; 27 | import io.vertx.core.Vertx; 28 | 29 | import javax.lang.model.element.Modifier; 30 | import java.io.IOException; 31 | import java.util.ArrayList; 32 | import java.util.Collection; 33 | import java.util.List; 34 | import java.util.function.Consumer; 35 | 36 | /** 37 | * Handles generating a DAL for each Cassandra Table entity. 38 | * 39 | * @author truelove@cyngn.com (Jeremy Truelove) 8/28/15 40 | */ 41 | public class DalGenerator { 42 | /** 43 | * Kicks off DAL generation. 44 | * @param tables the cassandra table meta data 45 | * @throws IOException if write to file fails 46 | */ 47 | public static void generate(Collection tables) throws IOException { 48 | String namespaceToUse = MetaData.instance.getDalNamespace(); 49 | generateDalInterface(namespaceToUse); 50 | 51 | for (TableMetadata table : tables) { 52 | String rawName = CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, table.getName()); 53 | String name = rawName + "Dal"; 54 | 55 | ClassName entityTable = ClassName.get(MetaData.instance.getTableNamespace(), rawName); 56 | 57 | TypeSpec.Builder dalBuilder = TypeSpec.classBuilder(name) 58 | .addModifiers(Modifier.PUBLIC); 59 | 60 | dalBuilder.addField(GeneratorHelper.getLogger(namespaceToUse, name)); 61 | addCassandraObjects(entityTable, dalBuilder); 62 | dalBuilder.addMethod(getConstructor(entityTable)); 63 | dalBuilder.addField(Vertx.class, "vertx", Modifier.FINAL); 64 | 65 | dalBuilder.addMethod(getSave(entityTable)); 66 | dalBuilder.addMethod(getDelete(entityTable)); 67 | dalBuilder.addMethod(getDeleteByKey(entityTable)); 68 | dalBuilder.addMethod(getEntityGet(entityTable)); 69 | 70 | addGetAllMethods(table, entityTable, dalBuilder); 71 | 72 | dalBuilder.addJavadoc(GeneratorHelper.getJavaDocHeader("DAL for Cassandra entity - {@link " + 73 | ClassName.get(MetaData.instance.getTableNamespace(), rawName) + "}", MetaData.instance.getUpdateTime())); 74 | 75 | dalBuilder.addSuperinterface(ParameterizedTypeName.get(ClassName.get(namespaceToUse, "CommonDal"), entityTable)); 76 | 77 | JavaFile javaFile = JavaFile.builder(namespaceToUse, dalBuilder.build()).build(); 78 | 79 | Disk.outputFile(javaFile); 80 | } 81 | } 82 | 83 | private static void addGetAllMethods(TableMetadata table, ClassName entityTable, TypeSpec.Builder builder) { 84 | ParameterizedTypeName callback = getOnCompleteWithListResult(entityTable); 85 | 86 | // get all use case 87 | if (AccessorGenerator.isSingleValueKeyedTable(table)) { 88 | MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("getAll") 89 | .addModifiers(Modifier.PUBLIC); 90 | methodBuilder.addParameter(callback, "onComplete", Modifier.FINAL); 91 | 92 | addParamLogging("info", methodBuilder, new ArrayList<>()); 93 | 94 | methodBuilder.addStatement("$T future = accessor.getAll()", ParameterizedTypeName.get(ListenableFuture.class), 95 | ParameterizedTypeName.get(ClassName.get(Result.class), entityTable)); 96 | 97 | methodBuilder.addStatement("$T.addCallback(future, $L, vertx)", FutureUtils.class, 98 | getGetAllResultCallback(new ArrayList<>(), entityTable)); 99 | builder.addMethod(methodBuilder.build()); 100 | } else { 101 | List> permutations = AccessorGenerator.getParametersForAccessors(table); 102 | for (List params : permutations) { 103 | MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("getAll") 104 | .addModifiers(Modifier.PUBLIC) 105 | .addJavadoc("Get all matching {@link $L}(s) by sub key.\n", entityTable); 106 | addParamLogging("info", methodBuilder, params); 107 | 108 | // add method parameters 109 | params.forEach(methodBuilder::addParameter); 110 | methodBuilder.addParameter(callback, "onComplete", Modifier.FINAL); 111 | 112 | methodBuilder.addCode("\n$T future = accessor.getAll(", ParameterizedTypeName.get(ClassName.get(ListenableFuture.class), 113 | ParameterizedTypeName.get(ClassName.get(Result.class), entityTable))); 114 | boolean first = true; 115 | for (ParameterSpec param : params) { 116 | if(first) { 117 | methodBuilder.addCode("$L", param.name); 118 | first = false; 119 | } else { methodBuilder.addCode(", $L", param.name); } 120 | } 121 | methodBuilder.addStatement(")"); 122 | 123 | methodBuilder.addStatement("$T.addCallback(future, $L, vertx)", FutureUtils.class, 124 | getGetAllResultCallback(params, entityTable)); 125 | builder.addMethod(methodBuilder.build()); 126 | } 127 | } 128 | } 129 | 130 | private static void addParamLogging(String logLevel, MethodSpec.Builder builder, List params) { 131 | String logLevelLocal = logLevel.toLowerCase(); 132 | builder.addCode("logger." + logLevelLocal + "(\"getAll -"); 133 | for(ParameterSpec param : params) { builder.addCode(" " + param.name + ": {}"); } 134 | 135 | if("error".equals(logLevelLocal)) { builder.addCode(" ex: \""); } 136 | else { builder.addCode("\""); } 137 | 138 | for(ParameterSpec param : params) { builder.addCode(", $L", param.name); } 139 | 140 | if("error".equals(logLevelLocal)) { builder.addStatement(", $L)", "error"); } 141 | else { builder.addStatement(")"); } 142 | } 143 | 144 | private static TypeSpec getGetAllResultCallback(List params, ClassName entityTable) { 145 | MethodSpec.Builder failureHandler = MethodSpec.methodBuilder("onFailure"); 146 | 147 | addParamLogging("error", failureHandler, params); 148 | failureHandler.addStatement("$L.accept(new $T(error, $S))", "onComplete", ResultContext.class, "Failed to get all."); 149 | 150 | // setup the callback 151 | TypeSpec.Builder resultCallback = TypeSpec.anonymousClassBuilder("") 152 | .addSuperinterface(ParameterizedTypeName.get(ClassName.get(FutureCallback.class), 153 | ParameterizedTypeName.get(ClassName.get(Result.class), entityTable))) 154 | .addMethod(MethodSpec.methodBuilder("onSuccess") 155 | .addAnnotation(Override.class) 156 | .addModifiers(Modifier.PUBLIC) 157 | .addParameter(ParameterizedTypeName.get(ClassName.get(Result.class), entityTable), "result") 158 | .addStatement("$L.accept(new ResultContext(true, result.all()))", "onComplete") 159 | .build()); 160 | 161 | return resultCallback.addMethod(failureHandler 162 | .addAnnotation(Override.class) 163 | .addModifiers(Modifier.PUBLIC) 164 | .addParameter(Throwable.class, "error") 165 | .build()) 166 | .build(); 167 | } 168 | 169 | /** 170 | * Create a general interface for all DALs to confirm to 171 | * @param namespace the namespace to create the interface in 172 | * @throws IOException 173 | */ 174 | private static void generateDalInterface(String namespace) throws IOException { 175 | TypeVariableName parameterizingType = TypeVariableName.get("T"); 176 | 177 | TypeSpec commonDal = TypeSpec.interfaceBuilder("CommonDal") 178 | .addModifiers(Modifier.PUBLIC) 179 | .addTypeVariable(parameterizingType) 180 | // save 181 | .addMethod(MethodSpec.methodBuilder("save") 182 | .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT) 183 | .addParameter(parameterizingType, "entity", Modifier.FINAL) 184 | .addParameter(getOnComplete(), "onComplete", Modifier.FINAL) 185 | .build()) 186 | // get 187 | .addMethod(MethodSpec.methodBuilder("get") 188 | .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT) 189 | .addParameter(getOnCompleteWithResult(parameterizingType), "onComplete", Modifier.FINAL) 190 | .varargs(true) 191 | .addParameter(Object[].class, "primaryKeys", Modifier.FINAL) 192 | .build()) 193 | // delete by key 194 | .addMethod(MethodSpec.methodBuilder("delete") 195 | .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT) 196 | .addParameter(getOnComplete(), "onComplete", Modifier.FINAL) 197 | .varargs(true) 198 | .addParameter(Object[].class, "primaryKeys", Modifier.FINAL) 199 | .build()) 200 | // delete 201 | .addMethod(MethodSpec.methodBuilder("delete") 202 | .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT) 203 | .addParameter(parameterizingType, "entity", Modifier.FINAL) 204 | .addParameter(getOnComplete(), "onComplete", Modifier.FINAL) 205 | .build()) 206 | .addJavadoc(GeneratorHelper.getJavaDocHeader("Common interface for all DAL classes", MetaData.instance.getUpdateTime())) 207 | .build(); 208 | 209 | JavaFile javaFile = JavaFile.builder(namespace, commonDal).build(); 210 | Disk.outputFile(javaFile); 211 | } 212 | 213 | private static void addCassandraObjects(ClassName tableEntity, TypeSpec.Builder builder) { 214 | builder.addField(FieldSpec.builder(CassandraSession.class, "session", Modifier.FINAL).build()); 215 | 216 | TypeName [] types = new TypeName [] {tableEntity}; 217 | builder.addField(FieldSpec.builder(ParameterizedTypeName.get(ClassName.get(VertxMapper.class), types), 218 | "mapper", Modifier.FINAL).build()); 219 | 220 | builder.addField(ClassName.get(MetaData.instance.getDalNamespace(), tableEntity.simpleName() + "Accessor"), 221 | "accessor", Modifier.FINAL); 222 | } 223 | 224 | private static MethodSpec getConstructor(ClassName tableEntity) { 225 | 226 | ClassName accessorClass = ClassName.get(MetaData.instance.getDalNamespace(), tableEntity.simpleName() + "Accessor"); 227 | 228 | return MethodSpec.constructorBuilder() 229 | .addModifiers(Modifier.PUBLIC) 230 | .addParameter(CassandraSession.class, "session", Modifier.FINAL) 231 | .addStatement("this.$N = $N", "session", "session") 232 | .addStatement("$T manager = new $T(session)", VertxMappingManager.class, DefaultVertxMappingManager.class) 233 | .addStatement("mapper = manager.mapper($T.class)", tableEntity) 234 | .addStatement("$T accessorMappingManager = new $T(session.getSession())", MappingManager.class, MappingManager.class) 235 | .addStatement("accessor = accessorMappingManager.createAccessor($T.class)", accessorClass) 236 | .addStatement("vertx = session.getVertx()") 237 | .build(); 238 | } 239 | 240 | private static MethodSpec getSave(ClassName tableEntity) { 241 | return getEntitySaveOrDelete(tableEntity, "save"); 242 | } 243 | 244 | private static MethodSpec getDelete(ClassName tableEntity) { 245 | return getEntitySaveOrDelete(tableEntity, "delete"); 246 | } 247 | 248 | private static MethodSpec getDeleteByKey(ClassName tableEntity) { 249 | TypeName aVoid = ClassName.get(Void.class); 250 | String simpleName = tableEntity.simpleName(); 251 | 252 | // setup the callback 253 | TypeSpec resultCallback = TypeSpec.anonymousClassBuilder("") 254 | .addSuperinterface(ParameterizedTypeName.get(ClassName.get(FutureCallback.class), aVoid)) 255 | .addMethod(MethodSpec.methodBuilder("onSuccess") 256 | .addAnnotation(Override.class) 257 | .addModifiers(Modifier.PUBLIC) 258 | .addParameter(aVoid, "result") 259 | .addStatement("$L.accept(new ResultContext(true))", "onComplete") 260 | .build()) 261 | .addMethod(MethodSpec.methodBuilder("onFailure") 262 | .addAnnotation(Override.class) 263 | .addModifiers(Modifier.PUBLIC) 264 | .addParameter(Throwable.class, "error") 265 | .addStatement("logger.error(\"delete - {}, ex: \", $L, $L)", "primaryKey", "error") 266 | .addStatement("$L.accept(new ResultContext(error, $L))", "onComplete", 267 | "\"Failed to delete " + simpleName + " by key: \" + primaryKey") 268 | .build()) 269 | .build(); 270 | 271 | return MethodSpec.methodBuilder("delete") 272 | .addModifiers(Modifier.PUBLIC) 273 | .addParameter(getOnComplete(), "onComplete", Modifier.FINAL) 274 | .addParameter(Object[].class, "primaryKey", Modifier.FINAL) 275 | .varargs(true) 276 | .addStatement("logger.info(\"delete - {}\", $L)", "primaryKey") 277 | .addCode("\n") 278 | .addCode("mapper.deleteAsync($L, $L", resultCallback, "primaryKey") 279 | .addCode(");\n") 280 | .addJavadoc("Delete a {@link $L} object by key.\n", tableEntity) 281 | .build(); 282 | } 283 | 284 | private static MethodSpec getEntitySaveOrDelete(ClassName tableEntity, String method) { 285 | String entityParamName = getEntityParam(tableEntity); 286 | String simpleName = tableEntity.simpleName(); 287 | 288 | // setup the callback 289 | TypeSpec resultCallback = TypeSpec.anonymousClassBuilder("") 290 | .addSuperinterface(ParameterizedTypeName.get(FutureCallback.class, Void.class)) 291 | .addMethod(MethodSpec.methodBuilder("onSuccess") 292 | .addAnnotation(Override.class) 293 | .addModifiers(Modifier.PUBLIC) 294 | .addParameter(Void.class, "result") 295 | .addStatement("$L.accept(new ResultContext(true))", "onComplete") 296 | .build()) 297 | .addMethod(MethodSpec.methodBuilder("onFailure") 298 | .addAnnotation(Override.class) 299 | .addModifiers(Modifier.PUBLIC) 300 | .addParameter(Throwable.class, "error") 301 | .addStatement("logger.error(\"$L - {}, ex: \", $L, $L)", method, entityParamName, "error") 302 | .addStatement("$L.accept(new ResultContext(error, $L))", "onComplete", 303 | "\"Failed to " + method + " " + simpleName + ": \" + " + entityParamName) 304 | .build()) 305 | .build(); 306 | 307 | 308 | return MethodSpec.methodBuilder(method) 309 | .addModifiers(Modifier.PUBLIC) 310 | .addParameter(tableEntity, entityParamName, Modifier.FINAL) 311 | .addParameter(getOnComplete(), "onComplete", Modifier.FINAL) 312 | .addStatement("logger.info(\"$L - {}\", $L)", method, entityParamName) 313 | .addCode("\n") 314 | .addCode("mapper.$LAsync($L, $L", method, entityParamName, resultCallback) 315 | .addCode(");\n") 316 | .addJavadoc("$L a {@link $L} object.\n", CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_CAMEL, method), 317 | tableEntity) 318 | .build(); 319 | } 320 | 321 | private static MethodSpec getEntityGet(ClassName tableEntity) { 322 | String simpleName = tableEntity.simpleName(); 323 | 324 | // setup the callback 325 | TypeSpec resultCallback = TypeSpec.anonymousClassBuilder("") 326 | .addSuperinterface(ParameterizedTypeName.get(ClassName.get(FutureCallback.class), tableEntity)) 327 | .addMethod(MethodSpec.methodBuilder("onSuccess") 328 | .addAnnotation(Override.class) 329 | .addModifiers(Modifier.PUBLIC) 330 | .addParameter(tableEntity, "result") 331 | .addStatement("$L.accept(new ResultContext(true, result))", "onComplete") 332 | .build()) 333 | .addMethod(MethodSpec.methodBuilder("onFailure") 334 | .addAnnotation(Override.class) 335 | .addModifiers(Modifier.PUBLIC) 336 | .addParameter(Throwable.class, "error") 337 | .addStatement("logger.error(\"get - {}, ex: \", $L, $L)", "primaryKey", "error") 338 | .addStatement("$L.accept(new ResultContext(error, $L))", "onComplete", 339 | "\"Failed to get " + simpleName + " by key: \" + primaryKey") 340 | .build()) 341 | .build(); 342 | 343 | return MethodSpec.methodBuilder("get") 344 | .addModifiers(Modifier.PUBLIC) 345 | .addParameter(getOnCompleteWithResult(tableEntity), "onComplete", Modifier.FINAL) 346 | .addParameter(Object[].class, "primaryKey", Modifier.FINAL) 347 | .varargs(true) 348 | .addStatement("logger.info(\"get - {}\", $L)", "primaryKey") 349 | .addCode("\n") 350 | .addCode("mapper.getAsync($L, $L", resultCallback, "primaryKey") 351 | .addCode(");\n") 352 | .addJavadoc("Get a {@link $L} object by primary key.\n", tableEntity) 353 | .build(); 354 | } 355 | 356 | private static String getEntityParam(ClassName tableEntity) { 357 | // rename so the variable doesn't collide with any key words 358 | return CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_CAMEL, tableEntity.simpleName()) + "Obj"; 359 | } 360 | 361 | private static ParameterizedTypeName getOnComplete() { 362 | return ParameterizedTypeName.get(Consumer.class, ResultContext.class); 363 | } 364 | 365 | private static ParameterizedTypeName getOnCompleteWithResult(TypeName type) { 366 | return ParameterizedTypeName.get(ClassName.get(Consumer.class), 367 | ParameterizedTypeName.get(ClassName.get(ResultContext.class), type)); 368 | } 369 | 370 | private static ParameterizedTypeName getOnCompleteWithListResult(TypeName type) { 371 | return ParameterizedTypeName.get(ClassName.get(Consumer.class), 372 | ParameterizedTypeName.get(ClassName.get(ResultContext.class), 373 | ParameterizedTypeName.get(ClassName.get(List.class), type))); 374 | } 375 | } 376 | --------------------------------------------------------------------------------