Wednesday, July 23, 2014

Spring Security Custom FilterChainProxy using Java Configuration

In a previous post I wrote how to custom configure FilterChainProxy using Java Bean XML configuration file. I got some feedback and of the things I was pointed out was that it could also be done using Java configuration instead of XML configuration. But at that time I couldn't do it because we were using XML configuration files for beans. But now as part of my work I needed to work with Spring using Java configuration. So I thought this is the right time to go back and write the whole custom FilterChainProxy configuration in Java configuration.

After some documentation reading, trial and error, stackoverflow I managed to get the following Java configuration to work as exactly as same as my previous post's XML configuration.


import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;

import javax.inject.Inject;
import javax.servlet.ServletException;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.AccessDecisionVoter;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.intercept.RunAsManager;
import org.springframework.security.access.intercept.RunAsManagerImpl;
import org.springframework.security.access.vote.AffirmativeBased;
import org.springframework.security.access.vote.RoleVoter;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.authentication.dao.ReflectionSaltSource;
import org.springframework.security.authentication.dao.SaltSource;
import org.springframework.security.authentication.encoding.Md5PasswordEncoder;
import org.springframework.security.authentication.encoding.PasswordEncoder;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.AccessDeniedHandlerImpl;
import org.springframework.security.web.access.ExceptionTranslationFilter;
import org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler;
import org.springframework.security.web.access.expression.ExpressionBasedFilterInvocationSecurityMetadataSource;
import org.springframework.security.web.access.expression.WebExpressionVoter;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.CookieClearingLogoutHandler;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
import org.springframework.security.web.context.SecurityContextPersistenceFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.web.filter.RequestContextFilter;

import com.lkbotics.visualizer.util.CustomUsernamePasswordAuthenticationFilter;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Inject
    UserDetailsService userService;

    @Bean
    public AuthenticationManager authenticationManager() throws Exception {
        AuthenticationManager authenticationManager = new ProviderManager(
                Arrays.asList(authenticationProvider()));
        return authenticationManager;
    }

    @Bean
    public AuthenticationProvider authenticationProvider() throws Exception {
        DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
        authenticationProvider.setUserDetailsService(userService);
        authenticationProvider.setSaltSource(saltSource());
        authenticationProvider.setPasswordEncoder(passwordEncoder());
        authenticationProvider.afterPropertiesSet();
        return authenticationProvider;
    }

    @Bean
    public SaltSource saltSource() throws Exception {
        ReflectionSaltSource saltSource = new ReflectionSaltSource();
        saltSource.setUserPropertyToUse("salt");
        saltSource.afterPropertiesSet();
        return saltSource;
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new Md5PasswordEncoder();
    }

    @Bean
    public FilterChainProxy springSecurityFilterChain()
            throws ServletException, Exception {
        List<SecurityFilterChain> securityFilterChains = new ArrayList<SecurityFilterChain>();
        securityFilterChains.add(new DefaultSecurityFilterChain(
                new AntPathRequestMatcher("/login**")));
        securityFilterChains.add(new DefaultSecurityFilterChain(
                new AntPathRequestMatcher("/resources/**")));
        securityFilterChains.add(new DefaultSecurityFilterChain(
                new AntPathRequestMatcher("/**"),
                securityContextPersistenceFilter(), 
                logoutFilter(),
                usernamePasswordAuthenticationFilter(),
                exceptionTranslationFilter(),
                filterSecurityInterceptor()));
        return new FilterChainProxy(securityFilterChains);
    }

    @Bean
    public SecurityContextPersistenceFilter securityContextPersistenceFilter() {
        return new SecurityContextPersistenceFilter(
                new HttpSessionSecurityContextRepository());
    }

    @Bean
    public ExceptionTranslationFilter exceptionTranslationFilter() {
        ExceptionTranslationFilter exceptionTranslationFilter = new ExceptionTranslationFilter(
                new LoginUrlAuthenticationEntryPoint("/login"));
        AccessDeniedHandlerImpl accessDeniedHandlerImpl = new AccessDeniedHandlerImpl();
        accessDeniedHandlerImpl.setErrorPage("/exception");
        exceptionTranslationFilter
                .setAccessDeniedHandler(accessDeniedHandlerImpl);
        exceptionTranslationFilter.afterPropertiesSet();
        return exceptionTranslationFilter;
    }

    @Bean
    public UsernamePasswordAuthenticationFilter usernamePasswordAuthenticationFilter()
            throws Exception {
        UsernamePasswordAuthenticationFilter usernamePasswordAuthenticationFilter = new UsernamePasswordAuthenticationFilter();
        usernamePasswordAuthenticationFilter
                .setAuthenticationManager(authenticationManager());
        usernamePasswordAuthenticationFilter.setAllowSessionCreation(true);
        SimpleUrlAuthenticationSuccessHandler successHandler = new SimpleUrlAuthenticationSuccessHandler(
                "/");
        successHandler.setAlwaysUseDefaultTargetUrl(true);
        usernamePasswordAuthenticationFilter
                .setAuthenticationSuccessHandler(successHandler);
        usernamePasswordAuthenticationFilter
                .setAuthenticationFailureHandler(new SimpleUrlAuthenticationFailureHandler(
                        "/login?error=true"));
        usernamePasswordAuthenticationFilter.afterPropertiesSet();

        return usernamePasswordAuthenticationFilter;

    }

    @Bean
    public FilterSecurityInterceptor filterSecurityInterceptor()
            throws Exception {
        FilterSecurityInterceptor filterSecurityInterceptor = new FilterSecurityInterceptor();
        filterSecurityInterceptor
                .setAuthenticationManager(authenticationManager());
        filterSecurityInterceptor
                .setAccessDecisionManager(accessDecisionManager());
        filterSecurityInterceptor.setRunAsManager(runAsManager());
        LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> requestMap = new LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>>();
        List<ConfigAttribute> configs = new ArrayList<ConfigAttribute>();
        configs.add(new org.springframework.security.access.SecurityConfig(
                "isAuthenticated()"));
        requestMap.put(new AntPathRequestMatcher("/**"), configs);
        FilterInvocationSecurityMetadataSource filterInvocationSecurityMetadataSource = new ExpressionBasedFilterInvocationSecurityMetadataSource(
                requestMap, new DefaultWebSecurityExpressionHandler());
        filterSecurityInterceptor
                .setSecurityMetadataSource(filterInvocationSecurityMetadataSource);
        filterSecurityInterceptor.afterPropertiesSet();

        return filterSecurityInterceptor;
    }

    public AffirmativeBased accessDecisionManager() throws Exception {
        List<AccessDecisionVoter> voters = new ArrayList<AccessDecisionVoter>();
        voters.add(new WebExpressionVoter());
        voters.add(new RoleVoter());
        AffirmativeBased affirmativeBased = new AffirmativeBased(voters);
        affirmativeBased.setAllowIfAllAbstainDecisions(false);
        affirmativeBased.afterPropertiesSet();

        return affirmativeBased;
    }

    @Bean
    public RunAsManager runAsManager() throws Exception {
        RunAsManagerImpl runAsManager = new RunAsManagerImpl();
        runAsManager.setKey("V_RUN_AS");
        runAsManager.afterPropertiesSet();
        return runAsManager;
    }

    @Bean
    public LogoutFilter logoutFilter() throws ServletException {
        List<LogoutHandler> handlers = new ArrayList<LogoutHandler>();
        handlers.add(new CookieClearingLogoutHandler("JSESSIONID"));
        handlers.add(new SecurityContextLogoutHandler());
        LogoutFilter logoutFilter = new LogoutFilter("/login",
                handlers.toArray(new LogoutHandler[] {}));
        logoutFilter.afterPropertiesSet();
        return logoutFilter;
    }
    
    
}

Furthermore you will need the following configuration in web.xml to support the Java configuration.

<context-param>
    <param-name>contextClass</param-name>
    <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
  </context-param>
  <context-param>
      <param-name>contextConfigLocation</param-name>
      <param-value><PACKAGE>.SecurityConfig</param-value>
  </context-param>
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>
  <filter>
      <filter-name>springSecurityFilterChain</filter-name>
      <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
  </filter>
  <filter-mapping>
      <filter-name>springSecurityFilterChain</filter-name>
      <url-pattern>/*</url-pattern>
  </filter-mapping>
  <servlet>

The same rules applies where DelegatingFilterProxy in web.xml must be named springSecurityFilterChain because our custom FilterChainProxy in SecurityConfig is named springSecurityFilterChain.

Tuesday, June 24, 2014

Simple Redis Master/Slave Replication

Redis is an in memory key value store developed to be scale-able from ground up. Redis enables this through client side sharding as well as server side master/slave replication. In this post I am going to explain a Simple 1 Master 2 Slave Server Replication Setup for Redis in a Single Host. This can be easily moved to multiple Hosts if and when the need arises with very minor changes. For this you must have downloaded redis and compiled it. If you haven't done that please follow my previous post.
The setup I am going to create looks like the above figure, where there is a Redis master listening on port 6379 and 2 Redis slaves connecting to the Master and will be listening on ports 6380 and 6381 respectively.

You need to follow the below steps to create 2 different configuration files for the slaves. 
  1. Open a Shell Terminal
  2. Navigate to redis home directory
  3. Issue "cp redis.conf redis-slave1.conf"
  4. Issue "cp redis.conf redis-slave2.conf"
  5. Open the "redis-slave1.conf" file using a Text Editor and find the line which has the port configuration and change it so that it looks like the following;  
          port 6380

     6.  And then find the line which has the slaveof configuration and change it so that it looks like the following; 

          slaveof 127.0.0.1 6379

       7.   And then find dbfilename configuration and change to "slave1-dump.rdb" as following;

          dbfilename slave1-dump.rdb

      8.  Repeat the steps 5 - 7 with "redis-slave2.conf" but this time the port must be 6381, dbfilename becomes "slave2-dump.rdb" and slaveof configuration remains the same.
       9.  Now you can start the Master followed by the 2 Slave by issuing the following commands;          

          nohup ./src/redis-server ./redis.conf > master.log 2>&1&

          nohup ./src/redis-server ./redis-slave1.conf > slave1.log 2>&1&

          nohup ./src/redis-server ./redis-slave2.conf > slave2.log 2>&1&

     10.  Now you can issue a "./src/redis-cli" and can connect to the master or any slave by providing the "./src/redis-cli -p <Slave Port>"

The recommended setup is that All writes are done to Master and All reads are performed on Slaves.
As you can see you just need to change the "127.0.0.1" host ip in your Slaves' slaveof command if you want to move this replication setup to multiple hosts. 

Sunday, June 22, 2014

Getting Started with Redis on Ubuntu

Redis, an in-memory key value data store. It comes under the category of NoSQL or Non Relational Database where storing, manipulating and retrieving of data in Redis doesn't require and use any SQL or Relations (Tables).

Relational Databases look at solving real world domain representation inside computers using a Relation/Table. A Relation/Table is a Set where the following properties are guaranteed.
  1. Every Cell contains one atomic value.
  2. Every Row is uniquely identifiable.
  3. Order of the columns has no significance.
  4. Order of the rows has no significance.
Furthermore relational databases allow to define constraints within and among relations. These constraints can be;
  1. Entity Integrity Constraints - Primary Key/Unique Columns 
  2. Referential Integrity Constraints - Foreign Key
On top of this there is a language called Structured Query Language which is a standardized way of Retrieving, Manipulating of data in those Relation/Table.

With the growth of high volume websites such as Twitter, Facebook and the emergence of IAAS (Infrastructure As A Service) solutions such as Amazon AWS, which uses distributed data centers around the globe, the need for NoSQL databases are ever so high! This is because issuing of a single SQL which covers data in across multiple data centers in different countries is not possible. Furthermore relational databases depends a lot on JOINs and distributed data centers pose a problem to this also. 

Redis, on the other hand doesn't use SQL or Relation/Table. Redis has built-in data structures and algorithms to solve frequently faced design issues.

Redis has Key/Value, Lists, Hashes (Maps), Set and Sorted Set data structures.

To get started with Redis in Ubuntu;
  1. You just need to download Redis.
  2. Unzip the File
  3. Open a Shell Terminal
  4. Navigate into the extracted directory
  5. Issue "make" command
  6. Optionally Issue "make test" to verify everything is working fine.
  7. Navigate into "src" folder
  8. Issue "chmod +x redis-server" and "chmod +x redis-cli"
  9. Issue "./redis-server"
  10. Now the Redis server will be up and listening in port 6379
  11. You can issue "./redis-cli" to start a Client where you can issue commands to Redis server

Friday, June 13, 2014

Custom Dozer Mapper Factory Bean

As part of my work I had to learn and make use of Dozer mapper. A Java Bean to Java Bean Mapper which enables Converting from one type of Java Bean (Ex:- Spring hateoas Resource) to another Java Bean (Ex:- Entity) easy and less error prone. You can learn more about this library from Dozer page.

The tricky part doing the job was that the requirement was that every JPA Entity had a corresponding Mutable and Non Mutable interface. Mutable interface extends from the Non Mutable interface. So the mapping needed to be done by using the Mutable interface to Spring hateoas Resource.

<?xml version="1.0" encoding="utf-8"?>
<mappings xmlns="http://dozer.sourceforge.net" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://dozer.sourceforge.net
          http://dozer.sourceforge.net/schema/beanmapping.xsd">

    <mapping>
        <class-a>com.shazin.example.domain.MutableItem</class-a>
        <class-b>com.shazin.example.web.resource.ItemEditResource</class-b>
        <field>
            <a>..</a>
            <b>..</b>
        </field>
        ..
    </mapping>
</mappings>

Furthermore when mapping the two back using the DozerBeanMapper API inside of the Service, the following needed to be done, completely avoiding the Entity class because at the service level only interfaces must be used not entities.

mapper.map(sourceItemResource, MutableItem.class);

This posed an issue because map method expected a second argument of implementation class of the destination bean.

The solution was writing an Custom implementation of org.dozer.BeanFactory which will provide the correct Implementation class based on the interface. So wrote the following AbstractMapperBeanFactory class.

import java.util.HashMap;
import java.util.Map;

import org.dozer.BeanFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public abstract class AbstractMapperBeanFactory implements BeanFactory {
    private static final Logger logger = LoggerFactory
            .getLogger(AbstractMapperBeanFactory.class);

    protected AbstractMapperBeanFactory() {
        register(sourceToDestinationMap);
    }

    private final Map<String, Class<?>> sourceToDestinationMap = new HashMap<>();

    protected abstract void register(
            Map<String, Class<?>> sourceToDestinationMap);

    @Override
    public final Object createBean(Object source, Class<?> sourceClass,
            String targetBeanId) {
        Class<?> destinationClass = null;
        Object result = null;
        try {
            Class<?> targetBeanClass = Thread.currentThread()
                    .getContextClassLoader().loadClass(targetBeanId);
            if (targetBeanClass.isInterface()) {
                destinationClass = sourceToDestinationMap.get(targetBeanId);
            } else {
                destinationClass = targetBeanClass;
            }

            if (logger.isDebugEnabled()) {
                logger.debug("Source Object : " + source);
                logger.debug("Source Class : " + sourceClass);
                logger.debug("Target Bean Id : " + targetBeanId);
                logger.debug("Destination Class : " + destinationClass);
                logger.debug("Target Bean Class : " + targetBeanClass);
            }

            if (destinationClass == null) {
                logger.warn(String.format(
                        "No matching destination class found for class %s",
                        targetBeanId));
            } else {
                result = destinationClass.newInstance();
            }

        } catch (ClassNotFoundException | InstantiationException
                | IllegalAccessException e) {
            logger.error(String.format(
                    "Error while creating target bean for class %s",
                    targetBeanId), e);
        }
        return result;
    }
}


And for each Module we could register Interfaces and corresponding implementations by Sub classing the above abstract class and implementing the register method as following.

public class CustomMappingBeanFactory extends AbstractMapperBeanFactory {

    protected void register(Map<String, Class<?>> sourceToDestinationMap) {
        sourceToDestinationMap.put(MutableItem.class.getName(), ItemEntity.class);
        ..
    }

}

And finally making use of the CustomMappingBeanFactory in the mapping configuration file as below.

<?xml version="1.0" encoding="utf-8"?>
<mappings xmlns="http://dozer.sourceforge.net" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://dozer.sourceforge.net
          http://dozer.sourceforge.net/schema/beanmapping.xsd">

    <mapping bean-factory="CustomMappingBeanFactory">
        <class-a>com.shazin.example.domain.MutableItem</class-a>
        <class-b>com.shazin.example.web.resource.ItemEditResource</class-b>
        <field>
            <a>..</a>
            <b>..</b>
        </field>
        ..
    </mapping>
</mappings>

Thursday, April 3, 2014

Java 8 Features Part 2

In a previous post I covered the features Parallel Operations and Method References newly available in Java 8. But the most significant and effecting feature is the Lambda Expressions. Lambda Expressions is a vast subject and was available in many other programming languages for sometime now. Java being an Object oriented programming language (with the exception of primitives of course) didn't support this up until its 1.8.0 version.

Problem (Or at least lack of efficiency) : Java was always Object oriented mostly methods are merely used to implement behavior or provide getters or setters. And more importantly a method can not exists without it being inside of a Class. This is a strong OOP Principle and has been having no problem for developers also.

Except! when you want to do this;

    public static void main(String[] args) {
        new Thread(new Runnable() { 
            public void run() {
                System.out.println("Hello, World!");
            }
        }).start();
    }

Well this code doesn't look any harm but as you can see for Just to pass the implementation which is in the Runnable.run() you need to create an anonymous class. In a scenario where you have different type of Classes and accepting Interfaces as arguments such in Swing ActionListener this can become a headache. Languages such as Javascript allows passing of functions around which eliminates this type of unnecessary codes. But Java didn't support it for good reasons.

Solution : Lambda Expressions to the Rescue! Lambda Expressions allows you to pass in Methods as if Java supports Global Methods. Enabling the flexibility of not defining anonymous classes and keeping the code clean and simple.

    public static void main(String[] args) {
        new Thread(() -> { System.out.println("Hello, World!"); }).start();
    }

What the HTML! is that?. Your inner Java coder would have screamed looking at that code. But fear not, not only this code compiles but it also runs and produces the exact same result.

Basically Lambda Expressions enables the developer to define a method at the location of where it is required or have a reference to a Method (This was covered in previous post). This feature enables the developer to simplify the code. Lambda Expression is a really vast area and can not be covered in one blog post. So I highly recommend you to read the links in the references section.

References

  • Lambda Expressions - http://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html

Wednesday, March 26, 2014

Java 8 Features Part 1

"Change is the only thing that doesn't change". This statement explains well anything and Java is not an exception in this regard. Java version 8 is released officially and you can download it from Oracle. There are some significant changes introduced in Java 8 and following are some of those;
  1. Parallel Operations - Parallel Operations for JVMs running on top of Multi Core Platforms
  2. Method References - Ability to pass in a Method as a Reference. 
  3. Lambda Expressions - Biggest Syntax Changes to Java Ever
So I'll start off with Parallel Operations and make my way down on each topic. 

Parallel Operations have been introduced to leverage the Multiple CPUs available in almost every modern computer. Operations such as Sorting could become significantly faster if Multiple CPUs were used. The Arrays.parallel* methods provide these functions.

import java.util.Arrays;

public class ParallelOperations {

    public static void main(String[] args) {
        Integer[] ints = Util.generateRandomIntegers();
        sort(ints);
        Util.display(ints);
        ints = Util.generateRandomIntegers();
        parallelSort(ints);
        Util.display(ints);
    }

    public static void sort(Integer[] ints) {
        long startTime = System.currentTimeMillis();
        Arrays.sort(ints);
        long endTime = System.currentTimeMillis();
        System.out.printf("sort() Finished in %d milliseconds\n",
                (endTime - startTime));
    }

    public static void parallelSort(Integer[] ints) {
        long startTime = System.currentTimeMillis();
        Arrays.parallelSort(ints);
        long endTime = System.currentTimeMillis();
        System.out.printf("parallelSort() Finished in %d milliseconds\n",
                (endTime - startTime));
    }

}


And passing method references can be done as following;

import java.util.Arrays;
import java.util.Comparator;
import java.util.function.BiConsumer;
import java.util.function.Consumer;

public class MethodReferences {

    public static void main(String[] args) {
        Integer[] ints = Util.generateRandomIntegers();
        // Passing Arrays.sort static method as an argument
        sort(Arrays::sort, ints);
        Util.display(ints);
        ints = Util.generateRandomIntegers();
        // Passing Arrays.parallelSort static method as an argument
        sort(Arrays::parallelSort, ints);
        Util.display(ints);
        String[] strings = new String[] {"one", "TWO", "tHree"};
        // Passing Arrays.parallelSort static method and String.compareTo instance method as an arguments
        sort(Arrays::sort, String::compareTo, strings);
        Util.display(strings);
        strings = new String[] {"ball", "apple", "cat"};
        // Passing Arrays.parallelSort static method and String.compareToIgnoreCase instance method as an arguments
        sort(Arrays::parallelSort, String::compareToIgnoreCase, strings);
        Util.display(strings);
    }
    
    public static void sort(Consumer<Integer[]> sorter, Integer[] values) {
        sorter.accept(values);
    }
    
    public static void sort(BiConsumer<String[], Comparator<String>> sorter, Comparator<String> comparator, String[] values) {
        sorter.accept(values, comparator);
    }
}

References
  • Java 8 Compiler support for Eclipse Kepler - https://wiki.eclipse.org/JDT/Eclipse_Java_8_Support_For_Kepler

Tuesday, March 25, 2014

Tiny Url with Redis

As part of work, I was asked to research about Redis, a Distributed Key-Value Persistence Store developed to make caching extremely fast. So far I have found the following by going through documentation, Redis;
  1. An In-memory Key-Value persistence store with off the shelf advanced data structures and algorithms
  2. Supports Hashes (Tables), Lists, Sets, Sorted Sets and of course Key-Value pairs
  3. Enables I/O concurrency but no execution parallelism (Single threaded engine).
  4. Supports command pipelining for lesser network roundtrips.
  5. Supports transactions on demand by queuing commands.
  6. Supports Custom Script Execution (Similar to Stored Procedures)
  7. Supports Publish/Subscribe on topics
Redis stands out from Memcached because of its built-in datastructures and operations such as Lists, Sets, Hashes and Sorting functions. Redis is widely used in alot of applications which requires high performance such Twitter, Instagram, Stackoverflow and many more.

To get some hands on experience on Redis I wrote a simple Tiny Url application using Redis and Spring MVC. Two Hash data structures are used in my application to store both the Url Code to Url Mapping and Url Code to Click Counts. 
  • tinyurls - To store Url Code to Url Mapping
  • tinyurls:clicks - To store Url Code to Click Counts
The controller for the application looks as the following. Where jedisClient is an instance of Jedis, a Java Client for Redis. 

package com.shazin.tinyurl;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.inject.Inject;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.TransactionBlock;
import redis.clients.jedis.exceptions.JedisException;

@Controller
@RequestMapping("/")
public class TinyUrlController {

    private static final String CLICKS = "tinyurls:clicks";

    private static final String TINY_URLS = "tinyurls";

    @Inject
    private Jedis jedisClient;

    @RequestMapping(value = "create", method = RequestMethod.POST)
    public ModelAndView create(@RequestParam("url") String url,
            HttpServletRequest request) {
        ModelMap map = new ModelMap();
        String tinyUrl = null;
        Long timeInMillis = System.currentTimeMillis();
        String urlKey = Long.toHexString(timeInMillis);
        jedisClient.hset(TINY_URLS, urlKey, url);
        jedisClient.hset(CLICKS, urlKey, "0");

        tinyUrl = generateTinyUrl(request, urlKey);

        map.put("tinyurl", tinyUrl);
        return new ModelAndView("home", map);
    }

    public String generateTinyUrl(HttpServletRequest request, String urlKey) {
        String tinyUrl;
        tinyUrl = new StringBuilder()
                .append((request.isSecure() ? "https://" : "http://"))
                .append(request.getServerName()).append(":")
                .append(request.getServerPort())
                .append(request.getContextPath()).append("/u/").append(urlKey)
                .toString();
        return tinyUrl;
    }

    @RequestMapping(value = "u/{urlKey}", method = RequestMethod.GET)
    public ModelAndView forwardUrl(@PathVariable("urlKey") String urlKey, HttpServletResponse response) {
        String destinationUrl = null;
        ModelMap map = new ModelMap();
        String url = jedisClient.hget(TINY_URLS, urlKey);
        if(url != null && url.length() > 0) {
            jedisClient.hincrBy(CLICKS, urlKey, 1);
            map.put("url", url);
            destinationUrl = "redirect";
        } else {
            map.put("errorMsg", "Invalid Code in Url!");
            destinationUrl = "home";
        }
        
        return new ModelAndView(destinationUrl, map);
    }
    
    @RequestMapping(value="/", method=RequestMethod.GET)
    public ModelAndView home() {
        return new ModelAndView("home");
    }
    
    @RequestMapping(value="/clicks", method=RequestMethod.GET)
    public ModelAndView clicks(HttpServletRequest request) {
        ModelMap map = new ModelMap();
        Map<String, String> clickCounts = jedisClient.hgetAll(CLICKS);
        
        Map<String, String> counts = new HashMap<String, String>();
        
        for(Map.Entry<String, String> entry:clickCounts.entrySet()) {
            counts.put(generateTinyUrl(request, entry.getKey()), entry.getValue());
        }
        
        map.put("clickCounts", counts);
        
        return new ModelAndView("clicks", map);
    }
}


And the home.jsp looks as the following.



<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<html>
<head>
<title>Tiny Url</title>
<link href="${pageContext.request.contextPath}/resources/main.css" type="text/css" rel="stylesheet"/>
</head>
<body>
    <form id="urlShortenerForm" action="${pageContext.request.contextPath}/create" method="POST">
        <center style="margin-top: 10%">
            <c:if test="${errorMsg != null}">
                <p style="color: red;">
                    <b>${errorMsg}</b>
                </p>
            </c:if>            
            <p>Shorten any url!</p>
            <c:if test="${tinyurl != null}">
                <p>
                    <b>The Tiny Url is <a href="${tinyurl}">${tinyurl}</a></b>
                </p>
            </c:if>
            <p>
                <textarea id="url" name="url" rows="5" cols="50"></textarea>
            </p>
            <p>
                <input type="button" onclick="validateUrl()" value="Shorten" />
            </p>
            <p>
                <a href="${pageContext.request.contextPath}/clicks">Clicks Count</a>
            </p>
        </center>
    </form>
</body>
<script type="text/javascript">
    function validateUrl() {
        var textArea = document.getElementById("url");
        var valid = false;
        var value = textArea.value;
        if(value.length > 0) {
            var httpIndex = value.toLowerCase().indexOf("http");
            var columnIndex = value.indexOf(":");
            var slashIndex = value.indexOf("/");        
            if(httpIndex > -1 && columnIndex > -1 && slashIndex > -1) {
                valid = true;
            }
        }
        if(valid) {
            var urlShortenerForm = document.getElementById("urlShortenerForm");
            urlShortenerForm.submit();
        } else {
            alert("Invalid Url, Please enter a valid Url");
            textArea.value = "";
            textArea.focus();
        }
    }
</script>
</html>

After submitting a URL, this how the Tiny URL will look like.


The clicks.jsp will look like the following


<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<html>
<head>
<title>Tiny Url Click Counts</title>
<link href="${pageContext.request.contextPath}/resources/main.css" type="text/css" rel="stylesheet"/>
<style type="text/css">
table
{
border-collapse:collapse;
}
table, td
{
border: 1px solid black;
text-align: center;
}
th {
background: aqua;
}
</style>
</head>
<body>
    <center style="margin-top: 10%">
        <p>Click Counts</p>

        <p>
        <table border="1">
            <tr>
                <th>Url</th>
                <th>Click Count</th>
            </tr>
            <c:choose>
                <c:when test="${clickCounts.size() != 0}">
                    <c:forEach items="${clickCounts}" var="clickCount">
                        <tr>
                            <td><a href="${clickCount.key}">${clickCount.key}</a></td>
                            <td>${clickCount.value}</td>
                        </tr>
                    </c:forEach>
                </c:when>
                <c:otherwise>
                    <tr>
                        <td colspan="2">No clicks found</td>
                    </tr>
                </c:otherwise>
            </c:choose>

        </table>
        </p>

        <p>
            <a href="${pageContext.request.contextPath}">Back</a>
        </p>
    </center>
</body>
</html>

Finally the redirect.jsp will use a Javascript to redirect to the destination URL.

<html>
    <script type="text/javascript">
        window.location = "${url}";
    </script>
    <b>Redirecting Please wait...</b>
</html>

So in the backend the Url Code is generated using the Current Time in Milliseconds converted to Hexadecimal, and stored with the URL in tinyurls hash along with the click count which is 0 in tinyurls:clicks hash. And when the generated tiny url is clicked the corresponding url is retrieved from tinyurls hash based on the Url Code and tinyurls:clicks is incremented by one based on Url Code. Finally the user is redirected to Original URL. 

For this to work redis-server must be running!

This is just a basic example of the capabilities of Redis but is a good starting point. Hoping to learn further!