com.samsung.appengine.web.server.RemindMeServlet.java Source code

Java tutorial

Introduction

Here is the source code for com.samsung.appengine.web.server.RemindMeServlet.java

Source

/*
 * Copyright 2010 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.samsung.appengine.web.server;

import java.io.IOException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.logging.Logger;

import javax.jdo.JDOObjectNotFoundException;
import javax.jdo.PersistenceManager;
import javax.jdo.Query;
import javax.jdo.Transaction;
import javax.servlet.http.HttpServletRequest;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import com.samsung.android.c2dm.server.C2DMessaging;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.users.User;
import com.samsung.appengine.allshared.AllConfig;
import com.samsung.appengine.allshared.JsonRpcException;
import com.samsung.appengine.allshared.JsonRpcMethod;
import com.samsung.appengine.allshared.RemindMeProtocol;
import com.samsung.appengine.javashared.Util;
import com.samsung.appengine.jsonrpc.server.JsonRpcServlet;
import com.samsung.appengine.web.server.ModelImpl.Alert;
import com.samsung.appengine.web.server.ModelImpl.DeviceRegistration;
import com.samsung.appengine.web.server.ModelImpl.UserInfo;

/**
 * The server side implementation of the JumpNote JSON-RPC service.
 */
@SuppressWarnings("serial")
public class RemindMeServlet extends JsonRpcServlet {

    public static final int DEBUG = -1; // 1 == yes, 0 == no, -1 == decide based on server info

    private static final Logger log = Logger.getLogger(RemindMeServlet.class.getName());

    private static final String PROTOCOL_VERSION = "1";
    public static final String DEVICE_TYPE_ANDROID = "android";

    @Override
    @SuppressWarnings("all")
    protected boolean isDebug(HttpServletRequest req) {
        if (DEBUG == 1)
            return true;
        else if (DEBUG == 0)
            return false;
        else
            return this.getServletContext().getServerInfo().contains("Development");
    }

    @JsonRpcMethod(method = RemindMeProtocol.UserInfo.METHOD)
    public JSONObject userInfo(final CallContext context) throws JSONException, JsonRpcException {
        String continueUrl = context.getParams().optString(RemindMeProtocol.UserInfo.ARG_LOGIN_CONTINUE, "/");

        JSONObject data = new JSONObject();
        if (context.getUserService().isUserLoggedIn()) {
            UserInfo userInfo = new UserInfo(context.getUserService().getCurrentUser());
            data.put(RemindMeProtocol.UserInfo.RET_USER, userInfo.toJSON());
            data.put(RemindMeProtocol.UserInfo.RET_LOGOUT_URL,
                    context.getUserService().createLogoutURL(continueUrl));
        } else {
            data.put(RemindMeProtocol.UserInfo.RET_LOGIN_URL, context.getUserService().createLoginURL(continueUrl));
        }

        return data;
    }

    @JsonRpcMethod(method = RemindMeProtocol.ServerInfo.METHOD)
    public JSONObject serverInfo(final CallContext context) throws JSONException, JsonRpcException {
        JSONObject responseJson = new JSONObject();
        responseJson.put(RemindMeProtocol.ServerInfo.RET_PROTOCOL_VERSION, PROTOCOL_VERSION);
        return responseJson;
    }

    @JsonRpcMethod(method = RemindMeProtocol.AlertsList.METHOD, requires_login = true)
    public JSONObject notesList(final CallContext context) throws JSONException, JsonRpcException {
        UserInfo userInfo = getCurrentUserInfo(context);

        // Note: this would be inefficient for large note collections
        Query query = context.getPersistenceManager().newQuery(Alert.class);
        query.setFilter("ownerKey == ownerKeyParam && pendingDelete == false");
        query.declareParameters(Key.class.getName() + " ownerKeyParam");
        @SuppressWarnings("unchecked")
        List<Alert> alerts = (List<Alert>) query.execute(userInfo.getKey());

        JSONObject responseJson = new JSONObject();
        try {
            JSONArray notesJson = new JSONArray();
            for (Alert note : alerts) {
                notesJson.put(note.toJSON());
            }

            responseJson.put(RemindMeProtocol.AlertsList.RET_NOTES, notesJson);
        } catch (JSONException e) {
            throw new JsonRpcException(500, "Error serializing response.", e);
        }

        return responseJson;
    }

    @JsonRpcMethod(method = RemindMeProtocol.AlertsGet.METHOD, requires_login = true)
    public JSONObject notesGet(final CallContext context) throws JSONException, JsonRpcException {
        UserInfo userInfo = getCurrentUserInfo(context);

        String noteId = context.getParams().getString(RemindMeProtocol.AlertsGet.ARG_ID);
        Key alertKey = Alert.makeKey(userInfo.getId(), noteId);
        try {
            Alert note = context.getPersistenceManager().getObjectById(Alert.class, alertKey);
            if (note.isPendingDelete()) {
                throw new JDOObjectNotFoundException();
            }
            if (!note.getOwnerId().equals(userInfo.getId())) {
                throw new JsonRpcException(403, "You do not have permission to access this note.");
            }
            return (JSONObject) note.toJSON();
        } catch (JDOObjectNotFoundException e) {
            throw new JsonRpcException(404, "Alert with ID " + noteId + " does not exist.");
        }
    }

    @JsonRpcMethod(method = RemindMeProtocol.AlertsCreate.METHOD, requires_login = true)
    public JSONObject notesCreate(final CallContext context) throws JSONException, JsonRpcException {
        UserInfo userInfo = getCurrentUserInfo(context);

        String clientDeviceId = null;
        JSONObject noteJson;
        Alert note;
        try {
            clientDeviceId = context.getParams().optString(RemindMeProtocol.ARG_CLIENT_DEVICE_ID);
            noteJson = context.getParams().getJSONObject(RemindMeProtocol.AlertsCreate.ARG_ALERT);
            noteJson.put("owner_id", userInfo.getId());
            note = new Alert(noteJson);
        } catch (JSONException e) {
            throw new JsonRpcException(400, "Invalid note parameter.", e);
        }

        context.getPersistenceManager().makePersistent(note);
        noteJson = (JSONObject) note.toJSON(); // get new parameters like ID, creation date, etc.

        enqueueDeviceMessage(context.getPersistenceManager(), userInfo, clientDeviceId);

        JSONObject responseJson = new JSONObject();
        responseJson.put(RemindMeProtocol.AlertsCreate.RET_ALERT, noteJson);
        return responseJson;
    }

    public void enqueueDeviceMessage(PersistenceManager pm, UserInfo userInfo, String clientDeviceId) {

        Query query = pm.newQuery(DeviceRegistration.class);
        query.setFilter("ownerKey == ownerKeyParam");
        query.declareParameters(Key.class.getName() + " ownerKeyParam");
        @SuppressWarnings("unchecked")
        List<DeviceRegistration> registrations = (List<DeviceRegistration>) query.execute(userInfo.getKey());

        int numDeviceMessages = 0;
        for (DeviceRegistration registration : registrations) {
            if (registration.getDeviceId().equals(clientDeviceId) || registration.getRegistrationToken() == null)
                continue;
            if (DEVICE_TYPE_ANDROID.equals(registration.getDeviceType())) {
                ++numDeviceMessages;
                String email = userInfo.getEmail();

                String collapseKey = Long.toHexString(email.hashCode());

                try {
                    C2DMessaging.get(getServletContext()).sendWithRetry(registration.getRegistrationToken(),
                            collapseKey, AllConfig.C2DM_MESSAGE_EXTRA, AllConfig.C2DM_MESSAGE_SYNC,
                            AllConfig.C2DM_ACCOUNT_EXTRA, email);
                } catch (IOException ex) {
                    log.severe("Can't send C2DM message, next manual sync " + "will get the changes.");
                }
            }
        }

        log.info("Scheduled " + numDeviceMessages + " C2DM device messages for user " + userInfo.getEmail() + ".");
    }
    //
    //    @JsonRpcMethod(method = RemindMeProtocol.AlertsEdit.METHOD, requires_login = true)
    //    public JSONObject notesEdit(final CallContext context) throws JSONException, JsonRpcException {
    //        UserInfo userInfo = getCurrentUserInfo(context);
    //
    //        String clientDeviceId;
    //        JSONObject noteJson;
    //        Alert note;
    //        String noteId = "n/a";
    //        Transaction tx = context.getPersistenceManager().currentTransaction();
    //        try {
    //            clientDeviceId = context.getParams().optString(RemindMeProtocol.ARG_CLIENT_DEVICE_ID);
    //            noteJson = context.getParams().getJSONObject(RemindMeProtocol.AlertsEdit.ARG_NOTE);
    //            noteId = noteJson.getString("id");
    //            Key noteKey = Alert.makeKey(userInfo.getId(), noteId);
    //
    //            tx.begin();
    //            note = context.getPersistenceManager().getObjectById(Alert.class, noteKey);
    //            if (!note.getOwnerId().equals(userInfo.getId())) {
    //                throw new JsonRpcException(403, "You do not have permission to modify this note.");
    //            }
    //            noteJson.put("owner_id", userInfo.getId());
    //            note.fromJSON(noteJson);
    //            tx.commit();
    //        } catch (JSONException e) {
    //            throw new JsonRpcException(400, "Invalid note parameter.", e);
    //        } catch (JDOObjectNotFoundException e) {
    //            throw new JsonRpcException(404, "Alert with ID " + noteId + " does not exist.");
    //        } finally {
    //            if (tx.isActive()) {
    //                tx.rollback();
    //            }
    //        }
    //
    //        enqueueDeviceMessage(context.getPersistenceManager(), userInfo, clientDeviceId);
    //
    //        noteJson = (JSONObject) note.toJSON(); // get more parameters like ID, creation date, etc.
    //        JSONObject responseJson = new JSONObject();
    //        responseJson.put(RemindMeProtocol.AlertsEdit.RET_NOTE, noteJson);
    //        return responseJson;
    //    }

    @JsonRpcMethod(method = RemindMeProtocol.AlertsDelete.METHOD, requires_login = true)
    public JSONObject notesDelete(final CallContext context) throws JSONException, JsonRpcException {
        UserInfo userInfo = getCurrentUserInfo(context);

        String clientDeviceId = null;
        Alert alert;
        String noteId;
        try {
            clientDeviceId = context.getParams().optString(RemindMeProtocol.ARG_CLIENT_DEVICE_ID);
            noteId = context.getParams().getString(RemindMeProtocol.AlertsDelete.ARG_ID);
        } catch (JSONException e) {
            throw new JsonRpcException(400, "Invalid note ID.", e);
        }

        Transaction tx = context.getPersistenceManager().currentTransaction();
        try {
            tx.begin();
            alert = context.getPersistenceManager().getObjectById(Alert.class,
                    Alert.makeKey(userInfo.getId(), noteId));
            if (alert.isPendingDelete()) {
                throw new JDOObjectNotFoundException();
            }
            if (!alert.getOwnerId().equals(userInfo.getId())) {
                throw new JsonRpcException(403, "You do not have permission to modify this note.");
            }

            alert.markForDeletion();
            tx.commit();
        } catch (JDOObjectNotFoundException e) {
            throw new JsonRpcException(404, "Alert with ID " + noteId + " does not exist.");
        } finally {
            if (tx.isActive()) {
                tx.rollback();
            }
        }

        enqueueDeviceMessage(context.getPersistenceManager(), userInfo, clientDeviceId);
        return null;
    }

    @JsonRpcMethod(method = RemindMeProtocol.AlertsSync.METHOD, requires_login = true)
    public JSONObject notesSync(final CallContext context) throws JSONException, JsonRpcException {
        // This method should return a list of updated notes since a current
        // date, optionally reconciling/merging a set of a local notes.
        String clientDeviceId = null;
        UserInfo userInfo = getCurrentUserInfo(context);
        Date sinceDate;

        try {
            clientDeviceId = context.getParams().optString(RemindMeProtocol.ARG_CLIENT_DEVICE_ID);
            sinceDate = Util
                    .parseDateISO8601(context.getParams().getString(RemindMeProtocol.AlertsSync.ARG_SINCE_DATE));
        } catch (ParseException e) {
            throw new JsonRpcException(400, "Invalid since_date.", e);
        } catch (JSONException e) {
            throw new JsonRpcException(400, "Invalid since_date.", e);
        }

        JSONObject responseJson = new JSONObject();
        JSONArray notesJson = new JSONArray();
        Transaction tx = context.getPersistenceManager().currentTransaction();
        Date newSinceDate = new Date();
        try {
            tx.begin();
            List<Alert> localAlerts = new ArrayList<Alert>();
            if (context.getParams().has(RemindMeProtocol.AlertsSync.ARG_LOCAL_ALERTS)) {
                JSONArray localChangesJson = context.getParams()
                        .getJSONArray(RemindMeProtocol.AlertsSync.ARG_LOCAL_ALERTS);
                for (int i = 0; i < localChangesJson.length(); i++) {
                    try {
                        JSONObject noteJson = localChangesJson.getJSONObject(i);

                        if (noteJson.has("id")) {
                            Key existingAlertKey = Alert.makeKey(userInfo.getId(), noteJson.get("id").toString());
                            try {
                                Alert existingAlert = (Alert) context.getPersistenceManager()
                                        .getObjectById(Alert.class, existingAlertKey);
                                if (!existingAlert.getOwnerId().equals(userInfo.getId())) {
                                    // User doesn't have permission to edit this note. Instead of
                                    // throwing an error, just re-create it on the server side.
                                    //throw new JsonRpcException(403,
                                    //        "You do not have permission to modify this note.");
                                    noteJson.remove("id");
                                }
                            } catch (JDOObjectNotFoundException e) {
                                // Alert doesn't exist, instead of throwing an error,
                                // just re-create the note on the server side (unassign its ID).
                                //throw new JsonRpcException(404, "Alert with ID "
                                //        + noteJson.get("id").toString() + " does not exist.");
                                noteJson.remove("id");
                            }
                        }

                        noteJson.put("owner_id", userInfo.getId());
                        Alert localAlert = new Alert(noteJson);
                        localAlerts.add(localAlert);
                    } catch (JSONException e) {
                        throw new JsonRpcException(400, "Invalid local note content.", e);
                    }
                }
            }

            // Query server-side note changes.
            Query query = context.getPersistenceManager().newQuery(Alert.class);
            query.setFilter("ownerKey == ownerKeyParam && modifiedDate > sinceDate");
            query.setOrdering("modifiedDate desc");
            query.declareParameters(Key.class.getName() + " ownerKeyParam, java.util.Date sinceDate");
            @SuppressWarnings("unchecked")
            List<Alert> alerts = (List<Alert>) query.execute(userInfo.getKey(), sinceDate);

            // Now merge the lists and conflicting objects.
            Reconciler<Alert> reconciler = new Reconciler<Alert>() {
                @Override
                public Alert reconcile(Alert o1, Alert o2) {
                    boolean pick1 = o1.getModifiedDate().after(o2.getModifiedDate());

                    // Make sure only the chosen version of the note is persisted
                    context.getPersistenceManager().makeTransient(pick1 ? o2 : o1);

                    return pick1 ? o1 : o2;
                }
            };

            Collection<Alert> reconciledAlerts = reconciler.reconcileLists(alerts, localAlerts);

            for (Alert alert : reconciledAlerts) {
                // Save the note.
                context.getPersistenceManager().makePersistent(alert);

                // Put it in the response output.
                notesJson.put(alert.toJSON());
            }
            tx.commit();
        } finally {
            if (tx.isActive()) {
                tx.rollback();
            }
        }

        enqueueDeviceMessage(context.getPersistenceManager(), userInfo, clientDeviceId);

        responseJson.put(RemindMeProtocol.AlertsSync.RET_ALERTS, notesJson);
        responseJson.put(RemindMeProtocol.AlertsSync.RET_NEW_SINCE_DATE, Util.formatDateISO8601(newSinceDate));
        return responseJson;
    }

    @JsonRpcMethod(method = RemindMeProtocol.DevicesRegister.METHOD, requires_login = true)
    public JSONObject devicesRegister(final CallContext context) throws JSONException, JsonRpcException {
        UserInfo userInfo = getCurrentUserInfo(context);

        JSONObject registrationJson;
        DeviceRegistration registrationParam;
        try {
            registrationJson = context.getParams().getJSONObject(RemindMeProtocol.DevicesRegister.ARG_DEVICE);
            registrationJson.put("owner_id", userInfo.getId());
            registrationParam = new DeviceRegistration(registrationJson);
        } catch (JSONException e) {
            throw new JsonRpcException(400, "Invalid device parameter.", e);
        }

        Transaction tx = context.getPersistenceManager().currentTransaction();
        try {
            tx.begin();
            Query query = context.getPersistenceManager().newQuery(DeviceRegistration.class);
            query.setFilter("ownerKey == ownerKeyParam && deviceId == deviceIdParam");
            query.declareParameters(Key.class.getName() + " ownerKeyParam, String deviceIdParam");
            @SuppressWarnings("unchecked")
            List<DeviceRegistration> registrations = (List<DeviceRegistration>) query.execute(userInfo.getKey(),
                    registrationParam.getDeviceId());

            // Update all existing registration tokens.
            boolean registeredForUser = false;
            for (DeviceRegistration registration : registrations) {
                if (registration.getOwnerId().equals(userInfo.getId()))
                    registeredForUser = true;
                registration.setRegistrationToken(registrationParam.getRegistrationToken());
            }

            // Register the device for the logged in user if not already registered.
            if (!registeredForUser) {
                context.getPersistenceManager().makePersistent(registrationParam);
            }
            tx.commit();
        } finally {
            if (tx.isActive()) {
                tx.rollback();
            }
        }

        registrationJson = (JSONObject) registrationParam.toJSON();

        JSONObject responseJson = new JSONObject();
        responseJson.put(RemindMeProtocol.DevicesRegister.RET_DEVICE, registrationJson);
        return responseJson;
    }

    @JsonRpcMethod(method = RemindMeProtocol.DevicesUnregister.METHOD, requires_login = true)
    public JSONObject devicesUnregister(final CallContext context) throws JSONException, JsonRpcException {
        UserInfo userInfo = getCurrentUserInfo(context);

        String deviceId;
        try {
            deviceId = context.getParams().getString(RemindMeProtocol.DevicesUnregister.ARG_DEVICE_ID);
        } catch (JSONException e) {
            throw new JsonRpcException(400, "Invalid device ID parameter.", e);
        }

        Transaction tx = context.getPersistenceManager().currentTransaction();
        try {
            tx.begin();
            Query query = context.getPersistenceManager().newQuery(DeviceRegistration.class);
            query.setFilter("ownerKey == ownerKeyParam && deviceId == deviceIdParam");
            query.declareParameters(Key.class.getName() + " ownerKeyParam, String deviceIdParam");
            @SuppressWarnings("unchecked")
            List<DeviceRegistration> registrations = (List<DeviceRegistration>) query.execute(userInfo.getKey(),
                    deviceId);

            if (registrations.size() == 0) {
                throw new JsonRpcException(404, "Device with provided ID is not registered.");
            }

            for (DeviceRegistration registration : registrations) {
                context.getPersistenceManager().deletePersistent(registration);
            }
            tx.commit();
        } finally {
            if (tx.isActive()) {
                tx.rollback();
            }
        }

        return null;
    }

    @JsonRpcMethod(method = RemindMeProtocol.DevicesClear.METHOD, requires_login = true)
    public JSONObject devicesClear(final CallContext context) throws JSONException, JsonRpcException {
        UserInfo userInfo = getCurrentUserInfo(context);

        Transaction tx = context.getPersistenceManager().currentTransaction();
        try {
            tx.begin();
            Query query = context.getPersistenceManager().newQuery(DeviceRegistration.class);
            query.setFilter("ownerKey == ownerKeyParam");
            query.declareParameters(Key.class.getName() + " ownerKeyParam");
            @SuppressWarnings("unchecked")
            List<DeviceRegistration> registrations = (List<DeviceRegistration>) query.execute(userInfo.getKey());

            for (DeviceRegistration registration : registrations) {
                context.getPersistenceManager().deletePersistent(registration);
            }
            tx.commit();
        } finally {
            if (tx.isActive()) {
                tx.rollback();
            }
        }

        return null;
    }

    public UserInfo getCurrentUserInfo(final CallContext context) {
        if (!context.getUserService().isUserLoggedIn())
            return null;

        User user = context.getUserService().getCurrentUser();

        try {
            UserInfo userInfo = context.getPersistenceManager().getObjectById(UserInfo.class, user.getUserId());
            return userInfo;
        } catch (JDOObjectNotFoundException e) {
            UserInfo userInfo = new UserInfo(user);
            context.getPersistenceManager().makePersistent(userInfo);
            return userInfo;
        }
    }
}