Queries with Objectify: UmbrellaException - google-app-engine

I am using Objectify to manage GAE Datastore for my GWT app. The problem is that I am not using queries properly and I get UmbrellaExceptions as per below:
Caused by: java.lang.RuntimeException: Server Error: java.lang.String cannot be cast to java.lang.Number
at com.google.web.bindery.requestfactory.shared.Receiver.onFailure(Receiver.java:44)
Say that I have a class Box with a unique field String id. I want to get the Box object whose id == "cHVQP6zZiUjM"
This is how I do it now:
public Box getBox(String boxId)
{
Objectify ofy = ObjectifyService.begin();
Query<Box> q=ofy.query(Box.class).filter("id",boxId);
Box targetBox = q.get();
return targetBox;
}
#Entity
public class Box extends DatastoreObject{
private String id;
private String title;
}
I tried doing this with ofy.load() but that method is not defined in my class Objectify (I don't know why).

Your key is encoded. Try using:
Box targetBox = ofy.get(Box.class, KeyFactory.stringToKey(boxId));
To decode your key.

The short answer: You are missing the #Id annotation in your entity.
The long answer: Id fields are special in the datastore. The id is not a real property, but rather a part of the Key that identifies the entity. You can't really filter on id fields, but you can filter on a special field called __key__. Objectify is somewhat clever about letting you filter by the id field and converting this to a __key__ filter under the covers, but it can't do it if you don't annotate the entity properly!
Actually I'm a little confused because Objectify shouldn't let you register the entity without an #Id field.
By the way, there are two sections of the documentation: Objectify4 (release coming soon) and Objectify3. Since you're using Ofy3, there is no load() method.
Another thing: Get-by-key operations are strongly preferred to queries when the operations are equivalent (as they are in your example).

Related

Is there a way to avoid explicitly writing document fields as Strings in Spring Data MongoDB queries?

I have recently started to use Spring Data MongoDB and I wonder if there is any way to avoid writing entities' attributes explicitly as they are stored in the database. For example, given the following class representing a MongoDB collection:
public class Employee {
#Id
public String id;
private double salary;
...
}
If I want to make a query using MongoTemplate like:
public List findEmployeeBySalaryRange(double salary) {
Query query = new Query();
query.addCriteria(Criteria.where("salary").lt(salary));
...
}
I would like to avoid writing "salary", since that will make the code harder to maintain in the future in case the field name changes. I am thinking of something like getting the field name from the class attribute, but I am not quite sure how. Is there a way to do it? I have looked into the documentation but did not find anything related unless I missed it.
Thanks in advance.
You may create a Utility Class to store all database field names, use #Field annotation on field with constant from that class and use that constant in query to avoid error prone hardcoded Strings.
In Employee Model
#Field(DbFields.SALARY)
private double salary;
In Query,
query.addCriteria(Criteria.where(DbFields.SALARY).lt(salary));
In DbFields Utility class
public static final String SALARY = "salary";

How can I prevent Spring Data Rest from from nulling out #CreatedDate during updates?

I am trying spring-data-rest with spring-data-mongo and a lot of things are working beautifully out of the box, including support for eTag field.
#EnableMongoAuditing annotations works very well too: when a document is created, the #CreatedDate and #LastModifiedDate fields are set.
The problem is that the #CreatedDate field being set to null during updates. I found an unresolved issue Mongo Auditing: #CreatedDate field gets set to null on updates with Spring Data Rest with a suggested workaround of using the #JsonIgnore annotation which does not work for me.
There was also a similar question here which does not appear to be the same issue.
I am using version 1.10.1.RELEASE of spring-data-mongo and 2.6.1.Release of spring-data-rest.
Is there a solution to this issue?
One solution is to tell Jackson to output the field to JSON when serializing the object, but never read the value when deserializing the object, using the access element of JsonProperty:
#Document
public class MyDocument {
#JsonProperty(access = JsonProperty.Access.READ_ONLY)
#CreatedDate
private Instant createdDate;
}
Spring Data REST will still output the createdDate field to JSON, but it will never read from it, including when performing an update.
Note that this will affect the serialization of your document class throughout the entire application. Often this will not be an issue, but it would pose a problem if there are other places in the code that need to be able to deserialize the createdDate from JSON.
Created date make sense only for immutable entities (which you are not going to update)
If entity is updatable, would like to use only last modified instead
For any other cases probably make sense use some history audition log..
#Entity
// ...
public class MyEntity {
// ...
#CreatedDate
private LocalDateTime createdAt; // modifiedAt
#PreUpdate
public void fixSpringDataRestNullDate() {
createdAt = LocalDateTime.now();
}
}

ApiTransformer for parametrized, unavailable type

I'm using Objectify and wish to have its Key<> type passed around in my API. I've created an ApiTransformer, but my questions is where to declare it, since the serialized Key<> class is not available, hence I cannot declare its transformer as a class annotation. I tried declaring it in the #Api annotation, but it doesn't work, I still get the error:
There was a problem generating the API metadata for your Cloud Endpoints classes: java.lang.IllegalArgumentException: Parameterized type com.googlecode.objectify.Key<[my package].User> not supported.
The ApiTransformer looks like:
public class KeyTransformer implements Transformer<Key<?>, String> {
public String transformTo(Key<?> in) {
return in.getString();
}
public Key<?> transformFrom(String in) {
return Key.valueOf(in);
}
}
And in my #Api I have:
#Api(name = "users", version = "v1",transformers = {KeyTransformer.class})
Unfortunately you can't. As you said you need to declare it on the Key class, your only chances to make this work are either.
1) Recompile the Key class for objectify with the #transformer annotation.
2) Extend the Key class with your own implementation and define the transformer there.
I don't really like any of those options so the way i usually resolve this is to hide the key object getter (by using #ApiResourceProperty(ignored=AnnotationBoolean.TRUE)) and only expose the id from that key.
That way you get a Endpoints frendly object, the only downside is you'll have to reconstitute the key using Key.create(YourClass.class, longId) manually whenever you need it.
You can add transforms to 3rd party classes by listing the transform in #Api annotation. I'm not dead sure it'll work parameterized class, but I don't see why not.
https://cloud.google.com/appengine/docs/java/endpoints/javadoc/com/google/api/server/spi/config/Api#transformers()

Add field to App Engine-hosted database

I'm currently developing a mobile application who uses a Google App Engine-hosted web service.
But i'm facing an issue. I just want to add a field in one my database's table.
App Engine doesn't use classic SQL syntax, but GQL. So i cannot use the ALTER TABLE statement. How can i do this with GQL ? I looked for a solution on the web, but there's not a lot of help.
public MyEntity() {
}
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Key idStation;
private String name;
private double longitude;
private double latitude;
private java.util.Date dateRefresh = new Date(); //the field i want to add in DB
So, now when i create a "MyEntity" object, it should add the "dateRefresh" field into the database... I create my object like this:
MyEntity station = new MyEntity();
station.setName("test");
station.setLatitude(0);
station.setLongitude(0);
station.setDateRefresh(new Date("01/01/1980"));
DaoFactory.getStationDao().addStation(station);
addStation method:
#Override
public MyEntity addStation(MyEntity station) {
EntityManager em = PersistenceManager.getEntityManagerFactory().createEntityManager();
try {
em.getTransaction().begin();
em.persist(station);
em.getTransaction().commit();
} finally {
if(em.getTransaction().isActive()) em.getTransaction().rollback();
em.close();
}
return station;
}
The field "dateRefresh" is never created into my DB...
Someone to help me please ?
Thanks in advance
Just add another field to your data structure, maybe providing a default clause, and that's all. For example, if you have a UserAccount:
class UserAccount(db.Model):
user = db.UserProperty()
user_id = db.StringProperty()
you may easily add:
class UserAccount(db.Model):
user = db.UserProperty()
user_id = db.StringProperty()
extra_info = db.IntegerProperty(default=0)
timezone = db.StringProperty(default="UTC")
and let it go.
While the datastore kinda mimics tables, data is stored on a per entity basis. There is no schema or table.
All you need to do is update your model class, and new entities will be saved with the structure (fields) of the new entity.
Old entities and indexes, however, are not automatically updated. They still have the same fields as they had when they were originally written to the datastore.
There's two ways to do this. One is to make sure your code can handle situations where your new properties are missing, ie make sure no exceptions are thrown, or handle the exceptions properly when you're missing the properties.
The second way is to write a little function (usu a mapreduce function) to update every entity with appropriate or null values for your new properties.
Note that indexes are not updated unless the entity is written. So if you add a new indexed property, old entities won't show up when you query for the new property. In this case, you must use the second method and update all the entities in the datastore so that they are indexed.

Displaying Mutable PostgreSQL Arrays in the NetBeans Master/Detail Sample Form using JPA 1.0

Some Background
I have a game database with a table called Games that has multiple attributes and one called Genres. The Genres attribute is defined as an integer[] in PostgreSQL. For the sake of simplicity, I'm not using any foreign key constraints, but essentially each integer in this array is a foreign key constraint on the id attribute in the Genres table. First time working with the NetBeans Master/Detail Sample Form and Java persistence and it's been working great so far except for 1 thing. I get this error when the program tries to display a column that has a 1-dimensional integer array. In this example, the value is {1, 11}.
Exception Description: The object [{1,11}], of class [class org.postgresql.jdbc3.Jdbc3Array], from mapping [oracle.toplink.essentials.mappings.DirectToFieldMapping[genres-->final.public.games.genres]] with descriptor [RelationalDescriptor(finalproject.Games --> [DatabaseTable(final.public.games)])], could not be converted to [class [B].
Exception [TOPLINK-3002] (Oracle TopLink Essentials - 2.0.1 (Build b09d-fcs (12/06/2007))): oracle.toplink.essentials.exceptions.ConversionException
My Research
From what I've been able to read, it looks like PostgreSQL arrays need something special done to them before you can display and edit them in this template. By default, the sample form uses TopLink Essentials (JPA 1.0) as its persistence library, but I can also use Hibernate (JPA 1.0).
Here is the code that needs to be changed in some way. From the Games.java file:
#Entity
#Table(name = "games", catalog = "final", schema = "public")
#NamedQueries({
// omitting named queries
#NamedQuery(name = "Games.findByGenres", query = "SELECT g FROM Games g WHERE g.genres = :genres")
})
public class Games implements Serializable {
#Transient
private PropertyChangeSupport changeSupport = new PropertyChangeSupport(this);
private static final long serialVersionUID = 1L;
// omitting other attributes
#Column(name = "genres")
private Serializable genres;
// omitting constructors and other getters/setters
public Serializable getGenres() {
return genres;
}
public void setGenres(Serializable genres) {
Serializable oldGenres = this.genres;
this.genres = genres;
changeSupport.firePropertyChange("genres", oldGenres, genres);
}
} // end class Games
Here are also some of the sites that might have the solution that I'm just not understanding:
https://forum.hibernate.org/viewtopic.php?t=946973
http://blog.xebia.com/2009/11/09/understanding-and-writing-hibernate-user-types/
// omitted hyperlink due to user restriction
Attempted Solutions
I'm able to get the data to display if I change the type of genres to String, but it is immutable and I cannot edit it. This is what I changed to do this:
#Column(name = "genres")
private String genres;
public String getGenres() {
return genres;
}
public void setGenres(String genres) {
String oldGenres = this.genres;
this.genres = genres;
changeSupport.firePropertyChange("genres", oldGenres, genres);
}
I also attempted to create a UserType file for use with Hibernate (JPA 1.0), but had no idea what was going wrong there.
I also attempted to use the #OneToMany and other tags, but these aren't working probably because I'm not using them properly.
What I'm Looking For
There has to be a simple way to get this data to display and make it editable, but since I'm completely new to persistence, I have no idea what to do.
The effort put into your question shows. Unfortunately JPA does not currently support PostgreSQL arrays. The fundamental problem is that arrays are not frequently used in many other databases frequently and so heavy reliance on them is somewhat PostgreSQL specific. Thus you can expect that general cross-db persistence API's are not generally going to support them well if at all. JPA is no exception, having currently no support for PostgreSQL arrays.
I have been looking at writing my own persistence API in Java that would support arrays, but it hasn't happened yet, would be PostgreSQL-only when written, and would be based on a very different principle than JPA and friends.

Resources