Skip to content

Commit cefc0b4

Browse files
authored
Merge pull request #3150 from patrickfav/feature/3133-maven-config
Add swagger config file option to maven plugin (#3133)
2 parents 13e9cf1 + 02d43ae commit cefc0b4

File tree

13 files changed

+567
-115
lines changed

13 files changed

+567
-115
lines changed

modules/swagger-maven-plugin/README.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,34 @@ to `swagger` [configuration property](https://github.com/swagger-api/swagger-cor
5656
</project>
5757
```
5858

59+
### Configuration example with provided Swagger configuration file
60+
61+
```
62+
<project>
63+
<build>
64+
<plugins>
65+
<plugin>
66+
<groupId>io.swagger.core.v3</groupId>
67+
<artifactId>swagger-maven-plugin</artifactId>
68+
<version>2.0.8-SNAPSHOT</version>
69+
<configuration>
70+
<outputFileName>openapi</outputFileName>
71+
<outputPath>${project.build.directory}/generatedtest</outputPath>
72+
<configurationFilePath>${project.basedir}/src/main/resources/configurationFile.yaml</configurationFilePath>
73+
</configuration>
74+
<executions>
75+
<execution>
76+
<phase>compile</phase>
77+
<goals>
78+
<goal>resolve</goal>
79+
</goals>
80+
</execution>
81+
</executions>
82+
</plugin>
83+
</plugins>
84+
</build>
85+
...
86+
```
5987

6088
#### Parameters
6189
Parameter | Description | Required | Default
@@ -69,6 +97,7 @@ Parameter | Description | Required | Default
6997
`resourceClasses`|see [configuration property](https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Integration-and-Configuration#configuration-properties)|false|
7098
`prettyPrint`|see [configuration property](https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Integration-and-Configuration#configuration-properties)|false|`TRUE`
7199
`openapiFilePath`|path to openapi file to be merged with resolved specification, see [config](https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Integration-and-Configuration#configuration-properties)|false|
100+
`configurationFilePath`|path to swagger config file to be merged with resolved specification, see [config](https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Integration-and-Configuration#configuration)|false|
72101
`filterClass`|see [configuration property](https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Integration-and-Configuration#configuration-properties)|false|
73102
`readerClass`|see [configuration property](https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Integration-and-Configuration#configuration-properties)|false|
74103
`scannerClass`|see [configuration property](https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Integration-and-Configuration#configuration-properties)|false|
@@ -80,3 +109,4 @@ Parameter | Description | Required | Default
80109

81110
***
82111

112+
Since version 2.0.8, configurationFilePath parameter is available, allowing to specify a path to a [swagger configuration file](https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Integration-and-Configuration#configuration); If single maven configuration parameters (e.g. `prettyPrint`) are also defined, these will overwrite any value set in configuration file; the same applies to `openapiFilePath` which takes precedence over `openAPI` field in configuration file.

modules/swagger-maven-plugin/src/main/java/io/swagger/v3/plugin/maven/SwaggerMojo.java

Lines changed: 209 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import org.apache.maven.plugins.annotations.Parameter;
1616
import org.apache.maven.plugins.annotations.ResolutionScope;
1717
import org.apache.maven.project.MavenProject;
18+
import org.codehaus.plexus.util.FileUtils;
1819
import org.codehaus.plexus.util.StringUtils;
1920

2021
import java.io.File;
@@ -23,9 +24,10 @@
2324
import java.nio.file.Files;
2425
import java.nio.file.Path;
2526
import java.nio.file.Paths;
26-
import java.util.Collection;
27-
import java.util.LinkedHashSet;
28-
import java.util.Set;
27+
import java.util.*;
28+
import java.util.function.BiFunction;
29+
30+
import static java.lang.String.format;
2931

3032
@Mojo(
3133
name = "resolve",
@@ -36,17 +38,17 @@
3638
)
3739
public class SwaggerMojo extends AbstractMojo {
3840

39-
public enum Format {JSON, YAML, JSONANDYAML};
41+
public enum Format {JSON, YAML, JSONANDYAML}
4042

41-
public void execute() throws MojoExecutionException, MojoFailureException
42-
{
43+
@Override
44+
public void execute() throws MojoExecutionException, MojoFailureException {
4345
if (skip) {
4446
getLog().info( "Skipping OpenAPI specification resolution" );
4547
return;
4648
}
4749
getLog().info( "Resolving OpenAPI specification.." );
4850

49-
if(project !=null) {
51+
if (project != null) {
5052
String pEnc = project.getProperties().getProperty("project.build.sourceEncoding");
5153
if (StringUtils.isNotBlank(pEnc)) {
5254
projectEncoding = pEnc;
@@ -56,43 +58,18 @@ public void execute() throws MojoExecutionException, MojoFailureException
5658
encoding = projectEncoding;
5759
}
5860

59-
OpenAPI openAPIInput = null;
60-
try {
61-
if (StringUtils.isNotBlank(openapiFilePath)) {
62-
Path openapiPath = Paths.get(openapiFilePath);
63-
if (openapiPath.toFile().exists() && openapiPath.toFile().isFile()) {
64-
String openapiFileContent = new String(Files.readAllBytes(openapiPath), encoding);
65-
if (StringUtils.isNotBlank(openapiFileContent)) {
66-
try {
67-
openAPIInput = Json.mapper().readValue(openapiFileContent, OpenAPI.class);
68-
} catch (Exception e) {
69-
try {
70-
openAPIInput = Yaml.mapper().readValue(openapiFileContent, OpenAPI.class);
71-
} catch (Exception e1) {
72-
getLog().error( "Error reading/deserializing openapi file" , e);
73-
throw new MojoFailureException(e.getMessage(), e);
74-
}
75-
}
76-
}
77-
}
78-
}
79-
} catch (Exception e) {
80-
getLog().error( "Error reading/deserializing openapi file" , e);
81-
throw new MojoFailureException(e.getMessage(), e);
82-
}
61+
// read swagger configuration if one was provided
62+
Optional<SwaggerConfiguration> swaggerConfiguration =
63+
readStructuredDataFromFile(configurationFilePath, SwaggerConfiguration.class, "configurationFilePath");
64+
65+
// read openApi config, if one was provided
66+
Optional<OpenAPI> openAPIInput =
67+
readStructuredDataFromFile(openapiFilePath, OpenAPI.class, "openapiFilePath");
68+
69+
config = mergeConfig(openAPIInput.orElse(null), swaggerConfiguration.orElse(new SwaggerConfiguration()));
70+
71+
setDefaultsIfMissing(config);
8372

84-
SwaggerConfiguration config = new SwaggerConfiguration()
85-
.filterClass(filterClass)
86-
.ignoredRoutes(ignoredRoutes)
87-
.prettyPrint(prettyPrint)
88-
.readAllResources(readAllResources)
89-
.readerClass(readerClass)
90-
.scannerClass(scannerClass)
91-
.resourceClasses(resourceClasses)
92-
.openAPI(openAPIInput)
93-
.resourcePackages(resourcePackages)
94-
.objectMapperProcessorClass(objectMapperProcessorClass)
95-
.modelConverterClasses(modelConverterClasses);
9673
try {
9774
GenericOpenApiContextBuilder builder = new JaxrsOpenApiContextBuilder()
9875
.openApiConfiguration(config);
@@ -147,6 +124,172 @@ public void execute() throws MojoExecutionException, MojoFailureException
147124
}
148125
}
149126

127+
private void setDefaultsIfMissing(SwaggerConfiguration config) {
128+
129+
if (prettyPrint == null) {
130+
prettyPrint = Boolean.FALSE;
131+
}
132+
if (readAllResources == null) {
133+
readAllResources = Boolean.TRUE;
134+
}
135+
if (config.isPrettyPrint() == null) {
136+
config.prettyPrint(prettyPrint);
137+
}
138+
if (config.isReadAllResources() == null) {
139+
config.readAllResources(readAllResources);
140+
}
141+
}
142+
143+
/**
144+
* Read the content of given file as either json or yaml and maps it to given class
145+
*
146+
* @param filePath to read content from
147+
* @param outputClass to map to
148+
* @param configName for logging, what user config will be read
149+
* @param <T> mapped type
150+
* @return empty optional if not path was given or the file was empty, read instance otherwis
151+
* @throws MojoFailureException if given path is not file, could not be read or is not proper json or yaml
152+
*/
153+
private <T> Optional<T> readStructuredDataFromFile(String filePath, Class<T> outputClass, String configName)
154+
throws MojoFailureException {
155+
try {
156+
// ignore if config is not provided
157+
if (StringUtils.isBlank(filePath)) {
158+
return Optional.empty();
159+
}
160+
161+
Path pathObj = Paths.get(filePath);
162+
163+
// if file does not exist or is not an actual file, finish with error
164+
if (!pathObj.toFile().exists() || !pathObj.toFile().isFile()) {
165+
throw new IllegalArgumentException(
166+
format("passed path does not exist or is not a file: '%s'", filePath));
167+
}
168+
169+
String fileContent = new String(Files.readAllBytes(pathObj), encoding);
170+
171+
// if provided file is empty, log warning and finish
172+
if (StringUtils.isBlank(fileContent)) {
173+
getLog().warn(format("It seems that file '%s' defined in config %s is empty",
174+
pathObj.toString(), configName));
175+
return Optional.empty();
176+
}
177+
178+
// get mappers in the order based on file extension
179+
List<BiFunction<String, Class<T>, T>> mappers = getSortedMappers(pathObj);
180+
181+
T instance = null;
182+
Throwable caughtEx = null;
183+
184+
// iterate through mappers and see if one is able to parse
185+
for (BiFunction<String, Class<T>, T> mapper : mappers) {
186+
try {
187+
instance = mapper.apply(fileContent, outputClass);
188+
break;
189+
} catch (Exception e) {
190+
caughtEx = e;
191+
}
192+
}
193+
194+
// if no mapper could read the content correctly, finish with error
195+
if (instance == null) {
196+
if (caughtEx == null) {
197+
caughtEx = new IllegalStateException("undefined state");
198+
}
199+
getLog().error(format("Could not read file '%s' for config %s", pathObj.toString(), configName), caughtEx);
200+
throw new IllegalStateException(caughtEx.getMessage(), caughtEx);
201+
}
202+
203+
return Optional.of(instance);
204+
} catch (Exception e) {
205+
getLog().error(format("Error reading/deserializing config %s file", configName), e);
206+
throw new MojoFailureException(e.getMessage(), e);
207+
}
208+
}
209+
210+
/**
211+
* Get sorted list of mappers based on given filename.
212+
* <p>
213+
* Will sort the 2 supported mappers: json and yaml based on what file extension is used.
214+
*
215+
* @param pathObj to get extension from.
216+
* @param <T> mapped type
217+
* @return list of mappers
218+
*/
219+
private <T> List<BiFunction<String, Class<T>, T>> getSortedMappers(Path pathObj) {
220+
String ext = FileUtils.extension(pathObj.toString());
221+
boolean yamlPreferred = false;
222+
if (ext.equalsIgnoreCase("yaml") || ext.equalsIgnoreCase("yml")) {
223+
yamlPreferred = true;
224+
}
225+
226+
List<BiFunction<String, Class<T>, T>> list = new ArrayList<>(2);
227+
228+
list.add((content, typeClass) -> {
229+
try {
230+
return Json.mapper().readValue(content, typeClass);
231+
} catch (IOException e) {
232+
throw new IllegalStateException(e);
233+
}
234+
});
235+
list.add((content, typeClass) -> {
236+
try {
237+
return Yaml.mapper().readValue(content, typeClass);
238+
} catch (IOException e) {
239+
throw new IllegalStateException(e);
240+
}
241+
});
242+
243+
if (yamlPreferred) {
244+
Collections.reverse(list);
245+
}
246+
247+
return Collections.unmodifiableList(list);
248+
}
249+
250+
private SwaggerConfiguration mergeConfig(OpenAPI openAPIInput, SwaggerConfiguration config) {
251+
// overwrite all settings provided by other maven config
252+
if (StringUtils.isNotBlank(filterClass)) {
253+
config.filterClass(filterClass);
254+
}
255+
if (isCollectionNotBlank(ignoredRoutes)) {
256+
config.ignoredRoutes(ignoredRoutes);
257+
}
258+
if (prettyPrint != null) {
259+
config.prettyPrint(prettyPrint);
260+
}
261+
if (readAllResources != null) {
262+
config.readAllResources(readAllResources);
263+
}
264+
if (StringUtils.isNotBlank(readerClass)) {
265+
config.readerClass(readerClass);
266+
}
267+
if (StringUtils.isNotBlank(scannerClass)) {
268+
config.scannerClass(scannerClass);
269+
}
270+
if (isCollectionNotBlank(resourceClasses)) {
271+
config.resourceClasses(resourceClasses);
272+
}
273+
if (openAPIInput != null) {
274+
config.openAPI(openAPIInput);
275+
}
276+
if (isCollectionNotBlank(resourcePackages)) {
277+
config.resourcePackages(resourcePackages);
278+
}
279+
if (StringUtils.isNotBlank(objectMapperProcessorClass)) {
280+
config.objectMapperProcessorClass(objectMapperProcessorClass);
281+
}
282+
if (isCollectionNotBlank(modelConverterClasses)) {
283+
config.modelConverterClasses(modelConverterClasses);
284+
}
285+
286+
return config;
287+
}
288+
289+
private boolean isCollectionNotBlank(Collection<?> collection) {
290+
return collection != null && !collection.isEmpty();
291+
}
292+
150293
@Parameter( property = "resolve.outputFileName", defaultValue = "openapi")
151294
private String outputFileName = "openapi";
152295

@@ -173,10 +316,10 @@ public void execute() throws MojoExecutionException, MojoFailureException
173316
*/
174317
@Parameter( property = "resolve.objectMapperProcessorClass" )
175318
private String objectMapperProcessorClass;
176-
@Parameter( property = "resolve.prettyPrint" )
177-
private Boolean prettyPrint = false;
178-
@Parameter( property = "resolve.readAllResources" )
179-
private Boolean readAllResources = Boolean.TRUE;
319+
@Parameter(property = "resolve.prettyPrint")
320+
private Boolean prettyPrint;
321+
@Parameter(property = "resolve.readAllResources")
322+
private Boolean readAllResources;
180323
@Parameter( property = "resolve.ignoredRoutes" )
181324
private Collection<String> ignoredRoutes;
182325
/**
@@ -191,19 +334,38 @@ public void execute() throws MojoExecutionException, MojoFailureException
191334
@Parameter( property = "resolve.openapiFilePath")
192335
private String openapiFilePath;
193336

337+
/**
338+
* @since 2.0.8
339+
*/
340+
@Parameter(property = "resolve.configurationFilePath")
341+
private String configurationFilePath;
342+
194343
@Parameter(defaultValue = "${project}", readonly = true)
195344
private MavenProject project;
196345

197346
@Parameter( property = "resolve.encoding" )
198347
private String encoding;
199348

200349
private String projectEncoding = "UTF-8";
350+
private SwaggerConfiguration config;
201351

202352
public String getOutputPath() {
203353
return outputPath;
204354
}
355+
205356
public String getOpenapiFilePath() {
206357
return openapiFilePath;
207358
}
208359

360+
String getConfigurationFilePath() {
361+
return configurationFilePath;
362+
}
363+
364+
void setContextId(String contextId) {
365+
this.contextId = contextId;
366+
}
367+
368+
SwaggerConfiguration getInternalConfiguration() {
369+
return config;
370+
}
209371
}

0 commit comments

Comments
 (0)