org.andstatus.app.net.social.Connection.java Source code

Java tutorial

Introduction

Here is the source code for org.andstatus.app.net.social.Connection.java

Source

/*
 * Copyright (C) 2011-2014 yvolk (Yuri Volkov), http://yurivolkov.com
 * Copyright (C) 2008 Torgny Bjers
 *
 * 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 org.andstatus.app.net.social;

import android.net.Uri;
import android.text.TextUtils;

import org.andstatus.app.account.AccountDataWriter;
import org.andstatus.app.data.MyDatabase.User;
import org.andstatus.app.net.http.ConnectionException;
import org.andstatus.app.net.http.ConnectionException.StatusCode;
import org.andstatus.app.net.http.HttpConnection;
import org.andstatus.app.net.http.HttpConnectionData;
import org.andstatus.app.net.http.OAuthConsumerAndProvider;
import org.andstatus.app.net.social.MbTimelineItem.ItemType;
import org.andstatus.app.origin.OriginConnectionData;
import org.andstatus.app.util.MyLog;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.File;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Locale;

/**
 * Handles connection to the API of the Microblogging System (i.e. to the "Origin")
 * Authenticated User info (User account in the Microblogging system) and connection properties 
 * are provided in the constructor.
 * 
 * @author yvolk@yurivolkov.com, torgny.bjers
 */
public abstract class Connection {

    static final String APPLICATION_ID = "http://andstatus.org/andstatus";
    public static final String KEY_PASSWORD = "password";
    protected static final String EXTENSION = ".json";

    /**
     * Connection APIs known
     */
    public enum ApiEnum {
        UNKNOWN_API,
        /** Twitter API v.1 https://dev.twitter.com/docs/api/1     */
        TWITTER1P0,
        /** Twitter API v.1.1 https://dev.twitter.com/docs/api/1.1 */
        TWITTER1P1,
        /** GNU social (former: Status Net) Twitter compatible API http://status.net/wiki/Twitter-compatible_API  */
        GNUSOCIAL_TWITTER,
        /** https://github.com/e14n/pump.io/blob/master/API.md */
        PUMPIO
    }

    /**
     * API routines (functions, "resources" in terms of Twitter)  enumerated
     */
    public enum ApiRoutineEnum {
        ACCOUNT_RATE_LIMIT_STATUS, ACCOUNT_VERIFY_CREDENTIALS,
        /** Returns most recent direct messages sent to the authenticating user */
        DIRECT_MESSAGES, CREATE_FAVORITE, DESTROY_FAVORITE, FOLLOW_USER, GET_CONFIG,
        /** List of users */
        GET_FRIENDS,
        /** List of Users' IDs */
        GET_FRIENDS_IDS, GET_OPEN_INSTANCES, GET_USER, POST_MESSAGE, POST_WITH_MEDIA, POST_DIRECT_MESSAGE, POST_REBLOG, DESTROY_MESSAGE, REGISTER_CLIENT,
        /**
         * Get the Home timeline (whatever it is...).
         * This is the equivalent of /home on the Web.
         */
        STATUSES_HOME_TIMELINE,
        /**
         * Get the user's replies.
         * 
         * Returns most recent @replies (status updates prefixed with @username) 
         * for the authenticating user.
         */
        STATUSES_MENTIONS_TIMELINE,
        /**
         * Get the User timeline for the user with the selectedUserId. We use credentials of Account which may be
         * not the same user. 
         */
        STATUSES_USER_TIMELINE(true), PUBLIC_TIMELINE(true), SEARCH_MESSAGES(true),

        GET_MESSAGE, STOP_FOLLOWING_USER,

        /**
         * OAuth APIs
         */
        OAUTH_ACCESS_TOKEN, OAUTH_AUTHORIZE, OAUTH_REQUEST_TOKEN,
        /** For the "OAuth Dynamic Client Registration", 
         * is the link proper?: http://hdknr.github.io/docs/identity/oauth_reg.html  */
        OAUTH_REGISTER_CLIENT,

        /**
         * Simply ignore this API call
         */
        DUMMY;

        private final boolean msgPublic;

        private ApiRoutineEnum() {
            this(false);
        }

        private ApiRoutineEnum(boolean msgPublic) {
            this.msgPublic = msgPublic;
        }

        public boolean isMsgPublic() {
            return msgPublic;
        }
    }

    protected HttpConnection http;
    protected OriginConnectionData data;

    protected Connection() {
    }

    /**
     * @return an empty string in case the API routine is not supported
     */
    protected abstract String getApiPath1(ApiRoutineEnum routine);

    /**
     * Full path of the API. Logged
     * @return URL or throws a ConnectionException in case the API routine is not supported
     */
    protected final String getApiPath(ApiRoutineEnum routine) throws ConnectionException {
        String path = this.getApiPath1(routine);
        if (TextUtils.isEmpty(path)) {
            String detailMessage = "The API is not supported: '" + routine + "'";
            MyLog.e(this.getClass().getSimpleName(), detailMessage);
            throw new ConnectionException(StatusCode.UNSUPPORTED_API,
                    this.getClass().getSimpleName() + ": " + detailMessage);
        } else {
            if (MyLog.isLoggable(null, MyLog.VERBOSE)) {
                MyLog.v(this.getClass().getSimpleName(), "API '" + routine + "' Path=" + path);
            }
        }
        return path;
    }

    /**
     * Use this method to check the connection's (Account's) capability before attempting to use it
     * and even before presenting corresponding action to the User. 
     * @param routine
     * @return true if supported
     */
    public boolean isApiSupported(ApiRoutineEnum routine) {
        boolean is = !TextUtils.isEmpty(this.getApiPath1(routine));
        if (!is && MyLog.isLoggable(null, MyLog.VERBOSE)) {
            MyLog.v(this.getClass().getSimpleName(), "The API routine '" + routine + "' is not supported");
        }
        return is;
    }

    /**
     * Check API requests status.
     */
    public abstract MbRateLimitStatus rateLimitStatus() throws ConnectionException;

    /**
     * Do we need password to be set?
     * By default password is not needed and is ignored
     */
    public final boolean isPasswordNeeded() {
        return http.isPasswordNeeded();
    }

    /**
     * Set User's password if the Connection object needs it
     */
    public final void setPassword(String password) {
        http.setPassword(password);
    }

    public final String getPassword() {
        return http.getPassword();
    }

    /**
     * Persist the connection data
     * @return true if something changed (so it needs to be rewritten to persistence...)
     */
    public boolean save(AccountDataWriter dw) {
        return http.save(dw);
    }

    public boolean save(JSONObject jso) throws JSONException {
        return http.save(jso);
    }

    /**
     * Do we have enough credentials to verify them?
     * @return true == yes
     */
    public final boolean getCredentialsPresent() {
        return http.getCredentialsPresent();
    }

    /**
     * Verify the user's credentials.
     */
    public abstract MbUser verifyCredentials() throws ConnectionException;

    public abstract MbMessage destroyFavorite(String statusId) throws ConnectionException;

    /**
     * Favorites the status specified in the ID parameter as the authenticating user.
     * Returns the favorite status when successful.
     * @see <a
     *      href="http://apiwiki.twitter.com/Twitter-REST-API-Method%3A-favorites%C2%A0create">Twitter
     *      REST API Method: favorites create</a>
     */
    public abstract MbMessage createFavorite(String statusId) throws ConnectionException;

    /**
     * Destroys the status specified by the required ID parameter.
     * The authenticating user must be the author of the specified status.
     * @see <a
     *      href="http://apiwiki.twitter.com/Twitter-REST-API-Method%3A-statuses%C2%A0destroy">Twitter
     *      REST API Method: statuses/destroy</a>
     */
    public abstract boolean destroyStatus(String statusId) throws ConnectionException;

    /**
     * Returns a list of users the specified user is following.
     */
    public List<MbUser> getUsersFollowedBy(String userId) throws ConnectionException {
        throw ConnectionException.fromStatusCode(StatusCode.UNSUPPORTED_API,
                "getUsersFollowedBy for userOid=" + userId);
    }

    /**
     * Returns a list of IDs for every user the specified user is following.
     */
    public List<String> getIdsOfUsersFollowedBy(String userId) throws ConnectionException {
        throw ConnectionException.fromStatusCode(StatusCode.UNSUPPORTED_API,
                "getIdsOfUsersFollowedBy for userOid=" + userId);
    }

    /**
     * Returns a single status, specified by the id parameter below.
     * The status's author will be returned inline.
     */
    public final MbMessage getMessage(String statusId) throws ConnectionException {
        MbMessage msg = getMessage1(statusId);
        if (msg != null) {
            msg.setPublic(true);
        }
        return msg;
    }

    /** See {@link #getMessage(String)} */
    protected abstract MbMessage getMessage1(String statusId) throws ConnectionException;

    /**
     * Update user status by posting to the Twitter REST API.
     * 
     * Updates the authenticating user's status, also known as tweeting/blogging.
     * To upload an image to accompany the tweet, use POST statuses/update_with_media.
     * 
     * @param message       Text of the "status"
     * @param inReplyToId   The ID of an existing status that the update is in reply to.
     * @param mediaUri 
     * @throws ConnectionException 
     *
     * @see <a
     *      href="https://dev.twitter.com/docs/api/1/post/statuses/update">Twitter
     *      POST statuses/update</a>
     */
    public abstract MbMessage updateStatus(String message, String inReplyToId, Uri mediaUri)
            throws ConnectionException;

    /**
     * Post Direct Message
     * @see <a
     *      href="https://dev.twitter.com/docs/api/1/post/direct_messages/new">POST direct_messages/new</a>
     * 
     * @param message
     * @param userId {@link User#USER_OID} - The ID of the user who should receive the direct message
     * @param mediaUri 
     * @return The sent message if successful (empty message if not)
     * @throws ConnectionException
     */
    public abstract MbMessage postDirectMessage(String message, String userId, Uri mediaUri)
            throws ConnectionException;

    /**
     * Post reblog ("Retweet")
     * @see <a
     *      href="https://dev.twitter.com/docs/api/1/post/statuses/retweet/%3Aid">POST statuses/retweet/:id</a>
     * 
     * @param rebloggedId id of the Reblogged message
     * @throws ConnectionException
     */
    public abstract MbMessage postReblog(String rebloggedId) throws ConnectionException;

    /**
     * Universal method for several Timeline Types...
     * @param userId For the {@link ApiRoutineEnum#STATUSES_USER_TIMELINE}, null for the other timelines
     */
    public abstract List<MbTimelineItem> getTimeline(ApiRoutineEnum apiRoutine, TimelinePosition sinceId, int limit,
            String userId) throws ConnectionException;

    public abstract List<MbTimelineItem> search(String searchQuery, int limit) throws ConnectionException;

    /**
     * Allows this User to follow the user specified in the userId parameter
     * Allows this User to stop following the user specified in the userId parameter
     * @param userId
     * @param follow true - Follow, false - Stop following
     * @return User object with 'following' flag set/reset
     * @throws ConnectionException
     */
    public abstract MbUser followUser(String userId, Boolean follow) throws ConnectionException;

    /**
     * Get information about the specified User
     * @param userId
     * @return User object
     * @throws ConnectionException
     */
    public abstract MbUser getUser(String userId) throws ConnectionException;

    protected final String fixSinceId(String sinceId) {
        String out = "";
        if (!TextUtils.isEmpty(sinceId) && sinceId.length() > 1) {
            // For some reason "0" results in "bad request"
            out = sinceId;
        }
        return out;
    }

    /**
     * Restrict the limit to 1 - 200
     * @param apiRoutine 
     */
    public int fixedDownloadLimitForApiRoutine(int limit, ApiRoutineEnum apiRoutine) {
        int out = 200;
        if (limit > 0 && limit < 200) {
            out = limit;
        }
        return out;
    }

    public final void setAccountData(OriginConnectionData connectionData)
            throws InstantiationException, IllegalAccessException {
        this.data = connectionData;
        http = connectionData.newHttpConnection();
        http.setConnectionData(HttpConnectionData.fromConnectionData(connectionData));
    }

    public void clearAuthInformation() {
        http.clearAuthInformation();
    }

    public void setUserTokenWithSecret(String token, String secret) {
        http.setUserTokenWithSecret(token, secret);
    }

    public OAuthConsumerAndProvider getOAuthConsumerAndProvider() {
        OAuthConsumerAndProvider oa = null;
        if (data.isOAuth()) {
            oa = (OAuthConsumerAndProvider) http;
        }
        return oa;
    }

    public void registerClientForAccount() throws ConnectionException {
        http.registerClient(getApiPath(ApiRoutineEnum.REGISTER_CLIENT));
    }

    public void clearClientKeys() {
        http.clearClientKeys();
    }

    public boolean areOAuthClientKeysPresent() {
        return http.data.areOAuthClientKeysPresent();
    }

    public void enrichConnectionData(OriginConnectionData connectionData2) {
        // Nothing to do
    }

    public boolean userObjectHasMessage() {
        return false;
    }

    public MbConfig getConfig() throws ConnectionException {
        return MbConfig.getEmpty();
    }

    public List<MbOrigin> getOpenInstances() throws ConnectionException {
        throw ConnectionException.fromStatusCode(StatusCode.UNSUPPORTED_API, MyLog.objTagToString(this));
    }

    /**
     * @return Unix time. Returns 0 in a case of an error or absence of such a field
     */
    public long dateFromJson(JSONObject jso, String fieldName) {
        long date = 0;
        if (jso != null && jso.has(fieldName)) {
            String updated = jso.optString(fieldName);
            if (updated.length() > 0) {
                date = parseDate(updated);
            }
        }
        return date;
    }

    /**
     * @return Unix time. Returns 0 in a case of an error
     */
    public long parseDate(String stringDate) {
        if (TextUtils.isEmpty(stringDate)) {
            return 0;
        }
        long unixDate = 0;
        String[] formats = { "", "E MMM d HH:mm:ss Z yyyy", "E, d MMM yyyy HH:mm:ss Z" };
        for (String format : formats) {
            if (TextUtils.isEmpty(format)) {
                try {
                    unixDate = Date.parse(stringDate);
                } catch (IllegalArgumentException e) {
                    MyLog.ignored(this, e);
                }
            } else {
                DateFormat dateFormat1 = new SimpleDateFormat(format, Locale.ENGLISH);
                try {
                    unixDate = dateFormat1.parse(stringDate).getTime();
                } catch (ParseException e) {
                    MyLog.ignored(this, e);
                }
            }
            if (unixDate != 0) {
                break;
            }
        }
        if (unixDate == 0) {
            MyLog.d(this, "Failed to parse the date: '" + stringDate + "'");
        }
        return unixDate;
    }

    protected void setMessagesPublic(List<MbTimelineItem> timeline) {
        for (MbTimelineItem item : timeline) {
            if (item.getType() == ItemType.MESSAGE) {
                item.mbMessage.setPublic(true);
            }
        }
    }

    public JSONArray getRequestArrayInObject(String path, String arrayName) throws ConnectionException {
        String method = "getRequestArrayInObject";
        JSONArray jArr = null;
        JSONObject jso = http.getRequest(path);
        if (jso != null) {
            try {
                jArr = jso.getJSONArray(arrayName);
            } catch (JSONException e) {
                throw ConnectionException.loggedJsonException(this, method + ", arrayName=" + arrayName, e, jso);
            }
        }
        return jArr;
    }

    public void downloadFile(String url, File file) throws ConnectionException {
        http.downloadFile(url, file);
    }

    public HttpConnection getHttp() {
        return http;
    }

    protected String prependWithBasicPath(String url) {
        if (!TextUtils.isEmpty(url) && !url.contains("://")) {
            url = http.data.basicPath + "/" + url;
        }
        return url;
    }
}