前言:
我们一般都会使用swagger生成restful接口,这样可以省不少时间,将更多的精力专注于写业务上。但接口也不是经常写,所以,swagger用的也不熟练。尤其是我喜欢拿之前的接口copy一份,然后在此基础上进行修改,一般情况下,都是能跑通的。不巧,这次并没有成功,原因在于我把服务端的接口拿来改成客户端的接口,当我沉浸于各种copy,delete的操作时,也给自己埋了一个大坑。导致我白白浪费了几个小时去找原因,最后发现是pom.xml文件中少了这一句:
<library>jersey2</library>
为了了解library的流程,我还去大致浏览了swagger-codegen的源码。因此,不想让自己的时间白白地浪费了,特意写下这篇文章,算是当作笔记。
一、错误重现
如果不加library这个属性,编译接口时,会报如下错误:
[ERROR] COMPILATION ERROR :
[INFO] -------------------------------------------------------------
[ERROR] /C:/workspace/common/dhcp/api/client/target/generated-sources/swagger/src/gen/java/com/test/dhcp/ProgressRequestBody.java:[16,27] package com.squareup.okhttp does not exist
[ERROR] /C:/workspace/common/dhcp/api/client/target/generated-sources/swagger/src/gen/java/com/test/dhcp/ProgressRequestBody.java:[17,27] package com.squareup.okhttp does not exist
[ERROR] /C:/workspace/common/dhcp/api/client/target/generated-sources/swagger/src/gen/java/com/test/dhcp/ProgressRequestBody.java:[21,12] package okio does not exist
[ERROR] /C:/workspace/common/dhcp/api/client/target/generated-sources/swagger/src/gen/java/com/test/dhcp/ProgressRequestBody.java:[22,12] package okio does not exist
[ERROR] /C:/workspace/common/dhcp/api/client/target/generated-sources/swagger/src/gen/java/com/test/dhcp/ProgressRequestBody.java:[23,12] package okio does not exist
[ERROR] /C:/workspace/common/dhcp/api/client/target/generated-sources/swagger/src/gen/java/com/test/dhcp/ProgressRequestBody.java:[24,12] package okio does not exist
[ERROR] /C:/workspace/common/dhcp/api/client/target/generated-sources/swagger/src/gen/java/com/test/dhcp/ProgressRequestBody.java:[25,12] package okio does not exist
[ERROR] /C:/workspace/common/dhcp/api/client/target/generated-sources/swagger/src/gen/java/com/test/dhcp/ProgressRequestBody.java:[27,42] cannot find symbol
symbol: class RequestBody
...
错误较多,只截取了前面一部分,通过这点错误信息,也足够说明问题了。
上面的错误提示找不到xxx的package,查看生成的源码ProgressRequestBody.java的内容:
package com.test.dhcp;
import com.squareup.okhttp.MediaType;
import com.squareup.okhttp.RequestBody;
import java.io.IOException;
import okio.Buffer;
import okio.BufferedSink;
import okio.ForwardingSink;
import okio.Okio;
import okio.Sink;
public class ProgressRequestBody extends RequestBody {
public interface ProgressRequestListener {
void onRequestProgress(long bytesWritten, long contentLength, boolean done);
}
private final RequestBody requestBody;
private final ProgressRequestListener progressListener;
public ProgressRequestBody(RequestBody requestBody, ProgressRequestListener progressListener) {
this.requestBody = requestBody;
this.progressListener = progressListener;
}
@Override
public MediaType contentType() {
return requestBody.contentType();
}
@Override
public long contentLength() throws IOException {
return requestBody.contentLength();
}
@Override
public void writeTo(BufferedSink sink) throws IOException {
BufferedSink bufferedSink = Okio.buffer(sink(sink));
requestBody.writeTo(bufferedSink);
bufferedSink.flush();
}
private Sink sink(Sink sink) {
return new ForwardingSink(sink) {
long bytesWritten = 0L;
long contentLength = 0L;
@Override
public void write(Buffer source, long byteCount) throws IOException {
super.write(source, byteCount);
if (contentLength == 0) {
contentLength = contentLength();
}
bytesWritten += byteCount;
progressListener.onRequestProgress(bytesWritten, contentLength, bytesWritten == contentLength);
}
};
}
}
从源码中可看到,ProgressRequestBody.java确实引入了com.squareup.okhttp和okio,pom.xml里面的依赖包如下:
<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>dhcp-api-model</artifactId>
</dependency>
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
</dependency>
<!-- HTTP client: jersey-client -->
<dependency>
<groupId>org.glassfish.jersey.core</groupId>
<artifactId>jersey-client</artifactId>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-multipart</artifactId>
</dependency>
<!-- JSON processing: jackson -->
<dependency>
<groupId>com.fasterxml.jackson.jaxrs</groupId>
<artifactId>jackson-jaxrs-base</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.jaxrs</groupId>
<artifactId>jackson-jaxrs-json-provider</artifactId>
</dependency>
<dependency>
<groupId>com.github.joschi.jackson</groupId>
<artifactId>jackson-datatype-threetenbp</artifactId>
</dependency>
<dependency>
<groupId>com.brsanthu</groupId>
<artifactId>migbase64</artifactId>
</dependency>
</dependencies>
确实没有引入报错的依赖包OkHttp,所以必然报错。但之前的接口文件,也是这些依赖,为什么他们就能编译通过,也不依赖这个包呢,就此,我开始了爬坑之路。
二、爬坑过程
既然之前的接口文件编译都没有问题,那么,肯定是我修改的配置文件有问题了,就从这里开始入手,一步步排查。中间做了很多无用功,就不再描述了,总之,一直忽略了细节,找错了方向。最后拿之前的客户端接口文件和当前修改的文件进行一一对比,折腾了半天才发现是少了library那一行,真是一个粗心,在小小的配置文件里,挖了一个大大的坑,白白在坑里耗了那么长时间。为了一探究竟,加深印象,所以就浏览了下swagger-codegen的源码。
三、阅读源码
根据pom.xml中配置的plugin,找到swagger-codegen的子模块:swagger-codegen-maven-plugin,该模块只有一个源码,如下图所示:
CodeGenMojo.java的执行入口是execute()方法,我们就从该方法进入,以下是源码,不相关的代码都被删掉了:
@Override
public void execute() throws MojoExecutionException {
// Using the naive approach for achieving thread safety
synchronized (CodeGenMojo.class) {
execute_();
}
}
protected void execute_() throws MojoExecutionException {
if (skip) {
getLog().info("Code generation is skipped.");
// Even when no new sources are generated, the existing ones should
// still be compiled if needed.
addCompileSourceRootIfConfigured();
return;
}
// attempt to read from config file
CodegenConfigurator configurator = CodegenConfigurator.fromFile(configurationFile);
// if a config file wasn't specified or we were unable to read it
if (configurator == null) {
configurator = new CodegenConfigurator();
}
......
if (isNotEmpty(library)) {
configurator.setLibrary(library);
}
......
final ClientOptInput input = configurator.toClientOptInput();
final CodegenConfig config = input.getConfig();
try {
new DefaultGenerator().opts(input).generate();
} catch (Exception e) {
// Maven logs exceptions thrown by plugins only if invoked with -e
// I find it annoying to jump through hoops to get basic diagnostic information,
// so let's log it in any case:
getLog().error(e);
throw new MojoExecutionException(
"Code generation failed. See above for the full exception.");
}
addCompileSourceRootIfConfigured();
}
从上面的源码可知,如果library属性不为空,则将其赋值给configurator对象:
configurator.setLibrary(library);
configurator对象初始化过程:
// attempt to read from config file
CodegenConfigurator configurator = CodegenConfigurator.fromFile(configurationFile);
// if a config file wasn't specified or we were unable to read it
if (configurator == null) {
configurator = new CodegenConfigurator();
}
因为并未指定configurationFile,所以library的值被赋值给CodegenConfigurator创建的configurator对象。
继续往下执行代码:
final ClientOptInput input = configurator.toClientOptInput();
final CodegenConfig config = input.getConfig();
这两行代码,是将CodegenConfigurator创建的configurator对象中的配置参数都赋值给CodegenConfig对象config。
继续往下执行,这行代码就是生成源码的入口了:
new DefaultGenerator().opts(input).generate();
我们先看下DefaultGenerator.opts()方法:
public Generator opts(ClientOptInput opts) {
this.opts = opts;
this.swagger = opts.getSwagger();
this.config = opts.getConfig();
this.config.additionalProperties().putAll(opts.getOpts().getProperties());
String ignoreFileLocation = this.config.getIgnoreFilePathOverride();
if (ignoreFileLocation != null) {
final File ignoreFile = new File(ignoreFileLocation);
if (ignoreFile.exists() && ignoreFile.canRead()) {
this.ignoreProcessor = new CodegenIgnoreProcessor(ignoreFile);
} else {
LOGGER.warn("Ignore file specified at {} is not valid. This will fall back to an existing ignore file if present in the output directory.", ignoreFileLocation);
}
}
if (this.ignoreProcessor == null) {
this.ignoreProcessor = new CodegenIgnoreProcessor(this.config.getOutputDir());
}
return this;
}
以上代码主要是赋值和初始化,接着跟踪DefaultGenerator.generate()方法:
public List<File> generate() {
if (swagger == null || config == null) {
throw new RuntimeException("missing swagger input or config!");
}
configureGeneratorProperties();
configureSwaggerInfo();
// resolve inline models
InlineModelResolver inlineModelResolver = new InlineModelResolver();
inlineModelResolver.flatten(swagger);
List<File> files = new ArrayList<File>();
// models
List<Object> allModels = new ArrayList<Object>();
generateModels(files, allModels);
// apis
List<Object> allOperations = new ArrayList<Object>();
generateApis(files, allOperations, allModels);
// supporting files
Map<String, Object> bundle = buildSupportFileBundle(allOperations, allModels);
generateSupportingFiles(files, bundle);
config.processSwagger(swagger);
return files;
}
上面的代码,就是生成model和apis的地方,且各自单独用一个方法实现。这里我们就不跟踪models的生成方法了,仅跟踪apis的生成方法generateApis():
protected void generateApis(List<File> files, List<Object> allOperations, List<Object> allModels) {
if (!isGenerateApis) {
return;
}
Map<String, List<CodegenOperation>> paths = processPaths(swagger.getPaths());
Set<String> apisToGenerate = null;
String apiNames = System.getProperty("apis");
if (apiNames != null && !apiNames.isEmpty()) {
apisToGenerate = new HashSet<String>(Arrays.asList(apiNames.split(",")));
}
if (apisToGenerate != null && !apisToGenerate.isEmpty()) {
Map<String, List<CodegenOperation>> updatedPaths = new TreeMap<String, List<CodegenOperation>>();
for (String m : paths.keySet()) {
if (apisToGenerate.contains(m)) {
updatedPaths.put(m, paths.get(m));
}
}
paths = updatedPaths;
}
for (String tag : paths.keySet()) {
try {
List<CodegenOperation> ops = paths.get(tag);
Collections.sort(ops, new Comparator<CodegenOperation>() {
@Override
public int compare(CodegenOperation one, CodegenOperation another) {
return ObjectUtils.compare(one.operationId, another.operationId);
}
});
Map<String, Object> operation = processOperations(config, tag, ops, allModels);
operation.put("hostWithoutBasePath", getHostWithoutBasePath());
operation.put("basePath", basePath);
operation.put("basePathWithoutHost", basePathWithoutHost);
operation.put("contextPath", contextPath);
operation.put("baseName", tag);
operation.put("apiPackage", config.apiPackage());
operation.put("modelPackage", config.modelPackage());
operation.putAll(config.additionalProperties());
operation.put("classname", config.toApiName(tag));
operation.put("classVarName", config.toApiVarName(tag));
operation.put("importPath", config.toApiImport(tag));
operation.put("classFilename", config.toApiFilename(tag));
if (!config.vendorExtensions().isEmpty()) {
operation.put("vendorExtensions", config.vendorExtensions());
}
// Pass sortParamsByRequiredFlag through to the Mustache template...
boolean sortParamsByRequiredFlag = true;
if (this.config.additionalProperties().containsKey(CodegenConstants.SORT_PARAMS_BY_REQUIRED_FLAG)) {
sortParamsByRequiredFlag = Boolean.valueOf(this.config.additionalProperties().get(CodegenConstants.SORT_PARAMS_BY_REQUIRED_FLAG).toString());
}
operation.put("sortParamsByRequiredFlag", sortParamsByRequiredFlag);
processMimeTypes(swagger.getConsumes(), operation, "consumes");
processMimeTypes(swagger.getProduces(), operation, "produces");
allOperations.add(new HashMap<String, Object>(operation));
for (int i = 0; i < allOperations.size(); i++) {
Map<String, Object> oo = (Map<String, Object>) allOperations.get(i);
if (i < (allOperations.size() - 1)) {
oo.put("hasMore", "true");
}
}
for (String templateName : config.apiTemplateFiles().keySet()) {
String filename = config.apiFilename(templateName, tag);
if (!config.shouldOverwrite(filename) && new File(filename).exists()) {
LOGGER.info("Skipped overwriting " + filename);
continue;
}
File written = processTemplateToFile(operation, templateName, filename);
if (written != null) {
files.add(written);
}
}
if(isGenerateApiTests) {
// to generate api test files
for (String templateName : config.apiTestTemplateFiles().keySet()) {
String filename = config.apiTestFilename(templateName, tag);
// do not overwrite test file that already exists
if (new File(filename).exists()) {
LOGGER.info("File exists. Skipped overwriting " + filename);
continue;
}
File written = processTemplateToFile(operation, templateName, filename);
if (written != null) {
files.add(written);
}
}
}
if(isGenerateApiDocumentation) {
// to generate api documentation files
for (String templateName : config.apiDocTemplateFiles().keySet()) {
String filename = config.apiDocFilename(templateName, tag);
if (!config.shouldOverwrite(filename) && new File(filename).exists()) {
LOGGER.info("Skipped overwriting " + filename);
continue;
}
File written = processTemplateToFile(operation, templateName, filename);
if (written != null) {
files.add(written);
}
}
}
} catch (Exception e) {
throw new RuntimeException("Could not generate api file for '" + tag + "'", e);
}
}
if (System.getProperty("debugOperations") != null) {
LOGGER.info("############ Operation info ############");
Json.prettyPrint(allOperations);
}
}
上面是完整的代码,我们需要重点关注下面这一段:
for (String templateName : config.apiTemplateFiles().keySet()) {
String filename = config.apiFilename(templateName, tag);
if (!config.shouldOverwrite(filename) && new File(filename).exists()) {
LOGGER.info("Skipped overwriting " + filename);
continue;
}
File written = processTemplateToFile(operation, templateName, filename);
if (written != null) {
files.add(written);
}
}
注意看,for循环里面就是根据名字去获取模板路径,然后将路径传递给processTemplateToFile()方法,跟踪代码:
protected File processTemplateToFile(Map<String, Object> templateData, String templateName, String outputFilename) throws IOException {
String adjustedOutputFilename = outputFilename.replaceAll("//", "/").replace('/', File.separatorChar);
if (ignoreProcessor.allowsFile(new File(adjustedOutputFilename))) {
String templateFile = getFullTemplateFile(config, templateName);
String template = readTemplate(templateFile);
Mustache.Compiler compiler = Mustache.compiler();
compiler = config.processCompiler(compiler);
Template tmpl = compiler
.withLoader(new Mustache.TemplateLoader() {
@Override
public Reader getTemplate(String name) {
return getTemplateReader(getFullTemplateFile(config, name + ".mustache"));
}
})
.defaultValue("")
.compile(template);
writeToFile(adjustedOutputFilename, tmpl.execute(templateData));
return new File(adjustedOutputFilename);
}
LOGGER.info("Skipped generation of " + adjustedOutputFilename + " due to rule in .swagger-codegen-ignore");
return null;
}
继续跟踪上述方法中的getFullTemplateFile()方法:
/**
* Get the template file path with template dir prepended, and use the
* library template if exists.
*
* @param config Codegen config
* @param templateFile Template file
* @return String Full template file path
*/
public String getFullTemplateFile(CodegenConfig config, String templateFile) {
//1st the code will check if there's a <template folder>/libraries/<library> folder containing the file
//2nd it will check for the file in the specified <template folder> folder
//3rd it will check if there's an <embedded template>/libraries/<library> folder containing the file
//4th and last it will assume the file is in <embedded template> folder.
//check the supplied template library folder for the file
final String library = config.getLibrary();
if (StringUtils.isNotEmpty(library)) {
//look for the file in the library subfolder of the supplied template
final String libTemplateFile = buildLibraryFilePath(config.templateDir(), library, templateFile);
if (new File(libTemplateFile).exists()) {
return libTemplateFile;
}
}
//check the supplied template main folder for the file
final String template = config.templateDir() + File.separator + templateFile;
if (new File(template).exists()) {
return template;
}
//try the embedded template library folder next
if (StringUtils.isNotEmpty(library)) {
final String embeddedLibTemplateFile = buildLibraryFilePath(config.embeddedTemplateDir(), library, templateFile);
if (embeddedTemplateExists(embeddedLibTemplateFile)) {
// Fall back to the template file embedded/packaged in the JAR file library folder...
return embeddedLibTemplateFile;
}
}
// Fall back to the template file embedded/packaged in the JAR file...
return config.embeddedTemplateDir() + File.separator + templateFile;
}
上述方法里面,第一行就是获取config中的library属性,根据library去获取对应的模板,注意看该方法的注释也进行了描述,共有4种情况寻找模板,如果没有library属性,则会返回默认的模板。我们继续跟踪buildLibraryFilePath()方法:
private String buildLibraryFilePath(String dir, String library, String file) {
return dir + File.separator + "libraries" + File.separator + library + File.separator + file;
}
上述方法就是获取libraries目录下的模板文件,我们现在去看下libraries目录下的文件长啥样:
swagger使用的是mustache作为模板引擎,关于mustache的介绍,本文不涉及。我们拿libraries目录下的JSON.mustache模板和外层的默认模板来对比。
library=jersey2的模板如下:
package {{invokerPackage}};
{{#threetenbp}}
import org.threeten.bp.*;
{{/threetenbp}}
import com.fasterxml.jackson.annotation.*;
import com.fasterxml.jackson.databind.*;
{{#java8}}
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
{{/java8}}
{{#joda}}
import com.fasterxml.jackson.datatype.joda.JodaModule;
{{/joda}}
{{#threetenbp}}
import com.fasterxml.jackson.datatype.threetenbp.ThreeTenModule;
{{/threetenbp}}
import java.text.DateFormat;
import javax.ws.rs.ext.ContextResolver;
{{>generatedAnnotation}}
public class JSON implements ContextResolver<ObjectMapper> {
private ObjectMapper mapper;
public JSON() {
mapper = new ObjectMapper();
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper.configure(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE, false);
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
mapper.enable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING);
mapper.enable(DeserializationFeature.READ_ENUMS_USING_TO_STRING);
mapper.setDateFormat(new RFC3339DateFormat());
{{#java8}}
mapper.registerModule(new JavaTimeModule());
{{/java8}}
{{#joda}}
mapper.registerModule(new JodaModule());
{{/joda}}
{{#threetenbp}}
ThreeTenModule module = new ThreeTenModule();
module.addDeserializer(Instant.class, CustomInstantDeserializer.INSTANT);
module.addDeserializer(OffsetDateTime.class, CustomInstantDeserializer.OFFSET_DATE_TIME);
module.addDeserializer(ZonedDateTime.class, CustomInstantDeserializer.ZONED_DATE_TIME);
mapper.registerModule(module);
{{/threetenbp}}
}
/**
* Set the date format for JSON (de)serialization with Date properties.
* @param dateFormat Date format
*/
public void setDateFormat(DateFormat dateFormat) {
mapper.setDateFormat(dateFormat);
}
@Override
public ObjectMapper getContext(Class<?> type) {
return mapper;
}
}
没有library属性的模板如下:
{{>licenseInfo}}
package {{invokerPackage}};
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonParseException;
import com.google.gson.TypeAdapter;
import com.google.gson.internal.bind.util.ISO8601Utils;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import com.google.gson.JsonElement;
import io.gsonfire.GsonFireBuilder;
import io.gsonfire.TypeSelector;
{{#joda}}
import org.joda.time.DateTime;
import org.joda.time.LocalDate;
import org.joda.time.format.DateTimeFormatter;
import org.joda.time.format.DateTimeFormatterBuilder;
import org.joda.time.format.ISODateTimeFormat;
{{/joda}}
{{#threetenbp}}
import org.threeten.bp.LocalDate;
import org.threeten.bp.OffsetDateTime;
import org.threeten.bp.format.DateTimeFormatter;
{{/threetenbp}}
{{#models.0}}
import {{modelPackage}}.*;
{{/models.0}}
import okio.ByteString;
import java.io.IOException;
import java.io.StringReader;
import java.lang.reflect.Type;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.ParsePosition;
{{#java8}}
import java.time.LocalDate;
import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter;
{{/java8}}
import java.util.Date;
import java.util.Map;
import java.util.HashMap;
public class JSON {
private Gson gson;
private boolean isLenientOnJson = false;
private DateTypeAdapter dateTypeAdapter = new DateTypeAdapter();
private SqlDateTypeAdapter sqlDateTypeAdapter = new SqlDateTypeAdapter();
{{#joda}}
private DateTimeTypeAdapter dateTimeTypeAdapter = new DateTimeTypeAdapter();
private LocalDateTypeAdapter localDateTypeAdapter = new LocalDateTypeAdapter();
{{/joda}}
{{#jsr310}}
private OffsetDateTimeTypeAdapter offsetDateTimeTypeAdapter = new OffsetDateTimeTypeAdapter();
private LocalDateTypeAdapter localDateTypeAdapter = new LocalDateTypeAdapter();
{{/jsr310}}
private ByteArrayAdapter byteArrayAdapter = new ByteArrayAdapter();
public static GsonBuilder createGson() {
GsonFireBuilder fireBuilder = new GsonFireBuilder()
{{#parent}}
.registerTypeSelector({{classname}}.class, new TypeSelector() {
@Override
public Class getClassForElement(JsonElement readElement) {
Map classByDiscriminatorValue = new HashMap();
{{#children}}
classByDiscriminatorValue.put("{{name}}".toUpperCase(), {{classname}}.class);
{{/children}}
classByDiscriminatorValue.put("{{classname}}".toUpperCase(), {{classname}}.class);
return getClassByDiscriminator(
classByDiscriminatorValue,
getDiscriminatorValue(readElement, "{{discriminator}}"));
}
})
{{/parent}}
;
GsonBuilder builder = fireBuilder.createGsonBuilder();
{{#disableHtmlEscaping}}
builder.disableHtmlEscaping();
{{/disableHtmlEscaping}}
return builder;
}
private static String getDiscriminatorValue(JsonElement readElement, String discriminatorField) {
JsonElement element = readElement.getAsJsonObject().get(discriminatorField);
if(null == element) {
throw new IllegalArgumentException("missing discriminator field: <" + discriminatorField + ">");
}
return element.getAsString();
}
private static Class getClassByDiscriminator(Map classByDiscriminatorValue, String discriminatorValue) {
Class clazz = (Class) classByDiscriminatorValue.get(discriminatorValue.toUpperCase());
if(null == clazz) {
throw new IllegalArgumentException("cannot determine model class of name: <" + discriminatorValue + ">");
}
return clazz;
}
public JSON() {
gson = createGson()
.registerTypeAdapter(Date.class, dateTypeAdapter)
.registerTypeAdapter(java.sql.Date.class, sqlDateTypeAdapter)
{{#joda}}
.registerTypeAdapter(DateTime.class, dateTimeTypeAdapter)
.registerTypeAdapter(LocalDate.class, localDateTypeAdapter)
{{/joda}}
{{#jsr310}}
.registerTypeAdapter(OffsetDateTime.class, offsetDateTimeTypeAdapter)
.registerTypeAdapter(LocalDate.class, localDateTypeAdapter)
{{/jsr310}}
.registerTypeAdapter(byte[].class, byteArrayAdapter)
.create();
}
/**
* Get Gson.
*
* @return Gson
*/
public Gson getGson() {
return gson;
}
/**
* Set Gson.
*
* @param gson Gson
* @return JSON
*/
public JSON setGson(Gson gson) {
this.gson = gson;
return this;
}
public JSON setLenientOnJson(boolean lenientOnJson) {
isLenientOnJson = lenientOnJson;
return this;
}
/**
* Serialize the given Java object into JSON string.
*
* @param obj Object
* @return String representation of the JSON
*/
public String serialize(Object obj) {
return gson.toJson(obj);
}
/**
* Deserialize the given JSON string to Java object.
*
* @param <T> Type
* @param body The JSON string
* @param returnType The type to deserialize into
* @return The deserialized Java object
*/
@SuppressWarnings("unchecked")
public <T> T deserialize(String body, Type returnType) {
try {
if (isLenientOnJson) {
JsonReader jsonReader = new JsonReader(new StringReader(body));
// see https://google-gson.googlecode.com/svn/trunk/gson/docs/javadocs/com/google/gson/stream/JsonReader.html#setLenient(boolean)
jsonReader.setLenient(true);
return gson.fromJson(jsonReader, returnType);
} else {
return gson.fromJson(body, returnType);
}
} catch (JsonParseException e) {
// Fallback processing when failed to parse JSON form response body:
// return the response body string directly for the String return type;
if (returnType.equals(String.class))
return (T) body;
else throw (e);
}
}
/**
* Gson TypeAdapter for Byte Array type
*/
public class ByteArrayAdapter extends TypeAdapter<byte[]> {
@Override
public void write(JsonWriter out, byte[] value) throws IOException {
if (value == null) {
out.nullValue();
} else {
out.value(ByteString.of(value).base64());
}
}
@Override
public byte[] read(JsonReader in) throws IOException {
switch (in.peek()) {
case NULL:
in.nextNull();
return null;
default:
String bytesAsBase64 = in.nextString();
ByteString byteString = ByteString.decodeBase64(bytesAsBase64);
return byteString.toByteArray();
}
}
}
{{#joda}}
/**
* Gson TypeAdapter for Joda DateTime type
*/
public static class DateTimeTypeAdapter extends TypeAdapter<DateTime> {
private DateTimeFormatter formatter;
public DateTimeTypeAdapter() {
this(new DateTimeFormatterBuilder()
.append(ISODateTimeFormat.dateTime().getPrinter(), ISODateTimeFormat.dateOptionalTimeParser().getParser())
.toFormatter());
}
public DateTimeTypeAdapter(DateTimeFormatter formatter) {
this.formatter = formatter;
}
public void setFormat(DateTimeFormatter dateFormat) {
this.formatter = dateFormat;
}
@Override
public void write(JsonWriter out, DateTime date) throws IOException {
if (date == null) {
out.nullValue();
} else {
out.value(formatter.print(date));
}
}
@Override
public DateTime read(JsonReader in) throws IOException {
switch (in.peek()) {
case NULL:
in.nextNull();
return null;
default:
String date = in.nextString();
return formatter.parseDateTime(date);
}
}
}
/**
* Gson TypeAdapter for Joda LocalDate type
*/
public class LocalDateTypeAdapter extends TypeAdapter<LocalDate> {
private DateTimeFormatter formatter;
public LocalDateTypeAdapter() {
this(ISODateTimeFormat.date());
}
public LocalDateTypeAdapter(DateTimeFormatter formatter) {
this.formatter = formatter;
}
public void setFormat(DateTimeFormatter dateFormat) {
this.formatter = dateFormat;
}
@Override
public void write(JsonWriter out, LocalDate date) throws IOException {
if (date == null) {
out.nullValue();
} else {
out.value(formatter.print(date));
}
}
@Override
public LocalDate read(JsonReader in) throws IOException {
switch (in.peek()) {
case NULL:
in.nextNull();
return null;
default:
String date = in.nextString();
return formatter.parseLocalDate(date);
}
}
}
public JSON setDateTimeFormat(DateTimeFormatter dateFormat) {
dateTimeTypeAdapter.setFormat(dateFormat);
return this;
}
public JSON setLocalDateFormat(DateTimeFormatter dateFormat) {
localDateTypeAdapter.setFormat(dateFormat);
return this;
}
{{/joda}}
{{#jsr310}}
/**
* Gson TypeAdapter for JSR310 OffsetDateTime type
*/
public static class OffsetDateTimeTypeAdapter extends TypeAdapter<OffsetDateTime> {
private DateTimeFormatter formatter;
public OffsetDateTimeTypeAdapter() {
this(DateTimeFormatter.ISO_OFFSET_DATE_TIME);
}
public OffsetDateTimeTypeAdapter(DateTimeFormatter formatter) {
this.formatter = formatter;
}
public void setFormat(DateTimeFormatter dateFormat) {
this.formatter = dateFormat;
}
@Override
public void write(JsonWriter out, OffsetDateTime date) throws IOException {
if (date == null) {
out.nullValue();
} else {
out.value(formatter.format(date));
}
}
@Override
public OffsetDateTime read(JsonReader in) throws IOException {
switch (in.peek()) {
case NULL:
in.nextNull();
return null;
default:
String date = in.nextString();
if (date.endsWith("+0000")) {
date = date.substring(0, date.length()-5) + "Z";
}
return OffsetDateTime.parse(date, formatter);
}
}
}
/**
* Gson TypeAdapter for JSR310 LocalDate type
*/
public class LocalDateTypeAdapter extends TypeAdapter<LocalDate> {
private DateTimeFormatter formatter;
public LocalDateTypeAdapter() {
this(DateTimeFormatter.ISO_LOCAL_DATE);
}
public LocalDateTypeAdapter(DateTimeFormatter formatter) {
this.formatter = formatter;
}
public void setFormat(DateTimeFormatter dateFormat) {
this.formatter = dateFormat;
}
@Override
public void write(JsonWriter out, LocalDate date) throws IOException {
if (date == null) {
out.nullValue();
} else {
out.value(formatter.format(date));
}
}
@Override
public LocalDate read(JsonReader in) throws IOException {
switch (in.peek()) {
case NULL:
in.nextNull();
return null;
default:
String date = in.nextString();
return LocalDate.parse(date, formatter);
}
}
}
public JSON setOffsetDateTimeFormat(DateTimeFormatter dateFormat) {
offsetDateTimeTypeAdapter.setFormat(dateFormat);
return this;
}
public JSON setLocalDateFormat(DateTimeFormatter dateFormat) {
localDateTypeAdapter.setFormat(dateFormat);
return this;
}
{{/jsr310}}
/**
* Gson TypeAdapter for java.sql.Date type
* If the dateFormat is null, a simple "yyyy-MM-dd" format will be used
* (more efficient than SimpleDateFormat).
*/
public static class SqlDateTypeAdapter extends TypeAdapter<java.sql.Date> {
private DateFormat dateFormat;
public SqlDateTypeAdapter() {
}
public SqlDateTypeAdapter(DateFormat dateFormat) {
this.dateFormat = dateFormat;
}
public void setFormat(DateFormat dateFormat) {
this.dateFormat = dateFormat;
}
@Override
public void write(JsonWriter out, java.sql.Date date) throws IOException {
if (date == null) {
out.nullValue();
} else {
String value;
if (dateFormat != null) {
value = dateFormat.format(date);
} else {
value = date.toString();
}
out.value(value);
}
}
@Override
public java.sql.Date read(JsonReader in) throws IOException {
switch (in.peek()) {
case NULL:
in.nextNull();
return null;
default:
String date = in.nextString();
try {
if (dateFormat != null) {
return new java.sql.Date(dateFormat.parse(date).getTime());
}
return new java.sql.Date(ISO8601Utils.parse(date, new ParsePosition(0)).getTime());
} catch (ParseException e) {
throw new JsonParseException(e);
}
}
}
}
/**
* Gson TypeAdapter for java.util.Date type
* If the dateFormat is null, ISO8601Utils will be used.
*/
public static class DateTypeAdapter extends TypeAdapter<Date> {
private DateFormat dateFormat;
public DateTypeAdapter() {
}
public DateTypeAdapter(DateFormat dateFormat) {
this.dateFormat = dateFormat;
}
public void setFormat(DateFormat dateFormat) {
this.dateFormat = dateFormat;
}
@Override
public void write(JsonWriter out, Date date) throws IOException {
if (date == null) {
out.nullValue();
} else {
String value;
if (dateFormat != null) {
value = dateFormat.format(date);
} else {
value = ISO8601Utils.format(date, true);
}
out.value(value);
}
}
@Override
public Date read(JsonReader in) throws IOException {
try {
switch (in.peek()) {
case NULL:
in.nextNull();
return null;
default:
String date = in.nextString();
try {
if (dateFormat != null) {
return dateFormat.parse(date);
}
return ISO8601Utils.parse(date, new ParsePosition(0));
} catch (ParseException e) {
throw new JsonParseException(e);
}
}
} catch (IllegalArgumentException e) {
throw new JsonParseException(e);
}
}
}
public JSON setDateFormat(DateFormat dateFormat) {
dateTypeAdapter.setFormat(dateFormat);
return this;
}
public JSON setSqlDateFormat(DateFormat dateFormat) {
sqlDateTypeAdapter.setFormat(dateFormat);
return this;
}
}
由此可以看出,library=jersey2的时候,引入的第三方包很少,也不涉及开始报错的那个包,自此,问题得到答案,心中豁然开朗不少。