Вы находитесь на странице: 1из 12

Fornax-Platform : 5.

1 Web CRUD GUI Tutorial (CSC)


This page last changed on Feb 01, 2009 by patrik_nordwall.

5. CRUD GUI Tutorial (CSC)

5.2 Rich Client CRUD GUI Tutorial (CSC)

Web CRUD Gui Tutorial


This tutorial describes how to generate, customize and use the CRUD gui, and it will use the Helloworld example application as base. I.e. we start out with the Hello World model:

Application Universe { basePackage=org.helloworld Module milkyway { Service PlanetService { String sayHello(String planetName); protected findByExample => PlanetRepository.findByExample; } Entity Planet { String name key; String message; Repository PlanetRepository { findByExample; } } } }

For guidance on environment setup, see the Archetype tutorial. There are two different implementations for the web project: Spring webflow with pure JSP Spring webflow with JSF and Facelets Note that you use different archetype for creating the chosen variant. Explained in Archetype tutorial. Some of the descriptions in this tutorial only apply to one of the variants, when that is the case it is marked with JSP only and JSF only. Table of content: Web CRUD Gui Tutorial Tutorial - Complete JUnit tests - Redesign of model - GUI model

Document generated by Confluence on Feb 01, 2009 16:20

Page 1

- Custom logic - Custom generation Explanations - What's generated? - JSP only - JSF only - Java - Configuration - CSS and themes - Internationalization - Required fields - Property Editors (JSP only) - Error handling - Hibernate Session Resources - Source - More Advanced Example - Links So, before you start, set up the project structure, import to eclipse, apply an initial model, generate code, etc, all according to Archetype tutorial.

Tutorial
Complete JUnit tests
For the business tier project see part 3 of the Hello World Tutorial. To complete the web projects JUnit tests: 1. Run CreatePlanetTest as JUnit Test. Red bar. Adjust the method populateFormSuccess to make it populate required fields:

@Override protected void populateFormSuccess(MockParameterMap parameters) { parameters.put("name", "Earth"); parameters.put("message", "Hello from Earth"); }

Adjust the method populateFormError to do like this:

@Override protected void populateFormError(MockParameterMap parameters) { parameters.put("name", ""); parameters.put("message", ""); }

2. Run. Green bar!

Document generated by Confluence on Feb 01, 2009 16:20

Page 2

3. Do the same for the rest of the failing tests.

Redesign of model
Ok, so, according to the Hello World Tutorial, the model looks like above. The problem with the Hello world model (see above) is that the model isn't going to be very interesting to run the CRUD GUI with. We need at least one more entity and we need some services to do operations on them (for more info about what's generated, see the Explanations part). 1. So, in step one we do a redesign of our model to look something like this:

Application Universe { basePackage=org.helloworld Module milkyway { Service PlanetService { String sayHello(String planetName); protected findByExample => PlanetRepository.findByExample; } Entity Planet { scaffold String name key; String message; Integer diameter nullable; Integer population nullable; - Set<@Moon> moons opposite planet; Repository PlanetRepository { findByExample; findByKeys; save; } } Entity Moon { not aggregateRoot // belongs to Planet Aggregate String name key; Integer diameter nullable; - @Planet planet opposite moons; } } }

If you use the scaffold key word on an entity, you automatically get CRUD services for that entity 2. Re-generate the code in both the business tier project and the web project You can save a lot of time by just generate source instead of doing a full build, i.e. maven install, run the external maven task mvn-generate-sources 3. Fix failing test once more 4. Recreate the database (as described here) 5. Deploy the application to the server (as described here) 6. Try and run the CRUD GUI on http://localhost:8080/helloworld-web. Click on the 'Create planet' menu

Document generated by Confluence on Feb 01, 2009 16:20

Page 3

choice and create a planet, and possible, create an optional Moon in conjunction to the Planet. When the planet is created, you can browse the complete list of Planets by clicking on the 'List all planets' menu choice. It isn't harder

GUI model
Ok, as explained in the 5.3 DSL for GRUD GUI section, you can customize the client code generation by adding a gui model. Lets say that we want to: give our client a specific name customize how we list our planets disable the possibility to edit the diameter of a planet Open the model.guidesign file and add something like:

import 'platform:/resource/sculptor-helloworld/src/main/resources/model.design' gui TheFarFarAwayClient for Universe { Module for milkyway { ListTask for Planet { name diameter population } UpdateTask for Planet { name population } } }

Custom logic
Ok, so, now we have a running application, but, it is a very general one. It doesn't contain any specific logic. Let's assume that we have a use case stating that:

"When a Planet is created, none of its Moons can have a diameter bigger than the planets"

Ok, what to do about that one? Well, we decide that we will implement this as a validation in the client (perhaps not the best place, but for this example it will do). 1. Start by creating the CreatePlanetValidatorTest JUnit test case, and put it in the src/test/java source directory

Document generated by Confluence on Feb 01, 2009 16:20

Page 4

2. We implement the test as if we expect the validation to produce an error:

public void testValidatePlanet() { // the form bean that is up for validation CreatePlanetForm form = new CreatePlanetForm(); form.setName("FooPlanet"); form.setDiameter(1000); Moon moon = new Moon("BarMoon"); moon.setDiameter(2000); form.addMoon(moon); BindException errors = new BindException(form, "createPlanetForm"); // the test target CreatePlanetValidator validator = new CreatePlanetValidator(); validator.validatePlanet(form, errors); assertTrue(errors.hasErrors()); }

3. Run the test. Red bar.

4. Implement the logic in CreatePlanetValidator as:

@Override public void validatePlanet(CreatePlanetForm form, Errors errors) { Planet planet = form.toModel(); int planetDiameter = planet.getDiameter(); Collection<Moon> moons = planet.getMoons(); for (Moon moon : moons) { if (planetDiameter <= moon.getDiameter()) { errors.reject("createPlanet.faultyDiameter"); } } }

5. Run the test again. Green bar!

6. Deploy and integration test it in the application.

Since you haven't change the structure of the CreatePlanetValidator you don't need to restart the server and you don't need to copy the class, the debugger will do that for you

Custom generation
Alright, we have the application up and running. We have added some logic so it performs as we want. But, are we satisfied with how it behave...? Doesn't we think that those pesky 'confirm'-steps are really annoying? Lets do something about it. And lets do it in a way that will stay, and will apply on future changes to the model. We change the generation so that we just makes a browser round trip and immediate redirects to the next step. So, whats the easiest way of manipulate the generation without checking out the source? In src/main/resources/templates there are a file called WebSpecialCases.xpt. It is a hook into the generation and here we can add generation logic. And in our case we will add an advice that will short

Document generated by Confluence on Feb 01, 2009 16:20

Page 5

circuit the generation of the confirmation page and instead have our own version of it (JSP only):

IMPORT sculptormetamodel IMPORT sculptorguimetamodel EXTENSION extensions::helper EXTENSION extensions::properties EXTENSION extensions::guihelper

AROUND templates::web::SpringWebflowCrudGuiFlowJsp::createUpdateFlowJspConfirmInc FOR UserTask FILE "WEB-INF/jsp/generated/" + module.name + "/" + name + "Confirm.inc" TO_GEN_WEBROOT <script type="text/javascript"> document.location="this.module.application.name.htm?_flowExecutionKey=${flowExecutionKey}& </script> ENDFILE ENDAROUND

Thats it!

The above is an example of custom generation through an advice. Of course are there better ways of accomplish the above task.

Explanations
What's generated?
In short, a fully functional CRUD web application, based on spring webflow. In this version of sculptor there aren't any gui specific parts in the DSL(this will probably change in the future), so the whole application is generated based on the same model as for the business tier application (you know, the one with the services etc). Besides that there are generation rules that looks at the service api and decides what to generate based on how they are formed. For instance, the menu is completely driven from what services an entity have available. If an entity have a 'save' service, the 'Create' menu option for that entity will be generated.

JSP only

Look inside the web project and drill down to src/main/webapp/WEB-INF/jsp to see all jsp's. There are some general pages like index.jsp, error.jsp etc, and then there are all domain object specific ones. Each module has a separate subfolder with pages, and each page has two parts. One that is always regenerated, and one that is generated once. The ones that are always generated are located under the generated sub folder. So, for example, the create planet input page is a composite of these parts: jsp/genereated/header.inc jsp/milkyway/createPlanetForm.jsp jsp/generated/milkyway/createPlanetForm.inc

Document generated by Confluence on Feb 01, 2009 16:20

Page 6

jsp/generated/footer.inc And here, the changeable part is the createPlanetForm.jsp page.

JSF only

Look inside the web project and drill down to src/main/webapp/WEB-INF/jsf to see all xhtml's. There are some general pages like index.xhtml, error.htmletc, and then there are all domain object specific ones. Each module has a separate subfolder with pages, and each page has two parts. One that is always regenerated, and one that is generated once. The ones that are always generated are located under the generated sub folder. So, for example, the create planet input page is a composite of these parts: jsf/genereated/header.inc jsf/milkyway/createPlanet.xhtml jsf/generated/milkyway/createPlanet.inc jsf/generated/footer.inc

And here, the changeable part is the createPlanet.xhtml page.

Java

As sculptor always does, classes are generated with a base class (always regenerated), and a concrete class (generated once) where you can add your own logic. For each domain object there are webflow implementation classes generated: Action - each CRUD method gets its own action where the logic resides Form - each CRUD method gets its own data carrier Validator - each CRUD method gets its own validator Property editor registrar - each CRUD method gets its own property editor registrar (JSP only)

Configuration

As always with spring there is a lot of configuration. The positive side here is that sculptor generates it for you. There are some application specific configuration files: web.xml - the traditional servlet container configuration, here with a predefined webflow front controller applicationContext.xml - the traditional spring configuration file [application-name]-servlet-config.xml - the spring front controller configuration file [application-name]-webflow-config.xml - the webflow framework configuration file

Document generated by Confluence on Feb 01, 2009 16:20

Page 7

Beside these application wide configuration files, each domain object CRUD method has it own flow configuration file and own bean definition file. These reside in WEB-INF/flows. For instance, the planet domain object has: Create create-Planet-flow.xml create-Planet-beans.xml Read view-Planet-flow.xml view-Planet-beans.xml Update update-Planet-flow.xml update-Planet-beans.xml Delete delete-Planet-flow.xml delete-Planet-beans.xml And besides the CRUD operations there is an additional flow: List list-Planet-flow.xml list-Planet-beans.xml

CSS and themes

Sculptor will generate a default style and a default spring theme. The style (CSS) will apply some color and position to the application. The positioning is best illustrated with a picture:!layout.gif! The generated style sheet is located in src/main/webapp/themes/basic/style.css and there is a generated property file for the default theme src/generated/resources/theme.properties. To add your own theme you add a corresponding properties file in src/main/resources/ and define your own style sheet.

css=/themes/basic/blue.css

The theme of the user is changed when the request contains a theme parameter. E.g. of links to switch theme:

<a href="index.htm?theme=theme"><spring:message code="theme.default" text="Olive theme" /></a> &nbsp;&nbsp;&nbsp; <a href="index.htm?theme=blue"><spring:message code="theme.blue" text="Blue theme" /></a>

Internationalization
All texts can be defined in resource bundles. Sculptor generates resource bundles with suggestions of

Document generated by Confluence on Feb 01, 2009 16:20

Page 8

English texts based on the names in the DSL model. You can copy these to src/main/resources/i18n when you localize to a specific language. The generated files are located in src/generated/resources/i18n. The texts are separated in 2 files plus 1 for each module: defaultMessages_en.properties messages_en.properties milkywayMessages_en.properties Date format is defined in the messages resource bundle.

format.YearMonthDayPattern=yyyy-MM-dd format.DateTimePattern=yyyy-MM-dd HH:mm

A special feature that is useful when the domain model evolves. Sculptor will display ??? at the texts in the gui that are not defined in a resource bundle, if you add the following to src/main/resources/sculptor-gui-generator.properties and regenerate.

gui.highlightMissingMessageResources=true

The locale of the user is changed when the request contains a locale parameter. E.g. of links to switch language:

<a href="index.htm?locale=en"><spring:message code="language.en" text="English" /></a> &nbsp;&nbsp;&nbsp; <a href="index.htm?locale=sv_SE"><spring:message code="language.sv" text="Svenska" /></a>

Required fields
Required fields are derived from the model. For attributes the rule are that if the attribute are 'nullable', the form field isn't required. For references we have two cases, 'one' and 'many' references. For 'one' references the same rule as for attributes apply, i.e. if the reference is 'nullable' it isn't required in the gui. For 'many' references the 'nullable' keyword isn't available in the model. Here we have decided to use the 'required' model keyword instead. It isn't its original purpose but it solves the problem until we have the means for modelling whats required in the gui. So, for 'many' references, if the reference has the keyword 'required' in the model, the gui validates the reference that the Collection holding the instances has at least one element.

Property Editors (JSP only)


If you try to enter a non digit in the diameter field of a Planet you will see an error message. PropertyEditorsare used for conversion of textual input to object values and conversion of object values to display format. Default PropertyEditors are used for common types such as dates and numbers. You can easily define your own property editor for a specific field by overriding registerCustomEditors in the registrar class. For example in CreatePlanetPropertyEditorRegistrar:

Document generated by Confluence on Feb 01, 2009 16:20

Page 9

@Override public void registerCustomEditors(PropertyEditorRegistry registry) { super.registerCustomEditors(registry); registry.registerCustomEditor(Integer.class, "diameter", new PositiveNumberEditor()); }

In the registrar class the message resources are available with the getMessagesAccessor method, which can be useful for defining locale aware settings, e.g. date pattern. Another approach is to define the PropertyEditor in the code generator properties. This is the way Sculptor defines the editors for date and time types. You can define your own DSL type like this:

javaType.PositiveInteger=Integer db.mysql.type.PositiveInteger=INTEGER

propertyEditor.PositiveInteger=org.myframework.propertyeditor.PositiveIntegerEditor

This means that you can define Planet with PositiveInteger types and don't have to add any code in registrar classes:

Entity Planet { scaffold String name key; String message; PositiveInteger diameter nullable; PositiveInteger population nullable; - Set<@Moon> moons opposite planet;

Error handling
Two types of exceptions, ApplicationException and SystemException, are used as described in Advanced Tutorial. Error messages for these two types of error situations are displayed in different ways. ApplicationException When an ApplicationException occurs a message is displayed in the application pages. It is displayed in the same place as validation error messages. Let us simulate an ApplicationException. The findById method throws PlanetNotFoundException, which is an ApplicationException, when a requested Planet doesn't exist. Click on List all Planets, right click on the id link of one Planet, and select Copy Link Location. Past into the browser URL field, but modify the last id request parameter, e.g. &id=999. When you try to load that page an error is displayed. The actual error message can be defined in messages resource bundle. The errorCodeof the ApplicationException is used as key to the resource bundle. Parameters of the exception can be used as message resource parameters.

org.helloworld.milkyway.exception.PlanetNotFoundException=Planet with id {0} doesn't exist.

Document generated by Confluence on Feb 01, 2009 16:20

Page 10

The error handling for ApplicationException is done by an advice that intercepts all methods in the actions. It catch exceptions and bind the error message to the form errors object. OptimisticLocking When two users try to update the same entity simultaneously an OptimisticLockingException is thrown. This exception is treated in the same way as ApplicationException, i.e. error message on the same page. SystemException A separate error page is displayed when a SystemException or any other unexpected RuntimeExceptionoccurs. Normally it is only necessary to display a general error message, but it is possible to define individual error messages for specific exceptions. The errorCode in the SystemException or the name of the RuntimeException is used as key in the resource bundle.

org.fornax.cartridges.sculptor.framework.errorhandling.SystemException=System error ({0}), <br/>caused by: {1} org.fornax.cartridges.sculptor.framework.errorhandling.DatabaseAccessException=System error. Database problem.

The fault barrier is implemented with a Spring exception resolver that directs to error.jsp, which resolves and displays the error message.

Hibernate Session
The GRUD Gui application uses a kind of Extended Session pattern for long Conversations. The same Hibernate session is used during a conversation, i.e. from a top flow and throughout its subflows, but it is disconnected in between each request. In practice this means that lazy loading of collections is possible when traversing associations. However, all root objects are retrieved with service methods and transactional services perform all updating operations. The transaction boundary is still at the service layer, but the DomainObjects are not detached when used in the presentation tier. Have a look at the JavaDoc and source code of OpenHibernateSessionInConversationListener to learn more about the implementation details.

Resources
Source
The complete source code for this tutorial is available in Subversion. Web Access (read only): http://fisheye3.cenqua.com/browse/fornax/trunk/cartridges/sculptor/sculptor-helloworld-parent http://fisheye3.cenqua.com/browse/fornax/trunk/cartridges/sculptor/sculptor-helloworld http://fisheye3.cenqua.com/browse/fornax/trunk/cartridges/sculptor/sculptor-helloworld-web http://fisheye3.cenqua.com/browse/fornax/trunk/cartridges/sculptor/sculptor-helloworld-ear

Document generated by Confluence on Feb 01, 2009 16:20

Page 11

Anonymous Access (read only): https://fornax.svn.sourceforge.net/svnroot/fornax/trunk/cartridges/sculptor/sculptor-helloworld-parent https://fornax.svn.sourceforge.net/svnroot/fornax/trunk/cartridges/sculptor/sculptor-helloworld https://fornax.svn.sourceforge.net/svnroot/fornax/trunk/cartridges/sculptor/sculptor-helloworld-web https://fornax.svn.sourceforge.net/svnroot/fornax/trunk/cartridges/sculptor/sculptor-helloworld-ear

More Advanced Example


You can try the more comprehensive Library example, which also has a generated GRUD Gui. This application implements both the JSP and JSF variants, and both can be deployed and used in parallel. Checkout these four projects from Subversion.

https://fornax.svn.sourceforge.net/svnroot/fornax/trunk/cartridges/sculptor/fornax-cartridges-sculptor-examples https://fornax.svn.sourceforge.net/svnroot/fornax/trunk/cartridges/sculptor/fornax-cartridges-sculptor-examples https://fornax.svn.sourceforge.net/svnroot/fornax/trunk/cartridges/sculptor/fornax-cartridges-sculptor-examples https://fornax.svn.sourceforge.net/svnroot/fornax/trunk/cartridges/sculptor/fornax-cartridges-sculptor-examples

Links
Spring Webflow

Document generated by Confluence on Feb 01, 2009 16:20

Page 12

Вам также может понравиться