Home | History | Annotate | Download | only in wiktionary
      1 /*
      2  * Copyright (C) 2009 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.example.android.wiktionary;
     18 
     19 import org.apache.http.HttpEntity;
     20 import org.apache.http.HttpResponse;
     21 import org.apache.http.StatusLine;
     22 import org.apache.http.client.HttpClient;
     23 import org.apache.http.client.methods.HttpGet;
     24 import org.apache.http.impl.client.DefaultHttpClient;
     25 import org.json.JSONArray;
     26 import org.json.JSONException;
     27 import org.json.JSONObject;
     28 
     29 import android.content.Context;
     30 import android.content.pm.PackageInfo;
     31 import android.content.pm.PackageManager;
     32 import android.content.pm.PackageManager.NameNotFoundException;
     33 import android.net.Uri;
     34 import android.util.Log;
     35 
     36 import java.io.ByteArrayOutputStream;
     37 import java.io.IOException;
     38 import java.io.InputStream;
     39 
     40 /**
     41  * Helper methods to simplify talking with and parsing responses from a
     42  * lightweight Wiktionary API. Before making any requests, you should call
     43  * {@link #prepareUserAgent(Context)} to generate a User-Agent string based on
     44  * your application package name and version.
     45  */
     46 public class SimpleWikiHelper {
     47     private static final String TAG = "SimpleWikiHelper";
     48 
     49     /**
     50      * Partial URL to use when requesting the detailed entry for a specific
     51      * Wiktionary page. Use {@link String#format(String, Object...)} to insert
     52      * the desired page title after escaping it as needed.
     53      */
     54     private static final String WIKTIONARY_PAGE =
     55             "http://en.wiktionary.org/w/api.php?action=query&prop=revisions&titles=%s&" +
     56             "rvprop=content&format=json%s";
     57 
     58     /**
     59      * Partial URL to append to {@link #WIKTIONARY_PAGE} when you want to expand
     60      * any templates found on the requested page. This is useful when browsing
     61      * full entries, but may use more network bandwidth.
     62      */
     63     private static final String WIKTIONARY_EXPAND_TEMPLATES =
     64             "&rvexpandtemplates=true";
     65 
     66     /**
     67      * {@link StatusLine} HTTP status code when no server error has occurred.
     68      */
     69     private static final int HTTP_STATUS_OK = 200;
     70 
     71     /**
     72      * Shared buffer used by {@link #getUrlContent(String)} when reading results
     73      * from an API request.
     74      */
     75     private static byte[] sBuffer = new byte[512];
     76 
     77     /**
     78      * User-agent string to use when making requests. Should be filled using
     79      * {@link #prepareUserAgent(Context)} before making any other calls.
     80      */
     81     private static String sUserAgent = null;
     82 
     83     /**
     84      * Thrown when there were problems contacting the remote API server, either
     85      * because of a network error, or the server returned a bad status code.
     86      */
     87     public static class ApiException extends Exception {
     88         public ApiException(String detailMessage, Throwable throwable) {
     89             super(detailMessage, throwable);
     90         }
     91 
     92         public ApiException(String detailMessage) {
     93             super(detailMessage);
     94         }
     95     }
     96 
     97     /**
     98      * Thrown when there were problems parsing the response to an API call,
     99      * either because the response was empty, or it was malformed.
    100      */
    101     public static class ParseException extends Exception {
    102         public ParseException(String detailMessage, Throwable throwable) {
    103             super(detailMessage, throwable);
    104         }
    105     }
    106 
    107     /**
    108      * Prepare the internal User-Agent string for use. This requires a
    109      * {@link Context} to pull the package name and version number for this
    110      * application.
    111      */
    112     public static void prepareUserAgent(Context context) {
    113         try {
    114             // Read package name and version number from manifest
    115             PackageManager manager = context.getPackageManager();
    116             PackageInfo info = manager.getPackageInfo(context.getPackageName(), 0);
    117             sUserAgent = String.format(context.getString(R.string.template_user_agent),
    118                     info.packageName, info.versionName);
    119 
    120         } catch(NameNotFoundException e) {
    121             Log.e(TAG, "Couldn't find package information in PackageManager", e);
    122         }
    123     }
    124 
    125     /**
    126      * Read and return the content for a specific Wiktionary page. This makes a
    127      * lightweight API call, and trims out just the page content returned.
    128      * Because this call blocks until results are available, it should not be
    129      * run from a UI thread.
    130      *
    131      * @param title The exact title of the Wiktionary page requested.
    132      * @param expandTemplates If true, expand any wiki templates found.
    133      * @return Exact content of page.
    134      * @throws ApiException If any connection or server error occurs.
    135      * @throws ParseException If there are problems parsing the response.
    136      */
    137     public static String getPageContent(String title, boolean expandTemplates)
    138             throws ApiException, ParseException {
    139         // Encode page title and expand templates if requested
    140         String encodedTitle = Uri.encode(title);
    141         String expandClause = expandTemplates ? WIKTIONARY_EXPAND_TEMPLATES : "";
    142 
    143         // Query the API for content
    144         String content = getUrlContent(String.format(WIKTIONARY_PAGE,
    145                 encodedTitle, expandClause));
    146         try {
    147             // Drill into the JSON response to find the content body
    148             JSONObject response = new JSONObject(content);
    149             JSONObject query = response.getJSONObject("query");
    150             JSONObject pages = query.getJSONObject("pages");
    151             JSONObject page = pages.getJSONObject((String) pages.keys().next());
    152             JSONArray revisions = page.getJSONArray("revisions");
    153             JSONObject revision = revisions.getJSONObject(0);
    154             return revision.getString("*");
    155         } catch (JSONException e) {
    156             throw new ParseException("Problem parsing API response", e);
    157         }
    158     }
    159 
    160     /**
    161      * Pull the raw text content of the given URL. This call blocks until the
    162      * operation has completed, and is synchronized because it uses a shared
    163      * buffer {@link #sBuffer}.
    164      *
    165      * @param url The exact URL to request.
    166      * @return The raw content returned by the server.
    167      * @throws ApiException If any connection or server error occurs.
    168      */
    169     protected static synchronized String getUrlContent(String url) throws ApiException {
    170         if (sUserAgent == null) {
    171             throw new ApiException("User-Agent string must be prepared");
    172         }
    173 
    174         // Create client and set our specific user-agent string
    175         HttpClient client = new DefaultHttpClient();
    176         HttpGet request = new HttpGet(url);
    177         request.setHeader("User-Agent", sUserAgent);
    178 
    179         try {
    180             HttpResponse response = client.execute(request);
    181 
    182             // Check if server response is valid
    183             StatusLine status = response.getStatusLine();
    184             if (status.getStatusCode() != HTTP_STATUS_OK) {
    185                 throw new ApiException("Invalid response from server: " +
    186                         status.toString());
    187             }
    188 
    189             // Pull content stream from response
    190             HttpEntity entity = response.getEntity();
    191             InputStream inputStream = entity.getContent();
    192 
    193             ByteArrayOutputStream content = new ByteArrayOutputStream();
    194 
    195             // Read response into a buffered stream
    196             int readBytes = 0;
    197             while ((readBytes = inputStream.read(sBuffer)) != -1) {
    198                 content.write(sBuffer, 0, readBytes);
    199             }
    200 
    201             // Return result from buffered stream
    202             return new String(content.toByteArray());
    203         } catch (IOException e) {
    204             throw new ApiException("Problem communicating with API", e);
    205         }
    206     }
    207 
    208 }
    209