Created
August 24, 2016 18:26
-
-
Save vincent-zurczak/65143a0dbe69b4a86c1ae094e00dec83 to your computer and use it in GitHub Desktop.
Generating swagger.json files with Enunciate and custom objects mappers
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 ); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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