diff options
author | Anton Tananaev <anton@traccar.org> | 2023-03-21 13:40:50 -0700 |
---|---|---|
committer | Anton Tananaev <anton@traccar.org> | 2023-03-21 13:40:58 -0700 |
commit | 2a0b2dee5eecd55f86c51fe2a25f7ac7484d6bde (patch) | |
tree | d0f187702e2b665b008de55e2467c6133d8ff17f | |
parent | 376df800e0f96b5237640420f7323a5980c0852b (diff) | |
download | trackermap-server-2a0b2dee5eecd55f86c51fe2a25f7ac7484d6bde.tar.gz trackermap-server-2a0b2dee5eecd55f86c51fe2a25f7ac7484d6bde.tar.bz2 trackermap-server-2a0b2dee5eecd55f86c51fe2a25f7ac7484d6bde.zip |
Response after position processed
7 files changed, 177 insertions, 23 deletions
diff --git a/src/main/java/org/traccar/BasePipelineFactory.java b/src/main/java/org/traccar/BasePipelineFactory.java index b184da45c..70f999c72 100644 --- a/src/main/java/org/traccar/BasePipelineFactory.java +++ b/src/main/java/org/traccar/BasePipelineFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2012 - 2022 Anton Tananaev (anton@traccar.org) + * Copyright 2012 - 2023 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,6 +25,7 @@ import io.netty.channel.ChannelPipeline; import io.netty.handler.timeout.IdleStateHandler; import org.traccar.config.Config; import org.traccar.config.Keys; +import org.traccar.handler.AcknowledgementHandler; import org.traccar.handler.ComputedAttributesHandler; import org.traccar.handler.CopyAttributesHandler; import org.traccar.handler.DefaultDataHandler; @@ -60,15 +61,19 @@ public abstract class BasePipelineFactory extends ChannelInitializer<Channel> { private final Injector injector; private final TrackerConnector connector; private final String protocol; - private int timeout; + private final boolean instantAcknowledgement; + private final int timeout; public BasePipelineFactory(TrackerConnector connector, Config config, String protocol) { this.injector = Main.getInjector(); this.connector = connector; this.protocol = protocol; - timeout = config.getInteger(Keys.PROTOCOL_TIMEOUT.withPrefix(protocol)); + instantAcknowledgement = config.getBoolean(Keys.SERVER_INSTANT_ACKNOWLEDGEMENT); + int timeout = config.getInteger(Keys.PROTOCOL_TIMEOUT.withPrefix(protocol)); if (timeout == 0) { - timeout = config.getInteger(Keys.SERVER_TIMEOUT); + this.timeout = config.getInteger(Keys.SERVER_TIMEOUT); + } else { + this.timeout = timeout; } } @@ -112,6 +117,9 @@ public abstract class BasePipelineFactory extends ChannelInitializer<Channel> { pipeline.addLast(new OpenChannelHandler(connector)); pipeline.addLast(new NetworkMessageHandler()); pipeline.addLast(new StandardLoggingHandler(protocol)); + if (!instantAcknowledgement) { + pipeline.addLast(new AcknowledgementHandler()); + } addProtocolHandlers(handler -> { if (handler instanceof BaseProtocolDecoder || handler instanceof BaseProtocolEncoder) { diff --git a/src/main/java/org/traccar/ExtendedObjectDecoder.java b/src/main/java/org/traccar/ExtendedObjectDecoder.java index f79a36c85..805f98cb7 100644 --- a/src/main/java/org/traccar/ExtendedObjectDecoder.java +++ b/src/main/java/org/traccar/ExtendedObjectDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 - 2020 Anton Tananaev (anton@traccar.org) + * Copyright 2015 - 2023 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,6 +23,7 @@ import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.util.ReferenceCountUtil; import org.traccar.config.Config; import org.traccar.config.Keys; +import org.traccar.handler.AcknowledgementHandler; import org.traccar.helper.DataConverter; import org.traccar.model.Position; @@ -30,6 +31,7 @@ import javax.inject.Inject; import java.net.SocketAddress; import java.nio.charset.StandardCharsets; import java.util.Collection; +import java.util.List; public abstract class ExtendedObjectDecoder extends ChannelInboundHandlerAdapter { @@ -68,6 +70,7 @@ public abstract class ExtendedObjectDecoder extends ChannelInboundHandlerAdapter public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { NetworkMessage networkMessage = (NetworkMessage) msg; Object originalMessage = networkMessage.getMessage(); + ctx.writeAndFlush(new AcknowledgementHandler.EventReceived()); try { Object decodedMessage = decode(ctx.channel(), networkMessage.getRemoteAddress(), originalMessage); onMessageEvent(ctx.channel(), networkMessage.getRemoteAddress(), originalMessage, decodedMessage); @@ -76,14 +79,19 @@ public abstract class ExtendedObjectDecoder extends ChannelInboundHandlerAdapter } if (decodedMessage != null) { if (decodedMessage instanceof Collection) { - for (Object o : (Collection) decodedMessage) { + var collection = (Collection) decodedMessage; + ctx.writeAndFlush(new AcknowledgementHandler.EventDecoded(collection)); + for (Object o : collection) { saveOriginal(o, originalMessage); ctx.fireChannelRead(o); } } else { + ctx.writeAndFlush(new AcknowledgementHandler.EventDecoded(List.of(decodedMessage))); saveOriginal(decodedMessage, originalMessage); ctx.fireChannelRead(decodedMessage); } + } else { + ctx.writeAndFlush(new AcknowledgementHandler.EventDecoded(List.of())); } } finally { ReferenceCountUtil.release(originalMessage); diff --git a/src/main/java/org/traccar/MainEventHandler.java b/src/main/java/org/traccar/MainEventHandler.java index 877f03ae7..658ff6d6d 100644 --- a/src/main/java/org/traccar/MainEventHandler.java +++ b/src/main/java/org/traccar/MainEventHandler.java @@ -27,6 +27,7 @@ import org.slf4j.LoggerFactory; import org.traccar.config.Config; import org.traccar.config.Keys; import org.traccar.database.StatisticsManager; +import org.traccar.handler.AcknowledgementHandler; import org.traccar.helper.DateUtil; import org.traccar.helper.NetworkUtil; import org.traccar.helper.model.PositionUtil; @@ -145,6 +146,8 @@ public class MainEventHandler extends ChannelInboundHandlerAdapter { LOGGER.info(builder.toString()); statisticsManager.registerMessageStored(position.getDeviceId(), position.getProtocol()); + + ctx.writeAndFlush(new AcknowledgementHandler.EventHandled(position)); } } diff --git a/src/main/java/org/traccar/config/Keys.java b/src/main/java/org/traccar/config/Keys.java index cb1ee63e1..c207efb1e 100644 --- a/src/main/java/org/traccar/config/Keys.java +++ b/src/main/java/org/traccar/config/Keys.java @@ -293,6 +293,13 @@ public final class Keys { List.of(KeyType.CONFIG)); /** + * Send device responses immediately before writing it in the database. + */ + public static final ConfigKey<Boolean> SERVER_INSTANT_ACKNOWLEDGEMENT = new BooleanConfigKey( + "server.instantAcknowledgement", + List.of(KeyType.CONFIG)); + + /** * Address for uploading aggregated anonymous usage statistics. Uploaded information is the same you can see on the * statistics screen in the web app. It does not include any sensitive (e.g. locations). */ diff --git a/src/main/java/org/traccar/handler/AcknowledgementHandler.java b/src/main/java/org/traccar/handler/AcknowledgementHandler.java new file mode 100644 index 000000000..c3f6038de --- /dev/null +++ b/src/main/java/org/traccar/handler/AcknowledgementHandler.java @@ -0,0 +1,121 @@ +/* + * Copyright 2023 Anton Tananaev (anton@traccar.org) + * + * 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 org.traccar.handler; + +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelOutboundHandlerAdapter; +import io.netty.channel.ChannelPromise; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Collection; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +public class AcknowledgementHandler extends ChannelOutboundHandlerAdapter { + + private static final Logger LOGGER = LoggerFactory.getLogger(AcknowledgementHandler.class); + + public interface Event { + } + + public static class EventReceived implements Event { + } + + public static class EventDecoded implements Event { + private final Collection<Object> objects; + + public EventDecoded(Collection<Object> objects) { + this.objects = objects; + } + + public Collection<Object> getObjects() { + return objects; + } + } + + public static class EventHandled implements Event { + private final Object object; + + public EventHandled(Object object) { + this.object = object; + } + + public Object getObject() { + return object; + } + } + + private static class Entry { + private final Object message; + private final ChannelPromise promise; + + private Entry(Object message, ChannelPromise promise) { + this.message = message; + this.promise = promise; + } + + public Object getMessage() { + return message; + } + + public ChannelPromise getPromise() { + return promise; + } + } + + private List<Entry> queue; + private final Set<Object> waiting = new HashSet<>(); + + @Override + public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { + List<Entry> output = new LinkedList<>(); + synchronized (this) { + if (msg instanceof Event) { + if (msg instanceof EventReceived) { + LOGGER.debug("Event received"); + if (queue == null) { + queue = new LinkedList<>(); + } + } else if (msg instanceof EventDecoded) { + EventDecoded event = (EventDecoded) msg; + LOGGER.debug("Event decoded {}", event.getObjects().size()); + waiting.addAll(event.getObjects()); + } else if (msg instanceof EventHandled) { + EventHandled event = (EventHandled) msg; + LOGGER.debug("Event handled"); + waiting.remove(event.getObject()); + } + if (!(msg instanceof EventReceived) && waiting.isEmpty()) { + output.addAll(queue); + queue = null; + } + } else if (queue != null) { + LOGGER.debug("Message queued"); + queue.add(new Entry(msg, promise)); + } else { + LOGGER.debug("Message sent"); + output.add(new Entry(msg, promise)); + } + } + for (Entry entry : output) { + ctx.write(entry.getMessage(), entry.getPromise()); + } + } + +} diff --git a/src/main/java/org/traccar/handler/FilterHandler.java b/src/main/java/org/traccar/handler/FilterHandler.java index 994276bb6..1d1c27b7a 100644 --- a/src/main/java/org/traccar/handler/FilterHandler.java +++ b/src/main/java/org/traccar/handler/FilterHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 - 2022 Anton Tananaev (anton@traccar.org) + * Copyright 2014 - 2023 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,9 +16,10 @@ package org.traccar.handler; import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.traccar.BaseDataHandler; import org.traccar.config.Config; import org.traccar.config.Keys; import org.traccar.helper.UnitsConverter; @@ -39,7 +40,7 @@ import java.util.Date; @Singleton @ChannelHandler.Sharable -public class FilterHandler extends BaseDataHandler { +public class FilterHandler extends ChannelInboundHandlerAdapter { private static final Logger LOGGER = LoggerFactory.getLogger(FilterHandler.class); @@ -177,7 +178,7 @@ public class FilterHandler extends BaseDataHandler { return false; } - private boolean filter(Position position) { + protected boolean filter(Position position) { StringBuilder filterType = new StringBuilder(); @@ -243,11 +244,17 @@ public class FilterHandler extends BaseDataHandler { } @Override - protected Position handlePosition(Position position) { - if (enabled && filter(position)) { - return null; + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + if (msg instanceof Position) { + Position position = (Position) msg; + if (enabled && filter(position)) { + ctx.writeAndFlush(new AcknowledgementHandler.EventHandled(position)); + } else { + ctx.fireChannelRead(position); + } + } else { + super.channelRead(ctx, msg); } - return position; } } diff --git a/src/test/java/org/traccar/handler/FilterHandlerTest.java b/src/test/java/org/traccar/handler/FilterHandlerTest.java index 9cb4a3bf2..26281e351 100644 --- a/src/test/java/org/traccar/handler/FilterHandlerTest.java +++ b/src/test/java/org/traccar/handler/FilterHandlerTest.java @@ -11,8 +11,8 @@ import org.traccar.session.cache.CacheManager; import java.util.Date; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Mockito.mock; @@ -71,18 +71,18 @@ public class FilterHandlerTest extends BaseTest { Position position = createPosition(new Date(), true, 10); - assertNotNull(filteringHandler.handlePosition(position)); - assertNotNull(passingHandler.handlePosition(position)); + assertFalse(filteringHandler.filter(position)); + assertFalse(passingHandler.filter(position)); position = createPosition(new Date(Long.MAX_VALUE), true, 10); - assertNull(filteringHandler.handlePosition(position)); - assertNotNull(passingHandler.handlePosition(position)); + assertTrue(filteringHandler.filter(position)); + assertFalse(passingHandler.filter(position)); position = createPosition(new Date(), false, 10); - assertNull(filteringHandler.handlePosition(position)); - assertNotNull(passingHandler.handlePosition(position)); + assertTrue(filteringHandler.filter(position)); + assertFalse(passingHandler.filter(position)); } @@ -92,7 +92,7 @@ public class FilterHandlerTest extends BaseTest { Position position = createPosition(new Date(), true, 0); position.set(Position.KEY_ALARM, Position.ALARM_GENERAL); - assertNotNull(filteringHandler.handlePosition(position)); + assertFalse(filteringHandler.filter(position)); } |