CustomField - implementing date range field, rollback restoring wrong value

Hi, i am trying to implement DateRangeField that would display two date fields for start and end date.
It will be bound to simple bean:

public class DateRange implements Serializable {
    protected Date startDate;
    protected Date endDate;
    // getters/setters
}

I have chosen to extend CustomField, with Horizontal layout as composition root, and two date fields inside it.
What i am doing is:

  • set DateRangeField internal value whenerver start/end DateField interval value changes
  • inside setPropertyDataSource i am creating DateRange instance if its null, and then bind start/end to startDate and endDate from DateRange bean
  • to DateRangeField i am adding validator for checking that start date is before end date, its working ok


Almost everything is working as i wanted, e.g. commit, discard, etc. but when commiting with bean field group, in case of DateRangeField commit passed, and some other field failed, rollback restores wrong(current) value, the value that it already had

Its thrid day i am trying to fix it, testing various approaches for setting value, and i cant figure it out. Are there better ways to handle this?

public class DateRangeField extends CustomField<DateRange> {
    private static final long serialVersionUID = 7462756001701404411L;
    public static final String PRIMIARY_STYLE = "date-range-field";

    protected Layout layout;
    protected DateField startDf;
    protected DateField endDf;
    
    public DateRangeField() {
        setPrimaryStyleName(PRIMIARY_STYLE);
        setInvalidAllowed(false);

        startDf = new DateField() {
            private static final long serialVersionUID = -8860680044941586329L;

            @Override
            protected void setInternalValue(Date newValue) {
                super.setInternalValue(newValue);
                DateRange dateRange = DateRangeField.this.getInternalValue();
                DateRange newInternal = new DateRange(newValue,
                        dateRange.endDate);
                DateRangeField.this.setInternalValue(newInternal);
            }
        };
        endDf = new DateField() {
            private static final long serialVersionUID = -9045301342847295418L;

            @Override
            protected void setInternalValue(Date newValue) {
                super.setInternalValue(newValue);
                DateRange dateRange = DateRangeField.this.getInternalValue();
                DateRange newInternal = new DateRange(dateRange.startDate,
                        newValue);
                DateRangeField.this.setInternalValue(newInternal);
            }
        };

        setBuffered(true);
    }

    @SuppressWarnings({ "rawtypes", "unchecked" })
    @Override
    public void setPropertyDataSource(Property newDataSource) {
        DateRange dateRange = (DateRange) newDataSource.getValue();
        if (dateRange == null) {
            dateRange = new DateRange();
            newDataSource.setValue(dateRange);
        }
        super.setPropertyDataSource(newDataSource);
        
        startDf.setPropertyDataSource(new TransactionalPropertyWrapper<Date>(
                new MethodProperty<Date>(dateRange, "startDate")));
        endDf.setPropertyDataSource(new TransactionalPropertyWrapper<Date>(
                new MethodProperty<Date>(dateRange, "endDate")));
    }

    @Override
    protected Component initContent() {
        layout = new HorizontalLayout();
        layout.addComponent(startDf);
        layout.addComponent(endDf);
        return layout;
    }

    @Override
    public Class<? extends DateRange> getType() {
        return DateRange.class;
    }
    
    @Override
    public void commit() throws SourceException, InvalidValueException {
        validate();
        startDf.commit();
        endDf.commit();
        super.commit();
    }

    @Override
    public void discard() throws SourceException {
        startDf.discard();
        endDf.discard();
        super.discard();
    }

    public void setResolution(Resolution resolution) {
        startDf.setResolution(resolution);
        endDf.setResolution(resolution);
    }

    @Override
    public void setImmediate(boolean immediate) {
        super.setImmediate(immediate);
        startDf.setImmediate(immediate);
        endDf.setImmediate(immediate);
    }

    @Override
    public void setBuffered(boolean buffered) {
        super.setBuffered(buffered);
        startDf.setBuffered(buffered);
        endDf.setBuffered(buffered);
    }

    @Override
    public void setReadOnly(boolean readOnly) {
        super.setReadOnly(readOnly);
        startDf.setReadOnly(readOnly);
        endDf.setReadOnly(readOnly);
    }

    public Layout getLayout() {
        return layout;
    }
}

Try this:

try {
   fieldGroup.commit();
} catch (CommitException e) {
   fieldGroup.discard();
}

hi, thanks for reply,
unfortunately the issue remains

Could you paste or push into GitHub a full example app? Then, I can better see what’s the issue in your case.

i think i have resolved the issue about 1 hour ago without realising it.

i have introduced another variable ‘internalValue’ and bound date fields to it. Then i am manually setting values at commit/discard, i doubt its corrent way, but i cant spend more time on this.

now, there is not point of making example app, i will just exmplain in detail the problem i was facing:

my DateRangeField was bound with TransactionalPropertyWrapper,
1 during commit, fieldGroup is starting an transaction on every transactional property in the form
2 now, in a loop, every field in the form is commited,
assume there are 3 fields: TextField
name
, DateRangeField
drf
, TextField
someOtherField
, which are commited in that order.
3 TextField
name
.commit() - OK passed
4 DateRangeField
drf
.commit() - OK passed
5 TextField
someOtherField
.commit() - Validation error, failed
6 fieldGroup now rollback every transactional property, the rollback should restore previous commited value, even though fields
name
and
drf
was just commited successfully, the problem was, my field was restored to just commited value, not the value before that

my current code, need some polishing, but shuld work

public class DateRangeField extends CustomField<DateRange> {
    private static final long serialVersionUID = 7462756001701404411L;
    public static final String PRIMIARY_STYLE = "date-range-field";

    protected Layout layout;
    protected DateField startDf;
    protected DateField endDf;

    protected DateRange internalValue;

    public DateRangeField() {
        setPrimaryStyleName(PRIMIARY_STYLE);
        setInvalidAllowed(false);

        startDf = new DateField();
        endDf = new DateField();

        setBuffered(true);
    }

    @SuppressWarnings("rawtypes")
    @Override
    public void setPropertyDataSource(Property newDataSource) {
        super.setPropertyDataSource(newDataSource);

        DateRange dateRange = (DateRange) newDataSource.getValue();
        if (dateRange != null) {
            internalValue = dateRange.clone();
        } else {
            internalValue = new DateRange();
        }
        startDf.setPropertyDataSource(new MethodProperty<Date>(internalValue,
                "startDate"));
        endDf.setPropertyDataSource(new MethodProperty<Date>(internalValue,
                "endDate"));
    }

    @Override
    protected Component initContent() {
        layout = new HorizontalLayout();
        layout.addComponent(startDf);
        layout.addComponent(endDf);
        return layout;
    }

    @Override
    public Class<? extends DateRange> getType() {
        return DateRange.class;
    }

    @Override
    public void commit() throws SourceException, InvalidValueException {
        startDf.commit();
        endDf.commit();
        setInternalValue(internalValue.clone());
        super.commit();
    }

    @Override
    public void discard() throws SourceException {
        super.discard();
        DateRange i = getInternalValue();
        if(i != null) {
            startDf.setValue(i.startDate);
            endDf.setValue(i.endDate);
        } else {
            startDf.setValue(null);
            endDf.setValue(null);
        }
    }

    public void setResolution(Resolution resolution) {
        startDf.setResolution(resolution);
        endDf.setResolution(resolution);
    }

    @Override
    public void setImmediate(boolean immediate) {
        super.setImmediate(immediate);
        startDf.setImmediate(immediate);
        endDf.setImmediate(immediate);
    }

    @Override
    public void setBuffered(boolean buffered) {
        super.setBuffered(buffered);
        startDf.setBuffered(buffered);
        endDf.setBuffered(buffered);
    }

    @Override
    public void setReadOnly(boolean readOnly) {
        super.setReadOnly(readOnly);
        startDf.setReadOnly(readOnly);
        endDf.setReadOnly(readOnly);
    }

    public Layout getLayout() {
        return layout;
    }
}