java.util.ConcurrentModificationException on cloneEntity

726 views Asked by At

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.

1

There are 1 answers

1
Vincent Vandenschrick On

You have a 1-N bi-directional association between TrainTimetable and Slot. 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. :

  • if you set the trainTimetable property on a slot, the slot will be added to the reverse slots collection of the train timetable.
  • if you add a slot to the slotsproperty of a train timetable, then the trainTimetable reverse property of the added slot will be updated to the train timetable.

In the following code :

 Slot clonedSlot = super.cloneEntity(it, entityFactory);

the clonedSlot will be added to the trainTimeTableToClone 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. :

trainTimetableToClone.getSlots().collect().each {