Sunday, December 13, 2009

URL Shortening Service : Flex Client & Google App Engine

I Tried this simple URL shortening Service Using Flex, Java and Google App Engine. This turned out to be very simple. I will post the client and the Server Code. Write in comments if you have any questions.


TinyUrl.mxml.

<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
               xmlns:s="library://ns.adobe.com/flex/spark"
               xmlns:mx="library://ns.adobe.com/flex/halo" minWidth="1024" minHeight="768">
    <s:layout>
        <s:TileLayout/>
    </s:layout>
    <fx:Script>
        <![CDATA[
            protected function button1_clickHandler(event:MouseEvent):void
            {
                sm.url = "http://localhost:8888/s/"+srcUrl.text;
                sm.send();
            }
        ]]>
    </fx:Script>
    <fx:Declarations>
        <mx:HTTPService id="sm"  useProxy="false"
                        method="GET" result="{shortUrl.text = sm.lastResult.toString()}">
        </mx:HTTPService>
    </fx:Declarations>
    <mx:VBox verticalAlign="middle" paddingLeft="20">
        <mx:Label text="URL Shortening Service"  fontStyle="normal" fontWeight="bold" fontSize="24"/>
        <mx:Label text="Enter Your URL Here" />
        <s:TextArea id="srcUrl" width="588" height="81">
        </s:TextArea>
        <s:Button label="Shorten" click="button1_clickHandler(event)"/>
        <s:Label text="Shortened URL" />
        <s:TextArea id="shortUrl"  width="588" height="73"/>
    </mx:VBox>
</s:Application>

Next the server part. First, persistence is a breeze in Googe AppEngine and hence I used their JDO approach to store it. The class (with the annotation) is as follows:
Mapping.java

package com.apps.tiny;
import javax.jdo.annotations.*;
@PersistenceCapable(identityType = IdentityType.DATASTORE)
public class Mapping {
        @PrimaryKey @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
        public Long id;
        @Persistent
        public String url;
}

Then comes the MappingManager, the piece that performs the I/O with the Datastore:
MappingManager.java

package com.apps.tiny;
import java.util.*;
import javax.jdo.JDOHelper;
import javax.jdo.PersistenceManager;
public class MappingManager {
    private static PersistenceManager pm = JDOHelper.getPersistenceManagerFactory("transactions-optional").getPersistenceManager();
    public static Long storeMapping(Mapping mapping) {
    long existingMapping = getMappingByUrl(mapping.url);
    return (existingMapping == -1)?pm.makePersistent(mapping).id:existingMapping;
    }
    public static Long getMappingByUrl(String url) {
    Iterator<Mapping> map = ((List)(pm.newQuery("select from " + Mapping.class.getName() + " where url == '" + url + "'").execute())).iterator();
    return (map == null || !map.hasNext())?-1:map.next().id;
    }
    public static String getLink(String urlId) {
    Iterator<Mapping> map = ((List)(pm.newQuery("select from " + Mapping.class.getName() + " where id == " + Long.valueOf(urlId, 36)).execute())).iterator();
    return (map == null || !map.hasNext())?null:map.next().url;
    }
}

The first method stores the mapping, the second gets a url id based on the url (so that we don't keep bloating our DB with repeated URLs) and the third gets a URL based on the Id. The expression "Long.valueOf(urlId, 36)" converts the url id from Base 36 String to a long.
Then comes the servlet code which converts a URL into a short URL and print it to the user:
TinyUrlServlet.java

package com.apps.tiny;
import java.io.IOException;
import javax.servlet.http.*;
@SuppressWarnings("serial")
public class TinyurlServlet extends HttpServlet {
    public void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws IOException {
        Mapping mapping = new Mapping();
        mapping.url = req.getPathInfo().substring(1);
        String response = req.getRequestURL().toString().replaceAll(req.getPathInfo(), "/").replace("/s/", "/r/") + Long.toString(MappingManager.storeMapping(mapping), 36);
        resp.setContentType("text/plain");
        resp.getWriter().println(response);
    }
}

The business logic, or storing the url to db, getting the id, converting to Base 36, constructing the URL and printing it.

Now, the last class which does the real work or taking a short URL and redirecting it to the correct URL stored in the DB:
Redirect.java

package com.apps.tiny;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.*;
@SuppressWarnings("serial")
public class Redirect extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.sendRedirect(MappingManager.getLink(req.getPathInfo().substring(1)));
}
}

The only thing left is the Web.xml

<?xml version="1.0" encoding="utf-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5">
    <servlet>
        <servlet-name>Tinyurl</servlet-name>
        <servlet-class>com.apps.tiny.TinyurlServlet</servlet-class>
    </servlet>
    <servlet>
        <servlet-name>Redirect</servlet-name>
        <servlet-class>com.apps.tiny.Redirect</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>Tinyurl</servlet-name>
        <url-pattern>/s/*</url-pattern>
    </servlet-mapping>
    <servlet-mapping>
        <servlet-name>Redirect</servlet-name>
        <url-pattern>/r/*</url-pattern>
    </servlet-mapping>
    <welcome-file-list>
        <welcome-file>index.html</welcome-file>
    </welcome-file-list>
</web-app>


Alright, We are all set to go.  You can check the demo here.

No comments: