diff --git a/modules/swagger-codegen/src/main/java/io/swagger/codegen/CodegenParameter.java b/modules/swagger-codegen/src/main/java/io/swagger/codegen/CodegenParameter.java index c5aeaab124c..4447915de4d 100644 --- a/modules/swagger-codegen/src/main/java/io/swagger/codegen/CodegenParameter.java +++ b/modules/swagger-codegen/src/main/java/io/swagger/codegen/CodegenParameter.java @@ -7,7 +7,7 @@ public class CodegenParameter { public Boolean isFormParam, isQueryParam, isPathParam, isHeaderParam, - isCookieParam, isBodyParam, isFile, notFile, hasMore, isContainer, secondaryParam; + isCookieParam, isBodyParam, isFile, notFile, hasMore, isContainer, secondaryParam, isBinary; public String baseName, paramName, dataType, collectionFormat, description, baseType, defaultValue; public String jsonSchema; public boolean isEnum; diff --git a/modules/swagger-codegen/src/main/java/io/swagger/codegen/CodegenResponse.java b/modules/swagger-codegen/src/main/java/io/swagger/codegen/CodegenResponse.java index 6d90152f514..1929bcf52dc 100644 --- a/modules/swagger-codegen/src/main/java/io/swagger/codegen/CodegenResponse.java +++ b/modules/swagger-codegen/src/main/java/io/swagger/codegen/CodegenResponse.java @@ -15,6 +15,7 @@ public class CodegenResponse { public Boolean primitiveType; public Boolean isMapContainer; public Boolean isListContainer; + public Boolean isBinary; public Object schema; public String jsonSchema; diff --git a/modules/swagger-codegen/src/main/java/io/swagger/codegen/DefaultCodegen.java b/modules/swagger-codegen/src/main/java/io/swagger/codegen/DefaultCodegen.java index 8a75eac30d6..1422b1c36a0 100644 --- a/modules/swagger-codegen/src/main/java/io/swagger/codegen/DefaultCodegen.java +++ b/modules/swagger-codegen/src/main/java/io/swagger/codegen/DefaultCodegen.java @@ -27,6 +27,7 @@ import io.swagger.models.properties.AbstractNumericProperty; import io.swagger.models.properties.ArrayProperty; import io.swagger.models.properties.BooleanProperty; +import io.swagger.models.properties.ByteArrayProperty; import io.swagger.models.properties.DateProperty; import io.swagger.models.properties.DateTimeProperty; import io.swagger.models.properties.DecimalProperty; @@ -311,6 +312,8 @@ public DefaultCodegen() { typeMapping.put("double", "Double"); typeMapping.put("object", "Object"); typeMapping.put("integer", "Integer"); + typeMapping.put("ByteArray", "byte[]"); + instantiationTypes = new HashMap(); @@ -447,6 +450,8 @@ public String getSwaggerType(Property p) { String datatype = null; if (p instanceof StringProperty) { datatype = "string"; + } else if (p instanceof ByteArrayProperty) { + datatype = "ByteArray"; } else if (p instanceof BooleanProperty) { datatype = "boolean"; } else if (p instanceof DateProperty) { @@ -529,6 +534,7 @@ public CodegenModel fromModel(String name, Model model, Map allDe if (model instanceof ArrayModel) { ArrayModel am = (ArrayModel) model; ArrayProperty arrayProperty = new ArrayProperty(am.getItems()); + m.hasEnums = false; // Otherwise there will be a NullPointerException in JavaClientCodegen.fromModel addParentContainer(m, name, arrayProperty); } else if (model instanceof RefModel) { // TODO @@ -974,6 +980,7 @@ public CodegenResponse fromResponse(String responseCode, Response response) { } } r.dataType = cm.datatype; + r.isBinary = cm.datatype.equals("byte[]"); if (cm.isContainer != null) { r.simpleType = false; r.containerType = cm.containerType; @@ -1070,12 +1077,17 @@ public CodegenParameter fromParameter(Parameter param, Set imports) { p.dataType = getTypeDeclaration(cm.classname); imports.add(p.dataType); } else { - // TODO: missing format, so this will not always work - Property prop = PropertyBuilder.build(impl.getType(), null, null); + Property prop = PropertyBuilder.build(impl.getType(), impl.getFormat(), null); prop.setRequired(bp.getRequired()); CodegenProperty cp = fromProperty("property", prop); if (cp != null) { p.dataType = cp.datatype; + if (p.dataType.equals("byte[]")) { + p.isBinary = true; + } + else { + p.isBinary = false; + } } } } else if (model instanceof ArrayModel) { diff --git a/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/JavaClientCodegen.java b/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/JavaClientCodegen.java index 379a2db2eb1..40818acd2d3 100644 --- a/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/JavaClientCodegen.java +++ b/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/JavaClientCodegen.java @@ -36,7 +36,7 @@ public class JavaClientCodegen extends DefaultCodegen implements CodegenConfig { protected String sourceFolder = "src/main/java"; protected String localVariablePrefix = ""; protected Boolean serializableModel = false; - + public JavaClientCodegen() { super(); outputFolder = "generated-code/java"; @@ -66,7 +66,8 @@ public JavaClientCodegen() { "Integer", "Long", "Float", - "Object") + "Object", + "byte[]") ); instantiationTypes.put("array", "ArrayList"); instantiationTypes.put("map", "HashMap"); @@ -137,7 +138,7 @@ public void processOpts() { if (additionalProperties.containsKey("localVariablePrefix")) { this.setLocalVariablePrefix((String) additionalProperties.get("localVariablePrefix")); } - + if (additionalProperties.containsKey("serializableModel")) { this.setSerializableModel(Boolean.valueOf((String)additionalProperties.get("serializableModel").toString())); } @@ -282,7 +283,7 @@ public String getSwaggerType(Property p) { if (typeMapping.containsKey(swaggerType)) { type = typeMapping.get(swaggerType); if (languageSpecificPrimitives.contains(type)) { - return toModelName(type); + return type; } } else { type = swaggerType; @@ -380,6 +381,7 @@ public void setLocalVariablePrefix(String localVariablePrefix) { this.localVariablePrefix = localVariablePrefix; } + public Boolean getSerializableModel() { return serializableModel; } @@ -387,7 +389,7 @@ public Boolean getSerializableModel() { public void setSerializableModel(Boolean serializableModel) { this.serializableModel = serializableModel; } - + private String sanitizePackageName(String packageName) { packageName = packageName.trim(); packageName = packageName.replaceAll("[^a-zA-Z0-9_\\.]", "_"); diff --git a/modules/swagger-codegen/src/main/resources/Java/ApiClient.mustache b/modules/swagger-codegen/src/main/resources/Java/ApiClient.mustache index 4e691c27493..1a0dc5bc3a7 100644 --- a/modules/swagger-codegen/src/main/resources/Java/ApiClient.mustache +++ b/modules/swagger-codegen/src/main/resources/Java/ApiClient.mustache @@ -28,6 +28,7 @@ import java.net.URLEncoder; import java.io.IOException; import java.io.File; import java.io.UnsupportedEncodingException; +import java.io.DataInputStream; import java.text.DateFormat; import java.text.SimpleDateFormat; @@ -372,22 +373,12 @@ public class ApiClient { } } - /** - * Invoke API by sending HTTP request with the given options. - * - * @param path The sub-path of the HTTP URL - * @param method The request method, one of "GET", "POST", "PUT", and "DELETE" - * @param queryParams The query parameters - * @param body The request body object - * @param headerParams The header parameters - * @param formParams The form parameters - * @param accept The request's Accept header - * @param contentType The request's Content-Type header - * @param authNames The authentications to apply - * @param returnType The return type into which to deserialize the response - * @return The response body in type of string - */ - public T invokeAPI(String path, String method, List queryParams, Object body, Map headerParams, Map formParams, String accept, String contentType, String[] authNames, TypeRef returnType) throws ApiException { + private ClientResponse getAPIResponse(String path, String method, List queryParams, Object body, byte[] binaryBody, Map headerParams, Map formParams, String accept, String contentType, String[] authNames) throws ApiException { + + if (body != null && binaryBody != null){ + throw new ApiException(500, "either body or binaryBody must be null"); + } + updateParamsForAuth(authNames, queryParams, headerParams); Client client = getClient(); @@ -447,7 +438,10 @@ public class ApiClient { if (encodedFormParams != null) { response = builder.type(contentType).post(ClientResponse.class, encodedFormParams); } else if (body == null) { - response = builder.post(ClientResponse.class, null); + if(binaryBody == null) + response = builder.post(ClientResponse.class, null); + else + response = builder.type(contentType).post(ClientResponse.class, binaryBody); } else if (body instanceof FormDataMultiPart) { response = builder.type(contentType).post(ClientResponse.class, body); } else { @@ -457,7 +451,10 @@ public class ApiClient { if (encodedFormParams != null) { response = builder.type(contentType).put(ClientResponse.class, encodedFormParams); } else if(body == null) { - response = builder.put(ClientResponse.class, serialize(body, contentType)); + if(binaryBody == null) + response = builder.put(ClientResponse.class, null); + else + response = builder.type(contentType).put(ClientResponse.class, binaryBody); } else { response = builder.type(contentType).put(ClientResponse.class, serialize(body, contentType)); } @@ -465,15 +462,39 @@ public class ApiClient { if (encodedFormParams != null) { response = builder.type(contentType).delete(ClientResponse.class, encodedFormParams); } else if(body == null) { - response = builder.delete(ClientResponse.class); + if(binaryBody == null) + response = builder.delete(ClientResponse.class); + else + response = builder.type(contentType).delete(ClientResponse.class, binaryBody); } else { response = builder.type(contentType).delete(ClientResponse.class, serialize(body, contentType)); } } else { throw new ApiException(500, "unknown method type " + method); } + return response; + } + + /** + * Invoke API by sending HTTP request with the given options. + * + * @param path The sub-path of the HTTP URL + * @param method The request method, one of "GET", "POST", "PUT", and "DELETE" + * @param queryParams The query parameters + * @param body The request body object - if it is not binary, otherwise null + * @param binaryBody The request body object - if it is binary, otherwise null + * @param headerParams The header parameters + * @param formParams The form parameters + * @param accept The request's Accept header + * @param contentType The request's Content-Type header + * @param authNames The authentications to apply + * @return The response body in type of string + */ + public T invokeAPI(String path, String method, List queryParams, Object body, byte[] binaryBody, Map headerParams, Map formParams, String accept, String contentType, String[] authNames, TypeRef returnType) throws ApiException { - if (response.getStatusInfo() == ClientResponse.Status.NO_CONTENT) { + ClientResponse response = getAPIResponse(path, method, queryParams, body, binaryBody, headerParams, formParams, accept, contentType, authNames); + + if(response.getStatusInfo() == ClientResponse.Status.NO_CONTENT) { return null; } else if (response.getStatusInfo().getFamily() == Family.SUCCESSFUL) { if (returnType == null) @@ -498,6 +519,58 @@ public class ApiClient { respBody); } } + /** + * Invoke API by sending HTTP request with the given options - return binary result + * + * @param path The sub-path of the HTTP URL + * @param method The request method, one of "GET", "POST", "PUT", and "DELETE" + * @param queryParams The query parameters + * @param body The request body object - if it is not binary, otherwise null + * @param binaryBody The request body object - if it is binary, otherwise null + * @param headerParams The header parameters + * @param formParams The form parameters + * @param accept The request's Accept header + * @param contentType The request's Content-Type header + * @param authNames The authentications to apply + * @return The response body in type of string + */ + public byte[] invokeBinaryAPI(String path, String method, List queryParams, Object body, byte[] binaryBody, Map headerParams, Map formParams, String accept, String contentType, String[]authNames) throws ApiException { + + ClientResponse response = getAPIResponse(path, method, queryParams, body, binaryBody, headerParams, formParams, accept, contentType, authNames); + + if(response.getStatusInfo() == ClientResponse.Status.NO_CONTENT) { + return null; + } + else if(response.getStatusInfo().getFamily() == Family.SUCCESSFUL) { + if(response.hasEntity()) { + DataInputStream stream = new DataInputStream(response.getEntityInputStream()); + byte[] data = new byte[response.getLength()]; + try { + stream.readFully(data); + } catch (IOException ex) { + throw new ApiException(500, "Error obtaining binary response data"); + } + return data; + } + else { + return new byte[0]; + } + } + else { + String message = "error"; + if(response.hasEntity()) { + try{ + message = String.valueOf(response.getEntity(String.class)); + } + catch (RuntimeException e) { + // e.printStackTrace(); + } + } + throw new ApiException( + response.getStatusInfo().getStatusCode(), + message); + } + } /** * Update query and header parameters based on authentication settings. diff --git a/modules/swagger-codegen/src/main/resources/Java/api.mustache b/modules/swagger-codegen/src/main/resources/Java/api.mustache index a4dc5e6f903..8ffc355072a 100644 --- a/modules/swagger-codegen/src/main/resources/Java/api.mustache +++ b/modules/swagger-codegen/src/main/resources/Java/api.mustache @@ -46,16 +46,16 @@ public class {{classname}} { {{/allParams}} * @return {{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}void{{/returnType}} */ public {{#returnType}}{{{returnType}}} {{/returnType}}{{^returnType}}void {{/returnType}}{{nickname}} ({{#allParams}}{{{dataType}}} {{paramName}}{{#hasMore}}, {{/hasMore}}{{/allParams}}) throws ApiException { - Object {{localVariablePrefix}}postBody = {{#bodyParam}}{{paramName}}{{/bodyParam}}{{^bodyParam}}null{{/bodyParam}}; + Object {{localVariablePrefix}}postBody = {{#bodyParam}}{{^isBinary}}{{paramName}}{{/isBinary}}{{#isBinary}}null{{/isBinary}}{{/bodyParam}}{{^bodyParam}}null{{/bodyParam}}; + byte[] {{localVariablePrefix}}postBinaryBody = {{#bodyParam}}{{#isBinary}}{{paramName}}{{/isBinary}}{{^isBinary}}null{{/isBinary}}{{/bodyParam}}{{^bodyParam}}null{{/bodyParam}}; {{#allParams}}{{#required}} - // verify the required parameter '{{paramName}}' is set - if ({{paramName}} == null) { - throw new ApiException(400, "Missing the required parameter '{{paramName}}' when calling {{nickname}}"); - } - {{/required}}{{/allParams}} - + // verify the required parameter '{{paramName}}' is set + if ({{paramName}} == null) { + throw new ApiException(400, "Missing the required parameter '{{paramName}}' when calling {{nickname}}"); + } + {{/required}}{{/allParams}} // create path and map variables - String {{localVariablePrefix}}path = "{{path}}".replaceAll("\\{format\\}","json"){{#pathParams}} + String {{localVariablePrefix}}path = "{{{path}}}".replaceAll("\\{format\\}","json"){{#pathParams}} .replaceAll("\\{" + "{{baseName}}" + "\\}", {{localVariablePrefix}}apiClient.escapeString({{{paramName}}}.toString())){{/pathParams}}; // query params @@ -86,12 +86,26 @@ public class {{classname}} { final String {{localVariablePrefix}}contentType = {{localVariablePrefix}}apiClient.selectHeaderContentType({{localVariablePrefix}}contentTypes); String[] {{localVariablePrefix}}authNames = new String[] { {{#authMethods}}"{{name}}"{{#hasMore}}, {{/hasMore}}{{/authMethods}} }; + {{#responses}} + {{#isDefault}} + + {{#isBinary}} + byte[] {{localVariablePrefix}}response = null; + {{localVariablePrefix}}response = {{localVariablePrefix}}apiClient.invokeBinaryAPI({{localVariablePrefix}}path, "{{httpMethod}}", {{localVariablePrefix}}queryParams,{{localVariablePrefix}} postBody, {{localVariablePrefix}}postBinaryBody, {{localVariablePrefix}}headerParams, {{localVariablePrefix}}formParams, {{localVariablePrefix}}accept, {{localVariablePrefix}}contentType, {{localVariablePrefix}}authNames); + return {{localVariablePrefix}}response; + {{/isBinary}} + + {{^isBinary}} {{#returnType}} TypeRef {{localVariablePrefix}}returnType = new TypeRef<{{{returnType}}}>() {}; - return {{localVariablePrefix}}apiClient.invokeAPI({{localVariablePrefix}}path, "{{httpMethod}}", {{localVariablePrefix}}queryParams, {{localVariablePrefix}}postBody, {{localVariablePrefix}}headerParams, {{localVariablePrefix}}formParams, {{localVariablePrefix}}accept, {{localVariablePrefix}}contentType, {{localVariablePrefix}}authNames, {{localVariablePrefix}}returnType); + return {{localVariablePrefix}}apiClient.invokeAPI({{localVariablePrefix}}path, "{{httpMethod}}", {{localVariablePrefix}}queryParams, {{localVariablePrefix}}postBody, {{localVariablePrefix}}postBinaryBody, {{localVariablePrefix}}headerParams, {{localVariablePrefix}}formParams, {{localVariablePrefix}}accept, {{localVariablePrefix}}contentType, {{localVariablePrefix}}authNames, {{localVariablePrefix}}returnType); {{/returnType}}{{^returnType}} - {{localVariablePrefix}}apiClient.invokeAPI({{localVariablePrefix}}path, "{{httpMethod}}", {{localVariablePrefix}}queryParams, {{localVariablePrefix}}postBody, {{localVariablePrefix}}headerParams, {{localVariablePrefix}}formParams, {{localVariablePrefix}}accept, {{localVariablePrefix}}contentType, {{localVariablePrefix}}authNames, null); + {{localVariablePrefix}}apiClient.invokeAPI({{localVariablePrefix}}path, "{{httpMethod}}", {{localVariablePrefix}}queryParams, {{localVariablePrefix}}postBody, {{localVariablePrefix}}postBinaryBody, {{localVariablePrefix}}headerParams, {{localVariablePrefix}}formParams, {{localVariablePrefix}}accept, {{localVariablePrefix}}contentType, {{localVariablePrefix}}authNames, null); {{/returnType}} + {{/isBinary}} + + {{/isDefault}} + {{/responses}} } {{/operation}} } diff --git a/modules/swagger-codegen/src/test/resources/2_0/binaryDataTest.json b/modules/swagger-codegen/src/test/resources/2_0/binaryDataTest.json new file mode 100644 index 00000000000..5ae05f9ae83 --- /dev/null +++ b/modules/swagger-codegen/src/test/resources/2_0/binaryDataTest.json @@ -0,0 +1,51 @@ +{ + "swagger": "2.0", + "info": { + "description": "This is a sample server Petstore server. You can find out more about Swagger at http://swagger.io or on irc.freenode.net, #swagger. For this sample, you can use the api key \"special-key\" to test the authorization filters", + "version": "1.0.0", + "title": "Swagger Petstore", + "termsOfService": "http://helloreverb.com/terms/", + "license": { + "name": "Apache 2.0", + "url": "http://www.apache.org/licenses/LICENSE-2.0.html" + } + }, + "basePath": "/v2", + "schemes": [ + "http" + ], + "paths": { + "/tests/binaryResponse": { + "post": { + "summary": "Echo test", + "operationId": "echotest", + "consumes": [ + "application/octet-stream" + ], + "produces": [ + "application/octet-stream" + ], + "parameters": [ + { + "name": "InputBinaryData", + "in": "body", + "required": true, + "schema": { + "type": "string", + "format": "binary" + } + } + ], + "responses": { + "200": { + "description": "OutputBinaryData", + "schema": { + "type": "string", + "format": "binary" + } + } + } + } + } + } +} diff --git a/modules/swagger-codegen/src/test/scala/CodegenTest.scala b/modules/swagger-codegen/src/test/scala/CodegenTest.scala index 0e76f5088df..015932948de 100644 --- a/modules/swagger-codegen/src/test/scala/CodegenTest.scala +++ b/modules/swagger-codegen/src/test/scala/CodegenTest.scala @@ -139,4 +139,19 @@ class CodegenTest extends FlatSpec with Matchers { val op = codegen.fromOperation(path, "get", p, model.getDefinitions()) op.returnType should be("String") } + + it should "return byte array when response format is byte" in { + val model = new SwaggerParser() + .read("src/test/resources/2_0/binaryDataTest.json") + System.err.println("model is " + model); + val codegen = new DefaultCodegen() + + val path = "/tests/binaryResponse" + val p = model.getPaths().get(path).getPost() + val op = codegen.fromOperation(path, "post", p, model.getDefinitions()) + op.returnType should be("byte[]") + op.bodyParam.dataType should be ("byte[]") + op.bodyParam.isBinary should equal (true); + op.responses.get(0).isBinary should equal(true); + } } \ No newline at end of file diff --git a/modules/swagger-codegen/src/test/scala/Java/JavaModelTest.scala b/modules/swagger-codegen/src/test/scala/Java/JavaModelTest.scala index 0043045a29d..2df8db477de 100644 --- a/modules/swagger-codegen/src/test/scala/Java/JavaModelTest.scala +++ b/modules/swagger-codegen/src/test/scala/Java/JavaModelTest.scala @@ -365,6 +365,28 @@ class JavaModelTest extends FlatSpec with Matchers { val vars = cm.vars cm.classname should be("WithDots") } + + it should "convert a modelwith binary data" in { + val model = new ModelImpl() + .description("model with binary") + .property("inputBinaryData", new ByteArrayProperty()); + + val codegen = new JavaClientCodegen() + val cm = codegen.fromModel("sample", model) + val vars = cm.vars + + vars.get(0).baseName should be ("inputBinaryData") + vars.get(0).getter should be ("getInputBinaryData") + vars.get(0).setter should be ("setInputBinaryData") + vars.get(0).datatype should be ("byte[]") + vars.get(0).name should be ("inputBinaryData") + vars.get(0).defaultValue should be ("null") + vars.get(0).baseType should be ("byte[]") + vars.get(0).hasMore should equal (null) + vars.get(0).required should equal (null) + vars.get(0).isNotContainer should equal (true) + + } }