
com.vaadin.addon.jpacontainer.JPAContainerItem Maven / Gradle / Ivy
/*
* JPAContainer
* Copyright (C) 2010 Oy IT Mill Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
package com.vaadin.addon.jpacontainer;
import com.vaadin.data.Property;
import com.vaadin.data.Property.ValueChangeListener;
import com.vaadin.data.Validator.InvalidValueException;
import java.lang.reflect.Constructor;
import java.util.Collection;
import java.util.EventObject;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
/**
* {@link EntityItem}-implementation that is used by {@link JPAContainer}.
* Should not be used directly by clients.
*
* @author Petter Holmström (IT Mill)
* @since 1.0
*/
final class JPAContainerItem implements EntityItem {
private static final long serialVersionUID = 3835181888110236341L;
private static boolean nullSafeEquals(Object o1, Object o2) {
try {
return o1 == o2 || o1.equals(o2);
} catch (NullPointerException e) {
return false;
}
}
/**
* {@link Property}-implementation that is used by {@link EntityItem}.
* Should not be used directly by clients.
*
* @author Petter Holmström (IT Mill)
* @since 1.0
*/
final class ItemProperty implements EntityItemProperty {
private static final long serialVersionUID = 2791934277775480650L;
private String propertyId;
private Object cachedValue;
/**
* Creates a new ItemProperty
.
*
* @param propertyId
* the property id of the new property (must not be null).
*/
ItemProperty(String propertyId) {
assert propertyId != null : "propertyId must not be null";
this.propertyId = propertyId;
// Initialize cached value if necessary
if (!isWriteThrough()) {
cacheRealValue();
}
}
/**
* Like the name suggests, this method notifies the listeners if the
* cached value and real value are different.
*/
void notifyListenersIfCacheAndRealValueDiffer() {
Object realValue = getRealValue();
if (!nullSafeEquals(realValue, cachedValue)) {
notifyListeners();
}
}
/**
* Caches the real value of the property.
*/
void cacheRealValue() {
Object realValue = getRealValue();
cachedValue = realValue;
}
/**
* Clears the cached value, without notifying any listeners.
*/
void clearCache() {
cachedValue = null;
}
/**
* Note! This method assumes that write through is OFF!
*
* Sets the real value to the cached value. If read through is on, the
* listeners are also notified as the value will appear to have changed
* to them.
*
* If the property is read only, nothing happens.
*
* @throws com.vaadin.data.Property.ConversionException
* if the real value could not be set for some reason.
*/
void commit() throws ConversionException {
if (!isReadOnly()) {
try {
setRealValue(cachedValue);
} catch (Exception e) {
throw new ConversionException(e);
}
/*
* If the observers are watching the cached item, there is no
* need for a notification as the value will not have changed to
* them. However, if they are wathing the backend entity a
* notification is required.
*/
if (isReadThrough()) {
notifyListeners();
}
}
}
/**
* Note! This method assumes that write through is OFF!
*
* Replaces the cached value with the real value. If read through is
* off, the listeners are also notified as the value will appera to have
* changed to them.
*/
void discard() {
Object realValue = getRealValue();
if (!nullSafeEquals(realValue, cachedValue)) {
cacheRealValue();
/*
* No use notifying the listeners if they are wathing the
* backend entity.
*/
if (!isReadThrough()) {
notifyListeners();
}
} else {
cacheRealValue();
}
}
public EntityItem> getItem() {
return JPAContainerItem.this;
}
public Class> getType() {
return propertyList.getPropertyType(propertyId);
}
public Object getValue() {
if (isReadThrough()) {
return getRealValue();
} else {
return cachedValue;
}
}
/**
* Gets the real value from the backend entity.
*
* @return the real value.
*/
private Object getRealValue() {
return propertyList.getPropertyValue(entity, propertyId);
}
@Override
public String toString() {
final Object value = getValue();
if (value == null) {
return null;
}
return value.toString();
}
public boolean isReadOnly() {
return !propertyList.isPropertyWritable(propertyId);
}
/**
* This functionality is not supported by this
* implementation.
*
* {@inheritDoc }
*/
public void setReadOnly(boolean newStatus) {
throw new UnsupportedOperationException(
"The read only state cannot be changed");
}
/**
* Sets the real value of the property to newValue
. The
* value is expected to be of the correct type at this point (i.e. any
* conversions from a String should have been done already). As this
* method updates the backend entity object, it also turns on the
* dirty
flag of the item.
*
* @see JPAContainerItem#isDirty()
* @param newValue
* the new value to set.
*/
private void setRealValue(Object newValue) {
propertyList.setPropertyValue(entity, propertyId, newValue);
dirty = true;
}
public void setValue(Object newValue) throws ReadOnlyException,
ConversionException {
if (isReadOnly()) {
throw new ReadOnlyException();
}
if (newValue != null
&& !getType().isAssignableFrom(newValue.getClass())) {
/*
* The type we try to set is incompatible with the type of the
* property. We therefore try to convert the value to a string
* and see if there is a constructor that takes a single string
* argument. If this fails, we throw an exception.
*/
try {
// Gets the string constructor
final Constructor> constr = getType().getConstructor(
new Class[] { String.class });
newValue = constr.newInstance(new Object[] { newValue
.toString() });
} catch (Exception e) {
throw new ConversionException(e);
}
}
try {
if (isWriteThrough()) {
setRealValue(newValue);
container.containerItemPropertyModified(
JPAContainerItem.this, propertyId);
} else {
cachedValue = newValue;
modified = true;
}
} catch (Exception e) {
throw new ConversionException(e);
}
if (!isReadThrough() || isWriteThrough()) {
/*
* We don't want to notify the listeners if we have only updated
* the cached value and they are watching the real value.
*/
notifyListeners();
}
}
private List listeners;
private class ValueChangeEvent extends EventObject implements
Property.ValueChangeEvent {
private static final long serialVersionUID = 4999596001491426923L;
private ValueChangeEvent(ItemProperty source) {
super(source);
}
public Property getProperty() {
return (Property) getSource();
}
}
/**
* Notifies all the listeners that the value of the property has
* changed.
*/
private void notifyListeners() {
if (listeners != null) {
final Object[] l = listeners.toArray();
final Property.ValueChangeEvent event = new ValueChangeEvent(
this);
for (int i = 0; i < l.length; i++) {
((Property.ValueChangeListener) l[i]).valueChange(event);
}
}
}
public void addListener(ValueChangeListener listener) {
assert listener != null : "listener must not be null";
if (listeners == null) {
listeners = new LinkedList();
}
listeners.add(listener);
}
public void removeListener(ValueChangeListener listener) {
assert listener != null : "listener must not be null";
if (listeners != null) {
listeners.remove(listener);
if (listeners.isEmpty()) {
listeners = null;
}
}
}
}
private T entity;
private JPAContainer container;
private PropertyList propertyList;
private Map