/**
* 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.HashSet;
import java.util.Map;
import java.util.Objects;
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.Constants;
import net.roboconf.core.model.beans.Application;
import net.roboconf.core.model.beans.ApplicationTemplate;
import net.roboconf.core.model.beans.Component;
import net.roboconf.core.model.beans.ExportedVariable;
import net.roboconf.core.model.beans.Graphs;
import net.roboconf.core.model.beans.ImportedVariable;
import net.roboconf.core.model.beans.Instance;
import net.roboconf.core.model.helpers.InstanceHelpers;
import net.roboconf.core.model.runtime.Preference;
import net.roboconf.core.model.runtime.Preference.PreferenceKeyCategory;
import net.roboconf.core.model.runtime.ScheduledJob;
import net.roboconf.core.model.runtime.TargetUsageItem;
import net.roboconf.core.model.runtime.TargetWrapperDescriptor;
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.beans.TargetAssociation;
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 {
new UpdateSwaggerJson().run( args );
} catch( Exception e ) {
e.printStackTrace();
System.exit( 3 );
}
}
/**
* The method that does the job.
* @param args
* @throws Exception
*/
public void run( String[] args ) throws Exception {
// Check
File baseDirectory = null;
if( args.length != 1
|| ! (baseDirectory = new File( args[ 0 ])).exists())
throw new RuntimeException( "The path of the module's directory was expected as an argument." );
// Update
UpdateSwaggerJson updater = new UpdateSwaggerJson();
JsonObject newDef = updater.prepareNewDefinitions();
updater.updateSwaggerJson( baseDirectory, newDef );
}
/**
* 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();
// Copy the test application here as test-jars cannot be referenced
// in compile scope.
Application app = newTestApplication();
// Create a model, as complete as possible
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 );
// (*) Component
Instance war = InstanceHelpers.findInstanceByPath( app, "/tomcat-vm/tomcat-server/hello-world" );
Objects.requireNonNull( war );
writer = new StringWriter();
mapper.writeValue( writer, war.getComponent());
s = writer.toString();
convertToTypes( s, Component.class, newDef );
// (*) Instance
writer = new StringWriter();
mapper.writeValue( writer, war );
s = writer.toString();
convertToTypes( s, Instance.class, newDef );
// (*) Diagnostics
Diagnostic diag = new Diagnostic( "/vm1/server1" );
DependencyInformation di = new DependencyInformation( "facetOrComponentName", true, false );
diag.getDependenciesInformation().add( di );
writer = new StringWriter();
mapper.writeValue( writer, diag );
s = writer.toString();
convertToTypes( s, Diagnostic.class, newDef );
writer = new StringWriter();
mapper.writeValue( writer, di );
s = writer.toString();
convertToTypes( s, DependencyInformation.class, newDef );
// (*) Target descriptors
TargetWrapperDescriptor twd = new TargetWrapperDescriptor();
twd.setId( "target-id" );
twd.setDescription( "some description" );
twd.setHandler( "iaas-ec2" );
twd.setName( "target name" );
twd.setDefault( true );
writer = new StringWriter();
mapper.writeValue( writer, twd );
s = writer.toString();
convertToTypes( s, TargetWrapperDescriptor.class, newDef );
// (*) Target usage items
TargetUsageItem tui = new TargetUsageItem();
tui.setName( "app or template name" );
tui.setQualifier( "template qualifier (null for applications)" );
tui.setReferencing( true );
tui.setUsing( true );
writer = new StringWriter();
mapper.writeValue( writer, tui );
s = writer.toString();
convertToTypes( s, TargetUsageItem.class, newDef );
// (*) Target associations
TargetAssociation ta = new TargetAssociation( "/vm-1", "VM", twd );
writer = new StringWriter();
mapper.writeValue( writer, ta );
s = writer.toString();
convertToTypes( s, TargetAssociation.class, newDef );
// (*) Preferences
Preference pref = new Preference( "key", "value", PreferenceKeyCategory.AUTONOMIC );
writer = new StringWriter();
mapper.writeValue( writer, pref );
s = writer.toString();
convertToTypes( s, Preference.class, newDef );
// (*) Scheduled jobs
ScheduledJob job = new ScheduledJob();
job.setAppName( "application name" );
job.setCmdName( "command name" );
job.setCron( "0 0 12 ? * WED" );
job.setJobId( "job-id" );
job.setJobName( "job name" );
writer = new StringWriter();
mapper.writeValue( writer, job );
s = writer.toString();
convertToTypes( s, ScheduledJob.class, newDef );
return newDef;
}
/**
* @param newDef the new "definitions" object
* @throws IOException if something went wrong
*/
private void updateSwaggerJson( File baseDirectory, JsonObject newDef ) throws IOException {
File f = new File( baseDirectory, "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: remove useless parts.
// For some operations, Enunciate indicates the return type is "file", which is wrong.
// Empty types, empty schemas and empty headers are useless too.
content = content.replaceAll( "\"type\"\\s*:\\s*\"file\",?", "" );
content = content.replaceAll( "\"type\"\\s*:\\s*\"\",?", "" );
content = content.replaceAll( "\"headers\"\\s*:\\s*\\{\\},?", "" );
content = content.replaceAll( "\"schema\": \\{\\s*\"description\"\\s*:\\s*\"\"\\s*\\s*},?", "" );
content = content.replaceAll( ",\\s+(\n[ \t]+\\})", "$1" );
// Hack
// Hack: arrays of arrays are a non-sense (Enunciate bug?)
StringBuilder sb = new StringBuilder();
sb.append( "(\\s*\"items\": \\{\n)" );
sb.append( "\\s*\"type\": \"array\",\n" );
sb.append( "\\s*\"items\": \\{\n" );
sb.append( "(\\s*\"\\$ref\": \"[^\"]+\"\n)" );
sb.append( "\\s*\\}" );
content = content.replaceAll( sb.toString(), "$1$2" );
// 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 static 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 );
}
/**
* @return a new test application
*/
static Application newTestApplication() {
ApplicationTemplate tpl = new ApplicationTemplate();
tpl.setName( "test-app" );
tpl.setQualifier( "test" );
// Root instances
Component vmComponent = new Component( "vm" ).installerName( Constants.TARGET_INSTALLER );
Instance tomcatVm = new Instance( "tomcat-vm" ).component( vmComponent );
Instance mySqlVm = new Instance( "mysql-vm" ).component( vmComponent );
// Children instances
Component tomcatComponent = new Component( "tomcat" ).installerName( "puppet" );
Instance tomcat = new Instance( "tomcat-server" ).component( tomcatComponent );
Component mySqlComponent = new Component( "mysql" ).installerName( "puppet" );
mySqlComponent.addExportedVariable( new ExportedVariable( "port", "3306" ));
mySqlComponent.addExportedVariable( new ExportedVariable( "ip", null ));
Instance mySql = new Instance( "mysql-server" ).component( mySqlComponent );
Component warComponent = new Component( "war" ).installerName( "script" );
warComponent.addExportedVariable( new ExportedVariable( "port", "8080" ));
warComponent.addExportedVariable( new ExportedVariable( "ip", null ));
warComponent.addImportedVariable( new ImportedVariable( "mysql.port", false, false ));
warComponent.addImportedVariable( new ImportedVariable( "mysql.ip", false, false ));
Instance war = new Instance( "hello-world" ).component( warComponent );
// Make the glue
InstanceHelpers.insertChild( tomcatVm, tomcat );
InstanceHelpers.insertChild( tomcat, war );
InstanceHelpers.insertChild( mySqlVm, mySql );
vmComponent.addChild( mySqlComponent );
vmComponent.addChild( tomcatComponent );
tomcatComponent.addChild( warComponent );
tpl.setGraphs( new Graphs());
tpl.getGraphs().getRootComponents().add( vmComponent );
tpl.getRootInstances().add( mySqlVm );
tpl.getRootInstances().add( tomcatVm );
// Create the application
Application app = new Application( "test", tpl );
return app;
}
}