Swagger2Markup : how to group by tags when using Swagger remote endpoint from test? - maven

I'm using the great swagger2markup plugin to generate Asciidoc documentation for my REST API as provided by Swagger. I have followed swagger2markup documentation and I am using a Spring MVC integration test to generate markup from my Springfox Swagger endpoint like this (I'm using Maven) :
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest(classes = { AppConfig.class, SwaggerConfig.class })
public class DocumentationIT {
protected MockMvc mockMvc;
#Autowired
protected WebApplicationContext webApplicationContext;
#Rule
public JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation("src/docs/asciidoc/apidoc/generated-snippets");
#Before
public void setUp(){
this.mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext)
.apply(documentationConfiguration(this.restDocumentation))
.build();
}
#Test
public void convertSwaggerToAsciiDoc() throws Exception {
this.mockMvc.perform(get("/v2/api-docs")
.accept(MediaType.APPLICATION_JSON))
.andDo(
Swagger2MarkupResultHandler
.outputDirectory("src/docs/asciidoc/apidoc")
.withExamples("src/docs/asciidoc/apidoc/generated-snippets").build())
.andExpect(status().isOk());
}
}
Everything is working great and all my paths are in my final documentation, however paths all appear directly at the root and are not grouped by resources (i.e. by controllers), so Method 1 from Controller 1 will appear at the same level as Method 2 from Controller 2.
My output :
What I'd like :
From what I can see, when using generation from a local file like in this swagger2-markup Maven project template you can specify a property to tell swagger2markup to group your paths by tags using the config property <swagger2markup.pathsGroupedBy>TAGS</swagger2markup.pathsGroupedBy>, however there does not seem to be such configuration when using Swagger2MarkupResultHandler from a test. The only option is withMarkupLanguage() but there is no withPathsGroupedBy() method...
Am i missing something here ?

As you mentioned, there is a property of swagger2markup.pathsGroupedBy provided by swagger2Markup to specify how the paths should be grouped. However, Swagger2MarkupResultHandler do not provide API to support the configuration.
According to Swagger2Markup API,
The properties of Swagger2Markup are defined in the class
io.github.swagger2markup.Swagger2MarkupProperties. The properties are
considered in the following order:
Java System properties
Custom properties
Default properties (included in Swagger2Markup)
it is possible to configure it by using system properties. Therefore, you can set a system property of swagger2markup.pathsGroupedBy to TAGS in the test.
If you prefer to configure it with Java API, you may extend Swagger2MarkupResultHandler and override the handle method with the use of Swagger2MarkupConfigBuilder API.

Related

how to use org.springframework.format.Formatter.print()

#Configuration
public class MyWebMvcConfigurationSupport extends WebMvcConfigurationSupport {
#Override
public FormattingConversionService mvcConversionService() {
FormattingConversionService f = super.mvcConversionService();
f.addFormatter(new DateFormatter("yyyy-MM-dd"));
return f;
}
}
#RestController
public class TestController {
#GetMapping
public Date test(Date date) {
return date;
}
}
When we access http://localhost:8080?date=2021-09-04, the argument type is converted through the DateFormatter's parse method, which relies on the SpringMVC framework to do the conversion. I wonder if the print method can also be invoked through the framework to return a string.
Do we need to manually invoke the print method, for example
#RestController
public class TestController {
#Resource
private FormattingConversionService conversionService;
#GetMapping
public String test(Date date) {
return conversionService.convert(date, String.class);
}
}
Inside the controller
You could use a class extending java.text.Format like SimpleDateFormatin your controller:
#RestController
public class TestController {
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
#GetMapping
public String test(Date date) {
return dateFormat.format(date);
}
}
At application level
Use DateTimeFormatterRegistrar to register your formats, like described in this tutorial.
Then you can register this set of formatters at Spring's FormattingConversionService.
Using Jackson
However if you would like to work with JSON or XML you should consider using FasterXML's Jackson. See similar question:
Spring 3.2 Date time format
This is the interface representing the environment in which the current application is running. It models two key aspects of the application environment: profiles and properties. The methods related to property access are exposed via the PropertyResolver superinterface.
A profile is a named, logical group of bean definitions to be registered with the container only if the given profile is active. Beans may be assigned to a profile whether defined in XML or via annotations; see the spring-beans 3.1 schema or the #Profile annotation for syntax details. The role of the Environment object with relation to profiles is in determining which profiles (if any) are currently active, and which profiles (if any) should be active by default.
Properties play an important role in almost all applications, and may originate from a variety of sources: properties files, JVM system properties, system environment variables, JNDI, servlet context parameters, ad-hoc Properties objects, Maps, and so on. The role of the environment object with relation to properties is to provide the user with a convenient service interface for configuring property sources and resolving properties from them.
Beans managed within an ApplicationContext may register to be EnvironmentAware or #Inject the Environment in order to query profile state or resolve properties directly.
In most cases, however, application-level beans should not need to interact with the Environment directly but instead may have to have ${...} property values replaced by a property placeholder configurer such as PropertySourcesPlaceholderConfigurer, which itself is EnvironmentAware and as of Spring 3.1 is registered by default when using context:property-placeholder/.
Configuration of the environment object must be done through the ConfigurableEnvironment interface, returned from all AbstractApplicationContext subclass getEnvironment() methods. See ConfigurableEnvironment Javadoc for usage examples demonstrating manipulation of property sources prior to application context refresh().

Load custom properties file in Spring Boot MVC Main

I have created a myApp.properties in resources folder location and mentioned the server.port in this file.
myApp.properties
myApp.server.port=8020
Now I want to read load this property into my application. But I have to read this before I actually a server.
Here I am trying to do like this
#SpringBootApplication
#ComponentScan(basePackages = {"com.myorg.myapp" })
#EnableConfigurationProperties
#PropertySource("classpath:myApp.properties")
#Component
public class MyAppApplication {
#Value("${myApp.server.port}")
private static String serverPort;
public static void main(String[] args) throws Exception{
try {
SpringApplication appCtxt = new SpringApplication(MyAppApplication.class);
appCtxt.setDefaultProperties(Collections
.singletonMap("server.port", serverPort));
appCtxt.run(args);
} catch (Exception e) {
e.printStackTrace();
}
}
But serverPort is coming as null.
I also tried to create a separate Config file like this but it can't be accessed in static main
#Configuration
#PropertySource("myApp.properties")
#ConfigurationProperties
public class MyAppConfig {
#Value("${myApp.server.port}")
private String serverPort;
/**
* #return the serverPort
*/
public String getServerPort() {
return serverPort;
}
}
Any suggestion would be helpful.
Spring boot injects properties during the initialization of the application context.
This happens (gets triggered) in the line:
appCtxt.run(args);
But you try to access the property before this line - that why it doesn't work.
So bottom line, using "#Value" in the main method doesn't work and it shouldn't.
Now from the code snippet, it looks like you could merely follow the "standards" of spring boot and create the file application.properties with:
server.port=1234
The process of starting the embedded web server in spring boot honors this property and bottom line it will have the same effect and Tomcat will be started on port 1234
Update 1
Based on OP's comment:
So, how can I have multiple application.properties.
In the Spring Boot's documentation it is written that application.properties are resolved from the classpath. So you can try the following assuming you have different modules A,B,C and web app D:
Create src/main/resources/application.properties inside each of 4 modules and pack everything together. The configuration values will be merged (hopefully they won't clash)
If you insist on naming properties A.properties, B.properties and C.properties for each of non-web modules, you can do the following (I'll show for module A, but B and C can do the same).
#Configuration
#PropertySource("classpath:A.properties")
public class AConfiguration {
}
Create in Module A: src/main/resources/A.properties
If you need to load the AConfiguration automatically - make the module A starter (using autoconfig feature of spring-boot):
Create src/resources/META-INF/spring.factories file with the following content:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
<package_of_AConfiguration>.AConfiguration
Also this has been the requirement to separate C from entire bundle where it might run as bundle for some and as a separate for some others
Although I haven't totally understood the requirement, but you can use #ConditionalOnProperty for configuration CConfiguration (that will be created just like AConfiguration.java in my previous example) but this times for module C.
If the conditional is met, configuration will run and load some beans / load its own properties or whatever. All in all conditionals (and in particular Profiles in spring) can help to reach the desired flexibility.
By default, the application.properties file can be used to store property pairs, though you can also define any number of additional property files.
If you save myApp.server.port=8020 in application.properties, it will work fine.
To register a custome property file, you can annotate a #Configuration class with the additional #PropertySource annotation:
#Configuration
#PropertySource("classpath:custom.properties")
#PropertySource("classpath:another.properties")
public class ConfigClass {
// Configuration
}
make sure, your class path is correct.

Loading String[] from properties file into origins field of #CrossOrigin using property placeholder expression

In my spring boot application I have the following controller
#RestController(value = "ProjectController")
#CrossOrigin(origins = {"${app.api.settings.cross-origin.urls}"})
public class ProjectController {
// some request mapping methods
}
The property app.api.settings.cross-origin.urls is already a key having comma separated valid urls in application.properties file like
app.api.settings.cross-origin.urls=http://localhost:3000, http://localhost:7070
This approach works till I have only single value like
app.api.settings.cross-origin.urls=http://localhost:3000 but not for comma separated values.
The origins field inside #CrossOrigin is of type String[] still it does not convert into String[] automatically.
I mean there should be some way to achieve this provided by the framework. Not a work around.
I can achieve using comma separated urls from properties files using #Value into a List<String> or String[] as a field inside a #Configuration class like below
#Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
#Value("${app.api.settings.cross-origin.urls}")
private String[] consumerUiOrigins;
#Override
public void addCorsMappings(CorsRegistry registry) {
registry
.addMapping("/api/**")
.allowedOrigins(consumerUiOrigins);
}
}
But this would be a global configuration having application wide applicability. I want to stick to the more fine grained #CrossOrigin annoation based CORS configuration.
So I put my question clearly below.
Is it possible to inject comma separated value from properties file as String[] using property plcaholer expression (${*}) into spring annotation fields having the same type i.e. String[] ????? If yes then how??? If no then can we tweak some core framework classes to achieve this??? Anyone please help....
P.S. - Please do not mark my question as duplicate of Use Spring Properties in Java with CrossOrigin Annotation or in Spring-Config XML
My question is more on usage of property placholder expressions inside spring annotation fields having multi element type like String[] and less on the configuration of CORS in spring applications.
Try doing as below in application.properties:
app.api.settings.cross-origin.urls="http://localhost:3000","http://localhost:7070"

Configuring Spring MockMvc to use custom argument resolver before built-in ones

I have a straightforward test case. I have a controller which has a parameter of a type Spring doesn't support by default, so I wrote a custom resolver.
I create the mock mvc instance I'm using like so:
mvc = MockMvcBuilders.standaloneSetup(controller).setCustomArgumentResolvers(new GoogleOAuthUserResolver()).build();
However, Spring is also registering almost 30 other argument resolvers, one of which is general enough that it is getting used to resolve the argument before mine. How can I set or sort the resolvers so that mine is invoked first?
This worked for me without reflection:
#RequiredArgsConstructor
#Configuration
public class CustomerNumberArgumentResolverRegistration {
private final RequestMappingHandlerAdapter requestMappingHandlerAdapter;
#PostConstruct
public void prioritizeCustomArgumentResolver () {
final List<HandlerMethodArgumentResolver> argumentResolvers = new ArrayList<>(Objects.requireNonNull(requestMappingHandlerAdapter.getArgumentResolvers()));
argumentResolvers.add(0, new CustomerNumberArgumentResolver());
requestMappingHandlerAdapter.setArgumentResolvers(argumentResolvers);
}
}
The issue was that the People class the Google OAuth library I am using extends Map and the mock servlet API provides no way to manipulate the order in which the handlers are registered.
I ended up using reflection to reach into the mocks guts and remove the offending handler.

metrics for my api powered by jersey

I try to instrumente my Jersey webservice with Metrics
http://metrics.codahale.com/manual/jersey/
I don't understand how to use this library?
Do I need to add something in my web.xml file?
Thanks
To instrument your Jersey web service, you must add the metrics-jersey module to your application, it contains a #Provider implementation class (make sure Jersey find it) that allow you to instrument your Jersey resources methods annotated with #Timed, Metered and ExceptionMetered.
By default, Metrics reports through JMX, so you can use JConsole to validate your instrumentations.
Like Alex wrote, there are others reporting options but it requires additional configuration or code (call enable method on the Reporter).
For example you can fetch reports in JSON by HTTP, or have you webservice send reports to a monitoring server such as Graphite.
As I can see, you just need to include metrics lib to the build path. On web-services methods you just use annotation #Timed.
To see the reports, you must enable the reporting style you like - reporters
Drop your linen and start your grin'n. I got this working!
Hook up the servlet. You need a generic spot to make and store the metrics. Build one of these for both MetricsRegistry and HealthCheckRegistry :
public class MetricsServletContextListener extends MetricsServlet.ContextListener {
public static final MetricRegistry METRIC_REGISTRY = new MetricRegistry();
#Override
protected MetricRegistry getMetricRegistry() {
return METRIC_REGISTRY;
}
}
Set the servlet context with the data in some startup area:
sc.getServletContext().setAttribute(
"com.codahale.metrics.servlets.HealthCheckServlet.registry",
healthChecks
);
sc.getServletContext().setAttribute(
"com.codahale.metrics.servlets.MetricsServlet.registry",
MetricsServletContextListener.METRIC_REGISTRY
);
Url is: http://blah/blah/metrics/metrics?pretty=true
Create one of these guys. This hooks up the metrics to Jersey:
#Provider
public class TmaticInstrumentedResourceMethodDispatchAdapterWrapper implements ResourceMethodDispatchAdapter {
private InstrumentedResourceMethodDispatchAdapter adapter = null;
public TmaticInstrumentedResourceMethodDispatchAdapterWrapper() {
adapter = new InstrumentedResourceMethodDispatchAdapter(MetricsServletContextListener.METRIC_REGISTRY);
}
#Override
public ResourceMethodDispatchProvider adapt(ResourceMethodDispatchProvider provider) {
return adapter.adapt(provider);
}
}
Tell jersey about it. Since it uses the #Provider annotation it must be in an area that can scan for it. I had to add mine to the web.xml here but you might not have to:
<init-param>
<param-name>com.sun.jersey.config.property.packages</param-name>
<param-value>blah.endpoint,blah.utils</param-value>
</init-param>
And add the annotatation #Timed to your jersey endpoint.

Resources