Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / Java

Google App Engine JAVA

5.00/5 (5 votes)
11 Jan 2014CPOL2 min read 13.4K   277  
Google App Engine - JAVA, DataStore implementation using JPA 1.0

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

Java
//
        TestModel testModel = new TestModel();
        testModel.setKey(TestManager.getInstance().generateKey());
        testModel.setFirstName(req.getParameter("first"));
        testModel.setLastName(req.getParameter("last"));
        TestManager.getInstance().add(testModel);
//
//      The rest is handled by the base classes.

TestModel.java

Java
/*
    The Model class is the base class, this class includes
    methods such as getAttribute, setAttribute through reflection
*/

@Entity
public class TestModel extends Model {

    @Id
    private Key key;

    private String firstName;

    @Extension(vendorName="datanucleus", key="gae.unindexed",
    value="true") // to unidex a column, by default every column is indexed.
    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

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;
    }

    /*
        Later on i'll share the TestGQL class
        with you that handles querying datastore.
    */
    @Override
    public String getQuery(String key) {
        return TestGQL.getInstance().get(key);
    }

    /*
        In datastore each entity has a key which is required,
        this function does the trick.
    */
    @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

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;
    }

    // Here we will define our queries
    @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

Java
/*
    The base class CKey contains the required attribute to make a key
*/
public class TestKey extends CKey {

    private static TestKey instance = new TestKey();

    private TestKey() {

    }

    public static TestKey getInstance() {
        return instance;
    }

    /*
        This method generates the autoincrement key for the model i.e TestModel
    */
    @Override
    public Key generateKey() {
        this.getSeq().increment(); // here we set the information
        // of the sequencer number that's currently available
        Sequencer.setSequence(this.getSeq()); // i've developed a class
        // Sequencer that records the sequence number for that particular Model it belongs to.
        return KeyFactory.createKey(this.getParent(), this.getKind(),
        this.getSeq().getSeq()); // this is the build-in method of google app engine java sdk.
    }

    @Override
    public void init() {
        this.setKind("TestModel");
        try {
            TestSequencer sequencer = (TestSequencer)Sequencer.getSequence
            (TestSequencer.class, 1); // Initializing the sequencer with default 1
            this.setSeq(sequencer);
            this.setParent(sequencer.getKey());
        } catch (IllegalAccessException e) {
            e.printStackTrace();  //To change body of catch statement
                //use File | Settings | File Templates.
        } catch (InstantiationException e) {
            e.printStackTrace();  //To change body of catch statement
                //use File | Settings | File Templates.
        }
    }
}

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

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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)