Skip to content

Instantly share code, notes, and snippets.

@vincent-zurczak
Created August 24, 2016 18:26
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save vincent-zurczak/65143a0dbe69b4a86c1ae094e00dec83 to your computer and use it in GitHub Desktop.
Save vincent-zurczak/65143a0dbe69b4a86c1ae094e00dec83 to your computer and use it in GitHub Desktop.
Generating swagger.json files with Enunciate and custom objects mappers
<?xml version="1.0"?>
<enunciate
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://enunciate.webcohesion.com/schemas/enunciate-2.5.0.xsd">
<title>Roboconf REST API</title>
<description>The REST API for Roboconf's Administration</description>
<contact name="the Roboconf team" url="http://roboconf.net" />
<modules>
<!-- Disabled modules: almost all -->
<jackson1 disabled="true" />
<jaxb disabled="true" />
<jaxws disabled="true" />
<spring-web disabled="true" />
<idl disabled="true" />
<c-xml-client disabled="true" />
<csharp-xml-client disabled="true" />
<java-xml-client disabled="true" />
<java-json-client disabled="true" />
<gwt-json-overlay disabled="true" />
<obj-c-xml-client disabled="true" />
<php-xml-client disabled="true" />
<php-json-client disabled="true" />
<ruby-json-client disabled="true" />
<!-- Enabled modules -->
<jackson disabled="false" collapse-type-hierarchy="true" />
<jaxrs disabled="false" />
<docs disabled="false" />
<swagger disabled="false" basePath="/roboconf-dm" host="localhost:8181" />
</modules>
</enunciate>
import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.ObjectCodec;
import com.fasterxml.jackson.core.Version;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.module.SimpleModule;
import net.roboconf.core.model.beans.*;
/**
* A set of utilities to bind Roboconf's runtime model to JSon.
* @author Vincent Zurczak - Linagora
*/
public final class JSonBindingUtils {
public static final Map<Class<?>,? super JsonSerializer<?>> SERIALIZERS = new HashMap<> ();
static {
SERIALIZERS.put( Instance.class, new InstanceSerializer());
SERIALIZERS.put( ApplicationTemplate.class, new ApplicationTemplateSerializer());
SERIALIZERS.put( Application.class, new ApplicationSerializer());
}
/**
* Creates a mapper with specific binding for Roboconf types.
* @return a non-null, configured mapper
*/
@SuppressWarnings( { "unchecked", "rawtypes" } )
public static ObjectMapper createObjectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.configure( DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false );
SimpleModule module = new SimpleModule( "RoboconfModule", new Version( 1, 0, 0, null, null, null ));
for( Map.Entry<Class<?>,? super JsonSerializer<?>> entry : SERIALIZERS.entrySet())
module.addSerializer((Class) entry.getKey(), (JsonSerializer) entry.getValue());
for( Map.Entry<Class<?>,? super JsonDeserializer<?>> entry : DESERIALIZERS.entrySet())
module.addDeserializer((Class) entry.getKey(), (JsonDeserializer) entry.getValue());
mapper.registerModule( module );
return mapper;
}
}
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>net.roboconf</groupId>
<artifactId>roboconf-dm-rest-services</artifactId>
<name>Roboconf :: Deployment Manager :: REST Services</name>
<packaging>bundle</packaging>
<properties>
<enunciate.version>2.6.0</enunciate.version>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.2.2</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>com.webcohesion.enunciate</groupId>
<artifactId>enunciate-maven-plugin</artifactId>
<version>${enunciate.version}</version>
<executions>
<execution>
<goals>
<goal>docs</goal>
</goals>
</execution>
</executions>
<configuration>
<docsDir>${project.build.directory}/docs</docsDir>
<configFile>${project.basedir}/enunciate.xml</configFile>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<executions>
<execution>
<phase>test-compile</phase>
<configuration>
<target>
<property name="test_classpath" refid="maven.test.classpath"/>
<java classname="net.roboconf.dm.rest.services.swagger.UpdateSwaggerJson" classpath="${test_classpath}" />
</target>
</configuration>
<goals>
<goal>run</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>1.12</version>
<executions>
<execution>
<goals>
<goal>attach-artifact</goal>
</goals>
<configuration>
<artifacts>
<artifact>
<file>${project.build.directory}/docs/apidocs/ui/swagger.json</file>
<type>json</type>
<classifier>swagger</classifier>
</artifact>
</artifacts>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import net.roboconf.core.internal.tests.TestApplication;
import net.roboconf.core.model.beans.*;
import net.roboconf.core.model.runtime.*;
import net.roboconf.core.utils.Utils;
import net.roboconf.dm.rest.commons.Diagnostic;
import net.roboconf.dm.rest.commons.Diagnostic.DependencyInformation;
import net.roboconf.dm.rest.commons.json.JSonBindingUtils;
/**
* @author Vincent Zurczak - Linagora
*/
public class UpdateSwaggerJson {
final Set<Class<?>> processedClasses = new HashSet<> ();
/**
* @param args
*/
public static void main( String[] args ) {
try {
UpdateSwaggerJson updater = new UpdateSwaggerJson();
JsonObject newDef = updater.prepareNewDefinitions();
updater.updateSwaggerJson( newDef );
} catch( Exception e ) {
e.printStackTrace();
}
}
/**
* Prepares the JSon object to inject as the new definitions in the swagger.json file.
* @return a non-null object
* @throws IOException if something failed
*/
public JsonObject prepareNewDefinitions() throws IOException {
ObjectMapper mapper = JSonBindingUtils.createObjectMapper();
StringWriter writer = new StringWriter();
JsonObject newDef = new JsonObject();
// Create a model, as complete as possible
TestApplication app = new TestApplication();
app.bindWithApplication( "externalExportPrefix1", "application 1" );
app.bindWithApplication( "externalExportPrefix1", "application 2" );
app.bindWithApplication( "externalExportPrefix2", "application 3" );
app.setName( "My Application with special chàràcters" );
app.getTemplate().externalExports.put( "internalGraphVariable", "variableAlias" );
app.getTemplate().setExternalExportsPrefix( "externalExportPrefix" );
app.getTemplate().setDescription( "some description" );
// Serialize things and generate the examples
// (*) Applications
writer = new StringWriter();
mapper.writeValue( writer, app );
String s = writer.toString();
convertToTypes( s, Application.class, newDef );
// (*) Application Templates
writer = new StringWriter();
mapper.writeValue( writer, app.getTemplate());
s = writer.toString();
convertToTypes( s, ApplicationTemplate.class, newDef );
// Etc.
return newDef;
}
/**
* @param newDef the new "definitions" object
* @throws IOException if something went wrong
*/
private void updateSwaggerJson( JsonObject newDef ) throws IOException {
File f = new File( "target/docs/apidocs/ui/swagger.json" );
if( ! f.exists())
throw new RuntimeException( "The swagger.json file was not found." );
JsonParser jsonParser = new JsonParser();
String content = Utils.readFileContent( f );
// Hack: for some operations, Enunciate indicates the return type is "file", which is wrong.
content = content.replaceAll( "\"type\"\\s*:\\s*\"file\"", "\"type\": \"\"" );
// Hack
JsonElement jsonTree = jsonParser.parse( content );
Set<String> currentTypes = new HashSet<> ();
for( Map.Entry<String,JsonElement> entry : jsonTree.getAsJsonObject().get( "definitions" ).getAsJsonObject().entrySet()) {
currentTypes.add( entry.getKey());
}
Set<String> newTypes = new HashSet<> ();
for( Map.Entry<String,JsonElement> entry : newDef.entrySet()) {
newTypes.add( entry.getKey());
}
currentTypes.removeAll( newTypes );
for( String s : currentTypes ) {
System.out.println( "Type not appearing in the updated swagger definitions: " + s );
}
Gson gson = new GsonBuilder().setPrettyPrinting().create();
jsonTree.getAsJsonObject().add( "definitions", jsonParser.parse( gson.toJson( newDef )));
String json = gson.toJson( jsonTree );
Utils.writeStringInto( json, f );
}
/**
* Creates a JSon object from a serialization result.
* @param serialization the serialization result
* @param clazz the class for which this serialization was made
* @param newDef the new definition object to update
*/
public void convertToTypes( String serialization, Class<?> clazz, JsonObject newDef ) {
convertToTypes( serialization, clazz.getSimpleName(), newDef );
this.processedClasses.add( clazz );
}
/**
* Creates a JSon object from a serialization result.
* @param serialization the serialization result
* @param className a class or type name
* @param newDef the new definition object to update
*/
public void convertToTypes( String serialization, String className, JsonObject newDef ) {
JsonParser jsonParser = new JsonParser();
JsonElement jsonTree = jsonParser.parse( serialization );
// Creating the swagger definition
JsonObject innerObject = new JsonObject();
// Start adding basic properties
innerObject.addProperty( "title", className );
innerObject.addProperty( "definition", "" );
innerObject.addProperty( "type", jsonTree.isJsonObject() ? "object" : jsonTree.isJsonArray() ? "array" : "string" );
// Prevent errors with classic Swagger UI
innerObject.addProperty( "properties", "" );
// Inner properties
innerObject.add( "example", jsonTree.getAsJsonObject());
// Update our global definition
newDef.add( "json_" + className, innerObject );
}
}
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import org.junit.Assert;
import org.junit.Test;
import net.roboconf.dm.rest.commons.json.JSonBindingUtils;
import net.roboconf.dm.rest.commons.json.MapWrapper;
import net.roboconf.dm.rest.commons.json.MappedCollectionWrapper;
import net.roboconf.dm.rest.commons.json.StringWrapper;
/**
* @author Vincent Zurczak - Linagora
*/
public class UpdateSwaggerJsonTest {
@Test
public void verifyProcessedClasses() throws Exception {
UpdateSwaggerJson updater = new UpdateSwaggerJson();
updater.prepareNewDefinitions();
Set<Class<?>> classes = new HashSet<> ();
classes.addAll( JSonBindingUtils.SERIALIZERS.keySet());
classes.removeAll( updater.processedClasses );
Assert.assertEquals( Collections.emptySet(), classes );
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment