Reputation: 11
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?
Upvotes: -1
Views: 610
Reputation: 11
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
Upvotes: 1
Reputation: 28099
I've never used it myself but you might be interested in tynamo's tapestry-model which i understand helps with generic CRUD.
Upvotes: 0