com.rightscale.provider.Dashboard.java Source code

Java tutorial

Introduction

Here is the source code for com.rightscale.provider.Dashboard.java

Source

// Dashboard: an Android front-end to the RightScale dashboard
// Copyright (C) 2009 Tony Spataro <code@tracker.xeger.net>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

package com.rightscale.provider;

import java.security.InvalidParameterException;
import java.util.ArrayList;
import java.util.List;
import net.xeger.rest.RestException;

import org.apache.http.client.HttpClient;

import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;

import com.rightscale.app.dashboard.Settings;
import com.rightscale.provider.rest.*;

/**
 * The Android content provider used to retrieve REST resources from the
 * RightScale API. The UI widgets know how to deal with content, so we wrap the
 * API in a layer that makes it act like content.
 * 
 * Note that "URIs" mentioned in this class do not necessarily map 1:1 to API
 * paths; the Android OS uses a fancy URI-based scheme to identify data types;
 * as such, each Java content-data type exposed by this class (deployments,
 * servers, ...) has its own content:// URI.
 * 
 * Also note that this content provider is a gigantic hack inasmuch as it does
 * not keep track of RightScale dashboard resources in a local content database,
 * which it really should. Instead, it synchronously issues API calls in
 * response to SQL queries. As such, it only knows how to handle an EXTREMELY
 * LIMITED set of SQL "where" clauses and it always returns all columns of the
 * given resource.
 * 
 * Supported resources so far: - Deployment (optionally, WHERE deployment_id =
 * x) - Server (optionally, WHERE deployment_id = x) - Server settings (WHERE
 * server_id = x)
 * 
 * @author tony
 * 
 */
public class Dashboard extends ContentProvider {
    public static final Uri BASE_CONTENT_URI = Uri.parse("content://com.rightscale.provider.dashboard");

    public static final String ID = "_id";
    public static final String HREF = "href";

    public static final String[] ACCOUNT_COLUMNS = AccountsResource.COLUMNS;
    public static final String[] DEPLOYMENT_COLUMNS = DeploymentsResource.COLUMNS;
    public static final String[] SERVER_COLUMNS = ServersResource.COLUMNS;
    public static final String[] SERVER_SETTING_COLUMNS = ServerSettingsResource.COLUMNS;
    public static final String[] SERVER_MONITORS_COLUMNS = ServerMonitorsResource.COLUMNS;
    public static final String[] SERVER_TEMPLATE_COLUMNS = ServerTemplatesResource.COLUMNS;
    public static final String[] SERVER_TEMPLATE_EXECUTABLE_COLUMNS = ServerTemplateExecutablesResource.COLUMNS;

    public static final String ACTION_LAUNCH = "launch";
    public static final String ACTION_TERMINATE = "terminate";
    public static final String ACTION_REBOOT = "reboot";
    public static final String ACTION_RUN_SCRIPT = "runScript";

    static protected List<String> WHERE_ACCOUNT = new ArrayList<String>();
    static {
        WHERE_ACCOUNT.add("account_id");
    }

    static protected List<String> WHERE_ACCOUNT_AND_ID = new ArrayList<String>();
    static {
        WHERE_ACCOUNT_AND_ID.add("account_id");
        WHERE_ACCOUNT_AND_ID.add("id");
    }

    static protected List<String> WHERE_ACCOUNT_AND_DEPLOYMENT = new ArrayList<String>();
    static {
        WHERE_ACCOUNT_AND_DEPLOYMENT.add("account_id");
        WHERE_ACCOUNT_AND_DEPLOYMENT.add("deployment_id");
    }

    static protected List<String> WHERE_ACCOUNT_AND_SERVER = new ArrayList<String>();
    static {
        WHERE_ACCOUNT_AND_SERVER.add("account_id");
        WHERE_ACCOUNT_AND_SERVER.add("server_id");
    }

    static protected List<String> WHERE_ACCOUNT_AND_SERVER_TEMPLATE_AND_APPLY = new ArrayList<String>();
    static {
        WHERE_ACCOUNT_AND_SERVER_TEMPLATE_AND_APPLY.add("account_id");
        WHERE_ACCOUNT_AND_SERVER_TEMPLATE_AND_APPLY.add("server_template_id");
        WHERE_ACCOUNT_AND_SERVER_TEMPLATE_AND_APPLY.add("apply");
    }

    /**
     * Session object that is shared among all callers of this class. Note that since a Context
     * is required to create a session, the first caller "wins".
     */
    static private DashboardSession _session = null;
    static private String _sessionEmail = null;
    static private String _sessionPassword = null;
    static private String _sessionSystem = null;

    /*
     * TODO figure out how to fit this better into Android's app framework, e.g.
     * use a BroadcastReceiver that can handle these actions as Intents.
     */
    static public void performAction(Context context, Uri uri, String accountId, String action) {
        performAction(context, uri, accountId, action, null);
    }

    /*
     * TODO figure out how to fit this better into Android's app framework, e.g.
     * use a BroadcastReceiver that can handle these actions as Intents.
     */
    static public void performAction(Context context, Uri uri, String accountId, String action, String param)
            throws DashboardError {
        String mimeType = _getType(uri);

        if (!mimeType.startsWith("vnd.android.cursor.item/")) {
            throw new DashboardError("Cannot perform actions on collection resource " + uri.toString());
        }

        List<String> pathSegments = uri.getPathSegments();
        String id = pathSegments.get(pathSegments.size() - 1);

        try {
            DashboardSession session = createSession(context);
            session.login();

            if (mimeType.endsWith(ServersResource.MIME_TYPE)) {
                ServersResource servers = new ServersResource(session, accountId);

                if (ACTION_LAUNCH.equals(action)) {
                    servers.launch(id);

                } else if (ACTION_TERMINATE.equals(action)) {
                    servers.terminate(id);

                } else if (ACTION_REBOOT.equals(action)) {
                    servers.reboot(id);
                } else if (ACTION_RUN_SCRIPT.equals(action)) {
                    servers.runScript(id, (String) param);
                }
            } else {
                throw new DashboardError("Cannot perform actions on unknown URI " + uri.toString());
            }
        } catch (RestException e) {
            forgetSession();
            Error err = new DashboardError(e);
            throw err;
        }
    }

    @Override
    public String getType(Uri uri) {
        return _getType(uri);
    }

    @Override
    public boolean onCreate() {
        return false;
    }

    @Override
    public Cursor query(Uri uri, String[] columns, String where, String[] whereArgs, String sortBy) {
        try {
            List<String> segments = uri.getPathSegments();
            DashboardSession session = createSession(getContext());
            String[] args = null;

            session.login();

            if (segments.size() == 1 && segments.get(0).equals("accounts")) {
                //Special case: asking for index of accounts
                AccountsResource accounts = new AccountsResource(session);
                return accounts.index();
            }

            if (segments.size() < 3 || !segments.get(0).equals("accounts")) {
                throw new DashboardError("Unknown content URI: " + uri);
            }

            if (segments.get(2).equals("deployments")) {
                if ((args = parseWhereArgs(where, whereArgs, WHERE_ACCOUNT_AND_ID)) != null) {
                    // SELECT ... FROM deployments WHERE id = ?
                    session.setCurrentAccount(args[0]);
                    DeploymentsResource deployments = new DeploymentsResource(session, args[0]);
                    return deployments.show(args[1]);
                } else if ((args = parseWhereArgs(where, whereArgs, WHERE_ACCOUNT)) != null) {
                    // SELECT ... FROM deployments
                    session.setCurrentAccount(args[0]);
                    DeploymentsResource deployments = new DeploymentsResource(session, args[0]);
                    return deployments.index();
                } else {
                    throw new DashboardError("Unknown where-clause: " + where);
                }
            } else if (segments.get(2).equals("servers")) {
                if ((args = parseWhereArgs(where, whereArgs, WHERE_ACCOUNT_AND_DEPLOYMENT)) != null) {
                    // SELECT ... FROM servers WHERE deployment_id = ?
                    session.setCurrentAccount(args[0]);
                    ServersResource servers = new ServersResource(session, args[0]);
                    return servers.indexForDeployment(args[1]);
                } else if ((args = parseWhereArgs(where, whereArgs, WHERE_ACCOUNT_AND_ID)) != null) {
                    // SELECT ... FROM servers WHERE id = ?
                    session.setCurrentAccount(args[0]);
                    ServersResource servers = new ServersResource(session, args[0]);
                    return servers.show(args[1]);
                } else if ((args = parseWhereArgs(where, whereArgs, WHERE_ACCOUNT)) != null) {
                    // SELECT ... FROM servers
                    session.setCurrentAccount(args[0]);
                    ServersResource servers = new ServersResource(session, args[0]);
                    return servers.index();
                } else {
                    throw new DashboardError("Unknown where-clause: " + where);
                }
            } else if (segments.get(2).equals("server_settings")) {
                if ((args = parseWhereArgs(where, whereArgs, WHERE_ACCOUNT_AND_SERVER)) != null) {
                    // SELECT ... FROM server_settings WHERE server_id = ?
                    session.setCurrentAccount(args[0]);
                    ServerSettingsResource serverSettings = new ServerSettingsResource(session, args[0]);
                    return serverSettings.showForServer(args[1]);
                } else {
                    throw new DashboardError("Unknown where-clause: " + where);
                }
            } else if (segments.get(2).equals("server_monitors")) {
                if ((args = parseWhereArgs(where, whereArgs, WHERE_ACCOUNT_AND_SERVER)) != null) {
                    // SELECT ... FROM server_monitors WHERE server_id = ?
                    session.setCurrentAccount(args[0]);
                    ServerMonitorsResource serverMonitors = new ServerMonitorsResource(session, args[0]);
                    return serverMonitors.indexForServer(args[1]);
                } else {
                    throw new DashboardError("Unknown where-clause: " + where);
                }
            } else if (segments.get(2).equals("server_templates")) {
                if ((args = parseWhereArgs(where, whereArgs, WHERE_ACCOUNT_AND_ID)) != null) {
                    // SELECT ... FROM server_templates WHERE id = ?
                    session.setCurrentAccount(args[0]);
                    ServerTemplatesResource serverTemplates = new ServerTemplatesResource(session, args[0]);
                    return serverTemplates.show(args[1]);
                } else if ((args = parseWhereArgs(where, whereArgs, WHERE_ACCOUNT)) != null) {
                    session.setCurrentAccount(args[0]);
                    ServerTemplatesResource serverTemplates = new ServerTemplatesResource(session, args[0]);
                    return serverTemplates.index();
                } else {
                    throw new DashboardError("Unknown where-clause: " + where);
                }
            } else if (segments.get(2).equals("server_template_executables")) {
                if ((args = parseWhereArgs(where, whereArgs,
                        WHERE_ACCOUNT_AND_SERVER_TEMPLATE_AND_APPLY)) != null) {
                    session.setCurrentAccount(args[0]);
                    ServerTemplateExecutablesResource serverTemplates = new ServerTemplateExecutablesResource(
                            session, args[0]);
                    return serverTemplates.indexForServerTemplate(args[1], args[2]);
                } else {
                    throw new DashboardError("Unknown where-clause: " + where);
                }
            }
        } catch (RuntimeException e) {
            throw e;
        } catch (RestException e) {
            forgetSession();
            Error err = new DashboardError(e);
            throw err;
        }

        throw new DashboardError("Unknown content URI " + uri);
    }

    @Override
    public Uri insert(Uri arg0, ContentValues arg1) {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public int update(Uri arg0, ContentValues arg1, String arg2, String[] arg3) {
        // TODO Auto-generated method stub
        return 0;
    }

    @Override
    public int delete(Uri arg0, String arg1, String[] arg2) {
        // TODO Auto-generated method stub
        return 0;
    }

    protected String[] parseWhereArgs(String where, String[] whereArgs, List<String> requiredParams) {
        if (where == null) {
            return null;
        }

        String[] tokens = where.split("\\s+");

        String[] args = new String[requiredParams.size()];
        String columnName = null;
        int n = 0;

        int state = 0;
        for (String token : tokens) {
            switch (state) {
            case 0:
                if (requiredParams.contains(token)) {
                    columnName = token;
                    state = 1;
                } else {
                    //if the query string contains something other than we expect, the parse fails 
                    return null;
                }
                break;
            case 1:
                if (token.equals("=")) {
                    state = 2;
                } else {
                    throw new DashboardError("Parse error: expected '=' but got " + token);
                }
                break;
            case 2:
                if (token.equals("?")) {
                    args[requiredParams.indexOf(columnName)] = whereArgs[n++];
                    state = 3;
                } else {
                    throw new DashboardError("Parse error: expected '?' but got " + token);
                }
                break;
            case 3:
                if (token.equalsIgnoreCase("and")) {
                    state = 0;
                } else {
                    throw new DashboardError("Parse error: expected 'AND' but got " + token);
                }
                break;
            }
        }

        if (n == requiredParams.size()) {
            return args;
        } else {
            return null;
        }
    }

    static public HttpClient createClient(Context context) {
        // notice that we don't login the session (on purpose)
        return createSession(context).createClient();
    }

    static public synchronized DashboardSession createSession(Context context) {
        String email = Settings.getEmail(context), password = Settings.getPassword(context),
                system = Settings.getSystem(context);

        if ((_sessionEmail != null && !_sessionEmail.equals(email))
                || (_sessionPassword != null && !_sessionPassword.equals(password))
                || (_sessionSystem != null && !_sessionSystem.equals(system))) {
            //Reset session if any relevant preferences have changed
            _session = null;
        }

        if (_session == null) {
            _sessionEmail = email;
            _sessionPassword = password;
            _sessionSystem = system;
            _session = new DashboardSession(email, password, system);
        }

        return _session;
    }

    static public synchronized void forgetSession() {
        _session = null;
    }

    static protected String _getType(Uri uri) {
        List<String> path = uri.getPathSegments();

        String model;
        String mimePrefix, mimeType;

        if (path.size() % 2 == 1) {
            // Odd-sized paths (/deployments, /deployments/1/servers, ...)
            // represent a collection of resources.
            model = path.get(path.size() - 1);
            mimePrefix = "vnd.android.cursor.dir/";
        } else {
            // Even-sized paths (/deployments/1, /server_templates/1/executables/5)
            // represent an individual item.
            model = path.get(path.size() - 2);
            mimePrefix = "vnd.android.cursor.item/";
        }

        if (model.equals("accounts")) {
            mimeType = AccountsResource.MIME_TYPE;
        } else if (model.equals("deployments")) {
            mimeType = DeploymentsResource.MIME_TYPE;
        } else if (model.equals("servers")) {
            mimeType = ServersResource.MIME_TYPE;
        } else if (model.equals("server_settings")) {
            mimeType = ServerSettingsResource.MIME_TYPE;
        } else {
            throw new InvalidParameterException("Unknown URI: " + model);
        }

        return mimePrefix + mimeType;
    }
}