Code Quality Unravelled: Part 2 Maven Multimodule Support in Your Site

Comment

Code Quality Unravelled: Part 2 Maven Multimodule Support in Your Site

This is the second installment in a series of posts about code quality, the tools that are available to track quality, and how to configure various systems to get all of the reporting.  There are a lot of posts on the internet about all of the tools and configurations that I'm going to describe, however, I thought it would be useful to have it all in one place.


Code Quality Unravelled: Part 1


What are multi-module projects?

There are times when you have a project that is made up of many components that you want to build together. When you have this setup there is a parent pom.xml and then each module is in a sub directory with its own pom.xml.

Aggregating Results

Multi-module projects have the ability to aggregate the quality reports and project information into one site. The site will include links to drilldown into each module as well as rolled up reports.

JXR, Javadoc, Checkstyle, JavaNCSS

Some plugins know how to handle aggregation without any extra configuration. JXR, Javadoc, Checkstyle, and JavaNCSS support aggregation with the same configuration that I listed in Part 1.


<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-jxr-plugin</artifactId>
  <version>2.5</version>
</plugin>

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-javadoc-plugin</artifactId>
  <version>2.10.3</version>
  <configuration>
    <additionalparam>-Xdoclint:none</additionalparam>
  </configuration>
</plugin>

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-checkstyle-plugin</artifactId>
  <version>2.16</version>
</plugin>

<plugin>
  <groupId>org.codehaus.mojo</groupId>
  <artifactId>javancss-maven-plugin</artifactId>
  <version>2.1</version>
</plugin>

Cobertura

Adding aggregation to Cobertura is as simple as adding an aggregate tag to the configuration section.


<plugin>
  <groupId>org.codehaus.mojo</groupId>
  <artifactId>cobertura-maven-plugin</artifactId>
  <version>2.7</version>
  <reportSets>
    <reportSet>
      <id>cobertura</id>
      <reports>
        <report>cobertura</report>
      </reports>
      <configuration>
        <aggregate>true</aggregate>
        <formats>
          <format>html</format>
          <format>xml</format>
        </formats>
      </configuration>
    </reportSet>
  </reportSets>
</plugin>

PMD/CPD, Surefire, and Taglist

In order to add aggregation for PMD, CPD, Surefire, and Taglist you need to add separate reportSets one for the individual module and one for the aggregate.


<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-pmd-plugin</artifactId>
  <version>3.5</version>
  <reportSets>
    <reportSet>
      <id>pmd-report</id>
      <reports>
        <report>pmd</report>
      </reports>
      <configuration>
        <skipEmptyReport>false</skipEmptyReport>
      </configuration>
    </reportSet>

    <reportSet>
      <id>pmd-aggregate</id>
      <inherited>false</inherited>
      <reports>
        <report>pmd</report>
      </reports>
      <configuration>
        <aggregate>true</aggregate>
        <skipEmptyReport>false</skipEmptyReport>
      </configuration>
    </reportSet>

    <reportSet>
      <id>cpd-report</id>
      <reports>
        <report>cpd</report>
      </reports>
      <configuration>
        <skipEmptyReport>false</skipEmptyReport>
      </configuration>
    </reportSet>

    <reportSet>
      <id>cpd-aggregate</id>
      <inherited>false</inherited>
      <reports>
        <report>cpd</report>
      </reports>
      <configuration>
        <aggregate>true</aggregate>
        <skipEmptyReport>false</skipEmptyReport>
      </configuration>
    </reportSet>
  </reportSets>
</plugin>

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-surefire-report-plugin</artifactId>
  <version>2.18.1</version>
  <reportSets>
    <reportSet>
      <id>unit-tests</id>
      <reports>
        <report>report-only</report>
      </reports>
      <configuration>
        <linkXRef>true</linkXRef>
        <alwaysGenerateSurefireReport>true</alwaysGenerateSurefireReport>
      </configuration>
    </reportSet>

    <reportSet>
      <id>unit-tests-aggregate</id>
      <inherited>false</inherited>
      <reports>
        <report>report-only</report>
      </reports>
      <configuration>
        <aggregate>true</aggregate>
        <linkXRef>true</linkXRef>
        <alwaysGenerateSurefireReport>true</alwaysGenerateSurefireReport>
      </configuration>
    </reportSet>
  </reportSets>
</plugin>

<plugin>
  <groupId>org.codehaus.mojo</groupId>
  <artifactId>taglist-maven-plugin</artifactId>
  <version>2.4</version>
  <reportSets>
    <reportSet>
      <id>taglist-report</id>
      <reports>
        <report>taglist</report>
      </reports>
      <configuration>
        <tagListOptions>
          <tagClasses>
            <tagClass>
              <displayName>Todo Work</displayName>
              <tags>
                <tag>
                  <matchString>todo</matchString>
                  <matchType>ignoreCase</matchType>
                </tag>
                <tag>
                  <matchString>FIXME</matchString>
                  <matchType>exact</matchType>
                </tag>
              </tags>
            </tagClass>
            <tagClass>
              <displayName>Architecture Review Needed</displayName>
              <tags>
                <tag>
                  <matchString>ARCH-REV</matchString>
                  <matchType>exact</matchType>
                </tag>
              </tags>
            </tagClass>
          </tagClasses>
        </tagListOptions>
      </configuration>
    </reportSet>

    <reportSet>
      <id>taglist-aggregate</id>
      <inherited>false</inherited>
      <reports>
        <report>taglist</report>
      </reports>
      <configuration>
        <aggregate>true</aggregate>
        <tagListOptions>
          <tagClasses>
            <tagClass>
              <displayName>Todo Work</displayName>
              <tags>
                <tag>
                  <matchString>todo</matchString>
                  <matchType>ignoreCase</matchType>
                </tag>
                <tag>
                  <matchString>FIXME</matchString>
                  <matchType>exact</matchType>
                </tag>
              </tags>
            </tagClass>
            <tagClass>
              <displayName>Architecture Review Needed</displayName>
              <tags>
                <tag>
                  <matchString>ARCH-REV</matchString>
                  <matchType>exact</matchType>
                </tag>
              </tags>
            </tagClass>
          </tagClasses>
        </tagListOptions>
      </configuration>
    </reportSet>
  </reportSets>
</plugin>

Not Everyone Plays Nicely

Unfortunately, not all of the reports we have talked about support aggregation. FindBugs and JDepend do not support aggregation, so when you run site you will have to look at these reports within each module instead of at the top level page. You can find more information about why FindBugs doesn't support this on the FindBugs site.

Generating the site

In addition to running the site report as I described in Part 1, there are a few other goals for the site plugin.

  • mvn site:site
    • Runs the site report putting the output in target/site
  • mvn site:stage
    • Runs the site report putting the output in target/staging
  • mvn site:deploy
    • Runs the site report and deploys the output to a location specified in a <distributionMangement> block

The generated reports contain deficiencies that you should be aware of:

  • Running maven site (or site:site) on a multi-module project will create links to the sub-modules that do not work. To get the links to work you will have to run site:stage or site:deploy after site:site.
  • For some reason when running site:stage, the cobertura report is a blank page. You can find the aggregate report for this in the target/site location.
  • The Surefire Report shows zeros for everything in the rolled up report, but the module reports still contain the information.

For a sample project that includes all of these reporting sections check out the repo on Github.


Code Quality Unravelled: Part 1


Comment

Code Quality Unravelled: Part 1 Building a Maven Site

Comment

Code Quality Unravelled: Part 1 Building a Maven Site

This is the first installment in a series of posts about code quality, the tools that are available to track quality, and how to configure various systems to get all of the quality reporting.  There are a lot of posts on the internet about all of the tools and configurations that I'm going to describe, however, I thought it would be useful to have it all in one place.


Code Quality Unravelled: Part 2


Building a Maven Site

Apache Maven is a build tool for Java. There are a ton of plugins to have your build do all sorts of processing and reporting. One of the key components of Maven is the site report. The site report collects all sorts of information about your project including descriptive information, contact information, license information and also various reports.

The pom.xml file is the place where you can configure all of the build information, dependencies, and reporting tools. The Apache Maven site has a great reference to all of the various components that make up a pom file. For the purposes of this post, I am going to focus on the reporting section of the pom file.

NOTE: This post was written using Maven version 3.2.3.


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.mycompany</groupId>
    <artifactId>test-java-build</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>test-java-build</name>

    <properties>
      <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <reporting>
      <plugins>
        <!-- This is where we will focus -->
      </plugins>
    </reporting>
</project>

There are a lot of reports that can be generated on the code base using Maven. I'm going to go over how to setup and configure the following tools: Cobertura, PMD/CPD, JXR, Javadoc, Surefire, FindBugs, JDepend, Taglist, Checkstyle.

Cobertura

Cobertura is a tool to report on the amount of code being covered by automated tests.


<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>cobertura-maven-plugin</artifactId>
    <version>2.7</version>
    <reportSets>
      <reportSet>
        <id>cobertura</id>
        <reports>
          <report>cobertura</report>
        </reports>
        <configuration>
          <formats>
            <format>html</format>
            <format>xml</format>
          </formats>
        </configuration>
      </reportSet>
    </reportSets>
</plugin>

This block will cause cobertura to generate the report in both xml and html format. You can find other configuration options on the plugin site. NOTE: If you are using Java 8 (specifically the new syntax) and start seeing stack traces referencing JavaNCSS, make sure you upgrade to version 2.7 of this plugin. This version brings in Cobertura 2.1.x which supports Java 8 correctly.

PMD/CPD

PMD analyzes your source code finding common programming flaws. CPD (Copy-paste-detector) also comes with PMD and looks for duplicate code in your project.


<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-pmd-plugin</artifactId>
    <version>3.5</version>
    <reportSets>
      <reportSet>
        <id>pmd-report</id>
        <reports>
          <report>pmd</report>
        </reports>
        <configuration>
          <skipEmptyReport>false</skipEmptyReport>
        </configuration>
      </reportSet>

      <reportSet>
        <id>cpd-report</id>
        <reports>
          <report>cpd</report>
        </reports>
        <configuration>
          <skipEmptyReport>false</skipEmptyReport>
        </configuration>
      </reportSet>
    </reportSets>
</plugin>

This block will run both PMD and CPD and include the reports regardless of whether there are issues detected or not. Plugin configuration options can be found on the plugin site. One key section to take a look at in the documentation is the Rule Set section. This shows you how to define or customize the rules you want PMD to check against.

JXR

JXR is plugin that produces an html view of all of the source in the project. This plugin is also used by other reports in order to link directly to the source.


<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-jxr-plugin</artifactId>
    <version>2.5</version>
</plugin>

Javadoc

The Javadoc plugin will generate the Javadoc for your project and include the documentation in the site output.


<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-javadoc-plugin</artifactId>
    <version>2.10.3</version>
</plugin>

Note: if using JDK 8 or newer, the Javadoc command has added a strict validator that checks for broken references and invalid HTML. This validator will fail the build if it finds anything it deems unacceptable. There is a good run down on this behavior on this blog. To have maven still present the warnings but not fail the build a configuration section can be added to the plugin definition like so:


<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-javadoc-plugin</artifactId>
    <version>2.10.3</version>
    <configuration>
      <additionalparam>-Xdoclint:none</additionalparam>
    </configuration>
</plugin>

JavaNCSS

The JavaNCSS Plugin generates metrics for quality and complexity on the project.


<plugin>
  <groupId>org.codehaus.mojo</groupId>
  <artifactId>javancss-maven-plugin</artifactId>
  <version>2.1</version>
</plugin>

Surefire

The Surefire Report Plugin generates a nice easy to read report of all of your test results.


<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-report-plugin</artifactId>
    <version>2.18.1</version>
    <reportSets>
      <reportSet>
        <id>unit-tests</id>
        <reports>
          <report>report-only</report>
        </reports>
        <configuration>
          <linkXRef>true</linkXRef>
          <alwaysGenerateSurefireReport>true</alwaysGenerateSurefireReport>
        </configuration>
      </reportSet>
    </reportSets>
</plugin>

This block will generate the surefire report without running the tests a second time (the default report "report" type runs the tests twice). The configuration option linkXRef links the line references in the tests to the actual source provided by JXR. The alwaysGenerateSurefireReport option will generate the surefire report whether all of the tests pass or not.

FindBugs

FindBugs analyzes the code looking for potential bugs.


<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>findbugs-maven-plugin</artifactId>
    <version>3.0.2</version>
</plugin>

JDepend

JDepend looks through the source tree analyzing the quality of the code in terms of extensibility and reusability.


<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>jdepend-maven-plugin</artifactId>
    <version>2.0</version>
</plugin>

Taglist

The Taglist plugin searches the code for various defined tags that are used to highlight areas of code that need more attention (e.g. TODO and FIXME).


<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>taglist-maven-plugin</artifactId>
    <version>2.4</version>
    <reportSets>
      <reportSet>
        <id>taglist-report</id>
        <reports>
          <report>taglist</report>
        </reports>
        <configuration>
          <tagListOptions>
            <tagClasses>
              <tagClass>
                <displayName>Todo Work</displayName>
                <tags>
                  <tag>
                    <matchString>todo</matchString>
                    <matchType>ignoreCase</matchType>
                  </tag>
                  <tag>
                    <matchString>FIXME</matchString>
                    <matchType>exact</matchType>
                  </tag>
                </tags>
              </tagClass>
              <tagClass>
                <displayName>Architecture Review Needed</displayName>
                <tags>
                  <tag>
                    <matchString>ARCH-REV</matchString>
                    <matchType>exact</matchType>
                  </tag>
                </tags>
              </tagClass>
            </tagClasses>
          </tagListOptions>
        </configuration>
      </reportSet>
    </reportSets>
</plugin>

This block configures taglist to search for the TODO tag (regardless of case), FIXME tag, and a custom ARCH-REV tag.

Checkstyle

Checkstyle is similar to PMD and FindBugs but instead of search for potential bugs focuses more on keeping a consistent coding style across the project.


<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-checkstyle-plugin</artifactId>
    <version>2.16</version>
</plugin>

The Checkstyle maven plugin has a lot of options to help configure the level of warnings and errors to include in the generated report. The plugin documentation has some good examples of adding extra configuration to customize the items that Checkstyle will check for you.

Generating the site

Now that we have all of our reports configured we can easily generate our maven site by running the following:

mvn clean test site

When the command is finished running you should have a directory under the target folder. This site folder contains all of the files needed to display the html website.

For a sample project that includes all of these reporting sections check out the repo on Github.


Code Quality Unravelled: Part 2


Comment

Why isn't Dropwizard validating objects in resource methods?

2 Comments

Why isn't Dropwizard validating objects in resource methods?

Dropwizard provides automatic validation of Jersey resource method parameters by simply adding the @Valid annotation. For example, in a method to save a new Person object, you might have code like:


@POST
public Response createPerson(@Valid Person person) {
    Person savedPerson = save(person);

    URI location = UriBuilder.fromResource(PersonResource.class)
            .path(savedPerson.getId().toString())
            .build();

    return Response.created(location).entity(savedPerson).build();
}

By adding @Valid to the person argument, Dropwizard ensures that the Person object will be validated using Hibernate Validator. The Person object will be validated according to the constraints defined on the Person class, for example maybe the @NotEmpty annotation is on first and last name properties. If the object passes validation, method execution continues and the logic to save the new person takes place. If validation fails, however, Dropwizard arranges for a 422 (Unprocessable Entity) response to be sent back to the client, and the resource method is never actually invoked. This is convenient, as it means you don't need any conditional logic in resource methods to manually check if an object is valid. Under the covers, Dropwizard registers its own custom provider class, JacksonMessageBodyProvider, which uses Jackson to parse request entities into objects and perform validation on the de-serialized entities.

Out of the Dropwizard box this automatic validation "just works" due to the above-mentioned JacksonMessageBodyProvider. (For this post, we are assuming Dropwizard 0.8.2, which uses Jersey 2.19) It worked for us just fine, until on one service it simply stopped working entirely. In other words, no validation took place and therefore any objects, valid or not, were being passed into resource methods. Since resource method code assumes objects have been validated already, this causes downstream problems. In our case, it manifested in HibernateExceptions being thrown when data accesss code tried to persist the (not validated) objects.

This was quite perplexing, and to make a (very long) debuggging story short, it turned out that someone had added one specific dependency in the Maven pom, which triggered auto-discovery of the JacksonFeature via the JacksonAutoDiscoverable. The dependency that had been added (indirectly, more on that later) was:


<dependency>
    <groupId>org.glassfish.jersey.media</groupId>
    <artifactId>jersey-media-json-jackson</artifactId>
    <version>2.19</version>
</dependency>

If you look in the jersey-media-json-jackson-2.19.jar file, there are only five classes. But the upshot is that this JAR specifies auto disovery for Jackson via the Auto-Discoverable Features mechanism, which causes the JacksonFeature class to register JacksonJaxbJsonProvider as both a MessageBodyReader and a MessageBodyWriter. And due to the vagaries of the way Jersey orders the message body readers, that provider ends up as the first available MessageBodyReader when processing requests, which in turn means the Dropwizard JacksonMessageBodyProvider never gets executed, and as a result no validation is performed!

For some code spelunking, check out the WorkerComparator class in MessageBodyFactory (in Jersey) which is used when sorting readers and writers via a call to Collections.sort(). The Javadoc for the comparator states "Pairs are sorted by distance from required type, media type and custom/provided (provided goes first)." In particular the last bit of that sentence is key, provided goes first - this means the auto-discovered feature (JacksonJaxbJsonProvider) takes precedence over the custom provider registered by Dropwizard (JacksonMessageBodyProvider).

Of course now that we know what is going on, the solution is pretty easy:

Make sure you don't have the jersey-media-json-jackson dependency, either directly or via a transitive dependency.

In our case it had actually come in via a Maven transitive dependency, which made tracking it down harder. An easy way to see exactly what dependencies exist and where they are coming from you can use mvn dependency:tree to display the entire dependency tree for your application.

Ultimately, while Jersey provides the auto disovery mechanism, I still prefer explicit configuration so it is very clear exactly what features are present. Dropwizard abstracts some of this from us, i.e. it registers the JacksonMessageBodyProvider in the AbstractServerFactory class (in the createAppServlet method), but since Dropwizard is mostly "just code" it is much easier to deterministically know what it is and isn't doing. So if you suddenly experience a lack of validation in Dropwizard, make sure the jersey-media-json-jackson dependency is not present. If that doesn't work, then you need to figure out what other MessageBodyReader is taking precedence, determine its origin, and eliminate it!

Sample code for this post can be found on GitHub.

2 Comments

Easily integrate Spring into Dropwizard

Comment

Easily integrate Spring into Dropwizard

Dropwizard is a really nice, simple framework for developing RESTful web services. Out of the box it comes with all kinds of nice features by leveraging a collection of mature libraries in a straightforward manner. The main libraries it uses are Jetty for web serving, Jersey for REST, Jackson for JSON processing, along with others such as Google Guava, Hibernate Validator, Liquibase for migrations, and more. It also comes with out of the box support for JDBI, a simple "SQL convenience library for Java", as well as Hibernate.

The JDBI and Hibernate support provide the basics needed to get going with either of those frameworks, and the Hibernate support provides the @UnitOfWork annotation for declarative transaction demarcation which you can apply to Jersey resource methods. Dropwizard then takes care of automatically opening a Hibernate session, starting a transaction, and either committing or rolling back that transaction and finally closing the session when the resource method completes. For many use cases this will be all you need or want.

In certain situations, though, you might want more control over transactions and for this Spring is often a good choice. Specifically, you might want to take an existing Spring backend codebase and integrate it into a RESTful web service using Dropwizard. Or maybe you are using Hibernate in a Dropwizard application but you don't want the transaction scoped around and entire resource method (which is what using the @UnitOfWork annotation gives you out of the box). Maybe you aren't using Hibernate and are using JDBI, Spring JDBC, or Spring JPA and want to use Spring's native @Transactional support. Or maybe you are not even using a relational database, and instead are using one of the Spring Data projects that support transactions in Neo4J, MongoDB, Redis, etc.

For situations like those described above, what you want is a simple way to use Spring in your Dropwizard application to manage transactions in DAOs or service classes, but not much else. In other words you don't need or want to use all the other many features provided in Spring, but you do want to take advantage of its automatic connection and transaction management. For Dropwizard projects I've been working on recently, we came up with a very simple abstraction and simple pattern to integrate Spring using a builder-style class to create application contexts. This makes Dropwizard configuration and other objects like ManagedDataSources available to the Spring context.

For example, suppose you will be using Hibernate in a Dropwizard application but want to use Spring to manage transactions. Suppose also that you configure a DataSourceFactory via the normal Dropwizard configuration mechanism and want that to be used by Spring when creating the Hibernate session factory. Also suppose you want the Dropwizard configuration object to be available to the Spring context. Assuming you have a simple "todo" Dropwizard application with a TodoApplication class and a TodoConfiguration class, you can write code like the following in the TodoApplication class:


@Override
public void run(TodoConfiguration configuration, Environment environment) throws Exception {
    DataSourceFactory dataSourceFactory = configuration.getDataSourceFactory();
    ManagedDataSource dataSource = dataSourceFactory.build(environment.metrics(), "dataSource");
 
    ApplicationContext context = new SpringContextBuilder()
            .addParentContextBean("dataSource", dataSource)
            .addParentContextBean("configuration", configuration)
            .addAnnotationConfiguration(TodoSpringConfiguration.class)
            .build();

    TodoDao todoDao = context.getBean(TodoDao.class);
    TodoResource todoResource = new TodoResource(todoDao);
    environment.jersey().register(todoResource);
}

In the above code, SpringContextBuilder is a very simple builder-style class that lets you create Spring application contexts by specifying parent beans that should be available to other beans (e.g. the "dataSource" and "configuration" beans), and then adding either annotation-based configuration classes or XML configuration file locations. This class is available on GitHub here.

The above code creates a Spring context from which you can then extract the beans, such as TodoDao, that will be used by Jersey resource classes. Note that we're not using Spring for autowiring dependencies in the TodoResource class, and are simply passing the DAO to its constructor. The resource class has no idea that the DAO is actually a Spring-managed bean, nor does it need to. This also makes it very easy to inject a mock into the resource class for unit tests.

So the only thing left to do is actually create the Spring application context. In the above code, we're using the TodoSpringConfiguration class which is a Java Config-based configuration class. The code below shows the basics, with a few details omitted:


@Configuration
@EnableTransactionManagement
@ComponentScan(basePackageClasses = TodoDao.class)
public class TodoSpringConfiguration {

    @Autowired
    private DataSource _dataSource;

    @Autowired
    private TodoConfiguration _configuration;

    @Bean
    public LocalSessionFactoryBean sessionFactory() {
        LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
        sessionFactory.setDataSource(_dataSource);
        sessionFactory.setPackagesToScan(Todo.class.getPackage().getName()));
        sessionFactory.setNamingStrategy(ImprovedNamingStrategy.INSTANCE);
        sessionFactory.setHibernateProperties(hibernateProperties());
        return sessionFactory;
    }

    @Bean
    @Autowired
    public HibernateTransactionManager transactionManager(SessionFactory sessionFactory) {
        return new HibernateTransactionManager(sessionFactory);
    }

    @Bean
    public PersistenceExceptionTranslationPostProcessor exceptionTranslation() {
        return new PersistenceExceptionTranslationPostProcessor();
    }

    private Properties hibernateProperties() {
        // details elided...
    }

}

As you can see, the TodoSpringConfiguration class is just a plain Spring Java-based configuration class. The only real noteworthy thing is how we used @Autowired to make the data source and Dropwizard configuration objects available. The sessionFactory() configuration method then simply uses the data source when constructing the session factory. Other beans could use the Dropwizard configuration object, for example to extract other configuration such as Hibernate-specific configuration properties.

That's really all there is to it. You just use SpringContextBuilder to create your Spring context, extract the beans you need, and pass them to Jersey resource classes or any other classes such as health checks. The classes using the Spring-managed beans can simply use them without needing to be aware of their Spring-managed nature. All of which helps keep your Dropwizard code clean while still gaining the advantage of powerful Spring features. The "todo" example application code is available on GitHub here along with instructions for building and running it.

Comment

Extjs Dynamic Stateful Grid

Comment

Extjs Dynamic Stateful Grid

Do you have a javascript MVC framework for your existing web application?  Tired of spending development cycles updating your application views every time your data provider sends something new?  Yeah, me too.  Thankfully there are a good number javascript MVC frameworks that can be used to display dynamic data AND save the display state of said data.

Recently I wanted to build such a web application that would display dynamic data key/value pairs as well as saving the user's view state of said data.  I’ve used the Extjs framework to build various web applications and I’ve been impressed with the Ext.grid.Panel’s features as well as the ease of creating ‘stateful’ components with the Extjs framework. While there isn’t anything directly out of the box that will allow you to create a dynamic stateful grid panel, the Extjs framework does give you the tools to build your own stateful grid component without modifying any of their source code.

My requirements consisted of:

  • Dynamically display json-based messages in a table (without knowing the json nested structure or key/value pairs of the data).
  • Allow for user to customize the table (sorting, grouping, columns displayed, column order, column width).
  • Save state of the user-customized table and reload state upon user return to web application.

Blog post assumptions:

  • You are somewhat familiar with Extjs.
  • You know how to setup an Extjs State Provider.
  • You are using a ‘standard’ Extjs store (I’m using an ajax proxy with my Extjs store).

Server Side Steps

Step 1: Flatten your result set.  The Dynamic Stateful Grid described in this post worked well with a flattened array of json objects.

Step 2: Define the 'dynamic columns' in your response to the client.

An Extjs grid will allow the grid columns to be defined dynamically.  To do this, the response to the Grid’s store must include a ‘metaData’ element which defines the fields and columns based on the data being served.  The ‘metaData’ element looks like this example:

{
  metaData: {
    columns: [
      {‘header’: key1, ‘dataIndex’: key1, ‘stateId’: key1},
      {‘header’: key2, ‘dataIndex’: key2, ‘stateId’: key2}
    ],
    fields: [
      {‘name’: key1, ‘type’: ‘string’},
      {‘name’: key2, ‘type’: ‘string’}
    ]
  },
  items: [
    {‘field1’: value1,  ‘field2’: value2, ‘field3’: value3},
    {‘field4’: value4,  ‘field5’: value5, ‘field6’: value6}
  ]
}

Client Side Steps

Step 1: Define a Dynamic Stateful Grid Panel (code below works with Extjs 4, 5, and 6)

When the Extjs grid sees the ‘metaData’ element in it’s store results, the ‘metachange’ event is fired.  We will need to override both the ‘metachange’ and ‘beforestatestave’ events to keep track of the columns shown/hidden, the column order, and the column widths.

Ext.define('Ext.grid.DynamicStatefulGridPanel', {
extend: 'Ext.grid.Panel',
alias: 'widget.dynamicstatefulgrid',
stateful: true,

initComponent: function() {
  var _this = this;
  this.on({
    beforestatesave: function(cmp, state, eOpts) {
      var savedState = Ext.state.Manager.getProvider().state[_this.stateId];

      // don't save a state of 0 columns shown
      if (state && state.columns.length === 0 && savedState && savedState.columns.length === 0) {
        return false;
      }

      // if the current state has 0 columns shown, replace it with the old state and 
      // return false to prevent saving an empty state
      if (state.columns.length === 0 && savedState && savedState.columns.length > 0) {
        state = savedState;
        return false;
      }

      // if a metachange event was fired previously, reset the metachangefired flag 
      // to prevent saving the incorrect state
      if (_this.store.metachangefired) {
        _this.store.metachangefired = false;
        return false;
      }

      // if new state and old state are present, update the column hidden and width attributes
      if (state && state.columns.length > 0 && savedState && savedState.columns.length > 0) {
        $.each(savedState.columns, function(index, savedColumn) {
          $.each.(state.columns, function(stateIndex, newColumn) {
            if (savedColumn['id'] === newColumn['id']) {
              if (savedColumn['hidden'] && newColumn['hidden'] != false) {
                newColumn['hidden'] = savedColumn['hidden'];
              }
              if (!newColumn['width'] && savedColumn['width']) {
                newColumn['width'] = savedColumn['width'];
              }
            }
          });
        });
      }
      _this.metachangefired = false;
    },
    beforereconfigure: function(cmp, store, columns, oldStore, oldColumns, eOpts) {
      _this.store.metachangefired = false;
    }
  });
  this.callParent(arguments);
},

bindStore: function(store) {
  var _this = this;
  //initialize the metachangefired flag on the store
  store.metachangefired = false;
  this.callParent(arguments);
  store.mon(store, 'metachange', function(store, meta) {
      store.metachangefired = true;

      // get the columns passed in on the metachange event. 
      // Note: these columns don't have the savedState applied yet
      var metaDataColumns = meta.columns;

      // initialze array to track saved state column order
      var reorderedMetaColumns = [];

      var provider = Ext.state.Manager.getProvider();
      var state = provider.state;

      // if a state is present for this grid (_this.stateId), update the columns 
      // with the saved state from the state provider
      if (state[_this.stateId]) {
        $.each(metaDataColumns, function(index, metaDataColumn) {
          $.each(state[_this.stateId]['columns'], function(stateIndex, stateColumn) {
            if (metaDataColumn['dataIndex'] === stateColumn['id']) {
              if (stateColumn['hidden']) {
                metaDataColumn['hidden'] = stateColumn['hidden'];
              }
              if (stateColumn['width']) {
                metaDataColumn['width'] = stateColumn['width'];

              }
            }
          });
        });
      }

      if (reorderedMetaColumns.length === 0) {
        reorderedMetaColumns = metaDataColumns;
      }

      // reconfigure the grid with the saved state applied to 
      // the dynamic metaData columns
      _this.reconfigure(store, reorderedMetaColumns);
    }
  }
});

Step 2: Instantiate your Stateful Grid Panel

var _this = this;
_this.store = Ext.create(‘My.ext.RecordStore’, {});
_this.grid = Ext.create(‘Ext.grid.DynamicStatefulGridPanel’, {
	store: _this.store,
	features: [{ftype: ‘grouping’}];
	columns: [],
	stateId: ‘my-dynamic-stateful-grid’
});

Step 3: Profit!

That's it!  Once your Extjs data Store is hooked up to a data source and your results are transformed into a flattened list structure, you'll be able to display whatever key/value pairs are present in the results set as well as save the user's state of columns displayed, column widths, column order, sorting and grouping.

 

Comment