Skip to content

Commit

Permalink
Merge pull request #2681 from swagger-api/ticket-2079
Browse files Browse the repository at this point in the history
 refs #2079 - JsonView support (includes and replaces #2662)
  • Loading branch information
frantuma committed Feb 23, 2018
2 parents 55d818c + 964f8dd commit bb31a0f
Show file tree
Hide file tree
Showing 9 changed files with 852 additions and 58 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -180,4 +180,10 @@
*/

Extension[] extensions() default @Extension(properties = @ExtensionProperty(name = "", value = ""));

/**
* Ignores JsonView annotations while resolving operations and types. For backward compatibility
*
*/
boolean ignoreJsonView() default false;
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.swagger.converter;

import com.fasterxml.jackson.annotation.JsonView;
import io.swagger.models.Model;
import io.swagger.models.properties.Property;

Expand Down Expand Up @@ -45,4 +46,17 @@ public interface ModelConverterContext {
* @return an Iterator of ModelConverters. This iterator is not reused
*/
public Iterator<ModelConverter> getConverters();

/**
*
* @return A nullable JsonView annotation.
*/
public JsonView getJsonView();

/**
*
* @param view A nullable JsonView annotation, which is normally added to the annotated method
* to filter the response.
*/
public void setJsonView(JsonView view);
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.swagger.converter;

import com.fasterxml.jackson.annotation.JsonView;
import io.swagger.models.ComposedModel;
import io.swagger.models.Model;
import io.swagger.models.ModelImpl;
Expand Down Expand Up @@ -28,6 +29,8 @@ public class ModelConverterContextImpl implements ModelConverterContext {
private final HashMap<Type, Model> modelByType;
private final Set<Type> processedTypes;

private JsonView jsonView; // for filtering the response only

public ModelConverterContextImpl(List<ModelConverter> converters) {
this.converters = converters;
modelByName = new TreeMap<String, Model>();
Expand Down Expand Up @@ -116,4 +119,12 @@ public Model resolve(Type type) {

return resolved;
}

public JsonView getJsonView() {
return jsonView;
}

public void setJsonView(JsonView jsonView) {
this.jsonView = jsonView;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.swagger.converter;

import com.fasterxml.jackson.annotation.JsonView;
import com.fasterxml.jackson.databind.type.TypeFactory;
import io.swagger.jackson.ModelResolver;
import io.swagger.models.Model;
Expand Down Expand Up @@ -53,16 +54,25 @@ public void addClassToSkip(String cls) {
}

public Property readAsProperty(Type type) {
ModelConverterContextImpl context = new ModelConverterContextImpl(
converters);
return readAsProperty(type, null);
}

public Property readAsProperty(Type type, JsonView jsonView) {
ModelConverterContextImpl context = new ModelConverterContextImpl(converters);
context.setJsonView(jsonView);
return context.resolveProperty(type, null);
}

public Map<String, Model> read(Type type) {
return read(type, null);
}

public Map<String, Model> read(Type type, JsonView jsonView) {
Map<String, Model> modelMap = new HashMap<String, Model>();
if (shouldProcess(type)) {
ModelConverterContextImpl context = new ModelConverterContextImpl(
converters);
context.setJsonView(jsonView);
Model resolve = context.resolve(type);
for (Entry<String, Model> entry : context.getDefinedModels()
.entrySet()) {
Expand All @@ -75,11 +85,16 @@ public Map<String, Model> read(Type type) {
}

public Map<String, Model> readAll(Type type) {
return readAll(type, null);
}

public Map<String, Model> readAll(Type type, JsonView annotation) {
if (shouldProcess(type)) {
ModelConverterContextImpl context = new ModelConverterContextImpl(
converters);
context.setJsonView(annotation);

LOGGER.debug("ModelConverters readAll from " + type);
LOGGER.debug("ModelConverters readAll with JsonView annotation from " + type);
context.resolve(type);
return context.getDefinedModels();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonUnwrapped;
import com.fasterxml.jackson.annotation.JsonView;
import com.fasterxml.jackson.annotation.ObjectIdGenerator;
import com.fasterxml.jackson.annotation.ObjectIdGenerators;
import com.fasterxml.jackson.databind.BeanDescription;
Expand Down Expand Up @@ -226,6 +227,8 @@ public Model resolve(JavaType type, ModelConverterContext context, Iterator<Mode
return new ModelImpl();
}

name = decorateModelName(context, name);

/**
* --Preventing parent/child hierarchy creation loops - Comment 1--
* Creating a parent model will result in the creation of child models. Creating a child model will result in
Expand Down Expand Up @@ -386,6 +389,9 @@ public Model resolve(JavaType type, ModelConverterContext context, Iterator<Mode
}

annotations = annotationList.toArray(new Annotation[annotationList.size()]);
if(hiddenByJsonView(annotations, context)) {
continue;
}

ApiModelProperty mp = member.getAnnotation(ApiModelProperty.class);

Expand Down Expand Up @@ -628,6 +634,47 @@ parent class @ApiModel annotation, then do the following:
return model;
}

/**
* Decorate the name based on the JsonView
*/
private String decorateModelName(ModelConverterContext context, String originalName) {
String name = originalName;
if (context.getJsonView() != null && context.getJsonView().value().length > 0) {
String COMBINER = "-or-";
StringBuffer sb = new StringBuffer();
for (Class<?> view : context.getJsonView().value()) {
sb.append(view.getSimpleName()).append(COMBINER);
}
String suffix = sb.toString().substring(0, sb.length() - COMBINER.length());
name = originalName + "_" + suffix;
}
return name;
}

private boolean hiddenByJsonView(Annotation[] annotations,
ModelConverterContext context) {
JsonView jsonView = context.getJsonView();
if (jsonView == null)
return false;

Class<?>[] filters = jsonView.value();
boolean containsJsonViewAnnotation = false;
for (Annotation ant : annotations) {
if (ant instanceof JsonView) {
containsJsonViewAnnotation = true;
Class<?>[] views = ((JsonView) ant).value();
for (Class<?> f : filters) {
for (Class<?> v : views) {
if (v == f || v.isAssignableFrom(f)) {
return false;
}
}
}
}
}
return containsJsonViewAnnotation;
}

protected boolean ignore(final Annotated member, final XmlAccessorType xmlAccessorTypeAnnotation, final String propName, final Set<String> propertiesToIgnore) {
if (propertiesToIgnore.contains(propName)) {
return true;
Expand Down
33 changes: 23 additions & 10 deletions modules/swagger-jaxrs/src/main/java/io/swagger/jaxrs/Reader.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package io.swagger.jaxrs;

import com.fasterxml.jackson.annotation.JsonView;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
Expand Down Expand Up @@ -770,7 +771,7 @@ String getPath(javax.ws.rs.Path classLevelPath, javax.ws.rs.Path methodLevelPath
}
}

private Map<String, Property> parseResponseHeaders(ResponseHeader[] headers) {
private Map<String, Property> parseResponseHeaders(ResponseHeader[] headers, JsonView jsonView) {
Map<String, Property> responseHeaders = null;
if (headers != null) {
for (ResponseHeader header : headers) {
Expand All @@ -783,7 +784,7 @@ private Map<String, Property> parseResponseHeaders(ResponseHeader[] headers) {
Class<?> cls = header.response();

if (!isVoid(cls)) {
final Property property = ModelConverters.getInstance().readAsProperty(cls);
final Property property = ModelConverters.getInstance().readAsProperty(cls, jsonView);
if (property != null) {
Property responseProperty = ContainerWrapper.wrapContainer(header.responseContainer(), property,
ContainerWrapper.ARRAY, ContainerWrapper.LIST, ContainerWrapper.SET);
Expand Down Expand Up @@ -834,15 +835,20 @@ private Operation parseMethod(Class<?> cls, Method method, AnnotatedMethod annot
Type responseType = null;
Map<String, Property> defaultResponseHeaders = new LinkedHashMap<String, Property>();

JsonView jsonViewAnnotation = ReflectionUtils.getAnnotation(method, JsonView.class);

if (apiOperation != null) {
if (apiOperation.hidden()) {
return null;
}
if (apiOperation.ignoreJsonView()) {
jsonViewAnnotation = null;
}
if (!apiOperation.nickname().isEmpty()) {
operationId = apiOperation.nickname();
}

defaultResponseHeaders = parseResponseHeaders(apiOperation.responseHeaders());
defaultResponseHeaders = parseResponseHeaders(apiOperation.responseHeaders(), jsonViewAnnotation);

operation.summary(apiOperation.value()).description(apiOperation.notes());

Expand Down Expand Up @@ -892,13 +898,13 @@ private Operation parseMethod(Class<?> cls, Method method, AnnotatedMethod annot
responseType = method.getGenericReturnType();
}
if (isValidResponse(responseType)) {
final Property property = ModelConverters.getInstance().readAsProperty(responseType);
final Property property = ModelConverters.getInstance().readAsProperty(responseType, jsonViewAnnotation);
if (property != null) {
final Property responseProperty = ContainerWrapper.wrapContainer(responseContainer, property);
final int responseCode = (apiOperation == null) ? 200 : apiOperation.code();
operation.response(responseCode, new Response().description(SUCCESSFUL_OPERATION).schema(responseProperty)
.headers(defaultResponseHeaders));
appendModels(responseType);
appendModelsWithJsonView(responseType, jsonViewAnnotation);
}
}

Expand Down Expand Up @@ -936,15 +942,15 @@ private Operation parseMethod(Class<?> cls, Method method, AnnotatedMethod annot
}

for (ApiResponse apiResponse : apiResponses) {
addResponse(operation, apiResponse);
addResponse(operation, apiResponse, jsonViewAnnotation);
}
// merge class level @ApiResponse
for (ApiResponse apiResponse : classApiResponses) {
String key = (apiResponse.code() == 0) ? "default" : String.valueOf(apiResponse.code());
if (operation.getResponses() != null && operation.getResponses().containsKey(key)) {
continue;
}
addResponse(operation, apiResponse);
addResponse(operation, apiResponse, jsonViewAnnotation);
}

if (ReflectionUtils.getAnnotation(method, Deprecated.class) != null) {
Expand Down Expand Up @@ -998,8 +1004,8 @@ private void processOperationDecorator(Operation operation, Method method) {
}
}

private void addResponse(Operation operation, ApiResponse apiResponse) {
Map<String, Property> responseHeaders = parseResponseHeaders(apiResponse.responseHeaders());
private void addResponse(Operation operation, ApiResponse apiResponse, JsonView jsonView) {
Map<String, Property> responseHeaders = parseResponseHeaders(apiResponse.responseHeaders(), jsonView);

Response response = new Response()
.description(apiResponse.message()).headers(responseHeaders);
Expand All @@ -1014,7 +1020,7 @@ private void addResponse(Operation operation, ApiResponse apiResponse) {
response.schema(new RefProperty(apiResponse.reference()));
} else if (!isVoid(apiResponse.response())) {
Type responseType = apiResponse.response();
final Property property = ModelConverters.getInstance().readAsProperty(responseType);
final Property property = ModelConverters.getInstance().readAsProperty(responseType, jsonView);
if (property != null) {
response.schema(ContainerWrapper.wrapContainer(apiResponse.responseContainer(), property));
appendModels(responseType);
Expand Down Expand Up @@ -1113,6 +1119,13 @@ private void appendModels(Type type) {
}
}

private void appendModelsWithJsonView(Type type, JsonView annotation) {
final Map<String, Model> models = ModelConverters.getInstance().readAll(type, annotation);
for (Map.Entry<String, Model> entry : models.entrySet()) {
swagger.model(entry.getKey(), entry.getValue());
}
}

private static boolean isVoid(Type type) {
final Class<?> cls = TypeFactory.defaultInstance().constructType(type).getRawClass();
return Void.class.isAssignableFrom(cls) || Void.TYPE.isAssignableFrom(cls);
Expand Down

0 comments on commit bb31a0f

Please sign in to comment.