At line 1 added 373 lines. |
!!!DTO in AppFuse using XSnapshot |
__Part III:__ [Replace Beanutils by XSnapshot|AppFuseXSnapshotReplace] - How to use xsnapshot for mapping model / Struts forms |
|
;:''This tutorial depends on __Part II:__ [Implementing the example|AppFuseXSnapshotBase] - Create Model, DAO, Manager, Actions for the example'' |
|
!! Table of content |
*[1] introduction |
*[2] modifing the model's doclet |
*[3] modifing Struts actions |
*[4] deploy the new webapp |
|
!!Introduction[#1] |
|
Now that we have integrated xsnapshot into your AppFuse project, we re going to modifie your sample to use xsnapshot to transfert data between the model and Struts forms. In a first time, we are going to concrentrate us on the ''ProjectAtion''. |
|
!! modifing the model's doclet[#2] |
|
First of all, we need to declare the form for editing Project objects. |
|
We start by removing the ''old form declaration'' in javadoc |
|
[{Java2HtmlPlugin |
|
/** |
* @author David Rouchy |
* |
* @hibernate.class table="project" |
* @struts.form include-all="true" extends="BaseForm" |
*/ |
}] |
|
by |
|
[{Java2HtmlPlugin |
|
/** |
* @author David Rouchy |
* |
* @hibernate.class table="project" |
* @xsnapshot.snapshot-class name="projectForm" |
* class="ProjectForm" |
* extends="org.apache.struts.action.ActionForm" |
*/ |
}] |
|
We have declared a snapshot class with name ''projectForm'. XSnapshot will generate a ProjectForm class (the package name is confiured in the ant task). |
|
XSnapshot need a name, because you can define multiple snapshot from the same class. We can imagine we need DTO for Struts and a other snapshot for by example a webservice. |
|
Xsnapshot will generate two classes, ProjectForm that contains data and some methods for converting, and ProjectFormHelper which is only used for convertion. In this state ProjectForm would contains no data, we need to declare which attirubte we want to convert into the snapshot. |
|
change the Project Getter by this |
|
[{Java2HtmlPlugin |
|
/** |
* @hibernate.property |
* @xsnapshot.property match="*" |
* @return the description |
*/ |
public String getDescription() { |
return this.description; |
} |
|
/** |
* @hibernate.id generator-class="native" |
* @xsnapshot.property match="*" |
* @return the id |
*/ |
public Long getId() { |
return this.id; |
} |
|
/** |
* @hibernate.property |
* @xsnapshot.property match="*" |
* @return the name |
*/ |
public String getName() { |
return this.name; |
} |
|
/** |
* |
* @hibernate.set lazy="true" cascade="none" table="person_project" |
* @hibernate.collection-key column="project" |
* @hibernate.collection-many-to-many class="org.appfuse.model.Person" |
* column="person" |
* |
* @xsnapshot.array match="*" name="participants" |
* model-element-type="org.appfuse.model.Person" |
* snapshot-element-type="java.lang.Long" |
* element-nested-property="id" |
* model-create-expr="Collection" |
* |
* @return the participants |
*/ |
public Set getParticipants() { |
return this.participants; |
} |
|
/** |
* We wil see later how to convert date by declaring non custom transformer |
* @hibernate.property |
* @xsnapshot.property match="*" |
* @return the startDate |
*/ |
public Date getStartDate() { |
return this.startDate; |
} |
|
/** |
* @hibernate.many-to-one class="org.appfuse.model.Person" cascade="none" |
* @xsnapshot.property match="*" |
* name="leader" type="java.lang.Long" |
* nested-property="id" copy-to-model="true" |
* @return the leader |
*/ |
public Person getLeader() { |
return this.leader; |
} |
}] |
|
|
''Simple'' properties are mapped by a doclet @xsnapshot.property. The match option define which snapshot (by name) has to map this property (* for all declared for all class' snapshot). |
|
Embedded object can be mapped by two methods |
|
* type="..." nested-property=".." only a property (normaly id) on the object is map into snapshot |
* snapshot-element-key="aForm" : the attribute is mapped into a snapshot which is already defined |
|
We are using the first method because for a Person selection, we only need to know their id. The id-to-entity XSnapshot bean will convert this id into hibernate object. |
|
Oject collection can be mapped into List or array (our case), the collection mapping is similar (but more simple) than in hibernate. At the difference with BeanUtils, we can map very easily a list of selection (a multiple select where each option contain id) into a Collection a objects. |
|
!! modifing Struts actions[#3] |
|
Now we need to replace in action the dto method. By default, appfuse use BeanUtils to copy property to convert form into object (and reciprocly). With XSnapshot, the method is a little different. Each mapping is declared into the registry, and there is a declared bean which can convert all declared object. |
|
In general we have to change into actions : |
|
* for model to form convertion : |
|
[{Java2HtmlPlugin |
|
form = (FormClass) convert(object); |
}] |
|
by |
|
[{Java2HtmlPlugin |
|
XsnapshotUtils xsnapshotUtils = (XsnapshotUtils) getBean("xsnapshotUtils") ; |
form = (FormClass) m_xsnapshotUtils.createSnapshot(object, "snapshotName") ; |
}] |
|
* for form to model convertion |
|
|
[{Java2HtmlPlugin |
|
ModelClass project = (ModelClass) convert(form); |
}] |
|
by |
|
[{Java2HtmlPlugin |
|
XsnapshotUtils xsnapshotUtils = (XsnapshotUtils) getBean("xsnapshotUtils") ; |
ModelClass object = (ModelClass) m_xsnapshotUtils.createModel(form) ; |
}] |
|
In your sample, change the methods of the class ProjectAction |
|
|
[{Java2HtmlPlugin |
|
public ActionForward cancel(ActionMapping mapping, ActionForm form, |
HttpServletRequest request, HttpServletResponse response) |
throws Exception { |
return mapping.findForward("search"); |
} |
|
public ActionForward delete(ActionMapping mapping, ActionForm form, |
HttpServletRequest request, HttpServletResponse response) |
throws Exception { |
if (log.isDebugEnabled()) { |
log.debug("Entering 'delete' method"); |
} |
|
ActionMessages messages = new ActionMessages(); |
ProjectForm projectForm = (ProjectForm) form; |
|
// Exceptions are caught by ActionExceptionHandler |
ProjectManager mgr = (ProjectManager) getBean("projectManager"); |
mgr.removeProject(projectForm.getId().toString()); |
|
messages.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage( |
"project.deleted")); |
|
// save messages in session, so they'll survive the redirect |
saveMessages(request.getSession(), messages); |
|
return mapping.findForward("search"); |
} |
|
public ActionForward edit(ActionMapping mapping, ActionForm form, |
HttpServletRequest request, HttpServletResponse response) |
throws Exception { |
if (log.isDebugEnabled()) { |
log.debug("Entering 'edit' method"); |
} |
|
ProjectForm projectForm = (ProjectForm) form; |
|
// if an id is passed in, look up the user - otherwise |
// don't do anything - user is doing an add |
if (projectForm.getId() != null) { |
ProjectManager mgr = (ProjectManager) getBean("projectManager"); |
Project project = mgr.getProject(projectForm.getId().toString()); |
|
XSnapshotUtils m_xsnapshotUtils = (XSnapshotUtils) this.getBean("xsnapshotUtils") ; |
projectForm = (ProjectForm) m_xsnapshotUtils.createSnapshot( |
project, "projectForm"); |
updateFormBean(mapping, request, projectForm); |
if (log.isDebugEnabled()) { |
log.debug("Model : " + project); |
log.debug("Form : " + projectForm); |
} |
|
} |
|
PersonManager mgr = (PersonManager) getBean("personManager"); |
request.setAttribute(Constants.PERSON_LIST, mgr |
.getPersons(new Person())); |
|
return mapping.findForward("edit"); |
} |
|
public ActionForward save(ActionMapping mapping, ActionForm form, |
HttpServletRequest request, HttpServletResponse response) |
throws Exception { |
if (log.isDebugEnabled()) { |
log.debug("Entering 'save' method"); |
} |
|
// Extract attributes and parameters we will need |
ActionMessages messages = new ActionMessages(); |
ProjectForm projectForm = (ProjectForm) form; |
boolean isNew = ("".equals(projectForm.getId()) || projectForm.getId() == null); |
|
ProjectManager mgr = (ProjectManager) getBean("projectManager"); |
XSnapshotUtils m_xsnapshotUtils = (XSnapshotUtils) this.getBean("xsnapshotUtils") ; |
Project project = (Project) m_xsnapshotUtils.createModel(projectForm); |
|
if (log.isDebugEnabled()) { |
log.debug("Form : " + projectForm); |
log.debug("Model : " + project); |
} |
|
mgr.saveProject(project); |
|
// add success messages |
if (isNew) { |
messages.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage( |
"project.added")); |
|
// save messages in session to survive a redirect |
saveMessages(request.getSession(), messages); |
|
return mapping.findForward("/projects.html?method=search"); |
} else { |
messages.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage( |
"project.updated")); |
saveMessages(request, messages); |
|
PersonManager pMgr = (PersonManager) getBean("personManager"); |
request.setAttribute(Constants.PERSON_LIST, pMgr.getPersons(null)); |
return mapping.findForward("edit"); |
} |
} |
|
public ActionForward search(ActionMapping mapping, ActionForm form, |
HttpServletRequest request, HttpServletResponse response) |
throws Exception { |
if (log.isDebugEnabled()) { |
log.debug("Entering 'search' method"); |
} |
|
ProjectForm projectForm = (ProjectForm) form; |
Project project = (Project) null; |
|
ProjectManager mgr = (ProjectManager) getBean("projectManager"); |
request.setAttribute(Constants.PROJECT_LIST, mgr.getProjects(project)); |
|
return mapping.findForward("list"); |
} |
|
public ActionForward unspecified(ActionMapping mapping, ActionForm form, |
HttpServletRequest request, HttpServletResponse response) |
throws Exception { |
return search(mapping, form, request, response); |
} |
}] |
|
Don't forget to add import |
|
[{Java2HtmlPlugin |
|
import net.sf.xsnapshot.XSnapshotUtils; |
}] |
|
!! deploy the new app |
|
* run ''ant clean deploy-war'' |
|
''Yeah Baby, Yeah:'' |
%%(color:green)BUILD SUCCESSFUL\\ |
Total time: 1 minute 53 seconds%% |
|
* now test editing a project |
|
[projectForm2.png] |
|
__Wooooh.................__ Nothing change ;-) |
|
If you look at the console, we can see that the convertion works form model to form. |
|
{{{ |
[appfuse] DEBUG [http-8080-Processor24] ProjectAction.edit(68) | Entering 'edit' method |
[appfuse] DEBUG [http-8080-Processor24] ProjectAction.edit(84) | Model : |
org.appfuse.model.Project@1d7999e[name=Appfuse,id=1,description= |
AppFuse is an application for "kickstarting" webapp |
development. |
,startDate=2004-03-01 |
13:41:57.0,participants=[org.appfuse.model.Person@177e8ce[lastName=Raible,id=1,firstName=Matt], |
org.appfuse.model.Person@1505b41[lastName=Davidson,id=2,firstName=James]], |
leader=org.appfuse.model.Person@177e8ce[lastName=Raible,id=1,firstName=Matt]] |
|
[appfuse] DEBUG [http-8080-Processor24] ProjectAction.edit(85) | Form : |
org.appfuse.webapp.form.ProjectForm@14c803d[m_description= |
AppFuse is an application for "kickstarting" webapp |
development. |
,m_id=1,m_name=Appfuse,m_participants={1,2},m_startDate=2004-03-01 13:41:57.0,m_leader=1] |
|
}}} |
|
Embedded objet are flatten into their id, participant list is converted into a array. |
|
|
You can change by example the id of the project leader by 2, __change the start date to 01/01/2006__ (you will see how to automate date convertion in the next part) |
|
OK great it works in project List we can see that the project leader has changed. |
|
[leader.png] |
|
In console, we can see that the convertion has worked well.... |
{{{ |
[appfuse] DEBUG [http-8080-Processor25] ProjectAction.save(101) | Entering 'save' method |
|
[appfuse] DEBUG [http-8080-Processor25] ProjectAction.save(114) | Form : |
org.appfuse.webapp.form.ProjectForm@1bcac12[m_description=AppFuse is an application for "kickstarting" |
webapp, |
m_id=1,m_name=Appfuse,m_participants={<null>},m_startDate=Sat Jan 01 00:00:00 CET 2005,m_leader=2] |
|
[appfuse] DEBUG [http-8080-Processor25] ProjectAction.save(115) | Model : |
org.appfuse.model.Project@5017e9[name=Appfuse,id=1,description=AppFuse is an application for "kickstarting" |
webapp,startDate=Sat Jan 01 00:00:00 CET |
2005,participants=<null>,leader=org.appfuse.model.Person@10b0fd[lastName=Davidson,id=2,firstName=James]] |
}}} |
|
---- |
''Next Up:'' __Part IV:__ [Create a transformer for non custom object|AppFuseXSnapshotTransformer] (coming soon) |