Swagr code-gen: Refactoring tasks, imports configurable, enum classes generation, sets handled and allowMultiple handling for params

This commit is contained in:
Deepak Michael 2011-07-25 12:03:18 +05:30
parent ad1a670bef
commit 18b06fd8c3
17 changed files with 332 additions and 45 deletions

View File

@ -24,6 +24,9 @@
</fileset>
</classpath>
</java>
<copy todir="../java/src/main/java/" overwrite="true">
<fileset dir="../java/src/lang/java"/>
</copy>
</target>
</project>

View File

@ -0,0 +1,30 @@
package $packageName$;
$imports:{ import |
import $import$;
}$
/**
* $enum.description$
* NOTE: This class is auto generated by the drive code generator program so please do not edit the program manually.
* @author deepak
*
*/
public enum $className$ {
$values: { value | $value.name$($value.value$)};separator=", "$;
final $enumValueType$ value;
$className$($enumValueType$ value) {
this.value = value;
}
public $enumValueType$ getValue() {
return value;
}
@Override public String toString() {
return String.valueOf(this.getValue());
}
};

View File

@ -1,7 +1,7 @@
package com.wordnik.model;
package $packageName$;
import com.wordnik.annotations.AllowableValues;
import com.wordnik.annotations.Required;
import $annotationPackageName$.AllowableValues;
import $annotationPackageName$.Required;
$imports:{ import |
import $import$;

View File

@ -1,16 +1,16 @@
package com.wordnik.api;
package $packageName$;
import com.wordnik.common.*;
import com.wordnik.common.ext.*;
import com.wordnik.exception.APIExceptionCodes;
import com.wordnik.exception.APIException;
import com.wordnik.model.*;
import java.util.*;
import com.wordnik.annotations.MethodArgumentNames;
import $annotationPackageName$.MethodArgumentNames;
import $exceptionPackageName$.APIExceptionCodes;
import $exceptionPackageName$.APIException;
import $modelPackageName$.*;
import org.codehaus.jackson.map.DeserializationConfig.Feature;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.type.TypeReference;
import java.util.*;
import java.io.IOException;
$imports:{ import |
@ -57,7 +57,7 @@ $endif$
$if(!method.inputModel)$
$method.queryParameters:{ argument |
if( $argument.name$ != null) {
queryParams.put("$argument.name$", $argument.name$);
queryParams.put("$argument.name$", toPathValue($argument.name$));
}
}$
$method.pathParameters:{ argument |
@ -119,7 +119,34 @@ $if(method.returnValueList)$
$endif$
$endif$
}
}$
}$
/**
* Overloaded method for returning the path value
* For a string value an empty value is returned if the value is null
* @param value
* @return
*/
private static String toPathValue(String value) {
return value == null ? "" : value;
}
/**
* Overloaded method for returning a path value
* For a list of objects a comma separated string is returned
* @param objects
* @return
*/
private static String toPathValue(List objects) {
StringBuilder out = new StringBuilder();
for(Object o: objects){
out.append(o.toString());
out.append(",");
}
if(out.indexOf(",") != -1) {
return out.substring(0, out.lastIndexOf(",") );
}
return out.toString();
}
}

View File

@ -1,4 +1,4 @@
package com.wordnik.api;
package $packageName$;
/**
* Maintains the compatible server version against which the drive is written

View File

@ -32,11 +32,13 @@ public class DriverCodeGenerator {
private static String VERSION_OBJECT_TEMPLATE = "VersionChecker";
private static String MODEL_OBJECT_TEMPLATE = "ModelObject";
private static String API_OBJECT_TEMPLATE = "ResourceObject";
public static final String API_CONFIG_LOCATION = "conf/apiConfig.xml";
private static final String ENUM_OBJECT_TEMPLATE = "EnumObject";
private static final String API_CONFIG_LOCATION = "conf/apiConfig.xml";
private static final String API_URL_CONFIG = "apiUrl";
private static final String API_KEY = "apiKey";
private static final String API_LISTING_URL = "apiListResource";
private static final String PACKAGE_NAME = "packageName";
private CodeGenConfig config = null;
private GenerationEnvironmentConfig envConfig = null;
private String baseUrl;
@ -72,6 +74,7 @@ public class DriverCodeGenerator {
}
generateModelClasses(resources, aTemplateGroup);
generateModelClassesForInput(resources, aTemplateGroup);
generateEnumForAllowedValues(resources, aTemplateGroup);
generateAPIClasses(resources, aTemplateGroup);
}
@ -212,23 +215,24 @@ public class DriverCodeGenerator {
/**
* Generates version file based on the version number received from the doc calls. This version file is used
* while making the API calls to make sure Client and back end are compatible.
* while making the API calls to make sure Client and back end are compatible.
* @param version
*/
private void generateVersionHelper(String version, StringTemplateGroup templateGroup) {
StringTemplate template = templateGroup.getInstanceOf(VERSION_OBJECT_TEMPLATE);
template.setAttribute("apiVersion", version);
template.setAttribute(PACKAGE_NAME, config.getApiPackageName());
File aFile = new File(envConfig.getResourceClassLocation() + config.getNameGenerator().getVersionCheckerClassName()
+ config.getClassFileExtension());
writeFile(aFile, template.toString(), "Version checker class");
}
/**
* Generates model classes. If the class is already generated then ignores the same.
*/
private void generateModelClasses(List<Resource> resources, StringTemplateGroup templateGroup) {
List<String> generatedClassNames = new ArrayList();
for(Resource resource: resources) {
for(Model model : resource.getModels()){
if(!generatedClassNames.contains(model.getName()) && !config.getCodeGenOverridingRules().isModelIgnored(model.getName())){
@ -244,20 +248,22 @@ public class DriverCodeGenerator {
StringTemplate template = templateGroup.getInstanceOf(MODEL_OBJECT_TEMPLATE);
template.setAttribute("fields", model.getFields());
template.setAttribute("imports", imports);
template.setAttribute("annotationPackageName", config.getAnnotationPackageName());
template.setAttribute("extends", config.getCodeGenOverridingRules().getModelExtendingClass());
template.setAttribute("className", model.getGenratedClassName());
template.setAttribute(PACKAGE_NAME, config.getModelPackageName());
File aFile = new File(envConfig.getModelClassLocation()+model.getGenratedClassName()+config.getClassFileExtension());
writeFile(aFile, template.toString(), "Model class");
generatedClassNames.add(model.getName());
}
}
}
generateWrapperClassForTestData(generatedClassNames, templateGroup);
}
}
/**
* Generates assembler classes if the API returns more than one objects.
* Generates assembler classes if the API returns more than one objects.
* @param resources
* @param templateGroup
*/
@ -287,11 +293,13 @@ public class DriverCodeGenerator {
template.setAttribute("fields", model.getFields());
template.setAttribute("imports", imports);
template.setAttribute("extends", config.getCodeGenOverridingRules().getModelExtendingClass());
template.setAttribute("annotationPackageName", config.getAnnotationPackageName());
template.setAttribute("className", model.getGenratedClassName());
template.setAttribute(PACKAGE_NAME, config.getModelPackageName());
File aFile = new File(envConfig.getModelClassLocation()+model.getGenratedClassName()+config.getClassFileExtension());
writeFile(aFile, template.toString(), "Input model class");
generatedClasses.add(model.getName());
}
}
}
}
}
@ -299,15 +307,71 @@ public class DriverCodeGenerator {
}
}
}
}
}
/**
* Generates one API class for each resource and each end point in the resource is translated as method.
* Generates an Enum class for method params that have an allowed values list.
* @param resources
* @param templateGroup
*/
private void generateEnumForAllowedValues(List<Resource> resources, StringTemplateGroup templateGroup) {
List<String> generatedEnums = new ArrayList<String>();
StringTemplate template;
String valuePrefix, valueSuffix = "";
String enumName;
for(Resource resource: resources) {
if(resource.getEndPoints() != null) {
for(Endpoint endpoint : resource.getEndPoints()){
if(endpoint.getOperations() != null) {
for(EndpointOperation operation : endpoint.getOperations()){
//ResourceMethod method = operation.generateMethod(endpoint, resource, config);
if(operation.getParameters() != null){
for(ModelField operationParam : operation.getParameters()){
//skipping the case where there is just one item - TODO process case of allowableValue like '0 to 1000'
if(operationParam.getAllowableValues() != null && operationParam.getAllowableValues().size() > 1) {
if(!generatedEnums.contains(operationParam.getName())){
//generate enum
template = templateGroup.getInstanceOf(ENUM_OBJECT_TEMPLATE);
List<String> imports = new ArrayList<String>();
imports.addAll(this.config.getDefaultModelImports());
enumName = config.getNameGenerator().getEnumName(operationParam.getName());
template.setAttribute("className", enumName);
template.setAttribute("description", operationParam.getDescription());
template.setAttribute("enumValueType", config.getDataTypeMapper().getObjectType(operationParam.getDataType(), true));
for (String allowableValue : operationParam.getAllowableValues()) {
if(operationParam.getDataType().equalsIgnoreCase("string")){
valuePrefix = valueSuffix = "\"";
}
else{
valuePrefix = valueSuffix = "";
};
template.setAttribute("values.{name,value}",
config.getNameGenerator().applyClassNamingPolicy(allowableValue.replaceAll("-","_")),
config.getNameGenerator().applyMethodNamingPolicy(valuePrefix.concat(allowableValue).concat(valueSuffix)));
}
template.setAttribute(PACKAGE_NAME, config.getModelPackageName());
File aFile = new File(envConfig.getModelClassLocation()+enumName+config.getClassFileExtension());
writeFile(aFile, template.toString(), "Enum class");
generatedEnums.add(operationParam.getName());
}
}
}
}
}
}
}
}
}
}
/**
* Generates one API class for each resource and each end point in the resource is translated as method.
* @param resources
* @param templateGroup
*/
private void generateAPIClasses(List<Resource> resources, StringTemplateGroup templateGroup) {
for(Resource resource : resources) {
List<ResourceMethod> methods = new ArrayList<ResourceMethod>();
List<String> imports = new ArrayList<String>();
@ -322,6 +386,10 @@ public class DriverCodeGenerator {
}
}
template.setAttribute("imports", imports);
template.setAttribute(PACKAGE_NAME, config.getApiPackageName());
template.setAttribute("annotationPackageName", config.getAnnotationPackageName());
template.setAttribute("modelPackageName", config.getModelPackageName());
template.setAttribute("exceptionPackageName", config.getExceptionPackageName());
template.setAttribute("resource", className);
template.setAttribute("methods", filteredMethods);
template.setAttribute("extends", config.getCodeGenOverridingRules().getServiceExtendingClass(className));
@ -330,7 +398,7 @@ public class DriverCodeGenerator {
writeFile(aFile, template.toString(), "API CLasses");
}
}
/**
* Creates a wrapper model class that contains all model classes as list of objects.
* This class is used for storing test data
@ -367,7 +435,9 @@ public class DriverCodeGenerator {
StringTemplate template = templateGroup.getInstanceOf(MODEL_OBJECT_TEMPLATE);
template.setAttribute("fields", model.getFields());
template.setAttribute("imports", imports);
template.setAttribute("annotationPackageName", config.getAnnotationPackageName());
template.setAttribute("extends", config.getCodeGenOverridingRules().getModelExtendingClass());
template.setAttribute(PACKAGE_NAME, config.getModelPackageName());
template.setAttribute("className", model.getGenratedClassName());
File aFile = new File(envConfig.getModelClassLocation()+model.getGenratedClassName()+config.getClassFileExtension());
writeFile(aFile, template.toString(), "Wrapper class for test data file");

View File

@ -28,6 +28,14 @@ public class CodeGenConfig {
*/
private List<String> defaultServiceImports;
private String modelPackageName;
private String apiPackageName;
private String exceptionPackageName;
private String annotationPackageName;
private CodeGenOverridingRules codeGenOverridingRules;
private DataTypeMapper dataTypeMapper;
@ -81,4 +89,36 @@ public class CodeGenConfig {
public void setNameGenerator(ServiceAndMethodNameGenerator nameGenerator) {
this.nameGenerator = nameGenerator;
}
public String getExceptionPackageName() {
return exceptionPackageName;
}
public void setExceptionPackageName(String exceptionPackageName) {
this.exceptionPackageName = exceptionPackageName;
}
public String getAnnotationPackageName() {
return annotationPackageName;
}
public void setAnnotationPackageName(String annotationPackageName) {
this.annotationPackageName = annotationPackageName;
}
public String getModelPackageName() {
return modelPackageName;
}
public void setModelPackageName(String modelPackageName) {
this.modelPackageName = modelPackageName;
}
public String getApiPackageName() {
return apiPackageName;
}
public void setApiPackageName(String apiPackageName) {
this.apiPackageName = apiPackageName;
}
}

View File

@ -35,7 +35,7 @@ public interface DataTypeMapper {
/**
* Signature that should be used when returning list of given object type.
*
* Example: in java this output will look as <Code> List<User> </Code> for methods that returns list of user objects
* Example: in java this output will look as <Code> List<User> </Code> for methods that returns a list of user objects
* @param typeClass of class that list object contains.
* @return
*/
@ -44,12 +44,21 @@ public interface DataTypeMapper {
/**
* Signature that should be used when returning map of given object type.
*
* Example: in java this output will look as <Code> Map<User> </Code> for methods that returns list of user objects
* Example: in java this output will look as <Code> Map<User> </Code> for methods that returns maps
* @param typeClass of class that list object contains.
* @return
*/
public String getMapReturnTypeSignature(String typeClass);
/**
* Signature that should be used when returning set of given object type.
*
* Example: in java this output will look as <Code> Set<User> </Code> for methods that returns a set of user objects
* @param typeClass of class that the set object contains.
* @return
*/
public String getSetReturnTypeSignature(String typeClass);
/**
* Initialization need for list objects. Example. If it is java list the initialization will look as
*
@ -63,7 +72,7 @@ public interface DataTypeMapper {
public String generateListInitialization(String typeClass);
/**
* Initialization need for map objects. Example. If it is java list the initialization will look as
* Initialization need for map objects. Example. If it is java map the initialization will look as
*
* <Code>
* new HashMap<ClassName>()
@ -74,6 +83,18 @@ public interface DataTypeMapper {
*/
public String generateMapInitialization(String typeClass);
/**
* Initialization need for set objects. Example. If it is java set the initialization will look as
*
* <Code>
* new HashSet<ClassName>()
* </Code>
*
* @param typeClass
* @return
*/
public String generateSetInitialization(String typeClass);
/**
* Gets list of imports that needs to be included when used objects of type List.
*
@ -102,6 +123,20 @@ public interface DataTypeMapper {
*/
public List<String> getMapImportPackages();
/**
* Gets list of imports that needs to be included when used objects of type Set.
*
* Example: in java while using sets we use an interface of <Code>Set</Code> and implementation of
* <Code>HashSet</Code>. SO the output will as follows:
* <Code>
* List<String> imports = new ArrayList<String>();
imports.add("java.util.Set");
imports.add("java.util.HashSet");
* </Code>
* @return
*/
public List<String> getSetImportPackages();
/**
* Gets list of imports that needs to be included when used objects of type Date.
*

View File

@ -7,11 +7,11 @@ package com.wordnik.codegen.config;
*/
public class GenerationEnvironmentConfig {
private String templateLocation; //lang config
private String templateLocation;
private String modelClassLocation; //output config
private String modelClassLocation;
private String resourceClassLocation; //output config
private String resourceClassLocation;
public String getTemplateLocation() {
return templateLocation;

View File

@ -81,6 +81,16 @@ public interface ServiceAndMethodNameGenerator {
*/
public String getInputObjectName(String serviceName, String resourcePath);
/**
* Generates a name for an enum for the param or field name.
*
* Example: for a param source the return could be SourceEnum
*
* @param input
* @return
*/
public String getEnumName(String input);
/**
* Gets the signature of the method that gets value for give attribute name.
*

View File

@ -21,8 +21,14 @@ public class JavaCodeGenConfig extends CodeGenConfig {
defaultModelImports.add("com.wordnik.common.WordnikObject");
List<String> defaultServiceImports = new ArrayList<String>();
defaultServiceImports.add("com.wordnik.model.Long");
defaultServiceImports.add("com.wordnik.common.*");
defaultServiceImports.add("com.wordnik.common.ext.*");
this.setDefaultModelImports(defaultModelImports);
this.setDefaultServiceImports(defaultServiceImports);
this.setModelPackageName("com.wordnik.model");
this.setApiPackageName("com.wordnik.api");
this.setExceptionPackageName("com.wordnik.exception");
this.setAnnotationPackageName("com.wordnik.annotations");
this.setCodeGenOverridingRules(new JavaCodeGenPverridingRules());
this.setDataTypeMapper(new JavaDataTypeMapper());
this.setNameGenerator(new JavaServiceAndMethodNameGenerator());

View File

@ -90,6 +90,10 @@ public class JavaDataTypeMapper implements DataTypeMapper {
return "Map<"+nameGenerator.applyClassNamingPolicy(typeClass)+">";
}
public String getSetReturnTypeSignature(String typeClass) {
return "Set<"+nameGenerator.applyClassNamingPolicy(typeClass)+">";
}
public String generateListInitialization(String typeClass) {
return " new ArrayList<"+nameGenerator.applyClassNamingPolicy(typeClass)+">()";
}
@ -98,6 +102,10 @@ public class JavaDataTypeMapper implements DataTypeMapper {
return " new HashMap<"+nameGenerator.applyClassNamingPolicy(typeClass)+">()";
}
public String generateSetInitialization(String typeClass) {
return " new HashSet<"+nameGenerator.applyClassNamingPolicy(typeClass)+">()";
}
public List<String> getListImportPackages() {
List<String> imports = new ArrayList<String>();
imports.add("java.util.List");
@ -112,6 +120,12 @@ public class JavaDataTypeMapper implements DataTypeMapper {
return imports;
}
public List<String> getSetImportPackages() {
List<String> imports = new ArrayList<String>();
imports.add("java.util.Set");
imports.add("java.util.HashSet");
return imports; }
public List<String> getDateImports() {
List<String> imports = new ArrayList<String>();
@ -134,6 +148,9 @@ public class JavaDataTypeMapper implements DataTypeMapper {
}else if (type.startsWith("Map[")) {
classShortName = type.substring(4, type.length()-1);
classShortName = getObjectType(classShortName, true);
}else if (type.startsWith("Set[")) {
classShortName = type.substring(4, type.length()-1);
classShortName = getObjectType(classShortName, true);
}else if (type.equals("ok")) {
classShortName = "void";
}else{
@ -143,9 +160,10 @@ public class JavaDataTypeMapper implements DataTypeMapper {
}
/**
* Gets the short name of the class the class.
* Input can be MAP, LIST or regular string. In case of map or list the class name will be class name
* Gets the class of the expected return value for a type string. Examples of type Strings are int, User, List[User]
* If the type string is a collection type like a map or list the string value returned would be the class
* that map or list is returning.
*
* @param type
* @return
*/
@ -159,7 +177,10 @@ public class JavaDataTypeMapper implements DataTypeMapper {
classShortName = "List<"+getObjectType(classShortName, true)+">";
}else if (type.startsWith("Map[")) {
classShortName = type.substring(4, type.length()-1);
classShortName = "List<"+getObjectType(classShortName, true) +">";
classShortName = "Map<"+getObjectType(classShortName, true) +">";
}else if (type.startsWith("Set[")) {
classShortName = type.substring(4, type.length()-1);
classShortName = "Set<"+getObjectType(classShortName, true) +">";
}else{
classShortName = getObjectType(type, true);
}

View File

@ -100,6 +100,22 @@ public class JavaServiceAndMethodNameGenerator implements ServiceAndMethodNameGe
return inputobjectName;
}
/**
* Generates a name for an enum for the param or field name.
* <p/>
* Example: for a param source the return could be SourceEnum
*
* @param input
* @return
*/
public String getEnumName(String input) {
if (input != null && input.length() > 0) {
return this.applyClassNamingPolicy(input).concat("Values");
} else {
throw new CodeGenerationException("Error getting Enum name becuase of null input");
}
}
/**
* Gets the signature of the method that gets value for give attribute name.
*

View File

@ -244,6 +244,11 @@ public class EndpointOperation {
arguments.add(anArgument);
method.setPostObject(true);
}
if(modelField.isAllowMultiple() && config.getDataTypeMapper().isPrimitiveType(modelField.getDataType())){
anArgument.setDataType(config.getDataTypeMapper().getListReturnTypeSignature(
config.getDataTypeMapper().getReturnValueType(modelField.getDataType())));
}
anArgument.setInputModelClassArgument(inputobjectName, config);
}
}

View File

@ -24,6 +24,8 @@ public class ModelField {
private boolean required = false;
private boolean allowMultiple = false;
private List<String> allowableValues = null;
private String paramType;
@ -80,6 +82,14 @@ public class ModelField {
return allowableValues;
}
public boolean isAllowMultiple() {
return allowMultiple;
}
public void setAllowMultiple(boolean allowMultiple) {
this.allowMultiple = allowMultiple;
}
public void setAllowableValues(List<String> allowableValues) {
this.allowableValues = allowableValues;
}
@ -159,7 +169,20 @@ public class ModelField {
}else{
fieldDefinition.setName(this.getName());
}
}else if(type.startsWith("Set[")){
fieldDefinition.getImportDefinitions().addAll(dataTypeMapper.getSetImportPackages());
String entryType = type.substring(4, type.length()-1);
entryType = dataTypeMapper.getObjectType(entryType, true);
String returnType = dataTypeMapper.getSetReturnTypeSignature(entryType);
fieldDefinition.setReturnType(returnType);
fieldDefinition.setInitialization(" = " + dataTypeMapper.generateSetInitialization(entryType));
if(this.getWrapperName() != null){
fieldDefinition.setName(this.getWrapperName());
}else{
fieldDefinition.setName(this.getName());
}
}else if (type.startsWith("Map[")) {
fieldDefinition.getImportDefinitions().addAll(dataTypeMapper.getMapImportPackages());
String keyClass, entryClass = "";
@ -180,9 +203,8 @@ public class ModelField {
fieldDefinition.setReturnType(dataTypeMapper.getObjectType(type, false));
fieldDefinition.setName(this.getName());
}
}
return fieldDefinition;
}
}

View File

@ -3,14 +3,16 @@ package com.wordnik.api;
import com.wordnik.common.*;
import com.wordnik.common.ext.*;
import com.wordnik.exception.APIExceptionCodes;
import com.wordnik.exception.APIException;
import com.wordnik.model.*;
import java.util.*;
import com.wordnik.annotations.MethodArgumentNames;
import $exceptionPackageName$.APIExceptionCodes;
import $exceptionPackageName$.APIException;
import org.codehaus.jackson.map.DeserializationConfig.Feature;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.type.TypeReference;
import java.util.*;
import java.io.IOException;
/**

View File

@ -52,7 +52,7 @@ public class WordnikAPI {
* used while building the driver. This value should be provided while testing the APIs against
* test servers or if there is any changes in production server URLs.
* @param enableLogging This will enable the logging using Jersey logging filter. Refer the following documentation
* for more details. {@link LoggingFilter}. Default output is sent to system.out.
* for more details. {@link LoggingFilter}. Default output is sent to system.out.
* Create a logger ({@link Logger} class and set using setLogger method.
*/
public static void initialize(String apiKey, String apiServer, boolean enableLogging) {