/* * Copyright 2011 Instituto Superior Tecnico * * https://fenix-ashes.ist.utl.pt/ * * This file is part of the vaadin-framework. * * The vaadin-framework Infrastructure is free software: you can * redistribute it and/or modify it under the terms of the GNU Lesser General * Public License as published by the Free Software Foundation, either version * 3 of the License, or (at your option) any later version.* * * vaadin-framework 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with vaadin-framework. If not, see <http://www.gnu.org/licenses/>. * */ package pt.ist.vaadinframework.data; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.EventObject; import java.util.HashMap; import java.util.LinkedList; import org.apache.commons.lang.StringUtils; import pt.ist.fenixframework.Atomic; import pt.ist.vaadinframework.data.util.ServiceUtils; import com.vaadin.data.Buffered; import com.vaadin.data.BufferedValidatable; import com.vaadin.data.Item; import com.vaadin.data.Property; import com.vaadin.data.Validatable; import com.vaadin.data.Validator.InvalidValueException; public abstract class AbstractBufferedItem<Id, Type> extends BufferedProperty<Type> implements Item, Item.PropertySetChangeNotifier { private final LinkedList<Id> list = new LinkedList<Id>(); private final HashMap<Id, Property> map = new HashMap<Id, Property>(); private ItemConstructor<Id> constructor; private ItemWriter<Id> writer; private LinkedList<Item.PropertySetChangeListener> propertySetChangeListeners = null; private boolean propertySetChangePropagationEnabled = true; private Item.PropertySetChangeEvent lastEvent; public AbstractBufferedItem(Property wrapped, Hint... hints) { super(wrapped, hints); } public AbstractBufferedItem(Class<? extends Type> type, Hint... hints) { super(type, hints); } public AbstractBufferedItem(Type value, Hint... hints) { super(value, hints); } public AbstractBufferedItem(Type value, Class<? extends Type> type, Hint... hints) { super(value, type, hints); } @Override protected void processNewCacheValue() { for (Id propertyId : getItemPropertyIds()) { if (getItemProperty(propertyId) instanceof Buffered) { ((Buffered) getItemProperty(propertyId)).discard(); } } } @Override public boolean addItemProperty(Object propertyId, Property property) { // Null ids are not accepted if (propertyId == null) { throw new NullPointerException("Item property id can not be null"); } // Cant add a property twice if (map.containsKey(propertyId)) { return false; } // Put the property to map map.put((Id) propertyId, property); list.add((Id) propertyId); // Send event fireItemPropertySetChange(); return true; } @Override public boolean removeItemProperty(Object propertyId) { // Cant remove missing properties if (map.remove(propertyId) == null) { return false; } list.remove(propertyId); // propertyValues.remove(propertyId); // Send change events fireItemPropertySetChange(); return true; } @Override public Collection<Id> getItemPropertyIds() { return list != null ? Collections.unmodifiableCollection(list) : (Collection<Id>) Collections.emptyList(); } public void setConstructor(ItemConstructor<Id> constructor) { this.constructor = constructor; } public void setWriter(ItemWriter<Id> writer) { this.writer = writer; } @Override public Property getItemProperty(Object propertyId) { Property property = map.get(propertyId); if (property == null) { property = makeProperty((Id) propertyId); } return property; } /** * Lazy creation of properties, this method is invoked for every propertyId * that is requested of the Item. The created properties are not * automatically registered in the item, you have to invoke {@link #addItemProperty(Object, Property)} yourself. You also need * to * ensure that the returned properties are of {@link BufferedProperty}s or {@link Item}s or {@link Collection}s over * {@link BufferedProperty}s. * * @param propertyId * The key of the property. * @return A {@link Property} instance. */ protected abstract Property makeProperty(Id propertyId); @Atomic @Override public void commit() throws SourceException, InvalidValueException { try { if (!isInvalidCommitted() && !isValid()) { validate(); } if (cache == null) { construct(true); fireValueChange(); } else { applyWriter(); } for (Id propertyId : getItemPropertyIds()) { if (getItemProperty(propertyId) instanceof Buffered) { ((Buffered) getItemProperty(propertyId)).commit(); } } if (isModified()) { wrapped.setValue(cache); } discard(); modified = false; } catch (Throwable e) { ServiceUtils.handleException(e); throw new SourceException(AbstractBufferedItem.this, e); } } private void construct(boolean taint) { Object value; if (constructor != null) { try { Method method = findMethod(constructor.getClass(), getArgumentTypes(constructor.getOrderedArguments())); Object[] argumentValues = readArguments(constructor.getOrderedArguments()); value = method.invoke(constructor, argumentValues); } catch (Throwable e) { ServiceUtils.handleException(e); throw new SourceException(this, e); } } else { try { value = getType().newInstance(); } catch (Throwable e) { ServiceUtils.handleException(e); throw new SourceException(this, e); } } cache = convertValue(value); modified = taint; } private void applyWriter() { if (writer != null) { try { if (fieldDiffer(writer.getOrderedArguments())) { LinkedList<Class<?>> argumentTypes = new LinkedList<Class<?>>(); argumentTypes.add(getType()); argumentTypes.addAll(Arrays.asList(getArgumentTypes(writer.getOrderedArguments()))); Method method = findMethod(writer.getClass(), argumentTypes.toArray(new Class<?>[0])); LinkedList<Object> argumentValues = new LinkedList<Object>(); argumentValues.add(cache); argumentValues.addAll(Arrays.asList(readArguments(writer.getOrderedArguments()))); // VaadinFrameworkLogger.getLogger().debug( // "persisting item with writer with properties: [" // + StringUtils.join(writer.getOrderedArguments(), ", ") + // "] with values: [" // + StringUtils.join(argumentValues.subList(1, // argumentValues.size()), ", ") + "]"); method.invoke(writer, argumentValues.toArray(new Object[0])); for (Id id : writer.getOrderedArguments()) { if (getItemProperty(id) instanceof Buffered) { ((Buffered) getItemProperty(id)).discard(); } } } } catch (Throwable e) { ServiceUtils.handleException(e); throw new SourceException(this, e); } } } private ArrayList<Throwable> getAllCauses(Throwable t) { final ArrayList<Throwable> causes = new ArrayList<Throwable>(); causes.add(t); if (t instanceof Buffered.SourceException) { for (Throwable sec : ((Buffered.SourceException) t).getCauses()) { causes.addAll(getAllCauses(sec)); } } else { if (t.getCause() != null) { causes.addAll(getAllCauses(t.getCause())); } } return causes; } // private Buffered.SourceException // handleDomainException(Buffered.SourceException se) { // final ArrayList<Throwable> causes = new ArrayList<Throwable>(); // for (Throwable throwable : getAllCauses(se)) { // if (throwable instanceof FFDomainException) { // return new Buffered.SourceException(se.getSource(), // new Throwable[] { new DomainExceptionErrorMessage(throwable) }); // } // causes.add(throwable); // } // return new Buffered.SourceException(se.getSource(), causes.toArray(new // Throwable[0])); // } private boolean fieldDiffer(Id[] arguments) { for (Id propertyId : arguments) { if (getItemProperty(propertyId) instanceof Buffered) { if (((Buffered) getItemProperty(propertyId)).isModified()) { return true; } } else { return true; } } return false; } private Method findMethod(Class<?> type, Class<?>[] types) throws NoSuchMethodException { for (Method method : type.getMethods()) { Class<?>[] mTypes = method.getParameterTypes(); boolean match = true; for (int i = 0; i < types.length; i++) { if (i >= mTypes.length) { match = false; break; } if (!mTypes[i].isAssignableFrom(Object.class) && !mTypes[i].isAssignableFrom(types[i])) { match = false; break; } } if (!getType().isAssignableFrom(method.getReturnType())) { match = false; } if (match) { return method; } } final String message = "Must specify a method in class %s with a signature compatible with the arguments in getOrderedArguments() [%s]"; throw new NoSuchMethodException(String.format(message, type.getName(), StringUtils.join(types, ","))); } private Class<?>[] getArgumentTypes(Id[] argumentIds) { Class<?>[] types = new Class<?>[argumentIds.length]; for (int i = 0; i < argumentIds.length; i++) { types[i] = getItemProperty(argumentIds[i]).getType(); } return types; } private Object[] readArguments(Id[] argumentIds) { Object[] arguments = new Object[argumentIds.length]; for (int i = 0; i < argumentIds.length; i++) { if (getItemProperty(argumentIds[i]) instanceof AbstractBufferedItem) { ((AbstractBufferedItem<?, ?>) getItemProperty(argumentIds[i])).construct(false); } arguments[i] = getItemProperty(argumentIds[i]).getValue(); } return arguments; } @Override public void discard() throws SourceException { super.discard(); for (Id propertyId : getItemPropertyIds()) { if (getItemProperty(propertyId) instanceof Buffered) { ((Buffered) getItemProperty(propertyId)).discard(); } } } @Override public void setWriteThrough(boolean writeThrough) throws SourceException, InvalidValueException { for (Id propertyId : getItemPropertyIds()) { if (getItemProperty(propertyId) instanceof Buffered) { ((Buffered) getItemProperty(propertyId)).setWriteThrough(writeThrough); } } super.setWriteThrough(writeThrough); } @Override public void setReadThrough(boolean readThrough) throws SourceException { for (Id propertyId : getItemPropertyIds()) { if (getItemProperty(propertyId) instanceof Buffered) { ((Buffered) getItemProperty(propertyId)).setReadThrough(readThrough); } } super.setReadThrough(readThrough); } @Override public void setInvalidAllowed(boolean invalidAllowed) throws UnsupportedOperationException { for (Id propertyId : getItemPropertyIds()) { if (getItemProperty(propertyId) instanceof Validatable) { ((Validatable) getItemProperty(propertyId)).setInvalidAllowed(invalidAllowed); } } super.setInvalidAllowed(invalidAllowed); } @Override public void setInvalidCommitted(boolean invalidCommitted) { for (Id propertyId : getItemPropertyIds()) { if (getItemProperty(propertyId) instanceof BufferedValidatable) { ((BufferedValidatable) getItemProperty(propertyId)).setInvalidCommitted(invalidCommitted); } } super.setInvalidCommitted(invalidCommitted); } @Override public boolean isValid() { for (Id propertyId : getItemPropertyIds()) { if (getItemProperty(propertyId) instanceof Validatable) { if (!((Validatable) getItemProperty(propertyId)).isValid()) { return false; } } } return super.isValid(); } @Override public void validate() throws InvalidValueException { for (Id propertyId : getItemPropertyIds()) { if (getItemProperty(propertyId) instanceof Validatable) { ((Validatable) getItemProperty(propertyId)).validate(); } } super.validate(); } /* Notifiers */ private class PropertySetChangeEvent extends EventObject implements Item.PropertySetChangeEvent { private PropertySetChangeEvent(Item source) { super(source); } /** * Gets the Item whose Property set has changed. * * @return source object of the event as an <code>Item</code> */ @Override public Item getItem() { return (Item) getSource(); } } /** * Registers a new property set change listener for this Item. * * @param listener * the new Listener to be registered. */ @Override public void addListener(Item.PropertySetChangeListener listener) { if (propertySetChangeListeners == null) { propertySetChangeListeners = new LinkedList<PropertySetChangeListener>(); } propertySetChangeListeners.add(listener); } /** * Removes a previously registered property set change listener. * * @param listener * the Listener to be removed. */ @Override public void removeListener(Item.PropertySetChangeListener listener) { if (propertySetChangeListeners != null) { propertySetChangeListeners.remove(listener); } } /** * Sends a Property set change event to all interested listeners. */ protected void fireItemPropertySetChange() { if (propertySetChangeListeners != null) { final Item.PropertySetChangeEvent event = new AbstractBufferedItem.PropertySetChangeEvent(this); if (propertySetChangePropagationEnabled) { final Object[] l = propertySetChangeListeners.toArray(); for (Object element : l) { ((Item.PropertySetChangeListener) element).itemPropertySetChange(event); } } else { lastEvent = event; } } } public void setPropertySetChangePropagationEnabled(boolean propertySetChangePropagationEnabled) { if (this.propertySetChangePropagationEnabled != propertySetChangePropagationEnabled) { this.propertySetChangePropagationEnabled = propertySetChangePropagationEnabled; if (propertySetChangePropagationEnabled && lastEvent != null) { if (propertySetChangeListeners != null) { final Object[] l = propertySetChangeListeners.toArray(); for (Object element : l) { ((Item.PropertySetChangeListener) element).itemPropertySetChange(lastEvent); } } lastEvent = null; } } } @Override public Collection<?> getListeners(Class<?> eventType) { if (Item.PropertySetChangeEvent.class.isAssignableFrom(eventType)) { if (propertySetChangeListeners == null) { return Collections.EMPTY_LIST; } return Collections.unmodifiableCollection(propertySetChangeListeners); } return Collections.EMPTY_LIST; } }