My need is to give information about data constraints or default values to the client app that will use the API. The schema or the ALPS generated by Spring Data Rest seems to be a good place to put this information.
But the part about documenting the API is a bit quick in the official reference documentation, and I can't find fully documented example in the community. I've tried to read the code of PersistentEntityToJsonSchemaConverter to have a insight of the offered possibilities, but the headache arrived first.
I know there is the #Description annotation that I can put on Entities and Properties that will change the title field of the schema.
I know the same fields can be modified in rest-messages.properties
Is there other fields that can be modified by annotations or configuration files ?
Putting default or constraints information in this description field really feels like not using it straight.
The question is almost old, I don't know if you have already found a solution.
Anywhere, you can build a completely custom ALPS profiling information if you build two custom converters that replace the converters used by Spring.
The first one needs to extends the converter org.springframework.data.rest.webmvc.alps.AlpsJsonHttpMessageConverter.
Here a possible implementation:
public class CustomAlpsJsonHttpMessageConverter extends AlpsJsonHttpMessageConverter {
public CustomAlpsJsonHttpMessageConverter(RootResourceInformationToAlpsDescriptorConverter converter) {
super(converter);
}
#Override
public boolean canWrite(Class<?> clazz, MediaType mediaType) {
return super.canWrite(clazz, mediaType);
}
#Override
public boolean canRead(Type type, Class<?> contextClass, MediaType mediaType) {
return super.canRead(type, contextClass, mediaType);
}
#Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request,
ServerHttpResponse response) {
return super.beforeBodyWrite(body, returnType, selectedContentType, selectedConverterType, request, response);
}
#Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return converterType.equals(AlpsJsonHttpMessageConverter.class)
|| converterType.equals(CustomAlpsJsonHttpMessageConverter.class);
}
}
The second one needs to extends the converter org.springframework.data.rest.webmvc.alps.RootResourceInformationToAlpsDescriptorConverter.
The RootResourceInformationToAlpsDescriptorConverter only have two public resources: the constructor and the "convert" method.
You may to overwrite every single private field/method of that class if you want to have a custom behaviour.
Pay attention that the "supports" method of your CustomAlpsJsonHttpMessageConverter will need to matches the given "converterType" with your new CustomAlpsJsonHttpMessageConverter class.
At that point you can customize the "convert" method of the class RootResourceInformationToAlpsDescriptorConverter, simply ovverriding it in your CustomRootResourceInformationToAlpsDescriptorConverter.
Finally, you have to register the two converters in the Application Context. In order to do that, you can extend the RepositoryRestMvcConfiguration class, and in your CustomRepositoryRestMvcConfiguration you will need to #Override the methods "alpsJsonHttpMessageConverter()" and "alpsConverter()".
Add also the #Bean annotation in the two ovverriding custom methods, like this:
#Bean
#Override
public AlpsJsonHttpMessageConverter alpsJsonHttpMessageConverter() {
return new CustomAlpsJsonHttpMessageConverter(alpsConverter());
}
#Bean
#Override
public RootResourceInformationToAlpsDescriptorConverter alpsConverter() {
Repositories repositories = repositories();
PersistentEntities persistentEntities = persistentEntities();
RepositoryEntityLinks entityLinks = entityLinks();
MessageSourceAccessor messageSourceAccessor = resourceDescriptionMessageSourceAccessor();
RepositoryRestConfiguration config = config();
ResourceMappings resourceMappings = resourceMappings();
return new CustomRootResourceInformationToAlpsDescriptorConverter(associationLinks(), repositories, persistentEntities,
entityLinks, messageSourceAccessor, config, objectMapper(), enumTranslator());
}
So you can have a completely custom ALPS, if you need.
I have tried this solution to build custom profiling links, and it works perfectly.
Related
I've got a Spring Boot Controller that returns a List<Person> and I would like to implement a custom HttpMessageConverter that can write collections of type Person.
I see AbstractHttpMessageConverter, but the supports() method only takes a Class, so I can test for Collection, but no way (as far as I know) to test for a Collection of type Person.
I also see GenericHttpMessageConverter and AbstractGenericHttpMessageConverter which sound promising, but I can't figure out to properly implement one.
I think I found a solution. The following seems to work...
#Component
static class PersonMessageConverter
extends AbstractGenericHttpMessageConverter<Collection<Person>> {
public PersonMessageConverter() {
super(MediaType.APPLICATION_JSON);
}
#Override
public boolean canWrite(Type type, Class<?> clazz, MediaType mediaType) {
TypeToken<Collection<Person>> personCollectionType = new com.google.common.reflect.TypeToken<>() {};
return canWrite(mediaType) && personCollectionType.isSupertypeOf(type);
}
#Override
protected void writeInternal(Collection<Person> persons, Type type, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
// do write...
}
// continue with read methods here
}
Is it possible to define multiple authenticationProviders in Micronaut?
Let's say I have an entity A which can be logged using authenticationProviderA: which given a user and pass checks the DB table of A.
Is it possible to add an entity B and its authenticationProviderB which given a user and pass will check the DB table of B?
If so, how do you define in your controller which authenticationProvider you want to use?
After taking a look at io.micronaut.security.authentication.Authenticator I've seen it's possible to have multiple authenticationProviders in Micronaut.
The documentation says:
An Authenticator operates on several {#link AuthenticationProvider} instances returning the first authenticated {#link AuthenticationResponse}.
From what I've seen you just have to implement AuthenticationProvider and the Authenticator will include the implementations (even if it isn't annotated!) in an internal list of AuthenticationProviders.
IMHO this isn't a good way to provide multiple ways to authenticate. In the example provided in the question, the authentication for A and B both require calls to DB which means depending on the order of the execution of the AuthenticationProviders unneeded BD calls will be executed.
I think would be better to provide a way to indicate which AuthenticationProviders has to be used by controller or endpoint.
Maybe there is a way to do that and I just don't know, so feel free to comment if so.
There is no built-in solution regarding the problem, but there are multiple ways to achieve what you want with small amount of code.
Solution №1:
Creating custom AuthenticationRequest.class and LoginController.class if you need multiple login end points:
public class AuthenticationRequestForEntityA extends UsernamePasswordCredentials { ... }
public class AuthenticationRequestForEntityB extends UsernamePasswordCredentials { ... }
in your custom LoginController replace the default UsernamePasswordCredentials with your specific AuthenticationRequestForEntityA or AuthenticationRequestForEntityB and copy paste the rest of the code from the original LoginController.class:
#Consumes({MediaType.APPLICATION_FORM_URLENCODED, MediaType.APPLICATION_JSON})
#Post
public Single<MutableHttpResponse<?>> login(#Valid #Body AuthenticationRequestForEntityA usernamePasswordCredentials, HttpRequest<?> request) {
Flowable<AuthenticationResponse> authenticationResponseFlowable = Flowable.fromPublisher(authenticator.authenticate(request, usernamePasswordCredentials));
...
and then in your authentication providers:
public class AuthenticationProviderA implements AuthenticationProvider {
#Override
public Publisher<AuthenticationResponse> authenticate(#Nullable HttpRequest<?> httpRequest, AuthenticationRequest<?, ?> authenticationRequest) {
if (authenticationRequest instanceof AuthenticationRequestForEntityA) {
return authenticate(authenticationRequest);
} else {
// return empty
}
}
}
public class AuthenticationProviderB implements AuthenticationProvider {
#Override
public Publisher<AuthenticationResponse> authenticate(#Nullable HttpRequest<?> httpRequest, AuthenticationRequest<?, ?> authenticationRequest) {
if (authenticationRequest instanceof AuthenticationRequestForEntityB) {
return authenticate(authenticationRequest);
} else {
// return empty
}
}
}
Solution №2: Creating your custom route-based AuthenticationProvider
Since HttpRequest is available in the AuthenticationProvider as an input parameter you can simply authenticate based on the httpRequest path or query parameter properties.
To make the code cleaner you can create your own RouteBasedAuthenticationProvider interface:
public interface RequestBasedAuthenticationProvider extends AuthenticationProvider {
/**
You can check the request path or request parameter or whatever
*/
boolean supports(HttpRequest<?> request);
}
then in Micronaut AuthenticationProvider:
#Context
public class AppAuthenticationProvider implements AuthenticationProvider {
private final Collection<RequestBasedAuthenticationProvider> providers;
constructor(...) {...}
#Override
public Publisher<AuthenticationResponse> authenticate(#Nullable HttpRequest<?> httpRequest, AuthenticationRequest<?, ?> authenticationRequest) {
return providers.stream()
.filter(provider -> provider.supports(httpRequest))
.findFirst()
.orElseThrow(//Throw provider not found error)
.authenticate(httpRequest, authenticationRequest);
}
}
[EDIT] The problem is with the
register(new ServiceBinder<>(MyService.class));
Jersey generates a warning and ignores the registration for all but the first one (Existing previous registration found for the type); it only considers the type-erased ServiceBinder class to decide there is a conflict.
It looks like I need to use a more sophisticated version of register to get past that issue.
[/EDIT]
In Jersey 1 I was able to use custom injectable providers to inject my objects into both class fields and method parameters, by extending
LazySingletonInjectableProvider
I can't figure out how to port that pattern to Jersey 2 (with hk2 on Tomcat 7). I have read everything I could find on the topic, including Jersey custom method parameter injection with inbuild injection - but I don't want to use a custom annotation, and I am not trying to inject a request parameter.
[EDIT] I made the wrong assumption regarding what works and what doesn't:
Injection into a class field in a ContainerRequestFilter works fine
Injection into a resource, either as class field or method parameter does not work
[EDIT 2]: The InjectionResolver as described below actually doesn't work at all, I have removed it. Jersey already has a ContextInjectionResolver which presumably should take care of the #Context annotation.
I have created and registered an AbstractBinder, and with that class field injection works fine; however method parameter injection doesn't (the binder never gets invoked and the parameter remains null).
I have tried to bind an InjectionResolver but that didn't help either.
Any suggestion on how to make this work would be greatly appreciated... here is the current code:
The HK2 binder:
public class ServiceBinder<T> extends AbstractBinder
{
private final Factory<T> _factory;
private final Class<? extends T> _clazz;
public OsgiServiceBinder(Class<T> clazz)
{
_factory = new ServiceFactory<>(clazz);
_clazz = clazz;
}
protected void configure()
{
bindFactory(_factory).to(_clazz); //.in(RequestScoped.class);
bind(ServiceInjectionResolver.class)
.to(new TypeLiteral<InjectionResolver<Context>>() { })
.in(PerLookup.class);
}
}
The injection resolver:
public class ServiceInjectionResolver<T> implements InjectionResolver<Context>
{
private Class<T> _clazz;
public OsgiServiceInjectionResolver(Class<T> clazz)
{
_clazz = clazz;
}
public Object resolve(Injectee injectee, ServiceHandle<?> root)
{
if (_clazz.getCanonicalName().equals(injectee.getRequiredType().getTypeName())) {
return Framework.getService(_clazz);
}
return null;
}
public boolean isConstructorParameterIndicator()
{
return false;
}
public boolean isMethodParameterIndicator()
{
return true;
}
}
The JAX-RS registration:
public class MyApplication extends Application
{
public MyApplication()
{
registerClasses(<resource classes>);
register(new ServiceBinder<>(MyService.class));
}
}
The resource class:
#Path("/schedules")
public class SchedulesResource
{
#Context UriInfo _uriInfo;
// This injection works fine, _service1 is properly initialized
#Context MyService _service1;
#PUT
#Consumes({MediaType.APPLICATION_JSON})
#Path("{jobGroup}/{jobName}")
public Response putSchedule(#Context MyService service2,
...)
{
// The injection of service2 doesn't work...
}
}
The Factory class:
public class ServiceFactory<T> implements Factory<T>
{
private Class<T> _clazz;
protected ServiceFactory(Class<T> clazz)
{
_clazz = clazz;
}
public T provide()
{
return Framework.getService(_clazz);
}
}
public void dispose(T t)
{
}
}
pok
The problem was actually with Jersey component registrations.
Even though I was registering binder instances, Jersey was checking the class (ServiceBinder) and discarding all but the first registration (WARN: existing registration found for the type).
This seems a bit bogus given I am registering instances, and I wish Jersey would fail with an error rather than log a warning when failing to register a component, but the solution is to simply change the registration pattern slightly:
// Doesn't work
register(new ServiceBinder<>(MyService1.class));
register(new ServiceBinder<>(MyService2.class));
// Works like a charm
register(new ServiceBinder(MyService1.class, MyService2.class));
where obviously the ServiceBinder is adjusted to call bindFactory for each supplied service.
When implementing RESTful API I wrap all my data in an object so it looks like this.
{error: null, code: 200, data: {...actual data...}}
This results in repetitive code I use everywhere to wrap data:
#Transactional
#RequestMapping(value = "/", method = RequestMethod.GET)
public #ResponseBody Result<List<BookShortDTO>> books() {
List<Book> books = booksDao.readBooks();
return Result.ok(books); // this gets repeated everywhere
}
So the question is how do I modify this (maybe with use of custom HttpMessageConverter maybe some other ways?) to just return booksDao.readBooks() and to get it wrapped automatically.
Like #Ralph suggested you can use a HandlerMethodReturnValueHandler to wrap your handlers return value.
The easiest way to achieve this is by extending RequestResponseBodyMethodProcessor and alter it's behavior a bit. Best is to create a custom annotation to mark your handler methods with. This will make sure your HandlerMethodReturnValueHandler will be called instead of others included by RequestMappingHandlerAdapter by default.
#Target({ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
public #interface ResultResponseBody {}
Here is a simple implementation of the custom HandlerMethodReturnValueHandler named ResultResponseHandlerMethodProcessor which will support values returned from methods annotated with ResultResponseBody. It's pretty simple. Just override the supportsReturnType() and handleReturnValue() methods to suit your needs (wrap the return value into a Result type).
public class ResultResponseHandlerMethodProcessor extends RequestResponseBodyMethodProcessor {
public ResultResponseHandlerMethodProcessor(final List<HttpMessageConverter<?>> messageConverters) {
super(messageConverters);
}
public ResultResponseHandlerMethodProcessor(final List<HttpMessageConverter<?>> messageConverters, final ContentNegotiationManager contentNegotiationManager) {
super(messageConverters, contentNegotiationManager);
}
#Override
public boolean supportsReturnType(final MethodParameter returnType) {
return returnType.getMethodAnnotation(ResultResponseBody.class) != null;
}
#Override
public void handleReturnValue(final Object returnValue, final MethodParameter returnType, final ModelAndViewContainer mavContainer, final NativeWebRequest webRequest) throws IOException, HttpMediaTypeNotAcceptableException {
super.handleReturnValue(Result.ok(returnValue), returnType, mavContainer, webRequest);
}
}
The only thing left is to add this class to the list of custom HandlerMethodReturnValueHandlers and provide it with a MappingJackson2HttpMessageConverter instance.
#EnableWebMvc
#Configuration
public class ApplicationConfiguration extends WebMvcConfigurerAdapter
#Override
public void addReturnValueHandlers(final List<HandlerMethodReturnValueHandler> returnValueHandlers) {
List<HttpMessageConverter<?>> messageConverters = new ArrayList<>();
messageConverters.add(new MappingJackson2HttpMessageConverter());
returnValueHandlers.add(new ResultResponseHandlerMethodProcessor(messageConverters));
}
}
I think, rather than changing the message converters (which would work), I would use an AOP approach - around advice on all the relevant controller methods would be quite easy to set up. It would also give you a nicer programming model, and finer grained control of which methods are intercepted.
You could use a HandlerMethodReturnValueHandler to replace the result.
The key point is: to replace the return value before delegating the (modified) retunr value to the serialisation.
See this blog: http://martypitt.wordpress.com/2012/11/05/custom-json-views-with-spring-mvc-and-jackson/ for an example how to archive a similar (not the same) goal. It also describe one way to register the HandlerMethodReturnValueHandler (for an other see Bart´s answer)
I'd like to try and convince you that what you are doing is right and does not require any changes.
As you've posted in the comments to your question, you have a number of different Result methods which set the error message, the code, and the data. Something like
Result.ok(data)
Result.forbidden()
Result.badRequest("<Something> caused a syntax error.")
Result.notModified("The entity was not modified.")
I'm assuming these methods are meant to map to the various HTTP status codes, but with custom error messages.
Your #Controller handler methods are meant to handle a request and prepare a response. That's what your method is currently doing and it is very explicit about what it does. The logic about what the Result should be belongs to the handler method, not a HandlerMethodReturnValueHandler, like others are proposing.
I would even suggest using ResponseEntity instead of #ResponseBody. You can return a ResponseEntity and set the HTTP response headers and status code explicitly. You would also set the response body.
Something like
return new ResponseEntity<>(Result.ok(books));
In this case, the default status code is 200.
But if you wanted to use
return Result.forbidden();
you would use
return new ResponseEntity<>(Result.forbidden(), HttpStatus.FORBIDDEN);
Spring will use the same HttpMessageConverter to convert your Result into JSON, but here you will have more control over the HTTP response.
I think it is necessary to
replace the default RequestResponseBodyMethodProcessor with you owner
processor
, otherwise the default RequestResponseBodyMethodProcessor will take control of handling return value.
Coming from Struts2 I'm used to declaring #Namespace annotation on super classes (or package-info.java) and inheriting classes would subsequently pick up on the value in the #Namespace annotation of its ancestors and prepend it to the request path for the Action. I am now trying to do something similar in Spring MVC using #RequestMapping annotation as follows (code trimmed for brevity):
package au.test
#RequestMapping(value = "/")
public abstract class AbstractController {
...
}
au.test.user
#RequestMapping(value = "/user")
public abstract class AbstractUserController extends AbstractController {
#RequestMapping(value = "/dashboard")
public String dashboard() {
....
}
}
au.test.user.twitter
#RequestMapping(value = "/twitter")
public abstract class AbstractTwitterController extends AbstractUserController {
...
}
public abstract class TwitterController extends AbstractTwitterController {
#RequestMapping(value = "/updateStatus")
public String updateStatus() {
....
}
}
/ works as expect
/user/dashboard works as expected
However when I would have expected /user/twitter/updateStatus to work it does not and checking the logs I can see a log entry which looks something like:
org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping
- Mapped URL path [/tweeter/updateStatus] onto handler
'twitterController'
Is there a setting I can enable that will scan the superclasses for #RequestMapping annotations and construct the correct path?
Also I take it that defining #RequestMapping on a package in package-info.java is illegal?
The following basically becomes /tweeter/updateStatus and not /user/tweeter/updateStatus
public abstract class TwitterController extends AbstractTwitterController {
#RequestMapping(value = "/updateStatus")
public String updateStatus() {
....
}
}
That's the expected behavior since you've overriden the original #RequestMapping you've declared in the AbstractController and AbstractUserController.
In fact when you declared that AbstractUserController it also overriden the #RequestMapping for AbstractController. It just gives you the illusion that the / from the AbstractController has been inherited.
"Is there a setting I can enable that will scan the superclasses for #RequestMapping annotations and construct the correct path?" Not that I know of.
According to the technique explained in Modifying #RequestMappings on startup,
yes, it's possible to construct a URL pattern from superclasses in a way you want.
In essence, you have to subclass RequestMappingHandlerMapping (most likely, it will be your HandlerMapping implementation, but please check first)
and override protected getMappingForMethod method.
Once this renders to be feasible, you are in full control of URL pattern generation.
From the example you gave it's not completely clear the exact merging policy, for example, what path you want to have if
a superclass AbstractTwitterController also implements updateStatus() method with its own #RequestMapping, or how would you like to concatenate the URL patterns across the hierarchy, top-down or bottom-up, (I assumed the former below),
but, hopefully, the following snippet will give you some ideas :
private static class PathTweakingRequestMappingHandlerMapping extends RequestMappingHandlerMapping {
#Override
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
RequestMappingInfo methodMapping = super.getMappingForMethod(method, handlerType);
if (methodMapping == null)
return null;
List<String> superclassUrlPatterns = new ArrayList<String>();
boolean springPath = false;
for (Class<?> clazz = handlerType; clazz != Object.class; clazz = clazz.getSuperclass())
if (clazz.isAnnotationPresent(RequestMapping.class))
if (springPath)
superclassUrlPatterns.add(clazz.getAnnotation(RequestMapping.class).value()[0]);// TODO handle other elements in the array if necessary
else
springPath = true;
if (!superclassUrlPatterns.isEmpty()) {
RequestMappingInfo superclassRequestMappingInfo = new RequestMappingInfo("",
new PatternsRequestCondition(String.join("", superclassUrlPatterns)), null, null, null, null, null, null);// TODO implement specific method, consumes, produces, etc depending on your merging policies
return superclassRequestMappingInfo.combine(methodMapping);
} else
return methodMapping;
}
}
Another good question is how to intercept the instantiation of RequestMappingHandlerMapping. In the Internet there are quite a number of various examples for various configuration strategies.
With JavaConfig, however, remember that if you provide WebMvcConfigurationSupport in your #Configuration set, then your #EnableWebMvc(explicit or implicit) will stop to work. I ended up with the following:
#Configuration
public class WebConfig extends DelegatingWebMvcConfiguration{
#Configuration
public static class UnconditionalWebMvcAutoConfiguration extends WebMvcAutoConfiguration {//forces #EnableWebMvc
}
#Override
protected RequestMappingHandlerMapping createRequestMappingHandlerMapping() {
return new PathTweakingRequestMappingHandlerMapping();
}
#Bean
#Primary
#Override
public RequestMappingHandlerMapping requestMappingHandlerMapping() {
return super.requestMappingHandlerMapping();
}
}
but would like to learn about better ways.