I am newbie to Spring related technologies.
I choose Spring Data Rest to implement a web annotation server. According to the standard, an annotation should be represented by JSON-LD, which means you can't bind the request to any of your domain object as the field names are changeable. (In C#, it is ok to bind it to dynamic). You just need to convert it to some-defined type before persisting to db.
Before converting it, I want to validate the request body.
I use:
#Service
public class AnnotationValidator implements Validator{
#Autowired
private Processor ldProcessor;
#Override
public boolean supports(Class<?> aClass) {
return AnnotationDocument.class.equals(aClass);
}
#Override
public void validate(Object o, Errors errors) {
Object processedAnnotation;
try {
processedAnnotation = ldProcessor.extractAnnotationModel(o);
} catch (JsonLdError jsonLdError) {
jsonLdError.printStackTrace();
}
}
}
In validate method, the Object o does not represent the request body. Indeed, it tries to cast the request body to AnnotationDocument, so I cannot validate it.
Finally, my question is:
How can I process the pure request body and check its fields?
I solved my problem by creating #RepositoryRestController but I think it should be simpler way.
Related
In my Spring Boot app, I just use Optional for the first time and after examining several projects and topics, now I am trying to build an approach as shown below:
Repository:
Optional<Employee> findByEmail(String email);
Service:
public Response findByEmail(String email) {
return employeeRepository.findByEmail(email)
// if record is found, I think no need to return status or message
.map(e -> Response.builder().data(e).build())
.orElseGet(() -> Response.builder().status(404)
.data(null).message("Not found!").build());
}
Response:
#Data
#Builder
public class Response {
private int status;
private Object data;
private String message;
}
Controller:
#GetMapping("/employees/{email}")
public ResponseEntity<Response> findByEmail(#PathVariable String email) {
final Response response = employeeService.findByEmail(email);
return ResponseEntity
.status(response.getStatus())
.body(response.getMessage(), response.getData());
// throws "Expected 1 arguments but found 2" error
}
Here is the points that I need to be clarified:
1. Is this a proper approach to use a common response for all the Optional types in a Spring Boot app? If not, how should I change it (I want to return a common response from the Service)?
2. How to fix the throws "Expected 1 arguments but found 2" error in the Controller?
From my comment above - You are mixing concerns. Service is supposed to only care about business logic (e.g. not HTTP Status codes). That's controller's job. Use of Optional is correct, but the Response return type from service layer is not. Also errors like Not Found are automatically handled by a Rest Controller in Spring boot if a resource is not found. If you want to add custom logic and prepare generic responses, include a proper exception handling e.g. #ControllerAdvice (which allows you reuse exceptions for controllers).
As an example, one of the solutions would be to throw NoSuchElementException.
This is illustrative and would apply if you want to handle other such situations (e.g. null pointers, internal server error, authentication errors in a more custom manner) in a generic manner.
public Employee findByEmail(String email) {
return employeeRepository.findByEmail(email) //assuming findByEmail is returning an Optional<Employee>, otherwise - simply use a null check.
.orElseThrow(NoSuchElementException::new)
}
Inside #ControllerAdvice class
#ExceptionHandler(NoSuchElementException.class)
#ResponseBody
#Order(Ordered.HIGHEST_PRECEDENCE)
public final ResponseEntity<APIResponseErrorContainer> handleNotFound(
NoSuchElementException ex) {
// log exception here if you wish to
return new ResponseEntity<>(createCustomResponseBody(), HttpStatus.NOT_FOUND);
}
I have a simple Spring Boot REST service for the IFTTT platform. Each authorized request will contain a header IFTTT-Service-Key with my account's service key and I will use that to either process the request or return a 401 (Unauthorized). However, I only want to do this for select endpoints -- and specifically not for ANY of the Spring actuator endpoints.
I have looked into Spring Security, using filters, using HandlerInterceptors, but none seem to fit what I am trying to do exactly. Spring security seems to come with a lot of extra stuff (especially the default user login), filters don't really seem to match the use case, and the handler interceptor works fine but I would have to code logic in to watch specific URLs and ignore others.
What is the best way to achieve what I am trying to do?
For reference, this is the code I have now:
public class ServiceKeyValidator implements HandlerInterceptor {
private final String myIftttServiceKey;
public ServiceKeyValidator(#Value("${ifttt.service-key}") String myIftttServiceKey) {
this.myIftttServiceKey = myIftttServiceKey;
}
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// TODO will have to put logic in to skip this when actuator endpoints are added
String serviceKeyHeader = request.getHeader("IFTTT-Service-Key");
if (!myIftttServiceKey.equals(serviceKeyHeader)) {
var error = new Error("Incorrect value for IFTTT-Service-Key");
var errorResponse = new ErrorResponse(Collections.singletonList(error));
throw new UnauthorizedException(errorResponse);
}
return HandlerInterceptor.super.preHandle(request, response, handler);
}
}
You need to add filtering for the required endpoints in the place where you register your HandlerInterceptor.
For example:
#EnableWebMvc
#Configuration
public class AppConfig implements WebMvcConfigurer {
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(
new ServiceKeyValidator())
.addPathPatterns("/ifttt/**")
.excludePathPatterns("/actuator/**");
}
}
You can use different URLs path matchers to filter which URL endpoints must be handled by your interceptor and which are not. As the method addPathPatterns returns InterceptorRegistration object that configures this.
In my APIs, there is custom request class for each APIs, I want to write code which gets fields from HttpHeaders from upcoming request and set that set of fields to that particular Request class, so it will do this for all request classes.
I have done this in MVC code, but don't know how to do this for reactive APIs with WebFlux(Library- Project Reactor).
Controller:
public Mono<ResponseEntity<JsonNode>> getData(#RequestHeader HttpHeaders header, GetDataRequest request){
.... // all stuff
}
now some data are coming from header like type, token, comID, etc.
I want to set these fields to Request Class GetDataRequest before further processing of the request as I will need these fields further,
but this request class different for all the requests, so I need common code, which set this to any request class which is passed to it.
Note: not using WebClient here, only Flux and Mono are there.
So basically, get fields from a header which is of type HttpHeaders, set these data to particular request class, but do this in WebFlux Framework, reactive APIS.
Please help anyone.
I would do the following:
define some base class for your requests that would have attributes you want to store headers values in, e.g.:
public class MyAbstractRequest {
private String header1;
private String header2;
// ...
// getters and setters
}
inherit all you request classes from this class, e.g.:
public class GetDataRequest extends MyAbstractRequest {
// GetDataRequest content here
}
create an argumentResolver for all those classes that inherit from MyAbstractRequest. To ensure the behavior is same as for normal request body deserialization use AbstractMessageReaderArgumentResolver as a base class:
public class MyArgumentResolver extends AbstractMessageReaderArgumentResolver {
public MyArgumentResolver(List<HttpMessageReader<?>> messageReaders, ReactiveAdapterRegistry adapterRegistry) {
super(messageReaders, adapterRegistry);
}
#Override
public boolean supportsParameter(MethodParameter parameter) {
return MyAbstractRequest.class.isAssignableFrom(parameter.getParameterType());
}
#Override
public Mono<Object> resolveArgument(MethodParameter parameter, BindingContext bindingContext, ServerWebExchange exchange) {
return readBody(parameter, true, bindingContext, exchange)
.map(o -> {
// your headers extraction logic here ...
((MyAbstractRequest) o).setHeader1(exchange.getRequest().getHeaders().getFirst("header1"));
((MyAbstractRequest) o).setHeader2(exchange.getRequest().getHeaders().getFirst("header2"));
return o;
});
}
}
configure your MyArgumentResolver in the webflux configuration:
#Configuration
public class WebFluxConfiguration implements WebFluxConfigurer {
#Autowired
ApplicationContext applicationContext;
#Override
public void configureArgumentResolvers(ArgumentResolverConfigurer configurer) {
ServerCodecConfigurer serverCodecConfigurer = applicationContext.getBean(ServerCodecConfigurer.class);
ReactiveAdapterRegistry reactiveAdapterRegistry = applicationContext.getBean("webFluxAdapterRegistry", ReactiveAdapterRegistry.class);
configurer.addCustomResolver(new MyArgumentResolver(serverCodecConfigurer.getReaders(), reactiveAdapterRegistry));
}
}
Now your requests should get injected into the controller methods with the configured resolver:
public Mono<ResponseEntity<JsonNode>> getData(GetDataRequest request){
}
I'm using Spring 4.3.7, and I got two form controllers with forms rendering using Spring form taglib in corresponding views. To preserve form data between requests (for rendering invalid forms) I store them in SessionAttributes.
LabCreateController:
#Controller
#RequestMapping(path = "/labs/create")
#SessionAttributes("form")
public class LabCreateController {
#ModelAttribute("form")
public LabCreateForm form() {
return new LabCreateForm();
}
#GetMapping
public String showForm() {
return "lab_create";
}
}
WallController:
#Controller
#RequestMapping(path = "/group/{id}/wall")
#SessionAttributes("form")
public class WallController {
#ModelAttribute("form")
public PostCreateForm form() {
return new PostCreateForm();
}
#GetMapping(path = "/new")
public String newPostGet() {
return "communities_newpost";
}
}
I open /labs/create in browser, everything is fine. Then I open /group/4/wall/new and get a following error:
Invalid property 'text' of bean class
[...LabCreateForm]
i.e it means that attribute form from LabCreateController somehow passed to WallController, though Spring documentation says:
Session attributes as indicated using this annotation correspond to a
specific handler's model attributes.
I believe it means they shouldn't be shared between controllers. Also this answer says that it is so since Spring 3.
Is it a bug or I'm missing something? If not, what is the appropriate way of storing a form inside one controller?
I'm developing REST API with Jersey as JAX-RS implementation.
In every resource I explicitly define expected parameters:
#GET
#Path("/someData")
public Response getSomeData(
#QueryParam("id") final Long id,
#QueryParam("name") final String name) {
...
}
There are a number of fixed parameters, which are common for all resources (e.g. "locale").
Is there any way (I'm ok with introducing Jersey-specific dependencies) I can forbid any parameters that belong neither to method parameters nor to the common parameters?
So for example if user invokes
/api/resource/someData?id=10&locale=en - he gets the data, but if he invokes
/api/resource/someData?id=10&locale=en&fakeParam=AAA - status 400 is returned, with content stating that fakeParam is unknown parameter.
Currently second request is processed the same way as the first one, and fakeParam is simply ignored.
I think described validation will help users of my API to spot bugs earlier.
I don't know of any way to do this with JAX-RS but you could easily roll your own solution. This is a bit cumbersome but you could do something like:
#Path("/api")
public class Service {
#Context
UriInfo uriInfo;
ImmutableSet<String> commonParams = ImmutableSet.of("locale");
#GET
#Path("validate")
#Produces(MediaType.APPLICATION_JSON)
public String validate(#QueryParam("foo") String param) {
Set<String> validParams = newHashSet(commonParams);
class Local {};
for (Annotation[] annotations: Local.class.getEnclosingMethod().getParameterAnnotations()) {
for (Annotation annotation: annotations) {
if (annotation instanceof QueryParam) {
validParams.add(((QueryParam)annotation).value());
}
}
}
if (!difference(uriInfo.getQueryParameters().keySet(), validParams).isEmpty()) {
//throw an unknown parameter exception
}
return "hello";
}
And if you're using Guice or some other AOP tool with Jersey you could probably put this into an aspect s.t. you wouldn't have to add boilerplate to every method you want to validate.