diff --git a/bin/generate-java-lib.sh b/bin/generate-java-lib.sh
new file mode 100755
index 0000000000..613480ff5d
--- /dev/null
+++ b/bin/generate-java-lib.sh
@@ -0,0 +1,16 @@
+#!/bin/bash
+echo "" > classpath.txt
+for file in `ls lib`;
+ do echo -n 'lib/' >> classpath.txt;
+ echo -n $file >> classpath.txt;
+ echo -n ':' >> classpath.txt;
+done
+for file in `ls build`;
+ do echo -n 'build/' >> classpath.txt;
+ echo -n $file >> classpath.txt;
+ echo -n ':' >> classpath.txt;
+done
+
+export CLASSPATH=$(cat classpath.txt):conf/java/templates
+export JAVA_OPTS="${JAVA_OPTS} -DrulePath=data -Dproperty=Xmx2g -DloggerPath=$BUILD_COMMON/test-config/log4j.properties"
+java $WORDNIK_OPTS $JAVA_CONFIG_OPTIONS $JAVA_OPTS -cp $CLASSPATH com.wordnik.swagger.codegen.config.java.JavaLibCodeGen "$@"
\ No newline at end of file
diff --git a/bin/test-java-lib.sh b/bin/test-java-lib.sh
new file mode 100755
index 0000000000..e69de29bb2
diff --git a/build.xml b/build.xml
index a9cb1c7023..2bc3490544 100644
--- a/build.xml
+++ b/build.xml
@@ -31,15 +31,15 @@
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
@@ -80,7 +80,7 @@
-
+
Must specify the parameter for apiConfiguration
eg. -DapiConfiguration==../api-server-lib/java/config/apiConfiguration.json
@@ -99,5 +99,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/conf/java/sample/java_code_gen_conf.json b/conf/java/sample/java_code_gen_conf.json
new file mode 100644
index 0000000000..74db34b345
--- /dev/null
+++ b/conf/java/sample/java_code_gen_conf.json
@@ -0,0 +1,27 @@
+{
+ "apiUrl":"http://localhost:8002/api/",
+
+ "apiKey":"special-key",
+
+ "defaultServiceBaseClass":"Object",
+
+ "defaultModelBaseClass":"Object",
+
+ "serviceBaseClasses":{},
+
+ "defaultModelImports":[],
+
+ "defaultServiceImports":[],
+
+ "modelPackageName":"com.wordnik.swagger.sample.sdk.java.model",
+
+ "apiPackageName":"com.wordnik.swagger.sample.sdk.java.api",
+
+ "ignoreMethods":[],
+
+ "ignoreModels":[],
+
+ "outputDirectory":"../swagger-sample-app/sdk-libs/src/main/java/com/wordnik/swagger/sample/sdk/java",
+
+ "libraryHome":"../swagger-sample-app/sdk-libs"
+}
diff --git a/conf/java/sample/lib-test-data.json b/conf/java/sample/lib-test-data.json
new file mode 100644
index 0000000000..68a5c2cebd
--- /dev/null
+++ b/conf/java/sample/lib-test-data.json
@@ -0,0 +1,42 @@
+{
+ "userList":[
+ {
+ "userName":"testuser1",
+ "password":"password1",
+ "email":"test1@dummy.com"
+ },
+ {
+ "userName":"testuser2",
+ "password":"password2",
+ "email":"test2@dummy.com"
+ }
+ ],
+ "petList":[
+ {
+ "id":101,
+ "name":"pet1",
+ "photoUrls":["url1","url2"],
+ "tags":[
+ {
+ "id":1,
+ "name":"tag1"
+ },
+ {
+ "id":2,
+ "name":"tag2"
+ }
+ ],
+ "status":"available",
+ "category":{"id":1,"name":"cat1"}
+ }
+ ],
+ "orderList":[
+ {
+ "id":101,
+ "petId":1,
+ "quantity":1,
+ "status":"placed",
+ "shipDate":13456789
+ }
+ ]
+}
diff --git a/conf/java/sample/lib-test-script.json b/conf/java/sample/lib-test-script.json
new file mode 100644
index 0000000000..c61bd06c0d
--- /dev/null
+++ b/conf/java/sample/lib-test-script.json
@@ -0,0 +1,265 @@
+{
+ "resources" : [
+ {
+ "id" : 1,
+ "name" : "Find Per by Id",
+ "httpMethod" : "GET",
+ "path" : "/pet.{format}/{petId}",
+ "suggestedMethodName" : "getPetById"
+ },
+ {
+ "id" : 2,
+ "name" : "Find pets by status",
+ "httpMethod" : "GET",
+ "path" : "/pet.{format}/findByStatus",
+ "suggestedMethodName" : "findPetsByStatus"
+ },
+ {
+ "id" : 3,
+ "name" : "Find pets by tags",
+ "httpMethod" : "GET",
+ "path" : "/pet.{format}/findByTags",
+ "suggestedMethodName" : "findPetsByTags"
+ },
+ {
+ "id" : 4,
+ "name" : "Add a pet",
+ "httpMethod" : "POST",
+ "path" : "/pet.{format}",
+ "suggestedMethodName" : "addPet"
+ },
+ {
+ "id" : 5,
+ "name" : "Update a pet",
+ "httpMethod" : "PUT",
+ "path" : "/pet.{format}",
+ "suggestedMethodName" : "updatePet"
+ },
+ {
+ "id" : 6,
+ "name" : "Create user",
+ "httpMethod" : "POST",
+ "path" : "/user.{format}",
+ "suggestedMethodName" : "createUser"
+ },
+ {
+ "id" : 7,
+ "name" : "Update user",
+ "httpMethod" : "PUT",
+ "path" : "/user.{format}/{username}",
+ "suggestedMethodName" : "updateUser"
+ },
+ {
+ "id" : 8,
+ "name" : "Delete user",
+ "httpMethod" : "DELETE",
+ "path" : "/user.{format}/{username}",
+ "suggestedMethodName" : "deleteUser"
+ },
+ {
+ "id" : 9,
+ "name" : "Get user by user name",
+ "httpMethod" : "GET",
+ "path" : "/user.{format}/{username}",
+ "suggestedMethodName" : "getUserByName"
+ },
+ {
+ "id" : 10,
+ "name" : "Login",
+ "httpMethod" : "GET",
+ "path" : "/user.{format}/login",
+ "suggestedMethodName" : "loginUser"
+ },
+ {
+ "id" : 11,
+ "name" : "Logout",
+ "httpMethod" : "GET",
+ "path" : "/user.{format}/logout",
+ "suggestedMethodName" : "logoutUser"
+ },
+ {
+ "id" : 12,
+ "name" : "Find order by id",
+ "httpMethod" : "GET",
+ "path" : "/store.{format}/order/{orderId}",
+ "suggestedMethodName" : "getOrderById"
+ },
+ {
+ "id" : 13,
+ "name" : "Delete order by id",
+ "httpMethod" : "DELETE",
+ "path" : "/store.{format}/order/{orderId}",
+ "suggestedMethodName" : "deleteOrder"
+ },
+ {
+ "id" : 14,
+ "name" : "Create order",
+ "httpMethod" : "POST",
+ "path" : "/store.{format}/order",
+ "suggestedMethodName" : "placeOrder"
+ }
+ ],
+ "testSuites" : [
+ {
+ "id" : 1,
+ "name" : "Test User service related APIs",
+ "testCases" : [
+ {
+ "name" : "Create User",
+ "id" : 1,
+ "resourceId" : 6,
+ "input" : {
+ "postData":"${input.userList[0]}"
+ },
+ "assertions" : [
+ {
+ "actualOutput" : "${output(1.1)}",
+ "condition" : "!=",
+ "expectedOutput" : "EXCEPTION"
+ }
+ ]
+ },
+ {
+ "name" : "Login User",
+ "id" : 2,
+ "resourceId" : 10,
+ "input" : {
+ "username":"${input.userList[0].username}",
+ "password":"${input.userList[0].password}"
+ },
+ "assertions" : [
+ {
+ "actualOutput" : "${output(1.2)}",
+ "condition" : "!=",
+ "expectedOutput" : "EXCEPTION"
+ }
+ ]
+ },
+ {
+ "name" : "Find user by name",
+ "id" : 3,
+ "resourceId" : 9,
+ "input" : {
+ "username":"${input.userList[0].username}"
+ },
+ "assertions" : [
+ {
+ "actualOutput" : "${output(1.3).username}",
+ "condition" : "!=",
+ "expectedOutput" : "${input.userList[0].username}"
+ }
+ ]
+ },
+ {
+ "name" : "Delete user by name",
+ "id" : 4,
+ "resourceId" : 9,
+ "input" : {
+ "username":"${input.userList[0].username}"
+ },
+ "assertions" : [
+ {
+ "actualOutput" : "${output(1.4)}",
+ "condition" : "!=",
+ "expectedOutput" : "EXCEPTION"
+ }
+ ]
+ }
+
+
+ ]
+ },
+ {
+ "id" : 2,
+ "name" : "Test Pet service related APIs",
+ "testCases" : [
+ {
+ "name" : "Add pet",
+ "id" : 1,
+ "resourceId" : 4,
+ "input" : {
+ "postData":"${input.petList[0]}"
+ },
+ "assertions" : [
+ {
+ "actualOutput" : "${output(2.1)}",
+ "condition" : "!=",
+ "expectedOutput" : "EXCEPTION"
+ }
+ ]
+ },
+ {
+ "name" : "Find pet by id",
+ "id" : 2,
+ "resourceId" : 1,
+ "input" : {
+ "petId":"1"
+ },
+ "assertions" : [
+ {
+ "actualOutput" : "${output(2.2)}",
+ "condition" : "!=",
+ "expectedOutput" : "NULL"
+ }
+ ]
+ },
+ {
+ "name" : "Find pet by status",
+ "id" : 3,
+ "resourceId" : 2,
+ "input" : {
+ "status":"available,sold,pending"
+ },
+ "assertions" : [
+ {
+ "actualOutput" : "${output(2.3).size}",
+ "condition" : ">",
+ "expectedOutput" : "0"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "id" : 3,
+ "name" : "Test Store service related APIs",
+ "testCases" : [
+ {
+ "name" : "Find order by id",
+ "id" : 1,
+ "resourceId" : 12,
+ "input" : {
+ "orderId":"1"
+ },
+ "assertions" : [
+ {
+ "actualOutput" : "${output(3.1)}",
+ "condition" : "!=",
+ "expectedOutput" : "NULL"
+ }
+ ]
+ },
+ {
+ "name" : "PLace order",
+ "id" : 2,
+ "resourceId" : 14,
+ "input" : {
+ "postData":"${input.orderList[0]}"
+ },
+ "assertions" : [
+ {
+ "actualOutput" : "${output(1.2)}",
+ "condition" : "!=",
+ "expectedOutput" : "EXCEPTION"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
+
+
+
+
+
\ No newline at end of file
diff --git a/conf/java/structure/src/main/java/com/wordnik/swagger/common/APIInvoker.java b/conf/java/structure/src/main/java/com/wordnik/swagger/common/APIInvoker.java
index a0b76b52f7..6f2d5c4b2d 100644
--- a/conf/java/structure/src/main/java/com/wordnik/swagger/common/APIInvoker.java
+++ b/conf/java/structure/src/main/java/com/wordnik/swagger/common/APIInvoker.java
@@ -136,7 +136,7 @@ public class APIInvoker {
* @throws com.wordnik.swagger.exception.APIException if the call to API server fails.
*/
public static String invokeAPI(String resourceURL, String method, Map queryParams, Object postObject) throws APIException {
+ String> queryParams, Object postData) throws APIException {
Client apiClient = Client.create();
@@ -186,9 +186,9 @@ public class APIInvoker {
if(method.equals(GET)) {
clientResponse = builder.get(ClientResponse.class);
}else if (method.equals(POST)) {
- clientResponse = builder.post(ClientResponse.class, serialize(postObject));
+ clientResponse = builder.post(ClientResponse.class, serialize(postData));
}else if (method.equals(PUT)) {
- clientResponse = builder.put(ClientResponse.class, serialize(postObject));
+ clientResponse = builder.put(ClientResponse.class, serialize(postData));
}else if (method.equals(DELETE)) {
clientResponse = builder.delete(ClientResponse.class);
}
diff --git a/conf/java/templates/ModelObject.st b/conf/java/templates/ModelObject.st
index 3b44e4eddb..2ae7d2767f 100644
--- a/conf/java/templates/ModelObject.st
+++ b/conf/java/templates/ModelObject.st
@@ -16,8 +16,7 @@
package $packageName$;
-import $annotationPackageName$.AllowableValues;
-import $annotationPackageName$.Required;
+import com.wordnik.swagger.runtime.annotations.*;
$imports:{ import |
import $import$;
diff --git a/conf/java/templates/ResourceObject.st b/conf/java/templates/ResourceObject.st
index 70fa182c87..1a29d9cd1d 100644
--- a/conf/java/templates/ResourceObject.st
+++ b/conf/java/templates/ResourceObject.st
@@ -17,17 +17,19 @@
package $packageName$;
-import $annotationPackageName$.MethodArgumentNames;
-import $exceptionPackageName$.APIExceptionCodes;
-import $exceptionPackageName$.APIException;
+//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 com.wordnik.swagger.annotations.*;
-import com.wordnik.swagger.common.*;
-import com.wordnik.swagger.exception.*;
+import com.wordnik.swagger.runtime.annotations.*;
+import com.wordnik.swagger.runtime.common.*;
+import com.wordnik.swagger.runtime.exception.*;
import java.util.*;
import java.io.IOException;
@@ -95,7 +97,7 @@ $method.pathParameters:{ argument |
$endif$
//make the API Call
$if(method.postObject)$
- String response = APIInvoker.invokeAPI(resourcePath, method, queryParams, postObject);
+ String response = APIInvoker.invokeAPI(resourcePath, method, queryParams, postData);
$endif$
$if(!method.postObject)$
diff --git a/ivy.xml b/ivy.xml
index 382fe8fc2b..ea132b8cbc 100644
--- a/ivy.xml
+++ b/ivy.xml
@@ -10,7 +10,7 @@
-
+
diff --git a/src/main/java/com/wordnik/swagger/codegen/LibraryCodeGenerator.java b/src/main/java/com/wordnik/swagger/codegen/LibraryCodeGenerator.java
index 812cc6e181..2be5e8bd32 100644
--- a/src/main/java/com/wordnik/swagger/codegen/LibraryCodeGenerator.java
+++ b/src/main/java/com/wordnik/swagger/codegen/LibraryCodeGenerator.java
@@ -20,9 +20,9 @@ import com.wordnik.swagger.codegen.config.*;
import com.wordnik.swagger.codegen.config.ApiConfiguration;
import com.wordnik.swagger.codegen.config.common.CamelCaseNamingPolicyProvider;
import com.wordnik.swagger.codegen.config.java.JavaDataTypeMappingProvider;
+import com.wordnik.swagger.codegen.exception.CodeGenerationException;
import com.wordnik.swagger.codegen.resource.*;
import com.wordnik.swagger.codegen.util.FileUtil;
-import com.wordnik.swagger.exception.CodeGenerationException;
import org.antlr.stringtemplate.StringTemplate;
import org.antlr.stringtemplate.StringTemplateGroup;
import org.codehaus.jackson.map.DeserializationConfig;
@@ -70,6 +70,28 @@ public class LibraryCodeGenerator {
this.setNameGenerator(new CamelCaseNamingPolicyProvider());
}
+ public LibraryCodeGenerator(String apiServerURL, String apiKey, String modelPackageName, String apiPackageName, String classOutputDir, String libraryHome){
+
+ final ObjectMapper mapper = new ObjectMapper();
+ mapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+ ApiConfiguration aApiConfiguration = new ApiConfiguration();
+ aApiConfiguration.setApiKey(apiKey);
+ aApiConfiguration.setApiPackageName(apiPackageName);
+ aApiConfiguration.setModelPackageName(modelPackageName);
+ aApiConfiguration.setApiUrl(apiServerURL);
+ this.setApiConfig(aApiConfiguration);
+ CodeGenRulesProvider codeGenRules = new CodeGenRulesProvider();
+ this.setCodeGenRulesProvider(codeGenRules);
+ LanguageConfiguration aLanguageConfiguration = new LanguageConfiguration();
+ aLanguageConfiguration.setOutputDirectory(classOutputDir);
+ aLanguageConfiguration.setLibraryHome(libraryHome);
+ initializeLangConfig(aLanguageConfiguration);
+ this.setLanguageConfig(aLanguageConfiguration);
+
+ this.setDataTypeMappingProvider(new JavaDataTypeMappingProvider());
+ this.setNameGenerator(new CamelCaseNamingPolicyProvider());
+ }
+
/**
* Generate classes needed for the model and API invocation
*/
@@ -324,6 +346,7 @@ public class LibraryCodeGenerator {
private void writeFile(File aFile, String content, String classType){
try{
+ System.out.println("Writing to the file " + aFile.getAbsolutePath());
FileWriter aWriter = new FileWriter(aFile);
BufferedWriter bufWriter = new BufferedWriter(aWriter);
bufWriter.write(content);
diff --git a/src/main/java/com/wordnik/swagger/codegen/api/SwaggerResourceDocReader.java b/src/main/java/com/wordnik/swagger/codegen/api/SwaggerResourceDocReader.java
index 46c0e78e79..1f14861b7c 100644
--- a/src/main/java/com/wordnik/swagger/codegen/api/SwaggerResourceDocReader.java
+++ b/src/main/java/com/wordnik/swagger/codegen/api/SwaggerResourceDocReader.java
@@ -20,11 +20,11 @@ import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.WebResource;
import com.wordnik.swagger.codegen.config.DataTypeMappingProvider;
+import com.wordnik.swagger.codegen.exception.CodeGenerationException;
import com.wordnik.swagger.codegen.resource.Endpoint;
import com.wordnik.swagger.codegen.resource.Resource;
import com.wordnik.swagger.codegen.config.ApiConfiguration;
import com.wordnik.swagger.codegen.config.NamingPolicyProvider;
-import com.wordnik.swagger.exception.CodeGenerationException;
import org.codehaus.jackson.map.DeserializationConfig;
import org.codehaus.jackson.map.ObjectMapper;
diff --git a/src/main/java/com/wordnik/swagger/codegen/config/ApiConfiguration.java b/src/main/java/com/wordnik/swagger/codegen/config/ApiConfiguration.java
index 1e7b76ba72..2809c7b95c 100644
--- a/src/main/java/com/wordnik/swagger/codegen/config/ApiConfiguration.java
+++ b/src/main/java/com/wordnik/swagger/codegen/config/ApiConfiguration.java
@@ -16,8 +16,9 @@
package com.wordnik.swagger.codegen.config;
-import com.wordnik.swagger.exception.CodeGenerationException;
+import com.wordnik.swagger.codegen.exception.CodeGenerationException;
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -39,13 +40,13 @@ public class ApiConfiguration {
* we may need to write custom classes and those classes will not be known to code generation. To import those
* classes in service classes we use this property
*/
- private List defaultModelImports;
+ private List defaultModelImports = new ArrayList();
/**
* Default service imports that we need to include in all service classes. This is needed because some times,
* we may need to write custom classes ans those classes will not be known to code generation. To import those
* classes in service classes we use this property
*/
- private List defaultServiceImports;
+ private List defaultServiceImports = new ArrayList();
private String modelPackageName;
private String apiPackageName;
diff --git a/src/main/java/com/wordnik/swagger/codegen/config/LanguageConfiguration.java b/src/main/java/com/wordnik/swagger/codegen/config/LanguageConfiguration.java
index 6b6d535d04..57bdf72108 100644
--- a/src/main/java/com/wordnik/swagger/codegen/config/LanguageConfiguration.java
+++ b/src/main/java/com/wordnik/swagger/codegen/config/LanguageConfiguration.java
@@ -16,7 +16,7 @@
package com.wordnik.swagger.codegen.config;
-import com.wordnik.swagger.exception.CodeGenerationException;
+import com.wordnik.swagger.codegen.exception.CodeGenerationException;
/**
* User: deepakmichael
diff --git a/src/main/java/com/wordnik/swagger/codegen/config/common/CamelCaseNamingPolicyProvider.java b/src/main/java/com/wordnik/swagger/codegen/config/common/CamelCaseNamingPolicyProvider.java
index b5b71f49d6..42204c545a 100644
--- a/src/main/java/com/wordnik/swagger/codegen/config/common/CamelCaseNamingPolicyProvider.java
+++ b/src/main/java/com/wordnik/swagger/codegen/config/common/CamelCaseNamingPolicyProvider.java
@@ -16,9 +16,8 @@
package com.wordnik.swagger.codegen.config.common;
-import com.wordnik.swagger.codegen.resource.Model;
+import com.wordnik.swagger.codegen.exception.CodeGenerationException;
import com.wordnik.swagger.codegen.config.NamingPolicyProvider;
-import com.wordnik.swagger.exception.CodeGenerationException;
/**
* User: ramesh
@@ -111,7 +110,7 @@ public class CamelCaseNamingPolicyProvider implements NamingPolicyProvider {
/**
* For input UserAPI and resource path /findUserById the suggested input object name will be: UserFindUserByIdInput
*
- * If the input path is /{userId}/delete the sugegsted name will be UserDeleteInput. The path parameters are ignored
+ * If the input path is /{userId}/delete the suggested name will be UserDeleteInput. The path parameters are ignored
* in generating the input object name
*
* Note: Input objects are only created when the number of input arguments in a method exceeds certain number so
that the method signatures are clean
@@ -123,7 +122,7 @@ public class CamelCaseNamingPolicyProvider implements NamingPolicyProvider {
*/
public String getInputObjectName(String serviceName, String resourcePath) {
- //Since service name has API at the end remove that fromt he name
+ //Since service name has API at the end remove that format he name
String inputobjectName = serviceName.substring(0, serviceName.length() - 3);
String[] pathElements = resourcePath.split("/");
diff --git a/src/main/java/com/wordnik/swagger/codegen/config/java/JavaDataTypeMappingProvider.java b/src/main/java/com/wordnik/swagger/codegen/config/java/JavaDataTypeMappingProvider.java
index 9dfa93a57d..f34ae08d5d 100644
--- a/src/main/java/com/wordnik/swagger/codegen/config/java/JavaDataTypeMappingProvider.java
+++ b/src/main/java/com/wordnik/swagger/codegen/config/java/JavaDataTypeMappingProvider.java
@@ -53,24 +53,29 @@ public class JavaDataTypeMappingProvider implements DataTypeMappingProvider {
static{
primitiveObjectMap.put("string", "String");
primitiveObjectMap.put("String", "String");
+ primitiveObjectMap.put("java.lang.String", "String");
primitiveObjectMap.put("int", "Integer");
primitiveObjectMap.put("integer", "Integer");
primitiveObjectMap.put("Integer", "Integer");
+ primitiveObjectMap.put("java.lang.Integer", "Integer");
primitiveObjectMap.put("boolean", "Boolean");
primitiveObjectMap.put("Boolean", "Boolean");
+ primitiveObjectMap.put("java.lang.Boolean", "Boolean");
primitiveObjectMap.put("long", "Long");
primitiveObjectMap.put("Long", "Long");
+ primitiveObjectMap.put("java.lang.Long", "Long");
primitiveObjectMap.put("float", "Float");
primitiveObjectMap.put("Float", "Float");
+ primitiveObjectMap.put("java.lang.Float", "Float");
primitiveObjectMap.put("Date", "Date");
primitiveObjectMap.put("date", "Date");
+ primitiveObjectMap.put("java.util.Date", "Date");
}
private NamingPolicyProvider nameGenerator = new CamelCaseNamingPolicyProvider();
public boolean isPrimitiveType(String type) {
- if(type.equalsIgnoreCase("String") || type.equalsIgnoreCase("int") || type.equalsIgnoreCase("integer") ||
- type.equalsIgnoreCase("boolean") || type.equalsIgnoreCase("float")|| type.equalsIgnoreCase("long") ){
+ if(primitiveObjectMap.containsKey(type)){
return true;
}
return false;
diff --git a/src/main/java/com/wordnik/swagger/codegen/config/java/JavaLibCodeGen.java b/src/main/java/com/wordnik/swagger/codegen/config/java/JavaLibCodeGen.java
index a88edfc43f..2e634afd68 100644
--- a/src/main/java/com/wordnik/swagger/codegen/config/java/JavaLibCodeGen.java
+++ b/src/main/java/com/wordnik/swagger/codegen/config/java/JavaLibCodeGen.java
@@ -17,17 +17,12 @@
package com.wordnik.swagger.codegen.config.java;
import com.wordnik.swagger.codegen.LibraryCodeGenerator;
-import com.wordnik.swagger.codegen.config.ApiConfiguration;
-import com.wordnik.swagger.codegen.config.CodeGenRulesProvider;
import com.wordnik.swagger.codegen.config.LanguageConfiguration;
import com.wordnik.swagger.codegen.config.common.CamelCaseNamingPolicyProvider;
+import com.wordnik.swagger.codegen.exception.CodeGenerationException;
import com.wordnik.swagger.codegen.util.FileUtil;
-import com.wordnik.swagger.exception.CodeGenerationException;
-import org.codehaus.jackson.map.DeserializationConfig;
-import org.codehaus.jackson.map.ObjectMapper;
import java.io.File;
-import java.io.IOException;
/**
* User: ramesh
@@ -40,9 +35,30 @@ public class JavaLibCodeGen extends LibraryCodeGenerator {
if(args.length < 1){
throw new CodeGenerationException("Invalid number of arguments passed: No command line argument was passed to the program for config json");
}
- String configPath = args[0];
- JavaLibCodeGen codeGenerator = new JavaLibCodeGen(configPath);
- codeGenerator.generateCode();
+ if(args.length == 1) {
+ String configPath = args[0];
+ JavaLibCodeGen codeGenerator = new JavaLibCodeGen(configPath);
+ codeGenerator.generateCode();
+ }
+ if(args.length == 6) {
+ String apiServerURL = args[0];
+ String apiKey = args[1];
+ String modelPackageName = args[2];
+ String apiPackageName = args[3];
+ String classOutputDir = args[4];
+ String libraryHome = args[5];
+ JavaLibCodeGen codeGenerator = new JavaLibCodeGen(apiServerURL, apiKey, modelPackageName,
+ apiPackageName, classOutputDir, libraryHome);
+ codeGenerator.generateCode();
+ }
+
+ }
+
+ public JavaLibCodeGen(String apiServerURL, String apiKey, String modelPackageName, String apiPackageName,
+ String classOutputDir, String libraryHome){
+ super(apiServerURL, apiKey, modelPackageName, apiPackageName, classOutputDir, libraryHome);
+ this.setDataTypeMappingProvider(new JavaDataTypeMappingProvider());
+ this.setNameGenerator(new CamelCaseNamingPolicyProvider());
}
public JavaLibCodeGen(String configPath){
@@ -62,10 +78,9 @@ public class JavaLibCodeGen extends LibraryCodeGenerator {
//create ouput directories
FileUtil.createOutputDirectories(javaConfiguration.getModelClassLocation(), javaConfiguration.getClassFileExtension());
FileUtil.createOutputDirectories(javaConfiguration.getResourceClassLocation(), javaConfiguration.getClassFileExtension());
- FileUtil.clearFolder(javaConfiguration.getLibraryHome() + "/src/main/java/com/wordnik/swagger/common");
- FileUtil.clearFolder(javaConfiguration.getLibraryHome() + "/src/main/java/com/wordnik/swagger/exception");
- FileUtil.clearFolder(javaConfiguration.getLibraryHome() + "/src/main/java/com/wordnik/swagger/annotations");
- FileUtil.copyDirectory(new File(javaConfiguration.getStructureLocation()), new File(javaConfiguration.getLibraryHome()));
+ FileUtil.clearFolder(javaConfiguration.getLibraryHome() + "/src/main/java/com/wordnik/swagger/runtime");
+ FileUtil.createOutputDirectories(javaConfiguration.getLibraryHome() + "/src/main/java/com/wordnik/swagger/runtime", "java");
+ FileUtil.copyDirectory(new File("src/main/java/com/wordnik/swagger/runtime"), new File(javaConfiguration.getLibraryHome()+"/src/main/java/com/wordnik/swagger/runtime"));
return javaConfiguration;
}
diff --git a/src/main/java/com/wordnik/swagger/exception/CodeGenerationException.java b/src/main/java/com/wordnik/swagger/codegen/exception/CodeGenerationException.java
similarity index 95%
rename from src/main/java/com/wordnik/swagger/exception/CodeGenerationException.java
rename to src/main/java/com/wordnik/swagger/codegen/exception/CodeGenerationException.java
index 27cdb8e224..1586ae4d50 100644
--- a/src/main/java/com/wordnik/swagger/exception/CodeGenerationException.java
+++ b/src/main/java/com/wordnik/swagger/codegen/exception/CodeGenerationException.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.wordnik.swagger.exception;
+package com.wordnik.swagger.codegen.exception;
/**
* Exception raised while generating code for java driver.
diff --git a/src/main/java/com/wordnik/swagger/codegen/resource/EndpointOperation.java b/src/main/java/com/wordnik/swagger/codegen/resource/EndpointOperation.java
index a3eb6613db..761fd112fa 100644
--- a/src/main/java/com/wordnik/swagger/codegen/resource/EndpointOperation.java
+++ b/src/main/java/com/wordnik/swagger/codegen/resource/EndpointOperation.java
@@ -35,6 +35,8 @@ public class EndpointOperation {
public static String PARAM_TYPE_PATH = "path";
public static String PARAM_TYPE_BODY = "body";
public static String PARAM_TYPE_HEADER = "header";
+ public static String POST_PARAM_NAME = "postData";
+
private static String AUTH_TOKEN_PARAM_NAME = "auth_token";
private static String API_KEY_PARAM_NAME = "api_key";
private static String FORMAT_PARAM_NAME = "format";
@@ -238,7 +240,7 @@ public class EndpointOperation {
arguments.add(anArgument);
}else if (modelField.getParamType().equalsIgnoreCase(PARAM_TYPE_BODY)) {
if(modelField.getName() == null) {
- modelField.setName("postObject");
+ modelField.setName(POST_PARAM_NAME);
}
anArgument.setName(modelField.getName());
anArgument.setDataType(dataTypeMapper.getClassType(modelField.getDataType(), false));
@@ -263,7 +265,7 @@ public class EndpointOperation {
modelforMethodInput.setName(inputobjectName);
List fields = new ArrayList();
for(MethodArgument argument: method.getArguments()){
- if(!argument.getName().equals("postObject") && !argument.getName().equals("authToken")){
+ if(!argument.getName().equals(POST_PARAM_NAME) && !argument.getName().equals("authToken")){
ModelField aModelField = new ModelField();
aModelField.setAllowedValues(argument.getAllowedValues());
aModelField.setDescription(argument.getDescription());
diff --git a/src/main/java/com/wordnik/swagger/codegen/util/FileUtil.java b/src/main/java/com/wordnik/swagger/codegen/util/FileUtil.java
index 210323ea2d..9726024af5 100644
--- a/src/main/java/com/wordnik/swagger/codegen/util/FileUtil.java
+++ b/src/main/java/com/wordnik/swagger/codegen/util/FileUtil.java
@@ -16,7 +16,7 @@
package com.wordnik.swagger.codegen.util;
-import com.wordnik.swagger.exception.CodeGenerationException;
+import com.wordnik.swagger.codegen.exception.CodeGenerationException;
import java.io.*;
@@ -63,9 +63,11 @@ public class FileUtil {
public static void clearFolder(String directoryLocation) {
File fDir = new File(directoryLocation);
File[] files = fDir.listFiles();
- for(File aFile : files) {
- aFile.delete();
- }
+ if(files != null) {
+ for(File aFile : files) {
+ aFile.delete();
+ }
+ }
}
// Clears the folder of the files with extension
@@ -76,9 +78,10 @@ public class FileUtil {
return (strName.endsWith(strExt));
}
});
-
- for (int i = 0; i < fLogs.length; i++) {
- deleteFile(fLogs[i].getAbsolutePath());
+ if(fLogs != null){
+ for (int i = 0; i < fLogs.length; i++) {
+ deleteFile(fLogs[i].getAbsolutePath());
+ }
}
}
@@ -110,6 +113,7 @@ public class FileUtil {
in.close();
out.close();
} catch (IOException e) {
+ e.printStackTrace();
throw new CodeGenerationException("Copy directory operation failed");
}
}
diff --git a/src/main/java/com/wordnik/swagger/runtime/annotations/AllowableValues.java b/src/main/java/com/wordnik/swagger/runtime/annotations/AllowableValues.java
new file mode 100644
index 0000000000..e8dde8001e
--- /dev/null
+++ b/src/main/java/com/wordnik/swagger/runtime/annotations/AllowableValues.java
@@ -0,0 +1,35 @@
+/**
+ * Copyright 2011 Wordnik, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.wordnik.swagger.runtime.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+
+/**
+ * Annotation used to provide list of possible values
+ * @author ramesh
+ *
+ */
+@Target({ElementType.FIELD,ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface AllowableValues {
+
+ String value() default "";
+}
diff --git a/src/main/java/com/wordnik/swagger/runtime/annotations/MethodArgumentNames.java b/src/main/java/com/wordnik/swagger/runtime/annotations/MethodArgumentNames.java
new file mode 100644
index 0000000000..29af03d089
--- /dev/null
+++ b/src/main/java/com/wordnik/swagger/runtime/annotations/MethodArgumentNames.java
@@ -0,0 +1,29 @@
+/**
+ * Copyright 2011 Wordnik, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.wordnik.swagger.runtime.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+
+@Target({ElementType.FIELD,ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface MethodArgumentNames {
+ String value() default "";
+}
diff --git a/src/main/java/com/wordnik/swagger/runtime/annotations/Required.java b/src/main/java/com/wordnik/swagger/runtime/annotations/Required.java
new file mode 100644
index 0000000000..bfdac9f7c0
--- /dev/null
+++ b/src/main/java/com/wordnik/swagger/runtime/annotations/Required.java
@@ -0,0 +1,33 @@
+/**
+ * Copyright 2011 Wordnik, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.wordnik.swagger.runtime.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation used to indicate given property or field is required or not
+ * @author ramesh
+ *
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Required {
+
+}
diff --git a/src/main/java/com/wordnik/swagger/runtime/common/APIInvoker.java b/src/main/java/com/wordnik/swagger/runtime/common/APIInvoker.java
new file mode 100644
index 0000000000..8669f3d672
--- /dev/null
+++ b/src/main/java/com/wordnik/swagger/runtime/common/APIInvoker.java
@@ -0,0 +1,273 @@
+/**
+ * Copyright 2011 Wordnik, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.wordnik.swagger.runtime.common;
+
+import java.io.IOException;
+import java.lang.String;
+import java.util.Map;
+import java.util.List;
+import java.util.HashMap;
+import java.util.logging.Logger;
+
+import javax.ws.rs.core.MultivaluedMap;
+
+import com.wordnik.swagger.runtime.exception.APIException;
+import com.wordnik.swagger.runtime.exception.APIExceptionCodes;
+import org.codehaus.jackson.map.ObjectMapper;
+import org.codehaus.jackson.map.DeserializationConfig.Feature;
+import org.codehaus.jackson.map.SerializationConfig;
+import org.codehaus.jackson.type.TypeReference;
+
+import com.sun.jersey.api.client.Client;
+import com.sun.jersey.api.client.ClientResponse;
+import com.sun.jersey.api.client.WebResource;
+import com.sun.jersey.api.client.WebResource.Builder;
+import com.sun.jersey.api.client.filter.LoggingFilter;
+
+
+/**
+ * Provides method to initialize the api server settings and also handles the logic related to invoking the API server
+ * along with serealizing and deserializing input and output responses.
+ *
+ * This is also a Base class for all API classes
+ *
+ * @author ramesh
+ *
+ */
+public class APIInvoker {
+
+ private static String apiServer = "http://api.wordnik.com/v4";
+ private static SecurityHandler securityHandler = null;
+ private static boolean loggingEnabled;
+ private static Logger logger = null;
+
+ protected static String POST = "POST";
+ protected static String GET = "GET";
+ protected static String PUT = "PUT";
+ protected static String DELETE = "DELETE";
+ public static ObjectMapper mapper = new ObjectMapper();
+ static{
+ mapper.getDeserializationConfig().set(Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+ mapper.getSerializationConfig().set(SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS, false);
+ mapper.configure(SerializationConfig.Feature.WRITE_NULL_PROPERTIES, false);
+ mapper.configure(SerializationConfig.Feature.WRITE_DATES_AS_TIMESTAMPS, false);
+ }
+
+ /**
+ * Initializes the API communication with required inputs.
+ * @param securityHandler security handler responsible for populating necessary security invocation while making API server calls
+ * @param apiServer Sets the URL for the API server. It is defaulted to the server
+ * 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 com.sun.jersey.api.client.filter.LoggingFilter}. Default output is sent to system.out.
+ * Create a logger ({@link java.util.logging.Logger} class and set using setLogger method.
+ */
+ public static void initialize(SecurityHandler securityHandler, String apiServer, boolean enableLogging) {
+ setSecurityHandler(securityHandler);
+ if(apiServer != null && apiServer.length() > 0) {
+ if(apiServer.substring(apiServer.length()-1).equals("/")){
+ apiServer = apiServer.substring(0, apiServer.length()-1);
+ }
+ setApiServer(apiServer);
+ }
+ loggingEnabled = enableLogging;
+ }
+
+ /**
+ * Set the logger instance used for Jersey logging.
+ * @param aLogger
+ */
+ public static void setLogger(Logger aLogger) {
+ logger = aLogger;
+ }
+
+ /**
+ * Gets the API key used for server communication.
+ * This value is set using initialize method.
+ * @return
+ */
+ public static SecurityHandler setSecurityHandler() {
+ return securityHandler;
+ }
+
+ private static void setSecurityHandler(SecurityHandler aSecurityHandler) {
+ securityHandler = aSecurityHandler;
+ }
+
+ /**
+ * Sets the URL for the API server. It is defaulted to the server used while building the driver.
+ * @return
+ */
+ private static String getApiServer() {
+ return apiServer;
+ }
+
+ public static void setApiServer(String server) {
+ apiServer = server;
+ }
+
+
+
+ /**
+ * Invokes the API and returns the response as json string.
+ *
+ * This is an internal method called by individual APIs for communication. It sets the required security information
+ * based ons ecuroty handler
+ *
+ * @param resourceURL - URL for the rest resource
+ * @param method - Method we should use for communicating to the back end.
+ * @param postData - if the method is POST, provide the object that should be sent as part of post request.
+ * @return JSON response of the API call.
+ * @throws com.wordnik.swagger.runtime.exception.APIException if the call to API server fails.
+ */
+ public static String invokeAPI(String resourceURL, String method, Map queryParams, Object postData) throws APIException {
+
+
+ Client apiClient = Client.create();
+
+ //check for app server values
+ if(getApiServer() == null || getApiServer().length() == 0) {
+ String[] args = {getApiServer()};
+ throw new APIException(APIExceptionCodes.API_SERVER_NOT_VALID, args);
+ }
+
+ //initialize the logger if needed
+ if(loggingEnabled) {
+ if(logger == null) {
+ apiClient.addFilter(new LoggingFilter());
+ }else{
+ apiClient.addFilter(new LoggingFilter(logger));
+ }
+ }
+
+ //make the communication
+ resourceURL = getApiServer() + resourceURL;
+ if(queryParams.keySet().size() > 0){
+ int i=0;
+ for(String paramName : queryParams.keySet()){
+ String symbol = "&";
+ if(i==0){
+ symbol = "?";
+ }
+ resourceURL = resourceURL + symbol + paramName + "=" + queryParams.get(paramName);
+ i++;
+ }
+ }
+ Map headerMap = new HashMap();
+ if(securityHandler != null){
+ securityHandler.populateSecurityInfo(resourceURL, headerMap);
+ }
+ WebResource aResource = apiClient.resource(resourceURL);
+
+
+ //set the required HTTP headers
+ Builder builder = aResource.type("application/json");
+ for(String key : headerMap.keySet()){
+ builder.header(key, headerMap.get(key));
+ }
+
+ ClientResponse clientResponse = null;
+ if(method.equals(GET)) {
+ clientResponse = builder.get(ClientResponse.class);
+ }else if (method.equals(POST)) {
+ clientResponse = builder.post(ClientResponse.class, serialize(postData));
+ }else if (method.equals(PUT)) {
+ clientResponse = builder.put(ClientResponse.class, serialize(postData));
+ }else if (method.equals(DELETE)) {
+ clientResponse = builder.delete(ClientResponse.class);
+ }
+
+ //process the response
+ if(clientResponse.getClientResponseStatus() == ClientResponse.Status.OK) {
+ String response = clientResponse.getEntity(String.class);
+ return response;
+ }else{
+ int responseCode = clientResponse.getClientResponseStatus().getStatusCode() ;
+ throw new APIException(responseCode, clientResponse.getEntity(String.class));
+ }
+ }
+
+ /**
+ * De-serialize the object from String to object of type input class name.
+ * @param response
+ * @param inputClassName
+ * @return
+ */
+ public static Object deserialize(String response, Class inputClassName) throws APIException {
+ try {
+ System.out.println("response: " + response + " , class name:" + inputClassName);
+ Object responseObject = mapper.readValue(response, inputClassName);
+ return responseObject;
+ } catch (IOException ioe) {
+ String[] args = new String[]{response, inputClassName.toString()};
+ throw new APIException(APIExceptionCodes.ERROR_CONVERTING_JSON_TO_JAVA, args, "Error in coversting response json value to java object : " + ioe.getMessage(), ioe);
+ }
+ }
+
+
+ /**
+ * serialize the object from String to input object.
+ * @param input
+ * @return
+ */
+ public static String serialize(Object input) throws APIException {
+ try {
+ if(input != null) {
+ return mapper.writeValueAsString(input);
+ }else{
+ return "";
+ }
+ } catch (IOException ioe) {
+ throw new APIException(APIExceptionCodes.ERROR_CONVERTING_JAVA_TO_JSON, "Error in coverting input java to json : " + ioe.getMessage(), ioe);
+ }
+ }
+
+
+ /**
+ * Overloaded method for returning the path value
+ * For a string value an empty value is returned if the value is null
+ * @param value
+ * @return
+ */
+ public 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
+ */
+ public 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();
+ }
+
+ public static boolean isLoggingEnable() {
+ return loggingEnabled;
+ }
+}
diff --git a/src/main/java/com/wordnik/swagger/runtime/common/ApiKeyAuthTokenBasedSecurityHandler.java b/src/main/java/com/wordnik/swagger/runtime/common/ApiKeyAuthTokenBasedSecurityHandler.java
new file mode 100644
index 0000000000..456f461715
--- /dev/null
+++ b/src/main/java/com/wordnik/swagger/runtime/common/ApiKeyAuthTokenBasedSecurityHandler.java
@@ -0,0 +1,67 @@
+/**
+ * Copyright 2011 Wordnik, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.wordnik.swagger.runtime.common;
+
+import java.util.Map;
+
+/**
+ * User: ramesh
+ * Date: 8/4/11
+ * Time: 6:39 PM
+ */
+public class ApiKeyAuthTokenBasedSecurityHandler implements SecurityHandler {
+
+ private String apiKey = "";
+ private String authToken = "";
+
+ public ApiKeyAuthTokenBasedSecurityHandler(String apiKey, String authToken) {
+ this.apiKey = apiKey;
+ this.authToken = authToken;
+ }
+
+ public String getAuthToken() {
+ return authToken;
+ }
+
+ public void setAuthToken(String authToken) {
+ this.authToken = authToken;
+ }
+
+ public String getApiKey() {
+ return apiKey;
+ }
+
+ public void setApiKey(String apiKey) {
+ this.apiKey = apiKey;
+ }
+
+ /**
+ * Populate the security infomration in http headers map and/or reqsource URL.
+ *
+ * Value spopulated in the http headers map will be set as http headers while making the server communication.
+ *
+ * Depending on the usecase requried information can be added to either of them or both.
+ *
+ * @param resourceURL
+ * @param headers
+ */
+ public void populateSecurityInfo(String resourceURL, Map httpHeaders) {
+ resourceURL = resourceURL + "api_key="+apiKey;
+ httpHeaders.put("api_key", apiKey);
+ httpHeaders.put("auth_token", authToken);
+ }
+}
diff --git a/src/main/java/com/wordnik/swagger/runtime/common/SecurityHandler.java b/src/main/java/com/wordnik/swagger/runtime/common/SecurityHandler.java
new file mode 100644
index 0000000000..2ac1040180
--- /dev/null
+++ b/src/main/java/com/wordnik/swagger/runtime/common/SecurityHandler.java
@@ -0,0 +1,47 @@
+/**
+ * Copyright 2011 Wordnik, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.wordnik.swagger.runtime.common;
+
+import java.util.Map;
+
+/**
+ * Provide methods that are responsible for handling security aspect while communicating with the backend server.
+ *
+ * Example: For some cases API key may need to be passed in the headers for all server communication and some times
+ * user authentication token may need to be passed along with api key.
+ *
+ * Implementers of this class are responsible for handling storing information related to secutiry and sending it
+ * along with all API calls
+ *
+ * User: ramesh
+ * Date: 4/12/11
+ * Time: 5:46 PM
+ */
+public interface SecurityHandler {
+
+ /**
+ * Populate the security infomration in http headers map and/or reqsource URL.
+ *
+ * Value spopulated in the http headers map will be set as http headers while making the server communication.
+ *
+ * Depending on the usecase requried information can be added to either of them or both.
+ *
+ * @param resourceURL
+ * @param headers
+ */
+ public void populateSecurityInfo(String resourceURL, Map httpHeaders);
+}
\ No newline at end of file
diff --git a/src/main/java/com/wordnik/swagger/runtime/exception/APIException.java b/src/main/java/com/wordnik/swagger/runtime/exception/APIException.java
new file mode 100644
index 0000000000..c7508d869f
--- /dev/null
+++ b/src/main/java/com/wordnik/swagger/runtime/exception/APIException.java
@@ -0,0 +1,100 @@
+/**
+ * Copyright 2011 Wordnik, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.wordnik.swagger.runtime.exception;
+
+import com.sun.jersey.api.client.ClientResponse;
+import org.codehaus.jackson.annotate.JsonAutoDetect;
+import org.codehaus.jackson.annotate.JsonCreator;
+import org.codehaus.jackson.annotate.JsonMethod;
+import org.codehaus.jackson.annotate.JsonProperty;
+
+/**
+ * Exception that is thrown if there are any issues in invoking Wordnik API.
+ *
+ * Each exception carries a code and message. Code can be either HTTP error response code {@link com.sun.jersey.api.client.ClientResponse.Status}
+ * or The list of possible Wordnik exception code that are listed in the interface {@link APIExceptionCodes}.
+ * User: ramesh
+ * Date: 3/31/11
+ * Time: 9:27 AM
+ */
+public class APIException extends Exception {
+
+ private String message;
+
+ private int code;
+
+ private String[] args;
+
+ @JsonCreator
+ public APIException() {
+ }
+
+ public APIException(String message) {
+ super(message);
+ }
+
+ public APIException(int code) {
+ this.code = code;
+ }
+
+ public APIException(int code, String message, Throwable t) {
+ super(message, t);
+ this.message = message;
+ this.code = code;
+ }
+
+ public APIException(int code, String[] args, String message, Throwable t) {
+ super(message, t);
+ this.message = message;
+ this.code = code;
+ this.args = args;
+ }
+
+ public APIException(int code, String message) {
+ super(message);
+ this.message = message;
+ this.code = code;
+ }
+
+ public APIException(int code, String[] args, String message) {
+ super(message);
+ this.message = message;
+ this.code = code;
+ this.args = args;
+ }
+
+ public APIException(int code, String[] args) {
+ this.code = code;
+ this.args = args;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ public void setMessage(String message) {
+ this.message = message;
+ }
+
+ public int getCode() {
+ return code;
+ }
+
+ public void setCode(int code) {
+ this.code = code;
+ }
+}
diff --git a/src/main/java/com/wordnik/swagger/runtime/exception/APIExceptionCodes.java b/src/main/java/com/wordnik/swagger/runtime/exception/APIExceptionCodes.java
new file mode 100644
index 0000000000..5c53af5aef
--- /dev/null
+++ b/src/main/java/com/wordnik/swagger/runtime/exception/APIExceptionCodes.java
@@ -0,0 +1,54 @@
+/**
+ * Copyright 2011 Wordnik, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.wordnik.swagger.runtime.exception;
+
+/**
+ * Lists all the possible exception codes
+ * @author ramesh
+ *
+ */
+public interface APIExceptionCodes {
+
+ /**
+ * System exception.
+ */
+ public static final int SYSTEM_EXCEPTION = 0;
+
+ /**
+ * With Arguments as current key.
+ */
+ public static final int API_KEY_NOT_VALID = 1000;
+ /**
+ * With arguments as current token value
+ */
+ public static final int AUTH_TOKEN_NOT_VALID = 1001;
+ /**
+ * With arguments as input JSON and output class anme
+ */
+ public static final int ERROR_CONVERTING_JSON_TO_JAVA = 1002;
+ /**
+ * With arguments as JAVA class name
+ */
+ public static final int ERROR_CONVERTING_JAVA_TO_JSON = 1003;
+
+ public static final int ERROR_FROM_WEBSERVICE_CALL = 1004;
+ /**
+ * With arguments as current API server name
+ */
+ public static final int API_SERVER_NOT_VALID = 1005;
+
+}
diff --git a/src/main/java/com/wordnik/swagger/testframework/APITestRunner.java b/src/main/java/com/wordnik/swagger/testframework/APITestRunner.java
new file mode 100644
index 0000000000..6e90636501
--- /dev/null
+++ b/src/main/java/com/wordnik/swagger/testframework/APITestRunner.java
@@ -0,0 +1,545 @@
+package com.wordnik.swagger.testframework;
+
+import com.wordnik.swagger.codegen.config.common.CamelCaseNamingPolicyProvider;
+import com.wordnik.swagger.runtime.common.APIInvoker;
+import com.wordnik.swagger.runtime.common.ApiKeyAuthTokenBasedSecurityHandler;
+import com.wordnik.swagger.runtime.common.SecurityHandler;
+import com.wordnik.swagger.runtime.exception.APIException;
+import org.apache.commons.beanutils.MethodUtils;
+import org.apache.commons.beanutils.PropertyUtils;
+import org.codehaus.jackson.map.DeserializationConfig.Feature;
+import org.codehaus.jackson.map.ObjectMapper;
+import org.codehaus.jackson.map.SerializationConfig;
+import org.codehaus.jackson.map.type.TypeFactory;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.InputStreamReader;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+
+/**
+ * Instance of this class is used to run the tests and assert the results based on
+ * JSON based test script.
+ * Created by IntelliJ IDEA.
+ * User: ramesh
+ * Date: 3/30/11
+ * Time: 6:27 PM
+ */
+public class APITestRunner {
+
+ private static String INPUT_DATA_EXPRESSION_PREFIX = "${input.";
+ private static String OUTPUT_DATA_EXPRESSION_PREFIX = "${output";
+ public static String POST_PARAM_NAME = "postData";
+
+ private static String CONDITION_EQUAL = "==";
+ private static String CONDITION_NOT_EQUAL = "!=";
+ private static String CONDITION_GREATER = ">";
+ private static String CONDITION_LESSER = "<";
+ private static String CONDITION_GREATER_EQUAL = ">=";
+ private static String CONDITION_LESSER_EQUAL = "<=";
+
+ private TestOutput testCaseOutput = new TestOutput();
+ private TestStatus testStatus = new TestStatus();
+ private Object testData = null;
+ private TestPackage aPackage = null;
+
+ private static String JAVA = "JAVA";
+ private static String SCALA = "SCALA";
+ private static String PYTHON = "PYTHON";
+ private static String RUBY = "RUBY";
+ private static String ANDROID = "ANDROID";
+ private static String OBJECTIVE_C = "OBJECTIVE_C";
+ private static String AS3 = "AS3";
+ private static String NET = "NET";
+ private static String PHP = "PHP";
+ private static String HASKEL = "HASKEL";
+ private static String CLOJURE = "CLOJURE";
+
+ private static ObjectMapper mapper = new ObjectMapper();
+ static{
+ mapper.getDeserializationConfig().set(Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+ mapper.configure(SerializationConfig.Feature.WRITE_NULL_PROPERTIES, false);
+ mapper.configure(SerializationConfig.Feature.WRITE_DATES_AS_TIMESTAMPS, false);
+ }
+
+ private CamelCaseNamingPolicyProvider namingPolicyProvider = new CamelCaseNamingPolicyProvider();
+
+ /**
+ * Follow the following argument pattern
+ *
+ * Arg[0] --> api server URL
+ * Arg[1] --> api key
+ * Arg[2] --> test script file path
+ * Arg[3] --> test data file path
+ * Arg[4] --> test data class name (class to which test data file will be deserialized)
+ * Arg[5] --> package where API classes are available
+ * Arg[6] --> Language to execute test cases
+ * Arg[7] --> Optional test cases id. provide this if you need to execute only one test case
+ *
+ * @param args
+ * @throws Exception
+ */
+ public static void main(String[] args) throws Exception {
+
+ String apiServer = args[0];
+ String apiKey = args[1];
+ String testScriptLocation = args[2];
+ String testDataLocation = args[3];
+ String testDataClass = args[4];
+ String apiPackageName = args[5];
+ String language = args[6];
+ String suiteId = "0";
+ if(args.length > 7){
+ suiteId = args[7];
+ }
+
+ ApiKeyAuthTokenBasedSecurityHandler securityHandler = new ApiKeyAuthTokenBasedSecurityHandler(apiKey, "");
+ APIInvoker.initialize(securityHandler, apiServer, true);
+ APITestRunner runner = new APITestRunner();
+ runner.initialize(testScriptLocation, testDataLocation, testDataClass);
+ runner.runTests(apiServer, apiPackageName, runner.getTestPackage(), language, new Integer(suiteId), apiPackageName, securityHandler);
+ }
+
+ public void initialize(String testScriptLocation, String testDataLocation, String testDataClass) throws Exception {
+ //load test script
+ File aFile = new File(testScriptLocation);
+ BufferedReader reader = new BufferedReader(new FileReader(aFile));
+ StringBuilder builder = new StringBuilder();
+ while(true){
+ String line = reader.readLine();
+ if(line == null){
+ break;
+ }else{
+ builder.append(line);
+ }
+ }
+ mapper.getDeserializationConfig().set(Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+ aPackage = (TestPackage) mapper.readValue(builder.toString(), TestPackage.class);
+
+ //load test data
+ aFile = new File(testDataLocation);
+ reader = new BufferedReader(new FileReader(aFile));
+ builder = new StringBuilder();
+ while(true){
+ String line = reader.readLine();
+ if(line == null){
+ break;
+ }else{
+ builder.append(line);
+ }
+ }
+ testData = mapper.readValue(builder.toString(), Class.forName(testDataClass));
+ reader.close();
+ }
+
+
+ /**
+ * Gets the package that is initialized for testing
+ * @return
+ */
+ public TestPackage getTestPackage() {
+ return aPackage;
+ }
+
+ /***
+ * Runs individual test cases in all test suites and stored the result in output object
+ * @param testPackage
+ */
+ private void runTests(String apiServer, String apiPackageName, TestPackage testPackage, String language, int suiteId,
+ String libraryPackageName, ApiKeyAuthTokenBasedSecurityHandler securityHandler) throws Exception {
+ /**
+ * Logic:
+ *
+ * 1. Get each test case
+ *
+ * 2. based on the patch arrive at the method that needs to be executed
+ *
+ * 3. From the method get list of input parameters
+ *
+ * 4. Read input parameters based on input structure
+ *
+ * 5 Execute method by calling system command
+ *
+ * 6. Get the output
+ *
+ * 7. Store output in output object
+ *
+ * 8. execute assertions
+ *
+ * 9. Continue with next test case
+ */
+
+ Map resourceMap = new HashMap();
+ for(TestResource resource: testPackage.getResources()){
+ resourceMap.put(resource.getId(), resource);
+ }
+
+ for(TestSuite suite : testPackage.getTestSuites()) {
+ if(suiteId != 0 && suiteId != suite.getId()){
+ continue;
+ }
+ int testSuiteId = suite.getId();
+ //1
+ for(TestCase testCase : suite.getTestCases()){
+ String testCasePath = testCasePath(testSuiteId , testCase.getId());
+
+ //2
+ TestResource resource = resourceMap.get(testCase.getResourceId());
+ String path = resource.getPath();
+ String className = namingPolicyProvider.getServiceName(path);
+ String methodName = resource.getSuggestedMethodName();
+
+ //3
+ Class apiClass = Class.forName(libraryPackageName +"." + className);
+ Method[] methods = apiClass.getMethods();
+ Method methodToExecute = null;
+ for(Method method : methods){
+ if(method.getName().equals(methodName)){
+ methodToExecute = method;
+ break;
+ }
+ }
+ try {
+ if(methodToExecute != null) {
+ //4
+ Map arguments = getArgumentsForMethodCall(methodToExecute, testCase);
+
+ String authToken = "\"\"";
+ String postData = "\"\"";
+ StringBuilder queryPathParameters = new StringBuilder();
+ for(String argName : arguments.keySet()){
+ Object value = arguments.get(argName);
+ if(argName.equals("authToken")){
+ authToken = value.toString();
+ }else if (argName.equals(POST_PARAM_NAME)){
+ postData = convertObjectToJSONString(value);
+ }else{
+ if(queryPathParameters.toString().length()> 0){
+ queryPathParameters.append("~");
+ }
+ queryPathParameters.append(argName+"="+value.toString());
+ }
+ }
+
+ //get eternal command
+ String[] externalCommand = constructExternalCommand(apiServer, apiPackageName,
+ securityHandler.getApiKey(), authToken,
+ resource.getPath(), resource.getHttpMethod(), resource.getSuggestedMethodName(),
+ queryPathParameters.toString(), postData, language);
+ //print the command
+ System.out.println("Test Case :" + testCasePath);
+ for(String arg : externalCommand){
+ System.out.print(arg + " ");
+ }
+ System.out.println("");
+ //execute and get data
+ String outputString = executeExternalTestCaseAndGetResult(externalCommand);
+ Object output = null;
+ if(outputString != null && outputString.length() > 0) {
+ output = convertJSONStringToObject(outputString, methodToExecute.getReturnType());
+ }
+ //6
+ Class returnType = methodToExecute.getReturnType();
+ if(!returnType.getName().equalsIgnoreCase("void")){
+ //7
+ testCaseOutput.getData().put(testCasePath, output);
+ }
+ //8
+ //log it as passed, if there is any failures in assertions, assertions will update the status
+ //to failed
+ testStatus.logStatus(testCase, testCasePath, true);
+ executeAssertions(testCasePath, testCase);
+ }
+ }catch(Exception e){
+ boolean asserted = false;
+ if(testCase.getAssertions() != null) {
+ for(Assertion assertion : testCase.getAssertions()){
+ if(assertion.getExpectedOutput().equalsIgnoreCase("Exception")){
+ testStatus.logStatus(testCase, testCasePath, true);
+ asserted = true;
+ }
+ }
+ }
+ if(!asserted){
+ testStatus.logStatus(testCase, testCasePath, false, e.getMessage(), e);
+ }
+ }
+ }
+ }
+ System.out.println(testStatus.printTestStatus());
+ }
+
+
+ /**
+ * Populate necessayr argument values tat needs ot be populated before calling the method
+ * @return
+ */
+ protected Map getArgumentsForMethodCall(Method methodToExecute, TestCase testCase) throws Exception {
+
+ Map queryPathParameters = new HashMap();
+ if(testCase.getInput() != null) {
+ for(String inputParamName: testCase.getInput().keySet()){
+ Object value = getParamValue(testCase.getInput().get(inputParamName));
+ queryPathParameters.put(inputParamName, value);
+ }
+ }
+ return queryPathParameters;
+ }
+
+ /**
+ * Execute all assertions in the test case. If there are nay failures test case will be amrked as failed
+ * and logged into test status object.
+ * @param testCase
+ */
+ private void executeAssertions(String testCasePath, TestCase testCase) {
+ List assertions = testCase.getAssertions();
+ if(assertions != null) {
+ for(Assertion assertion: assertions){
+ try{
+ Object actualOutPut = getParamValue(assertion.getActualOutput());
+ Object expectedValue = getParamValue(assertion.getExpectedOutput());
+ boolean failed = false;
+ if(assertion.getCondition().equals(CONDITION_EQUAL)){
+ if(expectedValue.toString().equalsIgnoreCase("NULL") && actualOutPut == null){
+ failed = false;
+ }else{
+ if(expectedValue.getClass().isAssignableFrom(String.class)){
+ actualOutPut = actualOutPut.toString();
+ }
+ if(!actualOutPut.equals(expectedValue)){
+ failed = true;
+ }
+ }
+ }else if(assertion.getCondition().equals(CONDITION_NOT_EQUAL)){
+ if(expectedValue.toString().equalsIgnoreCase("EXCEPTION")){
+ //this means user is not expecting any exception, output can be null, if we have reached
+ // here means there are no exceptions hence we can call the assertion is passed.
+ failed = false;
+ }
+ else if(actualOutPut == null || actualOutPut.equals(expectedValue)){
+ failed = true;
+ }
+ }else{
+ float actual = new Float(actualOutPut.toString());
+ float expected = new Float(expectedValue.toString());
+ if(assertion.getCondition().equals(CONDITION_GREATER)){
+ if(!(actual > expected)){
+ failed = true;
+ }
+ }else if(assertion.getCondition().equals(CONDITION_LESSER)){
+ if(!(actual < expected)){
+ failed = true;
+ }
+ }else if(assertion.getCondition().equals(CONDITION_LESSER_EQUAL)){
+ if(!(actual <= expected)){
+ failed = true;
+ }
+ }else if(assertion.getCondition().equals(CONDITION_GREATER_EQUAL)){
+ if(!(actual >= expected)){
+ failed = true;
+ }
+ }
+ }
+ if(failed) {
+ if(actualOutPut == null) {
+ testStatus.logAssertionStatus(testCasePath, false, expectedValue.toString(), null, assertion.getCondition());
+ }else{
+ testStatus.logAssertionStatus(testCasePath, false, expectedValue.toString(), actualOutPut.toString(), assertion.getCondition());
+ }
+ } else{
+ if(actualOutPut == null) {
+ testStatus.logAssertionStatus(testCasePath, true, expectedValue.toString(), "null", assertion.getCondition());
+ }else{
+ testStatus.logAssertionStatus(testCasePath, true, expectedValue.toString(), actualOutPut.toString(), assertion.getCondition());
+ }
+ }
+ }catch(Exception e){
+ e.printStackTrace();
+ testStatus.logAssertionStatus(testCasePath, false, assertion.getExpectedOutput(), assertion.getActualOutput(), assertion.getCondition(), e);
+ }
+ }
+ }
+ }
+
+ /**
+ * creates the test case unique path
+ * @param suiteId
+ * @param caseId
+ * @return
+ */
+ private String testCasePath(int suiteId, int caseId) {
+ return suiteId + "." + caseId;
+ }
+
+ /**
+ * Read the values based on expression.
+ * The expression can refer the value in input data structure or outputs generated from executing the current or previous
+ * test cases or direct input. The Test script follow the syntax of ${output if it is referring output data,
+ * ${input if it is referring input data.
+ * @param name
+ * @return
+ * @throws Exception
+ */
+ private Object getParamValue(String name) throws Exception {
+ //this means we should use the input form previous steps or data file.
+ if(name.startsWith("$")){
+ //sample:"${input.userList[0].username}"
+ if(name.startsWith(INPUT_DATA_EXPRESSION_PREFIX)){
+ String expression = name.substring(INPUT_DATA_EXPRESSION_PREFIX.length(), name.length()-1);
+ boolean hasSize = false;
+ if(expression.endsWith("size")){
+ expression = expression.substring(0, expression.length()-5);
+ hasSize = true;
+ }
+ Object value = PropertyUtils.getProperty(testData, expression);
+ if(hasSize){
+ return MethodUtils.invokeMethod(value, "size", null);
+ }
+
+ return value;
+ }else if(name.startsWith(OUTPUT_DATA_EXPRESSION_PREFIX)) {
+ //sample: ${output(1.1.1).token}
+ String expression = name.substring(OUTPUT_DATA_EXPRESSION_PREFIX.length(), name.length()-1);
+ expression = "data"+expression;
+ boolean hasSize = false;
+ if(expression.endsWith("size")){
+ expression = expression.substring(0, expression.length()-5);
+ hasSize = true;
+ }
+ Object value = PropertyUtils.getProperty(testCaseOutput, expression);
+ if(hasSize){
+ return MethodUtils.invokeMethod(value, "size", null);
+ }
+ return value;
+ }else{
+ throw new RuntimeException("Input expression for parameter " + name + "is not as per valid syntax ");
+ }
+ }else{
+ return name;
+ }
+ }
+
+ /**
+ * Converts JSON string to object.
+ */
+ public static Object convertJSONStringToObject(String inputJSON, Class objectType) throws Exception {
+ boolean isArray = false;
+ boolean isList = false;
+ Class className = objectType;
+ String ObjectTypeName = objectType.getName();
+
+ //identify if the input is a array
+ if(ObjectTypeName.startsWith("[")){
+ isArray = true;
+ className = objectType.getComponentType();
+ }
+
+ //identify if the input is a list
+ if(List.class.isAssignableFrom(objectType)){
+ isList = true;
+ }
+
+ if(isArray || isList){
+ Object responseObject = mapper.readValue(inputJSON, TypeFactory.type(objectType));
+ return responseObject;
+ }else{
+ return APIInvoker.deserialize(inputJSON, className);
+ }
+ }
+
+ /**
+ * Converts JSON string to object.
+ */
+ public static String convertObjectToJSONString(Object input) throws Exception {
+ return APIInvoker.serialize(input);
+ }
+
+ /**
+ * Reads the test case results from standard out, converts that into java object and stores the value
+ * in test case output data so that the same can be used in subsequent test case execution
+ *
+ * First line fo response should be a status line with possible values as SUCCESS, ERROR
+ */
+ private String executeExternalTestCaseAndGetResult(String[] command) throws Exception {
+
+ Process p = Runtime.getRuntime().exec(command);
+
+ BufferedReader stdInput = new BufferedReader(new
+ InputStreamReader(p.getInputStream()));
+ StringBuilder output = new StringBuilder();
+ String s = null;
+ boolean isStatusLine = true;
+ String status = "";
+ while ((s = stdInput.readLine()) != null) {
+ System.out.println(s);
+ if(isStatusLine){
+ status = s;
+ if(status.equalsIgnoreCase("SUCCESS")||status.equalsIgnoreCase("OK") ) {
+ isStatusLine = false;
+ }
+ }else{
+ output.append(s);
+ }
+ }
+ if(status.equalsIgnoreCase("SUCCESS")||status.equalsIgnoreCase("OK") ) {
+ return output.toString();
+ }else{
+ APIException exception = (APIException)convertJSONStringToObject(output.toString(),
+ APIException.class);
+ throw exception;
+ }
+ }
+
+
+ /**
+ * Get the java command line that needs to be executed for runnign a test case
+ */
+ private String[] constructExternalCommand(String apiServer, String apiPackageName, String apiKey, String authToken,
+ String resource, String httpMethod, String suggestedMethodName, String queryAndPathParams,
+ String postData, String language) {
+ List command = new ArrayList();
+ if(language.equals(JAVA)){
+ command.add("./bin/runjava.sh");
+ command.add("com.wordnik.swagger.testframework.JavaTestCaseExecutor");
+ }else if (language.equals(PYTHON)){
+ command.add("../python/runtest.py ");
+ }else if (language.equals(ANDROID)){
+ command.add("../android/driver-test/bin/runandroid.sh");
+ command.add("com.wordnik.swagger.testframework.JavaTestCaseExecutor");
+ }
+
+ command.addAll(getCommandInputs(apiServer, apiPackageName, apiKey, authToken, resource, httpMethod,
+ suggestedMethodName, queryAndPathParams, postData,
+ language));
+ String[] commandArray = new String[command.size()];
+ command.toArray(commandArray);
+ return commandArray;
+ }
+
+ private List getCommandInputs(String apiServer, String apiPackageName, String apiKey, String authToken, String resource, String httpMethod,
+ String suggestedMethodName, String queryAndPathParams, String postData,
+ String language) {
+ List inputs = new ArrayList();
+ inputs.add(apiServer);
+ inputs.add(apiPackageName);
+ inputs.add(apiKey);
+ inputs.add(authToken);
+ inputs.add(resource);
+ inputs.add(httpMethod);
+ inputs.add(suggestedMethodName);
+ inputs.add(queryAndPathParams);
+ if(postData.equals("\"\"")){
+ inputs.add(postData);
+ }else{
+ inputs.add(postData);
+ }
+ return inputs;
+ }
+
+}
diff --git a/src/main/java/com/wordnik/swagger/testframework/Assertion.java b/src/main/java/com/wordnik/swagger/testframework/Assertion.java
new file mode 100644
index 0000000000..3187e76c5f
--- /dev/null
+++ b/src/main/java/com/wordnik/swagger/testframework/Assertion.java
@@ -0,0 +1,44 @@
+package com.wordnik.swagger.testframework;
+
+public class Assertion {
+
+ private String expectedOutput;
+
+ private String actualOutputWithOutputWrapper;
+
+ private String condition;
+
+ private String actualOutput;
+
+ public String getExpectedOutput() {
+ return expectedOutput;
+ }
+
+ public void setExpectedOutput(String expression) {
+ this.expectedOutput = expression;
+ }
+
+ public String getCondition() {
+ return condition;
+ }
+
+ public void setCondition(String condition) {
+ this.condition = condition;
+ }
+
+ public String getActualOutput() {
+ return actualOutput;
+ }
+
+ public void setActualOutput(String value) {
+ this.actualOutput = value;
+ }
+
+ public String getActualOutputWithOutputWrapper() {
+ return actualOutputWithOutputWrapper;
+ }
+
+ public void setActualOutputWithOutputWrapper(String actualOutputWithOutputWrapper) {
+ this.actualOutputWithOutputWrapper = actualOutputWithOutputWrapper;
+ }
+}
diff --git a/src/main/java/com/wordnik/swagger/testframework/JavaTestCaseExecutor.java b/src/main/java/com/wordnik/swagger/testframework/JavaTestCaseExecutor.java
new file mode 100644
index 0000000000..6102100479
--- /dev/null
+++ b/src/main/java/com/wordnik/swagger/testframework/JavaTestCaseExecutor.java
@@ -0,0 +1,192 @@
+package com.wordnik.swagger.testframework;
+
+import com.wordnik.swagger.codegen.config.java.JavaDataTypeMappingProvider;
+import com.wordnik.swagger.runtime.annotations.MethodArgumentNames;
+import com.wordnik.swagger.codegen.config.common.CamelCaseNamingPolicyProvider;
+import com.wordnik.swagger.runtime.common.APIInvoker;
+import com.wordnik.swagger.runtime.common.ApiKeyAuthTokenBasedSecurityHandler;
+import com.wordnik.swagger.runtime.exception.APIException;
+import com.wordnik.swagger.runtime.exception.APIExceptionCodes;
+import org.apache.commons.beanutils.BeanUtils;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Instance of this class runs single test case
+ * User: ramesh
+ * Date: 4/22/11
+ * Time: 7:32 AM
+ */
+public class JavaTestCaseExecutor {
+
+ private CamelCaseNamingPolicyProvider namingPolicyProvider = new CamelCaseNamingPolicyProvider();
+ private JavaDataTypeMappingProvider datatypeMppingProvider = new JavaDataTypeMappingProvider();
+
+ /**
+ * Follow the following argument pattern
+ * Arguments in calling this method:
+ * ApiServerURL
+ *
+ * @param args
+ * @throws Exception
+ */
+ public static void main(String[] args) throws Exception {
+
+
+ JavaTestCaseExecutor runner = new JavaTestCaseExecutor();
+ String apiServer = args[0];
+ String servicePackageName = args[1];
+ String apiKey = args[2];
+ String authToken = args[3];
+ String resource = args[4];
+ String httpMethod = args[5];
+ String suggestedMethodName = args[6];
+ Map queryAndPathParameters = new HashMap();
+ String postData = null;
+ if(args.length > 7 && args[7].length() > 0){
+ String[] qpTuple = args[7].split("~");
+ for(String tuple: qpTuple){
+ String[] nameValue = tuple.split("=");
+ queryAndPathParameters.put(nameValue[0], nameValue[1]);
+ }
+ }
+ if(args.length > 8 ){
+ postData = args[8];
+ }
+ queryAndPathParameters.put("authToken", authToken);
+
+ ApiKeyAuthTokenBasedSecurityHandler securityHandler = new ApiKeyAuthTokenBasedSecurityHandler(apiKey, authToken);
+ APIInvoker.initialize(securityHandler, apiServer, true);
+
+ runner.executeTestCase(resource, servicePackageName, suggestedMethodName, queryAndPathParameters, postData);
+
+ }
+
+ private void executeTestCase(String resourceName, String servicePackageName, String suggestedName,
+ Map queryAndPathParameters, String postData) {
+
+ String className = namingPolicyProvider.getServiceName(resourceName);
+ String methodName = suggestedName;
+ //3
+ try {
+ Class apiClass = Class.forName(servicePackageName + "." + className);
+ Method[] methods = apiClass.getMethods();
+ Method methodToExecute = null;
+ for(Method method : methods){
+ if(method.getName().equals(methodName)){
+ methodToExecute = method;
+ break;
+ }
+ }
+
+ if(methodToExecute != null) {
+ //4
+ Object[] arguments = populateArgumentsForTestCaseExecution(methodToExecute, queryAndPathParameters,
+ postData, className, resourceName);
+ Object output = null;
+ if(arguments != null && arguments.length > 0){
+ //5
+ output = methodToExecute.invoke(null, arguments);
+ }else{
+ //5
+ output = methodToExecute.invoke(null);
+ }
+ //6
+ System.out.println("SUCCESS");
+ System.out.println(APITestRunner.convertObjectToJSONString(output));
+
+ }
+ }catch(APIException e){
+ StringWriter sWriter = new StringWriter();
+ PrintWriter writer = new PrintWriter(sWriter);
+ e.printStackTrace(writer);
+ System.out.println(sWriter.getBuffer().toString());
+ System.out.println(e.getMessage());
+ System.out.println("ERROR");
+ try{
+ System.out.println(APITestRunner.convertObjectToJSONString(e));
+ }catch(Exception ex){
+ ex.printStackTrace();
+ }
+ } catch(Exception e){
+ StringWriter sWriter = new StringWriter();
+ PrintWriter writer = new PrintWriter(sWriter);
+ e.printStackTrace(writer);
+ System.out.println(sWriter.getBuffer().toString());
+ e.printStackTrace();
+ System.out.println("ERROR");
+ try{
+ APIException apiException = new APIException(APIExceptionCodes.SYSTEM_EXCEPTION,
+ e.getMessage());
+ System.out.println(APITestRunner.convertObjectToJSONString(apiException));
+ }catch(Exception ex){
+ ex.printStackTrace();
+ }
+ }
+ }
+
+ /**
+ * Gets the list of input query and path parameters and post data vlues and covenrt them to arguments that
+ * can be used for calling the method. This logic will be different in each driver language depends on how method
+ * input arguments are created.
+ */
+ private Object[] populateArgumentsForTestCaseExecution(Method methodToExecute, Map queryAndPathParameters,
+ String postData, String serviceName, String resourcePath) throws Exception {
+ MethodArgumentNames argNames = methodToExecute.getAnnotation(MethodArgumentNames.class);
+ String[] argNamesArray = null;
+ if(argNames != null && argNames.value().length() > 0) {
+ argNamesArray = argNames.value().split(",");
+ }
+ Class[] argTypesArray = methodToExecute.getParameterTypes();
+ Object output = null;
+ String inputClassName = namingPolicyProvider.getInputObjectName(serviceName, resourcePath);
+
+ if(argNamesArray != null && argNamesArray.length > 0){
+ Object[] arguments = new Object[argNamesArray.length];
+
+ for(int i=0; i < argNamesArray.length; i++){
+ Object argument = null;
+ //if the method takes input model instead of individual arguments, convert individual arguments into input model object
+ if(argTypesArray[i].getName().equalsIgnoreCase(inputClassName)){
+ argument = populateInputModelObject(argTypesArray[i], queryAndPathParameters);
+ }else if(datatypeMppingProvider.isPrimitiveType(argTypesArray[i].getName())){
+ argument = queryAndPathParameters.get(argNamesArray[i].trim());
+ }else if (argNamesArray[i].trim().equals(APITestRunner.POST_PARAM_NAME)){
+ argument = APITestRunner.convertJSONStringToObject(postData, argTypesArray[i]);
+ }else{
+ argument = APITestRunner.convertJSONStringToObject(queryAndPathParameters.get(argNamesArray[i].trim()), argTypesArray[i]);
+ }
+ arguments[i] = argument;
+ }
+ return arguments;
+ }
+ return null;
+ }
+
+ /**
+ * Populates the swagger input model object.
+ *
+ * Input model is created when number of inputs to a method exceed certain limit.
+ * @param inputDefinitions
+ * @return
+ */
+ private Object populateInputModelObject(Class swaggerInputClass, Map inputDefinitions) throws Exception {
+ Object object = swaggerInputClass.getConstructor().newInstance();
+ Method[] methods = swaggerInputClass.getMethods();
+ for(Method method : methods){
+ if(method.getName().startsWith("get")){
+ String methodName = method.getName();
+ String fieldName = methodName.substring(3);
+ fieldName = namingPolicyProvider.applyMethodNamingPolicy(fieldName);
+ Object value = inputDefinitions.get(fieldName);
+ BeanUtils.setProperty(object, fieldName, value);
+ }
+ }
+ return object;
+ }
+
+}
diff --git a/src/main/java/com/wordnik/swagger/testframework/TestCase.java b/src/main/java/com/wordnik/swagger/testframework/TestCase.java
new file mode 100644
index 0000000000..02ea7d9ef5
--- /dev/null
+++ b/src/main/java/com/wordnik/swagger/testframework/TestCase.java
@@ -0,0 +1,75 @@
+package com.wordnik.swagger.testframework;
+
+import java.util.List;
+import java.util.Map;
+
+
+/**
+ * Test case executes individual test case. A given test suite has one or many test cases.
+ * @author ramesh
+ *
+ */
+public class TestCase {
+
+ private String name;
+
+ private int id;
+
+ private int resourceId;
+
+ private Map input;
+
+ private String referenceInput;
+
+ private List assertions;
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public int getId() {
+ return id;
+ }
+
+ public void setId(int id) {
+ this.id = id;
+ }
+
+ public int getResourceId() {
+ return resourceId;
+ }
+
+ public void setResourceId(int resourceId) {
+ this.resourceId = resourceId;
+ }
+
+ public Map getInput() {
+ return input;
+ }
+
+ public void setInput(Map input) {
+ this.input = input;
+ }
+
+ public String getReferenceInput() {
+ return referenceInput;
+ }
+
+ public void setReferenceInput(String referenceInput) {
+ this.referenceInput = referenceInput;
+ }
+
+ public List getAssertions() {
+ return assertions;
+ }
+
+ public void setAssertions(List assertions) {
+ this.assertions = assertions;
+ }
+
+
+}
diff --git a/src/main/java/com/wordnik/swagger/testframework/TestOutput.java b/src/main/java/com/wordnik/swagger/testframework/TestOutput.java
new file mode 100644
index 0000000000..0c4973dca4
--- /dev/null
+++ b/src/main/java/com/wordnik/swagger/testframework/TestOutput.java
@@ -0,0 +1,18 @@
+package com.wordnik.swagger.testframework;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class TestOutput {
+
+ Map data = new HashMap();
+
+ public Map getData() {
+ return data;
+ }
+
+ public void setData(Map data) {
+ this.data = data;
+ }
+
+}
diff --git a/src/main/java/com/wordnik/swagger/testframework/TestPackage.java b/src/main/java/com/wordnik/swagger/testframework/TestPackage.java
new file mode 100644
index 0000000000..8d34e6a1c1
--- /dev/null
+++ b/src/main/java/com/wordnik/swagger/testframework/TestPackage.java
@@ -0,0 +1,49 @@
+package com.wordnik.swagger.testframework;
+
+import java.util.List;
+
+public class TestPackage {
+
+ private List resources;
+
+ private List testSuites;
+
+
+ public List getResources() {
+ return resources;
+ }
+
+ public void setResources(List resources) {
+ this.resources = resources;
+ }
+
+ public List getTestSuites() {
+ return testSuites;
+ }
+
+ public void setTestSuites(List testSuites) {
+ this.testSuites = testSuites;
+ }
+
+ public TestCase getTestCaseById(int suiteId, int caseId) {
+ TestSuite aSuite = null;
+ TestCase aTestCase = null;
+ if(this.getTestSuites() != null){
+ for(TestSuite suite : getTestSuites()){
+ if(suite.getId() == suiteId){
+ aSuite = suite;
+ break;
+ }
+ }
+ }
+ if(aSuite != null){
+ for(TestCase testCase : aSuite.getTestCases()){
+ if(testCase.getId() == caseId){
+ aTestCase = testCase;
+ break;
+ }
+ }
+ }
+ return aTestCase;
+ }
+}
diff --git a/src/main/java/com/wordnik/swagger/testframework/TestResource.java b/src/main/java/com/wordnik/swagger/testframework/TestResource.java
new file mode 100644
index 0000000000..303e6bde1a
--- /dev/null
+++ b/src/main/java/com/wordnik/swagger/testframework/TestResource.java
@@ -0,0 +1,55 @@
+package com.wordnik.swagger.testframework;
+
+public class TestResource {
+
+ private int id;
+
+ private String name;
+
+ private String httpMethod;
+
+ private String path;
+
+ private String suggestedMethodName;
+
+ public int getId() {
+ return id;
+ }
+
+ public void setId(int id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getHttpMethod() {
+ return httpMethod;
+ }
+
+ public void setHttpMethod(String method) {
+ this.httpMethod = method;
+ }
+
+ public String getPath() {
+ return path;
+ }
+
+ public void setPath(String path) {
+ this.path = path;
+ }
+
+ public String getSuggestedMethodName() {
+ return suggestedMethodName;
+ }
+
+ public void setSuggestedMethodName(String suggestedMethodName) {
+ this.suggestedMethodName = suggestedMethodName;
+ }
+
+}
diff --git a/src/main/java/com/wordnik/swagger/testframework/TestStatus.java b/src/main/java/com/wordnik/swagger/testframework/TestStatus.java
new file mode 100644
index 0000000000..5708f01303
--- /dev/null
+++ b/src/main/java/com/wordnik/swagger/testframework/TestStatus.java
@@ -0,0 +1,165 @@
+package com.wordnik.swagger.testframework;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Stores the status of the tests executed.
+ * @author ramesh
+ *
+ */
+public class TestStatus {
+
+ private int totalTestCaseCount;
+
+ private int failedTestCaseCount;
+
+ private List testCaseStatuses = new ArrayList();
+ private Map testCaseStatusMap = new HashMap();
+
+ public void logStatus(TestCase testCase, String testCasePath, boolean passed){
+ logStatus(testCase, testCasePath, passed, null, null);
+ }
+
+ public void logStatus(TestCase testCase, String testCasePath, boolean passed, String failureDescription){
+ logStatus(testCase, testCasePath, passed, failureDescription, null);
+ }
+
+ public void logStatus(TestCase testCase, String testCasePath, boolean passed, String failureDescription, Throwable exception){
+ TestCaseStatus status = new TestCaseStatus(testCase, testCasePath, passed, failureDescription, exception);
+ testCaseStatuses.add(status);
+ testCaseStatusMap.put(testCasePath, status);
+ totalTestCaseCount++;
+ if(!passed){
+ failedTestCaseCount++;
+ }
+
+ }
+
+ public void logAssertionStatus(String path, boolean passed, String expectedValue, String actualValue, String condition) {
+ logAssertionStatus(path, passed, expectedValue, actualValue, condition, null);
+ }
+
+ public void logAssertionStatus(String path, boolean passed, String expectedValue, String actualValue, String condition, Throwable stack) {
+ TestCaseStatus status = testCaseStatusMap.get(path);
+ if(!passed && status.passed) {
+ status.passed = false;
+ failedTestCaseCount++;
+ }
+ status.addAssertionStatus(passed, expectedValue, actualValue, condition, stack);
+ }
+
+ public String printTestStatus() {
+ StringBuilder output = new StringBuilder();
+ output.append("Summary --> Total Test Cases: " + totalTestCaseCount + " Failed Test Cases: " + failedTestCaseCount);
+ output.append("\nDetails: \n");
+ for(TestCaseStatus status : testCaseStatuses) {
+ output.append(status.getStatusDescription() + "\n");
+ }
+ return output.toString();
+ }
+
+ /**
+ * Indicates if there are any failured in executing the test cases.
+ * @return
+ */
+ public boolean hasFailures() {
+ return (failedTestCaseCount > 0);
+ }
+
+ /**
+ * Inner class Stores the details on individual test cases.
+ * @author ramesh
+ *
+ */
+ private class TestCaseStatus {
+
+ String testCasePath;
+
+ boolean passed;
+
+ String failureDescription;
+
+ Throwable exceptionStack;
+
+ TestCase testCase;
+
+ List assertionStatuses = new ArrayList();
+
+ public TestCaseStatus(TestCase testCase, String testCasePath, boolean passed, String failureDescription,
+ Throwable exceptionStack) {
+ this.exceptionStack = exceptionStack;
+ this.testCase = testCase;
+ this.passed = passed;
+ this.failureDescription = failureDescription;
+ this.testCasePath = testCasePath;
+ }
+
+ public void addAssertionStatus(boolean passed, String expectedValue, String actualValue, String condition, Throwable stack) {
+ String assertionDesc = expectedValue + " " + condition + " " + actualValue;
+ AssertionStatus status = new AssertionStatus(assertionDesc, passed, stack);
+ assertionStatuses.add(status);
+ if(!passed){
+ this.passed = false;
+ }
+ }
+
+ public String getStatusDescription(){
+ StringBuilder output = new StringBuilder();
+ if(passed){
+ output.append(testCasePath);
+ output.append(" : ");
+ output.append(testCase.getName());
+ output.append(" : ");
+ output.append(" passed ");
+ output.append(" \n ");
+ }else{
+ output.append(testCasePath);
+ output.append(" : ");
+ output.append(testCase.getName());
+ output.append(" : ");
+ output.append(" failed ");
+ output.append(" \n ");
+ if ( failureDescription != null) {
+ output.append(" failure message : ");
+ output.append(failureDescription + "\n");
+ }
+ if(exceptionStack != null){
+ StringWriter out = new StringWriter();
+ PrintWriter writer = new PrintWriter(out);
+ exceptionStack.printStackTrace(writer);
+ output.append(out.toString());
+ }
+ for(AssertionStatus status:assertionStatuses){
+ if(!status.passed) {
+ output.append(status.getAssertionDescription()+"\n");
+ }
+ }
+ }
+ return output.toString();
+ }
+ }
+
+ private class AssertionStatus {
+
+ String assertionDescription;
+
+ boolean passed;
+
+ Throwable exceptionStack;
+
+ public AssertionStatus(String description, boolean passed, Throwable stack){
+ this.assertionDescription = description;
+ this.passed = passed;
+ this.exceptionStack = stack;
+ }
+
+ public String getAssertionDescription() {
+ return assertionDescription + " : " + (passed ? "Passed " : " Failed ") + ((exceptionStack !=null) ? exceptionStack.getMessage() : "" );
+ }
+ }
+}
diff --git a/src/main/java/com/wordnik/swagger/testframework/TestSuite.java b/src/main/java/com/wordnik/swagger/testframework/TestSuite.java
new file mode 100644
index 0000000000..96357cb1e2
--- /dev/null
+++ b/src/main/java/com/wordnik/swagger/testframework/TestSuite.java
@@ -0,0 +1,45 @@
+package com.wordnik.swagger.testframework;
+
+import java.util.List;
+
+/**
+ * Test suite contains collection of test cases.
+ *
+ * As a best practice write one test suite for each resource
+ * @author ramesh
+ *
+ */
+public class TestSuite {
+
+ private String name ;
+
+ private int id;
+
+ private List testCases;
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public int getId() {
+ return id;
+ }
+
+ public void setId(int id) {
+ this.id = id;
+ }
+
+ public List getTestCases() {
+ return testCases;
+ }
+
+ public void setTestCases(List testCases) {
+ this.testCases = testCases;
+ }
+
+
+}