简体   繁体   中英

How to use a generic editor for database access in tapestry 5?

I have a tapestry 5 project that contains the following:

  • An abstract entity in the entities package that is inherited by all the other concrete entities

     import java.io.Serializable; import javax.persistence.Basic; import javax.persistence.Column; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.MappedSuperclass; @MappedSuperclass public class AbstractEntity implements Serializable, Comparable<AbstractEntity> { private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Basic(optional = false) @Column(name = "ID") protected Integer id; @Override public int compareTo(AbstractEntity o) { return this.toString().compareTo(o.toString()); } } 
  • Several concrete entities (I will omit most of their bodies since I believe it's completely irrelevant for the question, they are simple entity data classes) that inherit the AbstractEntity. Example of one such entity class:

     //Imports go here @Entity @Table(name = "room") @NamedQueries({ @NamedQuery(name = "Room.findAll", query = "SELECT r FROM Room r")}) public class Room extends AbstractEntity { private static final long serialVersionUID = 1L; @Basic(optional = false) @Column(name = "ROOM_TYPE") @Validate("required") @Enumerated(EnumType.STRING) private RoomType roomType; //rest of the attributes and their annotations go here, as well as setter/getter methods 
  • A generic DAO interface

     import com.mycompany.myproject.entities.AbstractEntity; import java.util.List; public interface GenericDAO <T extends AbstractEntity>{ public abstract List<T> getListOfObjects(Class myclass); public abstract T getObjectById(Integer id, Class myclass); public abstract T addOrUpdate(T obj); public abstract T delete(Integer id, Class myclass); } 
  • Implementation of the generic DAO interface, which is bound to it in the AppModule in services package using the binder.bind

     import com.mycompany.myproject.entities.AbstractEntity; import java.util.Collections; import java.util.List; import org.hibernate.Criteria; import org.hibernate.Session; import org.hibernate.criterion.Restrictions; public class GenericDAOImpl<T extends AbstractEntity> implements GenericDAO<T> { private Session session; @Override public List getListOfObjects(Class myclass) { List<T> list = session.createCriteria(myclass).setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY).list(); Collections.sort(list); return list; } @Override public T getObjectById(Integer id, Class myclass) { AbstractEntity ae = (AbstractEntity) session.createCriteria(myclass) .add(Restrictions.eq("id", id)).list().get(0); return (T) ae; } @Override public AbstractEntity addOrUpdate(AbstractEntity obj) { return (T) session.merge(obj); } @Override public T delete(Integer id, Class myclass) { AbstractEntity ae = (AbstractEntity) session.createCriteria(myclass) .add(Restrictions.eq("id", id)).list().get(0); session.delete((T) ae); session.flush(); return (T) ae; } } 
  • A generic editor java class in the components package

     import com.mycompany.myproject.entities.AbstractEntity; import com.mycompany.myproject.services.GenericDAO; import java.util.List; import org.apache.tapestry5.ComponentResources; import org.apache.tapestry5.PropertyConduit; import org.apache.tapestry5.annotations.Persist; import org.apache.tapestry5.annotations.Property; import org.apache.tapestry5.beaneditor.BeanModel; import org.apache.tapestry5.hibernate.annotations.CommitAfter; import org.apache.tapestry5.ioc.annotations.Inject; import org.apache.tapestry5.services.BeanModelSource; import org.apache.tapestry5.services.PropertyConduitSource; public class GenericEditor<T extends AbstractEntity> { @Inject private PropertyConduitSource conduit; @Inject private GenericDAO genericDAO; @Property @Persist private T bean; @Property private T row; @Inject private BeanModelSource bms; @Inject private ComponentResources cr; private Class myclass; { PropertyConduit conduit1 = conduit.create(getClass(), "bean"); myclass = conduit1.getPropertyType(); } public List<T> getGrid(){ List<T> temp = genericDAO.getListOfObjects(myclass); return temp; } public BeanModel<T> getFormModel(){ return bms.createEditModel(myclass, cr.getMessages()).exclude("id"); } public BeanModel<T> getGridModel(){ return bms.createDisplayModel(myclass, cr.getMessages()).exclude("id"); } @CommitAfter Object onActionFromDelete(int id){ genericDAO.delete(id, myclass); return this; } @CommitAfter Object onActionFromEdit(int row){ bean = (T)genericDAO.getObjectById(row, myclass); return this; } @CommitAfter Object onSuccess(){ genericDAO.addOrUpdate(bean); try { bean = (T) myclass.newInstance(); } catch(Exception ex){ } return this; } 
  • An associated .tml file for the GenericEditor java class

     <!--GenericEditor.tml--> <html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_3.xsd" xmlns:p="tapestry:parameter"> <t:beaneditform object="bean" t:model="formModel" > </t:beaneditform> <t:grid t:source="grid" t:model="gridModel" add="edit,delete" row="row"> <p:editCell> <t:actionlink t:id="edit" context="row">Edit</t:actionlink> </p:editCell> <p:deleteCell> <t:actionlink t:id="delete" context="row">Delete</t:actionlink> </p:deleteCell> </t:grid> </html> 
  • Furthermore, there are several java classes in the pages package, as well as their associated .tml files, which were originally made without using genericDAO, but with using concrete DAO's so they looked like this (example of one of them):

     import com.mycompany.myproject.entities.Room; import com.mycompany.myproject.services.RoomDAO; import java.util.ArrayList; import java.util.List; import org.apache.tapestry5.annotations.Persist; import org.apache.tapestry5.annotations.Property; import org.apache.tapestry5.hibernate.annotations.CommitAfter; import org.apache.tapestry5.ioc.annotations.Inject; public class RoomPage { @Property private Room room; @Property private Room roomrow; @Inject private RoomDAO roomDAO; @Property private List<Room> rooms; void onActivate(){ if(rooms==null){ rooms = new ArrayList<Room>(); } rooms = roomDAO.getListOfRooms(); } @CommitAfter Object onSuccess(){ roomDAO.addOrUpdateRoom(room); room = new Room(); return this; } @CommitAfter Object onActionFromEdit(Room room2){ room = room2; return this; } @CommitAfter Object onActionFromDelete(int id){ roomDAO.deleteRoom(id); return this; } } 
  • And the associated .tml file:

     <!--RoomPage.tml--> <html t:type="layout" title="RoomPage" xmlns:t="http://tapestry.apache.org/schema/tapestry_5_3.xsd" xmlns:p="tapestry:parameter"> <div class="row"> <div class="col-sm-4 col-md-4 col-lg-3"> <t:beaneditform object="room" exclude="id" reorder="roomtype, floor, tv, internet" submitlabel="message:submit-label"/> </div> <div class="col-sm-8 col-md-8 col-lg-9"> <t:grid t:source="rooms" exclude="id" add="edit,delete" row="roomrow" include="roomtype, floor, tv, internet"> <p:editCell> <t:actionlink t:id="edit" context="roomrow">Edit</t:actionlink> </p:editCell> <p:deleteCell> <t:actionlink t:id="delete" context="roomrow.id">Delete</t:actionlink> </p:deleteCell> </t:grid> </div> </div> </html> 

The code above using concrete DAO works properly, a form for inputting new rows in the database appears on the page as expected, as well as the grid with rows from the database table.

So, the basic idea was to use the GenericEditor together with genericDAO in order to reduce the amount of code necessary and manipulate any of the database tables, using the BeanEditForm to input new rows in the table and Grid to show all rows from the table and delete or edit them. In theory, this should work for any entity that inherits the AbstractEntity class, so there wouldn't be a need to make a separate DAO interface/implementation pairing for each entity.

The problem is, I can't seem to get this to work as intended, as I'm not sure how to actually use the GenericEditor shown above. I have attempted the following:

  • RoomPage.java after modifications:

     import com.mycompany.myproject.components.GenericEditor; import com.mycompany.myproject.entities.Room; public class RoomPage{ @Component private GenericEditor<Room> ge; } 
  • RoomPage.tml after modifications:

     <!--RoomPage.tml--> <html t:type="layout" title="RoomPage" xmlns:t="http://tapestry.apache.org/schema/tapestry_5_3.xsd" xmlns:p="tapestry:parameter"> <t:GenericEditor t:id="ge" /> </html> 

But that apparently does not work, as all it yielded was a null pointer exception as well as this error:

Blockquote [ERROR] pages.RoomPage Render queue error in SetupRender[RoomPage:ge.grid]: Failure reading parameter 'source' of component RoomPage:ge.grid: org.apache.tapestry5.ioc.internal.util.TapestryException org.apache.tapestry5.ioc.internal.util.TapestryException: Failure reading parameter 'source' of component RoomPage:ge.grid: org.apache.tapestry5.ioc.internal.util.TapestryException [at classpath:com/mycompany/myproject/components/GenericEditor.tml, line 5]

I have then tried to remove the grid element entirely, and run the GenericEditor with BeanEditForm only. This has resulted in the page actually loading, but instead of showing an expected Form on the page, with the fields of the Room entity and the Create/Update button at the end of the form, all that appeared was the Create/Update button, without any field, as if the BeanEditForm was created on an object without any attributes. Pressing the Create/Update button creates another null pointer exception.

For debugging purposes, I have changed GenericEditor.java to work in a non-generic way, by creating another attribute of the generic type T in it, and then initializing it as a new object of type Room, casted as (T), and then declaring attribute class to be of the same type as the room attribute, as seen bellow

private T room; 
{
    //PropertyConduit conduit1 = conduit.create(getClass(), "bean");
    //class = conduit1.getPropertyType();
    room = (T) new Room();
    class = room.getClass();
}

Running the application with these changes (with grid still disabled and only beaneditform enabled), the page now renders all the input fields correctly. This has led me to conclusion that the problem lies within the fact that the GenericEditor does not receive the proper type through the generic, but I do not know if my logic is correct, and even if it is, how to get around this issue. Another possible source of the problem might be the PropertyConduit, I am not sure how it works exactly, and if I'm using it correctly or not, so I'm not ruling out that the issue originates there as well. Either way, my main guess is that I'm misusing the GenericEditor somehow, so as the title of this question says, how am I supposed to use the GenericEditor in order to access database properly with it?

I have searched stackoverflow for similar problems to my own but I have been unable to find anything similar, neither here nor elsewhere. I am hoping that someone here will be able to help me to identify what the issue is and help me get around it, as I really have no idea how to do so on my own. Thanks in advance.

Update: I have done some further debugging, by trying to check what type of class gets forwarded to GenericEditor's myclass. I have modified the following bit of GenericEditor.java:

    {
        PropertyConduit conduit1 = conduit.create(getClass(), "bean");
        myclass = conduit1.getPropertyType();
    }

to following:

    {
        PropertyConduit conduit1 = conduit.create(getClass(), "bean");
        System.out.println("conduit1.toString(): "+conduit1.toString());
        System.out.println("conduit1.getPropertyType().toString(): "+conduit1.getPropertyType().toString());
        System.out.println("conduit1.getPropertyType().getName(): "+conduit1.getPropertyType().getName());
        myclass = conduit1.getPropertyType();
        System.out.println("myclass.getName(): "+myclass.getName());
    }

and this has resulted in the following output:

conduit1.toString(): PropertyConduit[com.mycompany.myproject.components.GenericEditor bean]

conduit1.getPropertyType().toString(): class com.mycompany.myproject.entities.AbstractEntity

conduit1.getPropertyType().getName(): com.mycompany.myproject.entities.AbstractEntity

myclass.getName(): com.mycompany.myproject.entities.AbstractEntity

Which I believe pretty much means that type T forwarded to the GenericEditor is AbstractEntity, not Room as intended. If my assumption is correct, I'm misusing the GenericEditor as I'm not getting the proper class forwarded to it via generics, so how am I supposed to forward the proper class to it? Or is my assumption wrong and something else is amiss here?

I've managed to find an answer to this question, so I'm posting it here in case anyone ever needs it:

There were 2 reasons why the application did not work as intended: 1) In the GenericDAOImpl class, I've forgot to add the @Inject annotation above the "private Session session" line, which yielded the error in the first place so that piece of code should have looked like this:

//imports
public class GenericDAOImpl<T extends AbstractEntity> implements GenericDAO<T> {
@Inject
private Session session;
//rest of code unchanged

2) the very thing I was unsure about in the first place was how to use the GenericEditor component, and I was trying to do so in the wrong way, by trying to add the component into the class file and the associated tml file. What was supposed to be done instead was to simply extend GenericEditor, and delete the associated tml file, so the GenericEditor tml is used instead, like this:

public class RoomPage extends GenericEditor<Room>{
}

Upon making these 2 changes, the application works as intended

我从来没有亲自使用过它,但是您可能对tynamo的挂毯模型感兴趣,据我了解,该模型对通用CRUD有帮助。

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM