I need to insert dynamically the field names and values old and new in a trigger, in firebird 2.5 - firebird

I can insert table column names dynamically the problem is when I want to insert the new or old values in my log table I am getting a string 'old.Colname' or 'new.Colname' instead of old or new value.
DECLARE C_USER VARCHAR(255);
DECLARE VARIABLE OPERATION_EVENT CHAR(8);
DECLARE GROUPIDNO INT;
DECLARE LOGDATAID_NO INT;
DECLARE VARIABLE FN CHAR(31);
DECLARE VARIABLE NEWCOL CHAR(31);
DECLARE VARIABLE OLDCOL CHAR(31);
BEGIN
SELECT CURRENT_USER FROM RDB$DATABASE INTO :C_USER;
IF (DELETING) THEN
BEGIN
OPERATION_EVENT = 'DELETE';
END
ELSE
BEGIN
IF (UPDATING) THEN
OPERATION_EVENT = 'UPDATE';
ELSE
OPERATION_EVENT = 'INSERT';
END
SELECT MAX(GROUPID) FROM LOG_DATA INTO :GROUPIDNO;
IF(GROUPIDNO IS NULL) THEN
GROUPIDNO = 1;
ELSE
GROUPIDNO = GROUPIDNO + 1;
IF(INSERTING) THEN
BEGIN
FOR SELECT RDB$FIELD_NAME FROM RDB$RELATION_FIELDS WHERE RDB$RELATION_NAME = 'ARAC' INTO :FN DO
BEGIN
OLDCOL = 'old.'||:FN;
NEWCOL = 'new.'||:FN;
INSERT INTO LOG_DATA (OLD_VALUE,NEW_VALUE, COLUMN_NAME, TABLE_NAME, OPERATION,
CREATEDAT,USERS,GROUPID,LOGDATAID)
VALUES (:OLDCOL,:NEWCOL,:FN,'ARAC',trim(:OPERATION_EVENT),
current_timestamp,:C_USER,:GROUPIDNO,:LOGDATAID_NO + 1);
END
END
Here is a screen shot of my log table, I want to insert the old and new values, but column names are being inserted as strings instead

The problem is that you are trying to reference the old and new context as strings, and that is not possible. The specific problem is:
OLDCOL = 'old.'||:FN;
NEWCOL = 'new.'||:FN;
This produces a string with value 'old.<whatever the value of FN is>' (and same for new). It does not produce the value of the column with the name in FN from the OLD or NEW contexts.
Unfortunately, it is not possible to dynamically reference the columns in the OLD and NEW contexts by name. You will explicitly need to use OLD.columnname and NEW.columnname in your code, which means that you will need to write (or generate) a trigger that inserts each column individually.
Alternatively, you could upgrade to Firebird 3, and use a UDR to define a trigger in native code, C# or Java (or other supported languages). These UDR engines allow you to dynamically reference the columns in the old and new context.
As an example, using the FB/Java external engine (check the readme in the repository on how to install FB/Java):
Create a CHANGELOG table:
create table changelog (
id bigint generated by default as identity constraint pk_changelog primary key,
tablename varchar(31) character set unicode_fss not null,
row_id varchar(30) character set utf8,
columnname varchar(31) character set unicode_fss not null,
new_value varchar(2000) character set utf8,
old_value varchar(2000) character set utf8,
operation char(6) character set ascii not null,
modification_datetime timestamp default localtimestamp not null
)
And a FB/Java trigger:
package nl.lawinegevaar.fbjava.experiment;
import org.firebirdsql.fbjava.TriggerContext;
import org.firebirdsql.fbjava.Values;
import org.firebirdsql.fbjava.ValuesMetadata;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.EnumSet;
import java.util.Objects;
import java.util.Set;
import java.util.function.BiPredicate;
import static java.util.Collections.unmodifiableSet;
import static org.firebirdsql.fbjava.TriggerContext.Action.*;
public class ChangelogTrigger {
private static final Set<TriggerContext.Action> SUPPORTED_ACTIONS =
unmodifiableSet(EnumSet.of(INSERT, UPDATE, DELETE));
private static final int ROW_ID_LENGTH = 30;
private static final int VALUE_LENGTH = 2000;
private static final String INSERT_CHANGELOG =
"insert into changelog (tablename, row_id, columnname, new_value, old_value, operation) "
+ "values (?, ?, ?, ?, ?, ?)";
public static void logChanges() throws SQLException {
TriggerContext ctx = TriggerContext.get();
TriggerContext.Action action = ctx.getAction();
if (!SUPPORTED_ACTIONS.contains(action)) {
return;
}
String tableName = ctx.getTableName();
if (tableName.equals("CHANGELOG")) {
throw new IllegalStateException("Cannot apply ChangelogTrigger to table " + tableName);
}
String identifierColumn = ctx.getNameInfo();
ValuesMetadata fieldsMetadata = ctx.getFieldsMetadata();
int identifierIdx = identifierColumn != null ? fieldsMetadata.getIndex(identifierColumn) : -1;
Values oldValues = ctx.getOldValues();
Values newValues = ctx.getNewValues();
Values primaryValueSet = action == INSERT ? newValues : oldValues;
String identifierValue = identifierIdx != -1
? truncate(asString(primaryValueSet.getObject(identifierIdx)), ROW_ID_LENGTH)
: null;
try (PreparedStatement pstmt = ctx.getConnection().prepareStatement(INSERT_CHANGELOG)) {
pstmt.setString(1, tableName);
pstmt.setString(2, identifierValue);
BiPredicate<Object, Object> logColumn = action == UPDATE
? ChangelogTrigger::acceptIfModified
: ChangelogTrigger::acceptAlways;
boolean batchUsed = false;
for (int idx = 1; idx <= fieldsMetadata.getParameterCount(); idx++) {
Object oldValue = oldValues != null ? oldValues.getObject(idx) : null;
Object newValue = newValues != null ? newValues.getObject(idx) : null;
if (logColumn.test(oldValue, newValue)) {
String columnName = fieldsMetadata.getName(idx);
pstmt.setString(3, columnName);
pstmt.setString(4, truncate(asString(newValue), VALUE_LENGTH));
pstmt.setString(5, truncate(asString(oldValue), VALUE_LENGTH));
pstmt.setString(6, action.name());
pstmt.addBatch();
batchUsed = true;
}
}
if (batchUsed) {
pstmt.executeBatch();
}
}
}
private static boolean acceptAlways(Object oldValue, Object newValue) {
return true;
}
private static boolean acceptIfModified(Object oldValue, Object newValue) {
return !Objects.equals(oldValue, newValue);
}
private static String asString(Object value) {
return value != null ? String.valueOf(value) : null;
}
private static String truncate(String value, int maxLength) {
if (value == null || value.length() <= maxLength) {
return value;
}
return value.substring(0, maxLength - 3) + "...";
}
}
This FB/Java trigger is very generic and can be used for multiple tables. I haven't tested this trigger with all datatypes. For example, to be able to make the trigger work correctly with columns of type blob or other binary types will require additional work.
Build the trigger and load it into the database using the fbjava-deployer utility of FB/Java.
Then define the trigger on the table you want (in this case, I defined it on the TEST_CHANGELOG table):
create trigger log_test_changelog
before insert or update or delete
on test_changelog
external name 'nl.lawinegevaar.fbjava.experiment.ChangelogTrigger.logChanges()!ID'
engine JAVA
The external name defines the routine to call (nl.lawinegevaar.fbjava.experiment.ChangelogTrigger.logChanges()) and the name of the (single) primary key column of the table (ID), which is used to log the identifier in the ROW_ID column.

Related

MyBatis ... Get Last insert ID in loop "foreach"

Thank you for help :)
I tried to get last id, and read many post about it, but i don't arrive to apply it in my case.
First Class
private Date date;
private List<AdsEntity> adsDetails;
... getters and setters
Second Class (AdsEntity)
private int id;
private String description;
There is the code where i try to get the last id :
Mapper
#Insert({
"<script>",
"INSERT INTO tb_ads_details (idMyInfo, adDate)"
+ " VALUES"
+ " <foreach item='adsDetails' index='index' collection='adsDetails' separator=',' statement='SELECT LAST_INSERT_ID()' keyProperty='id' order='AFTER' resultType='java.lang.Integer'>"
+ " (#{adsDetails.description, jdbcType=INTEGER}) "
+ " </foreach> ",
"</script>"})
void saveAdsDetails(#Param("adsDetails") List<AdsDetailsEntity> adsDetails);
In debugging mode, when I watch List I see the id still at 0 and don't get any id.
So what I wrote didn't workout :(
Solution Tried with the answer from #Roman Konoval :
#Roman Konoval
I apply what you said, and the table is fully well set :)
Just one problem still, the ID is not fulfill
#Insert("INSERT INTO tb_ads_details SET `idMyInfo` = #{adsDetail.idMyInfo, jdbcType=INTEGER}, `adDate` = #{adsDetail.adDate, jdbcType=DATE}")
#SelectKey(statement = "SELECT LAST_INSERT_ID()", before = false, keyColumn = "id", keyProperty = "id", resultType = Integer.class )
void saveAdsDetails(#Param("adsDetail") AdsDetailsEntity adsDetail);
default void saveManyAdsDetails(#Param("adsDetails") List<AdsDetailsEntity> adsDetails)
{
for(AdsDetailsEntity adsDetail:adsDetails) {
saveAdsDetails(adsDetail);
}
}
Thank for your help :)
Solution add to #Roman Konoval proposal from #Chris advice
#Chris and #Roman Konoval
#Insert("INSERT INTO tb_ads_details SET `idMyInfo` = #{adsDetail.idMyInfo, jdbcType=INTEGER}, `adDate` = #{adsDetail.adDate, jdbcType=DATE}")
#SelectKey(statement = "SELECT LAST_INSERT_ID()", before = false, keyColumn = "id", keyProperty = "adsDetail.id", resultType = int.class )
void saveAdsDetails(#Param("adsDetail") AdsDetailsEntity adsDetail);
default void saveManyAdsDetails(#Param("adsDetails") List<AdsDetailsEntity> adsDetails)
{
for(AdsDetailsEntity adsDetail:adsDetails) {
saveAdsDetails(adsDetail);
}
}
Thanks to all of you, for the 3 suggestions!!!
yes. it doesnt work.
please take a look at mapper.dtd
foreach-tag doesnt support/provide the following properties statement, keyProperty order and resultType
if you need the id for each inserted item please let your DataAccessObject handle iteration and use something like this in your MapperInterface
#Insert("INSERT INTO tb_ads_details (idMyInfo, adDate) (#{adsDetail.idMyInfo, jdbcType=INTEGER}, #{adsDetail.adDate, jdbcType=DATE})")
#SelectKey(before = false, keyColumn = "ID", keyProperty = "id", resultType = Integer.class, statement = { "SELECT LAST_INSERT_ID()" } )
void saveAdsDetails(#Param("adsDetail") AdsDetailsEntity adsDetail);
please ensure AdsDetailsEntity-Class provides the properties idMyInfoand adDate
Edit 2019-08-21 07:25
some explanation
referring to the mentioned dtd the <selectKey>-tag is only allowed as direct child of <insert> and <update>. it refers to a single Object that is passed into the mapper-method and declared as parameterType.
its only executed once and its order property tells myBatis wether to execute it before or after the insert/update statement.
in your case, the <script> creates one single statement that is send to and handled by the database.
it is allowed to combine #Insert with <script> and <foreach> inside and #SelectKey. but myBatis doesnt intercept/observe/watch database handling the given statement. and as mentioned before, #SelectKey gets executed only once, before or after #Insert-execution. so in your particular case #SelectKey returns the id of the very last inserted element. if your script inserts ten elements, only the new generated id of tenth element will be returned. but #SelectKey requires a class-property with getter and setter to put the selected id into - which List<?> doesnt provide.
example
lets say you want to save an Advertisement and its AdvertisementDetails
Advertisement has an id, a date and details
public class Advertisement {
private List<AdvertisementDetail> adDetails;
private Date date;
private int id;
public Advertisement() {
super();
}
// getters and setters
}
AdvertisementDetail has its own id, a description and an id the Advertisementit belongs to
public class AdvertisementDetail {
private String description;
private int id;
private int idAdvertisement;
public AdvertisementDetail() {
super();
}
// getters and setters
}
the MyBatis-mapper could look like this. #Param is not used, so the properties are accessed direct.
#Mapper
public interface AdvertisementMapper {
#Insert("INSERT INTO tb_ads (date) (#{date, jdbcType=DATE})")
#SelectKey(
before = false,
keyColumn = "ID",
keyProperty = "id",
resultType = Integer.class,
statement = { "SELECT LAST_INSERT_ID()" })
void insertAdvertisement(
Advertisement ad);
#Insert("INSERT INTO tb_ads_details (idAdvertisement, description) (#{idAdvertisement, jdbcType=INTEGER}, #{description, jdbcType=VARCHAR})")
#SelectKey(
before = false,
keyColumn = "ID",
keyProperty = "id",
resultType = Integer.class,
statement = { "SELECT LAST_INSERT_ID()" })
void insertAdvertisementDetail(
AdvertisementDetail adDetail);
}
the DataAccessObject (DAO) could look like this
#Component
public class DAOAdvertisement {
#Autowired
private SqlSessionFactory sqlSessionFactory;
public DAOAdvertisement() {
super();
}
public void save(
final Advertisement advertisement) {
try (SqlSession session = this.sqlSessionFactory.openSession(false)) {
final AdvertisementMapper mapper = session.getMapper(AdvertisementMapper.class);
// insert the advertisement (if you have to)
// its new generated id is received via #SelectKey
mapper.insertAdvertisement(advertisement);
for (final AdvertisementDetail adDetail : advertisement.getAdDetails()) {
// set new generated advertisement-id
adDetail.setIdAdvertisement(advertisement.getId());
// insert adDetail
// its new generated id is received via #SelectKey
mapper.insertAdvertisementDetail(adDetail);
}
session.commit();
} catch (final PersistenceException e) {
e.printStackTrace();
}
}
}
What Chris wrote about inability to get ids in the foreach is correct. However there is a way to implement id fetching in mapper without the need to do it externally. This may be helpful if you use say spring and don't have a separate DAO layer and your mybatis mappers are the Repository.
You can use default interface method (see another tutorial about them) to insert the list of items by invoking a mapper method for single item insert and single item insert method does the id selection itself:
interface ItemMapper {
#Insert({"insert into myitem (item_column1, item_column2, ...)"})
#SelectKey(before = false, keyColumn = "ID",
keyProperty = "id", resultType = Integer.class,
statement = { "SELECT LAST_INSERT_ID()" } )
void saveItem(#Param("item") Item item);
default void saveItems(#Param("items") List<Item> items) {
for(Item item:items) {
saveItem(item);
}
}
MyBatis can assign generated keys to the list parameter if your DB/driver supports multiple generated keys via java.sql.Statement#getGeneratedKeys() (MS SQL Server, for example, does not support it, ATM).
The following example is tested with MySQL 5.7.27 + Connector/J 8.0.17 (you should include version info in the question).
Be sure to use the latest version of MyBatis (=3.5.2) as there have been several spec changes and bug fixes recently.
Table definition:
CREATE TABLE le tb_ads_details (
id INT PRIMARY KEY AUTO_INCREMENT,
description VARCHAR(32)
)
POJO:
private class AdsDetailsEntity {
private int id;
private String description;
// getters/setters
}
Mapper method:
#Insert({
"<script>",
"INSERT INTO tb_ads_details (description) VALUES",
"<foreach item='detail' collection='adsDetails' separator=','>",
" (#{detail.description})",
"</foreach>",
"</script>"
})
#Options(useGeneratedKeys = true, keyProperty="adsDetails.id", keyColumn="id")
void saveAdsDetails(#Param("adsDetails") List<AdsDetailsEntity> adsDetails);
Note: You should use batch insert (with ExecutorType.BATCH) instead of multi-row insert (=<foreach/>) when inserting a lot of rows.

How can I use an extended entity to create a new property in my EF6 class with property changed notification?

I have a table in my entity model called prices. It has several fields named value0, value1, value2, value3, value4... (these are their literal names, sigh..). I cannot rename them or in any way change them.
What I would like is to use an extended entity to create a new property called values. This would be a collection containing value1, value2 etc...
To get access to the values I would then simply need to write prices.values[1]
I need property changed notification for this.
So far I have tried this;
public partial class Prices
{
private ObservableCollection<double?> values = null;
public ObservableCollection<double?> Values
{
get
{
if (values != null)
values.CollectionChanged -= values_CollectionChanged;
else
values = new ObservableCollection<double?>(new double?[14]);
values[0] = value0;
values[1] = value1;
values[2] = value2;
values.CollectionChanged += values_CollectionChanged;
return values;
}
private set
{
value0 = value[0];
value1 = value[1];
value2 = value[2];
}
}
private void values_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
Values = values;
}
}
The issue comes when trying to set values. if I try to set a value by writing
prices.values[0] = someValue;
The new value is not always reflected in the collection (i.e. when I have previously set value and then try to overwrite the value).
I am willing to try any approach that would achieve my goal, I am not precious about having my solution fixed (although if anyone can explain what I'm missing that would be great!)
You could implement an indexer on Prices class without using a collection.
You can use switch to select the property to write or you can use reflection.
In this case I use reflection.
public double? this[int index]
{
get
{
if (index < 0 || index > 13) throw new ArgumentOutOfRangeException("index");
string propertyName = "Value" + index;
return (double?)GetType().GetProperty(propertyName).GetValue(this);
}
set
{
if (index < 0 || index > 13) throw new ArgumentOutOfRangeException("index");
string propertyName = "Value" + index;
GetType().GetProperty(propertyName).SetValue(this, value);
// Raise your event here
}
}

Can OpenJPA be used to reverse map a database view?

I have database views that have a column that uniquely identifies each row in the view. This column could be used as the primary key even though the view doesn't have a primary key in its definition (DDL) because it's a view.
OpenJPA is refusing to map the views to Java POJOs because there is no primary key.
I have a list of views and primary keys and I have a ReverseCustomizer. Is it possible I can give OpenJPA the column/field to be used as the primary key / id for each view / class?
Currently, the reverse mapping tool calls unmappedTable for each view and I'd like to tell the reverse mapper to do the mapping with the primary key I provide.
Here's something to help you get started.
public class MyDBReverseCustomizer implements ReverseCustomizer {
private ReverseMappingTool rmt;
#Override
public void setTool(ReverseMappingTool rmt) {
this.rmt = rmt;
}
#Override
public boolean unmappedTable(Table table) {
// this method is called to give this class an opportunity to map the
// table which would not be mapped otherwise. Returning false says
// the table wasn't mapped here.
//Class klass = rmt.generateClass(table.getIdentifier().getName(), null);
String packageName = rmt.getPackageName();
String tableName = table.getIdentifier().getName();
String className = NameConverters.convertTableName(tableName);
Class klass = rmt.generateClass(packageName+"."+className, null);
ClassMapping cls = rmt.newClassMapping(klass, table);
Column pk = null;
for ( Column column : table.getColumns() ) {
String columnName = column.getIdentifier().getName();
String fieldName = rmt.getFieldName(columnName, cls);
Class type = rmt.getFieldType(column, false);
FieldMapping field = cls.addDeclaredFieldMapping(fieldName, type);
field.setExplicit(true);
field.setColumns(new Column[]{column});
// TODO: set the appropriate strategy for non-primitive types.
field.setStrategy(new PrimitiveFieldStrategy(), null);
if ("MODEL_VIEW".equals(tableName) && "MODEL_ID".equals(columnName)) {
pk = column;
field.setPrimaryKey(true);
}
customize(field);
}
//cls.setPrimaryKeyColumns(new Column[]{pk});
cls.setObjectIdType(null, false);
cls.setIdentityType(ClassMapping.ID_DATASTORE);
cls.setStrategy(new FullClassStrategy(), null);
return true;
}
...
}

linq to entities

I have two three tables named
Language,Language Type,Employee
Fields of Language Type are LId,LanguageType
Fields Of Language are LId,EmpId,Language fluency
Fields of Employee are EmpId ,EmpName
Relationship between Language Type and language is one –to-one, and language and Employee table is many -to –one. Problem is when I enter data in language it displays the error
A dependent property in a Referential Constraint is mapped
to a store-generated column. Column: 'LId'."}.
Even though data is being inserted in language Type but not in language table .
lINQ query is for language table is
public void AddEmpLanguage(Language language, long id)
{
using (var context = new HRMSEntities())
{
Language emp = new Language
{
EmpId = id,
LanguageFluency = language.LanguageFluency,
};
context.Language.AddObject(emp);
}
}
You can try to add the language directly to a employee object
public void AddEmpLanguage(Language language, long empId)
{
using (var context = new HRMSEntities())
{
Language lang = new Language
{
LanguageFluency = language.LanguageFluency,
};
var employee = context.Employees.Find(empId);
/* or context.Employees.FirstOrDefault(x=>x.ID == empId); */
if(employee != null){
employee.Languages.Add(lang);
}
context.SaveChanges();
}
}

GWT SelectionModel is returning old selection

I have a cell table with an async data provider. If I update the data via the data provider the table renders the new data correctly but the selection model still holds onto and returns old objects.
Any ideas how to refresh the selection model?
I think you should make your SelectionModel work with different instance of the same "logical" object using the appropriate ProvidesKey. For instance, you could use ProvidesKey that calls getId on the object, so that two objects with the same such ID would be considered equal; so even if the SelectionModel holds onto the old object, it can still answer "yes, it's selected" when you give it the new object.
FYI, this is exactly what the EntityProxyKeyProvider does (using the stableId of the proxy). And the SimpleKeyProvider, used by default when you don't specify one, uses the object itself as its key.
I came across the same issue. Currently I have this as single selection model.
SelectedRow = store it when you select it.
Then when data is reloaded you can clear it by
celltable.getSelectionModel().setSelected(SelectedRow, false);
I guess it is too late for you but hope it helps someone else.
Here is my manual method for refreshing the SelectionModel. This allows you to use the selectedSet() when needed and it will actually contain the current data, rather than the old data - including the removal of deleted rows and updated fields!
I have included bits & pieces of a class extending DataGrid. This should have all the logic at least to solve your problems.
When a row is selected, call saveSelectionKeys().
When the grid data is altered call refeshSelectedSet().
If you know the key type, you can replace the isSameKey() method with something easier to deal with. This class uses generics, so this method attempts to figure out the object conversion itself.
.
public abstract class AsyncDataGrid<T> extends DataGrid<T> {
...
private MultiSelectionModel<T> selectionModel_;
private ListDataProvider<T> dataProvider_;
private List<T> dataList_;
private Set<Object> priorSelectionKeySet_;
private boolean canCompareKeys_;
...
public AsyncDataGrid( final ProvidesKey<T> keyProvider ){
super( keyProvider );
...
dataProvider_ = new ListDataProvider<T>();
dataList_ = dataProvider_.getList();
canCompareKeys_ = true;
...
}
private void saveSelectionKeys(){
priorSelectionKeySet_ = new HashSet<Object>();
Set<T> selectedSet = selectionModel_.getSelectedSet();
for( Iterator<T> it = selectedSet.iterator(); it.hasNext(); ) {
priorSelectionKeySet_.add( super.getValueKey( it.next() ) );
}
}
private void refeshSelectedSet(){
selectionModel_.clear();
if( priorSelectionKeySet_ != null ){
if( !canCompareKeys_ ) return;
for( Iterator<Object> keyIt = priorSelectionKeySet_.iterator(); keyIt.hasNext(); ) {
Object priorKey = keyIt.next();
for( Iterator<T> it = dataList_.iterator(); it.hasNext(); ) {
T row = it.next();
Object rowKey = super.getValueKey( row );
if( isSameKey( rowKey, priorKey ) ) selectionModel_.setSelected( row, true );
}
}
}
}
private boolean isSameRowKey( final T row1, final T row2 ) {
if( (row1 == null) || (row2 == null) ) return false;
Object key1 = super.getValueKey( row1 );
Object key2 = super.getValueKey( row2 );
return isSameKey( key1, key2 );
}
private boolean isSameKey( final Object key1, final Object key2 ){
if( (key1 == null) || (key1 == null) ) return false;
if( key1 instanceof Integer ){
return ( ((Integer) key1) - ((Integer) key2) == 0 );
}
else if( key1 instanceof Long ){
return ( ((Long) key1) - ((Long) key2) == 0 );
}
else if( key1 instanceof String ){
return ( ((String) key1).equals( ((String) key2) ) );
}
canCompareKeys_ = false;
return false;
}
}
I fixed my particular issue by using the following code to return the visible selection. It uses the selection model to determine what is selected and combines this with what is visible. The objects themselves are returned from the CellTable data which is always upto date if the data has ever been changed via an async provider (the selection model data maybe stale but the keys will be correct)
public Set<T> getVisibleSelection() {
/*
* 1) the selection model contains selection that can span multiple pages -
* we want to return just the visible selection
* 2) return the object from the cellTable and NOT the selection - the
* selection may have old, stale, objects if the data has been updated
* since the selection was made
*/
Set<Object> selectedSet = getKeys(selectionModel.getSelectedSet());
List<T> visibleSet = cellTable.getVisibleItems();
Set<T> visibleSelectionSet = new HashSet<T>();
for (T visible : visibleSet) {
if (selectedSet.contains(KEY_PROVIDER.getKey(visible))) {
visibleSelectionSet.add(visible);
}
}
return visibleSelectionSet;
}
public static Set<Object> getKeys(Collection<T> objects) {
Set<Object> ids = new HashSet<Object>();
for (T object : objects) {
ids.add(KEY_PROVIDER.getKey(object));
}
return ids;
}