Sunday, January 25, 2015

Spring by Example Update (1.5)


The Spring by Example site update is just a basic library update. All of the Spring by Example modules have also been published to the Maven repo. The site has been updated to use Spring Framework 4.1.x. Most other libraries have also been upgraded. Some major ones are Spring Integration 4.1.2, Spring Security 3.2.5, Spring Data JPA 1.7.1, and Hibernate 4.3.8.

Saturday, September 28, 2013

Dynamic REST Client & Controller Generation


Here is the example from SBE REST Modules on GitHub.

It let's you create an interface and put your Spring MVC REST annotations on it if you follow certain patterns. The REST client and controller can be generated using Spring beans. It's expected that your controllers would just delegate to a service layer, so the interface to that service can be registered using @RestResource on the class. The base URI and default response class are also defined here. By default, a method with @RequestMapping will expect the same method and method signature to be available to delegate to in the service class.

Another feature is to turn off creating a relative URI using the class' path, is to specify @RestRequestResource on a method and setting 'relative' to false. If the interface overloads a method to perform further conversion on the results, the method name can be specified. The framework still expects the method signatures to match. A converter can be specified to change the result before it's sent to the client. A good example of this is creating smaller models to match different needs without doing further customizations on the backend services or queries.

This all needs more work, but all of the basics are working now. I'll write this up soon on Spring by Example.



public interface PersistenceFindMarshallingService<R extends EntityResponseResult, FR extends EntityFindResponseResult> {

     public final static String PATH_DELIM = "/";
     public final static String PARAM_DELIM = "?";
     public final static String PARAM_VALUE_DELIM = "&";

     public final static String ID_VAR = "id";
     public final static String PAGE_VAR = "page";
     public final static String PAGE_SIZE_VAR = "page-size";

     public final static String PAGE_PATH = PATH_DELIM + PAGE_VAR;
     public final static String PAGE_SIZE_PATH = PATH_DELIM + PAGE_SIZE_VAR;
     public final static String PAGINATED = PAGE_PATH + PATH_DELIM + "{" + PAGE_VAR + "}" + PAGE_SIZE_PATH + PATH_DELIM + "{" + PAGE_SIZE_VAR + "}";

     public final static String ROOT_URI = PATH_DELIM;
     public final static String FIND_BY_ID_URI = PATH_DELIM + "{" + ID_VAR + "}";

     /**
      * Find by primary key.
      */
     @RequestMapping(value = FIND_BY_ID_URI, method = RequestMethod.GET)
     public R findById(@PathVariable(ID_VAR) Integer id);

     /**
      * Find a paginated record set.
      */
     @RequestMapping(value = PAGINATED, method = RequestMethod.GET)
     public FR find(@PathVariable(PAGE_VAR) int page, @PathVariable(PAGE_SIZE_VAR) int pageSize);

     /**
      * Find all records.
      */
     @RequestMapping(value = ROOT_URI, method = RequestMethod.GET)
     public FR find();

}


public interface PersistenceMarshallingService<R extends EntityResponseResult, FR extends EntityFindResponseResult, S extends PkEntityBase>
         extends PersistenceFindMarshallingService<R, FR> {

     public final static String DELETE_URI = ROOT_URI + "remove";

     /**
      * Save record.
      */
     @RequestMapping(value = ROOT_URI, method = RequestMethod.POST)
     public R create(@RequestBody S request);

     /**
      * Update record.
      */
     @RequestMapping(value = ROOT_URI, method = RequestMethod.PUT)
     public R update(@RequestBody S request);

     /**
      * Delete record.
      */
     // FIXME: server has marshalling error if DELETE
     @RequestMapping(value = DELETE_URI, method = RequestMethod.PUT)
     public R delete(@RequestBody S request);

}



@RestResource(service=ContactService.class, path=PATH, responseClass=PersonResponse.class)
public interface ContactMarshallingService extends PersistenceMarshallingService<PersonResponse, PersonFindResponse, Person> {

     final static String PATH = "/person-test";
     final static String SMALL_URI = "/small";
     final static String SMALL_PATH = "/small-person";

     public final static String SMALL_FIND_BY_ID_REQUEST = SMALL_PATH + PATH_DELIM + "{" + ID_VAR + "}";

     public final static String SMALL_FIND_PAGINATED_REQUEST = SMALL_PATH + PAGINATED;

     public final static String LAST_NAME_VAR = "lastName";
     public final static String LAST_NAME_PARAMS = PARAM_DELIM + LAST_NAME_VAR + "={" + LAST_NAME_VAR + "}";
     public final static String FIND_BY_LAST_NAME_CLIENT_REQUEST = PATH + LAST_NAME_PARAMS;
     public final static String SMALL_FIND_BY_LAST_NAME_CLIENT_REQUEST = PATH + SMALL_URI + LAST_NAME_PARAMS;

     @RequestMapping(value = SMALL_FIND_BY_ID_REQUEST, method = RequestMethod.GET)
     @RestRequestResource(relative=false, methodName="findById", converter=SmallContactConverter.class)
     public PersonResponse smallFindById(@PathVariable(ID_VAR) Integer id);

     @RequestMapping(value=SMALL_FIND_PAGINATED_REQUEST, method = RequestMethod.GET)
     @RestRequestResource(relative=false, methodName="find", converter=SmallContactConverter.class)
     public PersonFindResponse smallFind(@PathVariable(PAGE_VAR) int page, @PathVariable(PAGE_SIZE_VAR) int pageSize);

     @RequestMapping(value = PATH_DELIM, method = RequestMethod.GET, params= { LAST_NAME_VAR })
     public PersonFindResponse findByLastName(@RequestParam(LAST_NAME_VAR) String lastName);

     @RequestMapping(value = SMALL_URI, method = RequestMethod.GET, params= { LAST_NAME_VAR })
     @RestRequestResource(methodName="findByLastName", converter=SmallContactConverter.class)
     public PersonFindResponse smallFindByLastName(@RequestParam(LAST_NAME_VAR) String lastName);

}


Spring by Example Update (1.3)


I've been working on this off and on for a long time, but I finally have a Spring by Example site update ready. It's using Spring Framework 3.2.x and most major libraries are all upgraded. The biggest changes are to the Contact Application, which now references SBE REST Modules (available on Spring by Example's GitHub), which I will document on the site shortly. The Contact Application also has a better production ready DB connection pool configuration and upgrades the Jackson JSON mapper & view.

I did also try to create a shared base for messages/response for the JAXB beans, but I ran into some issues generating them in the Contact Application. The Fluent API doesn't look at parent classes when generating the '.withXXX' methods. It does look like it would be simple to customize the JAXB plugin to fix this, but I didn't want to take the time right now.

Below are the Contact Application modules.
  • DAO - DB Schema, JPA Entities, Spring Data JPA repositories.
  • Web Service Beans - JAXB beans generated from XSDs.
  • Services - APIs use JAXB beans and Dozer is used to convert between this beans and the JPA entities. Security and transactions are configured in this layer.
  • REST Services - The module has clients & controllers, as well as their Spring configurations. JSON and XML views are supported for requests.
  • Webapp - The webapp as a standard JSP UI, Sencha ExtJS, and also a Sencha Touch UI.
  • Test - The DAO, Services, and REST Services all have an abstract test class for each module that each test extends. This way within each module, all tests have a shared context so Spring only has to load once. All of these tests use an in memory database and the REST Services have an embedded jetty server. REST Services tests can be run with clients using JSON or XML for marshalling.


Sunday, November 11, 2012

Contact Application on Spring by Example

I've just updated Spring by Example to have a multi-module Contact Application. It's meant to be an example of an architecture pattern to follow for larger applications.

The DAO module has the DB schema, JPA entities, and uses Spring Data JPA repositories. It also has a Spring Profile for HSQL DB and PostgreSQL. The tests and webapp default to using the in memory database and PostgreSQL is meant to be used in the production deployment of the webapp.

The WS Beans module are JAXB beans generated from XSDs. They are generated to have a fluent API (ex: new Person.withId(1).withFirstName("John")) and are meant to provide an easy way to create different models for external APIs that can be easily serialized to JSON & XML.

The Services module uses the WS Beans (JAXB Beans) for it's main model and for any APIs. It is meant to be the layer where all business logic is located. It converts to and from the JPA entities & WS Beans using Dozer. It configures Spring's transactional support and Spring Security.

REST Services exposes the Services layer and provides clients & controllers for all REST APIs. All APIs can be exposed over JSON & XML. It has the standard JSON media type and a custom one that also includes class information for more complex data models.

The contact webapp has a standard JSP UI, Sencha ExtJS, and also a Sencha Touch UI.

There is a shared test module to keep inter-module dependencies less complex. The DAO, Services, and REST Services layer each have an abstract test base that creates a shared test context. The DAO and Services both create an in memory DB, and use Spring's transactional test framework to rollback transactions after each test runs. The REST Services module creates an in memory DB and an embedded Jetty instance. The embedded Jetty has it's own Spring context, separate from the test one. The embedded Jetty context loads as much as possible of the production Spring configuration, and the test context just loads the REST clients. You may also want to look at Spring Test MVC for testing controller, but the Contact Application approach runs very quickly and is really a full integration test.

Friday, August 24, 2012

Spring Data JPA Examples on Spring by Example

I've just updated Spring by Example to have two Spring Data JPA examples. One has basic repository use and shows how to make some custom queries, and the other one shows how to use Spring Data JPA auditing. All of the main webapps using JPA were updated to use Spring Data JPA and all JPA examples were updated to use Hibernate 4.1. I ran into issues updating the Hibernate template examples, so they will stay on Hibernate 3.6.

There were other miscellaneous updates. The AJAX tiles work in the web flow examples wasn't working so I removed it for now.

I'll be continuing to work on REST and UI examples, using ExtJS and Sencha Touch.

Saturday, August 18, 2012

SpringOne & Spring By Example Update

I've just updated Spring by Example to Spring 3.1 and Java 6. The new Spring by Example Repository is on GitHub.

Here are some general comments about the latest release. The Maven group and artifact IDs have been changed back to standard Maven naming and also all project dependencies (no OSGi ones in standard examples). The project is now one large multi-module project structure to make it easier to maintain and upgrade releases. Originally the goal was to have standalone projects so it was easier for someone to get started, but I don't have time to maintain the project this way anymore.

The GWT examples have been removed from the documentation and are not in git. If I have time in the future I may add them back, but I'll be focusing on some standard JS UI examples. The Spring dm Server (OSGi) examples have not been updated, but are still available in the documentation. They still located in Subversion since I won't actively try to maintain them anymore. Spring by Example JDBC has been removed and all projects (except for OSGi ones) have been changed to use the Spring JDBC Custom Namespace. Between the this namespace and Spring Data JPA, nothing is really necessary in this module. I'll be starting on a Spring Data JPA example next week.

Friday, January 20, 2012

Spring JAXB with CDATA Elements


I wanted to configure the Jaxb2Marshaller to support CDATA elements. This example works with Spring 3.1 and Java 6, using the Java 6 restricted XML parser classes to configure the CDATA elements.



<bean id="marshaller" class="org.springbyexample.marshaller.CdataJaxb2Marshaller">
     <property name="cdataElements">
         <list>
             <value>^value</value>
         </list>
     </property>
...


import java.io.IOException;
import java.io.OutputStream;

import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.transform.Result;
import javax.xml.transform.stream.StreamResult;

import org.apache.commons.lang.ArrayUtils;
import org.springframework.oxm.MarshallingFailureException;
import org.springframework.oxm.XmlMappingException;
import org.springframework.oxm.jaxb.Jaxb2Marshaller;
import org.springframework.oxm.mime.MimeContainer;


public class CdataJaxb2Marshaller extends Jaxb2Marshaller {
    
    private String[] cdataElements;
    
    public String[] getCdataElements() {
        return cdataElements;
    }

    public void setCdataElements(String[] cdataElements) {
        this.cdataElements = cdataElements;
    }
    
    @Override
    public void marshal(Object graph, Result result, MimeContainer mimeContainer) throws XmlMappingException {
        if (ArrayUtils.isNotEmpty(cdataElements)) {
            try {
                Marshaller marshaller = createMarshaller();
    
                com.sun.org.apache.xml.internal.serialize.XMLSerializer serializer =
                        createXMLSerializer(cdataElements, ((StreamResult)result).getOutputStream());
                marshaller.marshal(graph, serializer.asContentHandler());
            } catch (IOException e) {
                throw new MarshallingFailureException(e.getMessage(), e);
            } catch (JAXBException ex) {
                throw convertJaxbException(ex);
            }
        } else {
            super.marshal(graph, result);
        }
    }

    @SuppressWarnings("restriction")
    private com.sun.org.apache.xml.internal.serialize.XMLSerializer createXMLSerializer(String[] cDataElements, OutputStream cOut) {
        // This code is from a sample online: http://jaxb.java.net/faq/JaxbCDATASample.java
        // configure an OutputFormat to handle CDATA
        com.sun.org.apache.xml.internal.serialize.OutputFormat of = new com.sun.org.apache.xml.internal.serialize.OutputFormat();

        // specify which of your elements you want to be handled as CDATA.
        // The use of the '^' between the namespaceURI and the localname
        // seems to be an implementation detail of the xerces code.
        // When processing xml that doesn't use namespaces, simply omit the
        // namespace prefix as shown in the third CDataElement below.
        of.setCDataElements(cDataElements); //

        // set any other options you'd like
        of.setPreserveSpace(true);
        of.setIndenting(true);
        of.setPreserveSpace(false);

        // create the serializer
        com.sun.org.apache.xml.internal.serialize.XMLSerializer serializer = new com.sun.org.apache.xml.internal.serialize.XMLSerializer(of);
        serializer.setOutputByteStream(cOut);

        return serializer;
    }

}