/** * Copyright 2016-2017 Linagora, Université Joseph Fourier, Floralis * * The present code is developed in the scope of the joint LINAGORA - * Université Joseph Fourier - Floralis research program and is designated * as a "Result" pursuant to the terms and conditions of the LINAGORA * - Université Joseph Fourier - Floralis research program. Each copyright * holder of Results enumerated here above fully & independently holds complete * ownership of the complete Intellectual Property rights applicable to the whole * of said Results, and may freely exploit it in any manner which does not infringe * the moral rights of the other copyright holders. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package net.roboconf.swagger; import java.io.File; import java.io.IOException; import java.io.StringWriter; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.Objects; import java.util.TreeMap; import javax.ws.rs.core.MediaType; 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.JsonParser; import io.swagger.models.Contact; import io.swagger.models.Info; import io.swagger.models.License; import io.swagger.models.ModelImpl; import io.swagger.models.Operation; import io.swagger.models.Path; import io.swagger.models.Response; import io.swagger.models.Swagger; import io.swagger.models.Tag; import io.swagger.models.properties.RefProperty; import io.swagger.util.Json; import net.roboconf.core.model.beans.Application; import net.roboconf.core.model.beans.Instance; import net.roboconf.core.model.helpers.InstanceHelpers; import net.roboconf.core.model.runtime.EventType; import net.roboconf.core.utils.Utils; import net.roboconf.dm.rest.commons.beans.WebSocketMessage; import net.roboconf.dm.rest.commons.json.JSonBindingUtils; import net.roboconf.dm.rest.services.internal.ServletRegistrationComponent; /** * @author Vincent Zurczak - Linagora */ public class GenerateSwaggerJsonForWebSockets { private static final String INSTANCE = "instance"; private static final String APPLICATION = "application"; private static final String APPLICATION_TEMPLATE = "applicationTemplate"; private static final String TEXT = "text"; static final Map<String,String> NAME_TO_DESC = new TreeMap<> (); static final Map<String,String> NAME_TO_PARAM = new HashMap<> (); static { NAME_TO_DESC.put( INSTANCE, "Internal method invoked when an instance is created, deleted or modified." ); NAME_TO_DESC.put( APPLICATION, "Internal method invoked when an application is created, deleted or modified." ); NAME_TO_DESC.put( APPLICATION_TEMPLATE, "Internal method invoked when an application template is created, deleted or modified." ); NAME_TO_DESC.put( TEXT, "Internal method invoked when a raw text message must be propagated." ); StringBuilder sb = new StringBuilder(); sb.append( "The instance that was created, deleted or modified, as well as the context.\n" ); sb.append( "The context includes the application the instance belongs to.\n" ); sb.append( "It also includes the event type: CREATED, DELETED, CHANGED.\n" ); NAME_TO_PARAM.put( INSTANCE, sb.toString()); sb = new StringBuilder(); sb.append( "The application that was created, deleted or modified, as well as the context." ); sb.append( "The context includes the event type: CREATED, DELETED, CHANGED.\n" ); NAME_TO_PARAM.put( APPLICATION, sb.toString()); sb = new StringBuilder(); sb.append( "The application template that was created, deleted or modified, as well as the context.\n" ); sb.append( "The context includes the event type: CREATED, DELETED, CHANGED.\n" ); NAME_TO_PARAM.put( APPLICATION_TEMPLATE, sb.toString()); sb = new StringBuilder(); sb.append( "The message that was pushed directly from the DM.\n" ); NAME_TO_PARAM.put( TEXT, sb.toString()); } /** * @param args */ public static void main( String[] args ) { try { new GenerateSwaggerJsonForWebSockets().run( args ); } catch( Exception e ) { e.printStackTrace(); System.exit( 5 ); } } /** * The method that does the job. * @param args * @throws Exception */ public void run( String[] args ) throws Exception { // Check File baseDirectory = null; if( args.length != 2 || ! (baseDirectory = new File( args[ 0 ])).exists()) throw new RuntimeException( "The path of the module's directory was expected as an argument." ); // Generate File f = new File( baseDirectory, "target/docs/apidocs/ui/swagger-websocket.json" ); generate( args[1], f ); } void generate( String roboconfVersion, File jsonFile ) throws Exception { // Global model Swagger swagger = new Swagger(); swagger.setBasePath( ServletRegistrationComponent.WEBSOCKET_CONTEXT ); swagger.setHost( "localhost:8181" ); Tag tag = new Tag( ); tag.setName( "FromServer" ); swagger.setTags( Arrays.asList( tag )); Contact contact = new Contact(); contact.setName( "the Roboconf team" ); contact.setUrl( "http://roboconf.net" ); License license = new License(); license.setUrl( "http://www.apache.org/licenses/LICENSE-2.0.txt" ); license.setName( "The Apache Software License, Version 2.0" ); Info info = new Info(); info.setContact( contact ); info.setLicense( license ); info.setVersion( roboconfVersion ); info.setTitle( "Web Socket API" ); info.setDescription( "The Web Socket API for Roboconf's Administration" ); swagger.setInfo( info ); // The operations and the type definitions Map<String,Path> paths = new HashMap<>( NAME_TO_DESC.size()); swagger.setPaths( paths ); for( Map.Entry<String,String> entry : NAME_TO_DESC.entrySet()) { ModelImpl model = new ModelImpl(); model.setType( ModelImpl.OBJECT ); model.setTitle( entry.getKey()); swagger.addDefinition( "json_" + entry.getKey(), model ); Operation operation = new Operation(); operation.setDescription( entry.getValue()); operation.setSummary( entry.getValue()); operation.setTags( Arrays.asList( tag.getName())); operation.setOperationId( entry.getKey()); operation.setProduces( Arrays.asList( MediaType.APPLICATION_JSON )); Response response = new Response(); response.setDescription( NAME_TO_PARAM.get( entry.getKey())); RefProperty schema = new RefProperty(); schema.set$ref( "json_" + entry.getKey()); response.setSchema( schema ); operation.addResponse( "default", response ); Path path = new Path(); path.setPost( operation ); paths.put( entry.getKey(), path ); } // Write the model String swaggerJson = Json.pretty().writeValueAsString( swagger ); // Update the definition in JSon directly. // If we set the examples as strings, they are seen as a string // and not as a JSon object by Swagger UI. Map<String,String> definitions = prepareNewDefinitions(); JsonParser jsonParser = new JsonParser(); JsonElement jsonTree = jsonParser.parse( swaggerJson ); for( Map.Entry<String,String> entry : NAME_TO_DESC.entrySet()) { String serialization = definitions.get( entry.getKey()); JsonElement serializationAsJson = jsonParser.parse( serialization ); jsonTree.getAsJsonObject() .get( "definitions" ).getAsJsonObject() .get( "json_" + entry.getKey()).getAsJsonObject() .add( "example", serializationAsJson ); } // Back to a string Gson gson = new GsonBuilder().setPrettyPrinting().create(); swaggerJson = gson.toJson( jsonTree );; // Write the model in a file Utils.writeStringInto( swaggerJson, jsonFile ); } /** * Prepares the JSon object to inject as the new definitions in the swagger.json file. * @return a non-null map (key = operation ID, value = example) * @throws IOException if something went wrong */ public Map<String,String> prepareNewDefinitions() throws IOException { Map<String,String> result = new HashMap<> (); ObjectMapper mapper = JSonBindingUtils.createObjectMapper(); StringWriter writer = new StringWriter(); // Create a model, as complete as possible Application app = UpdateSwaggerJson.newTestApplication(); // Serialize things and generate the examples Instance tomcat = InstanceHelpers.findInstanceByPath( app, "/tomcat-vm/tomcat-server" ); Objects.requireNonNull( tomcat ); WebSocketMessage msg = new WebSocketMessage( tomcat, app, EventType.CHANGED ); writer = new StringWriter(); mapper.writeValue( writer, msg ); result.put( INSTANCE, writer.toString()); msg = new WebSocketMessage( app, EventType.CREATED ); writer = new StringWriter(); mapper.writeValue( writer, msg ); result.put( APPLICATION, writer.toString()); msg = new WebSocketMessage( app.getTemplate(), EventType.DELETED ); writer = new StringWriter(); mapper.writeValue( writer, msg ); result.put( APPLICATION_TEMPLATE, writer.toString()); msg = new WebSocketMessage( "A text message." ); writer = new StringWriter(); mapper.writeValue( writer, msg ); result.put( TEXT, writer.toString()); return result; } }