/**
* 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.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import javax.persistence.ManyToMany;
import com.vaadin.addon.jpacontainer.EntityContainer;
import com.vaadin.addon.jpacontainer.EntityItem;
import com.vaadin.addon.jpacontainer.EntityItemProperty;
import com.vaadin.addon.jpacontainer.metadata.EntityClassMetadata;
import com.vaadin.addon.jpacontainer.metadata.MetadataFactory;
import com.vaadin.addon.jpacontainer.metadata.PropertyMetadata;
import com.vaadin.v7.data.util.converter.Converter;
import com.vaadin.v7.ui.AbstractSelect;
public class MultiSelectConverter<T> implements
Converter<Collection<Object>, Collection<T>> {
private final AbstractSelect select;
private Boolean owningSide;
private String mappedBy;
public MultiSelectConverter(AbstractSelect select) {
this.select = select;
}
@SuppressWarnings("unchecked")
private EntityContainer<T> getContainer() {
return (EntityContainer<T>) select.getContainerDataSource();
}
@Override
public Collection<Object> convertToPresentation(Collection<T> value,
Class<? extends Collection<Object>> targetType, Locale locale)
throws com.vaadin.v7.data.util.converter.Converter.ConversionException {
// Value here is a collection of entities, should be transformed to a
// collection (set) of identifier
// TODO, consider creating a cached value
if (value == null || value.isEmpty()) {
try {
return createNewCollectionForType(getPropertyDataSource()
.getType());
} catch (Exception e) {
throw new ConversionException(e);
}
}
HashSet<Object> identifiers = new HashSet<Object>();
for (T entity : value) {
Object identifier = getContainer().getEntityProvider()
.getIdentifier(entity);
identifiers.add(identifier);
}
return identifiers;
}
@Override
public Collection<T> convertToModel(Collection<Object> value,
Class<? extends Collection<T>> targetType, Locale locale)
throws com.vaadin.v7.data.util.converter.Converter.ConversionException {
// NOTE, this currently works properly only if equals and hashcode
// methods have been implemented correctly (both depending on identifier
// of the entity)
// TODO create a filter that has a workaround for invalid
// equals/hashCode
// formattedValue here is a set of identifiers.
// We will modify the existing collection of entities to contain
// corresponding entities
Collection<Object> idset = value;
Collection<T> modelValue = (Collection<T>) getPropertyDataSource()
.getValue();
if (modelValue == null) {
try {
modelValue = createNewCollectionForType(getPropertyDataSource()
.getType());
} catch (Exception e) {
throw new ConversionException(e);
}
}
if (idset == null || idset.isEmpty()) {
modelValue.clear();
return modelValue;
}
HashSet<T> orphaned = new HashSet<T>(modelValue);
// Add those that did not exist do not exist already + remove them from
// orphaned collection
for (Object id : idset) {
EntityItem<T> item = getContainer().getItem(id);
T entity = item.getEntity();
if (!modelValue.contains(entity)) {
modelValue.add(entity);
addBackReference(entity);
}
orphaned.remove(entity);
}
// remove orphanded
for (T entity : orphaned) {
modelValue.remove(entity);
removeBackReference(entity);
}
if (!isOwningSide()) {
// refresh the item as modifying back references may also have
// changed the collections, without this we'd get concurrent
// modification exception.
// FIXME: when verifying a field using this converter this following
// line causes a value change event on that field, which causes all
// kinds of shit which ultimately causes an exception causing the
// validation to fail with a validation error message.
// getPropertyDataSource().getItem().refresh();
}
return modelValue;
}
private EntityItemProperty getPropertyDataSource() {
return (EntityItemProperty) select.getPropertyDataSource();
}
private void removeBackReference(T entity) {
if (!isOwningSide()) {
EntityItemProperty itemProperty = getBackReferenceItemProperty(entity);
Collection c = (Collection) itemProperty.getValue();
c.remove(getPropertyDataSource().getItem().getEntity());
itemProperty.setValue(c);
}
}
private EntityItemProperty getBackReferenceItemProperty(T entity) {
EntityItem item = getContainer().getItem(
getContainer().getEntityProvider().getIdentifier(entity));
EntityItemProperty itemProperty = item.getItemProperty(mappedBy);
return itemProperty;
}
private void addBackReference(T entity) {
if (!isOwningSide()) {
EntityItemProperty itemProperty = getBackReferenceItemProperty(entity);
Collection c = (Collection) itemProperty.getValue();
c.add(getPropertyDataSource().getItem().getEntity());
itemProperty.setValue(c);
}
}
/**
* Checks if the manytomany relation is owned by this side of the property.
* As a side effect detects the name of the owner property if the relation
* is owned by the other side.
*
* @return false if bidirectional connection and the mapping has a mappedBy
* parameter.
*/
private boolean isOwningSide() {
if (owningSide == null) {
Class<?> entityClass = getPropertyDataSource().getItem()
.getContainer().getEntityClass();
EntityClassMetadata<?> entityClassMetadata = MetadataFactory
.getInstance().getEntityClassMetadata(entityClass);
PropertyMetadata property = entityClassMetadata
.getProperty(getPropertyDataSource().getPropertyId());
ManyToMany annotation = property.getAnnotation(ManyToMany.class);
if (annotation.mappedBy() != null
&& !annotation.mappedBy().isEmpty()) {
owningSide = Boolean.FALSE;
mappedBy = annotation.mappedBy();
return owningSide;
}
owningSide = Boolean.TRUE;
}
return owningSide;
}
static Collection createNewCollectionForType(Class<?> type)
throws InstantiationException, IllegalAccessException {
if (type.isInterface()) {
if (type == Set.class) {
return new HashSet();
} else if (type == List.class) {
return new ArrayList();
} else {
throw new RuntimeException(
"Couldn't instantiate a collection for property.");
}
} else {
return (Collection) type.newInstance();
}
}
@Override
public Class<Collection<T>> getModelType() {
return getPropertyDataSource().getType();
}
@Override
public Class<Collection<Object>> getPresentationType() {
return getPropertyDataSource().getType();
}
}