In a Spring Boot Web MVC REST service I want to use the operation ID and path values from SpringDoc generated OpenAPI from within the service where its generated. How can I get the OpenAPI JSON doc without going through the web endpoint?
If I understand you correctly:
You want to get OpenAPI documentation in JSON format inside code your Spring application.
I do it this way:
1.) Create a component that extends from the OpenApiResource class.
And create a getOpenApiJson method that calls getOpenApi() (creating or receiving an OpenApi model) and writeJsonValue() (serialization of OpenAPI).
#Component
public class CustomOpenApiResource extends OpenApiResource {
public CustomOpenApiResource(ObjectFactory<OpenAPIService> openAPIBuilderObjectFactory,
AbstractRequestService requestBuilder,
GenericResponseService responseBuilder,
OperationService operationParser,
Optional<List<OperationCustomizer>> operationCustomizers,
Optional<List<OpenApiCustomiser>> openApiCustomisers,
Optional<List<OpenApiMethodFilter>> methodFilters,
SpringDocConfigProperties springDocConfigProperties,
SpringDocProviders springDocProviders) {
super(openAPIBuilderObjectFactory,
requestBuilder,
responseBuilder,
operationParser,
operationCustomizers,
openApiCustomisers,
methodFilters,
springDocConfigProperties,
springDocProviders);
}
#Override
protected String getServerUrl(HttpServletRequest request, String apiDocsUrl) {
/**
* How to implement this method you can find out for example from OpenApiWebMvcResource
*/
return "";
}
public String getOpenApiJson() throws JsonProcessingException {
return writeJsonValue(getOpenApi(Locale.getDefault()));
}
}
2.) Inject CustomOpenApiResource component
#Autowired
private CustomOpenApiResource resource;
And use getOpenApiJson()
String openApiJson = resource.getOpenApiJson();
Related
I'm relativly new to spring/spring boot.
At the moment I'm using a spring boot rest application which provides an FeignClient to be included in other projects. Now, I want those FeignClients be wrapped by a CircuitBreaker.
The best solution I came up with, is that I dynamically create a proxy which includes the CircuitBreaker implementation which itself calls the created FeignClient.
So let's assume I have the following interface which describes the RestController:
#RequestMapping("/")
public interface MyWebService {
#GetMapping("name")
public String getName();
}
Now, I have the interface for the FeignClient:
#FeignClient("app")
public interface WebServiceClient extends WebService {
}
So.. My goal would be to achieve something like I have another annotation e. g. #WithCircuitBreaker which I then will be scanned for and dynamically create a proxy bean which will be injected instead of the FeignClient bean.
At the moment my code looks like this:
#FeignClient("app")
#WithCircuitBreaker
public interface WebServiceClient extends WebService {
}
As far as I know, I can now create a #Configuration Class which will look like this:
#Configuration
public class WithCircuitBreakerConfiguration implements ImportAware {
private AnnotationMetadata annotationMetadata;
private AnnotationAttributes annotationAttributes;
#Override
public void setImportMetadata(AnnotationMetadata importMetadata) {
this.annotationMetadata = importMetadata;
Map<String, Object> annotatedClasses = importMetadata.getAnnotationAttributes(WithCircuitBreaker.class.getName());
this.annotationAttributes = AnnotationAttributes.fromMap(annotatedClasses);
}
What else to import to create the proxy and inject it?
}
Now I'm at the point, which I don't know how to continue. How to dynamically create a proxy class which does something like this:
public class PorxyedWebService {
private WebService feignClientProxy;
#Autowired
public ProxyedWebService(WebService feignClientProxy) {
this.feignClientProxy = feignClientProxy;
}
public String getName() {
...
<some circuitbreaker stuff>
....
return this.feignClientProxy.getName();
}
}
and then return this proxy instead of the proxy generated from Feign as soon as someone autowires the WebService interface.
I am not a Spring user, but I do know that Spring does not create proxies recursively if e.g. multiple Spring AOP aspects are applied to the same object. Instead, additional interceptors (or advices in AOP language) are registered upon the same proxy. I think you want to use that infrastructure in order to achieve whatever your objective is.
You can just use the resilience4j Spring Boot2 starter.
You can combine the #CircuitBreaker annotation with the #FeignClient annotation at interface level.
You can then use it as follows:
#FeignClient(name = DUMMY_FEIGN_CLIENT_NAME)
#CircuitBreaker(name = DUMMY_FEIGN_CLIENT_NAME)
public interface DummyFeignClient {
String DUMMY_FEIGN_CLIENT_NAME = "dummyFeignClient";
#GetMapping(path = "/api/{param}")
void doSomething(#PathVariable(name = "param") String param);
}
I currently have several APIs that share common objects. In some cases, I want to hide certain properties of these common objects when generating the swagger documentation. For instance, let's say I have a simple class:
public class Person {
private String forename;
private String surname;
private int age;
/* getters and setters with annotations here... */
}
For some APIs, I want the age field to appear in the swagger documentation but in other APIs I don't.
I don't want to use the hidden attribute of the #ApiModelProperty annotation as this will hide the property for all APIs. I can see there is an access attribute which I could use with my own filter class that extends SwaggerSpecFilter.
How can I do this by implementing the isPropertyAllowed method of this interface? There does not seem to be any parameter in that method that I can use to find out which Api is using the Model and property.
I've managed to implement this by having all my API classes have a 'Filter' class that will add/remove/edit any properties from the API models like so:
public class MyApiFilter implements SwaggerFilterIF {
#Override
public void filter(Swagger swagger) {
Map<String, Model> definitions = swagger.getDefinitions();
// remove 'age' from 'Person'
Model model = definitions.get("Person");
model.getProperties().remove("age");
}
This is then referenced in the API class:
#Api
#Path("/myapi")
public class MyApi implements SwaggerApiIF {
public MyApi () {
super();
}
#Override
public SwaggerFilterIF getFilter() {
return new MyApiFilter();
}
}
I then have a servlet to generate the API documentation for every API that I can access individually. In this servlet, I call the filter method on the 'Filter' class for the API which will filter the swagger definition as required:
for (final SwaggerApiIF api : apis) {
final Swagger swagger = new Reader(new Swagger(), config).read(api.getClass());
api.getFilter().filter(swagger);
}
I have a Jersey endpoint which uses a custom OSGi Service ExceptionManager Service.
#Path("service")
public class ServiceFacade {
private volatile ExceptionManager exceptionManager;
public ServiceFacade() {
BundleContext bC = FrameworkUtil.getBundle(ServiceFacade.class).getBundleContext();
ServiceReference<ExceptionManager> sR = bC.getServiceReference(ExceptionManager.class);
if (sR != null)
this.exceptionManager = bC.getService(sR);
}
#GET
#Consumes({MediaType.APPLICATION_JSON})
#Produces(MediaType.APPLICATION_JSON)
public Response sayHello() {
try {
if (exceptionManager == null)
return Response.status(Status.SERVICE_UNAVAILABLE).build();
// Do some work...
} catch (Exception e) {
exceptionManager.handle(e);
}
}
}
This Jersey class is added to the Jersey Application as a simple class, that means that every time a user hits this endpoint, a new instance of this class is created to handle the request. As you can see, the class contains a constructor which initializes the ExceptionManager Service. My question is, isn't there a simplified way of retrieving the service without going to BundleContext?
I have seen DependencyManager, but this bundle seems to only add the dependencies to the class (ServiceFacade in this case) during the Activation process, but that dependency resolution is too early this has to be done during run-time, every time an instance is created. Bellow is an approximation with DependencyManager but is not a solution for this:
public class Activator extends DependencyActivatorBase {
#Override
public void init(BundleContext bundleContext, DependencyManager dependencyManager) throws Exception {
dependencyManager.add(createComponent()
.setImplementation(ServiceFacade.class)
.add(createServiceDependency()
.setService(ExceptionManager.class)
.setRequired(true));
}
}
Thanks.-
You can obtain the reference to an OSGi service without accessing to BundleContext by using Declarative Services. A tutorial can be found here.
You can make the endpoint a singleton resource. This way you can let the dependency manager create a single instance and inject services and then add that instance to the Jersey application.
There are a few limitations, like Jersey's field or constructor injection does not work. You also have to be careful about concurrency when using fields of the resource.
Both 'aop:aspectj-autoproxy' and 'mvc:annotation-driven' are present in the XML config.
Both of these classes are defined as a bean inside of the same XML.
Using Spring 3.2.3.RELEASE and Google App Engine 1.8.1 in a local/dev environment.
My pointcut does not execute.
My advice. Declared inside a class annotated with #Aspect.
#Component
#Aspect
public class RequestLimiter {
private MemcacheService cache = MemcacheServiceFactory.getMemcacheService();
#Pointcut("within(#pcs.annotations.LimitRequests com.zdware.pcs.controllers.PingCollectorController)")
public void methodRequestLimited(){}
#Around("methodRequestLimited() && args(req,limitRequests)")
public Object requestGateWay(ProceedingJoinPoint jp, HttpServletRequest req,LimitRequests limitRequests) throws Throwable {
// do stuff
}
}
The method I am using to test in the controller layer.
#Controller
public class PingCollectorController {
#RequestMapping(value="/test")
#LimitRequests(requestTimeLimit = 1, functionName = "Test")
public String test(){
return "test"; // this will return me to a jsp that doesnt exist, but my advice is still not executing.
}
}
Is CGLIB in the classpath? It will be needed to generate the proxy (since your controller does not implement an interface, spring cannot use a simpler JDK proxy).
I have this in src/main/groovy/...
package com.mycompany.web;
// imports....
#Controller
class GroovyController {
#RequestMapping("/status_groovy")
public #ResponseBody String getStatus() {
return "Hello World from groovy!";
}
}
Using maven 3 and spring 3.1 (Milestone). Spring MVC works perfectly well for java controllers and everything is set up fine. The groovy class compiles fine and can be found in the classes directory along with the java controller classes.
I have similar controller written in java (JavaController) in same package but under src/main/java and its getting picked up properly by spring and mapped and I can see the response on screen when I hit the url.
package com.mycompany.web;
// imports....
#Controller
class JavaController {
#RequestMapping("/status")
public #ResponseBody String getStatus() {
return "Hello World!";
}
}
Jetty starts normally with no error in log but in I dont see groovy url getting mapped whereas i can see the java one.
2011-09-23 16:05:50,412 [main] INFO org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping - Mapped "{[/status],methods=[],params=[],headers=[],consumes=[],produces=[]}" onto public java.lang.String com.mycompany.web.JavaController.getStatus()
All the setting are fine as other parts of app are working just fine with annotations (component-scan etc.), Just that I can not get the url mapped in GroovyController
Can anyone explain what needs to be done in order to get Controllers written in groovy working?
PS: I am avoiding GroovyServlet to run the scripts because it has major downside when it comes to bean injection and url path mappings.
With all due respect to Ben (whom I work with), the problem here isn't that Spring is creating a cglib proxy. Rather, it's creating a dynamic JDK (or interface-based) proxy. This method of creating proxies can only implement methods declared in the target's implemented interfaces. You actually want Spring to create a cglib proxy, which creates a proxy that is a subclass of the target object and can therefore recreate all of its public methods. Unless you specify otherwise, Spring will create a cglib proxy if the target object doesn't implement any interfaces, and an interface-based proxy otherwise. Since all Groovy objects implement GroovyObject, you're getting an interface-based proxy, even though you didn't explicitly implement any interfaces in your Groovy controller. Ben's solution is correct in that if you create an interface with all your controller methods, you'll get the expected behavior. An alternative is to create a BeanFactoryPostProcessor which instructs Spring to create cglib proxies for classes that implement GroovyObject and only GroovyObject. Here's the code:
/**
* Finds all objects in the bean factory that implement GroovyObject and only GroovyObject, and sets the
* AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE value to true. This will, in the case when a proxy
* is necessary, force the creation of a CGLIB subclass proxy, rather than a dynamic JDK proxy, which
* would create a useless proxy that only implements the methods of GroovyObject.
*
* #author caleb
*/
public class GroovyObjectTargetClassPreservingBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
private static final Logger logger = LoggerFactory.getLogger(GroovyObjectTargetClassPreservingBeanFactoryPostProcessor.class);
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
for (String beanDefName : beanFactory.getBeanDefinitionNames()) {
BeanDefinition bd = beanFactory.getBeanDefinition(beanDefName);
//ignore abstract definitions (parent beans)
if (bd.isAbstract())
continue;
String className = bd.getBeanClassName();
//ignore definitions with null class names
if (className == null)
continue;
Class<?> beanClass;
try {
beanClass = ClassUtils.forName(className, beanFactory.getBeanClassLoader());
}
catch (ClassNotFoundException e) {
throw new CannotLoadBeanClassException(bd.getResourceDescription(), beanDefName, bd.getBeanClassName(), e);
}
catch (LinkageError e) {
throw new CannotLoadBeanClassException(bd.getResourceDescription(), beanDefName, bd.getBeanClassName(), e);
}
Class<?>[] interfaces = beanClass.getInterfaces();
if (interfaces.length == 1 && interfaces[0] == GroovyObject.class) {
logger.debug("Setting attribute {} to true for bean {}", AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, beanDefName);
bd.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, true);
}
}
}
}
Just include a bean of this type in your context, and voila! You can have Groovy controllers without needing to define interfaces.
I beg to differ. There is no need to implement an interface. The problem here is that the default AnnotationMethodHandlerAdapter does not read annotations from proxies. Hence we would have to create this proxy aware AnnotationMethodHandlerAdapter which extends the default AnnotationMethodHandlerAdapter of spring. We also need to instantiate a bean for this ProxyAwareAnnotationMethodHandlerAdapter in the Spring Configuration xml file.
Note: This feature is not available in Spring 3.x but since spring 4.0 would support groovy beans, this feature should be covered.
//ProxyAwareAnnotationMethodHandlerAdapter.java
package name.assafberg.spring;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.aop.TargetSource;
import org.springframework.aop.framework.Advised;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter;
/**
* Add proxy awareness to <code>AnnotationMethodHandlerAdapter</code>.
*
* #author assaf
*/
public class ProxyAwareAnnotationMethodHandlerAdapter extends AnnotationMethodHandlerAdapter {
/**
* #param request
* #param response
* #param handler
* #return
* #throws Exception
* #see org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, java.lang.Object)
*/
#Override
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
handler = unwrapHandler(handler);
return super.handle(request, response, handler);
}
/**
* #param handler
* #return
* #see org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter#supports(java.lang.Object)
*/
#Override
public boolean supports(Object handler) {
handler = unwrapHandler(handler);
return super.supports(handler);
}
/**
* Attempt to unwrap the given handler in case it is an AOP proxy
*
* #param handler
* #return Object
*/
private Object unwrapHandler(Object handler) {
if (handler instanceof Advised) {
try {
TargetSource targetSource = ((Advised) handler).getTargetSource();
return targetSource.getTarget();
} catch (Exception x) {
throw new RuntimeException(x);
}
} else {
return handler;
}
}
}
The spring configuration XML file must have the following. Instead of creating a bean of AnnotationMethodHandlerAdapter we must create a ProxyAwareAnnotationMethodHandlerAdapter bean.
<beans .........
...
...
<bean class="full.qualified.name.of.ProxyAwareAnnotationMethodHandlerAdapter" />
...
...
<lang:groovy script-source="classpath:com/example/mysample.groovy refresh-check-delay="1000" />
</beans>
Also Spring parses the configuration XML file using a SAX parser (based on event occurence). So, in order for spring to understand the annotations within the groovy scripts, the groovy beans (using tag) must be created after the ProxyAwareAnnotationMethodHandlerAdapter.
Hope than helps
Reference: http://forum.springsource.org/showthread.php?47271-Groovy-Controller
Unfortunately, if you want to get this running in Groovy you'll have to create an interface for your Controller class and annotate the method definitions as well. Spring creates a proxy for your class using Cglib. However, without creating a custom interface for your controller Spring is proxying on groovy.lang.GroovyObject because all Groovy objects implement that interface by default.
interface GroovyControllerInterface {
#RequestMapping("/status_groovy")
#ResponseBody String getStatus()
}
#Controller
class GroovyController implements GroovyControllerInterface {
#RequestMapping("/status_groovy")
public #ResponseBody String getStatus() {
return "Hello World from groovy!";
}
}