Using RestFB with Facebook's Old REST API
As always, you can jump straight to the RestFB Javadoc, or learn by example below.
If you're looking for help or additional examples, please check out the RestFB Google Group.
It's helpful to see exactly what's going over the wire to verify that RestFB is taking your input and sending it
to Facebook exactly as you expect. Feel free to use this example log4j.xml file in your own project
to see full POST request and response data logged to standard output.
The below documentation is for the Old REST API component of RestFB.
Initialization
// DefaultLegacyFacebookClient is the LegacyFacebookClient implementation // that ships with RestFB. You can customize it by passing in // custom JsonMapper and WebRequestor implementations, or simply // write your own FacebookClient instead for maximum control // Use this constructor if you'd like to go with legacy authentication (not recommended) LegacyFacebookClient facebookClient = new DefaultLegacyFacebookClient(MY_API_KEY, MY_SECRET_KEY); // Use this constructor if you'd like to go with the new OAuth2 token authentication (preferred) LegacyFacebookClient facebookClient = new DefaultLegacyFacebookClient(MY_ACCESS_TOKEN);
Not sure how to get an OAuth2 access token?
access_token parameter in your URL bar. That's it! Just copy and paste it into your code.
http://graph.facebook.com/btaylor?access_token=XXXXXXX
Making a Simple Call
We'll get the UID associated with the current Facebook API session key by calling the API method users.getLoggedInUser.
// Last parameter specifies that this API call's result // should be returned to us as a Long value Long uid = facebookClient.execute("users.getLoggedInUser", Long.class);
Making a More Complex Call
Here we make the API call fql.query to execute a custom query.
// FQL query which asks Facebook for your friends' names, // profile picture URLs, and network affiliations String query = "SELECT name, pic_big, affiliations FROM user " + "WHERE uid IN (SELECT uid2 FROM friend WHERE uid1=12345)"; // Executes an API call with result mapped to a list of classes we've defined. // Note that you can pass in an arbitrary number of Parameters - here we // send along the query as well as the "give me HTTPS URLs" flag List<User> users = facebookClient.executeForList("fql.query", User.class, Parameter.with("query", query), Parameter.with("return_ssl_resources", "true"));
1. What Happened Under the Covers
The Facebook API returned JSON in response to our query. You can click here to see it.
2. Turning JSON into Java
Since RestFB doesn't know in advance what kinds of data you're requesting from the API,
you need to define your own JavaBean classes that map to API responses. Luckily, this is a simple
process - just annotate fields with @Facebook and RestFB will automatically convert
Facebook's JSON to real Java objects.
public class User {
// By default, assumes JSON attribute name is the same as the Java field name
@Facebook
String name;
// If your Java field name is different than the JSON attribute name,
// just specify the JSON attribute name
@Facebook("pic_big")
String pictureUrl;
// Java doesn't remember generic type information at runtime due to type erasure.
// So, for a List type mapping, you have to help out by specifying
// what kind of type is contained in the List (Affiliation, in this case)
@Facebook(contains = Affiliation.class)
List<Affiliation> affiliations;
public String toString() {
return String.format("Name: %s\nProfile Image URL: %s\nAffiliations: %s",
name, pictureUrl, affiliations);
}
}
public class Affiliation {
@Facebook
String name;
@Facebook
String type;
public String toString() {
return String.format("%s (%s)", name, type);
}
}
3. Let's See the Results
for (User user : users) System.out.println(user + "\n");
And here they are on stdout:
Name: Heather Merlino Profile Image URL: https://secure-profile.facebook.com/profile6/138/106/n28455_7662.jpg Affiliations: [Intuit (work)] Name: Steve Nolan Profile Image URL: https://secure-profile.facebook.com/v228/344/541/n4057387_1795.jpg Affiliations: [Cornell (college), Bain & Company (work)] Name: Third Person Profile Image URL: https://secure-profile.facebook.com/v2328/314/454/n137387_5491.jpg Affiliations: []
Making an fql.multiquery Call (Since 1.1)
Given the unique nature of the fql.multiquery API call, RestFB provides the LegacyFacebookClient.executeMultiquery(Class, MultiqueryParameter, Parameter...) family of methods to easily perform multiquery operations with a minimum of configuration.
To use the multiquery support, simply create a map of named queries and create a class to hold the results. Here's an example:
LegacyFacebookClient facebookClient = new DefaultLegacyFacebookClient(MY_ACCESS_TOKEN);
String friendsQuery =
"SELECT name, pic_big FROM user " +
"WHERE uid IN (SELECT uid2 FROM friend WHERE uid1=12345)";
String groupsQuery =
"SELECT gid, name FROM group WHERE gid IN " +
"(SELECT gid FROM group_member WHERE uid=12345)";
// The key names specified here map to field names in the result object
Map<String, String> queries = new HashMap<String, String>();
queries.put("friends", friendsQuery);
queries.put("groups", groupsQuery);
// You can send along additional parameters if you'd like, too
Results results =
facebookClient.executeMultiquery(Results.class, MultiqueryParameter.with(queries));
Here's the result class and its supporting classes. By default, results are mapped to @Facebook-annotated fields by the
logical query name specified in the map. You may override this behavior by explicitly specifying the value as shown below on the users field.
class Results {
@Facebook(value = "friends", contains = User.class)
List<User> users;
@Facebook(contains = Group.class)
List<Group> groups;
}
// The multiquery support uses the same recursive mapping as the other parts of RestFB.
// It's not shown in this example, but you can create arbitrarily complex object graphs
// of @Facebook-annotated fields to handle complex query results.
class Group {
@Facebook(value = "gid")
Long id;
@Facebook
String name;
}
class User {
@Facebook
String name;
@Facebook("pic_big")
String pictureUrl;
}
Making a stream.publish Call with Complex Attachments (Since 1.4)
The stream.publish API call can accept
attachment and privacy (JSON objects) as well as action_links (JSON array) as parameters.
The 1.4 release of RestFB supports mapping of Java types to JSON to simplify this use case - you no longer have to
manually create a JSON string, as the framework will take care of that for you for Lists, Maps,
and your own Javabean types that have fields annotated with the @Facebook annotation.
LegacyFacebookClient facebookClient = new DefaultLegacyFacebookClient(MY_ACCESS_TOKEN);
ActionLink category = new ActionLink();
category.href = "http://bit.ly/KYbaN";
category.text = "humor";
Properties properties = new Properties();
properties.category = category;
properties.ratings = "5 stars";
Medium medium = new Medium();
medium.href = "http://bit.ly/187gO1";
medium.src = "http://bit.ly/GaTlC";
medium.type = "image";
Attachment attachment = new Attachment();
attachment.name = "i'm bursting with joy";
attachment.href = "http://bit.ly/187gO1";
attachment.caption = "{*actor*} rated the lolcat 5 stars";
attachment.description = "a funny looking cat";
attachment.properties = properties;
attachment.media = Collections.singletonList(medium);
// Send the request to Facebook.
// We specify the session key to use to make the call, the fact that we're
// expecting a String response, and the attachment (defined above).
String postId = facebookClient.execute("stream.publish", String.class,
Parameter.with("attachment", attachment));
Here are the supporting classes. You may download the full example and run it locally.
Note that you don't have to write your own classes - you can just as easily populate Maps instead
for a less strongly-typed solution.
class ActionLink {
@Facebook
String text;
@Facebook
String href;
}
class Medium {
@Facebook
String type;
@Facebook
String src;
@Facebook
String href;
}
class Properties {
@Facebook
ActionLink category;
@Facebook
String ratings;
}
class Attachment {
@Facebook
String name;
@Facebook
String href;
@Facebook
String caption;
@Facebook
String description;
@Facebook
Properties properties;
@Facebook
List<Medium> media;
}
One other tip: when converting from Java to JSON like we are here,
you don't need to use the contains attribute of the @Facebook annotation when sending a List type.
This is because we have enough type information at runtime to perform the conversion process. When converting from JSON to Java, we need assistance because
of type erasure and therefore must specify a value for the contains attribute.
For example:
@Facebook(contains = Medium) List<Medium> media;
...is unnecessary in this situation. You may write it as:
@Facebook List<Medium> media;
Running The Examples
RestFB comes with a few example Java source files that you can run and tweak yourself. Don't forget to put-Daccess_token=MY_ACCESS_TOKEN in quotes!
$ cd source/examples $ ant run-legacy-examples "-Daccess_token=MY_ACCESS_TOKEN"
JSON Mapping Rules
Using DefaultJsonMapper,
RestFB is able to recursively map JSON fields annotated with @Facebook to the following Java types out of the box:
StringIntegerBooleanLongDoubleFloatBigIntegerBigDecimal-
Your own JavaBean-compliant classes
Don't forget to provide a public default constructor! -
Lists of any of the above types
Note that, because of type erasure, you must specify what type of object is stored in theListvia the@Facebookannotation'scontainsattribute or aFacebookJsonMappingExceptionwill be thrown at runtime. This attribute is ignored if applied to non-Listtypes.
For example:
public class MyClass {
@Facebook
String name;
@Facebook
BigDecimal value;
@Facebook(contains = Integer.class)
List<Integer> numbers;
}
As of RestFB 1.4, Java to JSON mapping is supported by default JsonMapper.toJson(Object object).
You may recursively convert primitive wrapper types as specified above, Lists, Maps with String keys, and your own Javabean types
by applying the @Facebook annotation to any fields you'd like to include in the conversion.
When converting from Java to JSON, you never need to use the contains attribute of the @Facebook annotation - it's always ignored
because enough information is available at runtime to perform the conversion correctly.
As-is, DefaultJsonMapper should meet the needs of the vast majority of users.
If it doesn't support a feature you need, you can easily subclass it or write your own implementation of JsonMapper instead.
Error Handling
All LegacyFacebookClient methods throw the checked, abstract FacebookException.
These are the FacebookException subclasses that you may catch:
-
FacebookJsonMappingException
Thrown when an error occurs when attempting to map Facebook API response JSON to a Java object. It usually indicates that you've used the@Facebookannotation on a field with an unsupported type or the Facebook API JSON doesn't map correctly to the fields you've annotated (e.g. attempting to map a JSON string to a JavaBigDecimalor a JSON object to a JavaList). You generally should not explicitly catch this exception in your code, as it usually signifies programmer error in setting up@Facebookannotations. One valid use for catching this exception, however, is to detect when Facebook changes what an API call returns on their end, which would break your live code. It may be useful to catch this exception and then send a notification to you or your ops team to notify them that your application needs to be updated. -
FacebookNetworkException
Thrown when a failure occurs at the network level. This can happen if your machine doesn't have a network connection or the Facebook API endpoint returns a non HTTPOK(200) status code. If there's an HTTP status code available, it's included in the exception so you may take custom actions depending on what type of error occurred. -
FacebookResponseStatusException
Thrown when a request is made to Facebook and an HTTPOK(200) response is received, but the returned JSON signifies an error condition. For example, below is the list of errors the Facebook API may return for the Stream.removeLike call:Code Description 100 Invalid parameter. 102 Session key invalid or no longer valid (if it's a desktop application and the session is missing). 200 Permissions error. The application does not have permission to perform this action. 210 User not visible. The user doesn't have permission to act on that object. FacebookResponseStatusExceptionwill include both the error code and error message returned by the Facebook API so you may take custom actions depending on the type of error that occurred.
Here's some example code to illustrate the above. Keep in mind that your code doesn't need to handle every single exception the way we're doing here - this is just to demonstrate what's possible.
try {
Integer result =
facebookClient.execute("Stream.removeLike", sessionKey,
Integer.class, Parameter.with("post_id", "12345"));
} catch (FacebookJsonMappingException e) {
// Looks like this API method didn't really return an Integer
} catch (FacebookNetworkException e) {
// Looks like an error occurred at the network level
System.out.println("API returned HTTP status code " + e.getHttpStatusCode());
} catch (FacebookResponseStatusException e) {
// Facebook API returned a specific error
if (e.getErrorCode() == 200)
System.out.println("Permission denied!");
} catch (FacebookException e) {
// This is the catchall handler for any kind of Facebook exception
}
Extensibility and Unit Testing
In addition to LegacyFacebookClient, RestFB provides default implementations
for WebRequestor and
JsonMapper, two components that
DefaultFacebookClient depends on to do its work.
These dependencies are designed to allow for straightforward subclassing (if you only want to replace a little bit of functionality) and simple custom implementations (if you require full control).
This comes in handy when unit testing - for example, you can write your own WebRequestor implementation
that simulates a Facebook API endpoint response. You can drop in custom data designed to exercise your application's
Facebook integration or simulate error conditions to make sure you're handling them properly.
Here's a trivial example which shows one way you might implement this:
LegacyFacebookClient facebookClient =
new DefaultLegacyFacebookClient(MY_ACCESS_TOKEN,
// A one-off DefaultWebRequestor for testing that returns a hardcoded JSON
// list of numbers instead of hitting the Facebook API endpoint URL
new DefaultWebRequestor() {
@Override
public Response executePost(String url, String parameters) throws IOException {
return new Response(HttpURLConnection.HTTP_OK, "[123,456,789]");
}
}, new DefaultJsonMapper());
// Make an API request using the mocked WebRequestor
List<Integer> numbers = facebookClient.executeForList("ignored", Integer.class);
// Make sure we got what we were expecting
assert numbers.size() == 3;
assert numbers.get(1) == 456;