I using zuul with many swagger resources (I have different links to specifics api-docs) so my question is how can I configure ng-swagger-gen config to generate all classes from many resources?
This is my ng-swagger-gen config:
{
"$schema": "./node_modules/ng-swagger-gen/ng-swagger-gen-schema.json",
"swagger": "http://localhost:8080/v2/api-docs",
"output": "src/api/",
"apiModule": true
}
And this is my swagger confing in zuul application
#Primary
#Configuration
public class SwaggerConfig implements SwaggerResourcesProvider {
#Override
public List<SwaggerResource> get() {
List<SwaggerResource> resources = new ArrayList<>();
resources.add(swaggerResource("USER-SERVICE", "/api/user/v2/api-docs"));
resources.add(swaggerResource("STORY-SERVICE", "/api/story/v2/api-docs"));
return resources;
}
private SwaggerResource swaggerResource(String name, String location) {
SwaggerResource swaggerResource = new SwaggerResource();
swaggerResource.setName(name);
swaggerResource.setLocation(location);
swaggerResource.setSwaggerVersion("2.0");
return swaggerResource;
}
}
Related
Using swagger-core v3 2.1.6 these three configurations produce same openapi.json result.
No configuration:
#ApplicationPath("/rest")
public class AuthRestConfig extends Application {
public AuthRestConfig() {
}
}
Server Configuration, I need to use this kind of conf:
#ApplicationPath("/rest")
public class AuthRestConfig extends Application {
public AuthRestConfig(#Context ServletConfig servletConfig) {
super();
OpenAPI oas = new OpenAPI();
Info info = new Info()
.title("Suma Automaton OpenAPI")
.description("This is a sample server Petstore server. You can find out more about Swagger "
+ "at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). For this sample, "
+ "you can use the api key `special-key` to test the authorization filters.")
.termsOfService("http://swagger.io/terms/")
.contact(new Contact()
.email("baldodavi#gmail.com"));
oas.info(info);
String url = "/suma-automaton-ms";
List<Server> servers = new ArrayList<>();
Server server = new Server();
server.setUrl(url);
servers.add(server);
oas.setServers(servers);
SwaggerConfiguration oasConfig = new SwaggerConfiguration()
.openAPI(oas)
.prettyPrint(true)
.resourcePackages(Stream.of("io.swagger.sample.resource").collect(Collectors.toSet()));
try {
new JaxrsOpenApiContextBuilder<>()
.servletConfig(servletConfig)
.application(this)
.openApiConfiguration(oasConfig)
.buildContext(true);
} catch (OpenApiConfigurationException e) {
throw new RuntimeException(e.getMessage(), e);
}
}
}
Any suggestion about this behavior? Is there something I misunderstood? Consider I've the same behaviour also using standard configuration reported in https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Integration-and-configuration#jax-rs-application
Add this to your AuthRestConfig class:
import io.swagger.v3.jaxrs2.integration.resources.OpenApiResource;
#Override
public Set<Class<?>> getClasses() {
Set<Class<?>> resources = new HashSet<>();
resources.add(OpenApiResource.class);
return resources;
}
I've recently converted from Springfox to Springdoc-openapi for generating my OpenAPI for my Spring Boot Rest API service.
Everything was working perfectly until I added a security scheme. Once I did that, my schemes no longer appear and an error appears on the SwaggerUI page:
Could not resolve reference: Could not resolve pointer: /components/schemas/Ping does not exist in document
I am setting up my configuration programmatically, and have 2 groups.
I'm using Spring Boot v2.4.0 with springdoc-openapi-ui v1.5.1
Snippet of my pom.xml:
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
<version>1.5.1</version>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-hateoas</artifactId>
<version>1.5.1</version>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-security</artifactId>
<version>1.5.1</version>
</dependency>
Snippet from configuration:
#Bean
public GroupedOpenApi apiV1() {
String[] paths = {"/v1/**"};
String[] packagesToScan = {"com.test.controller"};
return GroupedOpenApi.builder()
.group("v1")
.packagesToScan(packagesToScan)
.pathsToMatch(paths)
.addOpenApiCustomiser(buildV1OpenAPI())
.build();
}
#Bean
public GroupedOpenApi apiV2() {
String[] paths = {"/v2/**"};
String[] packagesToScan = {"com.test.controller"};
return GroupedOpenApi.builder()
.group("v2")
.packagesToScan(packagesToScan)
.pathsToMatch(paths)
.addOpenApiCustomiser(buildV2OpenAPI())
.build();
}
public OpenApiCustomiser buildV1OpenAPI() {
return openApi -> openApi.info(apiInfo().version("v1"));
}
public OpenApiCustomiser buildV2OpenAPI() {
final String securitySchemeName = "Access Token";
return openApi -> openApi.info(apiInfo().version("v2"))
.addSecurityItem(new SecurityRequirement().addList(securitySchemeName))
.components(new Components().addSecuritySchemes(securitySchemeName, new SecurityScheme()
.type(SecurityScheme.Type.APIKEY)
.in(SecurityScheme.In.HEADER)
.name(HttpHeaders.AUTHORIZATION)));
}
// Describe the apis
private Info apiInfo() {
return new Info()
.title("Title")
.description("API Description");
}
For my v1 group, everything works fine. My Schemas appear on the Swagger UI page and I see them in the components section of the generated api-doc.
"components": {
"schemas": {
"ApplicationErrorResponse": {
...
}
},
"Ping": {
...
}
}
}
For my v2 group, the Schemas are not generated.
"components": {
"securitySchemes": {
"Access Token": {
"type": "apiKey",
"name": "Authorization",
"in": "header"
}
}
}
Any idea why my Schemas are not automatically scanned and added when adding the security scheme to the OpenAPI components programmatically? Am I missing something in my config?
Here's my request mapping in my controller.
#Operation(summary = "Verify API and backend connectivity",
description = "Confirm connectivity to the backend, as well and verify API service is running.")
#OkResponse
#GetMapping(value = API_VERSION_2 + "/ping", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Ping> getPingV2(HttpServletRequest request) {
...
}
And here's my #OkResponse annotation:
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.TYPE, ElementType.METHOD})
#Documented
#ApiResponse(responseCode = HTTP_200,
description = HTTP_200_OK,
headers = {
#Header(name = CONTENT_VERSION_HEADER, description = CONTENT_VERSION_HEADER_DESCRIPTION, schema = #Schema(type = "string")),
#Header(name = DEPRECATION_MESSAGE_HEADER, description = DEPRECATION_MESSAGE_HEADER_DESCRIPTION, schema = #Schema(type = "string")),
#Header(name = DESCRIPTION_HEADER, description = DESCRIPTION_HEADER_DESCRIPTION, schema = #Schema(type = "string"))
})
public #interface OkResponse {
}
My v1 mappings are defined similarly.
So, it would seem that when solely relying on OpenApiCustomiser for creating the OpenAPI, the scanned components are ignored, or at least overwritten with just the components specified in the customizer (I could have also programmatically added all of my schemas, but this would have been very cumbersome to maintain).
Changing my config to the following resolved my issue:
#Bean
public OpenAPI customOpenAPI() {
final String securitySchemeName = "Access Token";
return new OpenAPI()
.addSecurityItem(new SecurityRequirement().addList(securitySchemeName))
.components(new Components().addSecuritySchemes(securitySchemeName, new SecurityScheme()
.type(SecurityScheme.Type.APIKEY)
.in(SecurityScheme.In.HEADER)
.name(HttpHeaders.AUTHORIZATION)))
.info(apiInfo());
}
#Bean
public GroupedOpenApi apiV1() {
String[] paths = {"/v1/**"};
String[] packagesToScan = {"com.test.controller"};
return GroupedOpenApi.builder()
.group("v1")
.packagesToScan(packagesToScan)
.pathsToMatch(paths)
.addOpenApiCustomiser(buildV1OpenAPI())
.build();
}
#Bean
public GroupedOpenApi apiV2() {
String[] paths = {"/v2/**"};
String[] packagesToScan = {"com.test.controller"};
return GroupedOpenApi.builder()
.group("v2")
.packagesToScan(packagesToScan)
.pathsToMatch(paths)
.addOpenApiCustomiser(buildV2OpenAPI())
.build();
}
public OpenApiCustomiser buildV1OpenAPI() {
return openApi -> openApi.info(openApi.getInfo().version("v1"));
}
public OpenApiCustomiser buildV2OpenAPI() {
return openApi -> openApi.info(openApi.getInfo().version("v2"));
}
// Describe the apis
private Info apiInfo() {
return new Info()
.title("Title")
.description("API Description.");
}
While this technically does also add the Authorize button and security scheme to the v1 group, it can be ignored because those API endpoints are not secured anyway (internal API and they should be going away soon anyway).
Probably a better solution anyway as the Info is basically identical between the groups.
Instead of creating new Components you should just modify them:
public OpenApiCustomiser buildV2OpenAPI() {
final String securitySchemeName = "Access Token";
return openApi -> {
openApi.info(apiInfo().version("v2"))
.addSecurityItem(new SecurityRequirement().addList(securitySchemeName));
openApi.getComponents().addSecuritySchemes(securitySchemeName, new SecurityScheme()
.type(SecurityScheme.Type.APIKEY)
.in(SecurityScheme.In.HEADER)
.name(HttpHeaders.AUTHORIZATION));
return openApi;
};
}
How do I modify the document root of the built-in tomcat instead of using the "src/main/webapp"
Set the path to your document root as server.tomcat.document-root in application.properties
#Value("${server.tomcat.document-root}")
private String documentRoot;
#Bean
public WebServerFactoryCustomizer<ConfigurableWebServerFactory> webServerFactoryCustomizer() {
return new WebServerFactoryCustomizer<ConfigurableWebServerFactory>() {
#Override
public void customize(ConfigurableWebServerFactory factory) {
if (factory instanceof TomcatServletWebServerFactory) {
TomcatServletWebServerFactory tomcat = (TomcatServletWebServerFactory) factory;
if (!StringUtils.isEmpty(documentRoot)) {
File root = new File(documentRoot);
tomcat.setDocumentRoot(root);
}
}
}
};
}
In your application.properties, add a property:
server.servlet.contextPath=/yourpathgoeshere
I am trying to override Ribbon Server List to get a list of host names from consul. I have the consul piece working properly(when testing with hardcode values) to get the hostname and port for a service. The issue I am having is when I try to autowire in IClientConfig. I get an exception that IClientConfig bean could not be found. How do I override the ribbon configurations and autowire IClientConfig in the ribbonServerList method.
I have tried following the instructions here at http://projects.spring.io/spring-cloud/spring-cloud.html#_customizing_the_ribbon_client on how to customize ribbon client configuration. I keep getting the following error:
Description:
Parameter 0 of method ribbonServerList in com.intradiem.enterprise.keycloak.config.ConsulRibbonSSLConfig required a bean of type 'com.netflix.client.config.IClientConfig' that could not be found.
Which is causing spring-boot to fail.
Bellow are the classes that I am trying to use to create
AutoConfiguration Class:
#Configuration
#EnableConfigurationProperties
#ConditionalOnBean(SpringClientFactory.class)
#ConditionalOnProperty(value = "spring.cloud.com.intradiem.service.apirouter.consul.ribbon.enabled", matchIfMissing = true)
#AutoConfigureAfter(RibbonAutoConfiguration.class)
#RibbonClients(defaultConfiguration = ConsulRibbonSSLConfig.class)
//#RibbonClient(name = "question-answer-provider", configuration = ConsulRibbonSSLConfig.class)
public class ConsulRibbonSSLAutoConfig
{
}
Configuration Class:
#Component
public class ConsulRibbonSSLConfig
{
#Autowired
private ConsulClient client;
private String serviceId = "client";
public ConsulRibbonSSLConfig() {
}
public ConsulRibbonSSLConfig(String serviceId) {
this.serviceId = serviceId;
}
#Bean
#ConditionalOnMissingBean
public ServerList<?> ribbonServerList(IClientConfig clientConfig) {
ConsulSSLServerList serverList = new ConsulSSLServerList(client);
serverList.initWithNiwsConfig(clientConfig);
return serverList;
}
}
ServerList Code:
public class ConsulSSLServerList extends AbstractServerList<Server>
{
private final ConsulClient client;
private String serviceId = "client";
public ConsulSSLServerList(ConsulClient client) {
this.client = client;
}
#Override
public void initWithNiwsConfig(IClientConfig clientConfig) {
this.serviceId = clientConfig.getClientName();
}
#Override
public List<Server> getInitialListOfServers() {
return getServers();
}
#Override
public List<Server> getUpdatedListOfServers() {
return getServers();
}
private List<Server> getServers() {
List<Server> servers = new ArrayList<>();
Response<QueryExecution> results = client.executePreparedQuery(serviceId, QueryParams.DEFAULT);
List<QueryNode> nodes = results.getValue().getNodes();
for (QueryNode queryNode : nodes) {
QueryNode.Node node = queryNode.getNode();
servers.add(new Server(node.getMeta().containsKey("secure") ? "https" : "http", node.getNode(), queryNode.getService().getPort()));
}
return servers;
}
#Override
public String toString() {
final StringBuilder sb = new StringBuilder("ConsulSSLServerList{");
sb.append("serviceId='").append(serviceId).append('\'');
sb.append('}');
return sb.toString();
}
}
I was reading through the Spring Integration Documentation thinking that a file download would be pretty simple to implement. Instead, the article provided me with many different components that seem to over-qualify my needs:
The FTP Inbound Channel Adapter is a special listener that will connect to the FTP server and will listen for the remote directory events (e.g., new file created) at which point it will initiate a file transfer.
The streaming inbound channel adapter produces message with payloads of type InputStream, allowing files to be fetched without writing to the local file system.
Let's say I have a SessionFactory declared as follows:
#Bean
public SessionFactory<FTPFile> ftpSessionFactory() {
DefaultFtpSessionFactory sf = new DefaultFtpSessionFactory();
sf.setHost("localhost");
sf.setPort(20);
sf.setUsername("foo");
sf.setPassword("foo");
return new CachingSessionFactory<>(sf);
}
How do I go from here to downloading a single file on a given URL?
You can use an FtpRemoteFileTemplate...
#SpringBootApplication
public class So44194256Application implements CommandLineRunner {
public static void main(String[] args) {
SpringApplication.run(So44194256Application.class, args);
}
#Bean
public DefaultFtpSessionFactory ftpSessionFactory() {
DefaultFtpSessionFactory sf = new DefaultFtpSessionFactory();
sf.setHost("10.0.0.3");
sf.setUsername("ftptest");
sf.setPassword("ftptest");
return sf;
}
#Bean
public FtpRemoteFileTemplate template(DefaultFtpSessionFactory sf) {
return new FtpRemoteFileTemplate(sf);
}
#Autowired
private FtpRemoteFileTemplate template;
#Override
public void run(String... args) throws Exception {
template.get("foo/bar.txt",
inputStream -> FileCopyUtils.copy(inputStream,
new FileOutputStream(new File("/tmp/bar.txt"))));
}
}
To add to #garyrussell's answer:
In FTPS protocol, if you are behind a firewall, you will might encounter
Host attempting data connection x.x.x.x is not the same as server y.y.y.y error (as described here). The reason is the FtpSession instance returned from DefaultFtpsSessionFactory by default does remote verification test, i.e. it runs in an "active" mode.
The solution is to disable this verification on the FtpSession instance by setting the "passive mode", when you create the DefaultFtpsSessionFactory.
DefaultFtpsSessionFactory defaultFtpsSessionFactory() {
DefaultFtpsSessionFactory defaultFtpSessionFactory = new DefaultFtpsSessionFactory(){
#Override
public FtpSession getSession() {
FtpSession ftpSession = super.getSession();
ftpSession.getClientInstance().setRemoteVerificationEnabled(false);
return ftpSession;
}
};
defaultFtpSessionFactory.setHost("host");
defaultFtpSessionFactory.setPort(xx);
defaultFtpSessionFactory.setUsername("username");
defaultFtpSessionFactory.setPassword("password");
defaultFtpSessionFactory.setFileType(2); //binary data transfer
return defaultFtpSessionFactory;
}
following code block might be helpful
#Bean
public SessionFactory<LsEntry> sftpSessionFactory() {
DefaultSftpSessionFactory factory = new DefaultSftpSessionFactory(true) {
{
setHost("localhost");
setPort(20);
setUser("foo");
setPassword("foo");
setAllowUnknownKeys(true);
}
};
return new CachingSessionFactory<LsEntry>(factory);
}
#Bean
public SftpInboundFileSynchronizer sftpInboundFileSynchronizer() {
SftpInboundFileSynchronizer fileSynchronizer = new SftpInboundFileSynchronizer(sftpSessionFactory()) {
{
setDeleteRemoteFiles(true);
setRemoteDirectory("/remote");
setFilter(new SftpSimplePatternFileListFilter("*.txt"));
}
};
return fileSynchronizer;
}
#Bean
#InboundChannelAdapter(channel = "sftpChannel", poller = #Poller(fixedDelay = "600"))
public MessageSource<File> sftpMessageSource() {
SftpInboundFileSynchronizingMessageSource messageSource = new SftpInboundFileSynchronizingMessageSource(
sftpInboundFileSynchronizer()) {
{
setLocalDirectory(new File("/temp"));
setAutoCreateLocalDirectory(true);
setLocalFilter(new AcceptOnceFileListFilter<File>());
}
};
return messageSource;
}
obtained from https://github.com/edtoktay/spring-integraton