I have a table in MySQL database. Unfortunately, there is a composite primary key which is needed for JAAS authentication/authorization in GlassFish Server.
mysql> desc group_table;
+---------------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+---------------+--------------+------+-----+---------+-------+
| user_group_id | varchar(176) | NO | PRI | NULL | |
| group_id | varchar(15) | NO | PRI | NULL | |
+---------------+--------------+------+-----+---------+-------+
2 rows in set (0.05 sec)
The table contains data in the following format.
mysql> select * from group_table;
+-------------------------+------------+
| user_group_id | group_id |
+-------------------------+------------+
| [email protected] | ROLE_ADMIN |
| [email protected] | ROLE_USER |
| [email protected] | ROLE_USER |
| [email protected] | ROLE_USER |
| [email protected] | ROLE_USER |
+-------------------------+------------+
5 rows in set (0.00 sec)
A <p:dataTable>
with rowKey
works fine , when lazy
is set to false
.
<p:dataTable rowKey="#{row.groupTablePK.userGroupId} #{row.groupTablePK.groupId}">
...
</p:dataTable>
GroupTablePK
is an @Embeddable
class (JPA). The details about this class is not needed I presume.
When lazy
is however, enabled on a <p:dataTable>
, the getRowKey()
and the getRowData()
methods need to be implemented.
How can this be done, when there is a composite primary key which requires a combination of columns as a row key - a unique row identifier?
@Named
@ViewScoped
public class UserAuthorityManagedBean extends LazyDataModel<GroupTable> implements Serializable {
private static final long serialVersionUID = 1L;
@Override
public Object getRowKey(GroupTable groupTable) {
return groupTable != null ? groupTable.getGroupTablePK() : null;
}
@Override
public GroupTable getRowData(String rowKey) {
List<GroupTable> list = (List<GroupTable>) getWrappedData();
System.out.println("rowKey : " + rowKey);
}
@Override
public List<GroupTable> load(int first, int pageSize, List<SortMeta> multiSortMeta, Map<String, Object> filters) {
//... setRowCount(rowCount);
//... Return a List<GroupTable> from a business Service.
}
}
The above implementations are left incomplete.
When a row is selected in the <p:dataTable>
with these implementations, the sout
statement inside the getRowData()
method displays the following.
Info: rowKey : entity.GroupTablePK[ [email protected]
Info: rowKey : groupId=ROLE_USER ]
The getRowKey()
method returns an instance of GroupTablePK
but the getRowData()
method only accepts a String type parameter. It is not an object representing the composite primary key (hereby GroupTablePK
) so that it can be type-cast to an appropriate object type (GroupTablePK
) and based on which an instance of GroupTable
may be obtained from the given List<GroupTable>
and get the getRowData()
method to return that instance of GroupTable
.
How to proceed further?
The question is purely based on the immediate previous question :
EDIT:
I have hashcode()
and equals()
implementations in addition to toString()
in GroupTablePK
. The toString()
method in GroupTablePK
returns return "entity.GroupTablePK[ userGroupId=" + userGroupId + ", groupId=" + groupId + " ]";
but the getRowData()
method is invoked twice, when a row in a <p:dataTable>
is selected. It returns the string representation of GroupTablePK
in two parts in two subsequent calls. In the first call, it returns entity.GroupTablePK[ userGroupId=aaa
and then in the second call, it returns groupId=ROLE_USER ]
.
It should instead return entity.GroupTablePK[ userGroupId=aaa, groupId=ROLE_USER ]
at once in a single call.
This kind of comparison groupTable.getGroupTablePK().toString().equals(rowKey)
is therefore not possible which I was thinking about prior to this post. Such as,
@Override
public GroupTable getRowData(String rowKey) {
List<GroupTable> list = (List<GroupTable>) getWrappedData();
for (GroupTable groupTable : list) {
if (groupTable.getGroupTablePK().toString().equals(rowKey)) {
return groupTable;
}
}
return null;
}
EDIT 2:
The following is the shortest possible example removing the JPA noise to reproduce the problem.
Attempted alternatively on,
The behaviour remains stationary on all of these versions of PrimeFaces.
The managed bean:
@Named
@ViewScoped
public class CompositeRowKeyManagedBean extends LazyDataModel<GroupTable> implements Serializable {
private List<GroupTable> selectedValues; // Getter & setter.
private static final long serialVersionUID = 1L;
public CompositeRowKeyManagedBean() {}
private List<GroupTable> init() {
List<GroupTable> list = new ArrayList<GroupTable>();
GroupTablePK groupTablePK = new GroupTablePK("aaa", "ROLE_ADMIN");
GroupTable groupTable = new GroupTable(groupTablePK);
list.add(groupTable);
groupTablePK = new GroupTablePK("bbb", "ROLE_USER");
groupTable = new GroupTable(groupTablePK);
list.add(groupTable);
groupTablePK = new GroupTablePK("ccc", "ROLE_USER");
groupTable = new GroupTable(groupTablePK);
list.add(groupTable);
groupTablePK = new GroupTablePK("ddd", "ROLE_USER");
groupTable = new GroupTable(groupTablePK);
list.add(groupTable);
groupTablePK = new GroupTablePK("eee", "ROLE_USER");
groupTable = new GroupTable(groupTablePK);
list.add(groupTable);
return list;
}
@Override
public List<GroupTable> load(int first, int pageSize, String sortField, SortOrder sortOrder, Map<String, Object> filters) {
List<GroupTable> list = init();
setRowCount(list.size());
return list;
}
@Override
public Object getRowKey(GroupTable groupTable) {
return groupTable != null ? groupTable.getGroupTablePK() : null;
}
@Override
public GroupTable getRowData(String rowKey) {
List<GroupTable> list = (List<GroupTable>) getWrappedData();
System.out.println("rowKey : " + rowKey);
for (GroupTable groupTable : list) {
if (groupTable.getGroupTablePK().toString().equals(rowKey)) {
return groupTable;
}
}
return null;
}
public void onRowEdit(RowEditEvent event) {
GroupTablePK groupTablePK = ((GroupTable) event.getObject()).getGroupTablePK();
System.out.println("grouoId : " + groupTablePK.getGroupId() + " : userGroupId : " + groupTablePK.getUserGroupId());
}
}
The data table :
<p:dataTable var="row"
value="#{compositeRowKeyManagedBean}"
lazy="true"
editable="true"
selection="#{compositeRowKeyManagedBean.selectedValues}"
rows="50">
<p:column selectionMode="multiple"></p:column>
<p:ajax event="rowEdit" listener="#{compositeRowKeyManagedBean.onRowEdit}"/>
<p:column headerText="GroupId">
<h:outputText value="#{row.groupTablePK.userGroupId}"/>
</p:column>
<p:column headerText="UserGroupId">
<p:cellEditor>
<f:facet name="output">
<h:outputText value="#{row.groupTablePK.groupId}"/>
</f:facet>
<f:facet name="input">
<p:inputText value="#{row.groupTablePK.groupId}"/>
</f:facet>
</p:cellEditor>
</p:column>
<p:column headerText="Edit">
<p:rowEditor/>
</p:column>
</p:dataTable>
When a row is attempted to edit, the onRowEdit()
method is invoked. The getRowData()
is invoked twice and produces a split of the row key in two subsequent calls as said earlier.
These are two domain classes GroupTable
and GroupTablePK
.
public class GroupTable implements Serializable {
private static final long serialVersionUID = 1L;
protected GroupTablePK groupTablePK;
public GroupTable() {}
public GroupTable(GroupTablePK groupTablePK) {
this.groupTablePK = groupTablePK;
}
public GroupTable(String userGroupId, String groupId) {
this.groupTablePK = new GroupTablePK(userGroupId, groupId);
}
public GroupTablePK getGroupTablePK() {
return groupTablePK;
}
public void setGroupTablePK(GroupTablePK groupTablePK) {
this.groupTablePK = groupTablePK;
}
@Override
public int hashCode() {
int hash = 0;
hash += (groupTablePK != null ? groupTablePK.hashCode() : 0);
return hash;
}
@Override
public boolean equals(Object object) {
if (!(object instanceof GroupTable)) {
return false;
}
GroupTable other = (GroupTable) object;
if ((this.groupTablePK == null && other.groupTablePK != null) || (this.groupTablePK != null && !this.groupTablePK.equals(other.groupTablePK))) {
return false;
}
return true;
}
@Override
public String toString() {
return "entity.GroupTable[ groupTablePK=" + groupTablePK + " ]";
}
}
public class GroupTablePK implements Serializable {
private String userGroupId;
private String groupId;
public GroupTablePK() {}
public GroupTablePK(String userGroupId, String groupId) {
this.userGroupId = userGroupId;
this.groupId = groupId;
}
public String getUserGroupId() {
return userGroupId;
}
public void setUserGroupId(String userGroupId) {
this.userGroupId = userGroupId;
}
public String getGroupId() {
return groupId;
}
public void setGroupId(String groupId) {
this.groupId = groupId;
}
@Override
public int hashCode() {
int hash = 0;
hash += (userGroupId != null ? userGroupId.hashCode() : 0);
hash += (groupId != null ? groupId.hashCode() : 0);
return hash;
}
@Override
public boolean equals(Object object) {
if (!(object instanceof GroupTablePK)) {
return false;
}
GroupTablePK other = (GroupTablePK) object;
if ((this.userGroupId == null && other.userGroupId != null) || (this.userGroupId != null && !this.userGroupId.equals(other.userGroupId))) {
return false;
}
if ((this.groupId == null && other.groupId != null) || (this.groupId != null && !this.groupId.equals(other.groupId))) {
return false;
}
return true;
}
@Override
public String toString() {
return "entity.GroupTablePK[ userGroupId=" + userGroupId + ", groupId=" + groupId + " ]";
}
}
I ran your MCVE (kudos to that!) and reproduced it. The rowkey appears to be interpreted as a commaseparated string in the client side to cover the case when multiple selection is needed. This will fail if the string representation of a single rowkey contains a comma, as in your case. The rowkey argument you got in getRowData()
is clear evidence of it: they are the results when the original value is split on comma.
So, to solve this problem, you need to make sure that the getRowKey().toString()
doesn't contain a comma anywhere. Better use a different separator character. E.g. an underscore.
@Override
public Object getRowKey(GroupTable groupTable) {
GroupTablePK pk = groupTable != null ? groupTable.getGroupTablePK() : null;
return pk != null ? pk.getUserGroupId() + "_" + pk.getGroupId() : null;
}