Tuesday, December 30, 2014

AngularJS with server side custom validation (using Spring) and showing the server side error message on the GUI



AngularJS has a wonderful front end validation in built.

But as we all know (do we all?)  that client side validation is not enough and must be backed up by server side validation. So how do we do it?

AngularJS validation is almost a dream.

But like a nightmare, I couldn't figure out how to user server side validation instead of client side. Until now.

So let's get started.

The technologies being used.

  • FrontEnd - AngularJS
  • BackEnd - Spring Framework
    I am not using any DB, just a Map to save data in memory.


First the BackEnd
  1. Create your bean Person with id, name, and age. Use javax.validation.constraints.* to annotate name, and age.
    package domain;
    
    import java.io.Serializable;
    
    import javax.validation.constraints.Max;
    import javax.validation.constraints.Min;
    import javax.validation.constraints.NotNull;
    import javax.validation.constraints.Size;
    
    public class Person implements Serializable {
    	public Person() {
    		super();
    	}
    	public Person(Long id, String name, Integer age) {
    		super();
    		this.id = id;
    		this.name = name;
    		this.age = age;
    	}
    	/**
    	 *
    	 */
    	private static final long serialVersionUID = -1293372630653301413L;
    
    	private Long id;
    
    	@NotNull
    	@Size(min=5, max=20)
    	private String name;
    
    	@NotNull
    	@Min(18)
    	@Max(100)
    	private Integer age;
    
    	public Long getId() {
    		return id;
    	}
    	public void setId(Long id) {
    		this.id = id;
    	}
    	public String getName() {
    		return name;
    	}
    	public void setName(String name) {
    		this.name = name;
    	}
    	public Integer getAge() {
    		return age;
    	}
    	public void setAge(Integer age) {
    		this.age = age;
    	}
    }
    

     As you can see, name has to be minimum 5 characters, max 20, and is required. age has to be between 18 and 100, and required.
  2. Remove client side validation if any. Reason: I don't want to duplicate my validation logic (error possibilities).
  3. Create Our custom validator PersonValidator.
    package domain.validator;
    
    import java.util.HashMap;
    import java.util.Map;
    import java.util.Set;
    
    import javax.validation.ConstraintViolation;
    import javax.validation.ConstraintViolationException;
    import javax.validation.Validation;
    import javax.validation.ValidatorFactory;
    
    import org.springframework.validation.Errors;
    import org.springframework.validation.Validator;
    
    import domain.Person;
    import exception.MyValidationException;
    
    /**
     * Validator for Person.
     * @author Tathagat
     *
     */
    public class PersonValidator implements Validator {
    
    	@Override
    	public boolean supports(Class clazz) {
    		return Person.class.isAssignableFrom(clazz);
    	}
    
    	@Override
    	public void validate(Object target, Errors errors) {
    		// first do the normal annotation based validation
    		ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
    		javax.validation.Validator validator = factory.getValidator();
    		Set> validationErrors = validator.validate(target);
    
    		// throw all violations so they can be caught and processed
    		if (!validationErrors.isEmpty()) {
    			throw new ConstraintViolationException(validationErrors);
    		}
    
    		// here comes my custom validation
    		Map customValidationErrors = new HashMap();
    		Person person = (Person) target;
    		if ("Hitler".equalsIgnoreCase(person.getName())) {
    			customValidationErrors.put("name", "Are you kidding me?");
    		}
    
    		// throw once all custom validations are done
    		if (!customValidationErrors.isEmpty()) {
    			throw new MyValidationException(customValidationErrors);
    		}
    	}
    
    
    
    }
    
    
    The first 3 lines of the method validate() calls the standard validation based on javax.validation.constraints.*. If we don't do this, our standard validation is not called which is a bummer. Then the validationErrors need to be thrown. In the ExceptionHandlerController class below, these expceptions will be caught.

    Next (After all annotation based validations have passed (and therefore no exceptions have been thrown) we can do our custom validation. Note that both in one go are not possible.

    I am making sure that Hitler cannot be stored as a name. If there is a violation, I put it in a map and wrap it in a custom Exception MyValidationException (see below) and throw it.
  4. Create PersonController which supporst REST calls using Spring
    package controller;
    
    import java.util.HashMap;
    import java.util.LinkedList;
    import java.util.List;
    import java.util.Map;
    import java.util.Set;
    import java.util.concurrent.atomic.AtomicLong;
    
    import javax.validation.Valid;
    
    import org.springframework.web.bind.WebDataBinder;
    import org.springframework.web.bind.annotation.InitBinder;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    import org.springframework.web.bind.annotation.RestController;
    
    import domain.Person;
    import domain.validator.PersonValidator;
    
    @RestController
    @RequestMapping("api")
    public class PersonController {
    
    	/**
    	 * some default values
    	 */
    	public PersonController() {
    		super();
    		atomicId = new AtomicLong(0);
    		long id = atomicId.getAndIncrement();
    		persons.put(id, new Person(id, "Tathagat", 36));
    		id = atomicId.getAndIncrement();
    		persons.put(id, new Person(id, "Betty", 35));
    
    		personValidator = new PersonValidator();
    	}
    
    	private PersonValidator personValidator;
    
    	@InitBinder
    	public void initBinder(WebDataBinder binder) {
    		binder.setValidator(personValidator);
    	}
    
    	private AtomicLong atomicId = new AtomicLong();
    	private Map persons = new HashMap<>();
    
    	// persons GET - get all
    	@RequestMapping(value="/persons",
    			method=RequestMethod.GET)
    	public List getAll() {
    		List allPersons = new LinkedList<>();
    		Set keys = persons.keySet();
    		for (Long key : keys) {
    			allPersons.add(persons.get(key));
    		}
    		return allPersons;
    	}
    
    	// persons POST - create new
    	@RequestMapping(value="/persons",
    			method=RequestMethod.POST)
    	public void create(@Valid @RequestBody Person person) {
    		// if ID is null, then it's an insert
    		if (person.getId()==null) {
    			long id = atomicId.getAndIncrement();
    			person.setId(id);
    		} else {
    			// for update, we remove it first
    			persons.remove(person.getId());
    		}
    		persons.put(person.getId(), person);
    	}
    
    	// persons/:id GET - get single
    	@RequestMapping(value="/persons/{id}",
    			method=RequestMethod.GET)
    	public Person get(@PathVariable Long id) {
    		return persons.get(id);
    	}
    
    	// persons/:id DELETE - delete existing
    	@RequestMapping(value="/persons/{id}",
    			method=RequestMethod.DELETE)
    	public void delete(@PathVariable Long id) {
    		persons.remove(id);
    	}
    }
    
    The constructor adds 2 persons to the persons Map (which I am using instead of a DB). It also initializes our custom validator.

    There are 4 REST methods which conform to the REST methods as defined by the AngularJS resource: getAll (/api/persons - GET), create (/api/persons - POST), get (/api/persons/:id - GET), delete (/api/persons/:id - DELETE).

    The create method parameter has an extra annotation @Valid which basically says, validate the passed parameter.

    The method initBinder sets the validator that should be called when the create method is called.
  5. Create the ExceptionHandlerController class. Remember the exceptions being thrown by PersonValidator? They will be caught and processed here.
    package controller;
    
    import java.util.HashMap;
    import java.util.Map;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.validation.ConstraintViolation;
    import javax.validation.ConstraintViolationException;
    
    import org.springframework.http.HttpStatus;
    import org.springframework.validation.FieldError;
    import org.springframework.web.bind.MethodArgumentNotValidException;
    import org.springframework.web.bind.annotation.ControllerAdvice;
    import org.springframework.web.bind.annotation.ExceptionHandler;
    import org.springframework.web.bind.annotation.ResponseBody;
    import org.springframework.web.bind.annotation.ResponseStatus;
    
    import exception.MyValidationException;
    
    @ControllerAdvice
    public class ExceptionHandlerController {
    
    	@ResponseStatus(HttpStatus.BAD_REQUEST)
    	@ResponseBody
    	@ExceptionHandler({MethodArgumentNotValidException.class, ConstraintViolationException.class, MyValidationException.class})
    	public Map processCalidationExceptions(HttpServletRequest req, Exception ex) {
    		Map validationErrors = new HashMap<>();
    		if (ex instanceof MethodArgumentNotValidException) {
    			for (FieldError fieldError : ((MethodArgumentNotValidException)ex).getBindingResult().getFieldErrors()) {
    				validationErrors.put(fieldError.getField(), fieldError.getDefaultMessage());
    			}
    		}
    		if (ex instanceof ConstraintViolationException) {
    			for (ConstraintViolation violation : ((ConstraintViolationException) ex).getConstraintViolations()) {
    				validationErrors.put(violation.getPropertyPath().toString(), violation.getMessage());
    			}
    		}
    		if (ex instanceof MyValidationException) {
    			validationErrors.putAll(((MyValidationException)ex).getErrors());
    		}
    		return validationErrors;
    	}
    }
    
    The @ControllerAdvice advices the controller. @ExceptionHandler defines which exceptions should be handled here.

    As you can see, 3 types of exceptions are being caught.

    From all of them, I take out the field name and the error message and put it in a Map and return that.

    @ResponseBody indicates a method return value should be bound to the web response body. Basically the returned Map will be available in the front end on validation error.
  6. For the sake of completion of the backend, here is MyValidationException.
    package exception;
    
    import java.util.Map;
    
    public class MyValidationException extends RuntimeException {
    
    	/**
    	 *
    	 */
    	private static final long serialVersionUID = 8347119250363399412L;
    
    	private Map errors;
    
    	public MyValidationException(Map errors) {
    		this.errors = errors;
    	}
    
    	public Map getErrors() {
    		return errors;
    	}
    }
    
    
    The important thing is that this is a RuntimeException so I can throw it without declaring it. It just holds my custom errors (in a Map).


Now the FrontEnd


  1. First the module myApp.js
    var app = angular.module("myApp", ['ngResource']);
    
  2. Now the factory for Person, factory.js
    app.factory('Person', function($resource) {
    	return $resource('/api/persons/:id');
    });
    
  3. the controller.js. This is where the magic happens.
    app.controller("personController", function($scope, Person) {
        $scope.persons = Person.query();
        
        clearValidationErrorMessages = function() {
        	$scope.personForm.$setPristine(true);
        	$scope.serverErrors="";
        }
        
        $scope.load = function(id) {
        	$scope.person = Person.get({id: id});
        	clearValidationErrorMessages();
        	$('#personFormDiv').show();
        }
        
        $scope.loadForNew = function() {
        	$scope.person = new Person();
        	clearValidationErrorMessages();
        	$('#personFormDiv').show();
        }
        
        $scope.delete = function(id) {
        	Person.delete({id: id});
        	$scope.persons = Person.query();
        }
        
        $scope.insert_update = function() {
        	Person.save($scope.person,
        			function() {
        				$scope.persons = Person.query();
        				clearValidationErrorMessages();
        				$('#personFormDiv').hide();
        			},
        			function(errors) {
        				//console.log(errors);
        				$scope.serverErrors=errors.data;
        				for (var errorKey in errors.data) {
        					//console.log(errorKey + ':' + errors.data[errorKey]);
        					$scope.personForm[errorKey].$dirty=true;
        				}
        			});
        }
        
    });
    
    As soon as the page loads, all the persons are loaded using $scope.persons = Person.query(); persons are then used in the index.html below to create a table.

    When the EDIT button in the GUI is clicked, the load method is called which uses the REST to load that particular Person ($scope.person = Person.get({id: id});). It also clears all the validation error messages (if any), and shows the edit div (Which was hidden until now).

    When the NEW button is clicked, loadForNew is called. This will instantiate an empty Person and clear the error messages. It will also show the hidden div.

    DELETE will call delete which will call the REST endpoint for delete.

    On SAVE, insert_update will be called. This will save the loaded person using the REST endpoint. On success, the persons will be loaded again, validation error messages will be cleared, and the div will be hidden again. If there is an error (remember the map we were returning from the ExceptionHandlerController class?) we will loop through the errors and assign the individual fields as dirty. Also the error messages map is stored in the scope to fetch the error message.
  4. finally the GUI, index.html
    <!DOCTYPE html>
    <html>
    
    <head>
    <script src="https://code.jquery.com/jquery-2.1.3.min.js"></script>
    <script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.2.26/angular.min.js"></script>
    <script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.2.26/angular-resource.js"></script>
    <script src="scripts/myApp.js"></script>
    <script src="scripts/person/factory.js"></script>
    <script src="scripts/person/controller.js"></script>
    </head>
    
    <body>
    
    <div ng-app="myApp" ng-controller="personController">
     	<p>{{ persons }}</p>
     	
     	<button type="submit" ng-click="loadForNew()">NEW</button>
     	<table border="0">
    		<thead>
    		<tr>
    			<td>ID</td>
    			<td>Name</td>
    			<td>Age</td>
    			<td></td>
    			<td></td>
    		</tr>
    		</thead>
    		<tbody>
    		<tr ng-repeat="person in persons">
    			<td>
    				{{person.id}}
    			</td>
    			<td>
    				{{person.name}}
    			</td>
    			<td>
    				{{person.age}}
    			</td>
    			<td><button type="submit" ng-click="load(person.id)">EDIT</button></td>
    			<td><button type="submit" ng-click="delete(person.id)">DELETE</button></td>
    		</tr>
    		</tbody>
    	</table>
     	
     	<br/><br/><br/><br/>
     	
     	<div id="personFormDiv" style="display:none;">
     	<form name="personForm" novalidate ng-submit="insert_update()">
     		<table border="0">
     			<tr>
     				<td>ID</td>
     				<td>
     					<input type="text" READONLY name="id" ng-model="person.id" />
     				</td>
     			</tr>
     			<tr>
     				<td>Name</td>
     				<td>
     					<input type="text" name="name" ng-model="person.name" />
     					<span ng-show="personForm.name.$dirty">{{serverErrors['name']}}</span>
     				</td>
     			</tr>
     			<tr>
     				<td>Age</td>
     				<td>
     					<input type="number" name="age" ng-model="person.age" />
     					<span ng-show="personForm.age.$dirty">{{serverErrors['age']}}</span>
     					<span ng-show="personForm.age.$error.number">Please use a number.</span>
     				</td>
     			</tr>
     		</table>
     		<button type="submit" ng-disabled="personForm.$invalid">SAVE</button>
     	</form>
     	</div>
    </div>
    
    </body>
    </html>
    
    The error messages are only shown when the field is dirty. The message is fetched from the map which we stored in the scope (Which in turn comes from ExceptionHandlerController).
    <span ng-show="personForm.name.$dirty">{{serverErrors['name']}}</span>
    

Wednesday, December 17, 2014

How to fix “yo: command not found” after installing Yeoman using nvm

First see which version is installed
nvm ls


Let's say the version is xxx,
then

nvm use xxx

That's it. Now yo should work.

Friday, November 21, 2014

When VPN connects but internet does not work

Do the following on the server:

sudo iptables -t nat -A POSTROUTING -s 10.8.0.0/24 -o eth0 -j MASQUERADE

sudo su
echo "1" > /proc/sys/net/ipv4/ip_forward

Monday, November 17, 2014

Burn ISO into a USB on a mac and boot with it

1. Download the ISO image.

2. Open the Terminal.

3. Convert the .iso file to .img using the convert option of hdiutil e.g.,
hdiutil convert -format UDRW -o ~/path/to/target.img ~/path/to/ubuntu.iso

Note: OS X tends to put the .dmg ending on the output file automatically.

4. Run
diskutil list
to get the current list of devices.

5 Insert your flash media.

6 Run
diskutil list
again and determine the device node assigned to your flash media (e.g. /dev/disk2).

7 Run
diskutil unmountDisk /dev/diskN
(replace N with the disk number from the last command; in the previous example, N would be 2).

8 Execute
sudo dd if=/path/to/downloaded.img of=/dev/rdiskN bs=1m
(replace /path/to/downloaded.img with the path where the image file is located; for example, ./ubuntu.img or ./ubuntu.dmg).

Using /dev/rdisk instead of /dev/disk may be fasterIf you see the error dd: Invalid number '1m', you are using GNU dd. Use the same command but replace bs=1m with bs=1MIf you see the error dd: /dev/diskN: Resource busy, make sure the disk is not in use. Start the 'Disk Utility.app' and unmount (don't eject) the drive

9 Run
diskutil eject /dev/diskN
and remove your flash media when the command completes.

10
Restart your Mac and press alt/option key while the Mac is restarting to choose the USB stick.

Sunday, November 16, 2014

Fix older versions of programs showing up in "open with" / "right click" dialogue in mac / apple / mavericks.

Fix older versions of programs showing up in "open with" / "right click" dialogue in mac / apple / mavericks.

Open a Terminal and type the following and press enter.

/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/Support/lsregister -kill -r -domain local -domain system -domain user

and restart.

Voila.

Wednesday, October 29, 2014

jPicSizer - PicSizer for mac / apple

One thing I missed when I moved to my shining new Mac last year - PicSizer.

It is just a simple piece of software for windows that lets you batch resize images. Here is the link to the windows version: http://www.axiomx.com/picsizer.htm.

Unfortunately there is no Mac version available.

So I decided to write my own. I wrote it in Java, hence the name jPicSizer. It uses Imgsclar lib which can be found here: https://github.com/thebuzzmedia/imgscalr.

It requires java 8. It is an alpha version so pardon the bugs.

The background is customizable. I have a picture I took in Portugal this summer.

Here are a screenshot
Download

Released under Apache License Version 2.0.

Tuesday, October 28, 2014

Connect to github.com (clone a repository) from behind a firewall

I was (luckily) very very frustrated as I could not clone a repo from github.com till I found the solution. Socks proxy (from SSH Tunnel) simply did not work.

Here are the steps I took in order to finally get the repo cloned.

The repo I wanted to clone: git clone https://github.com/pentaho/mondrian.git


  1. Create a SSH Tunnel. Mine is set at localhost 8888.
  2. Do a local port forward to github.com 22. I forwarded port 3334. In putty settings it looks like: L3334 github.com:22.
  3. Create a github account.
  4. Copy your public ssh to your github account.
  5. Clone using git clone ssh://git@localhost:3334/pentaho/mondrian.git. Use the user git as in the command and not your user name.

Hopefully it helps some frustrated IT worker behind a corporate firewall.