Writing a URL Shortner in Java Struts2 & Hibernate

This is an attempt to create a simple URL shortner service in pure JEE with Struts2 and Hibernate.

shorty-url-shortner-struts2-hibernate

Creating Base Framework

I always have Basic framework ready which gives a kick start to the web app development. You don’t have to hassle about different configuration issues/jar file issues etc. Let us start with creating sample base framework for our project. We will use Struts2 and Hibernate for this implementation.
First lets create a Dynamic web project in eclipse.
Dynamic web project
We will name our project Shorty.

Then, we will add all the required Jar files for our project. Following is the list of Jars that I have included in the WEB-INF/lib folder.
shorty-struts2-hibernate-jars

You can download all of the above JARs from http://www.ibiblio.org/maven/

Now lets us change the web.xml file and add Struts filter. This will add Struts support in our base framework.
Following will be the web.xml content.
WEB-INF/web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_ID" version="2.4"
	xmlns="http://java.sun.com/xml/ns/j2ee"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
	<display-name>Go</display-name>
	<welcome-file-list>
		<welcome-file>index.jsp</welcome-file>
	</welcome-file-list>
 	<filter>
		<filter-name>struts2</filter-name>
		<filter-class>
			org.apache.struts2.dispatcher.FilterDispatcher
		</filter-class>
	</filter>
	<filter-mapping>
		<filter-name>struts2</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>
		
</web-app>

Now create a source folder called resources. Right click on your project in Project explorer -> New -> Source Folder.

Create a file hibernate.cfg.xml in the resources folder. This will be the configuration file of Hibernate which contains database connection strings and other related data.

resources/hibernate.cfg.xml

<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
        "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
        "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">

<hibernate-configuration>

	<session-factory>

		<property name="connection.driver_class">
			com.mysql.jdbc.Driver
		</property>
		<property name="connection.url">
			jdbc:mysql://localhost:3306/shorty
		</property>
		<property name="connection.username">username</property>
		<property name="connection.password">password</property>

		<property name="connection.pool_size">1</property>
		<property name="dialect">
			org.hibernate.dialect.MySQLDialect
		</property>
		<property name="current_session_context_class">thread</property>
		<property name="cache.provider_class">
			org.hibernate.cache.NoCacheProvider
		</property>
		<property name="show_sql">true</property>
		<property name="hbm2ddl.auto">update</property>

	</session-factory>

</hibernate-configuration>

Also lets create package structure for the source code in our base framework. We will create few packages in src folder.
url-shortner-base-framework-package

Now we are ready with a basic framework that has Hibernate and Struts2 support. This can now be used to create any application using these two technologies.

Let us start with creating database design for our simplest URL shortner.

Database Design for URL Shortner

Our requirement is very simple. We will have a single table in database that holds the value for URLs and its shortcode. Following will be the DDL for LINKS table. Note that we will also track number of clicks on each URL.

CREATE TABLE LINKS
( 	
	id 		INT PRIMARY KEY AUTO_INCREMENT,
	shortcode 	VARCHAR(20),
	url 		VARCHAR(255),
	clicks		INT DEFAULT 0,
	created	TIMESTAMP DEFAULT NOW()
);

Adding logic to create shortcode

URL Shortner is all about creating short codes for a long url. The logic that we will use to create a short code is simple. We will add URL into database table with auto generating primary key. This primary key will be a unique number. Then we convert this number to a string representation of base48 with characters 0 to 9 a to z and A to Z. Following is the logic for this.

	public static String base48Encode(Long no) {
		Double num = Double.valueOf(no);
		String charSet = "23456789abcdefghijkmnpqrstuvwxyzABCDEFGHIJKLMNPQRSTUVWXYZ";
		Integer length = charSet.length();
		String encodeString = new String();
		while(num > length) {
			encodeString = charSet.charAt(num.intValue() % length)+encodeString;
			 num = Math.ceil(new Double(num / length) - 1) ;
		}
		encodeString = charSet.charAt(num.intValue())+encodeString;
		
		return encodeString;
	}

In above method we have passed a long number (which will be auto generated primary key) and get string representation.

We will add this logic into a file ShortyUtil.java. Create ShortyUtil class under net.viralpatel.shorty.util package.

net.viralpatel.shorty.util.ShortyUtil

package net.viralpatel.shorty.util;

public class ShortyUtil {
	public static String base48Encode(Long no) {
		Double num = Double.valueOf(no);
		String charSet = "23456789abcdefghijkmnpqrstuvwxyzABCDEFGHIJKLMNPQRSTUVWXYZ";
		Integer length = charSet.length();
		String encodeString = new String();
		while(num > length) {
			encodeString = charSet.charAt(num.intValue() % length)+encodeString;
			 num = Math.ceil(new Double(num / length) - 1) ;
		}
		encodeString = charSet.charAt(num.intValue())+encodeString;
		
		return encodeString;
	}
	
	public static String getShortCodeFromURL(String URL) {
		
		int index=0;
		for(index=URL.length()-1; index>=0 && URL.charAt(index)!= '/' ;index--);
		String shortCode = URL.substring(index+1);
		
		return shortCode;
	}
}

Connecting to Database using Hibernate

Adding Hibernate related code is easy.

First add HibernateUtil class in net.viralpatel.shorty.util that we will use to create SessionFactory object.

net.viralpatel.shorty.util.HibernateUtil

package net.viralpatel.shorty.util;

import org.hibernate.SessionFactory;
import org.hibernate.cfg.AnnotationConfiguration;

public class HibernateUtil {

	private static final SessionFactory sessionFactory = buildSessionFactory();

	private static SessionFactory buildSessionFactory() {
		try {
			// Create the SessionFactory from hibernate.cfg.xml
			return new AnnotationConfiguration().configure().buildSessionFactory();
		} catch (Throwable ex) {
			// Make sure you log the exception, as it might be swallowed
			System.err.println("Initial SessionFactory creation failed." + ex);
			throw new ExceptionInInitializerError(ex);
		}
	}

	public static SessionFactory getSessionFactory() {
		return sessionFactory;
	}

}

Now create an entity class that will map to LINKS table in database. Create Link.java class under net.viralpatel.shorty.model package.

net.viralpatel.shorty.model.Link

package net.viralpatel.shorty.model;

import java.io.Serializable;
import java.sql.Date;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name="LINKS")
public class Link implements Serializable{
	
	private static final long serialVersionUID = -8767337896773261247L;

	private Long id;
	private String shortCode;
	private String url;
	private Long clicks;
	private Date created;

	@Id
	@GeneratedValue
	@Column(name="id")
	public Long getId() {
		return id;
	}
	@Column(name = "shortcode")
	public String getShortCode() {
		return shortCode;
	}
	@Column(name = "created")
	public Date getCreated() {
		return created;
	}
	@Column(name = "url")
	public String getUrl() {
		return url;
	}
	@Column(name = "clicks")
	public Long getClicks() {
		return clicks;
	}
	public void setClicks(Long clicks) {
		this.clicks = clicks;
	}
	public void setShortCode(String shortCode) {
		this.shortCode = shortCode;
	}
	public void setUrl(String url) {
		this.url = url;
	}
	public void setCreated(Date created) {
		this.created = created;
	}
	public void setId(Long id) {
		this.id = id;
	}
}

Now add the mapping for above entity class Link.java in hibernate.cfg.xml file. Add following link in >session-factory< tag:

<mapping class="net.viralpatel.shorty.model.Link" />

Also we will need a controller class that we invoke from Struts action class to do read/write in database. Create LinkController.java under net.viralpatel.shorty.controller package.

net.viralpatel.shorty.controller.LinkController

package net.viralpatel.shorty.controller;

import org.hibernate.Query;
import org.hibernate.classic.Session;

import net.viralpatel.shorty.model.Link;
import net.viralpatel.shorty.util.HibernateUtil;
import net.viralpatel.shorty.util.ShortyUtil;

public class LinkController extends HibernateUtil {


	public Link get(String shortCode) {
		
		Session session = HibernateUtil.getSessionFactory().getCurrentSession();
		session.beginTransaction();
		
		Query query = session.createQuery("from Link where shortcode = :shortcode");
		query.setString("shortcode", shortCode);
		Link link = (Link) query.uniqueResult();
		if(null != link) {
			link.setClicks(link.getClicks());
			session.save(link);
		}
		session.getTransaction().commit();
	
		return link;
	
	}
	
	public Link add(Link link) {
		
		Session session = HibernateUtil.getSessionFactory().getCurrentSession();
		session.beginTransaction();
		
		Query query = session.createQuery("from Link where url = :url");
		query.setString("url", link.getUrl());
		Link oldLink = (Link) query.uniqueResult();
		if(null != oldLink)
			return oldLink;
		
		session.save(link);
		if(null == link.getShortCode()) {
			link.setShortCode(ShortyUtil.base48Encode(link.getId()));
			session.save(link);
		}
		session.getTransaction().commit();
		return link;
	}
}

Adding Struts2 Support

The UI for our URL shortner is very simple. We will have a index.jsp page which has a textbox for entering long URL and a small Shorten button. Also we will add a functionality where we can see some statistics/details of any short url. For example if shortcode http://<shorturl>q1d points to http://viralpatel.net then http://<shorturl>q1d+ will show a page with details about url http://viralpatel.net.
Related: Tutorial: Create Struts 2 Application in Eclipse

Lets starts with creating Action class. Create a class LinkAction.java under net.viralpatel.shorty.view package.

LinkAction.java

package net.viralpatel.shorty.view;


import javax.servlet.http.HttpServletRequest;
import org.apache.struts2.interceptor.ServletRequestAware;
import net.viralpatel.shorty.controller.LinkController;
import net.viralpatel.shorty.model.Link;
import net.viralpatel.shorty.util.ShortyUtil;

import com.opensymphony.xwork2.ActionSupport;

public class LinkAction extends ActionSupport implements ServletRequestAware {

	private static final long serialVersionUID = 1L;
	private final static String DETAIL = "detail";
	private String url;
	private Link link;
	
	private LinkController linkController;
	
	private HttpServletRequest request;
	
	public LinkAction() {
		linkController = new LinkController();
	}
	public String add() {
		link = new Link();
		link.setUrl(this.url);
		link = linkController.add(link);
		
		return SUCCESS;
	}
	public String get() {
		
		String uri = request.getRequestURI();
		
		uri = ShortyUtil.getShortCodeFromURL(uri);

		if(uri.charAt(uri.length()-1) == '+') {
			uri = uri.substring(0, uri.length()-1);
			this.link = this.linkController.get(uri);
			return DETAIL;
		}
		
		this.link = this.linkController.get(uri);
		if(null == this.link) {
			addActionError(getText("error.url.unavailable"));
			return INPUT;
		} else {
			
			setUrl(link.getUrl());
			return SUCCESS;
		}
	}
	public String getUrl() {
		return url;
	}

	public void setUrl(String url) {
		this.url = url;
	}

	public Link getLink() {
		return link;
	}

	public void setLink(Link link) {
		this.link = link;
	}
	public void setServletRequest(HttpServletRequest request) {
		this.request = request;
	}
}

Also create struts configuration file struts.xml under resources folder and copy following content into it.
resources/struts.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
    "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
    "http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>
	<constant name="struts.enable.DynamicMethodInvocation"
		value="false" />
	<constant name="struts.devMode" value="false" />
	<constant name="struts.custom.i18n.resources"
		value="MessageResources" />

	<package name="default" extends="struts-default" namespace="/">
		<action name="add" class="net.viralpatel.shorty.view.LinkAction"
			method="add">
			<result name="success">index.jsp</result>
		</action>
		<action name="*" class="net.viralpatel.shorty.view.LinkAction"
			method="get">
			<result name="success" type="redirect">${url}</result>
			<result name="input">index.jsp</result>
			<result name="detail">detail.jsp</result>
		</action>
	</package>
</struts>

Note that we have used wildcard mapping in Struts2 action class. This is to ensure we call LinkAction for any shortcode passed in url shortner service.

Create a resource bundle file MessageResources.properties which will hold value for domain name of our URL shortner and an error message.
resources/MessageResources.properties

shorty.base.url=http://shorty/
error.url.unavailable=Couldn't find the site's URL to redirect to.

You may want to change value of key shorty.base.url to your domain name.

Add following JSP files in WebContent folder.

WebContent/index.jsp

<%@ taglib uri="/struts-tags" prefix="s"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Shorty - An Open Source URL Shortner in Struts2/Hibernate | ViralPatel.net</title>
<link href="css/style.css" rel="stylesheet"/>
</head>
<body>
<h1 class="title">Shorty</h1>
<br>
<p>A Simple URL Shortner in Struts2/Hibernate/MySQL</p>
<br><br>
<div id="link-container">

	<s:form action="add" method="post">
		<s:actionerror/>
		<s:textfield name="url" cssClass="link"/>
		<s:submit value="Shorten"/>
	</s:form>
	<s:if test="link.shortCode != null">
			<h3><s:text name="shorty.base.url"/><s:property value="link.shortCode"/></h3>
	</s:if>
	
</div>
</body>
</html>

WebContent/detail.jsp

<%@ taglib uri="/struts-tags" prefix="s"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Shorty - An Open Source URL Shortner in Struts2/Hibernate | ViralPatel.net</title>
<link href="css/style.css" rel="stylesheet" />
</head>
<body>
	<h1 class="title">Shorty</h1>

	Short Code: <s:property value="link.shortCode" /> <br />
	Original URL: <s:property value="link.url" /> <br />
	Clicks: <s:property value="link.clicks" /> <br />
	Created On: <s:property value="link.created" /> <br />

</body>
</html>

That’s all Folks

Execute the web project in your favorite container like Tomcat, Glassfish etc.
shorty-url-shortner-struts2-hibernate

Download

Download WAR File with Source Code



6 Comments

  • markvgti 18 February, 2010, 8:30

    In LinkController.get(…), shouldn’t the line
    link.setClicks(link.getClicks())

    instead read:
    link.setClicks(link.getClicks() + 1)

    In ShortyUtil.base48Encode(…): the charSet variable is missing 0 and 1—is this intentional or a typo?

    • Viral Patel 18 February, 2010, 12:44

      @markvgti:
      1. The get() method is called everytime user access the short url and we redirect it to long url. Also here we track the number of clicks and hence the link.setClicks(link.getClicks() + 1) is used to increment the number of clicks in get() method.

      2. Yes the 0 1 l o O are intentionally dropped from string charSet. Thanks for pointing out this as I forgot to mention this in post. I will update it. This is dropped to avoid any confusion in generated URL as O can be confused with 0 and 1 with l in some fonts. Although you may wish to include this.

  • Adam 22 February, 2010, 21:37

    I dont understand why you take a Long as the arg in the base64Encode method and then truncate the value to an int using intValue().

    Surely this means you cannot generate different strings for numbers greater than Integer.MAX_VALUE?

    Cheers

  • cari jodoh 24 January, 2011, 9:09

    well, I tried but failed, and maybe there is something wrong ..

  • rumah ku 12 May, 2011, 8:15

    yes,,, I tried but failed, and maybe there is something wrong ..

  • mhewedy 1 May, 2014, 21:38

    http://google.com/abc
    http://yahoo.com/abc

    above are two different urls, but definitely will return the same value for your service!
    So when the user request the shorten URL, There will be a confusion as to which one you will redirect!

Leave a Reply

Your email address will not be published. Required fields are marked *

Note

To post source code in comment, use [code language] [/code] tag, for example:

  • [code java] Java source code here [/code]
  • [code html] HTML here [/code]

Current ye@r *