Java tutorial
/* * Copyright (c) 2011 Raunak Gupta * * 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 com.marketplace.io; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URISyntaxException; import java.util.List; import org.apache.commons.io.IOUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.HttpVersion; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPut; import org.apache.http.entity.StringEntity; import org.apache.http.entity.mime.MultipartEntity; import org.apache.http.entity.mime.content.ContentBody; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.params.CoreProtocolPNames; import org.apache.http.params.HttpConnectionParams; import com.gc.android.market.api.model.Market; import com.gc.android.market.api.model.Market.AppsResponse; import com.gc.android.market.api.model.Market.CommentsResponse; import com.google.gson.Gson; import com.google.gson.JsonIOException; import com.google.gson.JsonSyntaxException; import com.marketplace.Constants; import com.marketplace.Utils; import com.marketplace.exceptions.ConnectivityException; /** * <code>Sender</code> saves details (app info, comments, images) to the * database. The class is designed to save these details to a Rails app, but can * be modified to save details to a SQL database. * * @author raunak * @version 1.0 */ public class Sender { /** * Instance of <code>Log</code> */ private Log log = LogFactory.getLog(Sender.class); /** * A <code>HttpPut</code> object for making HTTP Put requests */ private HttpPut httpPut; /** * A <code>HttpPost</code> object for making HTTP Post requests */ private HttpPost httpPost; /** * A <code>HttpClient</code> object */ private HttpClient httpClient; /** * Constructs a <code>Sender</code> */ public Sender() { this.httpPut = new HttpPut(); this.httpPost = new HttpPost(); this.httpClient = new DefaultHttpClient(); this.httpClient.getParams().setParameter(CoreProtocolPNames.PROTOCOL_VERSION, HttpVersion.HTTP_1_1); HttpConnectionParams.setConnectionTimeout(this.httpClient.getParams(), 20000); } /** * Check if an entry for app by the package name passed as the parameter * already exists in the database. * * @param packageName * the existence of packageName to check * @return <code>ExistResponse</code> containing the id of app in the * database if it exists. * * @throws ConnectivityException * thrown if there was a problem connecting with the database */ public ExistResponse appExists(String packageName) throws ConnectivityException { ExistResponse existResponse = null; AppQuery query = new AppQuery(); query.packageName = packageName; byte[] response = doBasicHttpPost(query.toString(), Constants.appExistUrl); InputStream is = new ByteArrayInputStream(response); Reader reader = new InputStreamReader(is); try { existResponse = new Gson().fromJson(reader, ExistResponse.class); } catch (JsonSyntaxException jse) { jse.printStackTrace(); } catch (JsonIOException jio) { jio.printStackTrace(); } return existResponse; } /** * Check if an entry exists in the database for an app with the passed * target parameter * * @param appId * the id of app in the database * @param apiLevel * the android sdk * @return <code>AppTargetExistResponse</code> containing a boolean flag. * The flag is set to true if target exists, false otherwise. * @throws ConnectivityException * thrown if there was a problem connecting with the database */ public AppTargetExistResponse appTargetExists(int appId, int apiLevel) throws ConnectivityException { AppTargetExistResponse existResponse = null; Target target = new Target(); target.app_target.app_id = appId; target.app_target.target_id = apiLevel; byte[] response = doBasicHttpPost(target.toString(), Constants.appTargetExistUrl); InputStream is = new ByteArrayInputStream(response); Reader reader = new InputStreamReader(is); try { existResponse = new Gson().fromJson(reader, AppTargetExistResponse.class); } catch (JsonSyntaxException jse) { jse.printStackTrace(); } catch (JsonIOException jio) { jio.printStackTrace(); } return existResponse; } /** * Check if a comment exists by the same author. * * @param appId * the id of app in the database * @param authorId * the id of author * @return <code>ExistResponse</code> containing the id of comment in the * database if it exists. * @throws ConnectivityException * thrown if there was a problem connecting with the database */ public ExistResponse appCommentExists(int appId, String authorId) throws ConnectivityException { ExistResponse existResponse = null; CommentQuery commentQuery = new CommentQuery(); commentQuery.app_id = appId; commentQuery.authorId = authorId; byte[] response = doBasicHttpPost(commentQuery.toString(), Constants.appCommentExistUrl); InputStream is = new ByteArrayInputStream(response); Reader reader = new InputStreamReader(is); try { existResponse = new Gson().fromJson(reader, ExistResponse.class); } catch (JsonSyntaxException jse) { jse.printStackTrace(); } catch (JsonIOException jio) { jio.printStackTrace(); } return existResponse; } /** * Check if a visual by the filename (passed as a parameter) already exist * in the database. * * @param appId * the id of app in the database * @param filename * the name of file to check the existence of * @return <code>ExistResponse</code> containing the id of visual in the * database if it exists. * @throws ConnectivityException * thrown if there was a problem connecting with the database */ public ExistResponse appVisualExists(int appId, String filename) throws ConnectivityException { ExistResponse existResponse = null; VisualQuery visualQuery = new VisualQuery(); visualQuery.app_id = appId; visualQuery.filename = filename; byte[] response = doBasicHttpPost(visualQuery.toString(), Constants.appsUrl + "/" + appId + Constants.partVisualExistUrl); InputStream is = new ByteArrayInputStream(response); Reader reader = new InputStreamReader(is); try { existResponse = new Gson().fromJson(reader, ExistResponse.class); } catch (JsonSyntaxException jse) { jse.printStackTrace(); } catch (JsonIOException jio) { jio.printStackTrace(); } return existResponse; } /** * Save android app details to a Rails app. * * @param data * the app data in jSon formate * @return an internal id assigned by Rails to the app. * @throws ConnectivityException * thrown if there was a problem connecting with the database */ public int saveApp(String data) throws ConnectivityException { AppSaveResponse appResponse = null; byte[] response = doBasicHttpPost(data, Constants.appsUrlJson); InputStream is = new ByteArrayInputStream(response); Reader reader = new InputStreamReader(is); try { appResponse = new Gson().fromJson(reader, AppSaveResponse.class); } catch (JsonSyntaxException jse) { jse.printStackTrace(); } catch (JsonIOException jio) { jio.printStackTrace(); } return appResponse.app.id; } /** * Saves app details, the android sdk it targets and the required permission * * @param appsResponse * the <code>AppResponse</code> object to save * @param sdkVersion * the target sdk by app * @throws ConnectivityException * thrown if there was a problem connecting with the database */ public void addAppToCollection(AppsResponse appsResponse, int sdkVersion) throws ConnectivityException { for (int appIndex = 0; appIndex < appsResponse.getAppCount(); appIndex++) { int appId; // Check if an entry already exists ExistResponse existResponse = appExists(appsResponse.getApp(appIndex).getPackageName()); String data = convertAppToJsonString(appsResponse.getApp(appIndex)); if (existResponse.exists) { appId = existResponse.id; doBasicHttpPut(data, Constants.appsUrl + "/" + appId + Constants.jsonExtension); } else { appId = saveApp(data); } log.info("Adding app " + appsResponse.getApp(appIndex).getTitle() + " to database"); AppTargetExistResponse appTarget = appTargetExists(appId, sdkVersion); if (!appTarget.exists) { Target target = new Target(); target.app_target.app_id = appId; target.app_target.target_id = sdkVersion; doBasicHttpPost(target.toString(), Constants.appTargetUrl); } Permissions permissions = new Permissions(); permissions.app_id = appId; permissions.permissions = Utils .permissionToInt(appsResponse.getApp(appIndex).getExtendedInfo().getPermissionIdList()); doBasicHttpPost(permissions.toString(), Constants.appPermissionUrl); } } /** * Saves app comments to the database * * @param commentsResponse * <code>CommentResponse</code> object containing the comments to * save * @param appId * the id of app in database * @throws ConnectivityException * thrown if there was a problem connecting with the database */ public void addCommentToCollection(CommentsResponse commentsResponse, int appId) throws ConnectivityException { for (int commentIndex = 0; commentIndex < commentsResponse.getCommentsCount(); commentIndex++) { ExistResponse existResponse = appCommentExists(appId, commentsResponse.getComments(commentIndex).getAuthorId()); String data = convertCommentToJsonString(commentsResponse.getComments(commentIndex), appId); if (existResponse.exists) { doBasicHttpPut(data, Constants.appCommentUrl + "/" + existResponse.id + Constants.jsonExtension); } else { doBasicHttpPost(data, Constants.appCommentUrlJson); } } } /** * aves app visuals to the database. * * @param data * app image * @param db_app_id * the id of app in database * @param filename * the name by which to save the image * @throws ConnectivityException * thrown if there was a problem connecting with the database */ public void addVisualToCollection(byte[] data, int db_app_id, String filename) throws ConnectivityException { ExistResponse existResponse = appVisualExists(db_app_id, filename); log.info("Visual exist? - " + existResponse.exists); if (existResponse.exists) { doComplexHttpPut(data, "image/jpeg", filename, Constants.appsUrl + "/" + db_app_id + Constants.visuals + "/" + existResponse.id); } else { doComplexHttpPost(data, "image/jpeg", filename, Constants.appsUrl + "/" + db_app_id + Constants.visuals); } } /** * Perform a simple HTTP Put request * * @param data * information that needs to be sent * @param url * the location to send the information to * @throws ConnectivityException * thrown if there was a problem connecting with the database */ public void doBasicHttpPut(String data, String url) throws ConnectivityException { HttpResponse httpResponse = null; StringEntity stringEntity = null; try { httpPut = new HttpPut(); httpPut.setURI(new URI(url)); httpPut.setHeader("Content-Type", "application/json"); } catch (URISyntaxException e) { throw new ConnectivityException("Error occured while setting URL"); } try { stringEntity = new StringEntity(data, "UTF-8"); if (stringEntity != null) { httpPut.setEntity(stringEntity); httpResponse = httpClient.execute(httpPut); HttpEntity entity = httpResponse.getEntity(); if (entity != null) { entity.getContent().close(); } } } catch (UnsupportedEncodingException e) { e.printStackTrace(); } catch (ClientProtocolException e) { throw new ConnectivityException("Error occured in Client Protocol"); } catch (IOException e) { e.printStackTrace(); } } /** * Perform a simple HTTP Post request * * @param data * information that needs to be sent * @param url * the location to send the information to * @return the response returned by Rails app * @throws ConnectivityException * thrown if there was a problem connecting with the database */ public byte[] doBasicHttpPost(String data, String url) throws ConnectivityException { byte[] response = null; HttpResponse httpResponse = null; StringEntity stringEntity = null; try { httpPost = new HttpPost(); httpPost.setURI(new URI(url)); httpPost.addHeader("Content-Type", "application/json"); } catch (URISyntaxException e) { throw new ConnectivityException("Error occured while setting URL"); } try { stringEntity = new StringEntity(data, "UTF-8"); if (stringEntity != null) { httpPost.setEntity(stringEntity); httpResponse = httpClient.execute(httpPost); HttpEntity entity = httpResponse.getEntity(); if (entity != null) { InputStream contentStream = entity.getContent(); response = IOUtils.toByteArray(contentStream); contentStream.close(); } } } catch (UnsupportedEncodingException e) { e.printStackTrace(); } catch (ClientProtocolException e) { throw new ConnectivityException("Error occured in Client Protocol"); } catch (IOException e) { e.printStackTrace(); } return response; } /** * Perform a complex HTTP Put (send files over the network) * * @param data * file that needs to be sent * @param mimeType * the content type of file * @param filename * the name of file * @param url * the location to send the file to * @throws ConnectivityException * thrown if there was a problem connecting with the database */ public void doComplexHttpPut(byte[] data, String mimeType, String filename, String url) throws ConnectivityException { HttpResponse httpResponse = null; try { httpPut = new HttpPut(); httpPut.setURI(new URI(url)); httpPut.addHeader("content_type", "image/jpeg"); } catch (URISyntaxException e) { throw new ConnectivityException("Error occured while setting URL"); } ContentBody contentBody = new ByteArrayBody(data, mimeType, filename); MultipartEntity multipartEntity = new MultipartEntity(); multipartEntity.addPart("image", contentBody); httpPut.setEntity(multipartEntity); try { httpResponse = httpClient.execute(httpPut); HttpEntity entity = httpResponse.getEntity(); if (entity != null) { entity.getContent().close(); } } catch (ClientProtocolException e) { throw new ConnectivityException("Error occured in Client Protocol"); } catch (IOException e) { e.printStackTrace(); } } /** * Perform a complex HTTP Post (send files over the network) * * @param data * file that needs to be sent * @param mimeType * the content type of file * @param filename * the name of file * @param url * the location to send the file to * @throws ConnectivityException * thrown if there was a problem connecting with the database */ public void doComplexHttpPost(byte[] data, String mimeType, String filename, String url) throws ConnectivityException { HttpResponse httpResponse = null; try { httpPost = new HttpPost(); httpPost.setURI(new URI(url)); httpPost.addHeader("content_type", "image/jpeg"); } catch (URISyntaxException e) { throw new ConnectivityException("Error occured while setting URL"); } ContentBody contentBody = new ByteArrayBody(data, mimeType, filename); MultipartEntity multipartEntity = new MultipartEntity(); multipartEntity.addPart("image", contentBody); httpPost.setEntity(multipartEntity); try { httpResponse = httpClient.execute(httpPost); HttpEntity entity = httpResponse.getEntity(); if (entity != null) { entity.getContent().close(); } } catch (ClientProtocolException e) { throw new ConnectivityException("Error occured in Client Protocol"); } catch (IOException e) { e.printStackTrace(); } } /** * Converts a Market.App object to json string * * @param app * the <code>Market.App</code> to convert * @return jSon representation of <code>Market.App</code> */ public String convertAppToJsonString(Market.App app) { App appJsonString = new App(); appJsonString.app.appId = app.getId(); appJsonString.app.appType = app.getAppType().toString(); appJsonString.app.category = app.getExtendedInfo().getCategory(); appJsonString.app.creator = app.getCreator(); appJsonString.app.description = app.getExtendedInfo().getDescription(); appJsonString.app.email = app.getExtendedInfo().getContactEmail(); appJsonString.app.installSize = app.getExtendedInfo().getInstallSize(); appJsonString.app.packageName = app.getPackageName(); appJsonString.app.phone = app.getExtendedInfo().getContactPhone(); appJsonString.app.price = app.getPrice(); appJsonString.app.priceCurrency = app.getPriceCurrency(); appJsonString.app.promoText = app.getExtendedInfo().getPromoText(); appJsonString.app.promoVideo = app.getExtendedInfo().getPromotionalVideo(); appJsonString.app.recentChanges = app.getExtendedInfo().getRecentChanges(); appJsonString.app.screenshotCount = app.getExtendedInfo().getScreenshotsCount(); appJsonString.app.title = app.getTitle(); appJsonString.app.version = app.getVersion(); appJsonString.app.website = app.getExtendedInfo().getContactWebsite(); appJsonString.rating.rating = app.getRating(); appJsonString.rating.ratingCount = app.getRatingsCount(); appJsonString.rating.downloadCount = app.getExtendedInfo().getDownloadsCount(); appJsonString.rating.downloadCountText = app.getExtendedInfo().getDownloadsCountText(); return appJsonString.toString(); } /** * Converts a Market.Comment object to json string * * @param comment * the <code>Market.Comment</code> to convert * @param app_id * the id of app in database * @return jSon representation of <code>Market.Comment</code> */ public String convertCommentToJsonString(Market.Comment comment, int app_id) { Comment commentJsonString = new Comment(); commentJsonString.app_id = app_id; commentJsonString.comment.text = comment.getText(); commentJsonString.comment.rating = comment.getRating(); commentJsonString.comment.authorId = comment.getAuthorId(); commentJsonString.comment.authorName = comment.getAuthorName(); commentJsonString.comment.creationTime = comment.getCreationTime(); return commentJsonString.toString(); } /** * <code>Permissions</code> is used for converting a list of permissions for * an Android app into jSon String. * * @author raunak * @version 1.0 */ class Permissions { /** * the app id in database */ public int app_id; /** * the permissions for an app */ public List<Integer> permissions; @Override public String toString() { return Utils.gson.toJson(this); } } /** * <code>Target</code> is used for converting a target sdk for an Android * app into jSon String. * * @author raunak * @version 1.0 */ class Target { public TargetContainer app_target; public Target() { this.app_target = new TargetContainer(); } public class TargetContainer { /** * The app id in database */ public int app_id; /** * the target sdk */ public int target_id; } @Override public String toString() { return Utils.gson.toJson(this); } } /** * <code>App</code> is used for creating an jSon representation of * <code>Market.App</code. * * @author raunak * @version 1.0 */ public class App { public AppContainer app; public Rating rating; public App() { app = new AppContainer(); rating = new Rating(); } /** * Contains basic information on an App. * * @author raunak * @version 1.0 */ public class AppContainer { public String title; public String promoText; public String priceCurrency; public String email; public String appId; public String website; public String version; public String recentChanges; public String promoVideo; public int versionCode; public String category; public String description; public String packageName; public int screenshotCount; public String price; public String phone; public String appType; public String creator; public int installSize; } /** * Contains rating information pertaining to an app. * * @author raunak * @version 1.0 */ public class Rating { public String rating; public int ratingCount; public int downloadCount; public String downloadCountText; } @Override public String toString() { return Utils.gson.toJson(this); } } /** * <code>Comment</code> is used for creating a jSon representation of * <code>Market.Comment</code>. * * @author raunak * @version 1.0 */ public class Comment { /** * The id of app in the database */ public int app_id; public CommentContainer comment; public Comment() { this.comment = new CommentContainer(); } /** * Holds basic info of comment * * @author raunak * @version 1.0 */ public class CommentContainer { public int rating; public String text; public String authorId; public String authorName; public long creationTime; } @Override public String toString() { return Utils.gson.toJson(this); } } /** * <code>AppQuery</code> is used to make packageName query against the * database * * @author raunak * @version 1.0 */ class AppQuery { /** * The name of package (for app) to package into a query. */ public String packageName; @Override public String toString() { return Utils.gson.toJson(this); } } /** * <code>CommentQuery</code> is used to create comment queries. The comment * query is used to check if an author has left a comment before. If so, * instead of inserting a new comment, update their comment in the database. * * @author raunak * @version 1.0 */ class CommentQuery { /** * Id of app in the database */ public int app_id; /** * Id of author */ public String authorId; @Override public String toString() { return Utils.gson.toJson(this); } } /** * <code>VisualQuery</code> is used to create queries for visual. The query * is to check if an image by the filename already exists. * * @author raunak * @version 1.0 */ class VisualQuery { /** * Id of app in the database */ public int app_id; /** * Name of file to query */ public String filename; @Override public String toString() { return Utils.gson.toJson(this); } } /** * The response returned from a rails app after saving an app. "{app:{id:1}" * <code>AppSaveResponse</code> is used to convert the reponse from json to * java object. * * @author raunak * @version 1.0 */ static class AppSaveResponse { public App app; public AppSaveResponse() { app = new App(); } public static class App { public int id; } } /** * <code>ExistResponse</code> essentially holds the id of app in the * database if the app exists. * * @author raunak * @version 1.0 */ static class ExistResponse { /** * The app id in the database. </br> <Strong>Note:</Strong> Only gets * assigned if the apps exists in database. */ public int id; /** * a boolean flag. true if app exists in database; false otherwise */ public boolean exists; } /** * <code>AppTargetExistResponse</code> holds a boolean flag. The flag is set * to true if the target exists for an app, false otherwise. * * @author raunak * @version 1.0 */ static class AppTargetExistResponse { /** * a boolean flag; true if target exists, false otherwise. */ public boolean exists; } /** * Close HTTP connection */ public void closeConnection() { this.httpClient.getConnectionManager().shutdown(); } }