blob: da673164e597242c5a678b1f17024fd92b1e5f08 [file] [log] [blame]
/**
* Copyright (c) 2011, 2015 - Lunifera GmbH (Gross Enzersdorf, Austria), Loetz GmbH&Co.KG (69115 Heidelberg, Germany)
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Florian Pirchner - Initial implementation
*/
package org.eclipse.osbp.dsl.dto.lib;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Objects;
import java.util.Spliterator;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.function.UnaryOperator;
import java.util.stream.Stream;
import org.eclipse.osbp.dsl.common.datatypes.IDto;
import org.eclipse.osbp.dsl.dto.lib.impl.DtoServiceAccess;
import org.eclipse.osbp.jpa.services.Query;
import org.eclipse.osbp.jpa.services.filters.LCompare;
import org.eclipse.osbp.runtime.common.annotations.DtoUtils;
import org.eclipse.osbp.runtime.common.filter.IDTOService;
import org.eclipse.osbp.runtime.common.hash.HashUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public abstract class AbstractOppositeDtoList<D extends IDto> extends ArrayList<D>
implements IEntityMappingList<D>, PropertyChangeListener, Serializable {
private static final String NOT_SUPPORTED_FOR_NOW = "Not supported for now.";
private static final long serialVersionUID = -5777389952283179658L;
private static Logger LOGGER = LoggerFactory.getLogger(AbstractOppositeDtoList.class);
private final Class<?> dtoType;
private final String parentProperty;
private final transient Supplier<Object> idSupplier;
private final transient PropertyChangeListener childListener;
private boolean resolved;
private boolean resolving;
private transient Map<Object, D> addedToSuper = new HashMap<>();
private transient Map<Object, D> added = new HashMap<>();
private transient Map<Object, D> removed = new HashMap<>();
private transient Map<Object, D> updated = new HashMap<>();
private MappingContext mappingContext;
public AbstractOppositeDtoList(MappingContext mappingContext, Class<?> dtoType, String parentProperty,
Supplier<Object> idSupplier, PropertyChangeListener containerListener) {
super();
this.mappingContext = mappingContext != null ? mappingContext : new MappingContext();
this.dtoType = dtoType;
this.idSupplier = idSupplier;
this.parentProperty = parentProperty;
childListener = (e) -> {
containerListener.propertyChange(e);
};
}
public List<D> getAdded() {
return new ArrayList<>(added.values());
}
public List<D> getRemoved() {
return new ArrayList<>(removed.values());
}
public List<D> getUpdated() {
return new ArrayList<>(updated.values());
}
/**
* Notifies the container of this list in case of cascading reference.
* @param changedDto
*/
protected void notifyContainer(D changedDto) {
// implement in subclass
}
@SuppressWarnings("unchecked")
protected void resolve() {
syncContextWithTransaction();
if (resolved || resolving || mappingContext.isNoCollectionResolving()) {
return;
}
boolean oldDirtyStateActive = mappingContext.isDirtyAdapterActive();
try {
resolving = true;
mappingContext.makeCurrent();
mappingContext.setDirtyAdapterActive(false);
IDTOService<?> service = DtoServiceAccess.getService(dtoType);
Query query = new Query(new LCompare.Equal(parentProperty, idSupplier.get()));
Collection<D> dtos = (Collection<D>) service.find(query);
// remove the removed from the added
for (D removed : getRemoved()) {
String hash = hashDto(removed);
added.remove(hash);
// remove from super
D toSuper = addedToSuper.remove(hash);
if (toSuper != null) {
super.remove(toSuper);
}
}
// now merge the query result
if (dtos.isEmpty()) {
// if result is empty, add all added
super.addAll(getAdded());
addedToSuper.putAll(added);
} else {
HashMap<Object, D> tempAdded = new HashMap<>(added);
for (D dto : dtos) {
String hashCode = hashDto(dto);
if (removed.containsKey(hashCode)) {
// if it was removed, we do not add the dto
continue;
} else {
if (tempAdded.containsKey(hashCode)) {
// if it was added and contained in the DB, then we
// add the added one
D instance = tempAdded.get(hashCode);
super.add(instance);
addedToSuper.put(hashCode, instance);
tempAdded.remove(hashCode);
} else {
// otherwise we add the persistent dto
super.add(dto);
addedToSuper.put(hashCode, dto);
// register property change listener to the
// persisted dto
if (DtoUtils.getAdapter(getClass(), dto) == null) {
DtoUtils.registerAdapter(this, dto);
}
dto.addPropertyChangeListener(childListener);
}
}
}
// now we add the pending added
super.addAll(tempAdded.values());
addedToSuper.putAll(tempAdded);
}
} catch (Exception ex) {
resolved = false;
resolving = false;
throw new RuntimeException(ex);
} finally {
resolved = true;
resolving = false;
mappingContext.setDirtyAdapterActive(oldDirtyStateActive);
mappingContext.unmakeCurrent();
}
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.osbp.dsl.dto.lib.IEntityMappingList#mapToEntity(org.eclipse
* .osbp.dsl.dto.lib.IMapper, java.util.function.Consumer,
* java.util.function.Consumer)
*/
@SuppressWarnings("unchecked")
@Override
public <E> void mapToEntity(IMapper<D, E> childMapper, Consumer<E> oppositeAdder, Consumer<E> oppositeRemover) {
syncContextWithTransaction();
boolean oldNoContainerMapping = mappingContext.isNoContainerMapping();
try {
mappingContext.makeCurrent();
// no container mapping! Otherwise childMapper.mapToEntity will
// map the opposite side, which will cause a wrong container entity
mappingContext.setNoContainerMapping(true);
for (D added : getAdded()) {
E addedE = mappingContext.get(childMapper.createEntityHash(added));
if (addedE == null) {
addedE = childMapper.createEntity();
childMapper.mapToEntity(added, addedE, mappingContext);
}
oppositeAdder.accept(addedE);
if (isResolved()) {
if (!super.contains(added)) {
super.add(added);
addedToSuper.put(hashDto(added), added);
}
}
}
added.clear();
// for remove we use container mapping! The container is null
// and removeFromChildren will not remove the elements.
mappingContext.setNoContainerMapping(true);
for (D removed : getRemoved()) {
E removedE = mappingContext.get(childMapper.createEntityHash(removed));
if (removedE == null) {
IDTOService<D> service = (IDTOService<D>) DtoServiceAccess.getService(dtoType);
Object id = service.getId((D) removed);
removedE = (E) mappingContext.findEntityByEntityManager(childMapper.createEntity().getClass(), id);
if (removedE == null) {
removedE = childMapper.createEntity();
childMapper.mapToEntity(removed, removedE, mappingContext);
}
}
oppositeRemover.accept(removedE);
super.remove(removed);
addedToSuper.remove(hashDto(removed));
}
removed.clear();
// handle the changed values by remove and add
//
for (D modified : getUpdated()) {
// remove
//
E modifiedE = mappingContext.get(childMapper.createEntityHash(modified));
if (modifiedE == null) {
IDTOService<D> service = (IDTOService<D>) DtoServiceAccess.getService(dtoType);
Object id = service.getId((D) modified);
modifiedE = (E) mappingContext.findEntityByEntityManager(childMapper.createEntity().getClass(), id);
if (modifiedE == null) {
modifiedE = childMapper.createEntity();
childMapper.mapToEntity(modified, modifiedE, mappingContext);
}
}
oppositeRemover.accept(modifiedE);
if (isResolved()) {
super.remove(modified);
addedToSuper.remove(hashDto(modified));
}
// add
//
// modifiedE = childMapper.createEntity();
childMapper.mapToEntity(modified, modifiedE, mappingContext);
mappingContext.register(childMapper.createEntityHash(modified), modifiedE);
oppositeAdder.accept(modifiedE);
if (isResolved()) {
if (!super.contains(modified)) {
super.add(modified);
addedToSuper.put(hashDto(modified), modified);
}
}
}
updated.clear();
} finally {
mappingContext.setNoContainerMapping(oldNoContainerMapping);
mappingContext.unmakeCurrent();
}
}
@SuppressWarnings("unchecked")
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (removed.containsKey(hashDto((D) evt.getSource()))) {
return;
}
if (!Objects.equals(evt.getOldValue(), evt.getNewValue())) {
updated.put(hashDto((D) evt.getSource()), (D) evt.getSource());
// notify the container that detail changed
notifyContainer((D) evt.getSource());
}
}
/**
* Checks if there is a current context from a transaction done right now.
* If so then use this one as the new mapping context since it is connected
* to the root service.
*/
private void syncContextWithTransaction() {
MappingContext currentContext = MappingContext.getCurrent();
if (currentContext != null) {
mappingContext = currentContext;
}
}
public boolean isResolved() {
return resolved;
}
private String hashDto(D dto) {
return HashUtil.createObjectWithIdHash(dto.getClass(), dto);
}
@Override
public Stream<D> parallelStream() {
resolve();
return super.parallelStream();
}
@Override
public Stream<D> stream() {
resolve();
return super.stream();
}
@Override
public boolean add(D dto) {
dto.addPropertyChangeListener(childListener);
String hash = hashDto(dto);
removed.remove(hash);
added.put(hash, dto);
// notify the container that detail changed
notifyContainer(dto);
if (resolving) {
return false;
}
if (!resolved) {
return true;
} else {
// add to super
addedToSuper.put(hash, dto);
return super.add(dto);
}
}
@Override
public void add(int arg0, D dto) {
add(dto);
}
@Override
public boolean addAll(Collection<? extends D> dtos) {
boolean result = true;
for (D dto : dtos) {
result &= add(dto);
}
return result;
}
@Override
public boolean addAll(int arg0, Collection<? extends D> dtos) {
boolean result = true;
for (D dto : dtos) {
result &= add(dto);
}
return result;
}
@Override
public void clear() {
super.clear();
added.clear();
removed.clear();
}
@Override
public Object clone() {
resolve();
return super.clone();
}
@SuppressWarnings("unchecked")
@Override
public boolean contains(Object arg0) {
String hash = hashDto((D) arg0);
if (getAdded().contains(hash)) {
return true;
}
if (getRemoved().contains(hash)) {
return false;
}
if (isResolved()) {
return addedToSuper.containsKey(hash);
} else {
IDTOService<D> service = (IDTOService<D>) DtoServiceAccess.getService(dtoType);
Object id = service.getId((D) arg0);
D persDto = service.get(id);
return persDto != null;
}
}
@Override
public void forEach(Consumer<? super D> arg0) {
resolve();
super.forEach(arg0);
}
@Override
public D get(int arg0) {
resolve();
return super.get(arg0);
}
@Override
public int indexOf(Object arg0) {
resolve();
return super.indexOf(arg0);
}
@Override
public boolean isEmpty() {
return size() == 0;
}
@Override
public Iterator<D> iterator() {
resolve();
return super.iterator();
}
@Override
public int lastIndexOf(Object arg0) {
resolve();
return super.lastIndexOf(arg0);
}
@Override
public ListIterator<D> listIterator() {
resolve();
return super.listIterator();
}
@Override
public ListIterator<D> listIterator(int arg0) {
resolve();
return super.listIterator(arg0);
}
@Override
public D remove(int arg0) {
// now we need to resolve, since we got an index...
resolve();
D result = super.remove(arg0);
addedToSuper.remove(hashDto(result));
updated.remove(hashDto(result));
removed.put(hashDto(result), result);
DtoUtils.unregisterAdapter(this, result);
result.removePropertyChangeListener(childListener);
// notify the container that detail changed
notifyContainer(result);
return result;
}
@SuppressWarnings("unchecked")
@Override
public boolean remove(Object dto) {
String hash = hashDto((D) dto);
removed.put(hash, (D) dto);
added.remove(hash);
updated.remove(hash);
DtoUtils.unregisterAdapter(this, dto);
((IDto) dto).removePropertyChangeListener(childListener);
// notify the container that detail changed
notifyContainer((D) dto);
if (!resolved) {
return true;
} else {
D ref = addedToSuper.remove(hash);
return super.remove(ref);
}
}
@SuppressWarnings("unchecked")
@Override
public boolean removeAll(Collection<?> arg0) {
boolean result = true;
for (Object x : arg0) {
D dto = (D) x;
result &= remove(dto);
}
return result;
}
@Override
public boolean removeIf(Predicate<? super D> arg0) {
throw new UnsupportedOperationException(NOT_SUPPORTED_FOR_NOW);
}
@Override
protected void removeRange(int arg0, int arg1) {
throw new UnsupportedOperationException(NOT_SUPPORTED_FOR_NOW);
}
@Override
public void replaceAll(UnaryOperator<D> arg0) {
throw new UnsupportedOperationException(NOT_SUPPORTED_FOR_NOW);
}
@Override
public boolean retainAll(Collection<?> arg0) {
throw new UnsupportedOperationException(NOT_SUPPORTED_FOR_NOW);
}
@Override
public D set(int arg0, D arg1) {
resolve();
D dto = super.set(arg0, arg1);
removed.put(hashDto(dto), dto);
added.put(hashDto(arg1), arg1);
return dto;
}
@Override
public int size() {
if (resolved) {
return super.size();
} else if (mappingContext.isNoCollectionResolving()) {
return 0;
}
IDTOService<?> service = DtoServiceAccess.getService(dtoType);
Query query = new Query(new LCompare.Equal(parentProperty, idSupplier.get()));
return service.size(query) + getAdded().size() - getRemoved().size();
}
@Override
public void sort(Comparator<? super D> arg0) {
resolve();
super.sort(arg0);
}
@Override
public Spliterator<D> spliterator() {
resolve();
return super.spliterator();
}
@Override
public List<D> subList(int arg0, int arg1) {
resolve();
return super.subList(arg0, arg1);
}
@Override
public Object[] toArray() {
resolve();
return super.toArray();
}
@Override
public <T> T[] toArray(T[] arg0) {
resolve();
return super.toArray(arg0);
}
@Override
public void trimToSize() {
resolve();
super.trimToSize();
}
@Override
public boolean containsAll(Collection<?> arg0) {
for (Object dto : arg0) {
if (!contains(dto)) {
return false;
}
}
return true;
}
@Override
public boolean equals(Object o) {
if (added.size() > 0 || removed.size() > 0) {
return false;
}
LOGGER.error("equals - Check what to do here!");
return super.equals(o);
}
@Override
public int hashCode() {
return super.hashCode();
}
}