/** * Copyright 2009-2013 Oy Vaadin Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.vaadin.addon.jpacontainer.fieldfactory; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.logging.Logger; import com.vaadin.addon.jpacontainer.EntityContainer; import com.vaadin.v7.data.Property; import com.vaadin.v7.data.Validator.InvalidValueException; import com.vaadin.v7.data.util.BeanItem; import com.vaadin.v7.data.util.BeanItemContainer; import com.vaadin.event.Action; import com.vaadin.ui.Button; import com.vaadin.ui.Button.ClickEvent; import com.vaadin.ui.Button.ClickListener; import com.vaadin.ui.Component; import com.vaadin.ui.CssLayout; import com.vaadin.v7.ui.CustomField; import com.vaadin.v7.ui.DefaultFieldFactory; import com.vaadin.v7.ui.Form; import com.vaadin.v7.ui.Table; import com.vaadin.v7.ui.Table.ColumnHeaderMode; import com.vaadin.v7.ui.TableFieldFactory; /** * TODO make referenced fields in embeddables properly editable TODO make this * work with basic data types (wrap value in a helper class ?). * */ @SuppressWarnings({ "unchecked", "rawtypes" }) public class ElementCollectionEditor extends CustomField implements Action.Handler, EmbeddableEditor { private static final Set<Class<?>> BASIC_DATA_TYPES = new HashSet<Class<?>>( Arrays.asList(Boolean.class, String.class, Integer.class, Long.class, Float.class, Double.class, Date.class, Number.class)); private final FieldFactory fieldFactory; private Class<?> referencedType; final private Action add = new Action(getMasterDetailAddItemCaption()); final private Action remove = new Action(getMasterDetailRemoveItemCaption()); final private Action[] actions = new Action[] { add, remove }; private BeanItemContainer container; private Table table; private String backReferencePropertyId; private final EntityContainer<?> containerForProperty; private Strategy strategy; /** * TODO make it possible to use this editor with Embedded types. * * @param containerForProperty * @param itemId * @param propertyId * @param uiContext */ public ElementCollectionEditor(FieldFactory fieldFactory, EntityContainer<?> containerForProperty, Object itemId, Object propertyId, Component uiContext) { this.fieldFactory = fieldFactory; this.containerForProperty = containerForProperty; containerForProperty.getItem(itemId).getEntity(); Class<?> masterEntityClass = containerForProperty.getEntityClass(); referencedType = fieldFactory.detectReferencedType( fieldFactory.getEntityManagerFactory(containerForProperty), propertyId, masterEntityClass); detectStrategy(); if (uiContext instanceof Form) { // copy write buffering eagerly from parent if Form, form sets // buffering mode for fields too late Form f = (Form) uiContext; boolean writeThrough = f.isBuffered(); setBuffered(writeThrough); } buildContainer(); setCaption(DefaultFieldFactory.createCaptionByPropertyId(propertyId)); } private void detectStrategy() { boolean isBasicDataType = BASIC_DATA_TYPES.contains(referencedType); if (isBasicDataType) { strategy = new ImmutableStrategy(); } else { strategy = new MutableStrategy(); } } private void buildContainer() { container = strategy.buildContainer(); } @Override public void setPropertyDataSource(Property newDataSource) { super.setPropertyDataSource(newDataSource); strategy.populateContainer(); } private void buildTable() { table = new Table(null, container); Object[] visibleProperties = fieldFactory .getVisibleProperties(referencedType); if (visibleProperties == null) { List<Object> asList = new ArrayList<Object>( Arrays.asList(getTable().getVisibleColumns())); asList.remove("id"); asList.remove(backReferencePropertyId); visibleProperties = asList.toArray(); } getTable().setPageLength(5); getTable().setVisibleColumns(visibleProperties); getTable().addActionHandler(this); getTable().setTableFieldFactory(getFieldFactoryForMasterDetailEditor()); getTable().setEditable(true); getTable().setSelectable(true); strategy.configureTable(); } protected Table getTable() { return table; } /** * TODO consider opening and adding parameters like propertyId, master class * etc * * @return */ private TableFieldFactory getFieldFactoryForMasterDetailEditor() { return fieldFactory; } protected String getMasterDetailRemoveItemCaption() { return "Remove"; } protected String getMasterDetailAddItemCaption() { return "Add"; } /* * (non-Javadoc) * * @see * com.vaadin.addon.jpacontainer.fieldfactory.JPAContainerCustomField#getType * () */ @Override public Class<?> getType() { return referencedType; } public void handleAction(Action action, Object sender, Object target) { if (action == add) { addNew(); } else { remove(target); } } public Action[] getActions(Object target, Object sender) { return actions; } private void remove(Object itemId) { if (itemId != null) { strategy.remove(itemId); } } private void addNew() { try { strategy.addNew(); } catch (Exception e) { Logger.getLogger(getClass().getName()).warning( "Could not instantiate detail instance " + container.getBeanType().getName()); } } @Override public void commit() throws SourceException, InvalidValueException { if (!isBuffered()) { strategy.commit(); } else { super.commit(); } } public EntityContainer getMasterEntityContainer() { return containerForProperty; } public Class<?> getEmbeddedClassType() { return referencedType; } public Collection getElements() { return (Collection) getPropertyDataSource().getValue(); } private void notifyPropertyOfChangedList() { // reset the same collection to item to notify entity item that the // property has changed. Needed to make property "dirty". getPropertyDataSource().setValue(getElements()); } private interface Strategy { BeanItemContainer buildContainer(); void configureTable(); void commit(); void remove(Object itemId); void addNew() throws InstantiationException, IllegalAccessException; void populateContainer(); } private class MutableStrategy implements Strategy { public BeanItemContainer buildContainer() { return new BeanItemContainer(referencedType); } public void populateContainer() { Collection<?> elements = getElements(); container.addAll(elements); } public void addNew() throws InstantiationException, IllegalAccessException { Object newInstance = container.getBeanType().newInstance(); container.addBean(newInstance); if (isBuffered()) { getElements().add(newInstance); notifyPropertyOfChangedList(); } } public void remove(Object itemId) { BeanItem item = container.getItem(itemId); container.removeItem(itemId); if (isBuffered()) { Collection collection = getElements(); collection.remove(item.getBean()); notifyPropertyOfChangedList(); } } public void commit() { Collection c = (Collection) getPropertyDataSource().getValue(); boolean isNew = c == null; HashSet orphaned = !isNew ? new HashSet(c) : null; Collection itemIds = container.getItemIds(); for (Object object : itemIds) { Object element = object; if (!isNew) { orphaned.remove(element); } if (c == null) { try { c = MultiSelectConverter .createNewCollectionForType(referencedType); } catch (InstantiationException e) { throw new SourceException(ElementCollectionEditor.this, e); } catch (IllegalAccessException e) { throw new SourceException(ElementCollectionEditor.this, e); } } if (isNew || !c.contains(element)) { c.add(element); } } if (!isNew) { c.removeAll(orphaned); } notifyPropertyOfChangedList(); } public void configureTable() { } } public class ValueHolder { private Object value; public ValueHolder(Object o) { value = o; } public Object getValue() { return value; } public void setValue(Object newValue) { if (this.value != newValue) { if (newValue == null) { if (isBuffered()) { getElements().remove(value); notifyPropertyOfChangedList(); } } else if (!newValue.equals(value) && isBuffered()) { getElements().remove(value); getElements().add(newValue); notifyPropertyOfChangedList(); } } this.value = newValue; } } private class ImmutableStrategy implements Strategy { public BeanItemContainer buildContainer() { return new BeanItemContainer<ValueHolder>(ValueHolder.class); } public void populateContainer() { Collection<?> elements = getElements(); ArrayList<ValueHolder> wrappedElements = new ArrayList<ValueHolder>(); for (Object o : elements) { wrappedElements.add(new ValueHolder(o)); } container.addAll(wrappedElements); } public void addNew() throws InstantiationException, IllegalAccessException { Object newInstance = referencedType.newInstance(); container.addBean(new ValueHolder(newInstance)); if (isBuffered()) { getElements().add(newInstance); notifyPropertyOfChangedList(); } } public void remove(Object itemId) { BeanItem item = container.getItem(itemId); container.removeItem(itemId); if (isBuffered()) { Collection collection = getElements(); collection.remove(((ValueHolder) item.getBean()).getValue()); notifyPropertyOfChangedList(); } } public void commit() { Collection c = getElements(); boolean isNew = c == null; HashSet orphaned = !isNew ? new HashSet(c) : null; Collection<ValueHolder> itemIds = container.getItemIds(); for (ValueHolder object : itemIds) { Object element = object.getValue(); if (!isNew) { orphaned.remove(element); } if (c == null) { try { c = MultiSelectConverter .createNewCollectionForType(referencedType); } catch (InstantiationException e) { throw new SourceException(ElementCollectionEditor.this, e); } catch (IllegalAccessException e) { throw new SourceException(ElementCollectionEditor.this, e); } } if (isNew || !c.contains(element)) { c.add(element); } } if (!isNew) { c.removeAll(orphaned); } notifyPropertyOfChangedList(); } public void configureTable() { getTable().setColumnHeaderMode(ColumnHeaderMode.HIDDEN); } } @Override protected Component initContent() { CssLayout vl = new CssLayout(); buildTable(); vl.addComponent(getTable()); CssLayout buttons = new CssLayout(); buttons.addComponent(new Button(getMasterDetailAddItemCaption(), new ClickListener() { public void buttonClick(ClickEvent event) { addNew(); } })); // TODO replace with a (-) button in a generated column? Table currently // not selectable. buttons.addComponent(new Button(getMasterDetailRemoveItemCaption(), new ClickListener() { public void buttonClick(ClickEvent event) { remove(getTable().getValue()); } })); vl.addComponent(buttons); return vl; } }