jmix-framework / jmix

Jmix framework
https://www.jmix.io
Apache License 2.0
695 stars 125 forks source link

GeoObjectClickEvent listener throws an exception when removing the clicked item from the container and adding it back #3712

Closed Flaurite closed 1 month ago

Flaurite commented 2 months ago

Environment

Jmix version: 2.3.x

Bug Description

When the GeoObjectClickEvent listener removes clicked item and add it back to container, the little delay in communication between client-side and server throws an exception. For instance:

@Subscribe("map.stopsLayer.stopsSource")
public void onMapStopsLayerStopsSourceGeoObjectClick(final GeoObjectClickNotifier.GeoObjectClickEvent<Stop> event) {
     Stop stop = event.getItem();
    stopsDc.getMutableItems().remove(stop);
    stopsDc.getMutableItems().add(stop);
}

This code in debug mode will throw an exception.

Steps To Reproduce

  1. Download demo project: feature-click-demo.zip
  2. Launch application in debug mode
  3. Add breakpoint to 28 line
  4. Click the feature on the map
  5. Go through all rows in debug

Current Behavior

Stacktrace:

java.lang.IllegalStateException: Cannot find feature
    at io.jmix.mapsflowui.kit.component.JmixMap.lambda$getFeatureBySyncId$4(JmixMap.java:346)
    at java.base/java.util.Optional.orElseThrow(Optional.java:403)
    at io.jmix.mapsflowui.kit.component.JmixMap.getFeatureBySyncId(JmixMap.java:346)
    at io.jmix.mapsflowui.kit.component.JmixMap.onFeatureClickEvent(JmixMap.java:318)
    at com.vaadin.flow.component.ComponentEventBus.fireEventForListener(ComponentEventBus.java:239)
    at com.vaadin.flow.component.ComponentEventBus.handleDomEvent(ComponentEventBus.java:488)
    at com.vaadin.flow.component.ComponentEventBus.lambda$addDomTrigger$dd1b7957$1(ComponentEventBus.java:298)
    at com.vaadin.flow.internal.nodefeature.ElementListenerMap.lambda$fireEvent$2(ElementListenerMap.java:447)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
    at com.vaadin.flow.internal.nodefeature.ElementListenerMap.fireEvent(ElementListenerMap.java:447)
    at com.vaadin.flow.server.communication.rpc.EventRpcHandler.handleNode(EventRpcHandler.java:62)
    at com.vaadin.flow.server.communication.rpc.AbstractRpcInvocationHandler.handle(AbstractRpcInvocationHandler.java:74)
    at com.vaadin.flow.server.communication.ServerRpcHandler.handleInvocationData(ServerRpcHandler.java:475)
    at com.vaadin.flow.server.communication.ServerRpcHandler.lambda$handleInvocations$5(ServerRpcHandler.java:456)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
    at com.vaadin.flow.server.communication.ServerRpcHandler.handleInvocations(ServerRpcHandler.java:456)
    at com.vaadin.flow.server.communication.ServerRpcHandler.handleRpc(ServerRpcHandler.java:324)
    at com.vaadin.flow.server.communication.UidlRequestHandler.synchronizedHandleRequest(UidlRequestHandler.java:114)
    at com.vaadin.flow.server.SynchronizedRequestHandler.handleRequest(SynchronizedRequestHandler.java:40)
    at com.vaadin.flow.server.VaadinService.handleRequest(VaadinService.java:1577)

Expected Behavior

No exceptions occur

Flaurite commented 2 months ago

The reason of the problem is the events that occur after clickEvent: singleClick or doubleClick. These event cannot find removed item, so the solution can be not throwing an exception but logs it.

The rough workaround:

1) Extend GeoMap component and override these methods:

public class ExtGeoMap extends GeoMap {

    @Override
    protected void onFeatureClickEvent(AbstractFeatureClickDomEvent event) {
        Layer<?> layer = getLayerBySyncId(event.getLayerSyncId());
        if (!(layer instanceof BaseVectorLayer)) {
            return;
        }

        AbstractVectorSource source = (AbstractVectorSource) layer.getSource();
        if (source instanceof AbstractClusterSource.HasVectorSource vectorSource) {
            source = vectorSource.getVectorSource();
        }

        Feature feature = getFeatureBySyncId(source, event.getFeatureSyncId());
        if (feature == null) {
            // Do not fire event
            return;
        }

        if (source instanceof AbstractFeatureSource featureSource) {
            EventBus eventBus = MapObservableUtils.getEventBus(featureSource);
            if (eventBus != null) {
                fireSourceFeatureClickEvent(eventBus, event, feature, featureSource);
            }
        }

        EventBus eventBus = MapObservableUtils.getEventBus(feature);
        if (eventBus != null) {
            fireFeatureClickEvent(eventBus, event, feature);
        }
    }

    @Nullable
    @Override
    protected Feature getFeatureBySyncId(AbstractVectorSource source, UUID syncId) {
        return (Feature) MapObservableUtils.getChildren(source).stream()
                .filter(f -> getSyncId(f).equals(syncId))
                .findFirst()
                .orElse(null);
    }
}
  1. Register component in the configuration:
    @Bean
    public ComponentRegistration extGeoMap() {
    return ComponentRegistrationBuilder.create(ExtGeoMap.class)
            .replaceComponent(GeoMap.class)
            .build();
    }
konyashkina commented 1 month ago

Reopened to test.

konyashkina commented 1 month ago

Tested on Jmix version: 2.4.999-SNAPSHOT Jmix Studio plugin version: 2.4.SNAPSHOT6876-242 IntelliJ version: IntelliJ IDEA 2024.2.3 (Community Edition)