com.google.appinventor.components.runtime.TinyWebDB.java Source code

Java tutorial

Introduction

Here is the source code for com.google.appinventor.components.runtime.TinyWebDB.java

Source

// -*- mode: java; c-basic-offset: 2; -*-
// Copyright 2009-2011 Google, All Rights reserved
// Copyright 2011-2012 MIT, All rights reserved
// Released under the MIT License https://raw.github.com/mit-cml/app-inventor/master/mitlicense.txt

package com.google.appinventor.components.runtime;

import com.google.appinventor.components.annotations.DesignerComponent;
import com.google.appinventor.components.annotations.DesignerProperty;
import com.google.appinventor.components.annotations.PropertyCategory;
import com.google.appinventor.components.annotations.SimpleEvent;
import com.google.appinventor.components.annotations.SimpleFunction;
import com.google.appinventor.components.annotations.SimpleObject;
import com.google.appinventor.components.annotations.SimpleProperty;
import com.google.appinventor.components.annotations.UsesPermissions;
import com.google.appinventor.components.common.ComponentCategory;
import com.google.appinventor.components.common.PropertyTypeConstants;
import com.google.appinventor.components.common.YaVersion;
import com.google.appinventor.components.runtime.collect.Lists;
import com.google.appinventor.components.runtime.errors.YailRuntimeError;
import com.google.appinventor.components.runtime.util.AsyncCallbackPair;
import com.google.appinventor.components.runtime.util.AsynchUtil;
import com.google.appinventor.components.runtime.util.JsonUtil;
import com.google.appinventor.components.runtime.util.WebServiceUtil;

import android.os.Handler;

import org.apache.http.NameValuePair;
import org.apache.http.message.BasicNameValuePair;
import org.json.JSONArray;
import org.json.JSONException;

// When the component is installed in App Inventor, the Javadoc
// comments will become included in the automatically-generated system
// documentation, except for lines starting with tags (such as @author).
/**
 * The TinyWebDB component communicates with a Web service to store
 * and retrieve information.  Although this component is usable, it is
 * very limited and meant primarily as a demonstration for people who
 * would like to create their own components that talk to the Web.
 * The accompanying Web service is at
 * (http://appinvtinywebdb.appspot.com).  The component has methods to
 * store a value under a tag and to retrieve the value associated with
 * the tag.  The interpretation of what "store" and "retrieve" means
 * is up to the Web service.  In this implementation, all tags and
 * values are strings (text).  This restriction may be relaxed in
 * future versions.
 *
 * @author halabelson@google.com (Hal Abelson)
 */

// The annotations here provide information to the compiler about
// integrating the component into App Inventor system.  The following
// three annotations stipulate that TinyWeb DB will appear in the
// designer, that it will be an object in the App Inventor language,
// and say what Android system permissions it requires.
//

@DesignerComponent(version = YaVersion.TINYWEBDB_COMPONENT_VERSION, description = "Non-visible component that communicates with a Web service to store and "
        + "retrieve information.", category = ComponentCategory.STORAGE, nonVisible = true, iconName = "images/tinyWebDB.png")
@SimpleObject
@UsesPermissions(permissionNames = "android.permission.INTERNET")
public class TinyWebDB extends AndroidNonvisibleComponent implements Component {

    private static final String LOG_TAG = "TinyWebDB";
    private static final String STOREAVALUE_COMMAND = "storeavalue";
    private static final String TAG_PARAMETER = "tag";
    private static final String VALUE_PARAMETER = "value";
    private static final String GETVALUE_COMMAND = "getvalue";

    private String serviceURL;
    private Handler androidUIHandler;

    /**
     * Creates a new TinyWebDB component.
     *
     * @param container the Form that this component is contained in.
     */
    public TinyWebDB(ComponentContainer container) {
        super(container.$form());
        // We use androidUIHandler when we set up operations (like
        // postStoreVaue and getStoreValue) that run asynchronously in a
        // separate thread, but which themselves want to cause actions
        // back in the UI thread.  They do this by posting those actions
        // to androidUIHandler.
        androidUIHandler = new Handler();
        // We set the initial value of serviceURL to be the
        // demo Web service.
        serviceURL = "http://appinvtinywebdb.appspot.com/";
    }

    // The two procedures below give the getter and setter for the
    // TinyWebDB component's ServiceURL property.  Each one has
    // a @SimpleProperty annotation to indicate that it's a property in
    // the language (and blocks will be generated for it).  The setter
    // also has a @DesignerProperty that makes this property appear in the
    // Properties listed with the component in the designer.  Here we've
    // stipulated that the property should appear with a default value:
    // the URL of the App Inv Tiny DB demonstration Web service.  Note
    // that this default specifies what should be shown in the designer:
    // it does not automatically set the value of ServiceURL by itself,
    // which is why we explicitly set the variable serviceURL above
    // where the component is created.

    /**
     * Returns the URL of the web service database.
     */
    @SimpleProperty(category = PropertyCategory.BEHAVIOR)
    public String ServiceURL() {
        return serviceURL;
    }

    /**
     * Specifies the URL of the  Web service.
     * The default value is the demo service running on App Engine.
     */
    @DesignerProperty(editorType = PropertyTypeConstants.PROPERTY_TYPE_STRING, defaultValue = "http://appinvtinywebdb.appspot.com")
    @SimpleProperty
    public void ServiceURL(String url) {
        serviceURL = url;
    }

    // StoreValue (and GetValue below) show how use the
    // event-driven style we recommend for operations that communicate
    // over the Web.  For each operation, there's (a) The function the
    // user calls (e.g., StoreValue); (b) A non-user visible function
    // that that runs asynchronously to do the actual communication,
    // wait for the result, and signal an event when the result is
    // obtained; (c) the event handler for that result (e.g., ValueStored)

    // Here's part (a):  The component function itself.  All it does is arrange
    // for part (b) to run in a separate thread.

    /**
     * Asks the Web service to store the given value under the given tag
     *
     * @param tag The tag to use
     * @param valueToStore The value to store. Can be any type of value (e.g.
     * number, text, boolean or list).
     */
    @SimpleFunction
    // The @SimpleFunction annotation arranges for this to be a
    // function (StoreValue)  associated with the component.
    public void StoreValue(final String tag, final Object valueToStore) {
        final Runnable call = new Runnable() {
            public void run() {
                postStoreValue(tag, valueToStore);
            }
        };
        AsynchUtil.runAsynchronously(call);
    }

    // Here's part (b): The actual communication, which runs
    // asynchronously.  It uses postCommand, from the WebServiceUtil
    // library.  PostCommand here takes four arguments: (1) The URL of
    // the Web service; (2) The name of the command to be posted to the
    // Web service; (3) parameters for the command; (4) an
    // AsyncCallbackPair, which specifies an onSuccess callback and an
    // onFailure callback.

    // The onSuccess callback is called with the response from the Web
    // server.  Here, for postStoreValue, we ignore the response, and
    // simply signal a ValueStored event.

    // The onFailure callback is called with an error message.  It calls
    // WebServiceError, which will signal a WebServiceError event for the
    // application.

    private void postStoreValue(String tag, Object valueToStore) {
        // The commented-out Log.w command writes a message to the
        // AppInventor Web server log.  It's useful to include these
        // commands to aid in debugging while the component is being
        // developed, and then commenting them out when the component is
        // deployed.
        // Log.w(LOG_TAG, "postStoreValue: sending tag = " +
        // tag + " and value = " + valueToStore);
        // Here we define the AsyncCallbackPair, myCallback.
        AsyncCallbackPair<String> myCallback = new AsyncCallbackPair<String>() {
            public void onSuccess(String response) {
                // the result here will be the JSON-encoded list ["STORED", tag, value]
                // but the component ignores this
                // Log.w(LOG_TAG, "postStoreValue: got result " + result);
                androidUIHandler.post(new Runnable() {
                    public void run() {
                        // Signal an event to indicate that the value was
                        // stored.  We post this to run in the Applcation's main
                        // UI thread, rather than in the separate thread where
                        // postStoreValue is running.
                        ValueStored();
                    }
                });
            }

            public void onFailure(final String message) {
                // Pass any failure message from the Web service command back
                // to the error handler.
                androidUIHandler.post(new Runnable() {
                    public void run() {
                        WebServiceError(message);
                    }
                });
            }
        };
        try {
            WebServiceUtil.getInstance().postCommand(serviceURL, STOREAVALUE_COMMAND,
                    Lists.<NameValuePair>newArrayList(new BasicNameValuePair(TAG_PARAMETER, tag),
                            new BasicNameValuePair(VALUE_PARAMETER, JsonUtil.getJsonRepresentation(valueToStore))),
                    myCallback);
        } catch (JSONException e) {
            throw new YailRuntimeError("Value failed to convert to JSON.", "JSON Creation Error.");
        }
    }

    // Here's part (c): The event that gets signaled when the Web service
    // replies that the store command succeeded.  The application writer
    // might want to specify a handler for this event, perhaps to show
    // a confirmation to the end user.

    /**
     * Event indicating that a StoreValue server request has succeeded.
     */
    @SimpleEvent
    public void ValueStored() {
        // invoke the application's "ValueStored" event handler.
        EventDispatcher.dispatchEvent(this, "ValueStored");
    }

    // The implementation of GetValue uses the same three-procedure
    // event-driven strategy as for StoreVale.  The main difference is
    // that where StoreVale uses postCommand, GetValue uses
    // postCommandReturningArray, which expects a JSON-encoded array as
    // the respose from the Web service.  For the Web service we're
    // using the response should be the two-element array ["VALUE", value] (i.e.,
    // the actual value tagged with "VALUE").

    // The onSuccess callback checks the response and signals an error
    // if it was null.  Otherwise it returns the second element of the
    // response (i.e., the value).  It also arranges to catch a JSON exception in
    // case the result coming back from the service was garbled.

    // The onFailure callback signals a WebServiceError, just as with
    // StoreValue.

    /**
     * GetValue asks the Web service to get the value stored under the given tag.
     * It is up to the Web service what to return if there is no value stored
     * under the tag.  This component just accepts whatever is returned.
     *
     * @param tag The tag whose value is to be retrieved.
     */
    @SimpleFunction
    public void GetValue(final String tag) {
        final Runnable call = new Runnable() {
            public void run() {
                postGetValue(tag);
            }
        };
        AsynchUtil.runAsynchronously(call);
    }

    private void postGetValue(final String tag) {
        // Log.w(LOG_TAG, "postGetValue: sending tag = " + tag);
        AsyncCallbackPair<JSONArray> myCallback = new AsyncCallbackPair<JSONArray>() {
            public void onSuccess(JSONArray result) {
                if (result == null) {
                    // Signal a Web error event to indicate that there was no response
                    // to this request for a value.
                    androidUIHandler.post(new Runnable() {
                        public void run() {
                            WebServiceError("The Web server did not respond to the get value request "
                                    + "for the tag " + tag + ".");
                        }
                    });
                    return;
                } else {
                    try {
                        // Log.w(LOG_TAG, "postGetValue: got result " + result);
                        // The Web service is designed to return the JSON encoded list ["VALUE", tag, value]
                        final String tagFromWebDB = result.getString(1);
                        String value = result.getString(2);
                        // If there's no entry with tag as a key then return the empty string.
                        final Object valueFromWebDB = (value.length() == 0) ? ""
                                : JsonUtil.getObjectFromJson(value);
                        androidUIHandler.post(new Runnable() {
                            public void run() {
                                // signal an event to indicate that a good value was returned.  Note
                                // that the event handler takes the value as an argument.
                                GotValue(tagFromWebDB, valueFromWebDB);
                            }
                        });
                    } catch (JSONException e) {
                        // Signal a Web error event to indicate the the server
                        // returned a garbled value.  From the user's perspective, there may be no practical
                        // difference between this and the "no response" error above, but application
                        // writers can create handlers to use these events as they choose.
                        androidUIHandler.post(new Runnable() {
                            public void run() {
                                WebServiceError(
                                        "The Web server returned a garbled value " + "for the tag " + tag + ".");
                            }
                        });
                        return;
                    }
                }
            }

            public void onFailure(final String message) {
                // Signal a Web error event to indicate that there was no response
                // to this request for a value.  Note that this needs to be posted to the UI
                // thread to avoid a subsequent UI event causing an exception.
                androidUIHandler.post(new Runnable() {
                    public void run() {
                        WebServiceError(message);
                    }
                });
                return;
            }
        };
        WebServiceUtil.getInstance().postCommandReturningArray(serviceURL, GETVALUE_COMMAND,
                Lists.<NameValuePair>newArrayList(new BasicNameValuePair(TAG_PARAMETER, tag)), myCallback);
        return;
    }

    /**
     * Indicates that a GetValue server request has succeeded.
     *
     * @param valueFromWebDB the value that was returned. Can be any type of value
     * (e.g. number, text, boolean or list).
     */
    @SimpleEvent
    public void GotValue(String tagFromWebDB, Object valueFromWebDB) {
        // Invoke the application's "GotValue" event handler
        EventDispatcher.dispatchEvent(this, "GotValue", tagFromWebDB, valueFromWebDB);
    }

    /**
     * Indicates that the communication with the Web service signaled an error
     *
     * @param message the error message
     */
    @SimpleEvent
    public void WebServiceError(String message) {
        // Invoke the application's "WebServiceError" event handler
        // Log.w(LOG_TAG, "calling error event handler: " + message);
        EventDispatcher.dispatchEvent(this, "WebServiceError", message);
    }
}