Copy object properties to another object in Groovy - java

I was using a funky way to do it suggested in:
https://stackoverflow.com/a/9072974/4470135
So my code is:
def copyProperties(source, target) {
def (sProps, tProps) = [source, target]*.properties*.keySet()
def commonProps = sProps.intersect(tProps) - ['class', 'metaClass']
commonProps.each { target[it] = source[it] }
}
What I get when I try to call a method that should convert an Entity into a Dto is:
No signature of method: java.util.ArrayList.keySet() is applicable
for argument types: () values: []\nPossible solutions: toSet(),
toSet(), set(int, java.lang.Object), set(int, java.lang.Object),
get(int), get(int)
UPDATE:
My source is a Serializable bean with fields:
private String passengerName;
#NotNull
#Size(min = 5, max = 40)
private String destination;
#NotNull
private String departureDate;
My target is a JPA Entity with the same fields, but with an additional #Id field and a slightly different date representation:
#DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
ZonedDateTime departureDate

The code is working, however, there are corner cases where it may break.
To fix this replace the property access properties with the method call getProperties(), which might be enough for your case. To cover all cases, you will need to write code for special cases (see bottom)
Working example for the original version
def copyProperties(source, target) {
def (sProps, tProps) = [source, target]*.properties*.keySet()
def commonProps = sProps.intersect(tProps) - ['class', 'metaClass']
commonProps.each { target[it] = source[it] }
}
def a = new Expando()
a.foo = "foo"
a.bar = "bar"
def b = new Expando()
b.baz = "baz"
b.bar = "old"
copyProperties(a, b)
println b
Example causing problems
If the parameters have a property called properties I get the same exception you got (if the value is a List):
def c = new Expando()
c.properties = []
c.bar = "bar"
def d = new Expando()
d.baz = "baz"
d.bar = "old"
copyProperties(c, d)
println d
What works in both cases:
def copyProperties(source, target) {
def (sProps, tProps) = [source, target]*.getProperties()*.keySet()
def commonProps = sProps.intersect(tProps) - ['class', 'metaClass']
commonProps.each { target[it] = source[it] }
}
Not that here I used an explicit call to getProperties rather than just accessing the properties property.
We can still break this
def e = new Object() {
// causes same Exception again
def getProperties() {
return []
}
def bar = "bar"
}
def f = new Expando()
f.baz = "baz"
f.bar = "old"
copyProperties(e, f)
You can fix the last example for e by using the metaClass explicitly
def copyProperties(source, target) {
def (sProps, tProps) = [source, target]*.getMetaClass()*.properties*.name
def commonProps = sProps.intersect(tProps) - ['class', 'metaClass']
commonProps.each { target[it] = source[it] }
}
However, that will fail due to f.
Handle special cases
def getProperties(Expando obj) {
return obj.getProperties().keySet()
}
def getProperties(Object obj) {
return obj.getMetaClass()*.properties*.name
}
def copyProperties(source, target) {
def (sProps, tProps) = [source, target].collect {getProperties(it)}
def commonProps = sProps.intersect(tProps) - ['class', 'metaClass']
commonProps.each { target[it] = source[it] }
}
Here we give objects that need a special treatment what they need ;)
Note that this only works like this for groovy with #CompileDynamic as the decision which getProperties implementation is called will be made at runtime. The alternative is a check with instanceof for all the cases.

User user = User.findById('1')
User copyUser = new User()
InvokerHelper.setProperties(copyUser, user.properties)

Related

An annotation argument must be a compile-time constant when trying to convert java enum

I tried to convert a java file to kotlin file but i'm getting this error : An annotation argument must be a compile-time constant
#StringDef(
BillingEnum.ALL,
BillingEnum.PAID,
BillingEnum.PENDING,
BillingEnum.OVERDUE,
BillingEnum.OPEN,
BillingEnum.DELETED
)
annotation class BillingEnum {
companion object {
var ALL = ""
var PAID = "paid"
var PENDING = "pending"
var OVERDUE = "overdue"
var OPEN = "open"
var DELETED = "deleted"
}
}
Before it looked like this:
#StringDef({
BillingEnum.ALL,
BillingEnum.PAID,
BillingEnum.PENDING,
BillingEnum.OVERDUE,
BillingEnum.OPEN,
BillingEnum.DELETED
})
public #interface BillingEnum {
String ALL = "";
String PAID = "paid";
String PENDING = "pending";
String OVERDUE = "overdue";
String OPEN = "open";
String DELETED = "deleted";
}
You must write
annotation class BillingEnum {
companion object {
const val ALL = ""
const val PAID = "paid"
const val PENDING = "pending"
const val OVERDUE = "overdue"
const val OPEN = "open"
const val DELETED = "deleted"
}
}

Reflection to get default value of parameter

I am trying to use reflection to get all the optional fields in a class and their default values (if any). This is my attempt:
fun getOptionalFields(componentType: KClass<out Any>): Map<String, DefaultValueData> {
val cons = componentType.primaryConstructor
val constructorSetup = cons?.parameters
?.filterNot { it.isOptional }
?.associate { it to getValueForNonOptional(it) } ?: error("Something went wrong when choosing default val")
val constructorInst = (cons.callBy(constructorSetup)::class as KClass<Any>)
val conParams = (componentType.primaryConstructor?.parameters ?: emptyList())
.filter { p -> p.isOptional }
.associate { p ->
Pair(p.name ?: "",
DefaultValueData(
p.type,
// the below line crashes
constructorInst.memberProperties.first { m -> m.name == p.name }.get(constructorInst)
)
)
}
return conParams
}
The error: Exception in thread "main" java.lang.IllegalArgumentException: object is not an instance of declaring class
I am a bit puzzled at what get() wants me to pass if not the instance to get the value from?
You are trying to get the value by providing a KClass instead of the actual instance.
This is a working solution based on your method signature and your code above:
data class Test(
val required: String,
val optional: Int = 7
)
val componentType = Test::class
val constructorInst = Test("required") // i skipped constructing the class via constructor
val conParams = (componentType.primaryConstructor?.parameters ?: emptyList())
.filter { p -> p.isOptional }
.associate { p ->
Pair(p.name ?: "",
Pair(
p.type,
componentType.memberProperties.first { m -> m.name == p.name }.get(constructorInst)
)
)
}
println(conParams) // <- OUTPUTS: {optional=(kotlin.Int, 7)}
Why have i removed this code?
val constructorSetup = cons?.parameters
?.filterNot { it.isOptional }
?.associate { it to getValueForNonOptional(it) } ?: error("Something went wrong when choosing default val")
val constructorInst = (cons.callBy(constructorSetup)::class as KClass<Any>)
The resulting object cons.callBy(constructorSetup) is unused because calling ::class on the expression rendered it useless. Additionally it is not required to perform the requested task in your question.
When updating your above code, result will look like
fun getOptionalFields(componentType: KClass<out Any>): Map<String, DefaultValueData> {
val cons = componentType.primaryConstructor
val constructorSetup = cons?.parameters
?.filterNot { it.isOptional }
?.associate { it to getValueForNonOptional(it) } ?: error("Something went wrong when choosing default val")
val constructorInst = cons.callBy(constructorSetup) // <- removed ::class and cast
val conParams = (componentType.primaryConstructor?.parameters ?: emptyList())
.filter { p -> p.isOptional }
.associate { p ->
val value = constructorInst::class.memberProperties.first { m -> m.name == p.name }.get(constructorInst) as KProperty1<out Any, out Any>
Pair(p.name ?: "",
DefaultValueData(
p.type,
value
)
)
}
return conParams
}

Set a customField Value in JIRA with Groovy JAVA

Ok Maybe I am just really new at this, but I am trying to use this code and it is not updating the custom field value.
Any idea why not? I am guessing its an over sight n my end. Any help is greatly appreciated
def rush = getCustomFieldValue("Rush?")
if (rush=="Yes") {
def cal = new java.util.GregorianCalendar();
cal.setTimeInMillis(customField.setCustomFieldValue("Rush Date", getTime()));
return new java.sql.Timestamp(cal.getTimeInMillis());
}
else {
return null
}
solved
import com.atlassian.jira.ComponentManager
import com.atlassian.jira.issue.customfields.manager.OptionsManager
def componentManager = ComponentManager.instance
def optionsManager = ComponentManager.getComponentInstanceOfType(OptionsManager.class)
def customFieldManager = componentManager.getCustomFieldManager()
def cf = customFieldManager.getCustomFieldObjectByName("Rush?")
def rush = issue.getCustomFieldValue(cf)
def paymentDate = new Date()
if (rush?.value=="Yes"){
if (paymentDate){
def cal = new java.util.GregorianCalendar();
cal.setTimeInMillis(paymentDate.getTime());
cal.add(java.util.Calendar.DAY_OF_MONTH, 0);
return new java.sql.Timestamp(cal.getTimeInMillis());
}
else {
return null
}}
Your snippet, as it stands, fails to me with the following error:
Caught: groovy.lang.MissingMethodException: No signature of method: Custom.getTime() is applicable for argument types: () values: []
That getTime() over there is not being invoked from any object. I guess you want only to setCustomFieldValue in customField, thus, cal.setTimeInMillis()ยน is not needed:
def customField
customField = [
'Rush?':'Yes',
setCustomFieldValue : { field, value -> customField[field] = value }
]
getCustomFieldValue = { customField[it] }
def rush = getCustomFieldValue("Rush?")
def cal = new java.util.GregorianCalendar()
def parseRush = {
if (rush=="Yes") {
customField.setCustomFieldValue("Rush Date", cal.getTime())
return new java.sql.Timestamp(cal.getTimeInMillis())
}
else {
return null
}
}
assert parseRush() == new java.sql.Timestamp(cal.timeInMillis)
assert customField['Rush Date'] == cal.time

Scala how to inject mock object to ScalatraFlatSpec

I stuck with Unit test in Scala for many days. I cannot inject mock object to Unit test. The ScalatraFlatSpec call to the actual database not my mock variable and i have no idea to do.
This is my API
class Dashboard extends Servlet {
get("/:brand_code") {
val start = System.currentTimeMillis
val brandCode = params.get("brand_code").get
var brandId = 0;
val sqlFind = "SELECT DISTINCT(id) FROM brands WHERE brand_code=?"
val found:List[Map[String, Any]] = ConnectionModel.getExecuteQuery(sqlFind, List(brandCode))
if(found.isEmpty){
halt(404, send("error", s"brand_code [$brandCode] not found."))
}else{
brandId = found(0).getOrElse("id", 0).toString.toInt
send("Yeah55", brandId)
}
}
And this is Servlet
abstract class Servlet extends ScalatraServlet with CorsSupport with JacksonJsonSupport {
protected implicit lazy val jsonFormats: Formats = DefaultFormats.withBigDecimal
protected override def transformResponseBody(body: JValue): JValue = body.underscoreKeys
protected lazy val body = parsedBody.extract[Map[String, Any]]
protected def send(message: String, data: Any = None) = Map("message" -> message, "data" -> data)
options("/*") {
response.setHeader(
"Access-Control-Allow-Headers", request.getHeader("Access-Control-Request-Headers")
)
}
before() {
contentType = formats("json")
}
}
And this is ConnectionModel and ConnectionModelAble
trait ConnectionModelAble {
def getExecuteQuery(sql: String, parameters: List[Any]): List[Map[String, Any]]
}
object ConnectionModel extends ConnectionModelAble{
var connection:Connection = {
val url = "jdbc:mysql://localhost:3306/db"
val username = "root"
val password = ""\
Class.forName("com.mysql.jdbc.Driver")
DriverManager.getConnection(url, username, password)
}
def getExecuteQuery(sql: String, parameters: List[Any]): List[Map[String, Any]]= {
try {
val statement = connection.createStatement()
var preparedStatement: PreparedStatement = connection.prepareStatement(sql);
var formatDate: DateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
// Do some execute
for (i <- 0 until parameters.size) {
parameters(i) match {
case _: Int => preparedStatement.setInt(i + 1, parameters(i).toString.toInt)
case _: Double => preparedStatement.setDouble(i + 1, parameters(i).toString.toDouble)
case _: Date => preparedStatement.setDate(i + 1, new java.sql.Date(formatDate.parse(parameters(i).toString).getTime))
case default => preparedStatement.setString(i + 1, parameters(i).toString)
}
}
val resultSet = preparedStatement.executeQuery()
val metaData: ResultSetMetaData = resultSet.getMetaData();
val columnCount = metaData.getColumnCount();
var ret: List[Map[String, Any]] = List();
while (resultSet.next()) {
var row: Map[String, Any] = Map[String, Any]();
for (i <- 1 to columnCount) {
val columnName = metaData.getColumnName(i);
var obj = resultSet.getObject(i);
row += columnName -> obj
}
ret = ret :+ row
}
ret
}catch {
case e: Exception => {
e.printStackTrace();
List()
}
}
}
And this is my unit test
class DashboardSpec extends ScalatraFlatSpec with MockitoSugar {
addServlet(new Dashboard, "/v1/dashboard/*")
it should "return get dashboard correctly" in {
val brandCode = "APAAA"
val brandId = 157
get("/v1/dashboard/APAAA") {
val connectModel = mock[ConnectionModelAble]
val sqlFind = "SELECT DISTINCT(id) FROM brands WHERE brand_code=?"
Mockito.when(connectModel.getExecuteQuery(sqlFind, List(brandCode))).thenReturn(
List(Map("id" -> 150))
)
assert(status == 200)
println(connectModel.getExecuteQuery(sqlFind, List(brandCode)))
println(body)
}
}
}
I found that body from unit test is not from my mock data, it's from real database. What should i do.
Thank you.
You aren't injecting your mock into the Dashboard, so the Connection you're seeing in getExecuteQuery is the one provided by ConnectionModel.connection. You probably want to use a dependency injection framework or something like the Cake pattern to make sure your Dashboard is referring to your mock instance.

Getting a substring using method does not work

I am trying to introduce a method in my program which should result into a substring. I wrote the following code but why would not this work?
class testmethod {
String FQDN = "TEST.domain.local"
def sname = shortname(FQDN);
println $sname
def shortname(Longname)
{
shortname1 = Longname.substring(0, Longname.indexOf('.'))
return shortname1
}
}
First of all the code should be(for better readability) :-
def shortname(String Longname) not def shortname(Longname).
Also shortname1 = Longname.substring(0, Longname.indexOf('.')) in this shortname1 is not defined.
Moreover you can try :-
def shortname(String Longname)
{
String[] shortnameArr = Longname.split("\\.");
return shortnameArr[0];// will return TEST
}
You're mixing both script and class concepts of the Groovy in one single piece of code.
Just remove the class definition and $ sign to use the script way:
String FQDN = "TEST.domain.local"
def sname = shortname(FQDN);
println sname
def shortname(Longname)
{
shortname1 = Longname.substring(0, Longname.indexOf('.'))
return shortname1
}
Or add class initialization and local variable declaration to use the class way:
class testmethod {
String FQDN = "TEST.domain.local"
def sname = shortname(FQDN);
def shortname(Longname)
{
def shortname1 = Longname.substring(0, Longname.indexOf('.'))
return shortname1
}
}
def tc = new testmethod()
println tc.sname

Categories