org.opendatakit.utils.WebUtils.java Source code

Java tutorial

Introduction

Here is the source code for org.opendatakit.utils.WebUtils.java

Source

/**
 * Copyright (C) 2011 University of Washington
 *
 * 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.opendatakit.utils;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.CharEncoding;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.javarosa.core.model.utils.DateUtils;
import org.opendatakit.constants.BasicConsts;

/**
 * Useful methods for parsing boolean and date values and formatting dates.
 *
 * @author mitchellsundt@gmail.com
 *
 */
public class WebUtils {

    static final String IS_FORWARD_CURSOR_VALUE_TAG = "isForwardCursor";
    static final String URI_LAST_RETURNED_VALUE_TAG = "uriLastReturnedValue";
    static final String ATTRIBUTE_VALUE_TAG = "attributeValue";
    static final String ATTRIBUTE_NAME_TAG = "attributeName";
    static final String CURSOR_TAG = "cursor";
    static final Log logger = LogFactory.getLog(WebUtils.class);
    /**
     * Date format pattern used to parse HTTP date headers in RFC 1123 format.
     * copied from apache.commons.lang.DateUtils
     */
    private static final String PATTERN_RFC1123 = "EEE, dd MMM yyyy HH:mm:ss zzz";

    /**
     * Date format pattern used to parse HTTP date headers in RFC 1036 format.
     * copied from apache.commons.lang.DateUtils
     */
    private static final String PATTERN_RFC1036 = "EEEE, dd-MMM-yy HH:mm:ss zzz";

    /**
     * Date format pattern used to parse HTTP date headers in ANSI C
     * <code>asctime()</code> format. copied from apache.commons.lang.DateUtils
     */
    private static final String PATTERN_ASCTIME = "EEE MMM d HH:mm:ss yyyy";
    private static final String PATTERN_DATE_TOSTRING = "EEE MMM dd HH:mm:ss zzz yyyy";
    private static final String PATTERN_ISO8601 = "yyyy-MM-dd'T'HH:mm:ss.SSSZ";
    private static final String PATTERN_ISO8601_WITHOUT_ZONE = "yyyy-MM-dd'T'HH:mm:ss.SSS";
    private static final String PATTERN_ISO8601_DATE = "yyyy-MM-ddZ";
    private static final String PATTERN_ISO8601_TIME = "HH:mm:ss.SSSZ";
    private static final String PATTERN_YYYY_MM_DD_DATE_ONLY_NO_TIME_DASH = "yyyy-MM-dd";
    private static final String PATTERN_NO_DATE_TIME_ONLY = "HH:mm:ss.SSS";
    private static final String PATTERN_GOOGLE_DOCS = "MM/dd/yyyy HH:mm:ss.SSS";
    private static final String PATTERN_GOOGLE_DOCS_DATE_ONLY = "MM/dd/yyyy";

    private static final String PURGE_DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";

    private WebUtils() {
    };

    /**
     * Safely encode a string for use as a query parameter.
     * 
     * @param rawString
     * @return encoded string
     */
    public static String safeEncode(String rawString) {
        if (rawString == null || rawString.length() == 0) {
            return null;
        }

        try {
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            GZIPOutputStream gzip = new GZIPOutputStream(out);
            gzip.write(rawString.getBytes(CharEncoding.UTF_8));
            gzip.finish();
            gzip.close();
            String candidate = Base64.encodeBase64URLSafeString(out.toByteArray());
            return candidate;
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
            throw new IllegalArgumentException("Unexpected failure: " + e.toString());
        } catch (IOException e) {
            e.printStackTrace();
            throw new IllegalArgumentException("Unexpected failure: " + e.toString());
        }
    }

    /**
     * Decode a safeEncode() string.
     * 
     * @param encodedWebsafeString
     * @return rawString
     */
    public static String safeDecode(String encodedWebsafeString) {
        if (encodedWebsafeString == null || encodedWebsafeString.length() == 0) {
            return encodedWebsafeString;
        }

        try {
            ByteArrayInputStream in = new ByteArrayInputStream(
                    Base64.decodeBase64(encodedWebsafeString.getBytes(CharEncoding.UTF_8)));
            GZIPInputStream gzip = new GZIPInputStream(in);
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            int ch = gzip.read();
            while (ch >= 0) {
                out.write(ch);
                ch = gzip.read();
            }
            gzip.close();
            out.flush();
            out.close();
            return new String(out.toByteArray(), CharEncoding.UTF_8);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
            throw new IllegalArgumentException("Unexpected failure: " + e.toString());
        } catch (IOException e) {
            e.printStackTrace();
            throw new IllegalArgumentException("Unexpected failure: " + e.toString());
        }
    }

    /**
     * Parse a string into a boolean value. Any of:
     * <ul>
     * <li>ok</li>
     * <li>yes</li>
     * <li>true</li>
     * <li>t</li>
     * <li>y</li>
     * </ul>
     * are interpretted as boolean true.
     *
     * @param value
     * @return
     */
    public static final Boolean parseBoolean(String value) {
        Boolean b = null;
        if (value != null && value.length() != 0) {
            b = Boolean.FALSE;
            if (value.compareToIgnoreCase("ok") == 0) {
                b = Boolean.TRUE;
            } else if (value.compareToIgnoreCase("yes") == 0) {
                b = Boolean.TRUE;
            } else if (value.compareToIgnoreCase("true") == 0) {
                b = Boolean.TRUE;
            } else if (value.compareToIgnoreCase("T") == 0) {
                b = Boolean.TRUE;
            } else if (value.compareToIgnoreCase("Y") == 0) {
                b = Boolean.TRUE;
            }
        }
        return b;
    }

    private static final Date parseDateSubset(String value, String[] parsePatterns, Locale l, TimeZone tz) {
        // borrowed from apache.commons.lang.DateUtils...
        Date d = null;
        SimpleDateFormat parser = null;
        ParsePosition pos = new ParsePosition(0);
        for (int i = 0; i < parsePatterns.length; i++) {
            if (i == 0) {
                if (l == null) {
                    parser = new SimpleDateFormat(parsePatterns[0]);
                } else {
                    parser = new SimpleDateFormat(parsePatterns[0], l);
                }
            } else {
                parser.applyPattern(parsePatterns[i]);
            }
            parser.setTimeZone(tz); // enforce UTC for formats without timezones
            pos.setIndex(0);
            d = parser.parse(value, pos);
            if (d != null && pos.getIndex() == value.length()) {
                return d;
            }
        }
        return d;
    }

    /**
     * Parse a string into a datetime value. Tries the common Http formats, the
     * iso8601 format (used by Javarosa), the default formatting from
     * Date.toString(), and a time-only format.
     *
     * @param value
     * @return
     */
    public static final Date parseDate(String value) {
        if (value == null || value.length() == 0)
            return null;

        String[] iso8601Pattern = new String[] { PATTERN_ISO8601 };

        String[] localizedParsePatterns = new String[] {
                // try the common HTTP date formats that have time zones
                PATTERN_RFC1123, PATTERN_RFC1036, PATTERN_DATE_TOSTRING };

        String[] localizedNoTzParsePatterns = new String[] {
                // ones without timezones... (will assume UTC)
                PATTERN_ASCTIME };

        String[] tzParsePatterns = new String[] { PATTERN_ISO8601, PATTERN_ISO8601_DATE, PATTERN_ISO8601_TIME };

        String[] noTzParsePatterns = new String[] {
                // ones without timezones... (will assume UTC)
                PATTERN_ISO8601_WITHOUT_ZONE, PATTERN_NO_DATE_TIME_ONLY, PATTERN_YYYY_MM_DD_DATE_ONLY_NO_TIME_DASH,
                PATTERN_GOOGLE_DOCS };

        Date d = null;
        // iso8601 parsing is sometimes off-by-one when JR does it...
        d = parseDateSubset(value, iso8601Pattern, null, TimeZone.getTimeZone("GMT"));
        if (d != null)
            return d;
        // try to parse with the JavaRosa parsers
        d = DateUtils.parseDateTime(value);
        if (d != null)
            return d;
        d = DateUtils.parseDate(value);
        if (d != null)
            return d;
        d = DateUtils.parseTime(value);
        if (d != null)
            return d;
        // try localized and english text parsers (for Web headers and interactive
        // filter spec.)
        d = parseDateSubset(value, localizedParsePatterns, Locale.ENGLISH, TimeZone.getTimeZone("GMT"));
        if (d != null)
            return d;
        d = parseDateSubset(value, localizedParsePatterns, null, TimeZone.getTimeZone("GMT"));
        if (d != null)
            return d;
        d = parseDateSubset(value, localizedNoTzParsePatterns, Locale.ENGLISH, TimeZone.getTimeZone("GMT"));
        if (d != null)
            return d;
        d = parseDateSubset(value, localizedNoTzParsePatterns, null, TimeZone.getTimeZone("GMT"));
        if (d != null)
            return d;
        // try other common patterns that might not quite match JavaRosa parsers
        d = parseDateSubset(value, tzParsePatterns, null, TimeZone.getTimeZone("GMT"));
        if (d != null)
            return d;
        d = parseDateSubset(value, noTzParsePatterns, null, TimeZone.getTimeZone("GMT"));
        if (d != null)
            return d;
        // try the locale- and timezone- specific parsers
        {
            DateFormat formatter = DateFormat.getDateTimeInstance();
            ParsePosition pos = new ParsePosition(0);
            d = formatter.parse(value, pos);
            if (d != null && pos.getIndex() == value.length()) {
                return d;
            }
        }
        {
            DateFormat formatter = DateFormat.getDateInstance();
            ParsePosition pos = new ParsePosition(0);
            d = formatter.parse(value, pos);
            if (d != null && pos.getIndex() == value.length()) {
                return d;
            }
        }
        {
            DateFormat formatter = DateFormat.getTimeInstance();
            ParsePosition pos = new ParsePosition(0);
            d = formatter.parse(value, pos);
            if (d != null && pos.getIndex() == value.length()) {
                return d;
            }
        }
        throw new IllegalArgumentException("Unable to parse the date: " + value);
    }

    public static final String asSubmissionDateTimeString(Date d) {
        if (d == null)
            return null;
        return DateUtils.formatDateTime(d, DateUtils.FORMAT_ISO8601);
    }

    public static final String asSubmissionDateOnlyString(Date d) {
        if (d == null)
            return null;
        return DateUtils.formatDate(d, DateUtils.FORMAT_ISO8601);
    }

    public static final String asSubmissionTimeOnlyString(Date d) {
        if (d == null)
            return null;
        return DateUtils.formatTime(d, DateUtils.FORMAT_ISO8601);
    }

    /**
     * Useful static method for constructing a UPPER_CASE persistence layer name
     * from a camelCase name. This inserts an underscore before a leading capital
     * letter and toUpper()s the resulting string. The transformation maps
     * multiple camelCase names to the same UPPER_CASE name so it is not
     * reversible.
     * <ul>
     * <li>thisURL => THIS_URL</li>
     * <li>thisUrl => THIS_URL</li>
     * <li>myFirstObject => MY_FIRST_OBJECT</li>
     * </ul>
     *
     * @param name
     * @return
     */
    public static final String unCamelCase(String name) {
        StringBuilder b = new StringBuilder();
        boolean lastCap = true;
        for (int i = 0; i < name.length(); ++i) {
            char ch = name.charAt(i);
            if (Character.isUpperCase(ch)) {
                if (!lastCap) {
                    b.append('_');
                }
                lastCap = true;
                b.append(ch);
            } else if (Character.isLetterOrDigit(ch)) {
                lastCap = false;
                b.append(Character.toUpperCase(ch));
            } else {
                throw new IllegalArgumentException("Argument is not a valid camelCase name: " + name);
            }
        }
        return b.toString();
    }

    /**
     * Return the GoogleDocs datetime string representation of a datetime.
     *
     * @param d
     * @return
     */
    public static final String googleDocsDateTime(Date d) {
        if (d == null)
            return null;
        SimpleDateFormat asGoogleDoc = new SimpleDateFormat(PATTERN_GOOGLE_DOCS);
        asGoogleDoc.setTimeZone(TimeZone.getTimeZone("GMT"));
        return asGoogleDoc.format(d);
    }

    /**
     * Return the GoogleDocs date string representation of a date-only datetime.
     *
     * @param d
     * @return
     */
    public static final String googleDocsDateOnly(Date d) {
        if (d == null)
            return null;
        SimpleDateFormat asGoogleDocDateOnly = new SimpleDateFormat(PATTERN_GOOGLE_DOCS_DATE_ONLY);
        asGoogleDocDateOnly.setTimeZone(TimeZone.getTimeZone("GMT"));
        return asGoogleDocDateOnly.format(d);
    }

    /**
     * Return the ISO8601 string representation of a date.
     *
     * @param d
     * @return
     */
    public static final String iso8601Date(Date d) {
        if (d == null)
            return null;
        // SDF is not thread-safe
        SimpleDateFormat asGMTiso8601 = new SimpleDateFormat(PATTERN_ISO8601); // with
                                                                               // time
                                                                               // zone
        asGMTiso8601.setTimeZone(TimeZone.getTimeZone("GMT"));
        return asGMTiso8601.format(d);
    }

    /**
     * Return the RFC1123 string representation of a date.
     * 
     * @param d
     * @return
     */
    public static final String rfc1123Date(Date d) {
        if (d == null)
            return null;
        // SDF is not thread-safe
        SimpleDateFormat asGMTrfc1123 = new SimpleDateFormat(PATTERN_RFC1123); // with
                                                                               // time
                                                                               // zone
        asGMTrfc1123.setTimeZone(TimeZone.getTimeZone("GMT"));
        return asGMTrfc1123.format(d);
    }

    public static final String purgeDateString(Date d) {
        if (d == null)
            return null;
        // SDF is not thread-safe
        SimpleDateFormat purgeDateFormat = new SimpleDateFormat(PURGE_DATE_FORMAT);
        return purgeDateFormat.format(d);
    }

    public static final Date parsePurgeDateString(String str) throws ParseException {
        if (str == null) {
            return null;
        }
        // SDF is not thread-safe
        SimpleDateFormat purgeDateFormat = new SimpleDateFormat(PURGE_DATE_FORMAT);
        return purgeDateFormat.parse(str);
    }

    /**
     * Return a string with utf-8 characters replaced with backslash-uxxxx codes.
     * Useful for debugging.
     *
     * @param str
     * @return printable rendition of non-ASCII utf-8 characters.
     */
    public static final String escapeUTF8String(String str) {
        StringBuilder b = new StringBuilder();
        for (int i = 0; i < str.length(); ++i) {
            int code = str.codePointAt(i);
            if (code < 127) {
                b.append(str.charAt(i));
            } else {
                String val = Integer.toHexString(code);
                while (val.length() < 4) {
                    val = '0' + val;
                }
                b.append("\\u" + val);
            }
        }
        return b.toString();
    }

    public static String readResponse(HttpResponse resp) throws IOException {

        HttpEntity e = resp.getEntity();
        if (e != null) {
            return WebUtils.readResponseHelper(e.getContent());
        }

        return BasicConsts.EMPTY_STRING;
    }

    private static String readResponseHelper(InputStream content) {
        StringBuffer response = new StringBuffer();

        if (content != null) {
            // TODO: this section of code is possibly causing 'WARNING: Going to
            // buffer
            // response body of large or unknown size. Using getResponseBodyAsStream
            // instead is recommended.'
            // The WARNING is most likely only happening when running appengine
            // locally,
            // but we should investigate to make sure
            BufferedReader reader = null;
            InputStreamReader isr = null;
            try {
                reader = new BufferedReader(isr = new InputStreamReader(content, BasicConsts.UTF8_ENCODE));
                String responseLine;
                while ((responseLine = reader.readLine()) != null) {
                    response.append(responseLine);
                }
            } catch (UnsupportedEncodingException ex) {
                ex.printStackTrace();
            } catch (IllegalStateException ex) {
                ex.printStackTrace();
            } catch (IOException ex) {
                ex.printStackTrace();
            } finally {
                try {
                    if (reader != null) {
                        reader.close();
                    }
                } catch (IOException ex) {
                    // no-op
                }
                try {
                    if (isr != null) {
                        isr.close();
                    }
                } catch (IOException ex) {
                    // no-op
                }
            }
        }
        return response.toString();
    }

}