| /** |
| * 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(); |
| } |
| } |