RestFB is a simple and flexible
Facebook Graph API and
Old REST API client written in Java.
It is open source software released under the terms of the MIT License.
Download
Version 1.6.12 (released March 10, 2013) of the library is available via RestFB's home on Github.
View the changelog,
or download it here.
The project zip contains sample code in the source/example
directory which can help you get up and running quickly.
If you're using Maven, it's easy to integrate RestFB with your project:
<dependency> <groupId>com.restfb</groupId> <artifactId>restfb</artifactId> <version>1.6.12</version> </dependency>
Motivations
Design goals:
- Minimal public API
- Maximal extensibility
- Robustness in the face of frequent Facebook API changes
- Simple metadata-driven configuration
- Zero dependencies
Non-goals:
- Support for non-Graph/non-REST API parts of the Facebook Platform
- Providing a mechanism for obtaining session keys or OAuth access tokens
- Using XML as a data transfer format in addition to JSON
- Formally-typed versions of all Facebook API methods, error codes, etc.
If RestFB doesn't meet your needs, check out sister project BatchFB. It's a good lower-level client, especially if you're concerned with batching and performance.
How To Use It
You can run some sample code, 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
or visit the source/examples
directory in the RestFB distribution.
The below documentation is for the Facebook Graph API component of RestFB.
Initialization
// DefaultFacebookClient is the FacebookClient 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. FacebookClient facebookClient = new DefaultFacebookClient(MY_ACCESS_TOKEN); // It's also possible to create a client that can only access // publicly-visible data - no access token required. // Note that many of the examples below will not work unless you supply an access token! FacebookClient publicOnlyFacebookClient = new DefaultFacebookClient();
Not sure how to get an OAuth access token?
Fetching Single Objects
// For all API calls, you need to tell RestFB how to turn the JSON // returned by Facebook into Java objects. In this case, the data // we get back should be mapped to the User and Page types, respectively. // You can write your own types too! User user = facebookClient.fetchObject("me", User.class); Page page = facebookClient.fetchObject("cocacola", Page.class); out.println("User name: " + user.getName()); out.println("Page likes: " + page.getLikes());
Fetching Multiple Objects in One Call (see Graph API documentation)
FetchObjectsResults fetchObjectsResults = facebookClient.fetchObjects(Arrays.asList("me", "cocacola"), FetchObjectsResults.class); out.println("User name: " + fetchObjectsResults.me.getName()); out.println("Page likes: " + fetchObjectsResults.page.getLikes()); ... // Holds results from a "fetchObjects" call. // You need to write this class yourself! public class FetchObjectsResults { @Facebook User me; // If the Facebook property name doesn't match // the Java field name, specify the Facebook field name in the annotation. @Facebook("cocacola") Page page; }
Before we go further, a few words about logging...
Thanks to Chris Pruett for the heads-up about that.
Fetching Connections
Connection<User> myFriends = facebookClient.fetchConnection("me/friends", User.class); Connection<Post> myFeed = facebookClient.fetchConnection("me/feed", Post.class); out.println("Count of my friends: " + myFriends.getData().size()); out.println("First item in my feed: " + myFeed.getData().get(0)); // Connections support paging and are iterable for (List<Post> myFeedConnectionPage : myFeed) for (Post post : myFeedConnectionPage) out.println("Post: " + post);
Searching (see Graph API documentation)
// Searching is just a special case of fetching Connections - // all you have to do is pass along a few extra parameters. Connection<Post> publicSearch = facebookClient.fetchConnection("search", Post.class, Parameter.with("q", "watermelon"), Parameter.with("type", "post")); Connection<User> targetedSearch = facebookClient.fetchConnection("me/home", User.class, Parameter.with("q", "Mark"), Parameter.with("type", "user")); out.println("Public search: " + publicSearch.getData().get(0).getMessage()); out.println("Posts on my wall by friends named Mark: " + targetedSearch.getData().size());
Fetching Insights
// Fetching Insights data is as simple as fetching a Connection Connection<Insight> insights = facebookClient.fetchConnection("PAGE_ID/insights", Insight.class); for (Insight insight : insights.getData()) out.println(insight.getName());
Executing FQL Queries (see FQL documentation)
String query = "SELECT uid, name FROM user WHERE uid=220439 or uid=7901103"; List<FqlUser> users = facebookClient.executeFqlQuery(query, FqlUser.class); out.println("Users: " + users); ... // Holds results from an "executeFqlQuery" call. // You need to write this class yourself! // Be aware that FQL fields don't always map to Graph API Object fields. public class FqlUser { @Facebook String uid; @Facebook String name; @Override public String toString() { return String.format("%s (%s)", name, uid); } }
Executing Multiple FQL Queries in One Call (see FQL documentation)
Map<String, String> queries = new HashMap<String, String>() { { put("users", "SELECT uid, name FROM user WHERE uid=220439 OR uid=7901103"); put("likers", "SELECT user_id FROM like WHERE object_id=122788341354") } }; MultiqueryResults multiqueryResults = facebookClient.executeFqlMultiquery(queries, MultiqueryResults.class); out.println("Users: " + multiqueryResults.users); out.println("People who liked: " + multiqueryResults.likers); ... // Holds results from an "executeFqlMultiquery" call. // You need to write these classes yourself (along with the FqlUser class above)! public class FqlLiker { @Facebook("user_id") String userId; @Override public String toString() { return userId; } } public class MultiqueryResults { @Facebook List<FqlUser> users; @Facebook List<FqlLiker> likers; }
Metadata/Introspection (see Graph API documentation)
// You can specify metadata=1 for many calls, not just this one. // See the Facebook Graph API documentation for more details. User userWithMetadata = facebookClient.fetchObject("me", User.class, Parameter.with("metadata", 1)); out.println("User metadata: has albums? " + userWithMetadata.getMetadata().getConnections().hasAlbums());
Passing Parameters (see Graph API documentation)
// You can pass along any parameters you'd like to the Facebook endpoint. Date oneWeekAgo = new Date(currentTimeMillis() - 1000L * 60L * 60L * 24L * 7L); Connection<Post> filteredFeed = facebookClient.fetchConnection("me/feed", Post.class, Parameter.with("limit", 3), Parameter.with("until", "yesterday"), Parameter.with("since", oneWeekAgo)); out.println("Filtered feed count: " + filteredFeed.getData().size());
Selecting Specific Fields (see Graph API documentation)
User user = facebookClient.fetchObject("me", User.class, Parameter.with("fields", "id, name")); out.println("User name: " + user.getName());
Getting Any Kind of Data as a JSON Object
// Sometimes you can't know field names at compile time // so the @Facebook annotation can't be used. // Or maybe you'd like full control over the data that gets returned. // Either way, RestFB has you covered. Just map any API call to JsonObject. // Here's how to fetch a single object JsonObject btaylor = facebookClient.fetchObject("btaylor", JsonObject.class); out.println(btaylor.getString("name")); // Here's how to fetch a connection JsonObject photosConnection = facebookClient.fetchObject("me/photos", JsonObject.class); String firstPhotoUrl = photosConnection.getJsonArray("data").getJsonObject(0).getString("source"); out.println(firstPhotoUrl); // Here's how to handle an FQL query String query = "SELECT uid, name FROM user WHERE uid=220439 or uid=7901103"; List<JsonObject> queryResults = facebookClient.executeFqlQuery(query, JsonObject.class); out.println(queryResults.get(0).getString("name")); // Sometimes it's helpful to use JsonMapper directly if you're working with JsonObjects. List<String> ids = new ArrayList<String>(); ids.add("btaylor"); ids.add("http://www.imdb.com/title/tt0117500/"); // First, make the API call... JsonObject results = facebookClient.fetchObjects(ids, JsonObject.class); // ...then pull out raw JSON data and map each type "by hand". // Normally your FacebookClient uses a JsonMapper internally, but // there's nothing stopping you from using it too! JsonMapper jsonMapper = new DefaultJsonMapper(); User user = jsonMapper.toJavaObject(results.getString("btaylor"), User.class); Url url = jsonMapper.toJavaObject(results.getString("http://restfb.com"), Url.class); out.println("User is " + user); out.println("URL is " + url);
A note about the Publish and Delete examples below
publish_stream,create_event
.
If you're in a hurry, though, here's a quick example:
- Create a Facebook Application
- Request
https://graph.facebook.com/oauth/authorize?client_id=MY_API_KEY& redirect_uri=http://www.facebook.com/connect/login_success.html& scope=publish_stream,create_event
- Facebook will redirect you to
http://www.facebook.com/connect/login_success.html? code=MY_VERIFICATION_CODE
- Request
https://graph.facebook.com/oauth/access_token?client_id=MY_API_KEY& redirect_uri=http://www.facebook.com/connect/login_success.html& client_secret=MY_APP_SECRET&code=MY_VERIFICATION_CODE
- Facebook will respond with
access_token=MY_ACCESS_TOKEN
Publishing a Message and Event (see Graph API documentation)
// Publishing a simple message. // FacebookType represents any Facebook Graph Object that has an ID property. FacebookType publishMessageResponse = facebookClient.publish("me/feed", FacebookType.class, Parameter.with("message", "RestFB test")); out.println("Published message ID: " + publishMessageResponse.getId()); // Publishing an event Date tomorrow = new Date(currentTimeMillis() + 1000L * 60L * 60L * 24L); Date twoDaysFromNow = new Date(currentTimeMillis() + 1000L * 60L * 60L * 48L); FacebookType publishEventResponse = facebookClient.publish("me/events", FacebookType.class, Parameter.with("name", "Party"), Parameter.with("start_time", tomorrow), Parameter.with("end_time", twoDaysFromNow)); out.println("Published event ID: " + publishEventResponse.getId());
Publishing a Photo (see Graph API documentation)
// Publishing an image to a photo album is easy! // Just specify the image you'd like to upload and RestFB will handle it from there. FacebookType publishPhotoResponse = facebookClient.publish("me/photos", FacebookType.class, BinaryAttachment.with("cat.png", getClass().getResourceAsStream("/cat.png")), Parameter.with("message", "Test cat")); out.println("Published photo ID: " + publishPhotoResponse.getId()); // Publishing a video works the same way. facebookClient.publish("me/videos", FacebookType.class, BinaryAttachment.with("cat.mov", getClass().getResourceAsStream("/cat.mov")), Parameter.with("message", "Test cat"));
Publishing a Checkin (see Graph API documentation)
Map<String, String> coordinates = new HashMap<String, String>(); coordinates.put("latitude", "37.06"); coordinates.put("longitude", "-95.67"); FacebookType publishCheckinResponse = facebookClient.publish("me/checkins", FacebookType.class, Parameter.with("message", "I'm here!"), Parameter.with("coordinates", coordinates), Parameter.with("place", 1234))); out.println("Published checkin ID: " + publishCheckinResponse.getId());
Deleting (see Graph API documentation)
Boolean deleted = facebookClient.deleteObject("some object ID"); out.println("Deleted object? " + deleted);
Using the Batch Request API (see Graph API documentation)
// The Batch API is great if you have multiple operations you'd like to // perform in one server trip. Let's build a batch with three GET requests and // one POST request here: BatchRequest meRequest = new BatchRequestBuilder("me").build(); BatchRequest badRequest = new BatchRequestBuilder("this-is-a-bad-request/xxx").build(); BatchRequest m83musicRequest = new BatchRequestBuilder("m83music/feed") .parameters(Parameter.with("limit", 5)).build(); BatchRequest postRequest = new BatchRequestBuilder("me/feed") .method("POST") .body(Parameter.with("message", "Testing!")).build(); // ...and execute the batch. List<BatchResponse> batchResponses = facebookClient.executeBatch(meRequest, badRequest, m83musicRequest, postRequest); // Responses are ordered to match up with their corresponding requests. BatchResponse meResponse = batchResponses.get(0); BatchResponse badResponse = batchResponses.get(1); BatchResponse m83musicResponse = batchResponses.get(2); BatchResponse postResponse = batchResponses.get(3); // Since batches can have heterogenous response types, it's up to you // to parse the JSON into Java objects yourself. Luckily RestFB has some built-in // support to help you with this. JsonMapper jsonMapper = new DefaultJsonMapper(); // Here we marshal to the built-in User type. User me = jsonMapper.toJavaObject(meResponse.getBody(), User.class); out.println(me); // To detect errors, check the HTTP response code. if(badResponse.getCode() != 200) out.println("Batch request failed: " + badResponse); // You can pull out connection data... out.println("M83's feed follows"); Connection<Post> m83musicPosts = new Connection<Post>(facebookClient, m83musicResponse.getBody(), Post.class); for (List<Post> m83musicPostsConnectionPage : m83musicPosts) for (Post post : m83musicPostsConnectionPage) out.println(post); // ...or do whatever you'd like with the raw JSON. out.println(postResponse.getBody());
Including Binary Attachments Using the Batch Request API (see Graph API documentation)
// Per the FB Batch API documentation, attached_files is a comma-separated list // of attachment names to include in the API call. // RestFB will use the filename provided to your BinaryAttachment minus the file // extension as the name of the attachment. // For example, "cat-pic.png" must be referenced here as "cat-pic". List<BatchRequest> batchRequests = Arrays.asList( new BatchRequestBuilder("me/photos").attachedFiles("cat-pic").build(), new BatchRequestBuilder("me/videos") .attachedFiles("cat-vid, cat-vid-2") .body(Parameter.with("message", "This cat is hilarious")) .build()); // Define the list of attachments to include in the batch. List<BinaryAttachment> binaryAttachments = Arrays.asList( BinaryAttachment.with("cat-pic.png", getClass().getResourceAsStream("/cat-pic.png")), BinaryAttachment.with("cat-vid.mov", getClass().getResourceAsStream("/cat-vid.mov")), BinaryAttachment.with("cat-vid-2.mov", getClass().getResourceAsStream("/cat-vid-2.mov"))); // Finally, execute the batch. facebookClient.executeBatch(batchRequests, binaryAttachments);
Extending an Access Token (see Graph API documentation)
// Tells Facebook to extend the lifetime of MY_ACCESS_TOKEN. // Facebook may return the same token or a new one. AccessToken accessToken = new DefaultFacebookClient().obtainExtendedAccessToken(MY_APP_ID, MY_APP_SECRET, MY_ACCESS_TOKEN); out.println("My extended access token: " + accessToken);
Getting an Application Access Token (see Graph API documentation)
// Obtains an access token which can be used to perform Graph API operations // on behalf of an application instead of a user. AccessToken accessToken = new DefaultFacebookClient().obtainAppAccessToken(MY_APP_ID, MY_APP_SECRET); out.println("My application access token: " + accessToken);
Converting Old REST API Session Keys to OAuth Tokens (see Graph API documentation)
// It's 2012, hopefully no one has to do this anymore! List<AccessToken> accessTokens = new DefaultFacebookClient().convertSessionKeysToAccessTokens(MY_APP_ID, MY_APP_SECRET, "sessionKey1", "sessionKey2"); out.println("OAuth token for sessionKey1: " + accessTokens.get(0).getAccessToken());
Running The Examples
RestFB comes with a few example Java source files that you can run and tweak yourself. Don't forget to put your access token inside the quotes!
$ cd source/example $ ant run-reader-examples "-Daccess_token=MY_ACCESS_TOKEN" $ ant run-publisher-examples "-Daccess_token=MY_ACCESS_TOKEN" $ ant run-legacy-examples "-Daccess_token=MY_ACCESS_TOKEN"
Facebook Graph Object Types Supported Out-of-the-Box
RestFB can map JSON to any class that has fields annotated with the @Facebook
annotation, which is discussed in more detail in the
JSON Mapping Rules section.
However, for your convenience, all basic Graph API Object types have
their own Java implementation in the com.restfb.types
package.
You may use these builtin types if you wish, or you can subclass them to add additional fields or functionality, or just write your
own if you have special requirements. The builtins come with reflective implementations of toString()
, hashCode()
,
and equals(Object o)
to make your life simpler. If you subclass any of the builtins and add additional fields with accessors,
those fields will be automatically taken into account by the above 3 methods.
The builtin types are as follows:
- Album
- Application
- AppRequest
- Checkin
- Comment
- Conversation
- Event
- FriendList
- Group
- Insight
- Link
- Note
- Page
- Photo
- Post
- Question
- QuestionOption
- Status Message
- User
- Venue
- Video
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:
- The convenience classes provided by RestFB in
com.restfb.types
String
Integer
Boolean
Long
Double
Float
BigInteger
BigDecimal
-
Your own JavaBean-compliant classes
Don't forget to provide a public default constructor! -
List
s of any of the above types
For example:
public class MyClass { @Facebook String name; @Facebook BigDecimal value; // If a Facebook field doesn't match your field's name, specify it explicitly @Facebook("lots_of_numbers") List<Integer> lotsOfNumbers; // You can annotate methods with @JsonMappingCompleted to perform // post-mapping operations. // // This is useful if you want to massage the data FB returns. @JsonMappingCompleted void allDone(JsonMapper jsonMapper) { if(lotsOfNumbers.size() == 0) throw new IllegalStateException("I was expecting more numbers!"); } }
Java to JSON mapping is supported by default via JsonMapper.toJson(Object object)
.
You may recursively convert primitive wrapper types as specified above, List
s, Map
s with String
keys, and your own Javabean types
by applying the @Facebook
annotation to any fields you'd like to include in the conversion.
The default behavior of DefaultJsonMapper
is to throw a FacebookJsonMappingException
if it cannot map JSON to Java correctly.
However, given the frequency with which the Facebook API changes, you might want to guard yourself from "surprise" errors in production by
exerting more fine-grained control over how the mapper handles mapping exceptions. You can do so like this:
FacebookClient facebookClient = new DefaultFacebookClient("MY_ACCESS_TOKEN", new DefaultWebRequestor(), new DefaultJsonMapper(new JsonMappingErrorHandler() { public boolean handleMappingError(String unmappableJson, Class<?> targetType, Exception e) { // Here you might log the fact that JSON mapping failed. err.println(format("Uh oh, mapping %s to %s failed...", unmappableJson, targetType)); // Returning true tells the mapper to map this JSON as null and keep going. // Returning false tells the mapper to throw an exception. return true; } }));
A note about Java to JSON mapping with the Graph API
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 FacebookClient
methods may throw FacebookException
,
which is an unchecked exception as of RestFB 1.6.
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@Facebook
annotation 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 JavaBigDecimal
or 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@Facebook
annotations. 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 an unexpected HTTP 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. -
FacebookGraphException
Thrown when the Graph API returns an error, as shown in the example JSON snippet below. Exposes thetype
andmessage
so you can handle them in your code and take custom action as needed.{ "error": { "type": "SomeBizarreTypeOfException", "message": "Everything went wrong." } }
Note thatFacebookGraphException
is a catchall Graph API exception. For your convenience, RestFB will throw more-specific subclassesFacebookOAuthException
andFacebookQueryParseException
if it detects either of these Graph API error types. These are described below. -
FacebookOAuthException
Thrown when the Graph API returns an OAuth-related error (typeOAuthException
orOAuthAccessTokenException
), as shown in the example JSON snippet below.{ "error": { "type": "OAuthException", "message": "Invalid access token signature." } }
-
FacebookQueryParseException
Thrown when the Graph API returns an FQL query parsing error (typeQueryParseException
), as shown in the example JSON snippet below.{ "error": { "type": "QueryParseException", "message": "Unknown path components: /fizzle" } }
-
FacebookResponseStatusException
This is thrown by RestFB when an FQL call fails.FacebookGraphException
and its subclasses are not applicable in that case because Facebook returns this "legacy" exception instead due to FQL not yet being a full-fledged member of the Graph API.FacebookResponseStatusException
will 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. -
FacebookResponseContentException
This is thrown by RestFB when Facebook responds with unexpected data. For example, when extending an access token, Facebook should return an HTTP response body of the formaccess_token=123&expires=456
. But if Facebook does not respond as expected - perhaps they modify the response format in some future API release -FacebookResponseContentException
will be thrown. It is unlikely that you will ever see this exception.
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.
String query = "SELECT name FROM user WHERE uid=220439 or uid=7901103"; try { List<User> users = facebookClient.executeFqlQuery(query, User.class); } catch (FacebookJsonMappingException e) { // Looks like this API method didn't really return a list of users } catch (FacebookNetworkException e) { // An error occurred at the network level out.println("API returned HTTP status code " + e.getHttpStatusCode()); } catch (FacebookOAuthException e) { // Authentication failed - bad access token? } catch (FacebookGraphException e) { // The Graph API returned a specific error out.println("Call failed. API says: " + e.getErrorMessage()); } catch (FacebookResponseStatusException e) { // Old-style Facebook error response. // The Graph API only throws these when FQL calls fail. // You'll see this exception more if you use the Old REST API // via LegacyFacebookClient. if (e.getErrorCode() == 200) out.println("Permission denied!"); } catch (FacebookException e) { // This is the catchall handler for any kind of Facebook exception }
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.
Extensibility and Unit Testing
In addition to FacebookClient
, 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:
FacebookClient facebookClient = new DefaultFacebookClient(MY_ACCESS_TOKEN, // A one-off DefaultWebRequestor for testing that returns a hardcoded JSON // object instead of hitting the Facebook API endpoint URL new DefaultWebRequestor() { @Override public Response executeGet(String url) throws IOException { return new Response(HttpURLConnection.HTTP_OK, "{'id':'123456','name':'Test Person'}"); } }, new DefaultJsonMapper()); // Make an API request using the mocked WebRequestor User user = facebookClient.fetchObject("ignored", User.class); // Make sure we got what we were expecting assert "123456".equals(user.getId()); assert "Test Person".equals(user.getName());
Links
Visit RestFB's home on Github.
Have a question not covered here? Ask in the RestFB Google Group.
Found a bug or have an enhancement request? Create a ticket in the issue tracker.
Download a read-only copy of the current source code using Git:
git clone git://github.com/revetkn/restfb.git
Some RestFB Users
If you're a happy RestFB user and would like to add a link to your site, please send me an email.
About
RestFB is written by Mark Allen and sponsored by Transmogrify, LLC.
Transmogrify is a Philadelphia-area software shop specializing in product design and web/mobile software development. Our experience runs the gamut from embedded systems to Oracle DBA work to the cutting-edge web framework and iOS/Android technologies. Give us a call at (215) 436-XMOG or send an email to product@xmog.com if you need a hand with anything software-related. We can handle every part of the software lifecycle, from initial requirements and design to ongoing maintenance and support.
Development of RestFB was aided by
- SomaFM
- Dead Cities, Red Seas, & Lost Ghosts (M83)
- Orbital 2 (Orbital)
- The Crew (7 Seconds)
- Treatment 5 (Osker)
- Kid Dynamite (Kid Dynamite)
Website icons provided by Mark James.
Licensing
RestFB is open source software released under the terms of the MIT License:
Copyright (c) 2010-2013 Mark Allen. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.