Sunday, May 15, 2011

Managing multiple Domain and Sub Domain on Google App Engine for Same Application

Google App engine is great and at some time you may end up writing a app which gets popular and you want to use the same application for different domain and different sub-domain but show different site based on same underlying data.

e.g. you are writing a site www.indiadata.com which will show all data from india
Now say you got another domain www.delhidata.com or delhi.indiadata.com,(Delhi is a city of Country India) which shows data of delhi only. so you want to use same data saved in indiadata.com on delhidata.com and vice versa.
And this tutorial can help you to do exactly same thing

Technologies/Framework used
  1. Spring MVC 3.0+
Spring MVC's annotation based controller is quite handy to use in different situation where url remains same but some header parameter is different or url parameter is different. i.e. You can write two different controller for the same url which will be invoked based on what are the parameters etc.
You can read more sbout such Spring controllers here

Now lets understand how this will work.
  • First we need to identify whethere my application url is called as http://www.example.com/somepage or http://test.example.com/somepage
  • Once its identified we will modify the incoming request and let it to be handled by Spring dispatcher servlet
  • Different controller implementation for different urls
    • One Controller for http://www.example.com/somepage
    • One controller for  http://test.example.com/somepage

First Implement a filter to check every request

Implement a filter to identify domain or subdomain, lets call it DomainFilter


import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;

public class DomainFilter implements Filter {
  
    private FilterConfig filterConfig;
 
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
        throws IOException, ServletException {
        MyServletRequestWrapper httpRequest = new MyServletRequestWrapper((HttpServletRequest)request);
         // Do not worry about MyServletRequestWrapper class, its implemented next
        String subDomain = getSubDomain(httpRequest);
  httpRequest.addHeader("subdomain", subDomain);
        filterChain.doFilter(httpRequest, response);
    }
    private String getSubDomain(HttpServletRequest httpRequest){
     //You can implement this function :) just to get www out of http://www.example.com or test out of http://test.example.com
  return "www";
    }
    public FilterConfig getFilterConfig() {
        return filterConfig;
    }

    public void init(FilterConfig filterConfig) {
        this.filterConfig = filterConfig;
    }
  
    public void destroy() {}
  
}


MyServletRequestWrapper : Class HttpServletRequest do not provide any method to modify Header in request so i extended HttpServletRequestWrapper to add this functionality.



public class MyServletRequestWrapper extends HttpServletRequestWrapper{

        private Map<String, String> headerMap;
        
        public void addHeader(String name, String value){
            headerMap.put(name, new String(value));
        }
        
        public MyServletRequestWrapper(HttpServletRequest request){
            super(request);
            headerMap = new HashMap<String, String>();
        }

        public Enumeration getHeaderNames(){
            
            HttpServletRequest request = (HttpServletRequest)getRequest();
            
            List list = new ArrayList();
            
            for( Enumeration e = request.getHeaderNames() ; e.hasMoreElements() ;)
                list.add(e.nextElement().toString());
            
            for( Iterator i = headerMap.keySet().iterator() ; i.hasNext() ;){
                list.add(i.next());
            }
            
            return Collections.enumeration(list);
        }
        
        public String getHeader(String name){
            Object value;
            if((value = headerMap.get(""+name)) != null)
                return value.toString();
            else
                return ((HttpServletRequest)getRequest()).getHeader(name);
            
        }
    }


Now apply this filter to all incoming request in web.xml



    <filter>
        <filter-name>SubSiteFilter</filter-name>
        <filter-class>com.next.fliters.DomainFilter</filter-class>
    </filter>    <filter-mapping>
        <filter-name>SubSiteFilter</filter-name>
        <url-pattern>*</url-pattern>
    </filter-mapping>



Now create a Spring Controller to handle www and other subdomain


    @Controller
    public class IndexController extends BaseController{
 //Follwoing controller will be called for url http://www.example.com/index.html
 @RequestMapping(value = "/index.html", method = RequestMethod.GET, headers="subdomain=www")
 public String mainIndexPage(Model model,HttpServletRequest request) {
  return "wwwindexpage";
 }
 
//Follwoing controller will be called for url http://test.example.com/index.html
 @RequestMapping(value = "/index.html", method = RequestMethod.GET, headers="subdomain=test")
 public String subsiteIndexPage(Model model,HttpServletRequest request) {
  return "subdomainindexpage";
 }

}


And now you are ready with your two different controller to handle two different url one with www and one with test.

Let me know if you end up with any problem

Facebook Login on Google App Engine(Java)

In this blog i will explain how you can use facebook login on your google app engine java application and how to publish on logged in user's facebook wall.

Frameworks/libraries i will be using are
1) Google App Engine Java
2) facebook-java-api(V 3.0.2)
3) Spring MVC(You can easily avoid it if you want to)

First thing i should warn you this facebook-java-api is not being supported actively so if you get into trouble you may find bit bit difficult to find a solution. But i hope using this tutorial you may not hit to any problem

Create Facebook Login Url
Login Url should be in the following Format
http://www.facebook.com/login.php?api_key=<Your Facebook Api Key1>&connect_display=popup&v=1.0&next=<Callback Url after user login and authorize app2>&cancel_url=<Callback Url after user do not login or do not authorize app3>&fbconnect=true&return_session=true&session_key_only=true&req_perms=<permissions4>
1 Your facebook application API key. For more information visit Facebook Developer
2 Your site's url after login at facebook is succesfull and user authorize your application to use his/her data.
e.g. http://www.yoursite.com/logindone
3 Your site's url after user declined to login at facebook or do not authorize your application to use his/her data.
e.g. http://www.yoursite.com/loginerror
4 Kind of information you want to access on behalf of User. Click here for more info on permission.

Once your url is ready, either directly display it on your html or hide it behind proxy url(using redirect on server side code).
Using Sprice MVC(annotaion based you can write login function as follows)


@RequestMapping(value = "/login", method = RequestMethod.GET)
 public String login(@RequestParam("op") String provider,@RequestParam("target") String target,HttpServletRequest request,Model model){
 String redirectUrl = null;
 try{
     String uri = request.getRequestURI();
     String requestUrl = request.getRequestURL().toString();
     int ind = requestUrl.lastIndexOf(uri);
     String serverName = requestUrl.substring(0, ind);
     //using provider param you can use many other logins like google etc but this example is only for facebook
     request.getSession().setAttribute("target", target);
     String returnUrl = serverName+"/logindone";
         redirectUrl="http://www.facebook.com/login.php?api_key=1102257e0b87a111b12ec1c3d04f6b6b&connect_display=popup&v=1.0&next="+returnUrl+"&cancel_url="+returnUrl+"&fbconnect=true&return_session=true&session_key_only=true&req_perms=read_stream,publish_stream,email,offline_access";
     return "redirect:"+redirectUrl;
 }



Call back url implementation
Using Sprice MVC(annotaion based you can write function as follows)

@RequestMapping(value = "/logindone", method = RequestMethod.GET)
    public String logindone(HttpServletRequest request,Model model) throws AppException{
        String target = (String)request.getSession().getAttribute("target");// save your site url in session when you are going to Login url, so that you can send user back to site url where he clicked on Login url
        try{
                JSONObject facebookCode = new JSONObject(request.getParameter("session"));
                String sessionKey = facebookCode.getString("session_key");
                FacebookJsonRestClient jsonRestClient = new FacebookJsonRestClient("API_KEY", "SECRET",sessionKey);
                long userUid = jsonRestClient.users_getLoggedInUser();
                JSONArray userInfoArray = (JSONArray)jsonRestClient.fql_query("select first_name,last_name,username, email,sex from user where uid = me()");

                String emailId = (String)userInfoArray.getJSONObject(0).get("email");
                String firstName = (String)userInfoArray.getJSONObject(0).get("first_name");
                String lastName = (String)userInfoArray.getJSONObject(0).get("last_name");
                String sex = (String)userInfoArray.getJSONObject(0).get("sex");
 //Now you can publish this on Facebook if you want to. Just brag about your site on facebook :)
                    try{
                        Attachment attachment = new Attachment();
                        attachment.setCaption(loggedInApplUser.getFirstName()+ " has joined MySite.com to Do Something");
                        attachment.setDescription("My Site is fastest growing website in my frield in India.");
                        AttachmentMedia media = new AttachmentMediaImage("http://cdn1.iconfinder.com/data/icons/locationicons/building.png","http://www.mysite.com");
                        attachment.setMedia(media);
                        jsonRestClient.stream_publish(loggedInApplUser.getFirstName()+ " has joined mysite.com to do something", 
                                attachment, null, null, null);
                    }catch(Exception ex){
                        ex.printStackTrace();
                    }
                }
        return "redirect:"+target;
    }