Drilling down model objects on Swagger - rest

My application is a restful api and its integrated with Swagger and OpenAPI.
I have generated all Java stubs using OpenAPI YAML file and everything is working fine.
But when i try o drill down model objects on Swagger then it cannot locate some of objects although there are part of project as project compiles fine.
As shown in below screenshot, drilldown fails to locate COnfiguration object.
Any ideas on how to resolve this.
Edit:
I have a restful webservice and i generate all the java stubs [Data transfer objects] from a YAML file using openapi-generator plugin. This plugin automatically generates a class OpenAPIDocumentationConfig and following are the details of the class. After this setup, models are automatically generated in Swagger UI.
Also want to add that I am using OpenAPI 3.0 but i need to split Object definitions into multiple files. So i am referring to them using definitions as i don't believe component schemas can be split into multiple files.
#Configuration
#EnableSwagger2
public class OpenAPIDocumentationConfig {
ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("ABC Service")
.description("ABC Service")
.license("")
.licenseUrl("http://unlicense.org")
.termsOfServiceUrl("")
.version("1.0.0")
.contact(new Contact("","", "xyz#abc.com"))
.build();
}
#Bean
public Docket customImplementation(ServletContext servletContext, #Value("${openapi.studioVALService.base-path:}") String basePath) {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.basePackage("com.x.y.z"))
.build()
.pathProvider(new BasePathAwareRelativePathProvider(servletContext, basePath))
.directModelSubstitute(java.time.LocalDate.class, java.sql.Date.class)
.directModelSubstitute(java.time.OffsetDateTime.class, java.util.Date.class)
.apiInfo(apiInfo());
}
class BasePathAwareRelativePathProvider extends RelativePathProvider {
private String basePath;
public BasePathAwareRelativePathProvider(ServletContext servletContext, String basePath) {
super(servletContext);
this.basePath = basePath;
}
#Override
protected String applicationPath() {
return Paths.removeAdjacentForwardSlashes(UriComponentsBuilder.fromPath(super.applicationPath()).path(basePath).build().toString());
}
#Override
public String getOperationPath(String operationPath) {
UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromPath("/");
return Paths.removeAdjacentForwardSlashes(
uriComponentsBuilder.path(operationPath.replaceFirst("^" + basePath, "")).build().toString());
}
}
}
EDIT 2:
I moved all definitions to components and schemas but they are still split in multiple files and are referring to components across files but still i get the same error.

If you are using OpenAPI 3 you should put schemas that you want to reuse inside components. To refeer to it you must use refs like:
$ref: "#/components/schemas/EquityOptionConfigurationDO"

Related

Is there any way to force spring not to use/create '_class' field in the mapping?

The thing is on production servers we got mapping for Elasticsearch with dynamic set to strict. Currently, we use a rest level client to communicate with Elastisearch, however, we would like to migrate to spring-data-elasticsearch.
Unfortunately, it seems spring data force to use either _class or #TypeAlias which also interfere with the mapping itself. Is any way to use spring-data without _class or #TypeAlias?
Ok I have found a workaround for it.
Be aware of using it when your elasticsearch model uses inheritance.
To solve this problem create class like this:
public class CustomMappingEsConverter extends MappingElasticsearchConverter {
public CustomMappingEsConverter(MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext, GenericConversionService conversionService) {
super(mappingContext, conversionService);
}
#Override
public Document mapObject(#Nullable Object source) {
Document target = Document.create();
if (source != null) {
this.write(source, target);
}
target.remove("_class"); // << workaround to remove those _class field in elasticsearch
return target;
}
}
And register the bean:
#Configuration
public class MappingEsConfiguration {
#Bean
#Primary
public CustomMappingEsConverter CustomMappingElasticsearchConverter(MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext,
GenericConversionService genericConversionService) {
return new CustomMappingEsConverter(mappingContext, genericConversionService);
}
}
After this changes I was able to use spring data without additional field _class.
Currently this is not possible. There is an open issue for that.
Edit 25.04.2021:
this feature will be available from the next version (4.3) on.

Retrieving additional non DB information using Spring Data Rest

I am using Spring Data Rest to expose a news feed REST API. I want to add an image (location) to the entity which will be retrieved by a separate web service API call.
What is the best way to do this using Spring Data Rest or would I have to create another separate REST API call/domain object etc.?
Any sample code would be fantastic.
You should use a ResourceProcessor
The Spring Data REST exporter executes any discovered ResourceProcessor's before it creates the output representation
#Bean
public ResourceProcessor<Resource<MyEntity>> myEntityProcessor() {
return new ResourceProcessor<Resource<MyEntity>>() {
#Override
public Resource<MyEntity> process(Resource<MyEntity> resource) {
resource.add(new Link("http://localhost:8080/images/images.jpg", "image"));
return resource;
}
};
}
Another example with access to the repository and EntityLinks object that helps to build links related to the entity..
#Component
class MyEntityResourceProcessor implements ResourceProcessor<Resource<MyEntity>> {
#Autoware
private MyEntityRepo repo;
#Autoware
private EntityLinks entityLinks;
#Override
public Resource<MyEntity> process(Resource<MyEntity> resource) {
MyEntity entity = resource.getContent();
// Some entity processing...
Link link entityLinks.linkForSingleResource(entity).slash("...").withRel("...")
resource.add(link);
return resource;
}
}
More examples of using ResourceProcessor you can find in RESTBucks project

Custom error is not rendered as hal in Spring Boot 1.3 and Spring hateoas 0.19

Initially I used Spring Boot 1.2 and Spring hateoas in my project, and I need to customize error message. So I created our class instead of the native VndErrors and VndError.
I created a class extends VndErrors.VndError.
public class MyError extends VndErrors.VndError{
//add some my custom fields
}
And antoher class to wrap the MyError.
public class ErrorDetails{
int total;
#JsonProperty("_embedded")
Map<String, List<MyError>> errors;
public ErrorDetails(List<MyError> err){
this.total=err.size();
errors.put("errors", err);
}
}
All exception are hanleded in a #ContrllerAdvice class. I used a custom Jackson2ObjectMapperBuilder to configure ObjectMapper in our project.
When I used Spring 1.2, it was rendered as expected. As following.
{
"total": 1,
"_embedded":{
"errors":[
{
//feilds,
_links:{
"self":""
}
}
]
}
}
But when upgraded to Spring Boot 1.3, it does not work as excepted.
The _links rendered as links, and the content type is application/json in the debug info.
Stage 1:
I am trying to create a simple pojo with a List of Link, it does not work.
public class ErrorDetails{}//pojo includes fields
public class MyError{
//add some my custom fields
#JsonUnwrapped
ErrorDetails content;
List<link> links;
}
public class ErrorResources{
int total;
#JsonProperty("_embedded")
Map<String, List<MyError>> errors;
public ErrorResources(List<MyError> err){
this.total=err.size();
errors.put("errors", err);
}
}
I found some related issues on github of Spring Hateoas project.
https://github.com/spring-projects/spring-hateoas/issues/279
https://github.com/spring-projects/spring-hateoas/issues/324
https://github.com/spring-projects/spring-hateoas/issues/288
I tried one of the suggestions of the issues above, when added #JsonSerialze(using=Jackson2HalModule.HalLinkListSerializer) on links of MyError class.
Got message similar with can not find the correct HttpMessageConverter, the content type of result is application/ocect(binary).
I also tried set the default contentType or default viewResolver to MappingJackson2JsonView, all did not change the result.
Whend I added a custom MappingJackson2HttpMessageConverter in my config:
#Bean
#Order(1)
public MappingJackson2HttpMessageConverter jacksonMessageConverter() {
ObjectMapper halObjectMapper=ctx.getBean("_halObjectMapper", ObjectMapper.class);
MappingJackson2HttpMessageConverter jacksonMessageConverter =
new MappingJackson2HttpMessageConverter();
jacksonMessageConverter.setObjectMapper(halObjectMapper);
jacksonMessageConverter.setSupportedMediaTypes(
Arrays.asList(MediaTypes.HAL_JSON, MediaType.APPLICATION_JSON_UTF8, MediaType.ALL));
return jacksonMessageConverter;
}
The error result is rendered as expected. But I do not think it is the correct way, because I used MediaType.ALL here. And it caused another big problem.
I used TestRestTemplate to test my rest APIs. The restTemlate tried to treat the input data as XML. I saw in the exception it indicated it tried to invoke a XmlHttpMessageConverter to process the content(it is json), even I have set the accept header as application/json. Of course, before I upgraded to Spring Boot 1.3 stack, it worked.
Stage 2:
I tried to use Resources and Resource to wrap the error result.
public class ErrorDetails{}//pojo includes error description fields
public class ErrorResource extends Resource<ErrorDetails>{
}
public class ErrorResources extends Resources<ErrorResource>{
}
public class ErrorMessage {
int total;
ErrorResources errors;
}
Spring still can not render the error result as hal format, it is application/json. When I added
#JsonSerialze(using=Jackson2HalModule.HalResourcesSerializer) on ErrorResources class, it raised an exception which complained the HalResourcesSerializer does not has a default constructor.
In the #ControllerAdvice class, I have tried to set the method return type to ErrorMessage and a wrapper ResponseEntity , it does not work.
Finally, my question is how to render the response body in a #ControllerAdvice same as the one in a normal #RestController? Why it does not work in a #ControllerAdvice class?
Is there a simple workaroud for this issue?

Is it possible to place variables into a resource path within a sling servlet?

We are trying to provide a clean URI structure for external endpoints to pull json information from CQ5.
For example, if you want to fetch information about a particular users history (assuming you have permissions etc), ideally we would like the endpoint to be able to do the following:
/bin/api/user/abc123/phone/555-klondike-5/history.json
In the URI, we would specifying /bin/api/user/{username}/phone/{phoneNumber}/history.json so that it is very easy to leverage the dispatcher to invalidate caching changes etc without invalidating a broad swath of cached information.
We would like to use a sling servlet to handle the request, however, I am not aware as to how to put variables into the path.
It would be great if there were something like #PathParam from JaxRS to add to the sling path variable, but I suspect it's not available.
The other approach we had in mind was to use a selector to recognise when we are accessing the api, and thus could return whatever we wanted to from the path, but it would necessitate a singular sling servlet to handle all of the requests, and so I am not happy about the approach as it glues a lot of unrelated code together.
Any help with this would be appreciated.
UPDATE:
If we were to use a OptingServlet, then put some logic inside the accepts function, we could stack a series of sling servlets on and make the acceptance decisions from the path with a regex.
Then during execution, the path itself can be parsed for the variables.
If the data that you provide comes from the JCR repository, the best is to structure it exactly as you want the URLs to be, that's the recommended way of doing things with Sling.
If the data is external you can create a custom Sling ResourceProvider that you mount on the /bin/api/user path and acquires or generates the corresponding data based on the rest of the path.
The Sling test suite's PlanetsResourceProvider is a simple example of that, see http://svn.apache.org/repos/asf/sling/trunk/launchpad/test-services/src/main/java/org/apache/sling/launchpad/testservices/resourceprovider/
The Sling resources docs at https://sling.apache.org/documentation/the-sling-engine/resources.html document the general resource resolution mechanism.
It is now possible to integrate jersy(JAX-RS) with CQ. We are able to create primitive prototype to say "Hello" to the world.
https://github.com/hstaudacher/osgi-jax-rs-connector
With this we can use the #PathParam to map the requests
Thanks and Regards,
San
There is no direct way to create such dynamic paths. You could register servlet under /bin/api/user.json and provide the rest of the path as a suffix:
/bin/api/user.json/abc123/phone/555-klondike-5/history
^ ^
| |
servlet path suffix starts here
then you could parse the suffix manually:
#SlingServlet(paths = "/bin/api/user", extensions = "json")
public class UserServlet extends SlingSafeMethodsServlet {
public void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response) {
String suffix = request.getRequestPathInfo().getSuffix();
String[] split = StringUtils.split(suffix, '/');
// parse split path and check if the path is valid
// if path is not valid, send 404:
// response.sendError(HttpURLConnection.HTTP_NOT_FOUND);
}
}
The RESTful way to approach this would be to have the information stored in the structure that you want to use. i.e. /content/user/abc123/phone/555-klondike-5/history/ would contain all the history nodes for that path.
In that usage. you can obtain an out of the box json response by simply calling
/content/user/abc123/phone/555-klondike-5/history.json
Or if you need something in a specific json format you could use the sling resource resolution to use a custom json response.
Excited to share this! I've worked ~ a week solving this, finally have the best Answer.
First: Try to use Jersey
The osgi-jax-rs-connector suggested by kallada is best, but I couldn't get it working on Sling 8. I lost a full day trying, all I have to show for it are spooky class not found errors and dependency issues.
Solution: The ResourceProvider
Bertrand's link is for Sling 9 only, which isn't released. So here's how you do it in Sling 8 and older!
Two Files:
ResourceProvider
Servlet
The ResourceProvider
The purpose of this is only to listen to all requests at /service and then produce a "Resource" at that virtual path, which doesn't actually exist in the JCR.
#Component
#Service(value=ResourceProvider.class)
#Properties({
#Property(name = ResourceProvider.ROOTS, value = "service/image"),
#Property(name = ResourceProvider.OWNS_ROOTS, value = "true")
})
public class ImageResourceProvider implements ResourceProvider {
#Override
public Resource getResource(ResourceResolver resourceResolver, String path) {
AbstractResource abstractResource;
abstractResource = new AbstractResource() {
#Override
public String getResourceType() {
return TypeServlet.RESOURCE_TYPE;
}
#Override
public String getResourceSuperType() {
return null;
}
#Override
public String getPath() {
return path;
}
#Override
public ResourceResolver getResourceResolver() {
return resourceResolver;
}
#Override
public ResourceMetadata getResourceMetadata() {
return new ResourceMetadata();
}
};
return abstractResource;
}
#Override
public Resource getResource(ResourceResolver resourceResolver, HttpServletRequest httpServletRequest, String path) {
return getResource(resourceResolver , path);
}
#Override
public Iterator<Resource> listChildren(Resource resource) {
return null;
}
}
The Servlet
Now you just write a servlet which handles any of the resources coming from that path - but this is accomplished by handling any resources with the resource type which is produced by the ResourceProvider listening at that path.
#SlingServlet(
resourceTypes = TypeServlet.RESOURCE_TYPE,
methods = {"GET" , "POST"})
public class TypeServlet extends SlingAllMethodsServlet {
static final String RESOURCE_TYPE = "mycompany/components/service/myservice";
#Override
protected void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response) throws ServletException, IOException {
final String [] pathParts = request.getResource().getPath().split("/");
final String id = pathParts[pathParts.length-1];
response.setContentType("text/html");
PrintWriter out = response.getWriter();
try {
out.print("<html><body>Hello, received this id: " + id + "</body></html>");
} finally {
out.close();
}
}
}
Obviously your servlet would do something much more clever, such as process the "path" String more intelligently and probably produce JSON.

Configure JAX-RS base URI programmatically per deployment

Im trying to use CDI extensions to discover JAX-RS resources at runtime and automatically publish them under different base URIs in a Java SE environment. Applications should not need to extend javax.ws.rs.core.Application themselves if possible.
I have read RestEasy documentation and javadoc but failed to find any obvious way to modify the #ApplicationPath at runtime.
One idea that im exploring is to try generate javax.ws.rs.core.Application and set the #ApplicationPath base URI programmatically, maybe by using an AnnotatedType CDI extension, and publish that as a * org.jboss.resteasy.spi.ResteasyDeployment`.
Are there other/better ways to do this?
EDIT:
Trying CDI extension event ProcessAnnotatedType to change #javax.ws.rs.Path of JAX-RS resources.
<X> void process(#Observes ProcessAnnotatedType<X> pat) {
if (!pat.getAnnotatedType().isAnnotationPresent(javax.ws.rs.Path.class)) {
return;
}
final AnnotatedType<X> org = pat.getAnnotatedType();
AnnotatedType<X> wrapped = new AnnotatedType<X>() {
#Override
public <T extends Annotation> T getAnnotation(final Class<T> annotation) {
if (javax.ws.rs.Path.class.equals(annotation)) {
class PathLiteral extends AnnotationLiteral<javax.ws.rs.Path> implements javax.ws.rs.Path {
#Override
public String value() {
return "change_me/" + (javax.ws.rs.Path) org.getAnnotation(annotation);
}
}
return (T) new PathLiteral();
} else {
return org.getAnnotation(annotation);
}
}
pat.setAnnotatedType(wrapped);
}
... then after bootstrap, constructing the bean using javax.enterprise.inject.spi.BeanManager was expecting the following code to print "change_me/...."
Set<Bean<?>> beans = beanManager.getBeans(jaxrsClass);
for (Bean<?> bean : beans) {
CreationalContext cc = bm.createCreationalContext(bean);
Object jaxrs = bean.create(cc);
Path p = jaxrs.getClass().getAnnotation(Path.class);
System.out.println(p.value());
}
... but this does not work. javax.ws.rs.Path is unchanged for JAX-RS resource 'jaxrsClass'.
What is wrong?
I doubt this can be done in a reliable way. It probably all comes down to which happens first: the CDI bootstrap or JAX-RS, of course in the future or in other application servers it could all be done in parallel.
It's certainly a cool idea though. What have they said on the RestEasy forums?
We are already using such an approach.
We are using the feature to use Subresource locators and take the power of guice.
At the startup we are scanning the classpath for all resources annotated with #Path. After that we are extracting the path and binding the resources with the help of Names/#Named. So the resources can later be injected with the help of the name.
bind(..).annotatedWith(Names.named("path")).to(..)
The next step is that you need a resource with a subresource locator.
#Path("{name}")
public Object find(#PathParam("name") name){
return injector.getInstance(..);
}
You could use this approach to bind them at runtime and also to change the original annotated path.