diff --git a/modules/swagger-codegen/src/main/java/com/wordnik/swagger/codegen/Codegen.java b/modules/swagger-codegen/src/main/java/com/wordnik/swagger/codegen/Codegen.java index c9e39c7f2f3..7ffb1ba1547 100644 --- a/modules/swagger-codegen/src/main/java/com/wordnik/swagger/codegen/Codegen.java +++ b/modules/swagger-codegen/src/main/java/com/wordnik/swagger/codegen/Codegen.java @@ -12,26 +12,33 @@ import java.io.File; import java.util.*; public class Codegen extends DefaultGenerator { + static Map configs = new HashMap(); + static String configString; + static { + List extensions = getExtensions(); + StringBuilder sb = new StringBuilder(); + + for(CodegenConfig config : extensions) { + if(sb.toString().length() != 0) + sb.append(", "); + sb.append(config.getName()); + configs.put(config.getName(), config); + configString = sb.toString(); + } + } + static String debugInfoOptions = "\nThe following additional debug options are available for all codegen targets:" + "\n -DdebugSwagger prints the swagger specification as interpreted by the codegen" + "\n -DdebugModels prints models passed to the template engine" + "\n -DdebugOperations prints operations passed to the template engine" + "\n -DdebugSupportingFiles prints additional data passed to the template engine"; public static void main(String[] args) { - List extensions = getExtensions(); - Map configs = new HashMap(); StringBuilder sb = new StringBuilder(); - for(CodegenConfig config : extensions) { - if(sb.toString().length() != 0) - sb.append(", "); - sb.append(config.getName()); - configs.put(config.getName(), config); - } Options options = new Options(); options.addOption("h", "help", false, "shows this message"); - options.addOption("l", "lang", true, "client language to generate.\nAvailable languages include:\n\t[" + sb.toString() + "]"); + options.addOption("l", "lang", true, "client language to generate.\nAvailable languages include:\n\t[" + configString + "]"); options.addOption("o", "output", true, "where to write the generated files"); options.addOption("i", "input-spec", true, "location of the swagger spec, as URL or file"); options.addOption("t", "template-dir", true, "folder containing the template files"); @@ -53,12 +60,12 @@ public class Codegen extends DefaultGenerator { return; } if (cmd.hasOption("l")) - clientOptInput.setConfig(getConfig(cmd.getOptionValue("l"), configs)); + clientOptInput.setConfig(getConfig(cmd.getOptionValue("l"))); if (cmd.hasOption("o")) clientOptInput.getConfig().setOutputDir(cmd.getOptionValue("o")); if (cmd.hasOption("h")) { if(cmd.hasOption("l")) { - config = getConfig(String.valueOf(cmd.getOptionValue("l")), configs); + config = getConfig(String.valueOf(cmd.getOptionValue("l"))); if(config != null) { options.addOption("h", "help", true, config.getHelp()); usage(options); @@ -103,7 +110,7 @@ public class Codegen extends DefaultGenerator { formatter.printHelp( "Codegen", options ); } - static CodegenConfig getConfig(String name, Map configs) { + public static CodegenConfig getConfig(String name) { if(configs.containsKey(name)) { return configs.get(name); } diff --git a/modules/swagger-codegen/src/main/java/com/wordnik/swagger/codegen/DefaultGenerator.java b/modules/swagger-codegen/src/main/java/com/wordnik/swagger/codegen/DefaultGenerator.java index a6ce42f7125..33f29c643a1 100644 --- a/modules/swagger-codegen/src/main/java/com/wordnik/swagger/codegen/DefaultGenerator.java +++ b/modules/swagger-codegen/src/main/java/com/wordnik/swagger/codegen/DefaultGenerator.java @@ -26,13 +26,14 @@ public class DefaultGenerator implements Generator { return this; } - public void generate() { + public List generate() { if(swagger == null || config == null) { throw new RuntimeException("missing swagger input or config!"); } if(System.getProperty("debugSwagger") != null) { Json.prettyPrint(swagger); } + List files = new ArrayList(); try { config.processOpts(); if(swagger.getInfo() != null) { @@ -94,6 +95,7 @@ public class DefaultGenerator implements Generator { .defaultValue("") .compile(template); writeToFile(filename, tmpl.execute(models)); + files.add(new File(filename)); } } } @@ -131,6 +133,7 @@ public class DefaultGenerator implements Generator { .compile(template); writeToFile(filename, tmpl.execute(operation)); + files.add(new File(filename)); } } if(System.getProperty("debugOperations") != null) { @@ -187,11 +190,13 @@ public class DefaultGenerator implements Generator { .compile(template); writeToFile(outputFilename, tmpl.execute(bundle)); + files.add(new File(outputFilename)); } else { String template = readTemplate(config.templateDir() + File.separator + support.templateFile); FileUtils.writeStringToFile(new File(outputFilename), template); System.out.println("copying file to " + outputFilename); + files.add(new File(outputFilename)); } } @@ -200,6 +205,7 @@ public class DefaultGenerator implements Generator { catch (Exception e) { e.printStackTrace(); } + return files; } public Map> processPaths(Map paths) { diff --git a/modules/swagger-codegen/src/main/java/com/wordnik/swagger/codegen/Generator.java b/modules/swagger-codegen/src/main/java/com/wordnik/swagger/codegen/Generator.java index 929ae580991..0dfbab86a0a 100644 --- a/modules/swagger-codegen/src/main/java/com/wordnik/swagger/codegen/Generator.java +++ b/modules/swagger-codegen/src/main/java/com/wordnik/swagger/codegen/Generator.java @@ -2,7 +2,10 @@ package com.wordnik.swagger.codegen; import com.wordnik.swagger.models.Swagger; +import java.io.File; +import java.util.List; + public interface Generator { Generator opts(ClientOptInput opts); - void generate(); + List generate(); } \ No newline at end of file diff --git a/modules/swagger-generator/pom.xml b/modules/swagger-generator/pom.xml new file mode 100644 index 00000000000..7f770c34de3 --- /dev/null +++ b/modules/swagger-generator/pom.xml @@ -0,0 +1,241 @@ + + 4.0.0 + + com.wordnik + swagger-codegen-project + 2.1.0-SNAPSHOT + ../.. + + com.wordnik + swagger-generator + war + swagger-generator + 1.0.0 + + src/main/java + + + maven-dependency-plugin + + + package + + copy-dependencies + + + ${project.build.directory}/lib + + + + + + net.alchim31.maven + scala-maven-plugin + + + scala-compile-first + process-resources + + add-source + compile + + + + scala-test-compile + process-test-resources + + testCompile + + + + + + incremental + + + -Xmx384m + + + -target:jvm-1.6 + -deprecation + + + + run-scalatest + org.scalatest.tools.Runner + + -p + ${project.build.testOutputDirectory} + + + -Xmx512m + + + + + + + org.apache.maven.plugins + maven-war-plugin + 2.1.1 + + + maven-failsafe-plugin + 2.6 + + + + integration-test + verify + + + + + + org.eclipse.jetty + jetty-maven-plugin + ${jetty-version} + + + / + + target/${project.artifactId}-${project.version} + 8079 + stopit + + 8002 + 60000 + + + + + start-jetty + pre-integration-test + + run + + + 0 + true + + + + stop-jetty + post-integration-test + + stop + + + + + + + + + com.wordnik + swagger-jersey2-jaxrs + ${swagger-core-version} + + + com.wordnik + swagger-codegen + ${project.parent.version} + + + ch.qos.logback + logback-classic + ${logback-version} + + + ch.qos.logback + logback-core + ${logback-version} + + + org.scalatest + scalatest_2.10 + ${scala-test-version} + test + + + junit + junit + ${junit-version} + test + + + javax.servlet + servlet-api + ${servlet-api-version} + + + org.glassfish.jersey.containers + jersey-container-servlet-core + ${jersey2-version} + + + org.glassfish.jersey.media + jersey-media-multipart + ${jersey2-version} + + + net.lingala.zip4j + zip4j + ${zip-version} + + + org.eclipse.jetty + jetty-server + ${jetty-version} + + + org.eclipse.jetty + jetty-start + ${jetty-version} + + + org.eclipse.jetty + jetty-xml + ${jetty-version} + + + org.eclipse.jetty + jetty-webapp + ${jetty-version} + + + org.eclipse.jetty + jetty-deploy + ${jetty-version} + + + org.scala-lang + scala-library + ${scala-version} + + + org.scala-lang + scala-compiler + ${scala-version} + test + + + + 2.3.4 + 2.5 + 1.0.1 + 4.8.1 + 1.0.0 + 2.4 + 1.6.3 + 2.5 + 1.3.2 + 9.0.7.v20131107 + 2.4.1 + 1.5.0-SNAPSHOT + + 3.1.5 + 2.10.0 + 1.9 + + diff --git a/modules/swagger-generator/sample.json b/modules/swagger-generator/sample.json new file mode 100644 index 00000000000..81963eb85fc --- /dev/null +++ b/modules/swagger-generator/sample.json @@ -0,0 +1,479 @@ +{ + "opts": {}, + "spec": { + "swagger": "2.0", + "title": "Petstore Sample API", + "info": { + "description": "A sample API that uses a petstore as an example to demonstrate features in the swagger-2.0 specification", + "title": "Petstore Sample API", + "contact": { + "name": "Wordnik API Team" + }, + "license": { + "name": "MIT", + "url": "http://github.com/gruntjs/grunt/blob/master/LICENSE-MIT" + } + }, + "host": "petstore.swagger.wordnik.com", + "basePath": "/api", + "paths": { + "/pet": { + "put": { + "tags": [ + "pet" + ], + "parameters": [ + { + "name": "body", + "in": "body", + "description": "Pet object that needs to be added to the store", + "required": false, + "schema": { + "$ref": "#/definitions/Pet" + } + } + ], + "responses": { + "405": { + "description": "Validation exception" + }, + "404": { + "description": "Pet not found" + }, + "400": { + "description": "Invalid ID supplied" + } + } + }, + "post": { + "tags": [ + "pet" + ], + "parameters": [ + { + "name": "body", + "in": "body", + "description": "Pet object that needs to be added to the store", + "required": false, + "schema": { + "$ref": "#/definitions/Tag" + } + } + ], + "responses": { + "405": { + "description": "Invalid input" + } + } + } + }, + "/user/createWithList": { + "post": { + "tags": [ + "user" + ], + "parameters": [ + { + "name": "body", + "in": "body", + "description": "List of user object", + "required": false + } + ] + } + }, + "/store/order/{orderId}": { + "get": { + "tags": [ + "store" + ], + "parameters": [ + { + "name": "orderId", + "in": "path", + "description": "ID of pet that needs to be fetched", + "required": true, + "type": "string" + } + ], + "responses": { + "404": { + "description": "Order not found" + }, + "400": { + "description": "Invalid ID supplied" + } + } + }, + "delete": { + "tags": [ + "store" + ], + "parameters": [ + { + "name": "orderId", + "in": "path", + "description": "ID of the order that needs to be deleted", + "required": true, + "type": "string" + } + ], + "responses": { + "404": { + "description": "Order not found" + }, + "400": { + "description": "Invalid ID supplied" + } + } + } + }, + "/user/createWithArray": { + "post": { + "tags": [ + "user" + ], + "parameters": [ + { + "name": "body", + "in": "body", + "description": "List of user object", + "required": false, + "schema": { + "$ref": "#/definitions/User" + } + } + ] + } + }, + "/store/order": { + "post": { + "tags": [ + "store" + ], + "parameters": [ + { + "name": "body", + "in": "body", + "description": "order placed for purchasing the pet", + "required": false, + "schema": { + "$ref": "#/definitions/Order" + } + } + ], + "responses": { + "400": { + "description": "Invalid Order" + } + } + } + }, + "/pet/findByStatus": { + "get": { + "tags": [ + "pet" + ], + "parameters": [ + { + "name": "status", + "in": "query", + "description": "Status values that need to be considered for filter", + "required": false, + "type": "string" + } + ], + "responses": { + "400": { + "description": "Invalid status value" + } + } + } + }, + "/user/{username}": { + "get": { + "tags": [ + "user" + ], + "parameters": [ + { + "name": "username", + "in": "path", + "description": "The name that needs to be fetched. Use user1 for testing. ", + "required": true, + "type": "string" + } + ], + "responses": { + "404": { + "description": "User not found" + }, + "400": { + "description": "Invalid username supplied" + } + } + }, + "put": { + "tags": [ + "user" + ], + "parameters": [ + { + "name": "username", + "in": "path", + "description": "name that need to be deleted", + "required": true, + "type": "string" + }, + { + "name": "body", + "in": "body", + "description": "Updated user object", + "required": false, + "schema": { + "$ref": "#/definitions/User" + } + } + ], + "responses": { + "404": { + "description": "User not found" + }, + "400": { + "description": "Invalid user supplied" + } + } + }, + "delete": { + "tags": [ + "user" + ], + "parameters": [ + { + "name": "username", + "in": "path", + "description": "The name that needs to be deleted", + "required": true, + "type": "string" + } + ], + "responses": { + "404": { + "description": "User not found" + }, + "400": { + "description": "Invalid username supplied" + } + } + } + }, + "/pet/findByTags": { + "get": { + "tags": [ + "pet" + ], + "parameters": [ + { + "name": "tags", + "in": "query", + "description": "Tags to filter by", + "required": false, + "type": "string" + } + ], + "responses": { + "400": { + "description": "Invalid tag value" + } + } + } + }, + "/user": { + "post": { + "tags": [ + "user" + ], + "parameters": [ + { + "name": "body", + "in": "body", + "description": "Created user object", + "required": false, + "schema": { + "$ref": "#/definitions/User" + } + } + ] + } + }, + "/pet/{petId}": { + "get": { + "tags": [ + "pet" + ], + "parameters": [ + { + "name": "petId", + "in": "path", + "description": "ID of pet that needs to be fetched", + "required": true, + "type": "string" + } + ], + "responses": { + "404": { + "description": "Pet not found" + }, + "400": { + "description": "Invalid ID supplied" + } + } + } + }, + "/user/logout": { + "get": { + "tags": [ + "user" + ] + } + }, + "/user/login": { + "get": { + "tags": [ + "user" + ], + "parameters": [ + { + "name": "username", + "in": "query", + "description": "The user name for login", + "required": false, + "type": "string" + }, + { + "name": "password", + "in": "query", + "description": "The password for login in clear text", + "required": false, + "type": "string" + } + ], + "responses": { + "400": { + "description": "Invalid username/password supplied" + } + } + } + } + }, + "definitions": { + "User": { + "properties": { + "id": { + "type": "integer", + "format": "int32" + }, + "lastName": { + "type": "string" + }, + "username": { + "type": "string" + }, + "phone": { + "type": "string" + }, + "email": { + "type": "string" + }, + "userStatus": { + "type": "integer", + "format": "int32" + }, + "firstName": { + "type": "string" + }, + "password": { + "type": "string" + } + } + }, + "Category": { + "properties": { + "id": { + "type": "integer", + "format": "int32" + }, + "name": { + "type": "string" + } + } + }, + "Pet": { + "properties": { + "id": { + "type": "integer", + "format": "int32" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/definitions/Tag" + } + }, + "category": { + "$ref": "#/definitions/Category" + }, + "status": { + "type": "string" + }, + "name": { + "type": "string" + }, + "photoUrls": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "Tag": { + "properties": { + "id": { + "type": "integer", + "format": "int32" + }, + "name": { + "type": "string" + } + } + }, + "Order": { + "properties": { + "id": { + "type": "integer", + "format": "int32" + }, + "petId": { + "type": "integer", + "format": "int32" + }, + "status": { + "type": "string" + }, + "complete": { + "type": "boolean" + }, + "quantity": { + "type": "integer", + "format": "int32" + }, + "shipDate": { + "type": "string", + "format": "date-time" + } + } + } + } + } +} \ No newline at end of file diff --git a/modules/swagger-generator/src/main/java/com/wordnik/swagger/generator/Bootstrap.java b/modules/swagger-generator/src/main/java/com/wordnik/swagger/generator/Bootstrap.java new file mode 100644 index 00000000000..d71cb887210 --- /dev/null +++ b/modules/swagger-generator/src/main/java/com/wordnik/swagger/generator/Bootstrap.java @@ -0,0 +1,44 @@ +/** + * Copyright 2014 Reverb, 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.generator; + +import com.wordnik.swagger.models.*; + +import javax.servlet.http.HttpServlet; +import javax.servlet.ServletContext; +import javax.servlet.ServletConfig; +import javax.servlet.ServletException; + +public class Bootstrap extends HttpServlet { + public void init(ServletConfig config) throws ServletException { + Info info = new Info() + .title("Swagger Generator") + .description("This is an online swagger codegen server. You can find out more " + + "at https://github.com/wordnik/swagger-generator or on irc.freenode.net, #swagger." + + "http://helloreverb.com/terms/") + .termsOfService("http://helloreverb.com/terms/") + .contact(new Contact() + .email("apiteam@swagger.io")) + .license(new License() + .name("Apache 2.0") + .url("http://www.apache.org/licenses/LICENSE-2.0.html")); + + ServletContext context = config.getServletContext(); + Swagger swagger = new Swagger().info(info); + context.setAttribute("swagger", swagger); + } +} diff --git a/modules/swagger-generator/src/main/java/com/wordnik/swagger/generator/exception/ApiException.java b/modules/swagger-generator/src/main/java/com/wordnik/swagger/generator/exception/ApiException.java new file mode 100644 index 00000000000..075531421e1 --- /dev/null +++ b/modules/swagger-generator/src/main/java/com/wordnik/swagger/generator/exception/ApiException.java @@ -0,0 +1,25 @@ +/** + * Copyright 2014 Reverb, 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.generator.exception; + +public class ApiException extends Exception{ + private int code; + public ApiException (int code, String msg) { + super(msg); + this.code = code; + } +} diff --git a/modules/swagger-generator/src/main/java/com/wordnik/swagger/generator/exception/BadRequestException.java b/modules/swagger-generator/src/main/java/com/wordnik/swagger/generator/exception/BadRequestException.java new file mode 100644 index 00000000000..339df2e1059 --- /dev/null +++ b/modules/swagger-generator/src/main/java/com/wordnik/swagger/generator/exception/BadRequestException.java @@ -0,0 +1,25 @@ +/** + * Copyright 2014 Reverb, 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.generator.exception; + +public class BadRequestException extends ApiException { + private int code; + public BadRequestException (int code, String msg) { + super(code, msg); + this.code = code; + } +} diff --git a/modules/swagger-generator/src/main/java/com/wordnik/swagger/generator/exception/NotFoundException.java b/modules/swagger-generator/src/main/java/com/wordnik/swagger/generator/exception/NotFoundException.java new file mode 100644 index 00000000000..a9b29e99496 --- /dev/null +++ b/modules/swagger-generator/src/main/java/com/wordnik/swagger/generator/exception/NotFoundException.java @@ -0,0 +1,25 @@ +/** + * Copyright 2014 Reverb, 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.generator.exception; + +public class NotFoundException extends ApiException { + private int code; + public NotFoundException (int code, String msg) { + super(code, msg); + this.code = code; + } +} diff --git a/modules/swagger-generator/src/main/java/com/wordnik/swagger/generator/model/ApiResponse.java b/modules/swagger-generator/src/main/java/com/wordnik/swagger/generator/model/ApiResponse.java new file mode 100644 index 00000000000..32e6f689103 --- /dev/null +++ b/modules/swagger-generator/src/main/java/com/wordnik/swagger/generator/model/ApiResponse.java @@ -0,0 +1,84 @@ +/** + * Copyright 2014 Reverb, 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.generator.model; + +import javax.xml.bind.annotation.XmlTransient; + +@javax.xml.bind.annotation.XmlRootElement +public class ApiResponse { + public static final int ERROR = 1; + public static final int WARNING = 2; + public static final int INFO = 3; + public static final int OK = 4; + public static final int TOO_BUSY = 5; + + int code; + String type; + String message; + + public ApiResponse(){} + + public ApiResponse(int code, String message){ + this.code = code; + switch(code){ + case ERROR: + setType("error"); + break; + case WARNING: + setType("warning"); + break; + case INFO: + setType("info"); + break; + case OK: + setType("ok"); + break; + case TOO_BUSY: + setType("too busy"); + break; + default: + setType("unknown"); + break; + } + this.message = message; + } + + @XmlTransient + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } +} diff --git a/modules/swagger-generator/src/main/java/com/wordnik/swagger/generator/model/Generated.java b/modules/swagger-generator/src/main/java/com/wordnik/swagger/generator/model/Generated.java new file mode 100644 index 00000000000..19c9a328177 --- /dev/null +++ b/modules/swagger-generator/src/main/java/com/wordnik/swagger/generator/model/Generated.java @@ -0,0 +1,36 @@ +/** + * Copyright 2014 Reverb, 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.generator.model; + +public class Generated { + private String filename; + private String friendlyName; + + public String getFilename() { + return filename; + } + public void setFilename(String filename) { + this.filename = filename; + } + + public String getFriendlyName() { + return friendlyName; + } + public void setFriendlyName(String friendlyName) { + this.friendlyName = friendlyName; + } +} diff --git a/modules/swagger-generator/src/main/java/com/wordnik/swagger/generator/model/GeneratorInput.java b/modules/swagger-generator/src/main/java/com/wordnik/swagger/generator/model/GeneratorInput.java new file mode 100644 index 00000000000..9c5a2aa15ce --- /dev/null +++ b/modules/swagger-generator/src/main/java/com/wordnik/swagger/generator/model/GeneratorInput.java @@ -0,0 +1,44 @@ +package com.wordnik.swagger.generator.model; + +import com.wordnik.swagger.annotations.ApiModelProperty; +import com.wordnik.swagger.models.auth.SecuritySchemeDefinition; + +import com.fasterxml.jackson.databind.*; + +import java.util.*; + +public class GeneratorInput { + private JsonNode spec; + private Map options; + private String swaggerUrl; + private SecuritySchemeDefinition auth; + + @ApiModelProperty(dataType="Object") + public JsonNode getSpec() { + return spec; + } + public void setSpec(JsonNode spec) { + this.spec = spec; + } + + public Map getOptions() { + return options; + } + public void setOptions(Map options) { + this.options = options; + } + + public String getSwaggerUrl() { + return swaggerUrl; + } + public void setSwaggerUrl(String url) { + this.swaggerUrl = url; + } + + public SecuritySchemeDefinition getSecurityDefinition() { + return auth; + } + public void setSecurityDefinition(SecuritySchemeDefinition auth) { + this.auth = auth; + } +} \ No newline at end of file diff --git a/modules/swagger-generator/src/main/java/com/wordnik/swagger/generator/model/InputOption.java b/modules/swagger-generator/src/main/java/com/wordnik/swagger/generator/model/InputOption.java new file mode 100644 index 00000000000..446bb53db1b --- /dev/null +++ b/modules/swagger-generator/src/main/java/com/wordnik/swagger/generator/model/InputOption.java @@ -0,0 +1,65 @@ +/** + * Copyright 2014 Reverb, 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.generator.model; + +public class InputOption { + private String name; + private String description; + private Boolean required; + private String defaultValue; + + public InputOption() {} + + public InputOption(String name, String description, String defaultValue, Boolean required) { + this.name = name; + this.description = description; + this.defaultValue = defaultValue; + this.required = required; + } + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getDescription() { + return description; + } + + public void setRequired(Boolean required) { + this.required = required; + } + + public Boolean getRequired() { + return required; + } + + public void setDefaultValue(String defaultValue) { + this.defaultValue = defaultValue; + } + + public String getDefaultValue() { + return defaultValue; + } +} diff --git a/modules/swagger-generator/src/main/java/com/wordnik/swagger/generator/model/ResponseCode.java b/modules/swagger-generator/src/main/java/com/wordnik/swagger/generator/model/ResponseCode.java new file mode 100644 index 00000000000..412da5aae1b --- /dev/null +++ b/modules/swagger-generator/src/main/java/com/wordnik/swagger/generator/model/ResponseCode.java @@ -0,0 +1,19 @@ +package com.wordnik.swagger.generator.model; + +public class ResponseCode { + private String code; + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + + public ResponseCode() {} + + public ResponseCode(String code) { + setCode(code); + } +} \ No newline at end of file diff --git a/modules/swagger-generator/src/main/java/com/wordnik/swagger/generator/online/Generator.java b/modules/swagger-generator/src/main/java/com/wordnik/swagger/generator/online/Generator.java new file mode 100644 index 00000000000..782fb95fcd7 --- /dev/null +++ b/modules/swagger-generator/src/main/java/com/wordnik/swagger/generator/online/Generator.java @@ -0,0 +1,95 @@ +package com.wordnik.swagger.online; + +import io.swagger.parser.SwaggerParser; + +import com.wordnik.swagger.generator.exception.*; +import com.wordnik.swagger.codegen.*; +import com.wordnik.swagger.models.Swagger; +import com.wordnik.swagger.generator.model.*; +import com.wordnik.swagger.util.Json; +import com.wordnik.swagger.generator.util.ZipUtil; + +import com.fasterxml.jackson.databind.JsonNode; + +import java.io.File; +import java.util.List; +import java.util.ArrayList; + +public class Generator { + public static String generateClient(String language, GeneratorInput opts) throws ApiException { + if(opts == null) { + throw new BadRequestException(400, "No options were supplied"); + } + JsonNode node = opts.getSpec(); + if(node == null) { + throw new BadRequestException(400, "No swagger specification was supplied"); + } + Swagger swagger = new SwaggerParser().read(node); + if(swagger == null) { + throw new BadRequestException(400, "The swagger specification supplied was not valid"); + } + + ClientOptInput clientOptInput = new ClientOptInput(); + ClientOpts clientOpts = new ClientOpts(); + String outputFolder = getTmpFolder().getAbsolutePath() + File.separator + language + "-client"; + String outputFilename = outputFolder + "-bundle.zip"; + + clientOptInput + .opts(clientOpts) + .swagger(swagger); + + CodegenConfig codegenConfig = Codegen.getConfig(language); + if(codegenConfig == null) { + throw new BadRequestException(400, "Unsupported target " + language + " supplied"); + } + + codegenConfig.setOutputDir(outputFolder); + + Json.prettyPrint(clientOpts); + + clientOptInput.setConfig(codegenConfig); + + try{ + List files = new Codegen().opts(clientOptInput).generate(); + if(files.size() > 0) { + List filesToAdd = new ArrayList(); + filesToAdd.add(new File(outputFolder)); + ZipUtil zip = new ZipUtil(); + zip.compressFiles(filesToAdd, outputFilename); + } + else { + throw new BadRequestException(400, "A target generation was attempted, but no files were created!"); + } + } + catch (Exception e) { + throw new BadRequestException(500, "Unable to build target: " + e.getMessage()); + } + return outputFilename; + } + + public static String generateServer(String language, GeneratorInput opts) { + return ""; + } + + public static InputOption clientOptions(String language) { + return null; + } + + public static InputOption serverOptions(String language) { + return null; + } + + protected static File getTmpFolder() { + try { + File outputFolder = File.createTempFile("codegen-", "-tmp"); + outputFolder.delete(); + outputFolder.mkdir(); + outputFolder.deleteOnExit(); + return outputFolder; + } + catch (Exception e) { + e.printStackTrace(); + return null; + } + } +} diff --git a/modules/swagger-generator/src/main/java/com/wordnik/swagger/generator/resource/ExceptionWriter.java b/modules/swagger-generator/src/main/java/com/wordnik/swagger/generator/resource/ExceptionWriter.java new file mode 100644 index 00000000000..f052c79f311 --- /dev/null +++ b/modules/swagger-generator/src/main/java/com/wordnik/swagger/generator/resource/ExceptionWriter.java @@ -0,0 +1,50 @@ +package com.wordnik.swagger.generator.resource; + +import com.wordnik.swagger.generator.util.ValidationException; + +import com.wordnik.swagger.generator.exception.ApiException; +import com.wordnik.swagger.generator.exception.BadRequestException; +import com.wordnik.swagger.generator.exception.NotFoundException; +import com.wordnik.swagger.generator.model.ApiResponse; + +import javax.ws.rs.ext.*; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.Status; + +@Provider +public class ExceptionWriter implements ExceptionMapper { + public Response toResponse(Exception exception) { + if (exception instanceof javax.ws.rs.WebApplicationException) { + javax.ws.rs.WebApplicationException e = (javax.ws.rs.WebApplicationException) exception; + return Response + .status(e.getResponse().getStatus()) + .entity(new ApiResponse(e.getResponse().getStatus(), + exception.getMessage())).build(); + } else if (exception instanceof com.fasterxml.jackson.core.JsonParseException) { + return Response.status(400) + .entity(new ApiResponse(400, "bad input")).build(); + } else if (exception instanceof ValidationException) { + ValidationException e = (ValidationException) exception; + return Response.status(Status.BAD_REQUEST).entity(e.getMessage()).build(); + } else if (exception instanceof NotFoundException) { + return Response + .status(Status.NOT_FOUND) + .entity(new ApiResponse(ApiResponse.ERROR, exception + .getMessage())).build(); + } else if (exception instanceof BadRequestException) { + return Response + .status(Status.BAD_REQUEST) + .entity(new ApiResponse(ApiResponse.ERROR, exception + .getMessage())).build(); + } else if (exception instanceof ApiException) { + return Response + .status(Status.BAD_REQUEST) + .entity(new ApiResponse(ApiResponse.ERROR, exception + .getMessage())).build(); + } else { + return Response.status(500) + .entity(new ApiResponse(500, "something bad happened")) + .build(); + } + } +} \ No newline at end of file diff --git a/modules/swagger-generator/src/main/java/com/wordnik/swagger/generator/resource/SwaggerResource.java b/modules/swagger-generator/src/main/java/com/wordnik/swagger/generator/resource/SwaggerResource.java new file mode 100644 index 00000000000..0f5c6e2c14c --- /dev/null +++ b/modules/swagger-generator/src/main/java/com/wordnik/swagger/generator/resource/SwaggerResource.java @@ -0,0 +1,155 @@ +/** + * Copyright 2014 Reverb, 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.generator.resource; + +import com.wordnik.swagger.codegen.*; +import com.wordnik.swagger.generator.util.*; +import com.wordnik.swagger.annotations.*; +import com.wordnik.swagger.generator.model.*; +import com.wordnik.swagger.generator.exception.BadRequestException; +import com.wordnik.swagger.online.Generator; + +import java.io.File; +import java.util.*; + +import javax.ws.rs.*; +import javax.ws.rs.core.*; + +@Path("/gen") +@Api(value = "/gen", description = "Resource for generating swagger components") +public class SwaggerResource { + private static Map fileMap = new HashMap(); + + @GET + @Path("/download/{fileId}") + @Produces({"application/zip", "application/json"}) + public Response downloadFile(@PathParam("fileId") String fileId) throws Exception { + Generated g = fileMap.get(fileId); + System.out.println("looking for fileId " + fileId); + System.out.println("got filename " + g.getFilename()); + if(g.getFilename() != null) { + byte[] bytes = org.apache.commons.io.FileUtils.readFileToByteArray(new java.io.File(g.getFilename())); + + return Response.ok(bytes, "application/zip") + .header("Content-Disposition","attachment; filename=\"" + g.getFriendlyName() + "-generated.zip\"") + .header("Accept-Range", "bytes") + .header("Content-Length", bytes.length) + .build(); + } + else { + return Response.status(404).build(); + } + } + + @POST + @Path("/clients/{language}") + @Produces({"application/zip", "application/json"}) + @ApiOperation(value = "Generates a client library based on the config", + notes = "The model representing this is not accurate, it needs to contain a consolidated JSON structure") + public Response generateClient( + @ApiParam(value = "The target language for the client library", allowableValues = "android,java,php,objc,docs", required = true) @PathParam("language") String language, + @ApiParam(value = "Configuration for building the client library", required = true) GeneratorInput opts) throws Exception { + + String filename = Generator.generateClient(language, opts); + + if(filename != null) { + String code = String.valueOf(System.currentTimeMillis()); + Generated g = new Generated(); + g.setFilename(filename); + g.setFriendlyName(language + "-client"); + fileMap.put(code, g); + System.out.println(code + ", " + filename); + return Response.ok().entity(new ResponseCode(code)).build(); + } + else { + return Response.status(500).build(); + } + } + + @GET + @Path("/clients") + @ApiOperation(value = "Gets languages supported by the client generator", + response = String.class, + responseContainer = "List") + public Response clientOptions() { + String[] languages = {"android", "java", "php", "objc", "docs"}; + return Response.ok().entity(languages).build(); + } + + @GET + @Path("/clients/{language}") + @ApiOperation(value = "Gets options for a client generation", + notes = "Values which are not required will use the provided default values", + response = InputOption.class, + responseContainer = "List") + @ApiResponses(value = { + @com.wordnik.swagger.annotations.ApiResponse(code = 400, message = "Invalid model supplied", response = ValidationMessage.class), + }) + + public Response clientLibraryOptions( + @ApiParam(value = "The target language for the client library", allowableValues = "android,java,php,objc,docs", required = true) @PathParam("language") String language) { + return Response.ok().entity(Generator.clientOptions(language)).build(); + } + + @GET + @Path("/servers") + @ApiOperation(value = "Gets languages supported by the server generator", + response = String.class, + responseContainer = "List") + public Response serverOptions() { + String[] languages = {"jaxrs","nodejs"}; + return Response.ok().entity(languages).build(); + } + + @GET + @Path("/servers/{language}") + @ApiOperation(value = "Gets options for a server generation", + notes = "Values which are not required will use the provided default values", + response = InputOption.class, + responseContainer = "List") + public Response serverFrameworkOptions( + @ApiParam(value = "The target framework for the client library", allowableValues = "jaxrs,nodejs", required = true) @PathParam("language") String framework) { + return Response.ok().entity(Generator.serverOptions(framework)).build(); + } + + @POST + @Path("/servers/{framework}") + @ApiOperation(value = "Generates a server library for the supplied server framework", + notes = "The model representing this is not accurate, it needs to contain a consolidated JSON structure") + public Response generateServerForLanguage( + @ApiParam(value = "framework", allowableValues = "jaxrs,nodejs", required = true) @PathParam("framework") String framework, + @ApiParam(value = "parameters", required = true) GeneratorInput opts) + throws Exception { + if(framework == null) + throw new BadRequestException(400, "Framework is required"); + String filename = Generator.generateServer(framework, opts); + System.out.println("generated name: " + filename); + + if(filename != null) { + String code = String.valueOf(System.currentTimeMillis()); + Generated g = new Generated(); + g.setFilename(filename); + g.setFriendlyName(framework + "-server"); + fileMap.put(code, g); + System.out.println(code + ", " + filename); + return Response.ok().entity(new ResponseCode(code)).build(); + } + else { + return Response.status(500).build(); + } + } +} diff --git a/modules/swagger-generator/src/main/java/com/wordnik/swagger/generator/util/ApiOriginFilter.java b/modules/swagger-generator/src/main/java/com/wordnik/swagger/generator/util/ApiOriginFilter.java new file mode 100644 index 00000000000..4d2d7ef5707 --- /dev/null +++ b/modules/swagger-generator/src/main/java/com/wordnik/swagger/generator/util/ApiOriginFilter.java @@ -0,0 +1,42 @@ +/** + * Copyright 2014 Reverb, 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.generator.util; + +import java.io.IOException; + +import javax.servlet.*; +import javax.servlet.http.HttpServletResponse; + +public class ApiOriginFilter implements javax.servlet.Filter { + @Override + public void doFilter(ServletRequest request, ServletResponse response, + FilterChain chain) throws IOException, ServletException { + HttpServletResponse res = (HttpServletResponse) response; + res.addHeader("Access-Control-Allow-Origin", "*"); + res.addHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT"); + res.addHeader("Access-Control-Allow-Headers", "Content-Type"); + chain.doFilter(request, response); + } + + @Override + public void destroy() { + } + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + } +} \ No newline at end of file diff --git a/modules/swagger-generator/src/main/java/com/wordnik/swagger/generator/util/ValidationException.java b/modules/swagger-generator/src/main/java/com/wordnik/swagger/generator/util/ValidationException.java new file mode 100644 index 00000000000..19747367ca0 --- /dev/null +++ b/modules/swagger-generator/src/main/java/com/wordnik/swagger/generator/util/ValidationException.java @@ -0,0 +1,27 @@ +package com.wordnik.swagger.generator.util; + +import java.util.List; + +public class ValidationException extends Exception { + private int code; + private String msg; + private List errors; + + public ValidationException(String msg) { + super(msg); + } + + public int getCode() { + return code; + } + public void setCode(int code) { + this.code = code; + } + + public String getMessage() { + return msg; + } + public void setMessage(String msg) { + this.msg = msg; + } +} \ No newline at end of file diff --git a/modules/swagger-generator/src/main/java/com/wordnik/swagger/generator/util/ValidationMessage.java b/modules/swagger-generator/src/main/java/com/wordnik/swagger/generator/util/ValidationMessage.java new file mode 100644 index 00000000000..b64633de2bd --- /dev/null +++ b/modules/swagger-generator/src/main/java/com/wordnik/swagger/generator/util/ValidationMessage.java @@ -0,0 +1,26 @@ +package com.wordnik.swagger.generator.util; + +public class ValidationMessage { + private String path, message, severity; + + public String getPath() { + return path; + } + public void setPath(String path) { + this.path = path; + } + + public String getMessage() { + return message; + } + public void setMessage(String message) { + this.message = message; + } + + public String getSeverity() { + return severity; + } + public void setSeverity(String severity) { + this.severity = severity; + } +} \ No newline at end of file diff --git a/modules/swagger-generator/src/main/java/com/wordnik/swagger/generator/util/ZipUtil.java b/modules/swagger-generator/src/main/java/com/wordnik/swagger/generator/util/ZipUtil.java new file mode 100644 index 00000000000..de462c57c1b --- /dev/null +++ b/modules/swagger-generator/src/main/java/com/wordnik/swagger/generator/util/ZipUtil.java @@ -0,0 +1,124 @@ +/** + * Copyright 2014 Reverb, 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.generator.util; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +/** +* This utility compresses a list of files to standard ZIP format file. +* It is able to compresses all sub files and sub directories, recursively. +* @author Ha Minh Nam +* +*/ +public class ZipUtil { + /** + * A constants for buffer size used to read/write data + */ + private static final int BUFFER_SIZE = 4096; + + /** + * Compresses a collection of files to a destination zip file + * @param listFiles A collection of files and directories + * @param destZipFile The path of the destination zip file + * @throws FileNotFoundException + * @throws IOException + */ + public void compressFiles(List listFiles, String destZipFile) throws FileNotFoundException, IOException { + + ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(destZipFile)); + + for (File file : listFiles) { + if (file.isDirectory()) { + addFolderToZip(file, file.getName(), zos); + } else { + addFileToZip(file, zos); + } + } + + zos.flush(); + zos.close(); + } + + /** + * Adds a directory to the current zip output stream + * @param folder the directory to be added + * @param parentFolder the path of parent directory + * @param zos the current zip output stream + * @throws FileNotFoundException + * @throws IOException + */ + private void addFolderToZip(File folder, String parentFolder, + ZipOutputStream zos) throws FileNotFoundException, IOException { + for (File file : folder.listFiles()) { + if (file.isDirectory()) { + addFolderToZip(file, parentFolder + "/" + file.getName(), zos); + continue; + } + + zos.putNextEntry(new ZipEntry(parentFolder + "/" + file.getName())); + + BufferedInputStream bis = new BufferedInputStream( + new FileInputStream(file)); + + long bytesRead = 0; + byte[] bytesIn = new byte[BUFFER_SIZE]; + int read = 0; + + while ((read = bis.read(bytesIn)) != -1) { + zos.write(bytesIn, 0, read); + bytesRead += read; + } + + zos.closeEntry(); + + } + } + + /** + * Adds a file to the current zip output stream + * @param file the file to be added + * @param zos the current zip output stream + * @throws FileNotFoundException + * @throws IOException + */ + private void addFileToZip(File file, ZipOutputStream zos) + throws FileNotFoundException, IOException { + zos.putNextEntry(new ZipEntry(file.getName())); + + BufferedInputStream bis = new BufferedInputStream(new FileInputStream( + file)); + + long bytesRead = 0; + byte[] bytesIn = new byte[BUFFER_SIZE]; + int read = 0; + + while ((read = bis.read(bytesIn)) != -1) { + zos.write(bytesIn, 0, read); + bytesRead += read; + } + + zos.closeEntry(); + } +} \ No newline at end of file diff --git a/modules/swagger-generator/src/main/resources/logback.xml b/modules/swagger-generator/src/main/resources/logback.xml new file mode 100644 index 00000000000..66ff7a9be02 --- /dev/null +++ b/modules/swagger-generator/src/main/resources/logback.xml @@ -0,0 +1,12 @@ + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + diff --git a/modules/swagger-generator/src/main/webapp/WEB-INF/web.xml b/modules/swagger-generator/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 00000000000..79d21b95f84 --- /dev/null +++ b/modules/swagger-generator/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,68 @@ + + + + + jersey + org.glassfish.jersey.servlet.ServletContainer + + jersey.config.server.provider.packages + + com.wordnik.swagger.jaxrs.json, + com.wordnik.swagger.generator.resource + + + + jersey.config.server.provider.classnames + + com.wordnik.swagger.online.ExceptionWriter, + com.wordnik.swagger.jersey.listing.ApiListingResourceJSON, + com.wordnik.swagger.jersey.listing.JerseyApiDeclarationProvider, + com.wordnik.swagger.jersey.listing.JerseyResourceListingProvider + + + + jersey.config.server.wadl.disableWadl + true + + 1 + + + + jersey + /api/* + + + + Jersey2Config + com.wordnik.swagger.jersey.config.JerseyJaxrsConfig + + api.version + 1.0.0 + + + swagger.api.basepath + http://generator.wordnik.com/online/api + + + swagger.filter + com.wordnik.swagger.generator.util.ApiAuthorizationFilterImpl + + 2 + + + + ApiOriginFilter + com.wordnik.swagger.generator.util.ApiOriginFilter + + + Bootstrap + com.wordnik.swagger.generator.Bootstrap + 2 + + + ApiOriginFilter + /* + + \ No newline at end of file diff --git a/pom.xml b/pom.xml index 40db216c563..1774c47954b 100644 --- a/pom.xml +++ b/pom.xml @@ -243,6 +243,7 @@ modules/swagger-codegen modules/swagger-codegen-distribution + modules/swagger-generator target/site @@ -302,7 +303,7 @@ 1.0.0-SNAPSHOT 2.11.1 2.3.4 - 1.5.0-M1 + 1.5.0-SNAPSHOT 2.1.4 2.3 1.2 @@ -314,4 +315,4 @@ 2.10.4 1.9 - \ No newline at end of file +