Showing posts with label spring mvc. Show all posts
Showing posts with label spring mvc. Show all posts
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);
}
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.
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.
Labels:
i18n,
jaxb,
jpa,
json,
spring by example,
spring data jpa,
spring mvc,
spring security
Saturday, August 2, 2008
Simple GWT Spring Webapp Example
I finally had time to finish up the GWT example I've been working on. I may add a bit more to it in the future (like an autocomplete search widget), but there is a lot there and should be helpful getting people started integrating GWT with Spring.
My general impression of using GWT is positive. It's definitely nice that you can work in Java and all the JavaScript is generated for you. Especially that it should perform well and work in different browers. It's a little tedious keeping everything in sync between the static servlet I use when running GWT in debug mode (avoids integrating Spring and injection into GWT debug) and the Spring controller. Also to have GWT use the main message resource for internationalization, a JavaScript object has to be maintained on the static HTML and JSP page (GWT debug vs. Spring webapp). It also wasn't obvious that if I made a bean to use with the main GWT entry point class that it had to be in the same package or a subpackage.
Also, as a side note, I have the person form working using Spring JS. It isn't too much work once I realized that it was working, but Spring Web Flow was refreshing the page afterword until I added at the end of the 'save' transition a render element specifying to only update the 'content' fragment. Otherwise only the 'onclick' attribute of the save button has to be modified.
<input type="submit" id="save"
name="_eventId_save" value="<fmt:message key="button.save">"
onclick="Spring.remoting.submitForm('save', 'person', {fragments:'content'}); return false;"/>
You currently can't specify a target div for returned fragments. I filed a JIRA (Enhance AjaxEventDecoration in Spring JS to specify a Target Div) to allow a targetId. Ideally it will be a list like fragments so you can specify what fragment goes with what div. I wanted to be able to have menu links refresh the content div without refreshing the entire page. Current funtionally assumes that you would just use AJAX for updates within functional areas. Like, I'm on the person page and want to just update the messages and personForm divs with fragments. So the fragment is expected to be wrapped in the div that is already is on the page and they are automatically matched up and updated. I think it will be a useful feature and hopefully will get added in an upcoming release.
My general impression of using GWT is positive. It's definitely nice that you can work in Java and all the JavaScript is generated for you. Especially that it should perform well and work in different browers. It's a little tedious keeping everything in sync between the static servlet I use when running GWT in debug mode (avoids integrating Spring and injection into GWT debug) and the Spring controller. Also to have GWT use the main message resource for internationalization, a JavaScript object has to be maintained on the static HTML and JSP page (GWT debug vs. Spring webapp). It also wasn't obvious that if I made a bean to use with the main GWT entry point class that it had to be in the same package or a subpackage.
Also, as a side note, I have the person form working using Spring JS. It isn't too much work once I realized that it was working, but Spring Web Flow was refreshing the page afterword until I added at the end of the 'save' transition a render element specifying to only update the 'content' fragment. Otherwise only the 'onclick' attribute of the save button has to be modified.
<input type="submit" id="save"
name="_eventId_save" value="<fmt:message key="button.save">"
onclick="Spring.remoting.submitForm('save', 'person', {fragments:'content'}); return false;"/>
You currently can't specify a target div for returned fragments. I filed a JIRA (Enhance AjaxEventDecoration in Spring JS to specify a Target Div) to allow a targetId. Ideally it will be a list like fragments so you can specify what fragment goes with what div. I wanted to be able to have menu links refresh the content div without refreshing the entire page. Current funtionally assumes that you would just use AJAX for updates within functional areas. Like, I'm on the person page and want to just update the messages and personForm divs with fragments. So the fragment is expected to be wrapped in the div that is already is on the page and they are automatically matched up and updated. I think it will be a useful feature and hopefully will get added in an upcoming release.
Labels:
gwt,
spring by example,
spring framework,
spring mvc,
tiles
Sunday, July 27, 2008
Spring by Example JDBC, Spring Dynamic Tiles Module, and Google Web Toolkit
I almost have everything ready to post a simple Google Web Toolkit (GWT) example with Spring. I have everything checked in and I also had to update Spring by Example JDBC and Spring by Example Dynamic Tiles Module.
Spring by Example JDBC version 1.0.3 was upgraded to match changes in Spring 2.5.5 and HsqldbInitializingDriverManagerDataSource & InitializingDriverManagerDataSource were changed to use SimpleDriverDataSource (which was added in Spring 2.5.5).
Spring by Example Dynamic Tiles Module 1.1 has a number of changes. The project was updated to use Spring 2.5.5, AJAX support like Spring JS and Spring Web Flow is provided, and a few other items (see release notes in the projects README.txt). Also, Spring by Example Dynamic Tiles Module 1.0 was compiled with Java 6, but should have been compiled with Java 5. The projects are basically all ready, but just need a little more testing. Although everything is checked into Subversion.
I'll try to get everything finalized and checked in over the next couple of days. I think the AJAX support that works with Spring JS' Spring.remoting.submitForm is great. It's really nice to have simple and easy control over updating specific div sections that match parts of a Tiles template.
Spring by Example JDBC version 1.0.3 was upgraded to match changes in Spring 2.5.5 and HsqldbInitializingDriverManagerDataSource & InitializingDriverManagerDataSource were changed to use SimpleDriverDataSource (which was added in Spring 2.5.5).
Spring by Example Dynamic Tiles Module 1.1 has a number of changes. The project was updated to use Spring 2.5.5, AJAX support like Spring JS and Spring Web Flow is provided, and a few other items (see release notes in the projects README.txt). Also, Spring by Example Dynamic Tiles Module 1.0 was compiled with Java 6, but should have been compiled with Java 5. The projects are basically all ready, but just need a little more testing. Although everything is checked into Subversion.
I'll try to get everything finalized and checked in over the next couple of days. I think the AJAX support that works with Spring JS' Spring.remoting.submitForm is great. It's really nice to have simple and easy control over updating specific div sections that match parts of a Tiles template.
Labels:
gwt,
spring by example,
spring framework,
spring mvc,
tiles
Saturday, May 24, 2008
Simple Form using Spring MVC Annotations
I just posted a new example using Spring 2.5's new MVC annotations. The new annotations seem pretty nice. I'm not always for tightly coupling configuration with annotations, but in this case most controllers normally have a single purpose. Plus, you still have the XML configuration as an option if a looser coupling between controller and mappings is needed.
The webapp has basic create, update, delete, and search functionality for a person form. The form basically just has a hidden id (primary key), first name, and last name fields. The example uses Spring MVC annotations, Tiles 2 with Spring By Example's Dynamic Tiles Spring MVC Module, Hibernate configured with annotations and used in Spring with a Hibernate template, and internationalized messages. So a requested URL will go through a controller if there is a mapping, data may be put in place for the page to render, message resources are also put into scope for the page to render, and finally the Dynamic Tiles view renderer takes the request URL and uses it as the body of a default Tiles template. The example is released under the Apache 2.0 license.
The @RequestMapping annotation is used to indicate what requests a method should handle and can handle Ant style patterns to match a request (not shown below). The @RequestParam annotation can be used to have a request parameter automatically mapped to a variable in the method signature. Or a class can be specified, like in the save method, and any values from the request that can be bound to an instance of the class will be and that instance will be passed into the method.
The create method shows that an Object returned will automatically put into the model based on the classes' name (Person.class --> 'person').
Excerpt from Controller
The webapp has basic create, update, delete, and search functionality for a person form. The form basically just has a hidden id (primary key), first name, and last name fields. The example uses Spring MVC annotations, Tiles 2 with Spring By Example's Dynamic Tiles Spring MVC Module, Hibernate configured with annotations and used in Spring with a Hibernate template, and internationalized messages. So a requested URL will go through a controller if there is a mapping, data may be put in place for the page to render, message resources are also put into scope for the page to render, and finally the Dynamic Tiles view renderer takes the request URL and uses it as the body of a default Tiles template. The example is released under the Apache 2.0 license.
The @RequestMapping annotation is used to indicate what requests a method should handle and can handle Ant style patterns to match a request (not shown below). The @RequestParam annotation can be used to have a request parameter automatically mapped to a variable in the method signature. Or a class can be specified, like in the save method, and any values from the request that can be bound to an instance of the class will be and that instance will be passed into the method.
The create method shows that an Object returned will automatically put into the model based on the classes' name (Person.class --> 'person').
Excerpt from Controller
@Controller
public class PersonController {
final Logger logger = LoggerFactory.getLogger(PersonController.class);
private static final String FORM_VIEW_KEY = "/form/person";
private static final String SEARCH_VIEW_KEY = "/search/person";
private static final String FORM_MODEL_KEY = "person";
private static final String SEARCH_MODEL_KEY = "persons";
/**
* Creates a person object and forwards to person form.
*/
@RequestMapping(value="/form/person.html")
public Person create() {
return new Person();
}
/**
* Gets a person based on it's id.
*/
@RequestMapping(value="/info/person.html")
public ModelAndView info(@RequestParam("id") Integer id) {
Person result = personDao.findPersonById(id);
return new ModelAndView(FORM_VIEW_KEY,
FORM_MODEL_KEY,
result);
}
/**
* Saves a person.
*/
@RequestMapping(value="/save/person.html")
public ModelAndView save(Person person) {
personDao.save(person);
return new ModelAndView(FORM_VIEW_KEY,
FORM_MODEL_KEY,
person);
}
...
/**
* Searches for all persons and returns them in a
*Collectionas 'persons' in the
*ModelMap.
*/
@RequestMapping(value="/search/person.html")
public ModelMap search() {
Collection<Person> lResults = personDao.findPersons();
return new ModelMap(SEARCH_MODEL_KEY, lResults);
}
}
Labels:
spring by example,
spring framework,
spring mvc
Subscribe to:
Posts (Atom)