PageRenderTime 46ms CodeModel.GetById 17ms RepoModel.GetById 1ms app.codeStats 0ms

/dumbhippo/branches/production-replaced-2006-12-13/server/src/com/dumbhippo/services/FacebookWebServices.java

https://gitlab.com/manoj-makkuboy/magnetism
Java | 330 lines | 227 code | 54 blank | 49 comment | 61 complexity | 542fcd44501f52f85445e483ed326292 MD5 | raw file
  1. package com.dumbhippo.services;
  2. import java.security.MessageDigest;
  3. import java.security.NoSuchAlgorithmException;
  4. import java.util.ArrayList;
  5. import java.util.Collections;
  6. import java.util.Date;
  7. import java.util.List;
  8. import java.util.Set;
  9. import java.util.HashSet;
  10. import org.slf4j.Logger;
  11. import com.dumbhippo.GlobalSetup;
  12. import com.dumbhippo.persistence.FacebookAccount;
  13. import com.dumbhippo.persistence.FacebookAlbumData;
  14. import com.dumbhippo.persistence.FacebookEvent;
  15. import com.dumbhippo.persistence.FacebookEventType;
  16. import com.dumbhippo.persistence.FacebookPhotoData;
  17. import com.dumbhippo.server.Configuration;
  18. import com.dumbhippo.server.HippoProperty;
  19. import com.dumbhippo.server.Configuration.PropertyNotFoundException;
  20. public class FacebookWebServices extends AbstractXmlRequest<FacebookSaxHandler> {
  21. static private final Logger logger = GlobalSetup.getLogger(FacebookWebServices.class);
  22. private String apiKey;
  23. private String secret;
  24. public FacebookWebServices(int timeoutMilliseconds, Configuration config) {
  25. super(timeoutMilliseconds);
  26. try {
  27. apiKey = config.getPropertyNoDefault(HippoProperty.FACEBOOK_API_KEY).trim();
  28. if (apiKey.length() == 0)
  29. apiKey = null;
  30. this.secret = config.getPropertyNoDefault(HippoProperty.FACEBOOK_SECRET).trim();
  31. if (secret.length() == 0)
  32. secret = null;
  33. } catch (PropertyNotFoundException e) {
  34. apiKey = null;
  35. secret = null;
  36. }
  37. if ((apiKey == null) || (secret == null))
  38. logger.warn("Facebook API key or secret are not set, can't make Facebook web services calls.");
  39. }
  40. public void updateSession(FacebookAccount facebookAccount, String facebookAuthToken) {
  41. List<String> params = new ArrayList<String>();
  42. String methodName = "facebook.auth.getSession";
  43. params.add("method=" + methodName);
  44. params.add("auth_token=" + facebookAuthToken);
  45. String wsUrl = generateFacebookRequest(params);
  46. FacebookSaxHandler handler = parseUrl(new FacebookSaxHandler(), wsUrl);
  47. if (handleErrorCode(facebookAccount, handler, methodName)) {
  48. return;
  49. }
  50. facebookAccount.setSessionKey(handler.getSessionKey());
  51. facebookAccount.setFacebookUserId(handler.getFacebookUserId());
  52. if (handler.getSessionKey() != null)
  53. facebookAccount.setSessionKeyValid(true);
  54. }
  55. /**
  56. * Return update time if the number of unread messages changed or the number of total messages
  57. * increased and some of them are unread messages.
  58. *
  59. * @param facebookAccount
  60. * @return update time if the set of unread messages changed
  61. */
  62. public long updateMessageCount(FacebookAccount facebookAccount) {
  63. // generate messages request using session from facebookAccount
  64. List<String> params = new ArrayList<String>();
  65. String methodName = "facebook.messages.getCount";
  66. params.add("method=" + methodName);
  67. params.add("session_key=" + facebookAccount.getSessionKey());
  68. String wsUrl = generateFacebookRequest(params);
  69. FacebookSaxHandler handler = parseUrl(new FacebookSaxHandler(), wsUrl);
  70. if (handleErrorCode(facebookAccount, handler, methodName)) {
  71. return -1;
  72. }
  73. int newUnread = handler.getUnreadMessageCount();
  74. int newTotal = handler.getTotalMessageCount();
  75. int oldUnread = facebookAccount.getUnreadMessageCount();
  76. int oldTotal = facebookAccount.getTotalMessageCount();
  77. if ((newUnread != -1) && (newTotal != -1)) {
  78. // with the current api, we can only know for sure that the set of new messages
  79. // changed if newUnread != oldUnread; however there is another situation when
  80. // the user could have new messages: if newTotal > oldTotal, the user must have received
  81. // new messages, though we do not know if they read the new ones and left the
  82. // old unread messages unread or actually read the old unread messages, and then
  83. // received the new ones
  84. // also, deleted messages are not included in the number of total messages we get
  85. // from facebook, which prevents us from always notifying the user if they possibly
  86. // have new messages
  87. // so we would not detect that the person has new messages if, for example, between
  88. // our requests they read 2 of their new messages, deleted them, and received 2 other
  89. // new messages
  90. facebookAccount.setTotalMessageCount(newTotal);
  91. if ((newUnread != oldUnread) || ((newTotal > oldTotal) && (newUnread > 0))) {
  92. long time = (new Date()).getTime();
  93. // we want the timestamp to correspond to the times when we signify change
  94. // in unread messages
  95. facebookAccount.setMessageCountTimestampAsLong(time);
  96. facebookAccount.setUnreadMessageCount(newUnread);
  97. return time;
  98. }
  99. } else {
  100. logger.error("Did not receive a valid response for facebook.messages.getCount request, did not receive an error either."
  101. + " Error message {}, error code {}", handler.getErrorMessage(), handler.getErrorCode());
  102. }
  103. return -1;
  104. }
  105. public FacebookEvent updateWallMessageCount(FacebookAccount facebookAccount) {
  106. List<String> params = new ArrayList<String>();
  107. String methodName = "facebook.wall.getCount";
  108. params.add("method=" + methodName);
  109. params.add("session_key=" + facebookAccount.getSessionKey());
  110. params.add("id=" + facebookAccount.getFacebookUserId());
  111. String wsUrl = generateFacebookRequest(params);
  112. FacebookSaxHandler handler = parseUrl(new FacebookSaxHandler(), wsUrl);
  113. if (handleErrorCode(facebookAccount, handler, methodName)) {
  114. return null;
  115. }
  116. int newCount = handler.getWallMessageCount();
  117. int oldCount = facebookAccount.getWallMessageCount();
  118. if (newCount != -1) {
  119. facebookAccount.setWallMessageCount(newCount);
  120. // if it is the first time we get the wall message count, we do not want to create
  121. // an event about it
  122. if ((newCount > oldCount) && (oldCount != -1)) {
  123. return new FacebookEvent(facebookAccount, FacebookEventType.NEW_WALL_MESSAGES_EVENT,
  124. newCount - oldCount, (new Date()).getTime());
  125. }
  126. } else {
  127. logger.error("Did not receive a valid response for facebook.wall.getCount request, did not receive an error either."
  128. + " Error message {}, error code {}", handler.getErrorMessage(), handler.getErrorCode());
  129. }
  130. return null;
  131. }
  132. public long updatePokeCount(FacebookAccount facebookAccount) {
  133. // generate messages request using session from facebookAccount
  134. List<String> params = new ArrayList<String>();
  135. String methodName = "facebook.pokes.getCount";
  136. params.add("method=" + methodName);
  137. params.add("session_key=" + facebookAccount.getSessionKey());
  138. String wsUrl = generateFacebookRequest(params);
  139. FacebookSaxHandler handler = parseUrl(new FacebookSaxHandler(), wsUrl);
  140. if (handleErrorCode(facebookAccount, handler, methodName)) {
  141. return -1;
  142. }
  143. int newUnseen = handler.getUnseenPokeCount();
  144. int newTotal = handler.getTotalPokeCount();
  145. int oldUnseen = facebookAccount.getUnseenPokeCount();
  146. int oldTotal = facebookAccount.getTotalPokeCount();
  147. if ((newUnseen != -1) && (newTotal != -1)) {
  148. facebookAccount.setTotalPokeCount(newTotal);
  149. if ((newUnseen != oldUnseen) || ((newTotal > oldTotal) && (newUnseen > 0))) {
  150. facebookAccount.setUnseenPokeCount(newUnseen);
  151. if ((newUnseen > 0) || (oldUnseen != -1)) {
  152. long time = (new Date()).getTime();
  153. // we want the timestamp to correspond to the times when we signify change
  154. // in unseen pokes
  155. facebookAccount.setPokeCountTimestampAsLong(time);
  156. return time;
  157. }
  158. }
  159. } else {
  160. logger.error("Did not receive a valid response for facebook.pokes.getCount request, did not receive an error either."
  161. + " Error message {}, error code {}", handler.getErrorMessage(), handler.getErrorCode());
  162. }
  163. return -1;
  164. }
  165. public List<FacebookPhotoData> updateTaggedPhotos(FacebookAccount facebookAccount) {
  166. List<String> params = new ArrayList<String>();
  167. String methodName = "facebook.photos.getOfUser";
  168. params.add("method=" + methodName);
  169. params.add("session_key=" + facebookAccount.getSessionKey());
  170. params.add("id=" + facebookAccount.getFacebookUserId());
  171. String wsUrl = generateFacebookRequest(params);
  172. FacebookSaxHandler handler = parseUrl(new FacebookSaxHandler(facebookAccount), wsUrl);
  173. if (handleErrorCode(facebookAccount, handler, methodName)) {
  174. return null;
  175. }
  176. int newCount = handler.getTaggedPhotoCount();
  177. int oldCount = facebookAccount.getTaggedPhotos().size();
  178. if (newCount != oldCount) {
  179. // we do not want to go over all photos if the count did not change, even if there was an
  180. // equal number of photos added and removed, we we'll get all the new photos next time
  181. // the count changes
  182. // the reason we have this logic here and not in the FacebookTrackerBean is because it
  183. // would be nice to change the code so that we parse through all the returned photos only
  184. // if the count did change
  185. return handler.getTaggedPhotos();
  186. } else if (!facebookAccount.getTaggedPhotosPrimed()) {
  187. // this covers the case when the first time we get tagged photos from facebook, the user has none
  188. facebookAccount.setTaggedPhotosPrimed(true);
  189. }
  190. return null;
  191. }
  192. public Set<FacebookAlbumData> getModifiedAlbums(FacebookAccount facebookAccount) {
  193. List<String> params = new ArrayList<String>();
  194. String methodName = "facebook.photos.getAlbums";
  195. params.add("method=" + methodName);
  196. params.add("session_key=" + facebookAccount.getSessionKey());
  197. params.add("id=" + facebookAccount.getFacebookUserId());
  198. String wsUrl = generateFacebookRequest(params);
  199. FacebookSaxHandler handler = parseUrl(new FacebookSaxHandler(facebookAccount), wsUrl);
  200. Set<FacebookAlbumData> modifiedAlbums = new HashSet<FacebookAlbumData>();
  201. if (handleErrorCode(facebookAccount, handler, methodName)) {
  202. return modifiedAlbums;
  203. }
  204. // because it is typical that the album would be created, and then photos would be
  205. // added to it over a period of time, we want to check the modification date for each album,
  206. // rather than the count of albums
  207. // this assumes that the modification date for a newly added album will be later than the
  208. // last album modification date we are storing, so that's how we will learn than the album
  209. // is new, rather than based on the total count of albums
  210. for (FacebookAlbumData album : handler.getAlbums()) {
  211. if (album.getModifiedTimestampAsLong() > facebookAccount.getAlbumsModifiedTimestampAsLong()) {
  212. modifiedAlbums.add(album);
  213. }
  214. }
  215. if (modifiedAlbums.isEmpty() && facebookAccount.getAlbumsModifiedTimestampAsLong() < 0) {
  216. // this covers the case when the first time we get albums from facebook, the user has none
  217. facebookAccount.setAlbumsModifiedTimestampAsLong((new Date()).getTime());
  218. }
  219. return modifiedAlbums;
  220. }
  221. private String generateFacebookRequest(List<String> params) {
  222. StringBuffer signatureBuffer = new StringBuffer();
  223. StringBuffer requestBuffer = new StringBuffer();
  224. requestBuffer.append("http://api.facebook.com/restserver.php?");
  225. params.add("api_key=" + apiKey);
  226. params.add("call_id=" + System.currentTimeMillis());
  227. // sort the list of parameters alphabetically
  228. Collections.sort(params);
  229. // concatinate them in order
  230. boolean firstParam = true;
  231. for (String param : params) {
  232. signatureBuffer.append(param);
  233. if (firstParam) {
  234. firstParam = false;
  235. } else {
  236. requestBuffer.append("&");
  237. }
  238. requestBuffer.append(param);
  239. }
  240. // concatinate the secret in the end of the signatureBuffer
  241. signatureBuffer.append(this.secret);
  242. StringBuffer signature = new StringBuffer();
  243. try {
  244. // create an MD5 hash of the constructed buffer
  245. MessageDigest md = MessageDigest.getInstance("MD5");
  246. for (byte b : md.digest(signatureBuffer.toString().getBytes())) {
  247. signature.append(Integer.toHexString((b & 0xf0) >>> 4));
  248. signature.append(Integer.toHexString(b & 0x0f));
  249. }
  250. } catch (NoSuchAlgorithmException e) {
  251. logger.error("No MD5 digest available!", e);
  252. }
  253. requestBuffer.append("&sig=");
  254. requestBuffer.append(signature);
  255. logger.debug("Created facebook web request {}", requestBuffer.toString());
  256. return requestBuffer.toString();
  257. }
  258. private boolean handleErrorCode(FacebookAccount facebookAccount, FacebookSaxHandler handler, String requestName) {
  259. if (handler == null)
  260. return true;
  261. if (handler.getErrorCode() > 0) {
  262. if (handler.getErrorCode() == FacebookSaxHandler.FacebookErrorCode.API_EC_PARAM_SESSION_KEY.getCode()) {
  263. // setSessionKeyValid to false if we received the response that the session key is no longer valid
  264. // FIXME this is somewhat conceptually weird, because we shouldn't do web services with a transaction
  265. // open, which means modifying a persistence bean won't do anything persistent.
  266. // Should consider keeping the layering clean by not passing persistence beans into web service code.
  267. facebookAccount.setSessionKeyValid(false);
  268. } else {
  269. logger.error("Did not receive a valid response for {} request, error message {}, error code {}",
  270. new String[]{requestName, handler.getErrorMessage(), Integer.toString(handler.getErrorCode())});
  271. }
  272. return true;
  273. }
  274. return false;
  275. }
  276. }