I want to do a custom validations using spring boot for below method.I want to validate "id" if it is other than a to z, 0 to 9 and - then I want to set error message and pass that in ResponseEntity.
#RestController
public class DataController {
#Autowired DataService dataService;
#RequestMapping("/data/{id}")
public ResponseEntity<Data> getData(#PathVariable String id)
{
Data messages = dataService.getData(id, Data.DataFormat.ALL);
return new ResponseEntity<>(messages, HttpStatus.OK);
}
You can't validate a single primitive type in parameter automatically.
You have to validate it manually.
Try this:
private static final Pattern ACCEPTED_CHARACTERS = Pattern.compile("^[a-z0-9\\-]+$");
#RequestMapping("/data/{id}")
public ResponseEntity<Data> getData(#PathVariable String id)
{
if (!ACCEPTED_CHARACTERS.matcher(id).matches()) {
return new ResponseEntity<>("Your messge", YOUR CODE);
}
Data messages = dataService.getData(id, Data.DataFormat.ALL);
return new ResponseEntity<>(messages, HttpStatus.OK);
}
In real app, the test for pattern should be done in an utility class
Another solution: You can use #Pattern and don't forget to use #Validated before your class.
#RestController
#RequestMapping("required url")
#Validated
public class yourClassName{
#RequestMapping("/data/{id}")
public ResponseEntity<Data> getData(#Valid #Pattern(regexp = "^[a-z0-9\\-]+$",message = "Your custom message") #PathVariable String id){
Data messages = dataService.getData(id, Data.DataFormat.ALL);
return new ResponseEntity<>(messages, HttpStatus.OK);
}
}
Related
Right now I use
#PreAuthorize("hasAuthority('CREATE_USER_PRIVILEGE')")
But I want the CREATE_USER_PRIVILEGE to come from a function(). Is this possible?
You could do something like this:
#RestController
class FooController {
#PreAuthorize("hasAuthority(#securityService.privilege)")
#GetMapping("/")
public ResponseEntity<String> helloSecurity(#RequestParam("id") Integer id){
return ResponseEntity.ok("Hello World");
}
}
#Service("securityService")
class SecurityService {
public String getPrivilege(){
return "CREATE_USER_PRIVILEGE";
}
}
Based on this great article
you have first to autowire your service using constructor or annotation then you can use the Spel language to use it as stated in the following example
#RequestMapping(value="/id/{domainObjectId}/dostuff", method=RequestMethod.POST, produces="application/json")
#PreAuthorize(value="hasRole('ROLE_DomainObjectAdmin') or #domainObjectServiceImpl.findDomainObject(#domainObjectId).getOwners().contains(#userAccount.getEmployee())")
public String setObjectiveComplete(#PathVariable String domainObjectId, UserAccount userAccount) {
// Do stuff
}
Based on the above solution, I've implemented something like this:
#Controller
class TestController {
//calling a PreAuthorize on method level/ can be used on class level as well
#PreAuthorize("hasAnyAuthority(#authorityService.authorities)")
#RequestMapping("/application")
public ModelAndView newPage() throws{
return new ModelAndView(view);
}
}
#Service("authorityService")
class AuthorityService{
#Value("${app.authorities}") // read roles from properties file
private String authorities;
public List<String> getAuthorities(){
// convert the comma separated Strings to list.
List<String> items = Arrays.asList(authorities.split("\\s*,\\s*"));
return items;
}
}
I am learning spring boot, and i developed the below simple example. I would like to annotate a class as Controller using #Controller. this class has constructor and I want to have access to GreetingFromDeuController as shown:
http://localhost:8080:/GreetingFromDeuController?str = "hi"
the error i am receiving is
#RequestMapping is not applicable on a constructor
please let me know how to solve.
code:
#Controller
#RequestMapping("/GreetingFromDeuController")
public class GreetingFromDeuController {
private String str;
#RequestMapping("/GreetingFrom/deu")
GreetingFromDeuController(#RequestParam(value = "str") String str) {
this.str = str;
}
#RequestMapping("/GreetingFromDeuController")
public String getGreetingFromDeu() {
return this.str;
}
}
First of all your constructor gets initialize much before you hit your URL. So you need to work on your design or tell me your business requirement and I will try to provide you a solution. My refactor code solution will help you to achieve that in two steps. First hit POST method which will do work on setting variable and then subsequent hits of GET method will return that set value.
We can refactor code like below. It will explain use of RequestMapping on method and class.
Considering we have to write two API, one for reading and one for writing.
URLS :
1. POST http://localhost:8080/example/greetings (in request body send {str:'hi'})
2. GET http://localhost:8080/example/greetings
#Controller
#RequestMapping("/example")
public class GreetingFromDeuController {
private String str;
#RequestMapping(value="/greetings" , method = RequestMethod.POST)
public void setGreetingFromDeu(#RequestBody(value = "str") String str)
{
this.str = str;
}
#RequestMapping(value="/greetings" , method = RequestMethod.GET)
public String getGreetingFromDeu()
{
return this.str;
}
}
The #RequestMapping documentation says:
Annotation for mapping web requests onto methods in request-handling
classes with flexible method signatures.
Then you can not do that, if you want to initialize your variables or whatever you can use several ways:
1.- Use #PostConstruct
#PostContruct
public void init() {
this.str = "Anything";
}
2.- Use a simple request to set anything only
#RequestMapping(value="/refresh/anythings", method = RequestMethod.PUT)
public void refresh(#RequestBody(value = "str") String str) {
this.str = str;
}
3.- Use #Value
In application.properties / application.yaml
properties.str = anything
In the Controller
#Value("${properties.str:default}") // by default str is "default"
public String str;
#RequestMapping(value="/greetings" , method = RequestMethod.GET)
public String getGreetingFromDeu() {
return this.str;
}
As far I am concerned, #RequestMapping is not meant for constructors. It should be used for annotating methods or classes. Methods that are responsible for handling requests.
#RequestMapping should be used to map request with endPoint. which can be used as class level and method level.
You can use #RestController (improved from #Controller see difference).
The ideal flow for Spring Boot is Controller -> Service -> Repository
Controller -> maps request with endPoint and return response
Service -> perform business logic
Repository -> Handle database operation
Example
#RestController
#RequestMapping("/api")
public class GreetingController {
#Autowired GreetinService greetingService;
// Request http://localhost:8080/api/GreetingFrom
#GetMapping("/GreetingFrom")
public ResponseEntity<String> GreetingRequestParam(#RequestParam(value = "name") String name) {
greetingService.performBusinessLogic(name);
return new ResponseEntity<String>("Greetings from "+name,HttpStatus.OK);
}
// Request http://localhost:8080/api/GreetingFrom/user2121
#GetMapping("/GreetingFrom/{name}")
public ResponseEntity<String> GreetingPathVariable(#PathVariable(value = "name") String name) {
return new ResponseEntity<String>("Greetings from "+name,HttpStatus.OK);
}
}
I have a rest controller with one method. This method takes one String argument annotated as #RequestBody. For some reason not mentioned here, I'm forced to use type String and manually convert it to TestDTO. From the API's consumer point of view body is type of TestDTO and I want to show this type in SwaggerUI.
Unfortunately (which is quite obvious) swagger shows that body is type of String. Look at the picture below.
What I want to achieve is to have String body in java code and TestDTO in swagger code. How can I force Swagger to show it? I tried to find annotations and its properties, but failed.
Rest controller code below:
#RestController
#Api(tags = { "test" }, description = "test related resources")
public class TestController {
#Autowired
ObjectMapper mapper;
#RequestMapping(path = "/test", method = RequestMethod.POST)
public void confirm(#RequestBody String requestBody) throws IOException {
//do sth with body
TestDTO dto = mapper.readValue(requestBody, TestDTO.class);
//do sth with dto
}
}
class TestDTO{
private String value;
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
I figured it out. Two changes need to be made.
First, like in #Dave Pateral's answer #ApiImplicitParams must be added
#RestController
#Api(tags = { "test" }, description = "test related resources")
public class TestController {
#Autowired
ObjectMapper mapper;
#ApiImplicitParams({
#ApiImplicitParam(name = "requestBody", required = true,
dataType = "TestDTO", paramType = "body")
})
#RequestMapping(path = "/test", method = RequestMethod.POST)
public void confirm(#RequestBody String requestBody) throws IOException {
//do sth with body
TestDTO dto = mapper.readValue(requestBody, TestDTO.class);
//do sth with dto
}
}
And then implicit Model must be registered in the docket, minimal working example below
#Configuration
public class SwaggerConfiguration {
#Autowired
private TypeResolver typeResolver;
#Bean
public Docket docket() {
return new Docket(DocumentationType.SWAGGER_2)
.additionalModels(typeResolver.resolve(TestDTO.class));
}
}
And the result is
Try put this annotation on your method:
#ApiImplicitParam(name = "test", value = "testDTO", required = true, dataType = "TestDTO")
I'm trying to get UserDetails object like below. But, I have some difficulties and impossible to get UserDetails object, so there is only JSONObject in authentication.getAttributes(). Is there any alternative way in micronaut to get UserDetails object?
Custom UserDetails object:
public class MyUserPrincipal implements UserDetails {
private Account account;
public MyUserPrincipal(Account account) {
this.account = account;
}
public Account getAccount() {
return getAccount();
}
}
Rest api:
//micronaut
#Post(value = "/echo")
#Status(HttpStatus.OK)
public Long echo(#Nullable Authentication authentication) {
Long accountId = (Long)((JSONObject)authentication.getAttributes().get("account")).get("id");
return accountId;
}
For example in Spring Security it is easy with #AuthenticationPrincipal annotation in parameter.
Rest api:
#GET
public ResponseEntity<?> echo(#AuthenticationPrincipal MyUserPrincipal user) {
return new ResponseEntity<>(user.getAccount().getAccountId(), HttpStatus.OK);
}
If you are still looking for a solution, here is what works.
You have to provide an implementation of JwtAuthenticationFactory and replace default DefaultJwtAuthenticationFactory.
Something like this (code below is in Kotlin):
#Singleton
#Replaces(bean = DefaultJwtAuthenticationFactory::class)
class CustomJwtAuthenticationFactory() : JwtAuthenticationFactory {
override fun createAuthentication(token: JWT?): Optional<Authentication> {
try {
val builder = JWTClaimsSet.Builder()
builder.claim("username", token?.jwtClaimsSet?.getStringClaim("username"))
return Optional.of(AuthenticationJWTClaimsSetAdapter(jwtClaims))
} catch (e: Exception) {
throw RuntimeException("ParseException creating authentication", e)
}
}
}
All claims added using the builder will get added in the Authentication object and can be accessed in any controller eg:
#Get("/hello-world")
fun hello(authentication: Authentication): String =
authentication["username"] as String
If you are using Kotlin, use could also add extension methods on Authentication method to fetch attributes that you add to Authentication class eg:
fun Authentication.username(): String = this.attributes["username"]
Note: username is just an example. It is available as name instance variable on instance of Authentication.
UserDetails does not exist after authentication. The only object available is the Authentication. If you want to standardize the code you have above you could create a bean that handles injection of that specific property.
You could use an annotation to designate the injection by creating an annotation along with an implementation of AnnotatedRequestArgumentBinder. Something like the following:
public class Temp implements AnnotatedRequestArgumentBinder<YourAnnotation, Long> {
#Override
public Class<YourAnnotation> getAnnotationType() {
return YourAnnotation.class;
}
#Override
public BindingResult<Long> bind(ArgumentConversionContext<Long> context, HttpRequest<?> source) {
if (source.getAttributes().contains(OncePerRequestHttpServerFilter.getKey(SecurityFilter.class))) {
final Optional<Authentication> authentication = source.getUserPrincipal(Authentication.class);
if (authentication.isPresent()) {
return () -> (Long)((JSONObject)authentication.getAttributes().get("account")).get("id");
}
}
return ArgumentBinder.BindingResult.EMPTY;
}
}
Suppose i have a page that lists the objects on a table and i need to put a form to filter the table. The filter is sent as an Ajax GET to an URL like that: http://foo.com/system/controller/action?page=1&prop1=x&prop2=y&prop3=z
And instead of having lots of parameters on my Controller like:
#RequestMapping(value = "/action")
public #ResponseBody List<MyObject> myAction(
#RequestParam(value = "page", required = false) int page,
#RequestParam(value = "prop1", required = false) String prop1,
#RequestParam(value = "prop2", required = false) String prop2,
#RequestParam(value = "prop3", required = false) String prop3) { ... }
And supposing i have MyObject as:
public class MyObject {
private String prop1;
private String prop2;
private String prop3;
//Getters and setters
...
}
I wanna do something like:
#RequestMapping(value = "/action")
public #ResponseBody List<MyObject> myAction(
#RequestParam(value = "page", required = false) int page,
#RequestParam(value = "myObject", required = false) MyObject myObject,) { ... }
Is it possible?
How can i do that?
You can absolutely do that, just remove the #RequestParam annotation, Spring will cleanly bind your request parameters to your class instance:
public #ResponseBody List<MyObject> myAction(
#RequestParam(value = "page", required = false) int page,
MyObject myObject)
I will add some short example from me.
The DTO class:
public class SearchDTO {
private Long id[];
public Long[] getId() {
return id;
}
public void setId(Long[] id) {
this.id = id;
}
// reflection toString from apache commons
#Override
public String toString() {
return ReflectionToStringBuilder.toString(this, ToStringStyle.SHORT_PREFIX_STYLE);
}
}
Request mapping inside controller class:
#RequestMapping(value="/handle", method=RequestMethod.GET)
#ResponseBody
public String handleRequest(SearchDTO search) {
LOG.info("criteria: {}", search);
return "OK";
}
Query:
http://localhost:8080/app/handle?id=353,234
Result:
[http-apr-8080-exec-7] INFO c.g.g.r.f.w.ExampleController.handleRequest:59 - criteria: SearchDTO[id={353,234}]
I hope it helps :)
UPDATE / KOTLIN
Because currently I'm working a lot of with Kotlin if someone wants to define similar DTO the class in Kotlin should have the following form:
class SearchDTO {
var id: Array<Long>? = arrayOf()
override fun toString(): String {
// to string implementation
}
}
With the data class like this one:
data class SearchDTO(var id: Array<Long> = arrayOf())
the Spring (tested in Boot) returns the following error for request mentioned in answer:
"Failed to convert value of type 'java.lang.String[]' to required type
'java.lang.Long[]'; nested exception is
java.lang.NumberFormatException: For input string: \"353,234\""
The data class will work only for the following request params form:
http://localhost:8080/handle?id=353&id=234
Be aware of this!
Since the question on how to set fields mandatory pops up under each post, I wrote a small example on how to set fields as required:
public class ExampleDTO {
#NotNull
private String mandatoryParam;
private String optionalParam;
#DateTimeFormat(iso = ISO.DATE) //accept Dates only in YYYY-MM-DD
#NotNull
private LocalDate testDate;
public String getMandatoryParam() {
return mandatoryParam;
}
public void setMandatoryParam(String mandatoryParam) {
this.mandatoryParam = mandatoryParam;
}
public String getOptionalParam() {
return optionalParam;
}
public void setOptionalParam(String optionalParam) {
this.optionalParam = optionalParam;
}
public LocalDate getTestDate() {
return testDate;
}
public void setTestDate(LocalDate testDate) {
this.testDate = testDate;
}
}
//Add this to your rest controller class
#RequestMapping(value = "/test", method = RequestMethod.GET)
public String testComplexObject (#Valid ExampleDTO e){
System.out.println(e.getMandatoryParam() + " " + e.getTestDate());
return "Does this work?";
}
I have a very similar problem. Actually the problem is deeper as I thought. I am using jquery $.post which uses Content-Type:application/x-www-form-urlencoded; charset=UTF-8 as default. Unfortunately I based my system on that and when I needed a complex object as a #RequestParam I couldn't just make it happen.
In my case I am trying to send user preferences with something like;
$.post("/updatePreferences",
{id: 'pr', preferences: p},
function (response) {
...
On client side the actual raw data sent to the server is;
...
id=pr&preferences%5BuserId%5D=1005012365&preferences%5Baudio%5D=false&preferences%5Btooltip%5D=true&preferences%5Blanguage%5D=en
...
parsed as;
id:pr
preferences[userId]:1005012365
preferences[audio]:false
preferences[tooltip]:true
preferences[language]:en
and the server side is;
#RequestMapping(value = "/updatePreferences")
public
#ResponseBody
Object updatePreferences(#RequestParam("id") String id, #RequestParam("preferences") UserPreferences preferences) {
...
return someService.call(preferences);
...
}
I tried #ModelAttribute, added setter/getters, constructors with all possibilities to UserPreferences but no chance as it recognized the sent data as 5 parameters but in fact the mapped method has only 2 parameters. I also tried Biju's solution however what happens is that, spring creates an UserPreferences object with default constructor and doesn't fill in the data.
I solved the problem by sending JSon string of the preferences from the client side and handle it as if it is a String on the server side;
client:
$.post("/updatePreferences",
{id: 'pr', preferences: JSON.stringify(p)},
function (response) {
...
server:
#RequestMapping(value = "/updatePreferences")
public
#ResponseBody
Object updatePreferences(#RequestParam("id") String id, #RequestParam("preferences") String preferencesJSon) {
String ret = null;
ObjectMapper mapper = new ObjectMapper();
try {
UserPreferences userPreferences = mapper.readValue(preferencesJSon, UserPreferences.class);
return someService.call(userPreferences);
} catch (IOException e) {
e.printStackTrace();
}
}
to brief, I did the conversion manually inside the REST method. In my opinion the reason why spring doesn't recognize the sent data is the content-type.
While answers that refer to #ModelAttribute, #RequestParam, #PathParam and the likes are valid, there is a small gotcha I ran into. The resulting method parameter is a proxy that Spring wraps around your DTO. So, if you attempt to use it in a context that requires your own custom type, you may get some unexpected results.
The following will not work:
#GetMapping(produces = APPLICATION_JSON_VALUE)
public ResponseEntity<CustomDto> request(#ModelAttribute CustomDto dto) {
return ResponseEntity.ok(dto);
}
In my case, attempting to use it in Jackson binding resulted in a com.fasterxml.jackson.databind.exc.InvalidDefinitionException.
You will need to create a new object from the dto.
Yes, You can do it in a simple way. See below code of lines.
URL - http://localhost:8080/get/request/multiple/param/by/map?name='abc' & id='123'
#GetMapping(path = "/get/request/header/by/map")
public ResponseEntity<String> getRequestParamInMap(#RequestParam Map<String,String> map){
// Do your business here
return new ResponseEntity<String>(map.toString(),HttpStatus.OK);
}
Accepted answer works like a charm but if the object has a list of objects it won't work as expected so here is my solution after some digging.
Following this thread advice, here is how I've done.
Frontend: stringify your object than encode it in base64 for submission.
Backend: decode base64 string then convert the string json into desired object.
It isn't the best for debugging your API with postman but it is working as expected for me.
Original object: { page: 1, size: 5, filters: [{ field: "id", value: 1, comparison: "EQ" }
Encoded object: eyJwYWdlIjoxLCJzaXplIjo1LCJmaWx0ZXJzIjpbeyJmaWVsZCI6ImlkUGFyZW50IiwiY29tcGFyaXNvbiI6Ik5VTEwifV19
#GetMapping
fun list(#RequestParam search: String?): ResponseEntity<ListDTO> {
val filter: SearchFilterDTO = decodeSearchFieldDTO(search)
...
}
private fun decodeSearchFieldDTO(search: String?): SearchFilterDTO {
if (search.isNullOrEmpty()) return SearchFilterDTO()
return Gson().fromJson(String(Base64.getDecoder().decode(search)), SearchFilterDTO::class.java)
}
And here the SearchFilterDTO and FilterDTO
class SearchFilterDTO(
var currentPage: Int = 1,
var pageSize: Int = 10,
var sort: Sort? = null,
var column: String? = null,
var filters: List<FilterDTO> = ArrayList<FilterDTO>(),
var paged: Boolean = true
)
class FilterDTO(
var field: String,
var value: Any,
var comparison: Comparison
)