From 16b1c7cacbab88f08fa0c9f1605fc1ab02c33c41 Mon Sep 17 00:00:00 2001 From: Christian Beikov Date: Tue, 18 Dec 2018 15:07:56 +0100 Subject: [PATCH] [#705] Properly copy and reset dirty state when converting entity view types --- .../view/impl/mapper/ViewMapper.java | 59 ++++++++++++++----- .../convert/view/ConvertViewTest.java | 34 ++++++++++- .../view/model/DocumentCloneUpdateView.java | 52 ++++++++++++++++ 3 files changed, 128 insertions(+), 17 deletions(-) create mode 100644 entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/convert/view/model/DocumentCloneUpdateView.java diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/mapper/ViewMapper.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/mapper/ViewMapper.java index f3cf776596..85bdc34772 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/mapper/ViewMapper.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/mapper/ViewMapper.java @@ -26,6 +26,7 @@ import com.blazebit.persistence.view.impl.metamodel.AbstractAttribute; import com.blazebit.persistence.view.impl.metamodel.AbstractMethodAttribute; import com.blazebit.persistence.view.impl.proxy.ConvertReflectionInstantiator; +import com.blazebit.persistence.view.impl.proxy.DirtyStateTrackable; import com.blazebit.persistence.view.impl.proxy.DirtyTracker; import com.blazebit.persistence.view.impl.proxy.ObjectInstantiator; import com.blazebit.persistence.view.impl.proxy.ProxyFactory; @@ -53,6 +54,7 @@ public class ViewMapper { private final int[] dirtyMapping; + private final boolean copyInitialState; private final AttributeAccessor[] sourceAccessors; private final ObjectInstantiator objectInstantiator; @@ -83,19 +85,17 @@ public ViewMapper(ManagedViewType sourceType, ManagedViewType targetType, if (targetAttribute != idAttribute) { parameterTypes[i] = targetAttribute.getConvertedJavaType(); sourceAccessors[i] = createAccessor(sourceType, targetType, ignoreMissing, markNew, entityViewManager, proxyFactory, targetAttribute); - if (!markNew) { - // Extract a mapping from target dirty state index to the source - int dirtyStateIndex = ((AbstractMethodAttribute) targetAttribute).getDirtyStateIndex(); - if (dirtyStateIndex != -1) { - MethodAttribute sourceAttribute = sourceType.getAttribute(targetAttribute.getName()); - if (sourceAttribute != null) { - int sourceIndex = ((AbstractMethodAttribute) sourceAttribute).getDirtyStateIndex(); - if (sourceIndex != -1) { - for (int j = dirtyMapping.size(); j <= dirtyStateIndex; j++) { - dirtyMapping.add(-1); - } - dirtyMapping.set(dirtyStateIndex, sourceIndex); + // Extract a mapping from target dirty state index to the source + int dirtyStateIndex = ((AbstractMethodAttribute) targetAttribute).getDirtyStateIndex(); + if (dirtyStateIndex != -1) { + MethodAttribute sourceAttribute = sourceType.getAttribute(targetAttribute.getName()); + if (sourceAttribute != null) { + int sourceIndex = ((AbstractMethodAttribute) sourceAttribute).getDirtyStateIndex(); + if (sourceIndex != -1) { + for (int j = dirtyMapping.size(); j <= dirtyStateIndex; j++) { + dirtyMapping.add(-1); } + dirtyMapping.set(dirtyStateIndex, sourceIndex); } } } @@ -113,6 +113,7 @@ public ViewMapper(ManagedViewType sourceType, ManagedViewType targetType, this.dirtyMapping = dirtyMappingArray; } + this.copyInitialState = !markNew; this.sourceAccessors = sourceAccessors; this.objectInstantiator = new ConvertReflectionInstantiator<>(proxyFactory, targetType, parameterTypes, markNew, entityViewManager); } @@ -190,13 +191,39 @@ public T map(S source) { } } T result = objectInstantiator.newInstance(tuple); - // TODO: copy initial state, on create new, mark everything as dirty if (dirtyMapping != null && source instanceof DirtyTracker) { DirtyTracker oldDirtyTracker = (DirtyTracker) source; DirtyTracker dirtyTracker = (DirtyTracker) result; - for (int i = 0; i < dirtyMapping.length; i++) { - if (oldDirtyTracker.$$_isDirty(dirtyMapping[i])) { - dirtyTracker.$$_markDirty(i); + if (copyInitialState) { + if (oldDirtyTracker instanceof DirtyStateTrackable && dirtyTracker instanceof DirtyStateTrackable) { + Object[] oldInitial = ((DirtyStateTrackable) oldDirtyTracker).$$_getInitialState(); + Object[] newInitial = ((DirtyStateTrackable) dirtyTracker).$$_getInitialState(); + for (int i = 0; i < dirtyMapping.length; i++) { + int dirtyStateIndex = dirtyMapping[i]; + if (oldDirtyTracker.$$_isDirty(dirtyStateIndex)) { + newInitial[dirtyStateIndex] = oldInitial[dirtyStateIndex]; + dirtyTracker.$$_markDirty(i); + } + } + } else { + for (int i = 0; i < dirtyMapping.length; i++) { + if (oldDirtyTracker.$$_isDirty(dirtyMapping[i])) { + dirtyTracker.$$_markDirty(i); + } + } + } + } else { + // Reset the initial state i.e. mark it as new + if (dirtyTracker instanceof DirtyStateTrackable) { + Object[] initialState = ((DirtyStateTrackable) dirtyTracker).$$_getInitialState(); + for (int i = 0; i < initialState.length; i++) { + initialState[i] = null; + } + } + for (int i = 0; i < dirtyMapping.length; i++) { + if (oldDirtyTracker.$$_isDirty(dirtyMapping[i])) { + dirtyTracker.$$_markDirty(i); + } } } } diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/convert/view/ConvertViewTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/convert/view/ConvertViewTest.java index 1686616d99..f0fa912c24 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/convert/view/ConvertViewTest.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/convert/view/ConvertViewTest.java @@ -21,11 +21,14 @@ import com.blazebit.persistence.testsuite.entity.Document; import com.blazebit.persistence.testsuite.entity.Person; import com.blazebit.persistence.testsuite.tx.TxVoidWork; +import com.blazebit.persistence.view.ConvertOption; import com.blazebit.persistence.view.EntityViewManager; import com.blazebit.persistence.view.EntityViewSetting; import com.blazebit.persistence.view.EntityViews; +import com.blazebit.persistence.view.change.ChangeModel; import com.blazebit.persistence.view.spi.EntityViewConfiguration; import com.blazebit.persistence.view.testsuite.AbstractEntityViewTest; +import com.blazebit.persistence.view.testsuite.convert.view.model.DocumentCloneUpdateView; import com.blazebit.persistence.view.testsuite.convert.view.model.DocumentCloneView; import com.blazebit.persistence.view.testsuite.convert.view.model.DocumentIdView; import com.blazebit.persistence.view.testsuite.convert.view.model.PersonView; @@ -36,7 +39,7 @@ import javax.persistence.EntityManager; -import static org.junit.Assert.assertEquals; +import static org.junit.Assert.*; /** * @@ -54,6 +57,7 @@ public void initEvm() { EntityViewConfiguration cfg = EntityViews.createDefaultConfiguration(); cfg.addEntityView(DocumentIdView.class); cfg.addEntityView(DocumentCloneView.class); + cfg.addEntityView(DocumentCloneUpdateView.class); cfg.addEntityView(SimplePersonView.class); cfg.addEntityView(PersonView.class); evm = cfg.createEntityViewManager(cbf); @@ -124,4 +128,32 @@ public void testConvertSubset() { assertEquals(documentView.getOwner(), idView.getOwner()); assertEquals(documentView.getOwner().getName(), idView.getOwner().getName()); } + + @Test + public void testConvertDirty() { + CriteriaBuilder criteria = cbf.create(em, Document.class); + DocumentCloneUpdateView documentView = evm.applySetting(EntityViewSetting.create(DocumentCloneUpdateView.class), criteria) + .getSingleResult(); + + documentView.setName("abc"); + DocumentCloneUpdateView secondView = evm.convert(documentView, DocumentCloneUpdateView.class); + + ChangeModel nameChange = evm.getChangeModel(secondView).get("name"); + assertTrue(nameChange.isDirty()); + assertEquals("doc1", nameChange.getInitialState()); + } + + @Test + public void testConvertDirtyToNew() { + CriteriaBuilder criteria = cbf.create(em, Document.class); + DocumentCloneUpdateView documentView = evm.applySetting(EntityViewSetting.create(DocumentCloneUpdateView.class), criteria) + .getSingleResult(); + + documentView.setName("abc"); + DocumentCloneUpdateView secondView = evm.convert(documentView, DocumentCloneUpdateView.class, ConvertOption.CREATE_NEW); + + ChangeModel nameChange = evm.getChangeModel(secondView).get("name"); + assertTrue(nameChange.isDirty()); + assertNull(nameChange.getInitialState()); + } } diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/convert/view/model/DocumentCloneUpdateView.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/convert/view/model/DocumentCloneUpdateView.java new file mode 100644 index 0000000000..bb77cc2e77 --- /dev/null +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/convert/view/model/DocumentCloneUpdateView.java @@ -0,0 +1,52 @@ +/* + * Copyright 2014 - 2018 Blazebit. + * + * 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.blazebit.persistence.view.testsuite.convert.view.model; + +import com.blazebit.persistence.testsuite.entity.Document; +import com.blazebit.persistence.testsuite.entity.Person; +import com.blazebit.persistence.view.CreatableEntityView; +import com.blazebit.persistence.view.EntityView; +import com.blazebit.persistence.view.IdMapping; +import com.blazebit.persistence.view.UpdatableEntityView; + +import java.io.Serializable; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * + * @author Christian Beikov + * @since 1.4.0 + */ +@EntityView(Document.class) +@UpdatableEntityView +@CreatableEntityView(validatePersistability = false) +public interface DocumentCloneUpdateView extends DocumentCloneView { + + public void setName(String name); + + public void setAge(long age); + + public void setOwner(PersonView owner); + + public void setPeople(List people); + + public void setPartners(Set partners); + + public void setContacts(Map contacts); +}