Objectify-Appengine, Part 2

Similar to Hibernate, Objectify-Appengine makes it easier to map and leverage POJOs against the Google App Engine datastore, also known as Bigtable. In the first half of this article, I introduced Objectify and some of its key features, including JPA annotations (of which it uses only a subset) and custom annotations that integrate with the GAE datastore. I also explained Objectify’s approach to relationships and demonstrated its query interface, which supports the GAE notions of filtering and sorting.

Part 2 shifts the focus from domain modeling to finalizing a web application in preparation for deploying it on GAE. First, I want to spend just a few minutes more on the domain model, however, so that I can introduce Objectify’s features for indexing and caching. Both features are pretty important when deploying applications to GAE, especially if you plan to move massive amounts of data.

About this series

The Java development landscape has changed radically since Java technology first emerged. Thanks to mature open source frameworks and reliable for-rent deployment infrastructures, it’s now possible to assemble, test, run, and maintain Java applications quickly and inexpensively. In this series, Andrew Glover explores the spectrum of technologies and tools that make this new Java development paradigm possible.

Indexing and caching with Objectify-Appengine

By default, all properties defined in a domain object (and consequently, Google’s low-levelEntity API) are indexed. As is true in the relational world, indexing makes searching easier for the underlying datastore, and therefore faster for end users. But indexing isn’t cost-free: when new objects are created — such as a new row in relational terms or a new Entity in Bigtable — you have to update your indexes to include the new data.

Once you start to leverage GAE and store and retrieve real data from it, indexing becomes a real-world (as in real dollar) concern. When you sign up for a GAE account, you automatically get 200 indexes for free; beyond that, there is a price. Likewise, if you exceed 6.50 CPU hours per day, you’ll start to rack up the Google debt. So it makes sense to index selectively. If you don’t need a property indexed (that is, you don’t plan on searching a domain object by that individual property), then turning off that index probably makes sense. In Listing 1, I show you how to use Objectify’s @Unindexed annotation to turn off a particular index.

I’ve extended the Retweet object from Part 1, adding some indexing functionality. You can see the changes in the sample code packet. In Listing 1, I’ve used @Unindexed to unindex theRetweet‘s user’s real-name property, which is dubbed userName as opposed to screenName. I also unindexed the user’s picture URL, a new feature that lets me create a nicer report on the UI.

I don’t plan on searching for Retweets by these properties, so it makes sense to unindex them. Of course, I can remove the @Unindexed annotation if something changes. Listing 1 shows how I’ve enhanced the Retweet object’s properties.
Listing 1. Retweet reloaded

public class Retweet
 @Id private String id;
 private String screenName;
 private Date date;
 private String tweet;
 private Long influence;
 private Key<User> owner;
 private Long tweetId;
 @Unindexed private String userName;
 @Unindexed private String userPicture;

The two bolded properties at the end of the listing — userName and userPicture — are new, and both are unindexed. I’ve renamed the original userName to screenName for better integration with Twitter. (In Twitter, your user name is your given name — “Andrew Glover” — and your screen name is your handle — “aglover”.)

Storing data in memcache

Like indexing, caching improves the end user experience: it cuts out the roundtrip to the datastore and back, thus making reads faster. Google App Engine uses memcache for caching data, and Objectify uses the @Cached annotation to plug into it. Just add @Cached to your domain objects and — presto! — the data (not the corresponding Java™ object) is stored in memcache and can be retrieved from memory on application reads.

I’ve added the @Cached annotation to both of my domain objects — User and Retweet. Listing 2 reveals the updated User object and also shows off a couple more @Unindexed properties:
Listing 2. Caching is simple!

public class User {
 @Id private String name;	
 @Unindexed private String token;
 @Unindexed private String tokenSecret;

Note that Objectify’s query calls still hit the datastore; it’s all other datastore interaction calls, like get, that leverage memcache. For instance, in the User object in Listing 2, thefindByName method leverages Objectify’s get call to load the corresponding domain object. (Recall that the name is the key.) If the User had already been created then its data would be cached, with subsequent calls to findByName retrieved from the cache and not the datastore. GAE’s quota on memcache calls is higher than on datastore calls, so it makes sense to use memcache whenever possible.

Back to top

Deploying to GAE

I’m done updating my domain objects, at least in terms of what I can do with Objectify. The next step is to deploy the application to GAE. As I explained last month, my intention is to build a web application that allows users to mine their retweets, being able to see which ones were retweeted by their most influential followers.

At a high level, the web application, which is comprised of User and Retweet, will do a number of things: First, the user must authenticate him or herself with Twitter via OAuth. Next, the application will fire off a background process to obtain the user’s retweet data. Simultaneously, the application will display a dashboard and make an Ajax call to retrieve the retweet data, which should already be persisted to the GAE datastore (aka Bigtable).

I built the domain aspects of the application last month, so what’s left is to integrate it with Twitter’s authorization system and then display the corresponding retrieved data.

Back to top

User authorization with OAuth

As you know if you’ve read Part 1, Twitter uses OAuth for user authorization. The form of OAuth required depends on whether the application is to be deployed in a desktop, mobile, or web environment. Other than that, there are two things you really need to know about user authorization with OAuth:

  • Applications don’t need to store a login and password.
  • Authorization is delegated to a trusted provider, like Twitter.

Authorization is done with various tokens that are traded. Given that the Retweet app is being deployed to a web environment, the first step is to register it with Twitter. When I register, I provide a few pieces of information, including a callback URL that will be used as a key. I can change the URL at runtime. In return, I receive a consumer key and a consumer secret, which of course I need to keep to myself.

Next, I need the user’s permission to sign in to Twitter and grab his or her retweets. The user grants permission by sending some information to Twitter, after which Twitter will invoke my callback URL. In doing so, Twitter will also pass along some tokens (as URL parameters, which can be programmatically obtained).

Authorization workflow

I’m going to use two servlets to handle the authorization workflow. The first servlet will create a Session object, register a callback URL for Twitter, and shunt the user to Twitter for authentication. The authentication process will also authorize my application to act on the user’s behalf, as you’ll soon see. The second servlet will be my callback; it will process the response from Twitter and create a corresponding User object.

Gaelyk is a Groovy framework whose terse syntax and coding shortcuts I’ll leverage to quickly build my GAE web application. The first thing I need to do in Gaelyk is to define my application’s custom URLs, which I can do using URL routes. Gaelyk’s URL routes configuration is essentially a DSL that allows me to (1) build readable URLs and (2) hide the underlying technology. Listing 3 shows the first stage of this mapping:
Listing 3. The beginning of a routes file

get "/login",  forward: "/login.groovy"
get "/tcallback",  forward: "/tcallback.groovy"

In Listing 3, I’ve defined two custom URLs. Notice how /login hides the .groovy aspect, making the URL a lot more readable. The DSL describes a simple sequence: If an HTTP GET is applied to a URL like your_domain/login, then forward that request to the /login.groovyasset, which in Gaelyk’s case is a servlet (or groovlet, really).

My login groovlet is also absurdly simple, as you can see in Listing 4:
Listing 4. The login groovlet

import twitter4j.TwitterFactory

def twitter = new TwitterFactory().getOAuthAuthorizedInstance("...", "...")

def requestToken = twitter.getOAuthRequestToken("http://b-50-1.appspot.com/tcallback")

	session = request.getSession(true)

session.setAttribute("requestToken_token", requestToken.token)
session.setAttribute("requestToken_secret", requestToken.tokenSecret)

In Listing 4, I use my consumer key and secret to create a TwitterFactory instance.TwitterFactory is part of the Twitter4J library. I then obtain a RequestToken instance, in the process passing in my own callback URL (http://b-50-1.appspot.com/tcallback, defined inListing 3), which Twitter will invoke after a user grants access. Lastly, I place two pieces of information into an HttpSession object. The information will be required when data is transferred back to the web application. A browser is then redirected to an authorization URL on Twitter’s website.

The callback handler

My callback handler, defined in Listing 5, simply obtains the required information from Twitter and links that data up with what’s found in the HttpSession object. It then creates a newUser. Alternately, if the datastore already contains a User object, the groovlet simply updates it with new credential data.
Listing 5. A callback handler

import java.util.Date
import twitter4j.TwitterFactory
import com.b50.gretweet.User

def twitter = new TwitterFactory().getOAuthAuthorizedInstance("...", "...")

def accTok = twitter.getOAuthAccessToken(

def scrname = twitter.getScreenName()

session.setAttribute("screenname", scrname)

new User(scrname, accTok.getToken(), accTok.getTokenSecret()).save()

defaultQueue <<  [
  countdownMillis: 1, url: "/retweetpull",
  taskName: "twitter-pull-${new Date().time}",
  method: 'POST', params: [id: scrname]

redirect "/dashboard/${scrname}"

Upon being called, the groovlet obtains a key parameter from Twitter: oauth_verifier. This parameter, combined with a key and secret, serves as permission to act on the user’s behalf going forward. The groovlet also updates the session object with the user’s Twitter handle, after which the User object is either created or updated.

The code dealing with defaultQueue is Gaelyk magic for placing a request onto a GAE queue, which I’ll discuss more in the next section. In the last bit of action in Listing 5, the user’s browser is redirected to a new URL, which has the mapping get "/dashboard/@name", forward: "/dashboard.gtpl?tname=@name". The URL is connected to a web page that receives a parameter named tname, which is the user’s Twitter screen name.

Back to top

GAE I/O and performance

If you want to build your apps on GAE, there are a few rules you must obey. Google defines what libraries you can and can’t use and also places a time limit on servlet response. The upper-bound limit on the time it takes a servlet to handle an incoming request is about 30 seconds. If a servlet takes longer than that time to send out a response, GAE generates an error in the form of an HTTP 500 code.

Max Ross discusses GAE

Sources inside of Google say the 30-second request-response rule could go away someday. SeeResources to learn more about GAE performance and the Google datastore.

Google has good reason for imposing the time limit: it needs to be able to scale your application as needed, without your assistance. So if you find that the code you’ve written requires too much time to process — that is, it interacts with other systems that you don’t have control over (or is just plain poorly coded) — then you probably need to rethink your application.

As a GAE developer, you don’t have access to the underlying machines your application is running on; in fact, you won’t know their configuration or even how much memory is available. But that’s probably not where performance issues will surface anyway. I/O is the usual culprit of poor performance, and in GAE, I/O issues will usually pop up when you interact with other systems, like Twitter.

Making API calls to Twitter can be unreliable because (as every Twitter user knows) Twitter occasionally goes down. Response times also can crawl due to volume overload. So building an application that relies on Twitter’s data requires a bit of planning. In GAE, it also requires queuing.

The GAE response timer

Google App Engine provides a queuing mechanism that allows tasks to be processed asynchronously in the background. Interestingly, the task itself is a servlet and what you throw onto a queue is a URL, along with optional parameters. You can see all this if you look back to Listing 5. GAE queues allow you to take potentially long-running processes, break them up into succinct tasks (servlets), and throw those tasks onto queues to be executed in the background. Of course, if a task does something interesting, you can save that data into the datastore and retrieve it at some later point — which is what I’m going to do with my user’s Twitter retweets.

Once a user has authenticated with Twitter, I’ll create a URL to another servlet that obtains retweets via Twitter’s API and throw that URL onto a queue. I’m going to use GAE’s default queue (named “default”), although I could create a unique queue if I wanted to. Listing 6 shows the code for putting a request onto GAE’s default queue, which you first saw in Listing 5:
Listing 6. Putting a request onto a queue

defaultQueue << [
  countdownMillis: 1, url: "/retweetpull",
  taskName: "twitter-pull-${new Date().time}",
  method: 'POST', params: [id: scrname]

I placed a request for the /retweetpull URL, which is aptly mapped as post "/retweetpull", forward: "/retweetpull.groovy". Note that this is an HTTP POST request. I also passed along the parameter id, which is the user’s screen name. Note that queue task names must be unique. I find it helpful to append a date-time stamp to create uniqueness. Another option demonstrated in Listing 6 is to define a countdown time that will run until the task is executed.

task in a GAE queue is just a servlet. Accordingly, a URL is invoked by a queue “reader,” and the particular servlet I wish to be executed is shown in Listing 7:
Listing 7. An asynchronously executed servlet

import twitter4j.TwitterFactory
import twitter4j.http.AccessToken
import com.b50.gretweet.User
import com.b50.gretweet.Retweet

def user =  User.findByName(params['id']) 

def twitter = getTwitter(user)

def retweets = []

def statuses = twitter.getRetweetsOfMe()

statuses.each { status ->
	def tid = status.getId()
	def dt = status.getCreatedAt()
	def txt = status.getText()

	users = twitter.getRetweetedBy(status.getId())

	users.each { usr ->		
                  retweets << new Retweet(usr.getScreenName(), tid, dt, txt,
                    usr.getFollowersCount(), usr.getProfileImageURL().toString(), 


def getTwitter(user){
	return new TwitterFactory().getOAuthAuthorizedInstance("...", 
			"...", new AccessToken(user.token, user.tokenSecret))	

The first thing the servlet in Listing 7 does is find a User instance, which it actually grabs from memcache. Next, as it’s been authorized to do, the servlet acts as the user to establish a Twitter session. With Twitter4J’s API, it obtains a list of retweets — for instance,twitter.getRetweetsOfMe(). It processes each one into a Retweet object, and places all theRetweet objects into a List (using Groovy’s handy << operator, which is really just an addmethod). When the batch is added to the User instance (via the addRetweets method), it is saved.

At this point, I’ve got a few crucial pieces of my web application coded. My next step is to determine how I’ll communicate — that is, what format I’ll use when issuing a request to grab the application data.

Back to top

Request format: JSON

Some say that JSON has replaced XML as the new lingua franca of Internet data exchange. JSON is lightweight compared to XML, which makes it easier to read and parse. You can see the difference for yourself by looking at the two listings below. The first listing shows retweet data represented using XML:

  <tweet>"I want our veterans to know: We remember..."</tweet>
  <username>"Barack Obama"</username> 

This one shows the same data represented using JSON:

 "tweet":"I want our veterans to know: We remember...",
 "username":"Barack Obama"

Note that the JSON document looks a lot like a map. There are names, which have values. This makes representing something like a domain object quite simple, but don’t let JSON’s simpleness fool you. JSON can represent lists, which can also be values. JSON’s markup is less verbose than that of XML, and the corresponding data is easier to visually comprehend. JSON’s terseness can also be an advantage in Ajax systems, which tend to tax browser processing and network bandwidth.

For these reasons, I prefer JSON to XML for browser-to-server communication. This is especially true for applications that use Ajax on the front end, as this one will. Just asjava.lang.Object‘s toString method provides a convenient mechanism for codifying an object, I find it helpful to make domain objects in a web application JSON-able via a toJSONmethod. My first step is to enforce a JSONable contract via the interface in Listing 8:
Listing 8. A toJSON contract

public interface JSONable {
  String toJSON();

Next, I have my domain objects implement the interface. I provide the behavior via a handy library known as JSON-lib, which has a simple, low-level API for creating JSON structures. For example, to represent a Retweet, my toJSON method will look like Listing 9:
Listing 9. Retweet’s toJSON

public String toJSON() {
  JSONObject json = new JSONObject();
  json.put("screenname", this.screenName);
  json.put("influence", this.influence);
  json.put("date", this.getFormattedDate());
  json.put("tweet", this.tweet);
  json.put("tweetid", this.tweetId);
  json.put("picture", this.userPicture);
  json.put("username", this.userName);
  return json.toString();

Note that I’ve chosen to have explicit control over how my JSON document looks. I’m not interested in camel-casing, although I’ve obeyed it by convention in my Java code. SouserName is a property in Retweet, but I’ve left it as username in my JSON document. In Listing 9, I’ve used JSON-lib‘s JSONObject and its put call to define each property I want represented. I’ve also left out the owner property.

Representing collections

The pattern of having individual objects represent themselves as JSON is helpful when building aggregate JSON documents. For example, if I want to represent a collection of retweets (say the top three retweets as ranked by influence), I can do so. Given a collection of three Retweet objects, making a larger JSON document to contain them is as easy as coding up a servlet that does it all, as in Listing 10:
Listing 10. Analysis servlet

import com.b50.gretweet.User
import com.b50.gretweet.Retweet
import net.sf.json.JSONObject

response.contentType = "application/json"

def user =  User.findByName(params['name']) 
def tweets = user.listAllRetweetsByInfluence() 
def topthree = tweets.unique(Retweet.getScreenNameComparator())
def justthree = ((topthree.size() >=3) ? topthree[0..2] : topthree[0..topthree.size()])
def jsonr = new JSONObject() jsonr.put("influencers", justthree*.toJSON())

println jsonr.toString()

In Listing 10, the response type is set to application/json, which alerts the browser that some JSON content is forthcoming. Next, the desired User instance is found. The parameter passed to this servlet is the user’s screen name. All related Retweet objects are then retrieved.

Using a Comparator ensures that only unique Retweets are returned. If a highly influential person were to retweet various tweets for a particular user, they would only be listed once. The Comparator is shown in Listing 11:
Listing 11. A Retweet Comparator

public static Comparator<Retweet> getScreenNameComparator(){
 return new Comparator<Retweet>() {
  public int compare(Retweet arg0, Retweet arg1) {
    return 0;
    return (arg0.influence > arg1.influence) ? 1 : -1; 

The justthree variable in Listing 10 is a List containing three Retweet objects. Groovy’s handy-dandy shortcuts let me invoke the toJSON method on all objects within a collection with a *. invocation.

The code results in a JSON document that looks like Listing 12 (I’ve added spaces and newlines for more readability):
Listing 12. A list of Retweets in JSON

      "tweet":"Podcast w/@stuarthalloway about Clojure http://bit.ly/bFBRND...",
      "tweet":"New weekly podcast series airs today @ devWorks! @matthewmcc...",
      "username":"Andres Almiray"
      "tweet":"new article published: \"MongoDB: A NoSQL datastore with (all...",
      "username":"John Ferguson Smart"

Now that I’ve got a servlet to pump out a list of Retweets in JSON format, I’m ready to connect that data to my dashboard web page.

Back to top

Asynchronous processing in GAE

The background processing that I set up a few sections back is pretty non-invasive from an application perspective: after a user has authenticated a session with Twitter, a job enters the queue and goes to work on obtaining a list of all retweets. My next task is to match up that application interaction with the user experience, which needs to be equally non-invasive. Rather than having a user click additional links or load additional pages, I’m going to use a little Ajax to smooth the path.

Once a user has granted my web application permission to act on his or her behalf in Twitter, they’ll be forwarded to a dashboard of sorts. On the dashboard, they’ll be presented with a report that will eventually list their retweets, which will be ranked by influence. When the user first clicks over to the page the report won’t be ready yet, of course, so I’ll have to load it up asynchronously.

Request and response

I’ve created a servlet to handle the asynchronous magic that returns JSON, which you saw inListing 10 (dubbed tanalysis.groovy). Calling the servlet from a web page via Ajax isn’t terribly difficult; in fact, I can simplify it with a library like JQuery. To achieve my goal of asynchronously updating the dashboard with a retweet analysis report I’m going to do a few things: first, when the page is finished loading, I’ll issue an Ajax call via JQuery’s getJSONmethod. This method will invoke the analysis servlet from Listing 10. I’ll also be passing along the current user as a parameter to the servlet call. But right before the servlet is hit, another JavaScript function will create the familiar “spinner” icon, which indicates ongoing activity.

When the servlet responds with a JSON document, the getJSON method will update a section of the HTML DOM with a list of three retweets. Finally, another JavaScript event will fire, turning off the spinner.

Listing 13 is the Ajax code found in my dashboard.gptl file:
Listing 13. dashboard.gptl

<script type="text/javascript">
$(document).ready(function() {
 $.getJSON('../tanalysis', {name: "${params['tname']}" }, function(data) {
   $.each(data.influencers, function(){
    $('.statuses').append('<li> <span
      > <img width="48" height="48" src="' +
	 this.picture +
     '" alt="Chris Burns"></span><span
       > <span>' +
     '<strong><a href="https://twitter.com/' +
	 this.screenname +
	 '">' +
	 this.username +
	 '</a></strong>' +
	 ' Followers: ' +
	 this.influence +
	 '<br/><span>Tweet ' +
	 '<a target="_blank" rel="nofollow" href="https://twitter.com/' +
	 this.screenname +
	 '/status/' +
	 this.tweetid +
	 '">' +
	 this.tweetid +
	 '</a> <img width="13" height="11" src="../images/new_window_icon.gif"/>' +
	 ':   ' +
	 this.tweet +

$('.log').ajaxStart(function() {
 $(this).text('Loading data...');
 $(".spinnericon").css('display', 'block');

$('.log').ajaxStop(function() {
 $(".spinnericon").css('display', 'none');

Note how easily the JSON response is parsed via JQuery and JavaScript. The getJSON method basically invokes a closure with my JSON, which I’ve named data. I can access elements in the JSON document quite easily — in fact, by name. In this case, data first points to a collection,data.influencers. I invoke the each method on the items in the collection, and then grab individual values by it — it.tweet, for example.

New to JavaScript?

If you’re new to JavaScript, some of the code in the last couple of sections will be unfamiliar to you. If you study it, however, you’ll notice that it’s a lot like Groovy: easy for a Java developer to read and pick up. JavaScript is ubiquitous on the web as the basis of productivity tools like JSON and JQuery, and it’s the underlying language of Ajax. Every Java 2.0 developer should be comfortable with JavaScript.

Notice the events I’ve defined below the getJSON method in Listing 13, like ajaxStart andajaxStop. These methods place a spinner icon and remove it, respectively, inside of an HTMLdiv element.

Now I’ve essentially got a working web application that authenticates a user with Twitter’s OAuth, grabs his or her data asynchronously, and then asynchronously updates a web page.

My only remaining concern is security.

User authentication — gotcha!

Astute readers might have noticed a slight issue with the code in Listing 13: A retweet report is generated for a user, and that user’s name is anyone on Twitter (or at least anyone for whom the web application has data). Could an unscrupulous person start randomly passing in user names? Yes, they could — but no report would be displayed, not unless they were logged in to Twitter. Under the hood, I’ve set up a little security catch: a ServletFilter that will check to see if the given parameter name matches the name currently in session for the current user. If there isn’t a match, the request will be redirected back to the login screen.
Listing 14. User authentication with ServletFilter

package com.b50.gretweet;

import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

public class UserFilter implements Filter {

 private ServletContext context;

 public void destroy() {}

 public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) 
  throws IOException, ServletException {

  String uri = ((HttpServletRequest)req).getRequestURI();
  HttpSession sess = ((HttpServletRequest)req).getSession();
  String username = uri.replace("/dashboard/", "");

  if(sess != null){
   String sessname = (String) sess.getAttribute("screenname");
  chain.doFilter(req, res);

 public void init(FilterConfig arg0) throws ServletException {
 this.context = arg0.getServletContext();

This security mechanism isn’t beautiful, but it’ll work for the purpose of my application and this article.

Back to top

In conclusion

In this two-part article, I first introduced Objectify-Appengine, which is a mapping layer for GAE’s datastore, much like Hibernate is for relational datastores. I showed you how to use Objectify to quickly model an application, which I then wired up with Twitter’s authorization system, OAuth. I used Gaelyk to build out the application’s back-end functions, like I/O and GAE queues, and JSON (rather than XML) to facilitate the browser-to-server communication. Finally, I threw in a little Ajax on the front-end, for a smoother end-user experience. I didn’t really have to do much else, because GAE manages things like database management and caching.

While GAE does have some rules that you might not be accustomed to, like the 30-second request-response guideline, it also provides mechanisms for handling them, like asynchronous queues. In many ways, GAE is the perfect expression of Java development 2.0: it facilitates rapid development on the cheap while offering the scalability and reliability users have come to expect.

Back to top


Sample code for this article j-javadev2-14.zip 137KB HTTP

Information about download methods



Get products and technologies

  • Download Objectify: The simplest convenient interface to the Google App Engine datastore.


  • Get involved in the My developerWorks community. Connect with other developerWorks users while exploring the developer-driven blogs, forums, groups, and wikis.

Trả lời

Mời bạn điền thông tin vào ô dưới đây hoặc kích vào một biểu tượng để đăng nhập:

WordPress.com Logo

Bạn đang bình luận bằng tài khoản WordPress.com Đăng xuất /  Thay đổi )

Google+ photo

Bạn đang bình luận bằng tài khoản Google+ Đăng xuất /  Thay đổi )

Twitter picture

Bạn đang bình luận bằng tài khoản Twitter Đăng xuất /  Thay đổi )

Facebook photo

Bạn đang bình luận bằng tài khoản Facebook Đăng xuất /  Thay đổi )


Connecting to %s