I wrote a custom strategy in order to clone an entity TrainTimetable with its collection of slots.
Below an extract of the model :
Entity('TrainTimetable') {
list 'slots' , ref:'Slot', composition:true
}
Entity('Slot') {
reference 'trainTimetable' , ref:'TrainTimetable', reverse:'TrainTimetable-slots'
}
And below the method :
package fr.yc.rail.backend;
import java.util.ArrayList;
import java.util.List;
import org.jspresso.framework.model.entity.IEntity;
import org.jspresso.framework.model.entity.IEntityFactory;
import org.jspresso.framework.model.entity.SmartEntityCloneFactory;
import fr.yc.rail.model.Slot;
import fr.yc.rail.model.TrainTimetable;
public class CloneTrainTimetableFactory extends SmartEntityCloneFactory {
@Override
public <E extends IEntity> E cloneEntity(E entityToClone,
IEntityFactory entityFactory) {
TrainTimetable clonedTrainTimetable = (TrainTimetable) super.cloneEntity(entityToClone, entityFactory);
TrainTimetable trainTimetableToClone = (TrainTimetable) entityToClone;
List<Slot> clonedSlots = new ArrayList<Slot>();
trainTimetableToClone.getSlots().each {
Slot clonedSlot = super.cloneEntity(it, entityFactory);
clonedSlots.add(clonedSlot);
}
clonedTrainTimetable.setSlots(clonedSlots);
return (E) clonedTrainTimetable;
}
}
When the method is called, the code below raises the ConcurrentModificationException error on the second iteration :
trainTimetableToClone.getSlots().each {
Slot clonedSlot = super.cloneEntity(it, entityFactory);
clonedSlots.add(clonedSlot);
}
Below the stack trace :
ERROR <2015-06-09 18:13:11,193> org.jspresso.framework.application.frontend.controller.AbstractFrontendController : An unexpected error occurred for user demo on session 4379dc1b.
java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(Unknown Source)
at java.util.ArrayList$Itr.next(Unknown Source)
at org.hibernate.collection.internal.AbstractPersistentCollection$IteratorProxy.next(AbstractPersistentCollection.java:774)
at org.codehaus.groovy.runtime.DefaultGroovyMethods.each(DefaultGroovyMethods.java:1378)
at org.codehaus.groovy.runtime.DefaultGroovyMethods.each(DefaultGroovyMethods.java:1372)
at org.codehaus.groovy.runtime.dgm$149.invoke(Unknown Source)
at org.codehaus.groovy.runtime.callsite.PojoMetaMethodSite$PojoMetaMethodSiteNoUnwrapNoCoerce.invoke(PojoMetaMethodSite.java:271)
at org.codehaus.groovy.runtime.callsite.PojoMetaMethodSite.call(PojoMetaMethodSite.java:53)
at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:45)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:108)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:116)
at fr.yc.rail.backend.CloneTrainTimetableFactory.cloneEntity(CloneTrainTimetableFactory.groovy:25)
at org.jspresso.framework.application.backend.action.CloneComponentCollectionAction.cloneElement(CloneComponentCollectionAction.java:61)
at org.jspresso.framework.application.backend.action.AbstractCloneCollectionAction.getAddedComponents(AbstractCloneCollectionAction.java:71)
at org.jspresso.framework.application.backend.action.AbstractAddCollectionToMasterAction.execute(AbstractAddCollectionToMasterAction.java:78)
at org.jspresso.framework.application.backend.AbstractBackendController.execute(AbstractBackendController.java:403)
at org.jspresso.framework.application.frontend.controller.AbstractFrontendController.executeBackend(AbstractFrontendController.java:1536)
at org.jspresso.framework.application.frontend.controller.AbstractFrontendController.execute(AbstractFrontendController.java:576)
at org.jspresso.framework.application.action.AbstractAction.execute(AbstractAction.java:114)
at org.jspresso.framework.application.frontend.controller.AbstractFrontendController.executeFrontend(AbstractFrontendController.java:1549)
at org.jspresso.framework.application.frontend.controller.AbstractFrontendController.execute(AbstractFrontendController.java:578)
at org.jspresso.framework.view.remote.RemoteActionFactory$ActionAdapter.actionPerformed(RemoteActionFactory.java:235)
at org.jspresso.framework.application.frontend.controller.remote.AbstractRemoteController.handleCommand(AbstractRemoteController.java:440)
at org.jspresso.framework.application.frontend.controller.remote.AbstractRemoteController.handleCommands(AbstractRemoteController.java:202)
at org.jspresso.framework.application.startup.remote.RemoteStartup.handleCommands(RemoteStartup.java:88)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at net.sf.qooxdoo.rpc.RemoteCallUtils.callCompatibleMethod(RemoteCallUtils.java:469)
at net.sf.qooxdoo.rpc.RpcServlet.handleRPC(RpcServlet.java:374)
at net.sf.qooxdoo.rpc.RpcServlet.doPost(RpcServlet.java:481)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:646)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:727)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:303)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
at org.jspresso.framework.util.http.HttpRequestHolder.doFilter(HttpRequestHolder.java:99)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:220)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:122)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:503)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:170)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:103)
at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:950)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:116)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:421)
at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1070)
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:611)
at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:316)
at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Unknown Source)
The error means that I am iterating on a collection that is changing in size, but I don't know how to get rid of it.
You have a 1-N bi-directional association between
TrainTimetableandSlot. This means that whenever you update one side of the association, Jspresso will take care of updating the other side in order to keep model consistency, e.g. :trainTimetableproperty on a slot, the slot will be added to the reverseslotscollection of the train timetable.slotsproperty of a train timetable, then thetrainTimetablereverse property of the added slot will be updated to the train timetable.In the following code :
the
clonedSlotwill be added to thetrainTimeTableToCloneslots due to the rules explained before. So the iterated collection actually changes.In order to fix the problem, making a copy of the collection before iterating it should be enough, i.e. :