/*
* Copyright 2014 the original author or authors.
*
* 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.springframework.social.facebook.api.impl;
import static org.springframework.social.facebook.api.impl.PagedListUtils.*;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.springframework.social.UncategorizedApiException;
import org.springframework.social.facebook.api.FacebookLink;
import org.springframework.social.facebook.api.FeedOperations;
import org.springframework.social.facebook.api.GraphApi;
import org.springframework.social.facebook.api.PagedList;
import org.springframework.social.facebook.api.PagingParameters;
import org.springframework.social.facebook.api.Post;
import org.springframework.social.facebook.api.Post.PostType;
import org.springframework.social.facebook.api.PostData;
import org.springframework.social.support.URIBuilder;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
class FeedTemplate extends AbstractFacebookOperations implements FeedOperations {
private static final PagingParameters FIRST_PAGE = new PagingParameters(25, null, null, null);
private final GraphApi graphApi;
private ObjectMapper objectMapper;
private final RestTemplate restTemplate;
public FeedTemplate(GraphApi graphApi, RestTemplate restTemplate, ObjectMapper objectMapper, boolean isAuthorizedForUser) {
super(isAuthorizedForUser);
this.graphApi = graphApi;
this.restTemplate = restTemplate;
this.objectMapper = objectMapper;
}
public PagedList<Post> getFeed() {
return getFeed("me", FIRST_PAGE);
}
public PagedList<Post> getFeed(PagingParameters pagedListParameters) {
return getFeed("me", pagedListParameters);
}
public PagedList<Post> getFeed(String ownerId) {
return getFeed(ownerId, FIRST_PAGE);
}
public PagedList<Post> getFeed(String ownerId, PagingParameters pagedListParameters) {
requireAuthorization();
JsonNode responseNode = fetchConnectionList(GraphApi.GRAPH_API_URL + ownerId + "/feed", pagedListParameters);
return deserializeList(responseNode, null, Post.class);
}
public PagedList<Post> getHomeFeed() {
return getHomeFeed(FIRST_PAGE);
}
public PagedList<Post> getHomeFeed(PagingParameters pagedListParameters) {
requireAuthorization();
JsonNode responseNode = fetchConnectionList(GraphApi.GRAPH_API_URL + "me/home", pagedListParameters);
return deserializeList(responseNode, null, Post.class);
}
public PagedList<Post> getStatuses() {
return getStatuses("me", FIRST_PAGE);
}
public PagedList<Post> getStatuses(PagingParameters pagedListParameters) {
return getStatuses("me", pagedListParameters);
}
public PagedList<Post> getStatuses(String userId) {
return getStatuses(userId, FIRST_PAGE);
}
public PagedList<Post> getStatuses(String userId, PagingParameters pagedListParameters) {
requireAuthorization();
JsonNode responseNode = fetchConnectionList(GraphApi.GRAPH_API_URL + userId + "/statuses", pagedListParameters);
return deserializeList(responseNode, "status", Post.class);
}
public PagedList<Post> getLinks() {
return getLinks("me", FIRST_PAGE);
}
public PagedList<Post> getLinks(PagingParameters pagedListParameters) {
return getLinks("me", pagedListParameters);
}
public PagedList<Post> getLinks(String ownerId) {
return getLinks(ownerId, FIRST_PAGE);
}
public PagedList<Post> getLinks(String ownerId, PagingParameters pagedListParameters) {
requireAuthorization();
JsonNode responseNode = fetchConnectionList(GraphApi.GRAPH_API_URL + ownerId + "/links", pagedListParameters);
return deserializeList(responseNode, "link", Post.class);
}
public PagedList<Post> getPosts() {
return getPosts("me", FIRST_PAGE);
}
public PagedList<Post> getPosts(PagingParameters pagedListParameters) {
return getPosts("me", pagedListParameters);
}
public PagedList<Post> getPosts(String ownerId) {
return getPosts(ownerId, FIRST_PAGE);
}
public PagedList<Post> getPosts(String ownerId, PagingParameters pagedListParameters) {
requireAuthorization();
JsonNode responseNode = fetchConnectionList(GraphApi.GRAPH_API_URL + ownerId + "/posts", pagedListParameters);
return deserializeList(responseNode, null, Post.class);
}
public Post getPost(String entryId) {
requireAuthorization();
ObjectNode responseNode = (ObjectNode) restTemplate.getForObject(GraphApi.GRAPH_API_URL + entryId, JsonNode.class);
return deserializePost(null, Post.class, responseNode);
}
public String updateStatus(String message) {
return post("me", message);
}
public String postLink(String message, FacebookLink link) {
return postLink("me", message, link);
}
public String postLink(String ownerId, String message, FacebookLink link) {
requireAuthorization();
MultiValueMap<String, Object> map = new LinkedMultiValueMap<String, Object>();
map.set("link", link.getLink());
map.set("name", link.getName());
map.set("caption", link.getCaption());
map.set("description", link.getDescription());
map.set("message", message);
return graphApi.publish(ownerId, "feed", map);
}
public String post(PostData post) {
requireAuthorization();
return graphApi.publish(post.getTargetFeedId(), "feed", post.toRequestParameters());
}
public String post(String ownerId, String message) {
requireAuthorization();
MultiValueMap<String, Object> map = new LinkedMultiValueMap<String, Object>();
map.set("message", message);
return graphApi.publish(ownerId, "feed", map);
}
public void deletePost(String id) {
requireAuthorization();
graphApi.delete(id);
}
public PagedList<Post> searchPublicFeed(String query) {
return searchPublicFeed(query, FIRST_PAGE);
}
public PagedList<Post> searchPublicFeed(String query, PagingParameters pagedListParameters) {
String url = GraphApi.GRAPH_API_URL + "search?q={query}&type=post";
Map<String, Object> params = new HashMap<String, Object>();
params.put("query", query);
if (pagedListParameters.getLimit() != null) {
url += "&limit={limit}";
params.put("limit", pagedListParameters.getLimit());
}
if (pagedListParameters.getSince() != null) {
url += "&since={since}";
params.put("since", pagedListParameters.getSince());
}
if (pagedListParameters.getUntil() != null) {
url += "&until={until}";
params.put("until", pagedListParameters.getUntil());
}
JsonNode responseNode = restTemplate.getForObject(url, JsonNode.class, params);
return deserializeList(responseNode, null, Post.class);
}
public PagedList<Post> searchHomeFeed(String query) {
return searchHomeFeed(query, FIRST_PAGE);
}
public PagedList<Post> searchHomeFeed(String query, PagingParameters pagedListParameters) {
requireAuthorization();
URIBuilder uriBuilder = URIBuilder.fromUri(GraphApi.GRAPH_API_URL + "me/home").queryParam("q", query);
uriBuilder = appendPagedListParameters(pagedListParameters, uriBuilder);
URI uri = uriBuilder.build();
JsonNode responseNode = restTemplate.getForObject(uri, JsonNode.class);
return deserializeList(responseNode, null, Post.class);
}
public PagedList<Post> searchUserFeed(String query) {
return searchUserFeed("me", query, FIRST_PAGE);
}
public PagedList<Post> searchUserFeed(String query, PagingParameters pagedListParameters) {
return searchUserFeed("me", query, pagedListParameters);
}
public PagedList<Post> searchUserFeed(String userId, String query) {
return searchUserFeed(userId, query, FIRST_PAGE);
}
public PagedList<Post> searchUserFeed(String userId, String query, PagingParameters pagedListParameters) {
requireAuthorization();
URIBuilder uriBuilder = URIBuilder.fromUri(GraphApi.GRAPH_API_URL + userId + "/feed").queryParam("q", query);
uriBuilder = appendPagedListParameters(pagedListParameters, uriBuilder);
URI uri = uriBuilder.build();
JsonNode responseNode = restTemplate.getForObject(uri, JsonNode.class);
return deserializeList(responseNode, null, Post.class);
}
public PagedList<Post> getCheckins() {
return getCheckins(new PagingParameters(25, 0, null, null));
}
public PagedList<Post> getCheckins(PagingParameters pagedListParameters) {
requireAuthorization();
MultiValueMap<String, String> params = getPagingParameters(pagedListParameters);
params.set("with", "location");
return graphApi.fetchConnections("me", "posts", Post.class, params);
}
public Post getCheckin(String checkinId) {
requireAuthorization();
return graphApi.fetchObject(checkinId, Post.class);
}
// private helpers
private JsonNode fetchConnectionList(String baseUri, PagingParameters pagedListParameters) {
URIBuilder uriBuilder = URIBuilder.fromUri(baseUri);
uriBuilder = appendPagedListParameters(pagedListParameters, uriBuilder);
URI uri = uriBuilder.build();
JsonNode responseNode = restTemplate.getForObject(uri, JsonNode.class);
return responseNode;
}
private <T> PagedList<T> deserializeList(JsonNode jsonNode, String postType, Class<T> type) {
JsonNode dataNode = jsonNode.get("data");
List<T> posts = new ArrayList<T>();
for (Iterator<JsonNode> iterator = dataNode.iterator(); iterator.hasNext();) {
posts.add(deserializePost(postType, type, (ObjectNode) iterator.next()));
}
if (jsonNode.has("paging")) {
JsonNode pagingNode = jsonNode.get("paging");
PagingParameters previousPage = getPagedListParameters(pagingNode, "previous");
PagingParameters nextPage = getPagedListParameters(pagingNode, "next");
return new PagedList<T>(posts, previousPage, nextPage);
}
return new PagedList<T>(posts, null, null);
}
private <T> T deserializePost(String postType, Class<T> type, ObjectNode node) {
try {
if (postType == null) {
postType = determinePostType(node);
}
// Must have separate postType field for polymorphic deserialization. If we key off of the "type" field, then it will
// be null when trying to deserialize the type property.
node.put("postType", postType); // used for polymorphic deserialization
node.put("type", postType); // used to set Post's type property
return objectMapper.reader(type).readValue(node.toString()); // TODO: EXTREMELY HACKY--TEMPORARY UNTIL I FIGURE OUT HOW JACKSON 2 DOES THIS
} catch (IOException shouldntHappen) {
throw new UncategorizedApiException("facebook", "Error deserializing " + postType + " post", shouldntHappen);
}
}
private String determinePostType(ObjectNode node) {
if (node.has("type")) {
try {
String type = node.get("type").textValue();
PostType.valueOf(type.toUpperCase());
return type;
} catch (IllegalArgumentException e) {
return "post";
}
}
return "post";
}
private URIBuilder appendPagedListParameters(PagingParameters pagedListParameters,
URIBuilder uriBuilder) {
if (pagedListParameters.getLimit() != null) {
uriBuilder = uriBuilder.queryParam("limit", String.valueOf(pagedListParameters.getLimit()));
}
if (pagedListParameters.getSince() != null) {
uriBuilder = uriBuilder.queryParam("since", String.valueOf(pagedListParameters.getSince()));
}
if (pagedListParameters.getUntil() != null) {
uriBuilder = uriBuilder.queryParam("until", String.valueOf(pagedListParameters.getUntil()));
}
return uriBuilder;
}
}