Introduction
This tip is for developers developing any app on Google app engine to fasten the process. I wrote this code because I got the job of developing an application on Google app engine and there was not a single complete code to help me. I wrote this so if anyone like me is having trouble, he might find it useful.
Background
I had been assigned with the task of implementing an application using Java on Google app engine, well there was no complete tutorial for such a task. So here it is, you might find it useful.
Using the Code
URLs are defined in /web/WEB-INF/web.xml, as an example /test has been added in the .xml file and a TestController.java is added to handle requests coming from /test method.
TestController.java ADD Case
TestModel testModel = new TestModel();
testModel.setKey(TestManager.getInstance().generateKey());
testModel.setFirstName(req.getParameter("first"));
testModel.setLastName(req.getParameter("last"));
TestManager.getInstance().add(testModel);
TestModel.java
@Entity
public class TestModel extends Model {
@Id
private Key key;
private String firstName;
@Extension(vendorName="datanucleus", key="gae.unindexed",
value="true")
private String lastName;
public Key getKey() {
return key;
}
@Override
public String keyToString() {
return KeyFactory.keyToString(this.key);
}
public void setKey (Key key) {
this.key = key;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
}
TestManager.java
/ *
The base class manager have the responsibilites
of communicating with the datastore
*/
public class TestManager extends Manager {
private static TestManager instance = new TestManager();
private TestManager() {
}
public static TestManager getInstance() {
return instance;
}
@Override
public String getQuery(String key) {
return TestGQL.getInstance().get(key);
}
@Override
public Key generateKey() {
return TestKey.getInstance().generateKey();
}
}
Now I'll tell you how to query a datastore. Each model class that requires querying a datastore will define a GQL class.
TestGQL.java
public class TestGQL extends GQL {
private static TestGQL instance = new TestGQL();
private static boolean initialized = false;
private TestGQL () {
}
public static TestGQL getInstance() {
if (! initialized)
TestGQL.instance.init();
return TestGQL.instance;
}
@Override
public void init() {
TestGQL.instance.addQuery("GetByFirstName",
"select tbl from TestModel tbl where firstName = :firstName");
TestGQL.initialized = true;
}
}
..
.. and this will be the procedure to execute it.
String qs = TestManager.getInstance().getQuery("GetByFirstName");
List<TestModel> list = TestManager.getInstance().query(qs)
.setParameter("firstName", Request.get("first"))
.getResultList();
Now to understand how the key generation mechanism works, each model will have the method to define key for it. If the key should act like a autoincrementer, then I've developed a sequencer
class that does the trick.
I'll show you how to implement a primary key equivalent to autoincrement column.
Note: This was my idea of implementing autoincrement key, if the reader finds a better way to do it, he should share it.
TestKey.java
public class TestKey extends CKey {
private static TestKey instance = new TestKey();
private TestKey() {
}
public static TestKey getInstance() {
return instance;
}
@Override
public Key generateKey() {
this.getSeq().increment();
Sequencer.setSequence(this.getSeq());
return KeyFactory.createKey(this.getParent(), this.getKind(),
this.getSeq().getSeq());
}
@Override
public void init() {
this.setKind("TestModel");
try {
TestSequencer sequencer = (TestSequencer)Sequencer.getSequence
(TestSequencer.class, 1);
this.setSeq(sequencer);
this.setParent(sequencer.getKey());
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
}
Let me tell you about the sequencer
class, the base class has all the methods needed to implement autoincrement column, so if a model requires an autoincrement column, we will create a class for that model and will inherit it from the base Sequencer
class, that's it.
Note: I've implemented this because for datastore app engine JPA does not have a implementation, so I need to write a custom implementation to handle autoincrement logic.
I've also added some utils classes that will parse json response and populate the models, and that will create a json response from a given model.
Parser.java
public class Parser {
public static JSONObject getJsonObject(String post) throws JSONException {
JSONObject jsonObject = new JSONObject(post);
return jsonObject;
}
public static JSONArray getJsonArray(String post) throws JSONException {
JSONArray jsonArray = new JSONArray(post);
return jsonArray;
}
public static JSONObject modelToJSON (Model model, Mapper mapper)
throws NoSuchFieldException, IllegalAccessException, JSONException,
NoSuchMethodException, InvocationTargetException {
JSONObject jsonObject = new JSONObject();
if (mapper.size() > 0) {
for (Entry entry: mapper.getEntrySet()) {
String value = entry.getValue().toString();
String key = entry.getKey().toString();
jsonObject.put(value, model.getProperty(key));
}
return jsonObject;
}
else {
for (String property: model.getProperties()) {
jsonObject.put(property, model.getProperty(property));
}
return jsonObject;
}
}
public static JSONArray modelsToJSON (List models, Mapper mapper)
throws IllegalAccessException, NoSuchFieldException, JSONException,
NoSuchMethodException, InvocationTargetException {
JSONArray jsonArray = new JSONArray();
for (Object model: models) {
jsonArray.put(Parser.modelToJSON((Model)model, mapper));
}
return jsonArray;
}
public static Model jsonObjectToModel(JSONObject jsonObject, Class model, Mapper mapper)
throws IllegalAccessException, InstantiationException,
JSONException, NoSuchFieldException {
Model m = null;
if (mapper.size() > 0) {
m = (Model)model.newInstance();
for (Entry entry: mapper.getEntrySet()) {
String value = entry.getValue().toString();
String key = entry.getKey().toString();
String jValue = jsonObject.get(value).toString();
m.setProperty(key, jValue);
}
return m;
}
else {
m = (Model)model.newInstance();
for (String property: m.getProperties()) {
String jValue = jsonObject.get(property).toString();
m.setProperty(property, jValue);
}
return m;
}
}
public static List<Model> jsonArrayToModel
(JSONArray jsonArray, Class model, Mapper mapper)
throws JSONException, IllegalAccessException,
NoSuchFieldException, InstantiationException {
List<Model> list = new ArrayList<Model>();
int length = jsonArray.length();
for (int index = 0; index < length; index ++) {
list.add(Parser.jsonObjectToModel(jsonArray.getJSONObject(index), model, mapper));
}
return list;
}
}
Some other classes are added as well to handle responses and requests data.
You can download the project from the link at the top of this tip.
Points of Interest
We can still enhance this framework to handle cross group entities, which I haven't done yet.
History
This is the first release of this code.