Skip to content
This repository was archived by the owner on Aug 25, 2021. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
# MsgCodec Changelog

## 3.3.0 (?)

### msgcodec-json-jaxrs
New module for simplified usage of JSON Codec in a REST API.

### msgcodec-json-swagger
New module to generate Swagger documentation in a REST API.

## 3.2.0

TODO: Missing

## 3.1.0

### msgcodec
Expand Down
8 changes: 7 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,13 @@ subprojects {
apply plugin: 'signing'
apply plugin: 'eclipse'
sourceCompatibility = '1.8'
ext.junit_version = '4.11'
ext {
junit_version = '4.11'
jaxrs_version = '2.0.1'
javax_annotation_version = '1.2'
swagger_version = '1.5.8'
}

compileTestJava.options.encoding = 'UTF-8'

task javadocJar(type: Jar) {
Expand Down
10 changes: 10 additions & 0 deletions msgcodec-json-jaxrs/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
description = 'JSON JAX-RS support'

dependencies {
compile project(':msgcodec')
compile project(':msgcodec-json')
compile group: 'javax.ws.rs', name: 'javax.ws.rs-api', version: jaxrs_version
compile group: 'javax.annotation', name: 'javax.annotation-api', version: javax_annotation_version

testCompile group: 'junit', name: 'junit', version:junit_version
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package com.cinnober.msgcodec.json.jaxrs;

import com.cinnober.msgcodec.Schema;
import com.cinnober.msgcodec.json.JsonCodec;
import com.cinnober.msgcodec.json.JsonCodecFactory;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import javax.annotation.Priority;
import javax.ws.rs.Consumes;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.ext.MessageBodyReader;

/**
* MessageBodyReader implementation for parsing MsgCodec messages in JSON format.
*
* @author [email protected]
*/
@Consumes(MediaType.APPLICATION_JSON)
@Priority(100) // Must be higher than JacksonMessageBodyProvider that is registered by default in e.g. DropWizard
public class JsonCodecMessageBodyReader implements MessageBodyReader<Object> {

private final Schema schema;
private final JsonCodec codec;

public JsonCodecMessageBodyReader(Schema schema) {
this.schema = schema;
codec = new JsonCodecFactory(schema).createCodec();
}

@Override
public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
return mediaType.equals(MediaType.APPLICATION_JSON_TYPE) && schema.getGroup(type) != null;
}

@Override
public Object readFrom(Class<Object> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, String> httpHeaders, InputStream entityStream) throws IOException, WebApplicationException {
try {
return codec.decodeStatic(type, entityStream);
} catch(Exception e) {
throw new WebApplicationException(Response.status(Status.BAD_REQUEST).entity(e.getMessage()).build());
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package com.cinnober.msgcodec.json.jaxrs;

import com.cinnober.msgcodec.Schema;
import com.cinnober.msgcodec.json.JsonCodec;
import com.cinnober.msgcodec.json.JsonCodecFactory;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import javax.annotation.Priority;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.MessageBodyWriter;

/**
* MessageBodyWriter implementation for serializing MsgCodec messages in JSON format.
*
* @author [email protected]
*/
@Produces(MediaType.APPLICATION_JSON)
@Priority(100) // Must be higher than JacksonMessageBodyProvider that is registered by default in e.g. DropWizard
public class JsonCodecMessageBodyWriter implements MessageBodyWriter<Object> {

private final Schema schema;
private final JsonCodec codec;

public JsonCodecMessageBodyWriter(Schema schema) {
this.schema = schema;
codec = new JsonCodecFactory(schema).createCodec();
}

@Override
public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
return mediaType.equals(MediaType.APPLICATION_JSON_TYPE) && schema.getGroup(type) != null;
}

@Override
public long getSize(Object t, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
return -1;
}

@Override
public void writeTo(Object t, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream) throws IOException, WebApplicationException {
codec.encodeStatic(t, entityStream);
}

}
Empty file.
Empty file.
Empty file.
11 changes: 11 additions & 0 deletions msgcodec-json-swagger/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
description = 'JSON Swagger documentation support'

dependencies {
compile project(':msgcodec')
compile project(':msgcodec-json')
compile group: 'javax.ws.rs', name: 'javax.ws.rs-api', version: jaxrs_version
// compile group: 'javax.annotation', name: 'javax.annotation-api', version: javax_annotation_version
compile group: 'io.swagger', name: 'swagger-jersey2-jaxrs', version: swagger_version

testCompile group: 'junit', name: 'junit', version:junit_version
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
package com.cinnober.msgcodec.json.swagger;

import com.cinnober.msgcodec.FieldDef;
import com.cinnober.msgcodec.GroupDef;
import com.cinnober.msgcodec.Schema;
import com.cinnober.msgcodec.TypeDef;
import com.cinnober.msgcodec.util.TimeFormat;
import com.fasterxml.jackson.databind.type.SimpleType;
import io.swagger.converter.ModelConverter;
import io.swagger.converter.ModelConverterContext;
import io.swagger.models.ComposedModel;
import io.swagger.models.Model;
import io.swagger.models.ModelImpl;
import io.swagger.models.RefModel;
import io.swagger.models.properties.AbstractProperty;
import io.swagger.models.properties.ArrayProperty;
import io.swagger.models.properties.BaseIntegerProperty;
import io.swagger.models.properties.BinaryProperty;
import io.swagger.models.properties.BooleanProperty;
import io.swagger.models.properties.DateProperty;
import io.swagger.models.properties.DateTimeProperty;
import io.swagger.models.properties.DecimalProperty;
import io.swagger.models.properties.DoubleProperty;
import io.swagger.models.properties.FloatProperty;
import io.swagger.models.properties.IntegerProperty;
import io.swagger.models.properties.LongProperty;
import io.swagger.models.properties.Property;
import io.swagger.models.properties.RefProperty;
import io.swagger.models.properties.StringProperty;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.Iterator;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
* The class converts a JSON msgcodec schema into a swagger model.
*
* <p>Usage:
* <pre>
* ModelConverters.getInstance().addConverter(new JsonCodecSwaggerModelConverter(schema));
* </pre>
*
* @author [email protected]
*/
public class JsonCodecSwaggerModelConverter implements ModelConverter {

private static final String DOC_ANOT = "doc";
private static final String JSTYPE_FIELD = "$type";

private final Schema schema;

public JsonCodecSwaggerModelConverter(Schema schema) {
this.schema = Objects.requireNonNull(schema);
}

private Type unbox(Type type) {
if (type instanceof SimpleType) {
SimpleType stype = (SimpleType) type;
return stype.getRawClass();
}
return type;
}


@Override
public Property resolveProperty(Type type, ModelConverterContext context, Annotation[] annotations, Iterator<ModelConverter> chain) {
if (chain.hasNext()) {
return chain.next().resolveProperty(type, context, annotations, chain);
}
return null;
}

@Override
public Model resolve(Type type, ModelConverterContext context, Iterator<ModelConverter> chain) {
Type javaType = unbox(type);
GroupDef group = schema.getGroup(javaType);
if (group != null) {
return createStaticModel(group, context);
}
if (chain.hasNext()) {
return chain.next().resolve(type, context, chain);
}
return null;
}

private ModelImpl createStaticModel(GroupDef group, ModelConverterContext context) {
ModelImpl model = new ModelImpl()
.name("static_"+group.getName())
.type("object")
.description(group.getAnnotation(DOC_ANOT));
addProperties(model, group, true, context);
return model;
}

private void addProperties(ModelImpl model, GroupDef group, boolean parents, ModelConverterContext context) {
if (parents) {
String superGroupName = group.getSuperGroup();
if (superGroupName != null) {
GroupDef superGroup = schema.getGroup(superGroupName);
addProperties(model, superGroup, parents, context);
// PENDING: refer to the super group properties using "allOf", instead of copying them
}
}
for (FieldDef field : group.getFields()) {
Property prop = createProperty(field.getType(), context);
prop.setName(field.getName());
prop.setRequired(field.isRequired());
prop.setDescription(field.getAnnotation(DOC_ANOT));
model.addProperty(field.getName(), prop);
}
}

private String registerDynamicModel(GroupDef group, ModelConverterContext context) {
if (group != null) {

ComposedModel model = new ComposedModel();
model.setDescription(group.getAnnotation(DOC_ANOT));
String supergroup = group.getSuperGroup();
model.child(new RefModel(supergroup != null ? supergroup : "any"));

registerDynamicModel(supergroup != null ? schema.getGroup(supergroup) : null, context);

ModelImpl selfModel = new ModelImpl().type("object");
addProperties(selfModel, group, false, context);
model.child(selfModel);

context.defineModel(group.getName(), model);
return group.getName();
} else {
ModelImpl model = new ModelImpl().type("object").name("any").discriminator(JSTYPE_FIELD);
Property prop = new StringProperty().required(true);
prop.setName(JSTYPE_FIELD);
model.addProperty(JSTYPE_FIELD, prop);

context.defineModel("any", model);
return "any";
}
}

private Property createProperty(TypeDef type, ModelConverterContext context) {
type = schema.resolveToType(type, true);
GroupDef group = schema.resolveToGroup(type);
switch (type.getType()) {
case BIGDECIMAL:
return new DecimalProperty("bigdecimal");
case DECIMAL:
return new DecimalProperty("decimal");
case BIGINT:
return new BaseIntegerProperty("biginteger");
case INT8:
return new BaseIntegerProperty("int8");//.minimum((double) (-1 << 7)).maximum((double) 0x7f);
case UINT8:
return new BaseIntegerProperty("uint8");//.minimum(0.0).maximum((double) 0xff);
case INT16:
return new BaseIntegerProperty("int16");//.minimum((double) (-1 << 15)).maximum((double) 0x7fff);
case UINT16:
return new BaseIntegerProperty("uint16");//.minimum(0.0).maximum((double) 0xffff);
case INT32:
return new IntegerProperty();
case UINT32:
return new BaseIntegerProperty("uint32");//.minimum(0.0).maximum((double) 0xffffffffL);
case INT64:
return new LongProperty();
case UINT64:
return new BaseIntegerProperty("uint64");//.minimum(0.0);
case FLOAT32:
return new FloatProperty();
case FLOAT64:
return new DoubleProperty();
case BOOLEAN:
return new BooleanProperty();
case BINARY:
return new BinaryProperty();
case STRING:
return new StringProperty();
case TIME: {
TypeDef.Time timeType = (TypeDef.Time) type;
AbstractProperty prop;
if (timeType.getUnit() == TimeUnit.DAYS) {
prop = new DateProperty();
} else {
prop = new DateTimeProperty();
}
long exampleValue = System.currentTimeMillis();
if (timeType.getUnit().compareTo(TimeUnit.MILLISECONDS) < 0) {
exampleValue *= timeType.getUnit().toMillis(1);
} else {
exampleValue /= timeType.getUnit().toMillis(1);
}
String example = TimeFormat.getTimeFormat(timeType.getUnit(), timeType.getEpoch())
.format(exampleValue);
prop.setExample(example);
if (timeType.getTimeZone() != null) {
prop.setVendorExtension("x-timezone", timeType.getTimeZone().getID());
}
return prop;
}
case ENUM: {
TypeDef.Enum enumType = (TypeDef.Enum) type;
return new StringProperty("enum")._enum(
enumType.getSymbols().stream().map(TypeDef.Symbol::getName).collect(Collectors.toList()));
}
case SEQUENCE: {
TypeDef.Sequence sequenceType = (TypeDef.Sequence) type;
return new ArrayProperty(createProperty(sequenceType.getComponentType(), context));
}
case REFERENCE: {
//TypeDef.Reference refType = (TypeDef.Reference) type;
ModelImpl model = createStaticModel(group, context);
context.defineModel(model.getName(), model);
return new RefProperty(model.getName());
}
case DYNAMIC_REFERENCE: {
TypeDef.DynamicReference refType = (TypeDef.DynamicReference) type;
String name = registerDynamicModel(group, context);
for (GroupDef instanceGroup: schema.getDynamicGroups(refType.getRefType())) {
registerDynamicModel(instanceGroup, context);
}
RefProperty prop = new RefProperty(name);
return prop;
}
default:
throw new RuntimeException("Unhandled type: " + type.getType());
}
}

}
Empty file.
Empty file.
Empty file.
2 changes: 2 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ The code is divided into the following projects:

- `msgcodec`: contains annotations etc required for defining messages and protocols
- `msgcodec-json`: JSON codec
- `msgcodec-json-jaxrs`: JSON codec JAX-RS support
- `msgcodec-json-swagger`: JSON codec Swagger support
- `msgcodec-xml`: XML codec
- `msgcodec-blink`: Blink (compact format) codec
- `msgcodec-javadoc`: Javadoc doclet for extracting javadoc comments from messages.
Expand Down
Loading