In Spring MVC, I can do this to get a value of items on the query string:
public void sendMessage(HttpServletResponse response,
#RequestParam("Session Id") String sessionId,
But how to I get the complete querystring as one long string? I.e. I don't want individual parameters from it, I want the whole thing?
Many thanks!
Add the HttpServletRequest as argument to the method, and get the query string from the request:
public void sendMessage(HttpServletRequest request,
HttpServletResponse response {
String queryString = request.getQueryString();
}
If you don't want to use HttpServletRequest in your controller, you can create HandlerMethodArgumentResolver that resolves query string.
Example:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.PARAMETER)
public #interface QueryString {
}
public class QueryStringResolver implements HandlerMethodArgumentResolver {
#Override
public boolean supportsParameter(MethodParameter parameter) {
Annotation[] parameterAnnotations = parameter.getParameterAnnotations();
for (Annotation parameterAnnotation : parameterAnnotations) {
if (QueryString.class.isInstance(parameterAnnotation)) {
return true;
}
}
return false;
}
#Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
return request.getQueryString();
}
}
<mvc:annotation-driven>
<mvc:argument-resolvers>
<bean class="mypackage.QueryStringResolver"/>
</mvc:argument-resolvers>
</mvc:annotation-driven>
public class MyController {
#RequestMapping(...)
public String someMethod(#QueryString String queryString) {
...
}
}
Something like this you need to do:
public void sendMessage(HttpServletResponse response,
#RequestParam("Session Id") String sessionId, HttpServletRequest request,..
{
String qString= request.getQueryString();
The Controller itself knows the contents of the entire query string.
public ModelAndView handleRequest(HttpServletRequest request,
HttpServletResponse response) throws Exception
You can then get the full query string from:
request.getQueryString();
Related
In my Spring Boot application (2.5.5) I get a large JSON body in the POST request to a specific endpoint. On that request I need to get both the parsed object and that whole object as a string to do some validation. The JSON object contains a lot of information that I don't need so that is not included in the Object so I can't convert it to a string.
#RestController
#RequestMapping("/example")
public class ExampleController {
#PostMapping("")
public void example(
#RequestBody String stringBody,
#RequestBody ExampleRequest exampleRequest
) {
// Validate request with 'stringBody'
// Do things with 'exampleRequest'
}
}
The best idea I had so far is to just use #RequestBody String stringBody and then convert that string to a JSON object but that is really not the ideal solution.
I know that you can't have two #RequestBody but I really need to somehow have both.
I believe that a custom HandlerMethodArgumentResolver is your best option.
For that I suggest you create a custom annotation as follows:
#Target({ElementType.PARAMETER})
#Retention(RetentionPolicy.RUNTIME)
public #interface ValidJsonSignature { }
Now you need to implement the custom HandlerMethodArgumentResolver:
public class JsonSignatureValidationArgumentResolver implements HandlerMethodArgumentResolver {
private final ObjectMapper objectMapper;
public JsonSignatureValidationArgumentResolver(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
#Override
public boolean supportsParameter(MethodParameter methodParameter) {
return methodParameter.getParameterAnnotation(ValidJsonSignature.class) != null;
}
#Override
public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception {
HttpServletRequest httpServletRequest = nativeWebRequest.getNativeRequest(HttpServletRequest.class);
String jsonPayload = StreamUtils.copyToString(httpServletRequest.getInputStream(), StandardCharsets.UTF_8);
// Do actual validation here
if (// Valid) {
// If valid, then convert String to method parameter type and return it
return objectMapper.treeToValue(objectMapper.readTree(jsonPayload), methodParameter.getParameterType());
} else {
// Throw exception if validation failed
}
}
}
Next, you need to register JsonSignatureValidationArgumentResolver as an argument resolver:
#Configuration
public class JsonSignatureValidationConfiguraion implements WebMvcConfigurer {
#Autowired
private ObjectMapper objectMapper;
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(new JsonSignatureValidationArgumentResolver(objectMapper));
}
}
Last but not the least, you need to annotate the Controller attribute with #ValidJsonSignature as follows:
#RestController
#RequestMapping("/example")
public class ExampleController {
#PostMapping("")
public void example(#ValidJsonSignature ExampleRequest exampleRequest) {
}
}
I have a Java Spring controller.
I want to escape all quotes in my request (sanitize it for using it in SQL queries for example).
Is there a way to do that with Spring ?
Example :
#RequestMapping(method = RequestMethod.POST)
public List<String[]> myEndpoint(#RequestBody Map<String, String> params, #AuthenticationPrincipal Account connectedUser) throws Exception{
return myService.runQuery(params, connectedUser);
}
If you want to validate all your request parameters in controllers, you can use custom validators. For Complete info, check Complete Example
Brief Overview:
Validator Implementation
#Component
public class YourValidator implements Validator {
#Override
public boolean supports(Class<?> clazz) {
return clazz.isAssignableFrom(YourPojoType.class);
}
#Override
public void validate(Object target, Errors errors) {
if (target instanceof YourPojoType) {
YourPojoType req = (YourPojoType) target;
Map<String, String> params = req.getParams();
//Do your validations.
//if any validation failed,
errors.rejectValue("yourFieldName", "YourCustomErrorCode", "YourCustomErrorMessage");
}
}
}
Controller
#RestController
public class YourController{
#Autowired
private YourValidator validator;
#RequestMapping(method = RequestMethod.POST)
public List<String[]> myEndpoint(#Valid YourPojoType req, BindingResult result, #AuthenticationPrincipal Account connectedUser) throws Exception{
if (result.hasErrors()) {
//throw exception
}
return myService.runQuery(params, connectedUser);
}
#InitBinder
private void initBinder(WebDataBinder binder) {
binder.setValidator(validator);
}
}
I have some spring #RestControllers methods that I would like to inject with a value that comes with every request as a request attribute(containing the user) something like:
#RestController
#RequestMapping("/api/jobs")
public class JobsController {
// Option 1 get user from request attribute as prop somehow
private String userId = "user1";
// Option 2 inject into method using aspect or something else
#RequestMapping(value = "", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<List<Jobs>> getJobs() throws ResourceNotFoundException {
// currentUser is injected
this.getJobs(currentUser);
}
I know I can do that:
#RestController
#RequestMapping("/api/jobs")
public class JobsController {
#RequestMapping(value = "", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<List<Jobs>> getJobs(HttpServletRequest request) throws ResourceNotFoundException {
String currentUser = null;
if (request.getAttribute("subject") != null) {
currentUser = request.getAttribute("subject").toString();
}
this.getJobs(currentUser);
}
But that would require me to add this code at every method in my program, which seems to me, to be a really bad practice.
Is there a way to achieve what I want?
If the answer do require aspect, a code example will be much appreciated since I only read about it, but never actually did something with aspect.
Update
The code i suggested can be simplified using this:
#RestController
#RequestMapping("/api/jobs")
public class JobsController {
#RequestMapping(value = "", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<List<Jobs>> getJobs(#Value("#{request.getAttribute('subject')}" String currentUser) throws ResourceNotFoundException {
this.getJobs(currentUser);
}
But still require me to add that parameter at every method.
Can this parameter be injected to every method somehow?
You could use a Filter to populate a ThreadLocal<String> variable that stores that attribute:
public class MyFilter implements Filter {
#Override
public void init(FilterConfig filterConfig) throws ServletException {}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
ContextHolder.setSubject(request.getAttribute('subject'));
chain.doFilter(request, response);
}
#Override
public void destroy() {
ContextHolder.removeSubject();
}
}
public class ContextHolder {
private static final ThreadLocal<String> SUBJECT = new ThreadLocal<String>() {
#Override
protected String initialValue() {
return "empty";
}
};
public static void setSubject(String subject) {
SUBJECT.set(subject);
}
public static String getSubject() {
return SUBJECT.get();
}
public static void removeSubject() {
SUBJECT.remove();
}
}
The filter will be configured to intercept all requests and populate the SUBJECT variable. By using a ThreadLocal, you make sure that each thread has it's own subject value. You can now get that value anywhere in your application by calling ContextHolder.getSubject():
#RequestMapping(value = "", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<List<Jobs>> getJobs(HttpServletRequest request) throws ResourceNotFoundException {
this.getJobs(ContextHolder.getSubject());
}
You will also have to register the Filter in the web.xml file:
<filter>
<filter-name>MyFilter</filter-name>
<filter-class>com.MyFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>MyFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
In case you had multiple attributes, you could use a ThreadLocal<Map<String, String>> variable instead.
Simply just add #ResuestAttribute in your rest contorller
#RestController
#RequestMapping(path="/yourpath")
#RequestMapping(method = RequestMethod.GET)
public ResponseEntity getAll(
#RequestAttribute(value = "yourAttribute") Object
yourAttribute......
If you really want to know about attributes then you should check out spring's #RequestParam annotation. You'd use it like this:
#RequestMapping(value = "", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<List<Jobs>> getJobs(#RequestParam("subject") String currentUser) throws ResourceNotFoundException {
this.getJobs(currentUser);
}
I've got multiple controllers for RESTful endpoints which currently return null if there's no resource at the endpoint. For instance,
#RequestMapping(method = ReqeustMethod.GET, value = "{id}")
#ResponseBody
public MyResource get(#PathVariable final Long id) {
return this.myService.get(id); // returns null if bad id
}
I want to return a specific, different resource to the client (ErrorResource) when there's no MyResource with the given id. I know I can do that with a separate method with #ExceptionHandler, such as:
#RequestMapping(method = RequestMethod.GET, value = "{id}")
#ResponseBody
public MyResource get(#PathVariable final Long id) {
final MyResource myResource = this.myService.get(id);
if (myResource == null) {
throw new NotFoundException();
}
return myResource;
}
#ExceptionHandler(NotFoundException.class)
#ResponseStatus(value = HttpStatus.NOT_FOUND)
#ResponseBody
public ErrorResource notFoundException(
final HttpServletRequest request,
final NotFoundException exception) {
final ErrorResource errorResource = new ErrorResource();
errorResource.setStatus(HttpStatus.NOT_FOUND.value());
errorResource.setDeveloperMessage("No resource found at " + request.getRequestURL());
return errorResource;
}
And that's nice. But what I'd really like to be able to do is have some kind of interceptor that figures out for me that whenever an API method is returning a null #ResponseBody, it should instead run the logic in my notFoundException() method. That will make all my controller methods a little cleaner. Is there any way to do that?
It sounds like a job for Spring's HttpMessageConverter.
You can write your own converter by implementing HttpMessageConverter<T> interface.
In your case I would implement a converter of HttpMessageConverter<MyResource> with a null check on the MyResource instance in the write method. If the MyResource instance is null, then build and write your ErrorResource instance.
Here is an example:
import java.io.IOException;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
public class MyResourceConverter implements HttpMessageConverter<MyResource> {
// a real message converter that will respond to ancillary methods and do the actual work
private HttpMessageConverter<Object> delegateConverter;
public MyResourceConverter(HttpMessageConverter<Object> delegateConverter){
this.delegateConverter = delegateConverter;
}
#Override
public boolean canRead(Class<?> clazz, MediaType mediaType) {
return delegateConverter.canRead(clazz, mediaType) && MyResource.class.equals(clazz);
}
#Override
public boolean canWrite(Class<?> clazz, MediaType mediaType) {
return delegateConverter.canWrite(clazz, mediaType) && MyResource.class.equals(clazz);
}
#Override
public MyResource read(Class<? extends MyResource> clazz,
HttpInputMessage inputMessage) throws IOException,
HttpMessageNotReadableException {
return (MyResource) delegateConverter.read(clazz, inputMessage);
}
#Override
public void write(MyResource t, MediaType contentType,
HttpOutputMessage outputMessage) throws IOException,
HttpMessageNotWritableException {
Object result = null;
if(t == null){
result = // build your ErrorResource here
}else{
result = t;
}
delegateConverter.write(result, contentType, outputMessage);
}
}
Note that this converter needs to be registered in your Spring configuration.
The configuration class must extend WebMvcConfigurerAdapter and override the configureMessageConverters method, like:
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
// Here we add our custom-configured HttpMessageConverters.
// yourDelegateConverter may be a MappingJackson2HttpMessageConverter instance for example
converters.add(new EmployeeConverter(yourDelegateConverter));
super.configureMessageConverters(converters);
}
References (from official Spring documentation):
HTTP Message conversion
HttpMessageConverter
WebMvcConfigurerAdapter
This is my controller class:
#Component
#RequestMapping("/test")
public class MyController
{
#RequestMapping(value = {"test"}, method = RequestMethod.GET)
#ResponseBody
public String test(#MyAnnotation String myValue)
{
return "myValue:"+myValue;
}
}
Moreover, below is my interceptor class:
public class MyInterceptor implements HandlerInterceptor
{
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception
{
MyAnnotation annotation = ((HandlerMethod)handler).getMethod().getAnnotation(MyAnnotation.class);
// TODO
}
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception
{
}
#Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception e) throws Exception
{
}
}
if I want myValue to be "test", then how to implement the custom annotation #MyAnnotation? what to do in class MyInterceptor