Android Room Kotlin: entities with non data classes - function

Regarding to classes that can/may be used for entities in room whith Kotlin,
Is it mandatory to use data classes? Or could I use 'normal classes', i.e., the ones I use for the 'bussiness logic'
In case data classes are mandatory: can I add functionality to what they have by default? i.e, can I add functions (for whatever taks they may need) to them?
The documentation doesn't say anything about limiting entities to data classes (although each and every code snippet use a data class).
Thanks.

Is it mandatory to use data classes?
No, you can use either including a mix.
data classes are a convenience class
can I add functionality to what they have by default?
Yes.
Perhaps consider the following:-
#Entity
class Table1 {
#PrimaryKey
var id: Long? = null
var name: String = ""
#Ignore
var notAColumnInTheTable = false
constructor(){}
#Ignore
constructor(name: String) {
this.id = null
this.name = name
this.notAColumnInTheTable = true
}
fun getIdAndName(): String {
return id.toString() + ":" + name
}
}
and :-
#Entity
data class Table2(
#PrimaryKey
var id: Long? = null,
var name: String,
#Ignore
var notAColumnInTheTable: Boolean = false
) {
constructor(name: String) : this( id = null,name = name, notAColumnInTheTable = true)
fun getIdAndName(): String {
return id.toString() + ":" + name
}
}
basically they are the same.
Using :-
#Dao
abstract class Table1And2Dao {
#Insert
abstract fun insert(table1: Table1): Long
#Insert
abstract fun insert(table2: Table2): Long
#Query("SELECT * FROM table1")
abstract fun getAllFromTable1(): List<Table1>
#Query("SELECT * FROM table2")
abstract fun getAllFromTable2(): List<Table2>
}
note the use of an abstract class rather than the normally see interface
along with a suitable #Database annotated class, in this case one that has a function that returns an instance of the built database and for convenience/brevity allows running on the main thread.
Then using :-
var db = AppDatabase.getDatabase(this)
var dao = db.getTable1AndTable2Dao()
dao.insert(Table1("TABLE1_1"))
dao.insert(Table2("TABLE2_1"))
for(t1: Table1 in dao.getAllFromTable1()) {
Log.d("DBINFO","Name is ${t1.name} ID is ${t1.id} NotAColumnInTable is ${t1.notAColumnInTheTable} idandname = ${t1.getIdAndName()}")
}
for(t2: Table2 in dao.getAllFromTable2()) {
Log.d("DBINFO","Name is ${t2.name} ID is ${t2.id} NotAColumnInTable is ${t2.notAColumnInTheTable} idandname = ${t2.getIdAndName()}")
}
Results in the log including:-
D/DBINFO: Name is TABLE1_1 ID is 1 NotAColumnInTable is true idandname = 1:TABLE1_1
D/DBINFO: Name is TABLE2_1 ID is 1 NotAColumnInTable is true idandname = 1:TABLE2_1
Via App Inspection :-
and :-

Related

Include additional columns in Where clause of Hibernate/JPA Generated UPDATE Query

I am using Hibernate/JPA.
When i do an entity.save() or session.update(entity), hibernate generates a query like this :-
update TABLE1 set COL_1=? , COL_2=? , COL_3=? where COL_PK=?
Can I include an additional column in the WHERE clause by means of any annotation in the entity, so it can result in a query like :-
update TABLE1 set COL_1=? , COL_2=? , COL_3=? where COL_PK=? **AND COL_3=?**
This is because our DB is sharded based on COL_3 and this needs to be present in where clause
I want to be able to achieve this using the session.update(entity) or entity.save() only.
If I understand things correctly, essentially what you are describing is that you want hibernate to act like you have a composite primary key even though your database has a single-column primary key (where you also have a #Version column to perform optimistic locking).
Strictly speaking, there is no need for your hibernate model to match your db-schema exactly. You can define the entity to have a composite primary key, ensuring that all updates occur based on the combination of the two values. The drawback here is that your load operations are slightly more complicated.
Consider the following entity:
#Entity
#Table(name="test_entity", uniqueConstraints = { #UniqueConstraint(columnNames = {"id"}) })
public class TestEntity implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id", nullable = false, unique = true)
private Long id;
#Id
#Column(name = "col_3", nullable = false)
private String col_3;
#Column(name = "value", nullable = true)
private String value;
#Version
#Column(nullable = false)
private Integer version;
... getters & setters
}
Then you can have the following method (in my case, I created a simple JUnit test)
#Test
public void test() {
TestEntity test = new TestEntity();
test.setCol_3("col_3_value");
test.setValue("first-value");
session.persist(test);
long id = test.getId();
session.flush();
session.clear();
TestEntity loadedTest = (TestEntity) session
.createCriteria(TestEntity.class)
.add(Restrictions.eq("id", id))
.uniqueResult();
loadedTest.setValue("new-value");
session.saveOrUpdate(loadedTest);
session.flush();
}
This generates the following SQL statements (enabled Hibernate logging)
Hibernate:
call next value for hibernate_sequence
Hibernate:
insert
into
test_entity
(value, version, id, col_3)
values
(?, ?, ?, ?)
Hibernate:
select
this_.id as id1_402_0_,
this_.col_3 as col_2_402_0_,
this_.value as value3_402_0_,
this_.version as version4_402_0_
from
test_entity this_
where
this_.id=?
Hibernate:
update
test_entity
set
value=?,
version=?
where
id=?
and col_3=?
and version=?
This makes loading slightly more complicated as you can see - I used a criteria here, but it satisfies your criteria, that your update statements always include the column col_3 in the 'where' clause.
The following solution works, however I recommend you to just wrap your saveOrUpdate method in a way that you ends up using a more natural approach. Mine is fine... but is a bit hacky.
Solution:
You can create your own annotation and inject your extra condition to hibernate save method using a hibernate interceptor. The steps are the following:
1. Create a class level annotation:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
public #interface ForcedCondition {
String columnName() default "";
String attributeName() default ""; // <-- this one is just in case your DB column differs from your attribute's name
}
2. Annotate your entity specifying your column DB name and your entity attribute name
#ForcedCondition(columnName = "col_3", attributeName= "col_3")
#Entity
#Table(name="test_entity")
public class TestEntity implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id", nullable = false, unique = true)
private Long id;
#Column(name = "col_3", nullable = false)
private String col_3;
public String getCol_3() {
return col_3;
}
... getters & setters
}
3. Add a Hibernate interceptor and inject the extra condition:
public class ForcedConditionInterceptor extends EmptyInterceptor {
private boolean forceCondition = false;
private String columnName;
private String attributeValue;
#Override
public boolean onSave(
Object entity,
Serializable id,
Object[] state,
String[] propertyNames,
Type[] types) {
// If your annotation is present, backup attribute name and value
if (entity.getClass().isAnnotationPresent(ForcedCondition.class)) {
// Turn on the flag, so later you'll inject the condition
forceCondition = true;
// Extract the values from the annotation
columnName = entity.getClass().getAnnotation(ForcedCondition.class)).columnName();
String attributeName = entity.getClass().getAnnotation(ForcedCondition.class)).attributeName();
// Use Reflection to get the value
// org.apache.commons.beanutils.PropertyUtils
attributeValue = PropertyUtils.getProperty(entity, attributeName);
}
return super.onSave(entity, id, state, propertyNames, types);
}
#Override
public String onPrepareStatement(String sql) {
if (forceCondition) {
// inject your extra condition, for better performance try java.util.regex.Pattern
sql = sql.replace(" where ", " where " + columnName + " = '" + attributeValue.replaceAll("'", "''") + "' AND ");
}
return super.onPrepareStatement(sql);
}
}
After all that everytime you call entity.save() or session.update(entity) over an entity annotated with #ForcedCondition, the SQL will be injected with the extra condition you want.
BTW: I didn't tested this code but it should get you along the way. If I did any mistake please tell me so I can correct.

Data-Driven Unit Test with MSTest : How to read and validate records from CSV file?

I use the MsTest and Data Driven approach for unit testing.
So far I have a Data.csv file with 5 rows added in my Unit Test project and I want to validate and output the UserId for each corresponding orgId and username.
For example, for Orgid '80010' and username 'Malika' - expected userid = 0000000047
data.csv file
How can I set up the data driven test method to read and validate the correct userId from the .CSV file?
C# Code:
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace AuthenticationServiceTest
{
[TestClass]
public class UT_GetUserIdFromUserName
{
public TestContext TestContext { get; set; }
[TestMethod]
[DataSource("Microsoft.VisualStudio.TestTools.DataSource.CSV", #"Data\Data.csv", "Data#csv",
DataAccessMethod.Sequential)]
public void TestMethod1()
{
// Arrange
int ExpUserId = Convert.ToInt32(TestContext.DataRow["0000000047"]);
int orgId = Convert.ToInt32(TestContext.DataRow[1]);
int username = Convert.ToInt32(TestContext.DataRow[2]);
// Act
string UserId = string.Empty;
// Assert
Assert.AreEqual(ExpUserId, UserId);
Console.WriteLine("Expected UserId: " + ExpUserId);
Console.WriteLine("Actual UserId: " + UserId);
}
}
}
You don't have to pick a specific ID the unit test engine will take care of parsing the file row by row.
You have to use the the TestContext.DataRow["column" or index] to get the value from a specific column of the CSV file.
int ExpUserId = Convert.ToInt32(TestContext.DataRow["0000000047"]);
Should be either
int ExpUserId = Convert.ToInt32(TestContext.DataRow[0]);
or
int ExpUserId = Convert.ToInt32(TestContext.DataRow["userId"]);

Parsing Complex JSON Objects with inheritance

I'm building a batch process that includes a number of steps of varying types.
export interface IStep {
id: number;
icon: string;
name: string;
selected: boolean;
}
export class InitStep implements IStep {
id: number;
icon: string;
name: string;
selected = false;
}
export class InputStep implements IStep {
id: number;
icon: string;
name: string;
selected = false;
primaryKey: string;
file: File;
}
export class QueryStep implements IStep {
constructor () {
this.filters = [];
this.output_fields = [];
this.table_fields = [];
const filter = new Filter;
this.filters.push(filter);
}
get input_ids(): number[] {
return this.filters.map(filter => filter.input_id);
}
id: number;
icon: string;
name: string;
selected = false;
table: string;
table_fields: string[];
filters: Filter[];
output_fields: string[];
}
export class OutputStep implements IStep {
constructor() {
this.fields = [];
}
id: number;
icon: string;
name: string;
selected = false;
fields: string[];
}
export class DeliveryStep implements IStep {
constructor() {
this.output_ids = [];
}
id: number;
icon: string;
name: string;
selected = false;
output_ids: number[];
format: BatchOutputType;
frequency: BatchFrequencyType;
email: string;
password: string;
}
I want to be able to have an array of any combination/number of these steps and be able to save them to and read from localstorage.
const key = 'notgunnawork';
localStorage.setItem(key, JSON.stringify(this.steps));
const s = JSON.parse(key) as IStep[];
I knew there was a snowball's chance in hell this was going to parse correctly, obviously the parser doesn't know which steps belong to what classes ultimately. I was just wondering if there was a simple way to get my array to come out looking the same way it went in. I'll eventually be posting this list to the server and would like my .Net Core code to also be able to parse this JSON without me having to make a custom parser.
EDIT
Added the full classes of what Im trying to serialize, for more detail. The error I'm getting whenever I try to serialize and then deserialize is: "Unexpected token o in JSON at position 1"
So, I'm going to answer what I think your issue is, and if I'm wrong then feel free to ignore me 🙂
Your problem is that you have a bunch of classes with methods but when you serialize instances of these to JSON and then deserialize them back, you end up with plain-old JavaScript objects and not instances of your classes. One way to handle this is to use a custom deserializer which knows about your classes and can "hydrate" or "revive" the plain-old JavaScript objects into genuine class instances. The JSON.parse() function allows you to specify a callback parameter called reviver which can be used to do just that.
First, we need to set up a system by which the reviver will know about your serializable classes. I'm going to use a class decorator which will add each class constructor to a registry the reviver can use. We will require that a serializable class constructor be assignable to a type we can call Serializable: it needs to have a no-argument constructor and the things it constructs need to have a className property:
// a Serializable class has a no-arg constructor and an instance property
// named className
type Serializable = new () => { readonly className: string }
// store a registry of Serializable classes
const registry: Record<string, Serializable> = {};
// a decorator that adds classes to the registry
function serializable<T extends Serializable>(constructor: T) {
registry[(new constructor()).className] = constructor;
return constructor;
}
Now, when you want to deserialize some JSON, you can check if the serialized thing has a className property that's a key in the registry. If so, you use the constructor for that classname in the registry, and copy properties into it via Object.assign():
// a custom JSON parser... if the parsed value has a className property
// and is in the registry, create a new instance of the class and copy
// the properties of the value into the new instance.
const reviver = (k: string, v: any) =>
((typeof v === "object") && ("className" in v) && (v.className in registry)) ?
Object.assign(new registry[v.className](), v) : v;
// use this to deserialize JSON instead of plain JSON.parse
function deserializeJSON(json: string) {
return JSON.parse(json, reviver);
}
Okay now that we have that, let's make some classes. (I'm using your original definitions here, before your edits.) Note that we are required to add a className property and we must have a no-arg constructor (this happens for free if you don't specify a constructor, since the default constructor is no-arg):
// mark each class as serializable, which requires a className and a no-arg constructor
#serializable
class StepType1 implements IStep {
id: number = 0;
name: string = "";
prop1: string = "";
readonly className = "StepType1"
}
#serializable // error, property className is missing
class OopsNoClassName {
}
#serializable // error, no no-arg constructor
class OopsConstructorRequiresArguments {
readonly className = "OopsConstructorRequiresArguments"
constructor(arg: any) {
}
}
#serializable
class StepType2 implements IStep {
id: number = 0;
name: string = "";
prop2: string = "";
prop3: string = "";
prop4: string = "";
readonly className = "StepType2"
}
#serializable
class StepType3 implements IStep {
id: number = 0;
name: string = "";
prop5: string = "";
prop6: string = "";
readonly className = "StepType3"
}
Now let's test it out. Make some objects as you would normally do, and put them in an array:
// create some objects of our classes
const stepType1 = new StepType1();
stepType1.id = 1;
stepType1.name = "Alice";
stepType1.prop1 = "apples";
const stepType2 = new StepType2();
stepType2.id = 2;
stepType2.name = "Bob";
stepType2.prop2 = "bananas";
stepType2.prop3 = "blueberries";
stepType2.prop4 = "boysenberries";
const stepType3 = new StepType3();
stepType3.id = 3;
stepType3.name = "Carol";
stepType3.prop5 = "cherries";
stepType3.prop6 = "cantaloupes";
// make an array of IStep[]
const arr = [stepType1, stepType2, stepType3];
And let's have a function which will examine the elements of an array and check to see if they are instances of your classes:
// verify that an array of IStep[] contains class instances
function verifyArray(arr: IStep[]) {
console.log("Array contents:\n" + arr.map(a => {
const constructorName = (a instanceof StepType1) ? "StepType1" :
(a instanceof StepType2) ? "StepType2" :
(a instanceof StepType3) ? "StepType3" : "???"
return ("id=" + a.id + ", name=" + a.name + ", instanceof " + constructorName)
}).join("\n") + "\n");
}
Let's make sure it works on arr:
// before serialization, everything is fine
verifyArray(arr);
// Array contents:
// id=1, name=Alice, instanceof StepType1
// id=2, name=Bob, instanceof StepType2
// id=3, name=Carol, instanceof StepType3
Then we serialize it:
// serialize to JSON
const json = JSON.stringify(arr);
To demonstrate your original problem, let's see what happens if we just use JSON.parse() without a reviver:
// try to deserialize with just JSON.parse
const badParsedArr = JSON.parse(json) as IStep[];
// uh oh, none of the deserialized objects are actually class instances
verifyArray(badParsedArr);
// Array contents:
// id=1, name=Alice, instanceof ???
// id=2, name=Bob, instanceof ???
// id=3, name=Carol, instanceof ???
As you can see, the objects in badParsedArr do have the id and name properties (and the other class-specific instance properties like prop3 if you checked) but they are not instances of your classes.
Now we can see if the problem is fixed by using our custom deserializer:
// do the deserialization with our custom deserializer
const goodParsedArr = deserializeJSON(json) as IStep[];
// now everything is fine again
verifyArray(goodParsedArr);
// Array contents:
// id=1, name=Alice, instanceof StepType1
// id=2, name=Bob, instanceof StepType2
// id=3, name=Carol, instanceof StepType3
Yes, it works!
The above method is fine, but there are caveats. The main one: it will work if your serializable classes contain properties which are themselves serializable, as long as your object graph is a tree, where each object appears exactly once. But if you have an object graph with any kind of cycle in it (meaning that the same object appears multiple times if you traverse the graph multiple ways) then you will get unexpected results. For example:
const badArr = [stepType1, stepType1];
console.log(badArr[0] === badArr[1]); // true, same object twice
const badArrParsed = deserializeJSON(JSON.stringify(badArr));
console.log(badArrParsed[0] === baddArrParsed[1]); // false, two different objects
In the above case, the same object appears multiple times. When you serialize and deserialize the array, your new array contains two different objects with the same property values. If you need to make sure that you only deserialize any particular object exactly once, then you need a more complicated deserialize() function which keeps track of some unique property (like id) and returns existing objects instead of creating new ones.
Other caveats: this assumes your serializable classes have instance properties consisting only of other serializable classes as well as JSON-friendly values like strings, numbers, arrays, plain objects, and null. If you use other things, like Dates, you will have to deal with the fact that those serialize into strings.
Exactly how complicated serialization/deserialization is for you depends heavily on your use case.
Okay, hope that helps. Good luck!

Does Flash have a method that does the reverse of toString?

When using ObjectUtil there is a method called, toString() that takes an object. If you pass it a class named, "Person" it will return the string "[class Person]".
var person:Person = new Person();
trace(ObjectUtil.toString(person));//UPDATE I'm not using ObjectUtil.toString()
// traces [class Person]
Is there a toObject() method? Something that takes the same format toString outputs and creates an instance like so:
var person:Person = ObjectUtil.toObject("[class Person]");
UPDATE:
Sorry. This is incorrect. I thought I was using ObjectUtil.toString(). I was not. When I use that method it returns something like:
(com.printui.assets.skins::fontList)#0
accessibilityDescription = ""
accessibilityEnabled = true
accessibilityImplementation = (null)
In my code somewhere it is returning "[class Person]" like I was described. This is the line:
var currentValue:* = target[property];
popUpValueInput.text = currentValue;
I thought it was using instance.toString() but toString() is not returning anything close to that:
var f:fontList = new fontList();
var f1:fontList = new fontList();
trace("" + f);
trace("" + f1);
trace(f1.toString());
Results in:
fontList2
fontList5
fontList5
In general you should do this:
In your Person class add this method:
public function toString():String
{
return "Person" ;
}
So to make an instance of the class by name use this code:
var p = new (getDefinitionByName( ObjectUtils.toString(person)))
or it can be used a regex in general for all classes (thanks to 1.21 gigawatts ):
var p = new (getDefinitionByName( ObjectUtil.toString(Person).match(/\((.*)\)/)[1] ) );

Grails - how to let a domain class convert JSON into a domain property

I want to teach my domain class to automatically convert the results of JSON.parse(someJSON) into a member that is also a custom domain class.
Given these domain classes:
class Person {
Long id
String name
static hasMany = [aliases: PersonAlias]
}
class PersonAlias {
Person person
Long id
String name
}
And this JSON representing a Person with some PersonAliases:
{
"id":20044397,
"name":"John Smith",
"aliases":[{"id":13376,"name":"Johnny Smith"},{"id":13377,"name":"J. Smith"}]
}
I want to keep the controller simple like:
class PersonController {
def saveViaAjax = {
def props = JSON.parse(params.JSON)
Person p = Person.get(props.id)
p.properties = props
p.save(flush: true)
}
}
But sadly I get this error:
Failed to convert property value of type
'org.codehaus.groovy.grails.web.json.JSONArray' to required type
'java.util.Set' for property 'aliases'; nested exception is
java.lang.IllegalStateException: Cannot convert value of type
[org.codehaus.groovy.grails.web.json.JSONObject] to required type
[heavymeta.PersonAlias] for property 'aliases[0]': no matching editors
or conversion strategy found
So, I want to teach my domain class to how to convert the JSON data into PersonAlias instances automatically. I'd like to avoid formatting the data in the controller before passing it to the Domain object. How do I accomplish these goals?
You can use the bindUsing annotation and provide your custom binding code to convert the json to the property being bound.
class Person {
Long id
String name
#BindUsing({obj, source ->
List retVal = []
def aliases = source['aliases']
if(aliases) {
aliases.each {
retVal << new PersonAlias(name:it.name)
}
}
return retVal
})
List<PersonAlias> aliases
static hasMany = [aliases: PersonAlias]
}
I think this plugin: https://github.com/pedjak/grails-marshallers might do what you're looking for? I have not tried it myself though.
I also encountered this problem - I did my best to document the fix on my website - See http://dalelotts.com/software-architect/grails
In general the solution is to convert the JSON to a parameter map that can be used for data binding. More info on the site, including an annotation driven DomainClassMarshaller for JSON
protected Object readFromJson(Class type, InputStream entityStream, String charset) {
def mapper = new ObjectMapper();
def parsedJSON = mapper.readValue(entityStream, typeRef);
Map<String, Object> map = new HashMap<>();
parsedJSON.entrySet().each {Map.Entry<String, Object> entry ->
if (List.isAssignableFrom(entry.getValue().getClass())) {
List values = (List) entry.getValue();
int limit = values.size()
for (int i = 0; i < limit; i++) {
final theValue = values.get(i)
map.put(entry.key + '[' + i + ']', theValue)
appendMapValues(map, theValue, entry.key + '[' + i + ']' )
}
} else {
map.put(entry.key, entry.value);
}
}
def result = type.metaClass.invokeConstructor(map)
// Workaround for http://jira.codehaus.org/browse/GRAILS-1984
if (!result.id) {
result.id = idFromMap(map)
}
result
}
private void appendMapValues(Map<String, Object> theMap, Object theValue, String prefix) {
if (Map.isAssignableFrom(theValue.getClass())) {
Map<String, Object> valueMap = (Map<String, Object>) theValue;
for (Map.Entry<String, Object> valueEntry : valueMap.entrySet()) {
theMap.put(prefix + '.' + valueEntry.key, valueEntry.value)
appendMapValues(theMap, valueEntry.value, prefix + '.' + valueEntry.key)
}
}
}