Grails - CreateCriteria to Retrieve any entry that contains any element in query - grails

Given I have this domain:
class Game {
Set<GameType> gameType
String name
}
And this enum:
enum Gametype {
RHYTHM, ADVENTURE, PUZZLE, RPG, HORROR, FIGHTING, MOBA, MMO
}
I need to retrieve a list of games that contains at least one of the game types indicated in the query. I tried using this code:
def retrieveGamesThatMayBeUnderGameTypes( List<GameType> listOfGameTypes) {
return Game.createCriteria().list(){
'in'("gameType", listOfGameTypes)
}
}
However, it returns a NullPointerException. Any ideas?

Just make sure your enum (Gametype) has a field called id. Something like:
enum Gametype {
RHYTHM('RHYTHM'),
ADVENTURE('ADVENTURE'),
....
String id
Gametype(String id) {
this.id = id
}
}
See this answer for more: Grails Enum Mapping

Related

Cannot use enum in repository query (neo4j/Spring Data)

I'm having a problem querying based on an Enum property of my NodeEntity.
The NodeEntity in question is defined:
#NodeEntity(label = "Entity")
public class MyEntity {
#GraphId
private Long internalId;
....
private State state;
#Transient
public enum State {
STATEONE, STATETWO, STATETHREE
}
....
It saves without a problem, the state Enum represented perfectly, and I can query using other properties (Strings) with no problem at all. However the problem is the following query in a repository:
#Query("MATCH (entity:Entity {state:{0}})" +
"RETURN entity")
List<MyEntity> findByState(MyEntity.State state)
i.e. find all entities with the given state.
There's no exception, however using this simply returns a List of 0 Entities.
I've tried all kinds of variations on this, using a WHERE clause for example, with no luck.
The Entities are persisted properly, using findAll() in the same test returns the expected List of Entities with their states exactly as I would expect.
Any thoughts?
Not quite sure what the value #Transient adds to the enum. It is anyway not persistable as a node or relationship in Neo4j. It is sufficient to define the field as one that should persist with
private State state;
and leave off the #Transient annotation from the enum.
With it, SDN ignores the field sent to the derived query.
However, if you have a good reason to mark the enum #Transient, please do share it and we'll re-visit this case.
There is a general problems using spring data rest interface to search on enum fields. Just using the enum-to-string converter cannot work for search where you want to find if the value is IN a collection of values:
public interface AppointmentRepository extends Neo4jRepository<Appointment, Long> {
Page<Appointment> findByDayOfWeekIn(#Param("days") List<DayOfWeek> days, Pageable pageable);
}
The above does not work out of the box because neo4j will try to convert a List to your property type: DayOfWeek
In order to work around this I needed a custom converter that handles both requests providing collection of values (the search) and single values (the normal read and write entity):
#SuppressWarnings({ "unchecked", "rawtypes" })
public abstract class SearchQueryEnumConverter<T extends Enum> {
private Class<T> enumType;
public SearchQueryEnumConverter() {
enumType = (Class<T>) ((ParameterizedType) this.getClass()).getActualTypeArguments();
}
public Object toGraphProperty(Object value) {
if (Collection.class.isAssignableFrom(value.getClass())) {
List<T> values = (List<T>) value;
return values.stream().map(Enum::name).collect(Collectors.toList());
}
return ((Enum) value).name();
}
public Object toEntityAttribute(Object value) {
if (Collection.class.isAssignableFrom(value.getClass())) {
List<String> values = (List<String>) value;
return values.stream().map(v -> (T) T.valueOf(enumType, v)).collect(Collectors.toList());
}
return (T) T.valueOf(enumType, value.toString());
}
}
The abstract converter can be reified by all enums, and used as parameter of the #Convert annotation:
public enum EnumType {
VALUE_A, VALUE_B;
public static class Converter extends SearchQueryEnumConverter<EnumType> implements AttributeConverter {
}
}
#NodeEntity
public Entity {
#Property
#Convert(EnumType.Converter.class)
EnumType type;
}

How can I change neo4j Id to UUID and get finder methods to work?

Neo4j needs an id field to work which is of type Long. This works well with Spring data neo4j. I would like to have another field of type UUID and have T findOne(T id) to work with my UUID not neo generated Ids.
As I am using Spring Data Rest, I don't want to expose neo's id in the URL.
http://localhost:8080/resource/{neoId}
to
http://localhost:8080/resource/{uuid}
Any ideas if this is possible?
UPDATED
{
name: "Root",
resourceId: "00671e1a-4053-4a68-9c59-f870915e3257",
_links: {
self: {
href: "http://localhost:8080/resource/9750"
},
parents: {
href: "http://localhost:8080/resource/9750/parents"
},
children: {
href: "http://localhost:8080/resource/9750/children"
}
}
}
When it comes to the finder method in your repository you are at liberty to either override the one provided in the CrudRepository in your own interface, or provide an alternative which may be T findByUuid(Long uuid).
Depending on how you are modelling your classes you can rely on the derived query from your method name, or you can annotate with a query, something like:
#Query(value = "MATCH (n:YourNodeType{uuid:{0}) RETURN n")
If you are going to be using a specific UUID class then you will need to tell Neo how to persist the UUID value. If you are will store it as a String (as seems sensible) then I believe there is no need to annotate the field, anything else then you will need a GraphProperty annotation:
#GraphProperty(propertyType = Long.class)
Again depending on the class of UUID you may need to register a conversion class with Spring which implements the org.springframework.core.convert.converter.Converter interface and converts between your domain class type (UUID) and the stored type (String).
Or, just convert the UUID to a string and store it yourself and don't worry about all the conversion.
Whatever you do, make sure that your new uuid is indexed and presumably unique.
You can add a String attribute to your entities, calling it uuid, and simply declare a E findByUuid(String uuid) in your Repository for E, Spring Data will automatically generate code for it.
For example:
#NodeEntity
public class Entity {
...
#Indexed
private String uuid;
...
public String getUuid() {
return uuid;
}
void setUuid(String uuid) {
this.uuid = uuid;
}
...
}
public interface EntityRepository extends GraphRepository<Entity> {
...
Entity findByUuid(String uuid);
...
}

combining criteria from params array

This example here is from the grails docs:
def emeaCriteria = {
eq "region", "EMEA"
}
def results = Airport.withCriteria {
emeaCriteria.delegate = delegate
emeaCriteria()
flights {
like "number", "BA%"
}
}
My webpage is passing back a checkbox group of ethnicities, returning the row ids. So what the server gets is:
ethnicity:[1, 4]
or if the user only picks one ethnicity:
ethnicity:4
def criteria = { params ->
//handle case where only one ethnicity is returned as just a string, not a list of strings
def list = params.ethnicty instanceof String ? [params.ethnicty] : params.ethnicity
if (list) {
inList('ethnicity', list)
}
}
I'm getting an error: java.lang.String cannot be cast to java.lang.Enum.
If I didn't have a list I think I could figure it out. The params are sending back string values, and they need to be converted to the enum class. But within the closure, how do you convert each entry into a list to the enum?
I figured it out through a combination of multiple website posts and with help from dmahapatro above.
def genderCriteria = {
if (params.gender) {
inList('gender', params.list('gender').collect { Gender.valueOf(it)} )
}
}
If a webpage passes back one or more enums (a single string or a list of strings) and you want criteria to check values from the list passed back, you have to provide a list of enum types (not strings or ints).
Here is my enum class for reference:
public enum Gender {
M('Male'),
F('Female'),
U('Unknown')
final String value
Gender(String value) {
this.value = value
}
public String toString() {
value
}
public String getKey() {
name()
}
public String getValue() {
value
}
}
And my criteria builder:
def c = MyDomain.createCriteria()
results = c.list {
genderCriteria.delegate = delegate
genderCriteria(params)
}
Even if no values are passed back for the gender field, it still works (because of the if statement in genderCriteria.
It may not be the best or cleanest solution, but it does work.

mapping a one-to-many where the map key is an int

I am trying to understand the annotations MapKey and MapKeyColumn and I have found them confusing. I was reading an article that made me even more confused (The specification section)
I have an entity with an int field and it is not the primary key:
public class Connections{
...
public final int getConnectionId() {
return this.connectionId;
}
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "FK_StartpointTNA")
public final Endpoint getStartpoint() {
return this.startpoint;
}
...
}
and in the other side I have
public class Endpoint{
...
#OneToMany(mappedBy = "startpoint", fetch = FetchType.LAZY, cascade = { javax.persistence.CascadeType.REMOVE })
#MapKeyColumn(name = "connectionId")
public Map<Integer, Connections> getConnections() {
return this.connections;
}
....
}
I dont know really how to fix this. I keep getting: org.apache.openjpa.persistence.ArgumentException: "connections" declared that it is mapped by "startpoint", but that is a not a field of the related type.
what is the proper way to map this?
As someone posted to the JIRA you opened, get rid of the final on your methods.
From the JPA 2 spec:
(Section 2.1 "The Entity Class", page 21) states: "The entity class must not be final. No methods or persistent instance variables of the entity class may be final."
I had the same issue and getting the same error-message
but in my case I was wrong with
mappedBy = "foo"
where foo must declare the field! Not the column.

Grails: use domain method in named query

in my domain model, I have a method that does something with my data.
e.g.
class Person {
String lastname
String firstname
String bigname() {
return lastname.toUpperCase()
}
static namedQueries = {
withBigname { name ->
eq(this.bigname(), name)
}
}
}
I want to use this method like a property in the named query, but this.bigname() only throws a java.lang.IncompatibleClassChangeError-Exception.
Does anyone know how to use domain methods in criteria and named queries?
Update: I now tried this:
class Person {
String lastname
String firstname
String bigname
static transients = [ 'bigname' ]
def getBigname() {
return lastname.toUpperCase()
}
static namedQueries = {
withBigname { name ->
eq('bigname', name)
}
}
}
But it only results in a "could not resolve property: bigname"-exception...
You cannot use class methods in queries, because queries are actually translated to SQL.
You might be able to get what you need by using writing the complexity in SQL a "SQL Restriction". Search for "SQL Restriction" on http://grails.org/doc/2.0.x/guide/GORM.html
HTH
Try to name the method in JavaBean getter and setter notation.
Rename method bigname() to getBigname().
On a static closure, you don't have a this. You'll either need to store the bigname in the Database, do some sort of case insensitive criteria.
looks like you are trying to accomplish this:
class Person {
String lastname
String firstname
static namedQueries = {
withName { name ->
eq('lastname', name, [ignoreCase: true])
}
}
}

Resources