Spring - Springboot controller GET requests with default parameters - spring

I have a Springboot controller with two GET endpoints:
#RestController
#RequestMapping("/foo")
class MyController {
fun getFoo(
#RequestParam("x", required = false) x: Int = 0
) = ...
fun getFoo(
#RequestParam("x", required = true) x: Int,
#RequestParam("y", required = true) y: Int
) = ...
The behaviour I want is that when called:
/foo calls the first endpoint with an optional x param.
/foo?x=123 calls the first endpoint with a supplied x param.
/foo?x=123&y=456' calls the second endpoint with the supplied xandy` params.
Currently I get an error:
{
"timestamp": "2020-07-20T13:11:24.732+0000",
"status": 400,
"error": "Bad Request",
"message": "Parameter conditions \"x\" OR \"x, y\" not met for actual request parameters: ",
"path": "/foo"
}
Any ideas how to determine a default endpoint when zero params are specified?

Set a params in #RequestMapping or its variants(#GetMapping, #PostMapping etc).
eg.
#GetMapping(params=arrayOf("!x", "!y"))
fun getFoo()
#GetMapping(params=arrayOf("x", "!y"))
fun getFoo(
#RequestParam("x", required = true) x: Int = 0
#GetMapping(params=arrayOf("x", "y"))
fun getFoo(
#RequestParam("x", required = true) x: Int,
#RequestParam("y", required = true) y: Int
Different params can be applied and identified on the same URI.

You can specify a defaultValue as a String in #RequestParam:
fun getFoo(
#RequestParam(name = "x", required = false, defaultValue = "0") x: Int,
#RequestParam(name = "y", required = false, defaultValue = "1") y: Int
) =
Spring will convert the String to whatever type you really want (Int in your case) using the same method as if you specified x as a parameter (same coercion, errors, etc).

If you want more then one mapping to the same url (lot of times it is a codesmell, but sometime it is necessary, you can do i twith filter params in #GetMapping or #RequestMapping annotations' params element.
#RestController
#RequestMapping("/foo")
class MyController {
#GetMapping(params = ["!y"])
fun getFoo(
#RequestParam("x", required = false) x: Int?
) = ...
#GetMapping(params = ["x", "y"])
fun getFoo(
#RequestParam("x", required = true) x: Int,
#RequestParam("y", required = true) y: Int
) = ...
}

Related

Exceptions not captured in opencsv parsing

I'm trying to parse a csv file and map it onto a data class. I've setup some validations for the columns and I'm testing it by sending incorrect values for those columns. opencsv throws a generic exception
Basic instantiation of the given bean type (and subordinate beans created through recursion, if applicable) was determined to be impossible.
Code details
Data class:
data class UserInfo(
#CsvBindByName(column = "Id", required = true) val id: Long,
#CsvBindByName(column = "FirstName", required = true) val firstName: String,
#CsvBindByName(column = "LastName", required = true) val lastName: String,
#CsvBindByName(column = "Email", required = true) val email: String,
#CsvBindByName(column = "PhoneNumber", required = true) val phoneNumber: String,
#PreAssignmentValidator(
validator = MustMatchRegexExpression::class, paramString = "^[0-9]{10}$")
#CsvBindByName(column = "Age", required = true)
val age: Int
)
csv parsing logic
fun uploadCsvFile(file: MultipartFile): List<UserInfo> {
throwIfFileEmpty(file)
var fileReader: BufferedReader? = null
try {
fileReader = BufferedReader(InputStreamReader(file.inputStream))
val csvToBean = createCSVToBean(fileReader)
val mappingStrategy: HeaderColumnNameMappingStrategy<Any> =
HeaderColumnNameMappingStrategy<Any>()
mappingStrategy.type = UserInfo::class.java
val userInfos = csvToBean.parse()
userInfos.stream().forEach { user -> println("Parsed data:$user") }
csvToBean.capturedExceptions.stream().forEach { ex -> println(ex.message) }
return userInfos
} catch (ex: Exception) {
throw CsvImportException("Error during csv import")
} finally {
closeFileReader(fileReader)
}
}
private fun createCSVToBean(fileReader: BufferedReader?): CsvToBean<UserInfo> =
CsvToBeanBuilder<UserInfo>(fileReader)
.withType(UserInfo::class.java)
.withThrowExceptions(false)
.withIgnoreLeadingWhiteSpace(true)
.build()
I'm looking for the proper error message for the validation / missing field so that I can communicate it to the error response.

Gradle Spring mvc validation data class recursive Kotlin

I need to create a recursive validation to be able to validate PizzaCreate Object and base attribute in recursive mode using Kotlin. The test should return 400 but it returns 200 ok (Base name size must be greater than 2):
data class Base(#field:Length(min = 2, max = 100) val name:String)
data class PizzaCreate(
val id: Long,
#field:Length(min = 2, max = 100) val name: String,
val description: String,
val price: Int,
#Valid val base: Base
)
#RestController
#RequestMapping("/pizza")
class PizzaController(val pizzaService: PizzaService) {
#PostMapping
fun post(#RequestBody #Valid pizza: PizzaCreate) = pizzaService.addPizza(pizza)
}
#Test
fun `should add pizza `() {
val pizza = easyRandom.nextObject(PizzaCreate::class.java).copy(id = 1, name="aaa", base = Base(""))
val pizzaOut = PizzaOut(id=1,name=pizza.name,description = pizza.description,price = pizza.price)
`when`(pizzaService.addPizza(pizza)).thenReturn(pizzaOut.toMono())
webTestClient.post()
.uri("/pizza")
.bodyValue(pizza)
.exchange()
.expectStatus().isBadRequest
.returnResult<PizzaOut>().responseBody
}
Validation on Base should be #field:Valid val base: Base instead of #Valid val base: Base
field: specifies the annotation is applied on the field not construc
ref:
https://stackoverflow.com/a/35853200
https://stackoverflow.com/a/36521309

Spring boot Neo4j - query depth not working correctly

TL;DR: #Depth(value = -1) throws nullpointer and other values above 1 are ignored
In my Spring Boot with Neo4j project I have 3 simple entities with relationships:
#NodeEntity
data class Metric(
#Id #GeneratedValue val id: Long = -1,
val name: String = "",
val description: String = "",
#Relationship(type = "CALCULATES")
val calculates: MutableSet<Calculable> = mutableSetOf()
) {
fun calculates(calculable: Calculus) = calculates.add(calculable)
fun calculate() = calculates.map { c -> c.calculate() }.sum()
}
interface Calculable {
fun calculate(): Double
}
#NodeEntity
data class Calculus(
#Id #GeneratedValue val id: Long = -1,
val name: String = "",
#Relationship(type = "LEFT")
var left: Calculable? = null,
#Relationship(type = "RIGHT")
var right: Calculable? = null,
var operator: Operator? = null
) : Calculable {
override fun calculate(): Double =
operator!!.apply(left!!.calculate(), right!!.calculate())
}
#NodeEntity
data class Value(
#Id #GeneratedValue val id: Long = -1,
val name: String = "",
var value: Double = 0.0
) : Calculable {
override fun calculate(): Double = value
}
enum class Operator : BinaryOperator<Double>, DoubleBinaryOperator {//not relevant}
I create a simple graph like this one:
With the following repositories:
#Repository
interface MetricRepository : Neo4jRepository<Metric, Long>{
#Depth(value = 2)
fun findByName(name: String): Metric?
}
#Repository
interface CalculusRepository : Neo4jRepository<Calculus, Long>{
fun findByName(name: String): Calculus?
}
#Repository
interface ValueRepository : Neo4jRepository<Value, Long>{
fun findByName(name: String): Value?
}
And the following code:
// calculus
val five = valueRepository.save(Value(
name = "5",
value = 5.0
))
val two = valueRepository.save(Value(
name = "2",
value = 2.0
))
val fiveTimesTwo = calculusRepository.save(Calculus(
name = "5 * 2",
operator = Operator.TIMES,
left = five,
right = two
))
println("---")
println(fiveTimesTwo)
val fromRepository = calculusRepository.findByName("5 * 2")!!
println(fromRepository) // sometimes has different id than fiveTimesTwo
println("5 * 2 = ${fromRepository.calculate()}")
println("--- \n")
// metric
val metric = metricRepository.save(Metric(
name = "Metric 1",
description = "Measures a calculus",
calculates = mutableSetOf(fromRepository)
))
metricRepository.save(metric)
println("---")
println(metric)
val metricFromRepository = metricRepository.findByName("Metric 1")!!
println(metricFromRepository) // calculates node is partially empty
println("--- \n")
To retrieve the same graph as shown in the picture above (taken from the actual neo4j dashboard), I do metricRepository.findByName("Metric 1") which has #Depth(value = 2) and then print the saved metric and the retrieved metric:
Metric(id=9, name=Metric 1, description=Measures a calculus, calculates=[Calculus(id=2, name=5 * 2, left=Value(id=18, name=5, value=5.0), right=Value(id=1, name=2, value=2.0), operator=TIMES)])
Metric(id=9, name=Metric 1, description=Measures a calculus, calculates=[Calculus(id=2, name=5 * 2, left=null, right=null, operator=TIMES)])
No matter the value of the depth, I can't get the Metric node with all his children nodes, it retrieves one level deep max and returns null on the leaf nodes.
I've read in the docs that depth=-1 retrieves the fully-resolved node but doing so causes the findByName() method to fail with a null pointer: kotlin.KotlinNullPointerException: null
Here is a list of resources I've consulted and a working GitHub repository with the full code:
GitHub Repo
Spring Data Neo4j Reference Documentation
Neo4j-OGM Docs
Final notes:
The entities all have default parameters because Kotlin then makes an empty constructor, I think the OGM needs it
I've also tried making custom queries but couldn't specify the depth value because there are different relationships and can be at different levels
To use the GitHub repository I linked you must have Neo4j installed, the repo has a stackoverflow-question branch with all the code.
Versions:
Spring boot: 2.3.0.BUILD-SNAPSHOT
spring-boot-starter-data-neo4j: 2.3.0.BUILD-SNAPSHOT
Thank you for helping and all feedback is welcomed!
The problem is not with the query depth but with the model. The Metric entity has a relation with Calculable, but Calculable itself has no relationships defined. Spring Data cannot scan all possible implementations of the Calculable interface for their relationships. If you changed Metrics.calculates type to MutableSet<Calculus>, it would work as expected.
To see Cypher requests send to the server you can add logging.level.org.neo4j.ogm.drivers.bolt=DEBUG to the application.properties
Request before the change:
MATCH (n:`Metric`) WHERE n.`name` = $`name_0` WITH n RETURN n,[ [ (n)->[r_c1:`CALCULATES`]->(x1) | [ r_c1, x1 ] ] ], ID(n) with params {name_0=Metric 1}
Request after the change:
MATCH (n:`Metric`) WHERE n.`name` = $`name_0` WITH n RETURN n,[ [ (n)->[r_c1:`CALCULATES`]->(c1:`Calculus`) | [ r_c1, c1, [ [ (c1)-[r_l2:`LEFT`]-(v2:`Value`) | [ r_l2, v2 ] ], [ (c1)-[r_r2:`RIGHT`]-(v2:`Value`) | [ r_r2, v2 ] ] ] ] ] ], ID(n) with params {name_0=Metric 1}

Referencing value and calling methods in generic class types

I'm new to Kotlin coming from C#. Currently I am trying to setup a class that takes in a couple of interchangeable generic types, the internal code of this class is a spring service end-point.
I have started with something like below, however I seem to have trouble with the syntax to reference the parameters of the request body as well as calling a method, which are of the types passed in through the class constructor. Syntax of generics and reflection does not seem that straight forward and most of the Kotlin examples I have been digging up has not seem to covered precisely what I am trying to do (if even possible). The object instance of type1 will be passed in through the body parameter and the object instance of type2 should be passed in through the constructor (syntax is probably not right).
Planning to use this as a template to setup several end-points based on the same base code but with different requests and services classes.
Any help is greatly appreciated.
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestMethod
import javax.validation.Valid
open class Base <T1,T2>(t1: Class<T1>, t2: Class<T2>) {
#Autowired
var type1 = t1
#Autowired
var type2 = t2
#ApiOperation(value = "API 1", response = myResponse::class)
#ApiResponses(value = *arrayOf(
ApiResponse(code = 200, message = "successful", response = CcdaResponse::class),
ApiResponse(code = 405, message = "Invalid", response = Void::class)))
#RequestMapping(
value = "/myEndPoint",
produces = arrayOf("application/json"),
consumes = arrayOf("application/json"),
method = arrayOf(RequestMethod.POST)
)
fun endpoint(
#ApiParam(value = "Options", required = true)
#Valid
#RequestBody
body: Class<T1>
): ResponseEntity<myResponse> {
val r = myResponse()
val response: ResponseEntity<myResponse>
response = ResponseEntity(r, HttpStatus.OK)
try {
//payload
val parameters = Parameters().apply {
Id1 = type1::body.Id1.get()
Id2 = type1::body.Id2.get()
Id3 = type1::body.Id3.get()
Id4 = type1::body.Id4.get()
v = type1::body.v.get()
}
//Do stuff like calling method in class of type2 passed in
val d = type2.getViewModel(parameters)
r.status = "ok"
} catch (e: Exception) {
r.message = e.toString()
r.status = "error"
} finally {
}
return response
}
}
The types of the parameters are passed in through the type arguments when creating an instance (the same as Java). So you do need to pass in the types themselves, adding a Class parameter just isn't the correct syntax.
I believe this is what you are looking for (omitted some code for brevity).
open class Base<T1, T2> (#Autowired var t2: T2) {
#Autowired var type1: T1? = null
fun endpoint(
#ApiParam(value = "Options", required = true) #Valid #RequestBody
body: T1
): ResponseEntity<MyResponse> {
type1 = body
}
}
Then, for instance, you can create an instance of this class with the types Int and String (for T1 and T2 respectively) in the following manner.
val t2 = "t2"
val base = Base<Int, String>(t2)
Or you can subclass the Base class with any (or none) of the types specified.
class SubBase(t2: String): Base<Int, String>(t2)

In Play Framework 2, how do I pass in an external value for form validators to use?

This can't be too uncommon. I want to get a "fullPathAndFileName" value, that's not in my form, into a validator.
In one of my controllers I have a file renaming action:
def renameAction(fullPathAndFileName: String) = Action { implicit request =>
val newRenameForm = renameForm.bindFromRequest()
newRenameForm.fold(
hasErrors = { form =>
Redirect(routes.TestApp.renderFormAction(fullPathAndFileName)).flashing("error" -> "New filename must be unused and cannot be empty.")
},
success = { newFileName =>
...
Here's my validator:
private val renameForm: Form[String] = Form("newFileName" -> nonEmptyText.verifying({txt => dupeNotFound(txt)}))
private def dupeNotFound(newFileName: String) = { !Asset.findAsset(replaceFileNameOfAsset(fullPathAndFileName, newFileName)) }
So this code won't compile, because fullPathAndFileName is not defined. How can I let the validator use that value?
Posting as answer instead of just commenting...anyway, this should work if I understand things correctly...
val newRenameForm = renameForm(fullPathAndFileName).bindFromRequest()
And the validator...
private val renameForm: (String) => Form[String] = (fullPathAndFileName: String) => Form("newFileName" -> nonEmptyText.verifying({txt => dupeNotFound(fullPathAndFileName,txt)}))
private def dupeNotFound(fullPathAndFileName: String, newFileName: String) = { !Asset.findAsset(replaceFileNameOfAsset(fullPathAndFileName, newFileName)) }

Resources