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
TrainTimetable
andSlot
. 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. :trainTimetable
property on a slot, the slot will be added to the reverseslots
collection of the train timetable.slots
property of a train timetable, then thetrainTimetable
reverse property of the added slot will be updated to the train timetable.In the following code :
the
clonedSlot
will be added to thetrainTimeTableToClone
slots 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. :