DigitalJoel

2011/02/23

jQuery Dragging a Table Row, Dropping a List Item

Filed under: jquery — Tags: , , , — digitaljoel @ 11:14 pm

I have a collection of survey questions. I’m using the jQuery datatables plugin to show those questions in a way that is sortable and filterable. The datatables plugin is awesome for this. You can look at the reference for the datatables plugin for more information.

Next, I wanted to be able to drag the question as represented in a table row into a list. The list needs to be orderable, because the order of the questions in the survey is important. This leads me to the conclusion that I need to use the jQuery Sortable functionality.

Here’s my problem. If I drag the table row and drop it on the ordered list, I end up with a list full of table rows. That’s not great. The key is in the reception of the dropped element in the sortable list. Let’s get to some source.

            // variables to hold the table and the list.
            var qTable;
            var newSurvey;

            // create the fancy datatable
            $(function() {
                // setup the datatable according to the docs at datatables.net
                // my table only has two columns, with the question text being in the first column.
                qTable = $('#questionTable').dataTable( {
                        "aoColumns": [
                                      { "asSorting": [ "desc", "asc" ] },
                                      { "asSorting": [ "desc", "asc", "asc" ] },
                                  ]
                        , "bJQueryUI": true
                    }
                );
                
                // Get all the table rows and make them draggable
                $(qTable.fnGetNodes()).draggable({
                    opacity: 0.7,
                    helper: function() {
                        var text = this.children[0].innerText;
                        var result = "<li id='"+this.id+"'>"+text+"</li>";
                        return result;
                    },
                    connectToSortable: '#newSurvey'
                });

                // setup the sortable ordered list
                newSurvey = $('#newSurvey');
                newSurvey.sortable({
                    beforeStop: function( event, ui ) {
                        var id = ui.helper.attr( "id" );
                        var text = ui.helper.text();
                        var li = "<li id='"+id+"'>"+text+"</li>";
                        $(ui.item).replaceWith( li );
                    }
                }).disableSelection();
                
            });

There are a couple of subtleties here that you probably would miss if I didn’t point them out to you.

First, this line:

$(qTable.fnGetNodes()).draggable({

Looks simple enough. This line adds the draggable functionality to all rows of the datatable. This was a key. If you use another selector, something like “#tableId tr” which you would think would work, you’ll be in trouble. That’s because if you change the data that is viewed in the table, i.e. by filtering, then the draggable functionality will be lost on the new rows that are shown. So, in order to apply it to ALL rows of the table, you must call the function supplied by the datatables plugin.

Next, the helper function in the draggable setup.

    helper: function() {
        var text = this.children[0].innerText;
        var result = "<li id='"+this.id+"'>"+text+"</li>";
        return result;
    },

The helper is what is going to be displayed to the user when they are dragging your table row. I didn’t really care about showing the second column, so here I convert it to an li. This conversion isn’t absolutely necessary, but I left it in for kicks. If you decide not to write your own helper, you can use a built in “clone” helper. This will leave the row in the table and clone it for what is dragged rather than removing it from the table when you drag it out.

Finally, the beforeStop function of the Sortable.

    beforeStop: function( event, ui ) {
        var id = ui.helper.attr( "id" );
        var text = ui.helper.text();
        var li = "<li id='"+id+"'>"+text+"</li>";
        $(ui.item).replaceWith( li );
    }

While we did the transformation in the helper of the draggable, the helper isn’t what is dropped in the sortable, the actual item is, which in our case, is still a tr. So, once again, I do the transformation. Since it’s easier to get the information out of the helper than out of the table row again, I used that here. It seems like I ought to be able to just get the text of the li in the helper, but I didn’t pursue that much further. Also, if you attempt to change the item to an LI in the draggable definition, then you may end up with LIs in your table. It could get ugly.

So, there you have it. You are no longer dropping TRs in your ULs. Your tables have TRs and your ULs have LIs.

One caveat. I attempted to upgrade from 1.4.4 to 1.5 of jquery core tonight and found that dropping into the list was broken if I dragged the item out of the top of the drop zone.

2011/02/17

Java Equals Implementation Performance

Filed under: java — Tags: , — digitaljoel @ 4:13 pm

I had some questions about various implementations of the equals (and by association hashcode) methods in Java. I recently implemented a solution in a project I’m working on that uses Apache’s EqualsBuilder in order to create a simple, elegant looking implementation. Knowing that the solution used reflection, and that equals may be called a lot more than you would think. So, I implemented a little test today in order to see the performance of various implementations. The contenders are the equals method generated by the Eclipse IDE, Apache Commons EqualsBuilder implementation using append, and also using reflection, and finally Pojomatic.

Here’s the source of the test

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.pojomatic.Pojomatic;
import org.pojomatic.annotations.AutoProperty;

public class EqualsTest {

  // how many objects we want to create
  static long count = 10000;
  // seed for our random number generator so all tests get the same variation
  static long seed = 12345;
  // range of variation in the values for our objects
  static int variation = 15;
  // how many times equals was called from the eclipse method
  static long plainCounter = 0;
  // how many times equals was called from the reflection method
  static long reflectCounter = 0;
  // how many times equals was called from the append method
  static long appendCounter = 0;
  // how many times equals was called from the pojomatic method
  static long pojomaticCounter = 0;
  // how many times equals was called from the broken method.
  static long brokenCounter = 0;

  public static void main(String[] args) {
    EqualsTest test = new EqualsTest();
    System.gc();
    test.testPlainEquals();
    System.gc();
    test.testAppendEquals();
    System.gc();
    test.testReflectEquals();
    System.gc();
    test.testPojomaticEquals();
    System.gc();
    test.testBrokenEquals();
  }

  // Do our little test that calls equals a lot of times.
  private void testSomething(List<Object> objects, Object object) {
    for (Object o : objects) {
      if (o.equals(object)) {
        return;
      }
    }
    objects.add(object);
  }

  // Test the performance of equals as implemented using pojomatic
  // http://pojomatic.sourceforge.net/pojomatic/index.html
  private void testPojomaticEquals() {
    Random rand = new Random(seed);
    List<Object> list = new ArrayList<Object>();
    long start = System.currentTimeMillis();
    for (long i = 0; i < count; i++) {
      String s = "asdf";
      long suffix = i * rand.nextInt(variation);
      MyObjectPojomatic obj = new MyObjectPojomatic(s + suffix, s + (suffix + 1), s + (suffix - 1),
          suffix, suffix + 1, suffix - 1);
      testSomething(list, obj);
    }
    long end = System.currentTimeMillis();
    System.out.println("Time taken for pojomatic is " + (end - start) + "ms" + " set size is "
      + list.size() + " calls = " + pojomaticCounter);
  }

  // test a broken implementation of equals to show a difference in the set size and total calls
  private void testBrokenEquals() {
    Random rand = new Random(seed);
    List<Object> list = new ArrayList<Object>();
    long start = System.currentTimeMillis();
    for (long i = 0; i < count; i++) {
      String s = "asdf";
      long suffix = i * rand.nextInt(variation);
      MyObjectBroken obj = new MyObjectBroken(s + suffix, s + (suffix + 1), s + (suffix - 1),
          suffix, suffix + 1, suffix - 1);
      testSomething(list, obj);
    }
    long end = System.currentTimeMillis();
    System.out.println("Time taken for broken is " + (end - start) + "ms" + " set size is "
      + list.size() + " calls = " + brokenCounter);
  }

  // test EqualsBuilder.reflectionEquals from apache commons.
  // http://commons.apache.org/lang/api-2.6/org/apache/commons/lang/builder/EqualsBuilder.html
  private void testReflectEquals() {
    Random rand = new Random(seed);
    List<Object> list = new ArrayList<Object>();
    long start = System.currentTimeMillis();
    for (long i = 0; i < count; i++) {
      String s = "asdf";
      long suffix = i * rand.nextInt(variation);
      MyObjectReflect obj = new MyObjectReflect(s + suffix, s + (suffix + 1), s + (suffix - 1),
          suffix, suffix + 1, suffix - 1);
      testSomething(list, obj);
    }
    long end = System.currentTimeMillis();
    System.out.println("Time taken for reflection is " + (end - start) + "ms" + " set size is "
      + list.size() + " calls = " + reflectCounter);
  }

  // test EqualsBuilder.append equals from apache commons.
  // http://commons.apache.org/lang/api-2.6/org/apache/commons/lang/builder/EqualsBuilder.html
  private void testAppendEquals() {
    Random rand = new Random(seed);
    List<Object> list = new ArrayList<Object>();
    long start = System.currentTimeMillis();
    for (long i = 0; i < count; i++) {
      String s = "asdf";
      long suffix = i * rand.nextInt(variation);
      MyObjectAppend obj = new MyObjectAppend(s + suffix, s + (suffix + 1), s + (suffix - 1),
          suffix, suffix + 1, suffix - 1);
      testSomething(list, obj);
    }
    long end = System.currentTimeMillis();
    System.out.println("Time taken for append is " + (end - start) + "ms" + " set size is "
      + list.size() + " calls = " + appendCounter);
  }

  // Test the equals method as generated by Eclipse IDE
  // http://eclipse.org/
  private void testPlainEquals() {
    Random rand = new Random(seed);
    List<Object> list = new ArrayList<Object>();
    long start = System.currentTimeMillis();
    for (long i = 0; i < count; i++) {
      String s = "asdf";
      long suffix = i * rand.nextInt(variation);
      MyObjectPlain obj = new MyObjectPlain(s + suffix, s + (suffix + 1), s + (suffix - 1), suffix,
          suffix + 1, suffix - 1);
      testSomething(list, obj);
    }
    long end = System.currentTimeMillis();
    System.out.println("Time taken for plain is " + (end - start) + "ms" + " set size is "
      + list.size() + " calls = " + plainCounter);
  }

  // Object that uses EqualsBuilder.append for equals.
  public class MyObjectAppend {
    public String s1;
    public String s2;
    public String s3;
    public Long l1;
    public Long l2;
    public Long l3;

    public MyObjectAppend(String a, String b, String c, Long d, Long e, Long f) {
      s1 = a;
      s2 = b;
      s3 = c;
      l1 = d;
      l2 = e;
      l3 = f;
    }

    @Override
    public boolean equals(Object obj) {
      appendCounter += 1;
      if (obj instanceof MyObjectAppend) {
        MyObjectAppend that = (MyObjectAppend) obj;
        EqualsBuilder builder = new EqualsBuilder();
        builder.append(this.s1, that.s1);
        builder.append(this.s2, that.s2);
        builder.append(this.s3, that.s3);
        builder.append(this.l1, that.l1);
        builder.append(this.l2, that.l2);
        builder.append(this.l3, that.l3);
        return builder.isEquals();
      }
      return false;
    }

    @Override
    public int hashCode() {
      HashCodeBuilder builder = new HashCodeBuilder();
      builder.append(this.s1);
      builder.append(this.s2);
      builder.append(this.s3);
      builder.append(this.l1);
      builder.append(this.l2);
      builder.append(this.l3);
      return builder.toHashCode();

    }
  }

  // Object that uses EqualsBuilder.reflectEquals for the implementation
  public class MyObjectReflect {
    public String s1;
    public String s2;
    public String s3;
    public Long l1;
    public Long l2;
    public Long l3;

    public MyObjectReflect(String a, String b, String c, Long d, Long e, Long f) {
      s1 = a;
      s2 = b;
      s3 = c;
      l1 = d;
      l2 = e;
      l3 = f;
    }

    @Override
    public boolean equals(Object obj) {
      reflectCounter += 1;
      return EqualsBuilder.reflectionEquals(this, obj);
    }

    @Override
    public int hashCode() {
      return HashCodeBuilder.reflectionHashCode(this);
    }
  }

  // Object that users pojomatic's equals implementation
  @AutoProperty
  public class MyObjectPojomatic {
    public String s1;
    public String s2;
    public String s3;
    public Long l1;
    public Long l2;
    public Long l3;

    public MyObjectPojomatic(String a, String b, String c, Long d, Long e, Long f) {
      s1 = a;
      s2 = b;
      s3 = c;
      l1 = d;
      l2 = e;
      l3 = f;
    }

    @Override
    public boolean equals(Object obj) {
      pojomaticCounter += 1;
      return Pojomatic.equals(this, obj);
    }

    @Override
    public int hashCode() {
      return Pojomatic.hashCode(this);
    }
  }

  // Object with a broken equals implementation.
  public class MyObjectBroken {
    public String s1;
    public String s2;
    public String s3;
    public Long l1;
    public Long l2;
    public Long l3;

    public MyObjectBroken(String a, String b, String c, Long d, Long e, Long f) {
      s1 = a;
      s2 = b;
      s3 = c;
      l1 = d;
      l2 = e;
      l3 = f;
    }

    @Override
    public boolean equals(Object obj) {
      brokenCounter += 1;
      return false;
    }

    @Override
    public int hashCode() {
      return 1;
    }
  }

  // Object that uses Eclipse's generated equals implementation.
  public class MyObjectPlain {
    public String s1;
    public String s2;
    public String s3;
    public Long l1;
    public Long l2;
    public Long l3;

    public MyObjectPlain(String a, String b, String c, Long d, Long e, Long f) {
      s1 = a;
      s2 = b;
      s3 = c;
      l1 = d;
      l2 = e;
      l3 = f;
    }

    @Override
    public int hashCode() {
      final int prime = 31;
      int result = 1;
      result = prime * result + getOuterType().hashCode();
      result = prime * result + ((l1 == null)
          ? 0
          : l1.hashCode());
      result = prime * result + ((l2 == null)
          ? 0
          : l2.hashCode());
      result = prime * result + ((l3 == null)
          ? 0
          : l3.hashCode());
      result = prime * result + ((s1 == null)
          ? 0
          : s1.hashCode());
      result = prime * result + ((s2 == null)
          ? 0
          : s2.hashCode());
      result = prime * result + ((s3 == null)
          ? 0
          : s3.hashCode());
      return result;
    }

    @Override
    public boolean equals(Object obj) {
      plainCounter += 1;
      if (this == obj)
        return true;
      if (obj == null)
        return false;
      if (getClass() != obj.getClass())
        return false;
      MyObjectPlain other = (MyObjectPlain) obj;
      if (!getOuterType().equals(other.getOuterType()))
        return false;
      if (l1 == null) {
        if (other.l1 != null)
          return false;
      }
      else if (!l1.equals(other.l1))
        return false;
      if (l2 == null) {
        if (other.l2 != null)
          return false;
      }
      else if (!l2.equals(other.l2))
        return false;
      if (l3 == null) {
        if (other.l3 != null)
          return false;
      }
      else if (!l3.equals(other.l3))
        return false;
      if (s1 == null) {
        if (other.s1 != null)
          return false;
      }
      else if (!s1.equals(other.s1))
        return false;
      if (s2 == null) {
        if (other.s2 != null)
          return false;
      }
      else if (!s2.equals(other.s2))
        return false;
      if (s3 == null) {
        if (other.s3 != null)
          return false;
      }
      else if (!s3.equals(other.s3))
        return false;
      return true;
    }

    private EqualsTest getOuterType() {
      return EqualsTest.this;
    }
  }
}

And here’s the output

Time taken for plain is 804ms set size is 8696 calls = 39185108
Time taken for append is 3030ms set size is 8696 calls = 39185108
Time taken for reflection is 20765ms set size is 8696 calls = 39185108
Time taken for pojomatic is 4094ms set size is 8696 calls = 39185108
Time taken for broken is 604ms set size is 10000 calls = 49995000

Given that the set size and the number of equals calls are the same, you can be reasonably sure that each implementation is as correct as the others, other than the intentionally broken one, which was meant to help illustrate this point.

The results speak for themselves. The Eclipse generated solution is fastest, followed by append, followed closely by pojomatic. Finally, the reflection based implementation took over 25x the amount of time as the fastest solution.

2011/02/05

Using Mockito To Test Spring MVC Ajax Interaction

Filed under: java, spring, testing — Tags: , , , , , , , — digitaljoel @ 4:36 pm

So, I shared in Ajax Post to Spring MVC Controller what I learned about making an ajax post to a Spring MVC Controller. Then I shared in Mock Testing Spring MVC Controller what I learned about using Mockito to test my Spring MVC controller. So what about testing my RequestHandler that handles the ajax post and returns a JSON object? Well, as Samuel L. Jackson says in Jurassic Park, “Hold on to your butts”

Here’s the method that handles the ajax post of form data.

    @RequestMapping( value="answer/new", method=RequestMethod.POST)
    public ResponseEntity<String> newAnswer( @RequestParam(value="answerSeverity", required=true ) String severity
            , @RequestParam( value="answerText", required=true ) String text
            , @RequestParam( value="requiresReason", required=false, defaultValue="false" ) boolean requiresReason
            , @RequestParam( value="answerReasons", required=false ) List<Long> reasonKeys
            )
    {
        Severity sev = Severity.valueOf( severity );
        SurveyAnswer answer = new SurveyAnswer( text, sev );
        answer.setRequiresReason( requiresReason );
        if ( requiresReason )
        {
            // add all the reasons
            List<SurveyAnswerReason> reasons = surveyService.findReasonsByKey( reasonKeys );
            for( SurveyAnswerReason reason : reasons )
            {
                answer.addReason( reason );
            }
        }
        answer = surveyService.persist( answer );
        this.getAnswers( sev ).add( answer );
        return createJsonResponse( answer );
    }

    private ResponseEntity<String> createJsonResponse( Object o )
    {
        HttpHeaders headers = new HttpHeaders();
        headers.set(  "Content-Type", "application/json" );
        String json = gson.toJson( o );
        return new ResponseEntity<String>( json, headers, HttpStatus.CREATED );
    }

You can read the previous post for information on what’s going on here, but basically, we handle the form post, create a new SurveyAnswer, and then return the created answer as a JSON object using the createJsonResponse method.

In order to mock test this, I’m going to have to mock all the calls to the surveyService methods. That would be findReasonsByKey, and persist. The persist was a bit tricky because I wanted it to just return the answer that was passed as an argument to ensure that the controller was creating the answer correctly. Here’s the code to do that.


        when( surveyService.persist( any( SurveyAnswer.class ))).thenAnswer(
                new Answer<SurveyAnswer>()
                {
                    @Override
                    public SurveyAnswer answer( InvocationOnMock invocation ) throws Throwable
                    {
                        Object[] args = invocation.getArguments();
                        return (SurveyAnswer) args[0];
                    }
                });
        when ( surveyService.findReasonsByKey( anyCollectionOf( Long.class ))).thenReturn( getReasons() );

I put it in my @Before annotated setup method in my unit test. I didn’t come up with it myself, I adapted it from an excellent answer to this question on StackOverflow.com. That snippet allows me to just return the argument passed to the method, which is basically what JPA would do, other than setting the key and version, which I don’t really need for my test anyway. The mocked out findReasonsByKey just returns a list of objects that I’m creating elsewhere for testing purposes only.

So, on to the test. Here’s the code:


    @Test
    public void testNewAnswerWithReasons()
    {
        ResponseEntity<String> response = controller.newAnswer( answerSeverity.name(), answerText,
                true, getReasonKeys() );
        assertEquals( "application/json", response.getHeaders().get( "Content-Type" ).get( 0 ));
        SurveyAnswer answer = gson.fromJson( response.getBody(), SurveyAnswer.class );
        assertEquals( getSingleAnswerWithReasons(), answer );
    }

There are some helper methods that create the object graph needed for the SurveyAnswer. It then calls the method on the controller (which is also initialized in the @Before setup method) and checks the result. I’m really looking for two things. First, that the response has the Content-Type set correctly to application/json, and second, that I get an answer that corresponds to the values I passed in. Here again, I use Google’s GSON library for converting from my JSON string to my Java object. Once that is done, I can just test for equality with the answer I’m expecting. Obviously, for that to work, you’ll need to make sure your equals method is correct, but that’s an issue well addressed elsewhere on the internet and well beyond the scope of this post.

Mock Testing Spring MVC Controller

Filed under: development, spring, testing — Tags: , , , , , , — digitaljoel @ 2:08 pm

I’m in the midst of implementing a Spring MVC based web application. We have hudson for continuous integration builds that run all of our unit tests, but testing spring MVC controllers still just isn’t quite as easy as I would hope. There is some information on testing the controllers in the official spring documentation, but for someone like me that’s not a spring guru, or just starting out, it wasn’t enough to get me going. I recently was introduced to Mockito, so I spent a bit of time today trying to get a test for our controller using Mockito. It was simple and took no time at all.

I have yet to try it in a more complex controller method, but I think it’ll work just fine , especially when I get some utilities in place to initialize the mock objects that are commonly used by the controller. As it stands, here’s what I did to get it going. This tests the controller as a POJO, without using any spring configuration or capabilities.

Here is the controller I wish to test. Obviously I stripped out a bunch of code not needed for this demonstration.

@Controller
@RequestMapping( "/admin/survey" )
public class SurveyAdminController
{
    
    @Resource
    private SurveyService surveyService;
    
    @Resource
    private UnitService unitService;
    
    @Resource
    private OrganizationService orgService;
    
    /**
     * Show the table of existing questions to the user
     * @param model
     * @return
     */
    @RequestMapping( "questions" )
    public ModelAndView listQuestions()
    {
        ModelAndView mav = new ModelAndView( "/admin/questionList");
        List<SurveyQuestion> questions = surveyService.findAllQuestions();
        mav.addObject( "questions", questions );
        List<UnitFeature> features = unitService.getAllFeatures();
        mav.addObject( "features", features );
        List<Organization> organizations = orgService.getAll();
        mav.addObject( "organizations", organizations );
        return mav;
    }
}

For this simple test, it’s just going to validate that the view name is correct, and that the model that’s returned contains the correct information.


package com.bi.controller;

import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.when;

import java.util.ArrayList;
import java.util.List;

import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.web.servlet.ModelAndView;

import com.bi.data.OrganizationService;
import com.bi.data.SurveyService;
import com.bi.data.UnitService;
import com.bi.data.corp.Organization;
import com.bi.data.survey.SurveyQuestion;
import com.bi.data.unit.UnitFeature;
import com.bi.web.util.MessageUtil;

public class SurveyAdminControllerTest
{
    @Mock SurveyService surveyService;
    @Mock UnitService unitService;
    @Mock OrganizationService orgService;
    @Mock MessageUtil messageUtil;

    @Before
    public void setup()
    {
        // this must be called for the @Mock annotations above to be processed.
        MockitoAnnotations.initMocks( this );
    }
    
    @Test
    public void testListQuestions()
    {
        // setup our mock question list
        List<SurveyQuestion> questions = new ArrayList<SurveyQuestion>();
        questions.add( new SurveyQuestion( "asdf", null ));
        when( surveyService.findAllQuestions()).thenReturn( questions );

        // setup our mock feature list
        List<UnitFeature> features = new ArrayList<UnitFeature>();
        features.add( new UnitFeature( "TEST FEATURE" ));
        when( unitService.getAllFeatures()).thenReturn( features );

        // setup our mock organization list
        List<Organization> orgs = new ArrayList<Organization>();
        orgs.add( new Organization( "TEST ORGANIZATION" ));
        when( orgService.getAll()).thenReturn( orgs );

        // create an instance of the controller we want to test
        SurveyAdminController controller = new SurveyAdminController();

        // since we aren't using spring, these values won't be injected, so set them manually
        ReflectionTestUtils.setField( controller, "surveyService", surveyService );
        ReflectionTestUtils.setField( controller, "unitService", unitService );
        ReflectionTestUtils.setField( controller, "orgService", orgService );

        // call the method under test
        ModelAndView mav = controller.listQuestions();

        // review the results.
        assertEquals( questions, mav.getModel().get( "questions" ));
        assertEquals( features, mav.getModel().get( "features" ));
        assertEquals( orgs, mav.getModel().get( "organizations" ));
        assertEquals( "/admin/questionList", mav.getViewName());
    }
}

The comments should be pretty self explanatory in the test class. The awesomeness of Mockito is how you setup the mocks. A line like this:

        when( unitService.getAllFeatures()).thenReturn( features );

reads very nicely and sets up the return value for my service object, which keeps me from needing a database or anything setup in order to test my controller method. Mockito ftw.

Blog at WordPress.com.