diff options
73 files changed, 1222 insertions, 1153 deletions
diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index cbe2721bb..e50354cbd 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -12,10 +12,10 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/setup-java@v3 + - uses: actions/checkout@v4 + - uses: actions/setup-java@v4 with: - distribution: zulu + distribution: temurin java-version: 11 cache: gradle - run: ./gradlew build --no-daemon --warning-mode=fail diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a8e4c5369..26695dbac 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,31 +14,25 @@ jobs: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 submodules: true - run: git checkout ${{ github.ref_name }} working-directory: ./traccar-web - - uses: actions/setup-java@v3 + - uses: actions/setup-java@v4 with: distribution: temurin - java-version: 17 + java-version: 21 cache: gradle - run: ./gradlew build - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: - node-version: 18 + node-version: 21 cache: npm - cache-dependency-path: | - traccar-web/package-lock.json - traccar-web/modern/package-lock.json - - run: | - wget -q https://trials.sencha.com/cmd/7.6.0/SenchaCmd-7.6.0.87-linux-amd64.sh.zip - unzip SenchaCmd-*.zip - ./SenchaCmd-*.sh -q - echo "$HOME/bin/Sencha/Cmd/" >> $GITHUB_PATH - - run: ./traccar-web/tools/package.sh + cache-dependency-path: traccar-web/package-lock.json + - run: npm ci && npm run build + working-directory: ./traccar-web - run: | sudo dpkg --add-architecture i386 sudo apt-get update @@ -48,10 +42,9 @@ jobs: working-directory: ./setup run: | wget -q http://files.jrsoftware.org/is/5/isetup-5.5.6.exe - wget -q https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.6+10/OpenJDK17U-jdk_x64_windows_hotspot_17.0.6_10.zip - wget -q https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.6+10/OpenJDK17U-jdk_x64_linux_hotspot_17.0.6_10.tar.gz - wget -q https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.6+10/OpenJDK17U-jdk_arm_linux_hotspot_17.0.6_10.tar.gz - wget -q https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.6+10/OpenJDK17U-jdk_aarch64_linux_hotspot_17.0.6_10.tar.gz + wget -q https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.2+13/OpenJDK21U-jdk_x64_windows_hotspot_21.0.2_13.zip + wget -q https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.2+13/OpenJDK21U-jdk_x64_linux_hotspot_21.0.2_13.tar.gz + wget -q https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.2+13/OpenJDK21U-jdk_aarch64_linux_hotspot_21.0.2_13.tar.gz ./package.sh ${{ github.event.inputs.version }} - name: Upload installers working-directory: ./setup diff --git a/build.gradle b/build.gradle index 3677f8f94..2fe2bfaab 100644 --- a/build.gradle +++ b/build.gradle @@ -14,7 +14,7 @@ compileJava.options.encoding = "UTF-8" jar.destinationDirectory = file("$projectDir/target") checkstyle { - toolVersion = "10.12.5" + toolVersion = "10.15.0" configFile = "gradle/checkstyle.xml" as File checkstyleTest.enabled = false } @@ -27,12 +27,12 @@ enforce { ext { guiceVersion = "7.0.0" - jettyVersion = "11.0.19" + jettyVersion = "11.0.20" jerseyVersion = "3.1.5" jacksonVersion = "2.15.3" // same version as jersey-media-json-jackson dependency - protobufVersion = "3.25.2" - jxlsVersion = "2.14.0" - junitVersion = "5.10.1" + protobufVersion = "4.26.1" + jxlsVersion = "2.14.0" // version 3 requires java 17 + junitVersion = "5.10.2" } protobuf { @@ -42,15 +42,15 @@ protobuf { } dependencies { - implementation "commons-codec:commons-codec:1.16.0" + implementation "commons-codec:commons-codec:1.16.1" implementation "com.h2database:h2:2.2.224" - implementation "com.mysql:mysql-connector-j:8.2.0" - implementation "org.mariadb.jdbc:mariadb-java-client:3.3.2" - implementation "org.postgresql:postgresql:42.7.1" - implementation "com.microsoft.sqlserver:mssql-jdbc:12.4.2.jre11" + implementation "com.mysql:mysql-connector-j:8.3.0" + implementation "org.mariadb.jdbc:mariadb-java-client:3.3.3" + implementation "org.postgresql:postgresql:42.7.3" + implementation "com.microsoft.sqlserver:mssql-jdbc:12.6.1.jre11" implementation "com.zaxxer:HikariCP:5.1.0" - implementation "io.netty:netty-all:4.1.104.Final" - implementation "org.slf4j:slf4j-jdk14:2.0.11" + implementation "io.netty:netty-all:4.1.108.Final" + implementation "org.slf4j:slf4j-jdk14:2.0.12" implementation "com.google.inject:guice:$guiceVersion" implementation "com.google.inject.extensions:guice-servlet:$guiceVersion" implementation "org.owasp.encoder:encoder:1.2.3" @@ -76,30 +76,30 @@ dependencies { implementation "org.apache.velocity:velocity-engine-core:2.3" implementation "org.apache.velocity.tools:velocity-tools-generic:3.1" implementation "org.apache.commons:commons-collections4:4.4" - implementation "org.mnode.ical4j:ical4j:3.2.14" + implementation "org.mnode.ical4j:ical4j:3.2.17" implementation "org.locationtech.spatial4j:spatial4j:0.8" implementation "org.locationtech.jts:jts-core:1.19.0" implementation "net.java.dev.jna:jna-platform:5.14.0" - implementation "com.github.jnr:jnr-posix:3.1.18" + implementation "com.github.jnr:jnr-posix:3.1.19" implementation "com.google.protobuf:protobuf-java:$protobufVersion" - implementation "com.amazonaws:aws-java-sdk-sns:1.12.636" - implementation "org.apache.kafka:kafka-clients:3.6.1" + implementation "com.amazonaws:aws-java-sdk-sns:1.12.694" + implementation "org.apache.kafka:kafka-clients:3.7.0" implementation "com.hivemq:hivemq-mqtt-client:1.3.3" - implementation "redis.clients:jedis:5.1.0" + implementation "redis.clients:jedis:5.1.2" implementation "com.google.firebase:firebase-admin:9.2.0" - implementation "com.nimbusds:oauth2-oidc-sdk:11.9.1" + implementation "com.nimbusds:oauth2-oidc-sdk:11.10.1" implementation "com.rabbitmq:amqp-client:5.20.0" implementation "com.warrenstrange:googleauth:1.5.0" testImplementation "org.junit.jupiter:junit-jupiter-api:$junitVersion" testImplementation "org.junit.jupiter:junit-jupiter-engine:$junitVersion" - testImplementation "org.mockito:mockito-core:5.8.0" + testImplementation "org.mockito:mockito-core:5.11.0" } test { useJUnitPlatform() } -task copyDependencies(type: Copy) { +tasks.register('copyDependencies', Copy) { into "$projectDir/target/lib" from configurations.runtimeClasspath } @@ -109,7 +109,7 @@ jar { manifest { attributes( "Main-Class": "org.traccar.Main", - "Implementation-Version": "5.12", + "Implementation-Version": "6.0", "Class-Path": configurations.runtimeClasspath.files.collect { "lib/$it.name" }.join(" ")) } } @@ -6,7 +6,7 @@ <entry key='config.default'>./setup/default.xml</entry> - <entry key='web.path'>./traccar-web/web</entry> + <entry key='web.path'>./traccar-web/simple</entry> <entry key='web.debug'>true</entry> <entry key='web.console'>true</entry> @@ -16,6 +16,7 @@ <entry key='logger.console'>true</entry> <entry key='logger.queries'>false</entry> + <entry key='logger.fullStackTraces'>true</entry> <entry key='mail.debug'>true</entry> @@ -24,6 +25,9 @@ <entry key='database.user'>sa</entry> <entry key='database.password'></entry> + <!--<entry key='server.instantAcknowledgement'>true</entry> + <entry key='server.buffering.threshold'>5000</entry>--> + <entry key='atrack.custom'>true</entry> <entry key='intellitrac.port'>6037</entry> diff --git a/schema/changelog-4.0-clean.xml b/schema/changelog-4.0-clean.xml index b4d8ac0ba..4236e6e95 100644 --- a/schema/changelog-4.0-clean.xml +++ b/schema/changelog-4.0-clean.xml @@ -335,7 +335,7 @@ <column name="id" type="INT" autoIncrement="true"> <constraints primaryKey="true" /> </column> - <column name="registration" type="BOOLEAN" defaultValueBoolean="true"> + <column name="registration" type="BOOLEAN" defaultValueBoolean="false"> <constraints nullable="false" /> </column> <column name="latitude" type="DOUBLE" defaultValueNumeric="0"> @@ -614,7 +614,7 @@ <addForeignKeyConstraint baseTableName="tc_user_user" baseColumnNames="userid" constraintName="fk_user_user_userid" onDelete="CASCADE" referencedColumnNames="id" referencedTableName="tc_users" /> <insert tableName="tc_servers"> - <column name="registration" valueBoolean="true" /> + <column name="registration" valueBoolean="false" /> <column name="latitude" valueNumeric="0" /> <column name="longitude" valueNumeric="0" /> <column name="zoom" valueNumeric="0" /> diff --git a/setup/default.xml b/setup/default.xml index 95bb1f04b..092b4f494 100644 --- a/setup/default.xml +++ b/setup/default.xml @@ -11,7 +11,7 @@ --> <entry key='web.port'>8082</entry> - <entry key='web.path'>./modern</entry> + <entry key='web.path'>./web</entry> <entry key='web.sanitize'>false</entry> <entry key='web.persistSession'>false</entry> <entry key='web.showUnknownDevices'>true</entry> diff --git a/setup/package.sh b/setup/package.sh index f8ec927eb..5b3c9bcde 100755 --- a/setup/package.sh +++ b/setup/package.sh @@ -15,7 +15,6 @@ usage () { echo "Available platforms:" echo " * linux-64" echo " * linux-arm" - echo " * linux-arm64" echo " * windows-64" echo " * other" exit 1 @@ -65,17 +64,14 @@ if [ $PLATFORM = "all" -o $PLATFORM = "windows-64" ]; then check_requirement "Windows 64 Java" "ls OpenJDK*64_windows*.zip" "Missing Windows 64 JDK (https://adoptium.net/)" check_requirement "Wine" "which wine" "Missing wine binary" fi -if [ $PLATFORM = "all" -o $PLATFORM = "linux-64" -o $PLATFORM = "linux-arm" -o $PLATFORM = "linux-arm64" ]; then +if [ $PLATFORM = "all" -o $PLATFORM = "linux-64" -o $PLATFORM = "linux-arm" ]; then check_requirement "Makeself" "which makeself" "Missing makeself binary" fi if [ $PLATFORM = "all" -o $PLATFORM = "linux-64" ]; then check_requirement "Linux 64 Java" "ls OpenJDK*x64_linux*.tar.gz" "Missing Linux 64 JDK (https://adoptium.net/)" fi if [ $PLATFORM = "all" -o $PLATFORM = "linux-arm" ]; then - check_requirement "Linux ARM Java" "ls OpenJDK*arm_linux*.tar.gz" "Missing Linux ARM JDK (https://adoptium.net/)" -fi -if [ $PLATFORM = "all" -o $PLATFORM = "linux-arm64" ]; then - check_requirement "Linux ARM 64 Java" "ls OpenJDK*aarch64_linux*.tar.gz" "Missing Linux ARM 64 JDK (https://adoptium.net/)" + check_requirement "Linux ARM Java" "ls OpenJDK*aarch64_linux*.tar.gz" "Missing Linux ARM JDK (https://adoptium.net/)" fi if [ $PREREQ = false ]; then info "Missing build requirements, aborting..." @@ -85,14 +81,13 @@ else fi prepare () { - mkdir -p out/{conf,data,lib,logs,legacy,modern,schema,templates} + mkdir -p out/{conf,data,lib,logs,web,schema,templates} cp ../target/tracker-server.jar out cp ../target/lib/* out/lib cp ../schema/* out/schema cp -r ../templates/* out/templates - cp -r ../traccar-web/web/* out/legacy - cp -r ../traccar-web/modern/build/* out/modern + cp -r ../traccar-web/build/* out/web cp default.xml out/conf cp traccar.xml out/conf @@ -158,23 +153,16 @@ package_linux_64 () { package_linux_arm () { info "Building Linux ARM installer" - package_linux arm arm + package_linux arm aarch64 ok "Created Linux ARM installer" } -package_linux_arm64 () { - info "Building Linux ARM 64 installer" - package_linux arm64 aarch64 - ok "Created Linux ARM 64 installer" -} - prepare case $PLATFORM in all) package_linux_64 package_linux_arm - package_linux_arm64 package_windows package_other ;; @@ -187,10 +175,6 @@ case $PLATFORM in package_linux_arm ;; - linux-arm64) - package_linux_arm64 - ;; - windows-64) package_windows ;; diff --git a/setup/traccar.iss b/setup/traccar.iss index 2ccee1c3e..466a40c30 100644 --- a/setup/traccar.iss +++ b/setup/traccar.iss @@ -1,6 +1,6 @@ [Setup] AppName=Traccar -AppVersion=5.12 +AppVersion=6.0 DefaultDirName={pf}\Traccar OutputBaseFilename=traccar-setup ArchitecturesInstallIn64BitMode=x64 diff --git a/src/main/java/org/traccar/BaseDataHandler.java b/src/main/java/org/traccar/BaseDataHandler.java deleted file mode 100644 index 48794b0d7..000000000 --- a/src/main/java/org/traccar/BaseDataHandler.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2015 - 2018 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; - -import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.ChannelInboundHandlerAdapter; -import org.traccar.model.Position; - -public abstract class BaseDataHandler extends ChannelInboundHandlerAdapter { - - @Override - public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { - if (msg instanceof Position) { - Position position = handlePosition((Position) msg); - if (position != null) { - ctx.fireChannelRead(position); - } - } else { - super.channelRead(ctx, msg); - } - } - - protected abstract Position handlePosition(Position position); - -} diff --git a/src/main/java/org/traccar/BasePipelineFactory.java b/src/main/java/org/traccar/BasePipelineFactory.java index ca4a4ae63..40360cca7 100644 --- a/src/main/java/org/traccar/BasePipelineFactory.java +++ b/src/main/java/org/traccar/BasePipelineFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2012 - 2023 Anton Tananaev (anton@traccar.org) + * Copyright 2012 - 2024 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,36 +25,13 @@ 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; -import org.traccar.handler.DistanceHandler; -import org.traccar.handler.EngineHoursHandler; -import org.traccar.handler.FilterHandler; -import org.traccar.handler.GeocoderHandler; -import org.traccar.handler.GeofenceHandler; -import org.traccar.handler.GeolocationHandler; -import org.traccar.handler.HemisphereHandler; -import org.traccar.handler.MotionHandler; -import org.traccar.handler.NetworkForwarderHandler; -import org.traccar.handler.NetworkMessageHandler; -import org.traccar.handler.OpenChannelHandler; -import org.traccar.handler.RemoteAddressHandler; -import org.traccar.handler.SpeedLimitHandler; -import org.traccar.handler.StandardLoggingHandler; -import org.traccar.handler.TimeHandler; -import org.traccar.handler.events.AlertEventHandler; -import org.traccar.handler.events.BehaviorEventHandler; -import org.traccar.handler.events.CommandResultEventHandler; -import org.traccar.handler.events.DriverEventHandler; -import org.traccar.handler.events.FuelEventHandler; -import org.traccar.handler.events.GeofenceEventHandler; -import org.traccar.handler.events.IgnitionEventHandler; -import org.traccar.handler.events.MaintenanceEventHandler; -import org.traccar.handler.events.MediaEventHandler; -import org.traccar.handler.events.MotionEventHandler; -import org.traccar.handler.events.OverspeedEventHandler; +import org.traccar.handler.network.AcknowledgementHandler; +import org.traccar.handler.network.MainEventHandler; +import org.traccar.handler.network.NetworkForwarderHandler; +import org.traccar.handler.network.NetworkMessageHandler; +import org.traccar.handler.network.OpenChannelHandler; +import org.traccar.handler.network.RemoteAddressHandler; +import org.traccar.handler.network.StandardLoggingHandler; import java.util.Map; @@ -83,15 +60,7 @@ public abstract class BasePipelineFactory extends ChannelInitializer<Channel> { protected abstract void addProtocolHandlers(PipelineBuilder pipeline); - @SafeVarargs - private void addHandlers(ChannelPipeline pipeline, Class<? extends ChannelHandler>... handlerClasses) { - for (Class<? extends ChannelHandler> handlerClass : handlerClasses) { - if (handlerClass != null) { - pipeline.addLast(injector.getInstance(handlerClass)); - } - } - } - + @SuppressWarnings("unchecked") public static <T extends ChannelHandler> T getHandler(ChannelPipeline pipeline, Class<T> clazz) { for (Map.Entry<String, ChannelHandler> handlerEntry : pipeline) { ChannelHandler handler = handlerEntry.getValue(); @@ -107,6 +76,11 @@ public abstract class BasePipelineFactory extends ChannelInitializer<Channel> { return null; } + private <T> T injectMembers(T object) { + injector.injectMembers(object); + return object; + } + @Override protected void initChannel(Channel channel) { final ChannelPipeline pipeline = channel.pipeline(); @@ -119,15 +93,10 @@ public abstract class BasePipelineFactory extends ChannelInitializer<Channel> { pipeline.addLast(new OpenChannelHandler(connector)); if (config.hasKey(Keys.SERVER_FORWARD)) { int port = config.getInteger(Keys.PROTOCOL_PORT.withPrefix(protocol)); - var handler = new NetworkForwarderHandler(port); - injector.injectMembers(handler); - pipeline.addLast(handler); + pipeline.addLast(injectMembers(new NetworkForwarderHandler(port))); } pipeline.addLast(new NetworkMessageHandler()); - - var loggingHandler = new StandardLoggingHandler(protocol); - injector.injectMembers(loggingHandler); - pipeline.addLast(loggingHandler); + pipeline.addLast(injectMembers(new StandardLoggingHandler(protocol))); if (!connector.isDatagram() && !config.getBoolean(Keys.SERVER_INSTANT_ACKNOWLEDGEMENT)) { pipeline.addLast(new AcknowledgementHandler()); @@ -135,7 +104,7 @@ public abstract class BasePipelineFactory extends ChannelInitializer<Channel> { addProtocolHandlers(handler -> { if (handler instanceof BaseProtocolDecoder || handler instanceof BaseProtocolEncoder) { - injector.injectMembers(handler); + injectMembers(handler); } else { if (handler instanceof ChannelInboundHandler) { handler = new WrapperInboundHandler((ChannelInboundHandler) handler); @@ -146,35 +115,9 @@ public abstract class BasePipelineFactory extends ChannelInitializer<Channel> { pipeline.addLast(handler); }); - addHandlers( - pipeline, - TimeHandler.class, - GeolocationHandler.class, - HemisphereHandler.class, - DistanceHandler.class, - RemoteAddressHandler.class, - FilterHandler.class, - GeofenceHandler.class, - GeocoderHandler.class, - SpeedLimitHandler.class, - MotionHandler.class, - CopyAttributesHandler.class, - EngineHoursHandler.class, - ComputedAttributesHandler.class, - PositionForwardingHandler.class, - DefaultDataHandler.class, - MediaEventHandler.class, - CommandResultEventHandler.class, - OverspeedEventHandler.class, - BehaviorEventHandler.class, - FuelEventHandler.class, - MotionEventHandler.class, - GeofenceEventHandler.class, - AlertEventHandler.class, - IgnitionEventHandler.class, - MaintenanceEventHandler.class, - DriverEventHandler.class, - MainEventHandler.class); + pipeline.addLast(injector.getInstance(RemoteAddressHandler.class)); + pipeline.addLast(injector.getInstance(ProcessingHandler.class)); + pipeline.addLast(injector.getInstance(MainEventHandler.class)); } } diff --git a/src/main/java/org/traccar/BaseProtocolDecoder.java b/src/main/java/org/traccar/BaseProtocolDecoder.java index 495a866c0..b764e5cdf 100644 --- a/src/main/java/org/traccar/BaseProtocolDecoder.java +++ b/src/main/java/org/traccar/BaseProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2012 - 2022 Anton Tananaev (anton@traccar.org) + * Copyright 2012 - 2024 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. @@ -154,25 +154,8 @@ public abstract class BaseProtocolDecoder extends ExtendedObjectDecoder { public void getLastLocation(Position position, Date deviceTime) { if (position.getDeviceId() != 0) { position.setOutdated(true); - - Position last = cacheManager.getPosition(position.getDeviceId()); - if (last != null) { - position.setFixTime(last.getFixTime()); - position.setValid(last.getValid()); - position.setLatitude(last.getLatitude()); - position.setLongitude(last.getLongitude()); - position.setAltitude(last.getAltitude()); - position.setSpeed(last.getSpeed()); - position.setCourse(last.getCourse()); - position.setAccuracy(last.getAccuracy()); - } else { - position.setFixTime(new Date(0)); - } - if (deviceTime != null) { position.setDeviceTime(deviceTime); - } else { - position.setDeviceTime(new Date()); } } } diff --git a/src/main/java/org/traccar/ExtendedObjectDecoder.java b/src/main/java/org/traccar/ExtendedObjectDecoder.java index cddddcd80..9468e2fff 100644 --- a/src/main/java/org/traccar/ExtendedObjectDecoder.java +++ b/src/main/java/org/traccar/ExtendedObjectDecoder.java @@ -23,7 +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.handler.network.AcknowledgementHandler; import org.traccar.helper.DataConverter; import org.traccar.model.Position; diff --git a/src/main/java/org/traccar/MainEventHandler.java b/src/main/java/org/traccar/MainEventHandler.java deleted file mode 100644 index fb0171d63..000000000 --- a/src/main/java/org/traccar/MainEventHandler.java +++ /dev/null @@ -1,194 +0,0 @@ -/* - * Copyright 2012 - 2022 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; - -import io.netty.channel.Channel; -import io.netty.channel.ChannelHandler; -import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.ChannelInboundHandlerAdapter; -import io.netty.channel.socket.DatagramChannel; -import io.netty.handler.codec.http.HttpRequestDecoder; -import io.netty.handler.timeout.IdleStateEvent; -import org.slf4j.Logger; -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; -import org.traccar.model.Device; -import org.traccar.model.Position; -import org.traccar.session.ConnectionManager; -import org.traccar.session.cache.CacheManager; -import org.traccar.storage.Storage; -import org.traccar.storage.StorageException; -import org.traccar.storage.query.Columns; -import org.traccar.storage.query.Condition; -import org.traccar.storage.query.Request; - -import jakarta.inject.Inject; -import jakarta.inject.Singleton; -import java.util.Arrays; -import java.util.HashSet; -import java.util.LinkedHashSet; -import java.util.Set; - -@Singleton -@ChannelHandler.Sharable -public class MainEventHandler extends ChannelInboundHandlerAdapter { - - private static final Logger LOGGER = LoggerFactory.getLogger(MainEventHandler.class); - - private final Set<String> connectionlessProtocols = new HashSet<>(); - private final Set<String> logAttributes = new LinkedHashSet<>(); - - private final CacheManager cacheManager; - private final Storage storage; - private final ConnectionManager connectionManager; - private final StatisticsManager statisticsManager; - - @Inject - public MainEventHandler( - Config config, CacheManager cacheManager, Storage storage, ConnectionManager connectionManager, - StatisticsManager statisticsManager) { - this.cacheManager = cacheManager; - this.storage = storage; - this.connectionManager = connectionManager; - this.statisticsManager = statisticsManager; - String connectionlessProtocolList = config.getString(Keys.STATUS_IGNORE_OFFLINE); - if (connectionlessProtocolList != null) { - connectionlessProtocols.addAll(Arrays.asList(connectionlessProtocolList.split("[, ]"))); - } - logAttributes.addAll(Arrays.asList(config.getString(Keys.LOGGER_ATTRIBUTES).split("[, ]"))); - } - - @Override - public void channelRead(ChannelHandlerContext ctx, Object msg) { - if (msg instanceof Position) { - - Position position = (Position) msg; - Device device = cacheManager.getObject(Device.class, position.getDeviceId()); - - try { - if (PositionUtil.isLatest(cacheManager, position)) { - Device updatedDevice = new Device(); - updatedDevice.setId(position.getDeviceId()); - updatedDevice.setPositionId(position.getId()); - storage.updateObject(updatedDevice, new Request( - new Columns.Include("positionId"), - new Condition.Equals("id", updatedDevice.getId()))); - - cacheManager.updatePosition(position); - connectionManager.updatePosition(true, position); - } - } catch (StorageException error) { - LOGGER.warn("Failed to update device", error); - } - - StringBuilder builder = new StringBuilder(); - builder.append("[").append(NetworkUtil.session(ctx.channel())).append("] "); - builder.append("id: ").append(device.getUniqueId()); - for (String attribute : logAttributes) { - switch (attribute) { - case "time": - builder.append(", time: ").append(DateUtil.formatDate(position.getFixTime(), false)); - break; - case "position": - builder.append(", lat: ").append(String.format("%.5f", position.getLatitude())); - builder.append(", lon: ").append(String.format("%.5f", position.getLongitude())); - break; - case "speed": - if (position.getSpeed() > 0) { - builder.append(", speed: ").append(String.format("%.1f", position.getSpeed())); - } - break; - case "course": - builder.append(", course: ").append(String.format("%.1f", position.getCourse())); - break; - case "accuracy": - if (position.getAccuracy() > 0) { - builder.append(", accuracy: ").append(String.format("%.1f", position.getAccuracy())); - } - break; - case "outdated": - if (position.getOutdated()) { - builder.append(", outdated"); - } - break; - case "invalid": - if (!position.getValid()) { - builder.append(", invalid"); - } - break; - default: - Object value = position.getAttributes().get(attribute); - if (value != null) { - builder.append(", ").append(attribute).append(": ").append(value); - } - break; - } - } - LOGGER.info(builder.toString()); - - statisticsManager.registerMessageStored(position.getDeviceId(), position.getProtocol()); - - ctx.writeAndFlush(new AcknowledgementHandler.EventHandled(position)); - } - } - - @Override - public void channelActive(ChannelHandlerContext ctx) { - if (!(ctx.channel() instanceof DatagramChannel)) { - LOGGER.info("[{}] connected", NetworkUtil.session(ctx.channel())); - } - } - - @Override - public void channelInactive(ChannelHandlerContext ctx) { - LOGGER.info("[{}] disconnected", NetworkUtil.session(ctx.channel())); - closeChannel(ctx.channel()); - - boolean supportsOffline = BasePipelineFactory.getHandler(ctx.pipeline(), HttpRequestDecoder.class) == null - && !connectionlessProtocols.contains(ctx.pipeline().get(BaseProtocolDecoder.class).getProtocolName()); - connectionManager.deviceDisconnected(ctx.channel(), supportsOffline); - } - - @Override - public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { - while (cause.getCause() != null && cause.getCause() != cause) { - cause = cause.getCause(); - } - LOGGER.info("[{}] error", NetworkUtil.session(ctx.channel()), cause); - closeChannel(ctx.channel()); - } - - @Override - public void userEventTriggered(ChannelHandlerContext ctx, Object evt) { - if (evt instanceof IdleStateEvent) { - LOGGER.info("[{}] timed out", NetworkUtil.session(ctx.channel())); - closeChannel(ctx.channel()); - } - } - - private void closeChannel(Channel channel) { - if (!(channel instanceof DatagramChannel)) { - channel.close(); - } - } - -} diff --git a/src/main/java/org/traccar/MainModule.java b/src/main/java/org/traccar/MainModule.java index 26654947e..791d61c61 100644 --- a/src/main/java/org/traccar/MainModule.java +++ b/src/main/java/org/traccar/MainModule.java @@ -72,9 +72,12 @@ import org.traccar.geolocation.GeolocationProvider; import org.traccar.geolocation.GoogleGeolocationProvider; import org.traccar.geolocation.OpenCellIdGeolocationProvider; import org.traccar.geolocation.UnwiredGeolocationProvider; +import org.traccar.handler.CopyAttributesHandler; +import org.traccar.handler.FilterHandler; import org.traccar.handler.GeocoderHandler; import org.traccar.handler.GeolocationHandler; import org.traccar.handler.SpeedLimitHandler; +import org.traccar.handler.TimeHandler; import org.traccar.helper.ObjectMapperContextResolver; import org.traccar.helper.SanitizerModule; import org.traccar.helper.WebHelper; @@ -338,6 +341,34 @@ public class MainModule extends AbstractModule { @Singleton @Provides + public static CopyAttributesHandler provideCopyAttributesHandler(Config config, CacheManager cacheManager) { + if (config.getBoolean(Keys.PROCESSING_COPY_ATTRIBUTES_ENABLE)) { + return new CopyAttributesHandler(config, cacheManager); + } + return null; + } + + @Singleton + @Provides + public static FilterHandler provideFilterHandler( + Config config, CacheManager cacheManager, Storage storage, StatisticsManager statisticsManager) { + if (config.getBoolean(Keys.FILTER_ENABLE)) { + return new FilterHandler(config, cacheManager, storage, statisticsManager); + } + return null; + } + + @Singleton + @Provides + public static TimeHandler provideTimeHandler(Config config) { + if (config.hasKey(Keys.TIME_OVERRIDE)) { + return new TimeHandler(config); + } + return null; + } + + @Singleton + @Provides public static BroadcastService provideBroadcastService( Config config, ObjectMapper objectMapper) throws IOException { if (config.hasKey(Keys.BROADCAST_TYPE)) { diff --git a/src/main/java/org/traccar/ProcessingHandler.java b/src/main/java/org/traccar/ProcessingHandler.java new file mode 100644 index 000000000..fd048d127 --- /dev/null +++ b/src/main/java/org/traccar/ProcessingHandler.java @@ -0,0 +1,203 @@ +/* + * Copyright 2024 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; + +import com.google.inject.Injector; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import jakarta.inject.Inject; +import jakarta.inject.Singleton; +import org.traccar.config.Config; +import org.traccar.database.BufferingManager; +import org.traccar.database.NotificationManager; +import org.traccar.handler.BasePositionHandler; +import org.traccar.handler.ComputedAttributesHandler; +import org.traccar.handler.CopyAttributesHandler; +import org.traccar.handler.DatabaseHandler; +import org.traccar.handler.DistanceHandler; +import org.traccar.handler.EngineHoursHandler; +import org.traccar.handler.FilterHandler; +import org.traccar.handler.GeocoderHandler; +import org.traccar.handler.GeofenceHandler; +import org.traccar.handler.GeolocationHandler; +import org.traccar.handler.HemisphereHandler; +import org.traccar.handler.MotionHandler; +import org.traccar.handler.OutdatedHandler; +import org.traccar.handler.PositionForwardingHandler; +import org.traccar.handler.PostProcessHandler; +import org.traccar.handler.SpeedLimitHandler; +import org.traccar.handler.TimeHandler; +import org.traccar.handler.events.AlertEventHandler; +import org.traccar.handler.events.BaseEventHandler; +import org.traccar.handler.events.BehaviorEventHandler; +import org.traccar.handler.events.CommandResultEventHandler; +import org.traccar.handler.events.DriverEventHandler; +import org.traccar.handler.events.FuelEventHandler; +import org.traccar.handler.events.GeofenceEventHandler; +import org.traccar.handler.events.IgnitionEventHandler; +import org.traccar.handler.events.MaintenanceEventHandler; +import org.traccar.handler.events.MediaEventHandler; +import org.traccar.handler.events.MotionEventHandler; +import org.traccar.handler.events.OverspeedEventHandler; +import org.traccar.handler.network.AcknowledgementHandler; +import org.traccar.helper.PositionLogger; +import org.traccar.model.Position; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Queue; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +@Singleton +@ChannelHandler.Sharable +public class ProcessingHandler extends ChannelInboundHandlerAdapter implements BufferingManager.Callback { + + private final NotificationManager notificationManager; + private final PositionLogger positionLogger; + private final BufferingManager bufferingManager; + private final List<BasePositionHandler> positionHandlers; + private final List<BaseEventHandler> eventHandlers; + private final PostProcessHandler postProcessHandler; + + private final Map<Long, Queue<Position>> queues = new HashMap<>(); + + private synchronized Queue<Position> getQueue(long deviceId) { + return queues.computeIfAbsent(deviceId, k -> new LinkedList<>()); + } + + @Inject + public ProcessingHandler( + Injector injector, Config config, NotificationManager notificationManager, PositionLogger positionLogger) { + this.notificationManager = notificationManager; + this.positionLogger = positionLogger; + bufferingManager = new BufferingManager(config, this); + + positionHandlers = Stream.of( + OutdatedHandler.class, + TimeHandler.class, + GeolocationHandler.class, + HemisphereHandler.class, + DistanceHandler.class, + FilterHandler.class, + GeofenceHandler.class, + GeocoderHandler.class, + SpeedLimitHandler.class, + MotionHandler.class, + EngineHoursHandler.class, + ComputedAttributesHandler.class, + CopyAttributesHandler.class, + PositionForwardingHandler.class, + DatabaseHandler.class) + .map((clazz) -> (BasePositionHandler) injector.getInstance(clazz)) + .filter(Objects::nonNull) + .collect(Collectors.toUnmodifiableList()); + + eventHandlers = Stream.of( + MediaEventHandler.class, + CommandResultEventHandler.class, + OverspeedEventHandler.class, + BehaviorEventHandler.class, + FuelEventHandler.class, + MotionEventHandler.class, + GeofenceEventHandler.class, + AlertEventHandler.class, + IgnitionEventHandler.class, + MaintenanceEventHandler.class, + DriverEventHandler.class) + .map((clazz) -> (BaseEventHandler) injector.getInstance(clazz)) + .filter(Objects::nonNull) + .collect(Collectors.toUnmodifiableList()); + + postProcessHandler = injector.getInstance(PostProcessHandler.class); + } + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + if (msg instanceof Position) { + bufferingManager.accept(ctx, (Position) msg); + } else { + super.channelRead(ctx, msg); + } + } + + @Override + public void onReleased(ChannelHandlerContext context, Position position) { + Queue<Position> queue = getQueue(position.getDeviceId()); + boolean queued; + synchronized (queue) { + queued = !queue.isEmpty(); + queue.offer(position); + } + if (!queued) { + processPositionHandlers(context, position); + } + } + + private void processPositionHandlers(ChannelHandlerContext ctx, Position position) { + var iterator = positionHandlers.iterator(); + iterator.next().handlePosition(position, new BasePositionHandler.Callback() { + @Override + public void processed(boolean filtered) { + if (!filtered) { + if (iterator.hasNext()) { + iterator.next().handlePosition(position, this); + } else { + processEventHandlers(ctx, position); + } + } else { + finishedProcessing(ctx, position, true); + } + } + }); + } + + private void processEventHandlers(ChannelHandlerContext ctx, Position position) { + eventHandlers.forEach(handler -> handler.analyzePosition( + position, (event) -> notificationManager.updateEvents(Map.of(event, position)))); + finishedProcessing(ctx, position, false); + } + + private void finishedProcessing(ChannelHandlerContext ctx, Position position, boolean filtered) { + if (!filtered) { + postProcessHandler.handlePosition(position, ignore -> { + positionLogger.log(ctx, position); + ctx.writeAndFlush(new AcknowledgementHandler.EventHandled(position)); + processNextPosition(ctx, position.getDeviceId()); + }); + } else { + ctx.writeAndFlush(new AcknowledgementHandler.EventHandled(position)); + processNextPosition(ctx, position.getDeviceId()); + } + } + + private void processNextPosition(ChannelHandlerContext ctx, long deviceId) { + Queue<Position> queue = getQueue(deviceId); + Position nextPosition; + synchronized (queue) { + queue.poll(); // remove current position + nextPosition = queue.peek(); + } + if (nextPosition != null) { + processPositionHandlers(ctx, nextPosition); + } + } + +} diff --git a/src/main/java/org/traccar/ServerManager.java b/src/main/java/org/traccar/ServerManager.java index e91be50a2..22af66b41 100644 --- a/src/main/java/org/traccar/ServerManager.java +++ b/src/main/java/org/traccar/ServerManager.java @@ -82,12 +82,8 @@ public class ServerManager implements LifecycleObject { @Override public void stop() throws Exception { - try { - for (TrackerConnector connector : connectorList) { - connector.stop(); - } - } finally { - GlobalTimer.release(); + for (TrackerConnector connector : connectorList) { + connector.stop(); } } diff --git a/src/main/java/org/traccar/api/resource/DeviceResource.java b/src/main/java/org/traccar/api/resource/DeviceResource.java index 89bba7237..56253152f 100644 --- a/src/main/java/org/traccar/api/resource/DeviceResource.java +++ b/src/main/java/org/traccar/api/resource/DeviceResource.java @@ -62,6 +62,9 @@ import java.util.List; @Consumes(MediaType.APPLICATION_JSON) public class DeviceResource extends BaseObjectResource<Device> { + private static final int DEFAULT_BUFFER_SIZE = 8192; + private static final int IMAGE_SIZE_LIMIT = 500000; + @Inject private Config config; @@ -172,6 +175,23 @@ public class DeviceResource extends BaseObjectResource<Device> { return Response.noContent().build(); } + private String imageExtension(String type) { + switch (type) { + case "image/jpeg": + return "jpg"; + case "image/png": + return "png"; + case "image/gif": + return "gif"; + case "image/webp": + return "webp"; + case "image/svg+xml": + return "svg"; + default: + throw new IllegalArgumentException("Unsupported image type"); + } + } + @Path("{id}/image") @POST @Consumes("image/*") @@ -186,10 +206,20 @@ public class DeviceResource extends BaseObjectResource<Device> { new Condition.Permission(User.class, getUserId(), Device.class)))); if (device != null) { String name = "device"; - String extension = type.substring("image/".length()); + String extension = imageExtension(type); try (var input = new FileInputStream(file); var output = mediaManager.createFileStream(device.getUniqueId(), name, extension)) { - input.transferTo(output); + + long transferred = 0; + byte[] buffer = new byte[DEFAULT_BUFFER_SIZE]; + int read; + while ((read = input.read(buffer, 0, buffer.length)) >= 0) { + output.write(buffer, 0, read); + transferred += read; + if (transferred > IMAGE_SIZE_LIMIT) { + throw new IllegalArgumentException("Image size limit exceeded"); + } + } } return Response.ok(name + "." + extension).build(); } diff --git a/src/main/java/org/traccar/api/resource/UserResource.java b/src/main/java/org/traccar/api/resource/UserResource.java index 47ea9b07c..fbc31e46a 100644 --- a/src/main/java/org/traccar/api/resource/UserResource.java +++ b/src/main/java/org/traccar/api/resource/UserResource.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 - 2022 Anton Tananaev (anton@traccar.org) + * Copyright 2015 - 2024 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. @@ -95,7 +95,9 @@ public class UserResource extends BaseObjectResource<User> { } } } else { - if (!permissionsService.getServer().getRegistration()) { + if (UserUtil.isEmpty(storage)) { + entity.setAdministrator(true); + } else if (!permissionsService.getServer().getRegistration()) { throw new SecurityException("Registration disabled"); } if (permissionsService.getServer().getBoolean(Keys.WEB_TOTP_FORCE.getKey()) @@ -106,10 +108,6 @@ public class UserResource extends BaseObjectResource<User> { } } - if (UserUtil.isEmpty(storage)) { - entity.setAdministrator(true); - } - entity.setId(storage.addObject(entity, new Request(new Columns.Exclude("id")))); storage.updateObject(entity, new Request( new Columns.Include("hashedPassword", "salt"), diff --git a/src/main/java/org/traccar/config/Keys.java b/src/main/java/org/traccar/config/Keys.java index 4aacb2cd8..d346084bd 100644 --- a/src/main/java/org/traccar/config/Keys.java +++ b/src/main/java/org/traccar/config/Keys.java @@ -293,6 +293,14 @@ public final class Keys { false); /** + * If not zero, enable buffering of incoming data to handle ordering locations. The value is threshold for + * buffering in milliseconds. + */ + public static final ConfigKey<Long> SERVER_BUFFERING_THRESHOLD = new LongConfigKey( + "server.buffering.threshold", + List.of(KeyType.CONFIG)); + + /** * Server wide connection timeout value in seconds. See protocol timeout for more information. */ public static final ConfigKey<Integer> SERVER_TIMEOUT = new IntegerConfigKey( diff --git a/src/main/java/org/traccar/database/BufferingManager.java b/src/main/java/org/traccar/database/BufferingManager.java new file mode 100644 index 000000000..4d288c8d0 --- /dev/null +++ b/src/main/java/org/traccar/database/BufferingManager.java @@ -0,0 +1,115 @@ +/* + * Copyright 2024 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.database; + +import io.netty.channel.ChannelHandlerContext; +import io.netty.util.HashedWheelTimer; +import io.netty.util.Timeout; +import io.netty.util.Timer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.traccar.config.Config; +import org.traccar.config.Keys; +import org.traccar.model.Position; + +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.TreeSet; +import java.util.concurrent.TimeUnit; + +public class BufferingManager { + + private static final Logger LOGGER = LoggerFactory.getLogger(BufferingManager.class); + + public interface Callback { + void onReleased(ChannelHandlerContext context, Position position); + } + + private static final class Holder implements Comparable<Holder> { + + private final ChannelHandlerContext context; + private final Position position; + private Timeout timeout; + + private Holder(ChannelHandlerContext context, Position position) { + this.context = context; + this.position = position; + } + + private int compareTime(Date left, Date right) { + if (left != null && right != null) { + return left.compareTo(right); + } + return 0; + } + + @Override + public int compareTo(Holder other) { + int fixTimeResult = compareTime(position.getFixTime(), other.position.getFixTime()); + if (fixTimeResult != 0) { + return fixTimeResult; + } + + int deviceTimeResult = compareTime(position.getDeviceTime(), other.position.getDeviceTime()); + if (deviceTimeResult != 0) { + return deviceTimeResult; + } + + return position.getServerTime().compareTo(other.position.getServerTime()); + } + } + + private final Timer timer = new HashedWheelTimer(); + private final Callback callback; + private final long threshold; + + private final Map<Long, TreeSet<Holder>> buffer = new HashMap<>(); + + public BufferingManager(Config config, Callback callback) { + this.callback = callback; + threshold = config.getLong(Keys.SERVER_BUFFERING_THRESHOLD); + } + + private Timeout scheduleTimeout(Holder holder) { + return timer.newTimeout( + timeout -> { + LOGGER.info("released {}", holder.position.getFixTime()); + buffer.get(holder.position.getDeviceId()).remove(holder); + callback.onReleased(holder.context, holder.position); + }, + threshold, TimeUnit.MILLISECONDS); + } + + public void accept(ChannelHandlerContext context, Position position) { + if (threshold > 0) { + synchronized (buffer) { + LOGGER.info("queued {}", position.getFixTime()); + var queue = buffer.computeIfAbsent(position.getDeviceId(), k -> new TreeSet<>()); + Holder holder = new Holder(context, position); + holder.timeout = scheduleTimeout(holder); + queue.add(holder); + queue.tailSet(holder).forEach(h -> { + h.timeout.cancel(); + h.timeout = scheduleTimeout(h); + }); + } + } else { + callback.onReleased(context, position); + } + } + +} diff --git a/src/main/java/org/traccar/database/GroupTree.java b/src/main/java/org/traccar/database/GroupTree.java deleted file mode 100644 index 8798f55bc..000000000 --- a/src/main/java/org/traccar/database/GroupTree.java +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Copyright 2016 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.database; - -import org.traccar.model.Device; -import org.traccar.model.Group; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -public class GroupTree { - - private static class TreeNode { - - private Group group; - private Device device; - private Collection<TreeNode> children = new HashSet<>(); - - TreeNode(Group group) { - this.group = group; - } - - TreeNode(Device device) { - this.device = device; - } - - @Override - public int hashCode() { - if (group != null) { - return (int) group.getId(); - } else { - return (int) device.getId(); - } - } - - @Override - public boolean equals(Object obj) { - if (!(obj instanceof TreeNode)) { - return false; - } - TreeNode other = (TreeNode) obj; - if (other == this) { - return true; - } - if (group != null && other.group != null) { - return group.getId() == other.group.getId(); - } else if (device != null && other.device != null) { - return device.getId() == other.device.getId(); - } - return false; - } - - public Group getGroup() { - return group; - } - - public Device getDevice() { - return device; - } - - public void setParent(TreeNode parent) { - if (parent != null) { - parent.children.add(this); - } - } - - public Collection<TreeNode> getChildren() { - return children; - } - - } - - private final Map<Long, TreeNode> groupMap = new HashMap<>(); - - public GroupTree(Collection<Group> groups, Collection<Device> devices) { - - for (Group group : groups) { - groupMap.put(group.getId(), new TreeNode(group)); - } - - for (TreeNode node : groupMap.values()) { - if (node.getGroup().getGroupId() != 0) { - node.setParent(groupMap.get(node.getGroup().getGroupId())); - } - } - - Map<Long, TreeNode> deviceMap = new HashMap<>(); - - for (Device device : devices) { - deviceMap.put(device.getId(), new TreeNode(device)); - } - - for (TreeNode node : deviceMap.values()) { - if (node.getDevice().getGroupId() != 0) { - node.setParent(groupMap.get(node.getDevice().getGroupId())); - } - } - - } - - public Collection<Group> getGroups(long groupId) { - Set<TreeNode> results = new HashSet<>(); - getNodes(results, groupMap.get(groupId)); - Collection<Group> groups = new ArrayList<>(); - for (TreeNode node : results) { - if (node.getGroup() != null) { - groups.add(node.getGroup()); - } - } - return groups; - } - - public Collection<Device> getDevices(long groupId) { - Set<TreeNode> results = new HashSet<>(); - getNodes(results, groupMap.get(groupId)); - Collection<Device> devices = new ArrayList<>(); - for (TreeNode node : results) { - if (node.getDevice() != null) { - devices.add(node.getDevice()); - } - } - return devices; - } - - private void getNodes(Set<TreeNode> results, TreeNode node) { - if (node != null) { - for (TreeNode child : node.getChildren()) { - results.add(child); - getNodes(results, child); - } - } - } - -} diff --git a/src/main/java/org/traccar/GlobalTimer.java b/src/main/java/org/traccar/handler/BasePositionHandler.java index a97321ba2..0a82e96b7 100644 --- a/src/main/java/org/traccar/GlobalTimer.java +++ b/src/main/java/org/traccar/handler/BasePositionHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2012 Anton Tananaev (anton@traccar.org) + * Copyright 2015 - 2024 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. @@ -13,30 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.traccar; +package org.traccar.handler; -import io.netty.util.HashedWheelTimer; -import io.netty.util.Timer; +import org.traccar.model.Position; -public final class GlobalTimer { +public abstract class BasePositionHandler { - private static Timer instance = null; - - private GlobalTimer() { - } - - public static void release() { - if (instance != null) { - instance.stop(); - } - instance = null; - } - - public static Timer getTimer() { - if (instance == null) { - instance = new HashedWheelTimer(); - } - return instance; + public interface Callback { + void processed(boolean filtered); } + public abstract void handlePosition(Position position, Callback callback); } diff --git a/src/main/java/org/traccar/handler/ComputedAttributesHandler.java b/src/main/java/org/traccar/handler/ComputedAttributesHandler.java index 8b010ceae..4293bd1fc 100644 --- a/src/main/java/org/traccar/handler/ComputedAttributesHandler.java +++ b/src/main/java/org/traccar/handler/ComputedAttributesHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 - 2023 Anton Tananaev (anton@traccar.org) + * Copyright 2017 - 2024 Anton Tananaev (anton@traccar.org) * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,26 +16,15 @@ */ package org.traccar.handler; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.List; - -import io.netty.channel.ChannelHandler; -import org.apache.commons.jexl3.JexlFeatures; -import org.apache.commons.jexl3.JexlEngine; +import jakarta.inject.Inject; import org.apache.commons.jexl3.JexlBuilder; -import org.apache.commons.jexl3.introspection.JexlSandbox; +import org.apache.commons.jexl3.JexlEngine; import org.apache.commons.jexl3.JexlException; +import org.apache.commons.jexl3.JexlFeatures; import org.apache.commons.jexl3.MapContext; +import org.apache.commons.jexl3.introspection.JexlSandbox; 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.model.Attribute; @@ -43,12 +32,17 @@ import org.traccar.model.Device; import org.traccar.model.Position; import org.traccar.session.cache.CacheManager; -import jakarta.inject.Inject; -import jakarta.inject.Singleton; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; -@Singleton -@ChannelHandler.Sharable -public class ComputedAttributesHandler extends BaseDataHandler { +public class ComputedAttributesHandler extends BasePositionHandler { private static final Logger LOGGER = LoggerFactory.getLogger(ComputedAttributesHandler.class); @@ -144,7 +138,7 @@ public class ComputedAttributesHandler extends BaseDataHandler { } @Override - protected Position handlePosition(Position position) { + public void handlePosition(Position position, Callback callback) { Collection<Attribute> attributes = cacheManager.getDeviceObjects(position.getDeviceId(), Attribute.class); for (Attribute attribute : attributes) { if (attribute.getAttribute() != null) { @@ -202,7 +196,7 @@ public class ComputedAttributesHandler extends BaseDataHandler { } } } - return position; + callback.processed(false); } } diff --git a/src/main/java/org/traccar/handler/CopyAttributesHandler.java b/src/main/java/org/traccar/handler/CopyAttributesHandler.java index 42b438e41..c7452e58c 100644 --- a/src/main/java/org/traccar/handler/CopyAttributesHandler.java +++ b/src/main/java/org/traccar/handler/CopyAttributesHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 - 2022 Anton Tananaev (anton@traccar.org) + * Copyright 2016 - 2024 Anton Tananaev (anton@traccar.org) * Copyright 2016 - 2017 Andrey Kunitsyn (andrey@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,45 +16,35 @@ */ package org.traccar.handler; -import io.netty.channel.ChannelHandler; -import org.traccar.BaseDataHandler; +import jakarta.inject.Inject; import org.traccar.config.Config; import org.traccar.config.Keys; import org.traccar.helper.model.AttributeUtil; import org.traccar.model.Position; import org.traccar.session.cache.CacheManager; -import jakarta.inject.Inject; -import jakarta.inject.Singleton; - -@Singleton -@ChannelHandler.Sharable -public class CopyAttributesHandler extends BaseDataHandler { +public class CopyAttributesHandler extends BasePositionHandler { - private final boolean enabled; private final CacheManager cacheManager; @Inject public CopyAttributesHandler(Config config, CacheManager cacheManager) { - enabled = config.getBoolean(Keys.PROCESSING_COPY_ATTRIBUTES_ENABLE); this.cacheManager = cacheManager; } @Override - protected Position handlePosition(Position position) { - if (enabled) { - String attributesString = AttributeUtil.lookup( - cacheManager, Keys.PROCESSING_COPY_ATTRIBUTES, position.getDeviceId()); - Position last = cacheManager.getPosition(position.getDeviceId()); - if (last != null && attributesString != null) { - for (String attribute : attributesString.split("[ ,]")) { - if (last.hasAttribute(attribute) && !position.hasAttribute(attribute)) { - position.getAttributes().put(attribute, last.getAttributes().get(attribute)); - } + public void handlePosition(Position position, Callback callback) { + String attributesString = AttributeUtil.lookup( + cacheManager, Keys.PROCESSING_COPY_ATTRIBUTES, position.getDeviceId()); + Position last = cacheManager.getPosition(position.getDeviceId()); + if (last != null && attributesString != null) { + for (String attribute : attributesString.split("[ ,]")) { + if (last.hasAttribute(attribute) && !position.hasAttribute(attribute)) { + position.getAttributes().put(attribute, last.getAttributes().get(attribute)); } } } - return position; + callback.processed(false); } } diff --git a/src/main/java/org/traccar/handler/DefaultDataHandler.java b/src/main/java/org/traccar/handler/DatabaseHandler.java index cca6dcd0a..5d96ebb34 100644 --- a/src/main/java/org/traccar/handler/DefaultDataHandler.java +++ b/src/main/java/org/traccar/handler/DatabaseHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 - 2022 Anton Tananaev (anton@traccar.org) + * Copyright 2015 - 2024 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. @@ -15,41 +15,39 @@ */ package org.traccar.handler; -import io.netty.channel.ChannelHandler; +import jakarta.inject.Inject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.traccar.BaseDataHandler; +import org.traccar.database.StatisticsManager; import org.traccar.model.Position; import org.traccar.storage.Storage; import org.traccar.storage.query.Columns; import org.traccar.storage.query.Request; -import jakarta.inject.Inject; -import jakarta.inject.Singleton; - -@Singleton -@ChannelHandler.Sharable -public class DefaultDataHandler extends BaseDataHandler { +public class DatabaseHandler extends BasePositionHandler { - private static final Logger LOGGER = LoggerFactory.getLogger(DefaultDataHandler.class); + private static final Logger LOGGER = LoggerFactory.getLogger(DatabaseHandler.class); private final Storage storage; + private final StatisticsManager statisticsManager; @Inject - public DefaultDataHandler(Storage storage) { + public DatabaseHandler(Storage storage, StatisticsManager statisticsManager) { this.storage = storage; + this.statisticsManager = statisticsManager; } @Override - protected Position handlePosition(Position position) { + public void handlePosition(Position position, Callback callback) { try { position.setId(storage.addObject(position, new Request(new Columns.Exclude("id")))); + statisticsManager.messageStoredCount(position.getDeviceId()); } catch (Exception error) { LOGGER.warn("Failed to store position", error); } - return position; + callback.processed(false); } } diff --git a/src/main/java/org/traccar/handler/DistanceHandler.java b/src/main/java/org/traccar/handler/DistanceHandler.java index db8c73779..e8ae7753a 100644 --- a/src/main/java/org/traccar/handler/DistanceHandler.java +++ b/src/main/java/org/traccar/handler/DistanceHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 - 2023 Anton Tananaev (anton@traccar.org) + * Copyright 2016 - 2024 Anton Tananaev (anton@traccar.org) * Copyright 2015 Amila Silva * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,19 +16,14 @@ */ package org.traccar.handler; -import io.netty.channel.ChannelHandler; import jakarta.inject.Inject; -import jakarta.inject.Singleton; -import org.traccar.BaseDataHandler; import org.traccar.config.Config; import org.traccar.config.Keys; import org.traccar.helper.DistanceCalculator; import org.traccar.model.Position; import org.traccar.session.cache.CacheManager; -@Singleton -@ChannelHandler.Sharable -public class DistanceHandler extends BaseDataHandler { +public class DistanceHandler extends BasePositionHandler { private final CacheManager cacheManager; @@ -45,7 +40,7 @@ public class DistanceHandler extends BaseDataHandler { } @Override - protected Position handlePosition(Position position) { + public void handlePosition(Position position, Callback callback) { double distance = 0.0; if (position.hasAttribute(Position.KEY_DISTANCE)) { @@ -76,7 +71,7 @@ public class DistanceHandler extends BaseDataHandler { position.set(Position.KEY_DISTANCE, distance); position.set(Position.KEY_TOTAL_DISTANCE, totalDistance + distance); - return position; + callback.processed(false); } } diff --git a/src/main/java/org/traccar/handler/EngineHoursHandler.java b/src/main/java/org/traccar/handler/EngineHoursHandler.java index 621205b34..5aae6f673 100644 --- a/src/main/java/org/traccar/handler/EngineHoursHandler.java +++ b/src/main/java/org/traccar/handler/EngineHoursHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 - 2022 Anton Tananaev (anton@traccar.org) + * Copyright 2018 - 2024 Anton Tananaev (anton@traccar.org) * Copyright 2018 Andrey Kunitsyn (andrey@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,17 +16,11 @@ */ package org.traccar.handler; -import io.netty.channel.ChannelHandler; -import org.traccar.BaseDataHandler; +import jakarta.inject.Inject; import org.traccar.model.Position; import org.traccar.session.cache.CacheManager; -import jakarta.inject.Inject; -import jakarta.inject.Singleton; - -@Singleton -@ChannelHandler.Sharable -public class EngineHoursHandler extends BaseDataHandler { +public class EngineHoursHandler extends BasePositionHandler { private final CacheManager cacheManager; @@ -36,7 +30,7 @@ public class EngineHoursHandler extends BaseDataHandler { } @Override - protected Position handlePosition(Position position) { + public void handlePosition(Position position, Callback callback) { if (!position.hasAttribute(Position.KEY_HOURS)) { Position last = cacheManager.getPosition(position.getDeviceId()); if (last != null) { @@ -49,7 +43,7 @@ public class EngineHoursHandler extends BaseDataHandler { } } } - return position; + callback.processed(false); } } diff --git a/src/main/java/org/traccar/handler/FilterHandler.java b/src/main/java/org/traccar/handler/FilterHandler.java index a15d3ffad..796c302fb 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 - 2023 Anton Tananaev (anton@traccar.org) + * Copyright 2014 - 2024 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. @@ -15,9 +15,7 @@ */ package org.traccar.handler; -import io.netty.channel.ChannelHandler; -import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.ChannelInboundHandlerAdapter; +import jakarta.inject.Inject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.traccar.config.Config; @@ -36,17 +34,12 @@ import org.traccar.storage.query.Condition; import org.traccar.storage.query.Order; import org.traccar.storage.query.Request; -import jakarta.inject.Inject; -import jakarta.inject.Singleton; import java.util.Date; -@Singleton -@ChannelHandler.Sharable -public class FilterHandler extends ChannelInboundHandlerAdapter { +public class FilterHandler extends BasePositionHandler { private static final Logger LOGGER = LoggerFactory.getLogger(FilterHandler.class); - private final boolean enabled; private final boolean filterInvalid; private final boolean filterZero; private final boolean filterDuplicate; @@ -71,7 +64,6 @@ public class FilterHandler extends ChannelInboundHandlerAdapter { @Inject public FilterHandler( Config config, CacheManager cacheManager, Storage storage, StatisticsManager statisticsManager) { - enabled = config.getBoolean(Keys.FILTER_ENABLE); filterInvalid = config.getBoolean(Keys.FILTER_INVALID); filterZero = config.getBoolean(Keys.FILTER_ZERO); filterDuplicate = config.getBoolean(Keys.FILTER_DUPLICATE); @@ -277,17 +269,8 @@ public class FilterHandler extends ChannelInboundHandlerAdapter { } @Override - 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); - } + public void handlePosition(Position position, Callback callback) { + callback.processed(filter(position)); } } diff --git a/src/main/java/org/traccar/handler/GeocoderHandler.java b/src/main/java/org/traccar/handler/GeocoderHandler.java index e4f240a90..b84237856 100644 --- a/src/main/java/org/traccar/handler/GeocoderHandler.java +++ b/src/main/java/org/traccar/handler/GeocoderHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2012 - 2021 Anton Tananaev (anton@traccar.org) + * Copyright 2012 - 2024 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. @@ -15,9 +15,6 @@ */ 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.config.Config; @@ -26,8 +23,7 @@ import org.traccar.geocoder.Geocoder; import org.traccar.model.Position; import org.traccar.session.cache.CacheManager; -@ChannelHandler.Sharable -public class GeocoderHandler extends ChannelInboundHandlerAdapter { +public class GeocoderHandler extends BasePositionHandler { private static final Logger LOGGER = LoggerFactory.getLogger(GeocoderHandler.class); @@ -46,39 +42,38 @@ public class GeocoderHandler extends ChannelInboundHandlerAdapter { } @Override - public void channelRead(final ChannelHandlerContext ctx, Object message) { - if (message instanceof Position && !ignorePositions) { - final Position position = (Position) message; - if (processInvalidPositions || position.getValid()) { - if (reuseDistance != 0) { - Position lastPosition = cacheManager.getPosition(position.getDeviceId()); - if (lastPosition != null && lastPosition.getAddress() != null - && position.getDouble(Position.KEY_DISTANCE) <= reuseDistance) { - position.setAddress(lastPosition.getAddress()); - ctx.fireChannelRead(position); - return; - } + public void handlePosition(Position position, Callback callback) { + if (!ignorePositions) { + callback.processed(false); + } + + if (processInvalidPositions || position.getValid()) { + if (reuseDistance != 0) { + Position lastPosition = cacheManager.getPosition(position.getDeviceId()); + if (lastPosition != null && lastPosition.getAddress() != null + && position.getDouble(Position.KEY_DISTANCE) <= reuseDistance) { + position.setAddress(lastPosition.getAddress()); + callback.processed(false); + return; } + } - geocoder.getAddress(position.getLatitude(), position.getLongitude(), - new Geocoder.ReverseGeocoderCallback() { - @Override - public void onSuccess(String address) { - position.setAddress(address); - ctx.fireChannelRead(position); - } + geocoder.getAddress(position.getLatitude(), position.getLongitude(), + new Geocoder.ReverseGeocoderCallback() { + @Override + public void onSuccess(String address) { + position.setAddress(address); + callback.processed(false); + } - @Override - public void onFailure(Throwable e) { - LOGGER.warn("Geocoding failed", e); - ctx.fireChannelRead(position); - } - }); - } else { - ctx.fireChannelRead(position); - } + @Override + public void onFailure(Throwable e) { + LOGGER.warn("Geocoding failed", e); + callback.processed(false); + } + }); } else { - ctx.fireChannelRead(message); + callback.processed(false); } } diff --git a/src/main/java/org/traccar/handler/GeofenceHandler.java b/src/main/java/org/traccar/handler/GeofenceHandler.java index 68bc6dbf0..8b363057a 100644 --- a/src/main/java/org/traccar/handler/GeofenceHandler.java +++ b/src/main/java/org/traccar/handler/GeofenceHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2023 Anton Tananaev (anton@traccar.org) + * Copyright 2023 - 2024 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. @@ -15,20 +15,15 @@ */ package org.traccar.handler; -import io.netty.channel.ChannelHandler; -import org.traccar.BaseDataHandler; +import jakarta.inject.Inject; import org.traccar.config.Config; import org.traccar.helper.model.GeofenceUtil; import org.traccar.model.Position; import org.traccar.session.cache.CacheManager; -import jakarta.inject.Inject; -import jakarta.inject.Singleton; import java.util.List; -@Singleton -@ChannelHandler.Sharable -public class GeofenceHandler extends BaseDataHandler { +public class GeofenceHandler extends BasePositionHandler { private final Config config; private final CacheManager cacheManager; @@ -40,13 +35,13 @@ public class GeofenceHandler extends BaseDataHandler { } @Override - protected Position handlePosition(Position position) { + public void handlePosition(Position position, Callback callback) { List<Long> geofenceIds = GeofenceUtil.getCurrentGeofences(config, cacheManager, position); if (!geofenceIds.isEmpty()) { position.setGeofenceIds(geofenceIds); } - return position; + callback.processed(false); } } diff --git a/src/main/java/org/traccar/handler/GeolocationHandler.java b/src/main/java/org/traccar/handler/GeolocationHandler.java index a54ea03e3..c46bd3250 100644 --- a/src/main/java/org/traccar/handler/GeolocationHandler.java +++ b/src/main/java/org/traccar/handler/GeolocationHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 - 2023 Anton Tananaev (anton@traccar.org) + * Copyright 2015 - 2024 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. @@ -15,9 +15,6 @@ */ 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.config.Config; @@ -27,8 +24,7 @@ import org.traccar.geolocation.GeolocationProvider; import org.traccar.model.Position; import org.traccar.session.cache.CacheManager; -@ChannelHandler.Sharable -public class GeolocationHandler extends ChannelInboundHandlerAdapter { +public class GeolocationHandler extends BasePositionHandler { private static final Logger LOGGER = LoggerFactory.getLogger(GeolocationHandler.class); @@ -51,46 +47,41 @@ public class GeolocationHandler extends ChannelInboundHandlerAdapter { } @Override - public void channelRead(final ChannelHandlerContext ctx, Object message) { - if (message instanceof Position) { - final Position position = (Position) message; - if ((position.getOutdated() || processInvalidPositions && !position.getValid()) - && position.getNetwork() != null - && (!requireWifi || position.getNetwork().getWifiAccessPoints() != null)) { - if (reuse) { - Position lastPosition = cacheManager.getPosition(position.getDeviceId()); - if (lastPosition != null && position.getNetwork().equals(lastPosition.getNetwork())) { - updatePosition( - position, lastPosition.getLatitude(), lastPosition.getLongitude(), - lastPosition.getAccuracy()); - ctx.fireChannelRead(position); - return; - } + public void handlePosition(Position position, Callback callback) { + if ((position.getOutdated() || processInvalidPositions && !position.getValid()) + && position.getNetwork() != null + && (!requireWifi || position.getNetwork().getWifiAccessPoints() != null)) { + if (reuse) { + Position lastPosition = cacheManager.getPosition(position.getDeviceId()); + if (lastPosition != null && position.getNetwork().equals(lastPosition.getNetwork())) { + updatePosition( + position, lastPosition.getLatitude(), lastPosition.getLongitude(), + lastPosition.getAccuracy()); + callback.processed(false); + return; } + } - if (statisticsManager != null) { - statisticsManager.registerGeolocationRequest(); - } + if (statisticsManager != null) { + statisticsManager.registerGeolocationRequest(); + } - geolocationProvider.getLocation(position.getNetwork(), - new GeolocationProvider.LocationProviderCallback() { - @Override - public void onSuccess(double latitude, double longitude, double accuracy) { - updatePosition(position, latitude, longitude, accuracy); - ctx.fireChannelRead(position); - } + geolocationProvider.getLocation(position.getNetwork(), + new GeolocationProvider.LocationProviderCallback() { + @Override + public void onSuccess(double latitude, double longitude, double accuracy) { + updatePosition(position, latitude, longitude, accuracy); + callback.processed(false); + } - @Override - public void onFailure(Throwable e) { - LOGGER.warn("Geolocation network error", e); - ctx.fireChannelRead(position); - } - }); - } else { - ctx.fireChannelRead(position); - } + @Override + public void onFailure(Throwable e) { + LOGGER.warn("Geolocation network error", e); + callback.processed(false); + } + }); } else { - ctx.fireChannelRead(message); + callback.processed(false); } } diff --git a/src/main/java/org/traccar/handler/HemisphereHandler.java b/src/main/java/org/traccar/handler/HemisphereHandler.java index 294e449db..48929538f 100644 --- a/src/main/java/org/traccar/handler/HemisphereHandler.java +++ b/src/main/java/org/traccar/handler/HemisphereHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 - 2022 Anton Tananaev (anton@traccar.org) + * Copyright 2016 - 2024 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. @@ -15,18 +15,12 @@ */ package org.traccar.handler; -import io.netty.channel.ChannelHandler; -import org.traccar.BaseDataHandler; +import jakarta.inject.Inject; import org.traccar.config.Config; import org.traccar.config.Keys; import org.traccar.model.Position; -import jakarta.inject.Inject; -import jakarta.inject.Singleton; - -@Singleton -@ChannelHandler.Sharable -public class HemisphereHandler extends BaseDataHandler { +public class HemisphereHandler extends BasePositionHandler { private int latitudeFactor; private int longitudeFactor; @@ -52,14 +46,14 @@ public class HemisphereHandler extends BaseDataHandler { } @Override - protected Position handlePosition(Position position) { + public void handlePosition(Position position, Callback callback) { if (latitudeFactor != 0) { position.setLatitude(Math.abs(position.getLatitude()) * latitudeFactor); } if (longitudeFactor != 0) { position.setLongitude(Math.abs(position.getLongitude()) * longitudeFactor); } - return position; + callback.processed(false); } } diff --git a/src/main/java/org/traccar/handler/MotionHandler.java b/src/main/java/org/traccar/handler/MotionHandler.java index 68a31a16a..804ffdc25 100644 --- a/src/main/java/org/traccar/handler/MotionHandler.java +++ b/src/main/java/org/traccar/handler/MotionHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 - 2023 Anton Tananaev (anton@traccar.org) + * Copyright 2017 - 2024 Anton Tananaev (anton@traccar.org) * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,19 +16,13 @@ */ package org.traccar.handler; -import io.netty.channel.ChannelHandler; -import org.traccar.BaseDataHandler; +import jakarta.inject.Inject; import org.traccar.config.Keys; import org.traccar.helper.model.AttributeUtil; import org.traccar.model.Position; import org.traccar.session.cache.CacheManager; -import jakarta.inject.Inject; -import jakarta.inject.Singleton; - -@Singleton -@ChannelHandler.Sharable -public class MotionHandler extends BaseDataHandler { +public class MotionHandler extends BasePositionHandler { private final CacheManager cacheManager; @@ -38,13 +32,13 @@ public class MotionHandler extends BaseDataHandler { } @Override - protected Position handlePosition(Position position) { + public void handlePosition(Position position, Callback callback) { if (!position.hasAttribute(Position.KEY_MOTION)) { double threshold = AttributeUtil.lookup( cacheManager, Keys.EVENT_MOTION_SPEED_THRESHOLD, position.getDeviceId()); position.set(Position.KEY_MOTION, position.getSpeed() > threshold); } - return position; + callback.processed(false); } } diff --git a/src/main/java/org/traccar/handler/OutdatedHandler.java b/src/main/java/org/traccar/handler/OutdatedHandler.java new file mode 100644 index 000000000..536440f66 --- /dev/null +++ b/src/main/java/org/traccar/handler/OutdatedHandler.java @@ -0,0 +1,56 @@ +/* + * Copyright 2024 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 jakarta.inject.Inject; +import org.traccar.model.Position; +import org.traccar.session.cache.CacheManager; + +import java.util.Date; + +public class OutdatedHandler extends BasePositionHandler { + + private final CacheManager cacheManager; + + @Inject + public OutdatedHandler(CacheManager cacheManager) { + this.cacheManager = cacheManager; + } + + @Override + public void handlePosition(Position position, Callback callback) { + if (position.getOutdated()) { + Position last = cacheManager.getPosition(position.getDeviceId()); + if (last != null) { + position.setFixTime(last.getFixTime()); + position.setValid(last.getValid()); + position.setLatitude(last.getLatitude()); + position.setLongitude(last.getLongitude()); + position.setAltitude(last.getAltitude()); + position.setSpeed(last.getSpeed()); + position.setCourse(last.getCourse()); + position.setAccuracy(last.getAccuracy()); + } else { + position.setFixTime(new Date(315964819000L)); // gps epoch 1980-01-06 + } + if (position.getDeviceTime() == null) { + position.setDeviceTime(position.getServerTime()); + } + } + callback.processed(false); + } + +} diff --git a/src/main/java/org/traccar/PositionForwardingHandler.java b/src/main/java/org/traccar/handler/PositionForwardingHandler.java index a79b01367..8512d4552 100644 --- a/src/main/java/org/traccar/PositionForwardingHandler.java +++ b/src/main/java/org/traccar/handler/PositionForwardingHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 - 2022 Anton Tananaev (anton@traccar.org) + * Copyright 2015 - 2024 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. @@ -13,12 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.traccar; +package org.traccar.handler; -import io.netty.channel.ChannelHandler; import io.netty.util.Timeout; import io.netty.util.Timer; import io.netty.util.TimerTask; +import jakarta.annotation.Nullable; +import jakarta.inject.Inject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.traccar.config.Config; @@ -30,15 +31,10 @@ import org.traccar.model.Device; import org.traccar.model.Position; import org.traccar.session.cache.CacheManager; -import jakarta.annotation.Nullable; -import jakarta.inject.Inject; -import jakarta.inject.Singleton; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; -@Singleton -@ChannelHandler.Sharable -public class PositionForwardingHandler extends BaseDataHandler { +public class PositionForwardingHandler extends BasePositionHandler { private static final Logger LOGGER = LoggerFactory.getLogger(PositionForwardingHandler.class); @@ -128,14 +124,14 @@ public class PositionForwardingHandler extends BaseDataHandler { } @Override - protected Position handlePosition(Position position) { + public void handlePosition(Position position, Callback callback) { if (positionForwarder != null) { PositionData positionData = new PositionData(); positionData.setPosition(position); positionData.setDevice(cacheManager.getObject(Device.class, position.getDeviceId())); new AsyncRequestAndCallback(positionData).send(); } - return position; + callback.processed(false); } } diff --git a/src/main/java/org/traccar/handler/PostProcessHandler.java b/src/main/java/org/traccar/handler/PostProcessHandler.java new file mode 100644 index 000000000..5b1b2ef86 --- /dev/null +++ b/src/main/java/org/traccar/handler/PostProcessHandler.java @@ -0,0 +1,67 @@ +/* + * Copyright 2024 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 jakarta.inject.Inject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.traccar.helper.model.PositionUtil; +import org.traccar.model.Device; +import org.traccar.model.Position; +import org.traccar.session.ConnectionManager; +import org.traccar.session.cache.CacheManager; +import org.traccar.storage.Storage; +import org.traccar.storage.StorageException; +import org.traccar.storage.query.Columns; +import org.traccar.storage.query.Condition; +import org.traccar.storage.query.Request; + +public class PostProcessHandler extends BasePositionHandler { + + private static final Logger LOGGER = LoggerFactory.getLogger(PostProcessHandler.class); + + private final CacheManager cacheManager; + private final Storage storage; + private final ConnectionManager connectionManager; + + @Inject + public PostProcessHandler(CacheManager cacheManager, Storage storage, ConnectionManager connectionManager) { + this.cacheManager = cacheManager; + this.storage = storage; + this.connectionManager = connectionManager; + } + + @Override + public void handlePosition(Position position, Callback callback) { + try { + if (PositionUtil.isLatest(cacheManager, position)) { + Device updatedDevice = new Device(); + updatedDevice.setId(position.getDeviceId()); + updatedDevice.setPositionId(position.getId()); + storage.updateObject(updatedDevice, new Request( + new Columns.Include("positionId"), + new Condition.Equals("id", updatedDevice.getId()))); + + cacheManager.updatePosition(position); + connectionManager.updatePosition(true, position); + } + } catch (StorageException error) { + LOGGER.warn("Failed to update device", error); + } + callback.processed(false); + } + +} diff --git a/src/main/java/org/traccar/handler/SpeedLimitHandler.java b/src/main/java/org/traccar/handler/SpeedLimitHandler.java index 6edb6e912..4c0922d01 100644 --- a/src/main/java/org/traccar/handler/SpeedLimitHandler.java +++ b/src/main/java/org/traccar/handler/SpeedLimitHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 - 2022 Anton Tananaev (anton@traccar.org) + * Copyright 2020 - 2024 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. @@ -15,20 +15,13 @@ */ package org.traccar.handler; -import io.netty.channel.ChannelHandler; -import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.ChannelInboundHandlerAdapter; +import jakarta.inject.Inject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.traccar.model.Position; import org.traccar.speedlimit.SpeedLimitProvider; -import jakarta.inject.Inject; -import jakarta.inject.Singleton; - -@Singleton -@ChannelHandler.Sharable -public class SpeedLimitHandler extends ChannelInboundHandlerAdapter { +public class SpeedLimitHandler extends BasePositionHandler { private static final Logger LOGGER = LoggerFactory.getLogger(SpeedLimitHandler.class); @@ -40,26 +33,22 @@ public class SpeedLimitHandler extends ChannelInboundHandlerAdapter { } @Override - public void channelRead(final ChannelHandlerContext ctx, Object message) { - if (message instanceof Position) { - final Position position = (Position) message; - speedLimitProvider.getSpeedLimit(position.getLatitude(), position.getLongitude(), - new SpeedLimitProvider.SpeedLimitProviderCallback() { - @Override - public void onSuccess(double speedLimit) { - position.set(Position.KEY_SPEED_LIMIT, speedLimit); - ctx.fireChannelRead(position); - } - - @Override - public void onFailure(Throwable e) { - LOGGER.warn("Speed limit provider failed", e); - ctx.fireChannelRead(position); - } - }); - } else { - ctx.fireChannelRead(message); - } + public void handlePosition(Position position, Callback callback) { + + speedLimitProvider.getSpeedLimit(position.getLatitude(), position.getLongitude(), + new SpeedLimitProvider.SpeedLimitProviderCallback() { + @Override + public void onSuccess(double speedLimit) { + position.set(Position.KEY_SPEED_LIMIT, speedLimit); + callback.processed(false); + } + + @Override + public void onFailure(Throwable e) { + LOGGER.warn("Speed limit provider failed", e); + callback.processed(false); + } + }); } } diff --git a/src/main/java/org/traccar/handler/TimeHandler.java b/src/main/java/org/traccar/handler/TimeHandler.java index 3c3e17450..f6c67bb76 100644 --- a/src/main/java/org/traccar/handler/TimeHandler.java +++ b/src/main/java/org/traccar/handler/TimeHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 - 2022 Anton Tananaev (anton@traccar.org) + * Copyright 2019 - 2024 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. @@ -15,36 +15,23 @@ */ package org.traccar.handler; -import io.netty.channel.ChannelHandler; -import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.ChannelInboundHandlerAdapter; -import org.traccar.BaseProtocolDecoder; +import jakarta.inject.Inject; import org.traccar.config.Config; import org.traccar.config.Keys; import org.traccar.model.Position; -import jakarta.inject.Inject; -import jakarta.inject.Singleton; import java.util.Arrays; import java.util.HashSet; import java.util.Set; -@Singleton -@ChannelHandler.Sharable -public class TimeHandler extends ChannelInboundHandlerAdapter { +public class TimeHandler extends BasePositionHandler { - private final boolean enabled; private final boolean useServerTime; private final Set<String> protocols; @Inject public TimeHandler(Config config) { - enabled = config.hasKey(Keys.TIME_OVERRIDE); - if (enabled) { - useServerTime = config.getString(Keys.TIME_OVERRIDE).equalsIgnoreCase("serverTime"); - } else { - useServerTime = false; - } + useServerTime = config.getString(Keys.TIME_OVERRIDE).equalsIgnoreCase("serverTime"); String protocolList = config.getString(Keys.TIME_PROTOCOLS); if (protocolList != null) { protocols = new HashSet<>(Arrays.asList(protocolList.split("[, ]"))); @@ -54,21 +41,17 @@ public class TimeHandler extends ChannelInboundHandlerAdapter { } @Override - public void channelRead(ChannelHandlerContext ctx, Object msg) { - - if (enabled && msg instanceof Position && (protocols == null - || protocols.contains(ctx.pipeline().get(BaseProtocolDecoder.class).getProtocolName()))) { + public void handlePosition(Position position, Callback callback) { - Position position = (Position) msg; + if (protocols == null || protocols.contains(position.getProtocol())) { if (useServerTime) { position.setDeviceTime(position.getServerTime()); position.setFixTime(position.getServerTime()); } else { position.setFixTime(position.getDeviceTime()); } - } - ctx.fireChannelRead(msg); + callback.processed(false); } } diff --git a/src/main/java/org/traccar/handler/events/AlertEventHandler.java b/src/main/java/org/traccar/handler/events/AlertEventHandler.java index 531a0f957..ca580b60d 100644 --- a/src/main/java/org/traccar/handler/events/AlertEventHandler.java +++ b/src/main/java/org/traccar/handler/events/AlertEventHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 - 2022 Anton Tananaev (anton@traccar.org) + * Copyright 2016 - 2024 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. @@ -15,21 +15,13 @@ */ package org.traccar.handler.events; -import java.util.Collections; -import java.util.Map; - -import io.netty.channel.ChannelHandler; +import jakarta.inject.Inject; import org.traccar.config.Config; import org.traccar.config.Keys; import org.traccar.model.Event; import org.traccar.model.Position; import org.traccar.session.cache.CacheManager; -import jakarta.inject.Inject; -import jakarta.inject.Singleton; - -@Singleton -@ChannelHandler.Sharable public class AlertEventHandler extends BaseEventHandler { private final CacheManager cacheManager; @@ -42,7 +34,7 @@ public class AlertEventHandler extends BaseEventHandler { } @Override - protected Map<Event, Position> analyzePosition(Position position) { + public void analyzePosition(Position position, Callback callback) { Object alarm = position.getAttributes().get(Position.KEY_ALARM); if (alarm != null) { boolean ignoreAlert = false; @@ -55,10 +47,9 @@ public class AlertEventHandler extends BaseEventHandler { if (!ignoreAlert) { Event event = new Event(Event.TYPE_ALARM, position); event.set(Position.KEY_ALARM, (String) alarm); - return Collections.singletonMap(event, position); + callback.eventDetected(event); } } - return null; } } diff --git a/src/main/java/org/traccar/handler/events/BaseEventHandler.java b/src/main/java/org/traccar/handler/events/BaseEventHandler.java index 4a4fb40ff..009c83145 100644 --- a/src/main/java/org/traccar/handler/events/BaseEventHandler.java +++ b/src/main/java/org/traccar/handler/events/BaseEventHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 - 2022 Anton Tananaev (anton@traccar.org) + * Copyright 2016 - 2024 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. @@ -15,33 +15,17 @@ */ package org.traccar.handler.events; -import java.util.Map; - -import org.traccar.BaseDataHandler; -import org.traccar.database.NotificationManager; import org.traccar.model.Event; import org.traccar.model.Position; -import jakarta.inject.Inject; - -public abstract class BaseEventHandler extends BaseDataHandler { - - private NotificationManager notificationManager; +public abstract class BaseEventHandler { - @Inject - public void setNotificationManager(NotificationManager notificationManager) { - this.notificationManager = notificationManager; + public interface Callback { + void eventDetected(Event event); } - @Override - protected Position handlePosition(Position position) { - Map<Event, Position> events = analyzePosition(position); - if (events != null && !events.isEmpty()) { - notificationManager.updateEvents(events); - } - return position; - } - - protected abstract Map<Event, Position> analyzePosition(Position position); - + /** + * Event handlers should be processed synchronously. + */ + public abstract void analyzePosition(Position position, Callback callback); } diff --git a/src/main/java/org/traccar/handler/events/BehaviorEventHandler.java b/src/main/java/org/traccar/handler/events/BehaviorEventHandler.java index 08ae35fcd..d654e18ce 100644 --- a/src/main/java/org/traccar/handler/events/BehaviorEventHandler.java +++ b/src/main/java/org/traccar/handler/events/BehaviorEventHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2021 - 2022 Anton Tananaev (anton@traccar.org) + * Copyright 2021 - 2024 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. @@ -15,7 +15,7 @@ */ package org.traccar.handler.events; -import io.netty.channel.ChannelHandler; +import jakarta.inject.Inject; import org.traccar.config.Config; import org.traccar.config.Keys; import org.traccar.helper.UnitsConverter; @@ -23,13 +23,6 @@ import org.traccar.model.Event; import org.traccar.model.Position; import org.traccar.session.cache.CacheManager; -import jakarta.inject.Inject; -import jakarta.inject.Singleton; -import java.util.Collections; -import java.util.Map; - -@Singleton -@ChannelHandler.Sharable public class BehaviorEventHandler extends BaseEventHandler { private final double accelerationThreshold; @@ -45,7 +38,7 @@ public class BehaviorEventHandler extends BaseEventHandler { } @Override - protected Map<Event, Position> analyzePosition(Position position) { + public void analyzePosition(Position position, Callback callback) { Position lastPosition = cacheManager.getPosition(position.getDeviceId()); if (lastPosition != null && position.getFixTime().equals(lastPosition.getFixTime())) { @@ -54,14 +47,13 @@ public class BehaviorEventHandler extends BaseEventHandler { if (accelerationThreshold != 0 && acceleration >= accelerationThreshold) { Event event = new Event(Event.TYPE_ALARM, position); event.set(Position.KEY_ALARM, Position.ALARM_ACCELERATION); - return Collections.singletonMap(event, position); + callback.eventDetected(event); } else if (brakingThreshold != 0 && acceleration <= -brakingThreshold) { Event event = new Event(Event.TYPE_ALARM, position); event.set(Position.KEY_ALARM, Position.ALARM_BRAKING); - return Collections.singletonMap(event, position); + callback.eventDetected(event); } } - return null; } } diff --git a/src/main/java/org/traccar/handler/events/CommandResultEventHandler.java b/src/main/java/org/traccar/handler/events/CommandResultEventHandler.java index b70f8f33b..b98807b23 100644 --- a/src/main/java/org/traccar/handler/events/CommandResultEventHandler.java +++ b/src/main/java/org/traccar/handler/events/CommandResultEventHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 - 2022 Anton Tananaev (anton@traccar.org) + * Copyright 2016 - 2024 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. @@ -15,18 +15,10 @@ */ package org.traccar.handler.events; -import java.util.Collections; -import java.util.Map; - -import io.netty.channel.ChannelHandler; +import jakarta.inject.Inject; import org.traccar.model.Event; import org.traccar.model.Position; -import jakarta.inject.Inject; -import jakarta.inject.Singleton; - -@Singleton -@ChannelHandler.Sharable public class CommandResultEventHandler extends BaseEventHandler { @Inject @@ -34,14 +26,13 @@ public class CommandResultEventHandler extends BaseEventHandler { } @Override - protected Map<Event, Position> analyzePosition(Position position) { + public void analyzePosition(Position position, Callback callback) { Object commandResult = position.getAttributes().get(Position.KEY_RESULT); if (commandResult != null) { Event event = new Event(Event.TYPE_COMMAND_RESULT, position); event.set(Position.KEY_RESULT, (String) commandResult); - return Collections.singletonMap(event, position); + callback.eventDetected(event); } - return null; } } diff --git a/src/main/java/org/traccar/handler/events/DriverEventHandler.java b/src/main/java/org/traccar/handler/events/DriverEventHandler.java index b68327983..31f8d2b4b 100644 --- a/src/main/java/org/traccar/handler/events/DriverEventHandler.java +++ b/src/main/java/org/traccar/handler/events/DriverEventHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 - 2022 Anton Tananaev (anton@traccar.org) + * Copyright 2017 - 2024 Anton Tananaev (anton@traccar.org) * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,19 +16,12 @@ */ package org.traccar.handler.events; -import io.netty.channel.ChannelHandler; +import jakarta.inject.Inject; import org.traccar.helper.model.PositionUtil; import org.traccar.model.Event; import org.traccar.model.Position; import org.traccar.session.cache.CacheManager; -import jakarta.inject.Inject; -import jakarta.inject.Singleton; -import java.util.Collections; -import java.util.Map; - -@Singleton -@ChannelHandler.Sharable public class DriverEventHandler extends BaseEventHandler { private final CacheManager cacheManager; @@ -39,9 +32,9 @@ public class DriverEventHandler extends BaseEventHandler { } @Override - protected Map<Event, Position> analyzePosition(Position position) { + public void analyzePosition(Position position, Callback callback) { if (!PositionUtil.isLatest(cacheManager, position)) { - return null; + return; } String driverUniqueId = position.getString(Position.KEY_DRIVER_UNIQUE_ID); if (driverUniqueId != null) { @@ -53,10 +46,9 @@ public class DriverEventHandler extends BaseEventHandler { if (!driverUniqueId.equals(oldDriverUniqueId)) { Event event = new Event(Event.TYPE_DRIVER_CHANGED, position); event.set(Position.KEY_DRIVER_UNIQUE_ID, driverUniqueId); - return Collections.singletonMap(event, position); + callback.eventDetected(event); } } - return null; } } diff --git a/src/main/java/org/traccar/handler/events/FuelEventHandler.java b/src/main/java/org/traccar/handler/events/FuelEventHandler.java index e5085ecc2..c5675f51d 100644 --- a/src/main/java/org/traccar/handler/events/FuelEventHandler.java +++ b/src/main/java/org/traccar/handler/events/FuelEventHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 - 2023 Anton Tananaev (anton@traccar.org) + * Copyright 2017 - 2024 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. @@ -15,7 +15,7 @@ */ package org.traccar.handler.events; -import io.netty.channel.ChannelHandler; +import jakarta.inject.Inject; import org.traccar.config.Keys; import org.traccar.helper.model.AttributeUtil; import org.traccar.helper.model.PositionUtil; @@ -24,12 +24,6 @@ import org.traccar.model.Event; import org.traccar.model.Position; import org.traccar.session.cache.CacheManager; -import jakarta.inject.Inject; -import jakarta.inject.Singleton; -import java.util.Map; - -@Singleton -@ChannelHandler.Sharable public class FuelEventHandler extends BaseEventHandler { private final CacheManager cacheManager; @@ -40,14 +34,14 @@ public class FuelEventHandler extends BaseEventHandler { } @Override - protected Map<Event, Position> analyzePosition(Position position) { + public void analyzePosition(Position position, Callback callback) { Device device = cacheManager.getObject(Device.class, position.getDeviceId()); if (device == null) { - return null; + return; } if (!PositionUtil.isLatest(cacheManager, position)) { - return null; + return; } if (position.hasAttribute(Position.KEY_FUEL_LEVEL)) { @@ -61,19 +55,17 @@ public class FuelEventHandler extends BaseEventHandler { double threshold = AttributeUtil.lookup( cacheManager, Keys.EVENT_FUEL_INCREASE_THRESHOLD, position.getDeviceId()); if (threshold > 0 && change >= threshold) { - return Map.of(new Event(Event.TYPE_DEVICE_FUEL_INCREASE, position), position); + callback.eventDetected(new Event(Event.TYPE_DEVICE_FUEL_INCREASE, position)); } } else if (change < 0) { double threshold = AttributeUtil.lookup( cacheManager, Keys.EVENT_FUEL_DROP_THRESHOLD, position.getDeviceId()); if (threshold > 0 && Math.abs(change) >= threshold) { - return Map.of(new Event(Event.TYPE_DEVICE_FUEL_DROP, position), position); + callback.eventDetected(new Event(Event.TYPE_DEVICE_FUEL_DROP, position)); } } } } - - return null; } } diff --git a/src/main/java/org/traccar/handler/events/GeofenceEventHandler.java b/src/main/java/org/traccar/handler/events/GeofenceEventHandler.java index dbe2b8118..c8ecfb1ed 100644 --- a/src/main/java/org/traccar/handler/events/GeofenceEventHandler.java +++ b/src/main/java/org/traccar/handler/events/GeofenceEventHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 - 2023 Anton Tananaev (anton@traccar.org) + * Copyright 2016 - 2024 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. @@ -15,7 +15,7 @@ */ package org.traccar.handler.events; -import io.netty.channel.ChannelHandler; +import jakarta.inject.Inject; import org.traccar.helper.model.PositionUtil; import org.traccar.model.Calendar; import org.traccar.model.Event; @@ -23,15 +23,9 @@ import org.traccar.model.Geofence; import org.traccar.model.Position; import org.traccar.session.cache.CacheManager; -import jakarta.inject.Inject; -import jakarta.inject.Singleton; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; -@Singleton -@ChannelHandler.Sharable public class GeofenceEventHandler extends BaseEventHandler { private final CacheManager cacheManager; @@ -42,9 +36,9 @@ public class GeofenceEventHandler extends BaseEventHandler { } @Override - protected Map<Event, Position> analyzePosition(Position position) { + public void analyzePosition(Position position, Callback callback) { if (!PositionUtil.isLatest(cacheManager, position)) { - return null; + return; } List<Long> oldGeofences = new ArrayList<>(); @@ -60,7 +54,6 @@ public class GeofenceEventHandler extends BaseEventHandler { oldGeofences.removeAll(position.getGeofenceIds()); } - Map<Event, Position> events = new HashMap<>(); for (long geofenceId : oldGeofences) { Geofence geofence = cacheManager.getObject(Geofence.class, geofenceId); if (geofence != null) { @@ -69,7 +62,7 @@ public class GeofenceEventHandler extends BaseEventHandler { if (calendar == null || calendar.checkMoment(position.getFixTime())) { Event event = new Event(Event.TYPE_GEOFENCE_EXIT, position); event.setGeofenceId(geofenceId); - events.put(event, position); + callback.eventDetected(event); } } } @@ -79,10 +72,8 @@ public class GeofenceEventHandler extends BaseEventHandler { if (calendar == null || calendar.checkMoment(position.getFixTime())) { Event event = new Event(Event.TYPE_GEOFENCE_ENTER, position); event.setGeofenceId(geofenceId); - events.put(event, position); + callback.eventDetected(event); } } - return events; } - } diff --git a/src/main/java/org/traccar/handler/events/IgnitionEventHandler.java b/src/main/java/org/traccar/handler/events/IgnitionEventHandler.java index ba4159a42..bbf9fadd1 100644 --- a/src/main/java/org/traccar/handler/events/IgnitionEventHandler.java +++ b/src/main/java/org/traccar/handler/events/IgnitionEventHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 - 2022 Anton Tananaev (anton@traccar.org) + * Copyright 2016 - 2024 Anton Tananaev (anton@traccar.org) * Copyright 2016 Andrey Kunitsyn (andrey@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,21 +16,13 @@ */ package org.traccar.handler.events; -import java.util.Collections; -import java.util.Map; - -import io.netty.channel.ChannelHandler; +import jakarta.inject.Inject; import org.traccar.helper.model.PositionUtil; import org.traccar.model.Device; import org.traccar.model.Event; import org.traccar.model.Position; import org.traccar.session.cache.CacheManager; -import jakarta.inject.Inject; -import jakarta.inject.Singleton; - -@Singleton -@ChannelHandler.Sharable public class IgnitionEventHandler extends BaseEventHandler { private final CacheManager cacheManager; @@ -41,14 +33,12 @@ public class IgnitionEventHandler extends BaseEventHandler { } @Override - protected Map<Event, Position> analyzePosition(Position position) { + public void analyzePosition(Position position, Callback callback) { Device device = cacheManager.getObject(Device.class, position.getDeviceId()); if (device == null || !PositionUtil.isLatest(cacheManager, position)) { - return null; + return; } - Map<Event, Position> result = null; - if (position.hasAttribute(Position.KEY_IGNITION)) { boolean ignition = position.getBoolean(Position.KEY_IGNITION); @@ -57,15 +47,12 @@ public class IgnitionEventHandler extends BaseEventHandler { boolean oldIgnition = lastPosition.getBoolean(Position.KEY_IGNITION); if (ignition && !oldIgnition) { - result = Collections.singletonMap( - new Event(Event.TYPE_IGNITION_ON, position), position); + callback.eventDetected(new Event(Event.TYPE_IGNITION_ON, position)); } else if (!ignition && oldIgnition) { - result = Collections.singletonMap( - new Event(Event.TYPE_IGNITION_OFF, position), position); + callback.eventDetected(new Event(Event.TYPE_IGNITION_OFF, position)); } } } - return result; } } diff --git a/src/main/java/org/traccar/handler/events/MaintenanceEventHandler.java b/src/main/java/org/traccar/handler/events/MaintenanceEventHandler.java index 2fa2e8869..573ad4ad6 100644 --- a/src/main/java/org/traccar/handler/events/MaintenanceEventHandler.java +++ b/src/main/java/org/traccar/handler/events/MaintenanceEventHandler.java @@ -16,20 +16,12 @@ */ package org.traccar.handler.events; -import java.util.HashMap; -import java.util.Map; - -import io.netty.channel.ChannelHandler; +import jakarta.inject.Inject; import org.traccar.model.Event; import org.traccar.model.Maintenance; import org.traccar.model.Position; import org.traccar.session.cache.CacheManager; -import jakarta.inject.Inject; -import jakarta.inject.Singleton; - -@Singleton -@ChannelHandler.Sharable public class MaintenanceEventHandler extends BaseEventHandler { private final CacheManager cacheManager; @@ -40,13 +32,12 @@ public class MaintenanceEventHandler extends BaseEventHandler { } @Override - protected Map<Event, Position> analyzePosition(Position position) { + public void analyzePosition(Position position, Callback callback) { Position lastPosition = cacheManager.getPosition(position.getDeviceId()); if (lastPosition == null || position.getFixTime().compareTo(lastPosition.getFixTime()) < 0) { - return null; + return; } - Map<Event, Position> events = new HashMap<>(); for (Maintenance maintenance : cacheManager.getDeviceObjects(position.getDeviceId(), Maintenance.class)) { if (maintenance.getPeriod() != 0) { double oldValue = getValue(lastPosition, maintenance.getType()); @@ -58,13 +49,11 @@ public class MaintenanceEventHandler extends BaseEventHandler { Event event = new Event(Event.TYPE_MAINTENANCE, position); event.setMaintenanceId(maintenance.getId()); event.set(maintenance.getType(), newValue); - events.put(event, position); + callback.eventDetected(event); } } } } - - return events; } private double getValue(Position position, String type) { diff --git a/src/main/java/org/traccar/handler/events/MediaEventHandler.java b/src/main/java/org/traccar/handler/events/MediaEventHandler.java index 52d8e6961..2745296c4 100644 --- a/src/main/java/org/traccar/handler/events/MediaEventHandler.java +++ b/src/main/java/org/traccar/handler/events/MediaEventHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2022 Anton Tananaev (anton@traccar.org) + * Copyright 2022 - 2024 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. @@ -15,18 +15,12 @@ */ package org.traccar.handler.events; -import io.netty.channel.ChannelHandler; +import jakarta.inject.Inject; import org.traccar.model.Event; import org.traccar.model.Position; -import jakarta.inject.Inject; -import jakarta.inject.Singleton; -import java.util.Map; -import java.util.stream.Collectors; import java.util.stream.Stream; -@Singleton -@ChannelHandler.Sharable public class MediaEventHandler extends BaseEventHandler { @Inject @@ -34,8 +28,8 @@ public class MediaEventHandler extends BaseEventHandler { } @Override - protected Map<Event, Position> analyzePosition(Position position) { - return Stream.of(Position.KEY_IMAGE, Position.KEY_VIDEO, Position.KEY_AUDIO) + public void analyzePosition(Position position, Callback callback) { + Stream.of(Position.KEY_IMAGE, Position.KEY_VIDEO, Position.KEY_AUDIO) .filter(position::hasAttribute) .map(type -> { Event event = new Event(Event.TYPE_MEDIA, position); @@ -43,7 +37,7 @@ public class MediaEventHandler extends BaseEventHandler { event.set("file", position.getString(type)); return event; }) - .collect(Collectors.toMap(event -> event, event -> position)); + .forEach(callback::eventDetected); } } diff --git a/src/main/java/org/traccar/handler/events/MotionEventHandler.java b/src/main/java/org/traccar/handler/events/MotionEventHandler.java index 15902d6d4..41d68985b 100644 --- a/src/main/java/org/traccar/handler/events/MotionEventHandler.java +++ b/src/main/java/org/traccar/handler/events/MotionEventHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 - 2023 Anton Tananaev (anton@traccar.org) + * Copyright 2016 - 2024 Anton Tananaev (anton@traccar.org) * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,14 +16,13 @@ */ package org.traccar.handler.events; -import io.netty.channel.ChannelHandler; +import jakarta.inject.Inject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.traccar.config.Keys; import org.traccar.helper.model.AttributeUtil; import org.traccar.helper.model.PositionUtil; import org.traccar.model.Device; -import org.traccar.model.Event; import org.traccar.model.Position; import org.traccar.reports.common.TripsConfig; import org.traccar.session.cache.CacheManager; @@ -35,13 +34,6 @@ import org.traccar.storage.query.Columns; import org.traccar.storage.query.Condition; import org.traccar.storage.query.Request; -import jakarta.inject.Inject; -import jakarta.inject.Singleton; -import java.util.Collections; -import java.util.Map; - -@Singleton -@ChannelHandler.Sharable public class MotionEventHandler extends BaseEventHandler { private static final Logger LOGGER = LoggerFactory.getLogger(MotionEventHandler.class); @@ -56,17 +48,17 @@ public class MotionEventHandler extends BaseEventHandler { } @Override - protected Map<Event, Position> analyzePosition(Position position) { + public void analyzePosition(Position position, Callback callback) { long deviceId = position.getDeviceId(); Device device = cacheManager.getObject(Device.class, deviceId); if (device == null || !PositionUtil.isLatest(cacheManager, position)) { - return null; + return; } boolean processInvalid = AttributeUtil.lookup( cacheManager, Keys.EVENT_MOTION_PROCESS_INVALID_POSITIONS, deviceId); if (!processInvalid && !position.getValid()) { - return null; + return; } TripsConfig tripsConfig = new TripsConfig(new AttributeUtil.CacheProvider(cacheManager, deviceId)); @@ -82,7 +74,9 @@ public class MotionEventHandler extends BaseEventHandler { LOGGER.warn("Update device motion error", e); } } - return state.getEvent() != null ? Collections.singletonMap(state.getEvent(), position) : null; + if (state.getEvent() != null) { + callback.eventDetected(state.getEvent()); + } } } diff --git a/src/main/java/org/traccar/handler/events/OverspeedEventHandler.java b/src/main/java/org/traccar/handler/events/OverspeedEventHandler.java index 3bb5f713c..9598581e6 100644 --- a/src/main/java/org/traccar/handler/events/OverspeedEventHandler.java +++ b/src/main/java/org/traccar/handler/events/OverspeedEventHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 - 2023 Anton Tananaev (anton@traccar.org) + * Copyright 2016 - 2024 Anton Tananaev (anton@traccar.org) * Copyright 2018 Andrey Kunitsyn (andrey@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,7 +16,7 @@ */ package org.traccar.handler.events; -import io.netty.channel.ChannelHandler; +import jakarta.inject.Inject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.traccar.config.Config; @@ -24,7 +24,6 @@ import org.traccar.config.Keys; import org.traccar.helper.model.AttributeUtil; import org.traccar.helper.model.PositionUtil; import org.traccar.model.Device; -import org.traccar.model.Event; import org.traccar.model.Geofence; import org.traccar.model.Position; import org.traccar.session.cache.CacheManager; @@ -36,13 +35,6 @@ import org.traccar.storage.query.Columns; import org.traccar.storage.query.Condition; import org.traccar.storage.query.Request; -import jakarta.inject.Inject; -import jakarta.inject.Singleton; -import java.util.Collections; -import java.util.Map; - -@Singleton -@ChannelHandler.Sharable public class OverspeedEventHandler extends BaseEventHandler { private static final Logger LOGGER = LoggerFactory.getLogger(OverspeedEventHandler.class); @@ -55,8 +47,7 @@ public class OverspeedEventHandler extends BaseEventHandler { private final double multiplier; @Inject - public OverspeedEventHandler( - Config config, CacheManager cacheManager, Storage storage) { + public OverspeedEventHandler(Config config, CacheManager cacheManager, Storage storage) { this.cacheManager = cacheManager; this.storage = storage; minimalDuration = config.getLong(Keys.EVENT_OVERSPEED_MINIMAL_DURATION) * 1000; @@ -65,15 +56,15 @@ public class OverspeedEventHandler extends BaseEventHandler { } @Override - protected Map<Event, Position> analyzePosition(Position position) { + public void analyzePosition(Position position, Callback callback) { long deviceId = position.getDeviceId(); Device device = cacheManager.getObject(Device.class, position.getDeviceId()); if (device == null) { - return null; + return; } if (!PositionUtil.isLatest(cacheManager, position) || !position.getValid()) { - return null; + return; } double speedLimit = AttributeUtil.lookup(cacheManager, Keys.EVENT_OVERSPEED_LIMIT, deviceId); @@ -105,7 +96,7 @@ public class OverspeedEventHandler extends BaseEventHandler { } if (speedLimit == 0) { - return null; + return; } OverspeedState state = OverspeedState.fromDevice(device); @@ -120,7 +111,9 @@ public class OverspeedEventHandler extends BaseEventHandler { LOGGER.warn("Update device overspeed error", e); } } - return state.getEvent() != null ? Collections.singletonMap(state.getEvent(), position) : null; + if (state.getEvent() != null) { + callback.eventDetected(state.getEvent()); + } } } diff --git a/src/main/java/org/traccar/handler/AcknowledgementHandler.java b/src/main/java/org/traccar/handler/network/AcknowledgementHandler.java index 4c1085998..e87f5d34c 100644 --- a/src/main/java/org/traccar/handler/AcknowledgementHandler.java +++ b/src/main/java/org/traccar/handler/network/AcknowledgementHandler.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.traccar.handler; +package org.traccar.handler.network; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelOutboundHandlerAdapter; diff --git a/src/main/java/org/traccar/handler/network/MainEventHandler.java b/src/main/java/org/traccar/handler/network/MainEventHandler.java new file mode 100644 index 000000000..f60004126 --- /dev/null +++ b/src/main/java/org/traccar/handler/network/MainEventHandler.java @@ -0,0 +1,98 @@ +/* + * Copyright 2012 - 2024 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.network; + +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.channel.socket.DatagramChannel; +import io.netty.handler.codec.http.HttpRequestDecoder; +import io.netty.handler.timeout.IdleStateEvent; +import jakarta.inject.Inject; +import jakarta.inject.Singleton; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.traccar.BasePipelineFactory; +import org.traccar.BaseProtocolDecoder; +import org.traccar.config.Config; +import org.traccar.config.Keys; +import org.traccar.helper.NetworkUtil; +import org.traccar.session.ConnectionManager; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +@Singleton +@ChannelHandler.Sharable +public class MainEventHandler extends ChannelInboundHandlerAdapter { + + private static final Logger LOGGER = LoggerFactory.getLogger(MainEventHandler.class); + + private final ConnectionManager connectionManager; + private final Set<String> connectionlessProtocols = new HashSet<>(); + + @Inject + public MainEventHandler(Config config, ConnectionManager connectionManager) { + this.connectionManager = connectionManager; + String connectionlessProtocolList = config.getString(Keys.STATUS_IGNORE_OFFLINE); + if (connectionlessProtocolList != null) { + connectionlessProtocols.addAll(Arrays.asList(connectionlessProtocolList.split("[, ]"))); + } + } + + @Override + public void channelActive(ChannelHandlerContext ctx) { + if (!(ctx.channel() instanceof DatagramChannel)) { + LOGGER.info("[{}] connected", NetworkUtil.session(ctx.channel())); + } + } + + @Override + public void channelInactive(ChannelHandlerContext ctx) { + LOGGER.info("[{}] disconnected", NetworkUtil.session(ctx.channel())); + closeChannel(ctx.channel()); + + boolean supportsOffline = BasePipelineFactory.getHandler(ctx.pipeline(), HttpRequestDecoder.class) == null + && !connectionlessProtocols.contains(ctx.pipeline().get(BaseProtocolDecoder.class).getProtocolName()); + connectionManager.deviceDisconnected(ctx.channel(), supportsOffline); + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { + while (cause.getCause() != null && cause.getCause() != cause) { + cause = cause.getCause(); + } + LOGGER.info("[{}] error", NetworkUtil.session(ctx.channel()), cause); + closeChannel(ctx.channel()); + } + + @Override + public void userEventTriggered(ChannelHandlerContext ctx, Object evt) { + if (evt instanceof IdleStateEvent) { + LOGGER.info("[{}] timed out", NetworkUtil.session(ctx.channel())); + closeChannel(ctx.channel()); + } + } + + private void closeChannel(Channel channel) { + if (!(channel instanceof DatagramChannel)) { + channel.close(); + } + } + +} diff --git a/src/main/java/org/traccar/handler/NetworkForwarderHandler.java b/src/main/java/org/traccar/handler/network/NetworkForwarderHandler.java index 470e175ca..5f07ce355 100644 --- a/src/main/java/org/traccar/handler/NetworkForwarderHandler.java +++ b/src/main/java/org/traccar/handler/network/NetworkForwarderHandler.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.traccar.handler; +package org.traccar.handler.network; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; diff --git a/src/main/java/org/traccar/handler/NetworkMessageHandler.java b/src/main/java/org/traccar/handler/network/NetworkMessageHandler.java index b1d926bfa..c2fb9016a 100644 --- a/src/main/java/org/traccar/handler/NetworkMessageHandler.java +++ b/src/main/java/org/traccar/handler/network/NetworkMessageHandler.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.traccar.handler; +package org.traccar.handler.network; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelDuplexHandler; diff --git a/src/main/java/org/traccar/handler/OpenChannelHandler.java b/src/main/java/org/traccar/handler/network/OpenChannelHandler.java index e416f35ae..21aaae676 100644 --- a/src/main/java/org/traccar/handler/OpenChannelHandler.java +++ b/src/main/java/org/traccar/handler/network/OpenChannelHandler.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.traccar.handler; +package org.traccar.handler.network; import io.netty.channel.ChannelDuplexHandler; import io.netty.channel.ChannelHandlerContext; diff --git a/src/main/java/org/traccar/handler/RemoteAddressHandler.java b/src/main/java/org/traccar/handler/network/RemoteAddressHandler.java index 61ada5b91..c52bb2be1 100644 --- a/src/main/java/org/traccar/handler/RemoteAddressHandler.java +++ b/src/main/java/org/traccar/handler/network/RemoteAddressHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 - 2022 Anton Tananaev (anton@traccar.org) + * Copyright 2015 - 2024 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. @@ -13,17 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.traccar.handler; +package org.traccar.handler.network; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; +import jakarta.inject.Inject; +import jakarta.inject.Singleton; import org.traccar.config.Config; import org.traccar.config.Keys; import org.traccar.model.Position; -import jakarta.inject.Inject; -import jakarta.inject.Singleton; import java.net.InetSocketAddress; @Singleton diff --git a/src/main/java/org/traccar/handler/StandardLoggingHandler.java b/src/main/java/org/traccar/handler/network/StandardLoggingHandler.java index 513602dd8..dae93655d 100644 --- a/src/main/java/org/traccar/handler/StandardLoggingHandler.java +++ b/src/main/java/org/traccar/handler/network/StandardLoggingHandler.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.traccar.handler; +package org.traccar.handler.network; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufUtil; diff --git a/src/main/java/org/traccar/helper/PositionLogger.java b/src/main/java/org/traccar/helper/PositionLogger.java new file mode 100644 index 000000000..9f149edf4 --- /dev/null +++ b/src/main/java/org/traccar/helper/PositionLogger.java @@ -0,0 +1,94 @@ +/* + * Copyright 2024 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.helper; + +import io.netty.channel.ChannelHandlerContext; +import jakarta.inject.Inject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.traccar.config.Config; +import org.traccar.config.Keys; +import org.traccar.model.Device; +import org.traccar.model.Position; +import org.traccar.session.cache.CacheManager; + +import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.Set; + +public class PositionLogger { + + private static final Logger LOGGER = LoggerFactory.getLogger(PositionLogger.class); + + private final CacheManager cacheManager; + private final Set<String> logAttributes = new LinkedHashSet<>(); + + @Inject + public PositionLogger(Config config, CacheManager cacheManager) { + this.cacheManager = cacheManager; + logAttributes.addAll(Arrays.asList(config.getString(Keys.LOGGER_ATTRIBUTES).split("[, ]"))); + } + + public void log(ChannelHandlerContext context, Position position) { + Device device = cacheManager.getObject(Device.class, position.getDeviceId()); + + StringBuilder builder = new StringBuilder(); + builder.append("[").append(NetworkUtil.session(context.channel())).append("] "); + builder.append("id: ").append(device.getUniqueId()); + for (String attribute : logAttributes) { + switch (attribute) { + case "time": + builder.append(", time: ").append(DateUtil.formatDate(position.getFixTime(), false)); + break; + case "position": + builder.append(", lat: ").append(String.format("%.5f", position.getLatitude())); + builder.append(", lon: ").append(String.format("%.5f", position.getLongitude())); + break; + case "speed": + if (position.getSpeed() > 0) { + builder.append(", speed: ").append(String.format("%.1f", position.getSpeed())); + } + break; + case "course": + builder.append(", course: ").append(String.format("%.1f", position.getCourse())); + break; + case "accuracy": + if (position.getAccuracy() > 0) { + builder.append(", accuracy: ").append(String.format("%.1f", position.getAccuracy())); + } + break; + case "outdated": + if (position.getOutdated()) { + builder.append(", outdated"); + } + break; + case "invalid": + if (!position.getValid()) { + builder.append(", invalid"); + } + break; + default: + Object value = position.getAttributes().get(attribute); + if (value != null) { + builder.append(", ").append(attribute).append(": ").append(value); + } + break; + } + } + LOGGER.info(builder.toString()); + } + +} diff --git a/src/main/java/org/traccar/model/Device.java b/src/main/java/org/traccar/model/Device.java index a3088a613..c2da9be79 100644 --- a/src/main/java/org/traccar/model/Device.java +++ b/src/main/java/org/traccar/model/Device.java @@ -53,7 +53,7 @@ public class Device extends GroupedModel implements Disableable, Schedulable { } public void setUniqueId(String uniqueId) { - if (uniqueId.contains("../") || uniqueId.contains("..\\")) { + if (uniqueId.contains("..")) { throw new IllegalArgumentException("Invalid unique id"); } this.uniqueId = uniqueId.trim(); diff --git a/src/main/java/org/traccar/protocol/AtrackProtocolDecoder.java b/src/main/java/org/traccar/protocol/AtrackProtocolDecoder.java index 8896dcfb0..834f4f16c 100644 --- a/src/main/java/org/traccar/protocol/AtrackProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/AtrackProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 - 2021 Anton Tananaev (anton@traccar.org) + * Copyright 2013 - 2024 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. @@ -325,6 +325,9 @@ public class AtrackProtocolDecoder extends BaseProtocolDecoder { case "SA": position.set(Position.KEY_SATELLITES, buf.readUnsignedByte()); break; + case "MT": + position.set(Position.KEY_MOTION, buf.readUnsignedByte() > 0); + break; case "MV": position.set(Position.KEY_POWER, buf.readUnsignedShort() * 0.1); break; diff --git a/src/main/java/org/traccar/protocol/Gl200TextProtocolDecoder.java b/src/main/java/org/traccar/protocol/Gl200TextProtocolDecoder.java index 7a8d67cf9..775e98401 100644 --- a/src/main/java/org/traccar/protocol/Gl200TextProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/Gl200TextProtocolDecoder.java @@ -41,12 +41,54 @@ import java.text.SimpleDateFormat; import java.util.Date; import java.util.LinkedList; import java.util.List; +import java.util.Map; import java.util.TimeZone; import java.util.regex.Matcher; import java.util.regex.Pattern; public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { + private static final Map<String, String> PROTOCOL_MODELS = Map.ofEntries( + Map.entry("02", "GL200"), + Map.entry("04", "GV200"), + Map.entry("06", "GV300"), + Map.entry("08", "GMT100"), + Map.entry("09", "GV50P"), + Map.entry("0F", "GV55"), + Map.entry("10", "GV55 LITE"), + Map.entry("11", "GL500"), + Map.entry("1A", "GL300"), + Map.entry("1F", "GV500"), + Map.entry("25", "GV300"), + Map.entry("27", "GV300W"), + Map.entry("28", "GL300VC"), + Map.entry("2C", "GL300W"), + Map.entry("2F", "GV55"), + Map.entry("30", "GL300"), + Map.entry("31", "GV65"), + Map.entry("35", "GV200"), + Map.entry("36", "GV500"), + Map.entry("3F", "GMT100"), + Map.entry("40", "GL500"), + Map.entry("41", "GV75W"), + Map.entry("42", "GT501"), + Map.entry("44", "GL530"), + Map.entry("45", "GB100"), + Map.entry("50", "GV55W"), + Map.entry("52", "GL50"), + Map.entry("55", "GL50B"), + Map.entry("5E", "GV500MAP"), + Map.entry("6E", "GV310LAU"), + Map.entry("BD", "CV200"), + Map.entry("C2", "GV600M"), + Map.entry("DC", "GV600MG"), + Map.entry("DE", "GL500M"), + Map.entry("F1", "GV350M"), + Map.entry("F8", "GV800W"), + Map.entry("FC", "GV600W"), + Map.entry("802004", "GV58LAU"), + Map.entry("802005", "GV355CEU")); + private boolean ignoreFixTime; private final DateFormat dateFormat; @@ -62,9 +104,19 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { ignoreFixTime = getConfig().getBoolean(Keys.PROTOCOL_IGNORE_FIX_TIME.withPrefix(getProtocolName())); } - private String getDeviceModel(DeviceSession deviceSession, String value) { - String model = value.isEmpty() ? getDeviceModel(deviceSession) : value; - return model != null ? model.toUpperCase() : ""; + private String getDeviceModel(DeviceSession deviceSession, String protocolVersion) { + String declaredModel = getDeviceModel(deviceSession); + if (declaredModel != null) { + return declaredModel.toUpperCase(); + } + String versionPrefix; + if (protocolVersion.length() > 6) { + versionPrefix = protocolVersion.substring(0, 6); + } else { + versionPrefix = protocolVersion.substring(0, 2); + } + String model = PROTOCOL_MODELS.get(versionPrefix); + return model != null ? model : ""; } private Position initPosition(Parser parser, Channel channel, SocketAddress remoteAddress) { @@ -122,12 +174,12 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { private static final Pattern PATTERN_INF = new PatternBuilder() .text("+").expression("(?:RESP|BUFF):GTINF,") - .expression("(?:.{6}|.{10})?,") // protocol version + .expression("(.{6}|.{10})?,") // protocol version .number("(d{15}|x{14}),") // imei .expression("(?:[0-9A-Z]{17},)?") // vin .expression("(?:[^,]+)?,") // device name .number("(xx),") // state - .expression("(?:[0-9Ff]{20})?,") // iccid + .expression("([0-9Ff]{20})?,") // iccid .number("(d{1,2}),") // rssi .number("d{1,2},") .expression("[01]{1,2},") // external power @@ -148,6 +200,7 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { .expression("(?:[01])?,").optional() // pin15 mode .number("(d+)?,") // adc1 .number("(d+)?,").optional() // adc2 + .number("(d+)?,").optional() // adc3 .number("(xx)?,") // digital input .number("(xx)?,") // digital output .number("[-+]dddd,") // timezone @@ -163,10 +216,17 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { private Object decodeInf(Channel channel, SocketAddress remoteAddress, String sentence) { Parser parser = new Parser(PATTERN_INF, sentence); - Position position = initPosition(parser, channel, remoteAddress); - if (position == null) { + if (!parser.matches()) { return null; } + String protocolVersion = parser.next(); + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, parser.next()); + if (deviceSession == null) { + return null; + } + Position position = new Position(); + position.setProtocol(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); switch (parser.nextHexInt()) { case 0x16: @@ -197,9 +257,15 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { break; } + position.set(Position.KEY_ICCID, parser.next()); position.set(Position.KEY_RSSI, parser.nextInt()); - parser.next(); // odometer or external power + String model = getDeviceModel(deviceSession, protocolVersion); + if (model.equals("GV310LAU")) { + position.set(Position.KEY_POWER, parser.nextDouble() / 1000); + } else { + parser.next(); // odometer or external power + } position.set(Position.KEY_BATTERY, parser.nextDouble()); position.set(Position.KEY_CHARGE, parser.nextInt() == 1); @@ -210,6 +276,9 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { position.set(Position.PREFIX_ADC + 1, parser.next()); position.set(Position.PREFIX_ADC + 2, parser.next()); + if (model.equals("GV310LAU")) { + position.set(Position.PREFIX_ADC + 3, parser.next()); + } position.set(Position.KEY_INPUT, parser.next()); position.set(Position.KEY_OUTPUT, parser.next()); @@ -455,7 +524,7 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { private Object decodeCan(Channel channel, SocketAddress remoteAddress, String[] v) throws ParseException { int index = 0; index += 1; // header - index += 1; // protocol version + String protocolVersion = v[index++]; // protocol version DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, v[index++]); if (deviceSession == null) { return null; @@ -464,7 +533,8 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { Position position = new Position(getProtocolName()); position.setDeviceId(deviceSession.getDeviceId()); - String model = getDeviceModel(deviceSession, v[index++]); + String model = getDeviceModel(deviceSession, protocolVersion); + index += 1; // device name index += 1; // report type index += 1; // can bus state long reportMask = Long.parseLong(v[index++], 16); @@ -872,13 +942,14 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { private Object decodeEri(Channel channel, SocketAddress remoteAddress, String[] v) throws ParseException { int index = 0; index += 1; // header - index += 1; // protocol version + String protocolVersion = v[index++]; // protocol version DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, v[index++]); if (deviceSession == null) { return null; } - String model = getDeviceModel(deviceSession, v[index++]); + String model = getDeviceModel(deviceSession, protocolVersion); + index += 1; // device name long mask = Long.parseLong(v[index++], 16); Double power = v[index++].isEmpty() ? null : Integer.parseInt(v[index - 1]) * 0.001; index += 1; // report type @@ -960,7 +1031,7 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { } private static final Pattern PATTERN_IGN = new PatternBuilder() - .text("+").expression("(?:RESP|BUFF):GTIG[NF],") + .text("+").expression("(?:RESP|BUFF):GT[IV]G[NF],") .expression("(?:.{6}|.{10})?,") // protocol version .number("(d{15}|x{14}),") // imei .expression("[^,]*,") // device name @@ -984,7 +1055,7 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { decodeLocation(position, parser); - position.set(Position.KEY_IGNITION, sentence.contains("IGN")); + position.set(Position.KEY_IGNITION, sentence.contains("GN")); position.set(Position.KEY_HOURS, parseHours(parser.next())); position.set(Position.KEY_ODOMETER, parser.nextDouble() * 1000); @@ -1606,6 +1677,8 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { break; case "IGN": case "IGF": + case "VGN": + case "VGF": result = decodeIgn(channel, remoteAddress, sentence); break; case "LSW": diff --git a/src/main/java/org/traccar/protocol/HuabaoProtocolDecoder.java b/src/main/java/org/traccar/protocol/HuabaoProtocolDecoder.java index 2186fb91f..648c5fb42 100644 --- a/src/main/java/org/traccar/protocol/HuabaoProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/HuabaoProtocolDecoder.java @@ -979,6 +979,12 @@ public class HuabaoProtocolDecoder extends BaseProtocolDecoder { case 0x0103: position.set(Position.KEY_FUEL_LEVEL, buf.readUnsignedInt() * 0.01); break; + case 0x0111: + position.set("fuelTemp", buf.readUnsignedByte() - 40); + break; + case 0x012E: + position.set("oilLevel", buf.readUnsignedShort() * 0.1); + break; case 0x052A: position.set(Position.KEY_FUEL_LEVEL, buf.readUnsignedShort() * 0.01); break; @@ -1113,6 +1119,30 @@ public class HuabaoProtocolDecoder extends BaseProtocolDecoder { } getLastLocation(position, time); break; + case 0x15: + int event = buf.readInt(); + switch (event) { + case 51: + position.set(Position.KEY_ALARM, Position.ALARM_ACCELERATION); + break; + case 52: + position.set(Position.KEY_ALARM, Position.ALARM_BRAKING); + break; + case 53: + position.set(Position.KEY_ALARM, Position.ALARM_CORNERING); + break; + case 54: + position.set(Position.KEY_ALARM, Position.ALARM_LANE_CHANGE); + break; + case 56: + position.set(Position.KEY_ALARM, Position.ALARM_ACCIDENT); + break; + default: + position.set(Position.KEY_EVENT, event); + break; + } + getLastLocation(position, time); + break; default: return null; } diff --git a/src/test/java/org/traccar/database/GroupTreeTest.java b/src/test/java/org/traccar/database/GroupTreeTest.java deleted file mode 100644 index 7c0d478f8..000000000 --- a/src/test/java/org/traccar/database/GroupTreeTest.java +++ /dev/null @@ -1,56 +0,0 @@ -package org.traccar.database; - -import org.junit.jupiter.api.Test; -import org.traccar.model.Device; -import org.traccar.model.Group; - -import java.util.ArrayList; -import java.util.Collection; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -public class GroupTreeTest { - - private static Group createGroup(long id, String name, long parent) { - Group group = new Group(); - group.setId(id); - group.setName(name); - group.setGroupId(parent); - return group; - } - - private static Device createDevice(long id, String name, long parent) { - Device device = new Device(); - device.setId(id); - device.setName(name); - device.setGroupId(parent); - return device; - } - - @Test - public void testGetDescendants() { - Collection<Group> groups = new ArrayList<>(); - groups.add(createGroup(1, "First", 0)); - groups.add(createGroup(2, "Second", 1)); - groups.add(createGroup(3, "Third", 2)); - groups.add(createGroup(4, "Fourth", 2)); - groups.add(createGroup(5, "Fifth", 4)); - - Collection<Device> devices = new ArrayList<>(); - devices.add(createDevice(1, "One", 3)); - devices.add(createDevice(2, "Two", 5)); - devices.add(createDevice(3, "One", 5)); - - GroupTree groupTree = new GroupTree(groups, devices); - - assertEquals(4, groupTree.getGroups(1).size()); - assertEquals(3, groupTree.getGroups(2).size()); - assertEquals(0, groupTree.getGroups(3).size()); - assertEquals(1, groupTree.getGroups(4).size()); - - assertEquals(3, groupTree.getDevices(1).size()); - assertEquals(1, groupTree.getDevices(3).size()); - assertEquals(2, groupTree.getDevices(4).size()); - } - -} diff --git a/src/test/java/org/traccar/handler/DistanceHandlerTest.java b/src/test/java/org/traccar/handler/DistanceHandlerTest.java index 7d2f1e2e3..04a27ad79 100644 --- a/src/test/java/org/traccar/handler/DistanceHandlerTest.java +++ b/src/test/java/org/traccar/handler/DistanceHandlerTest.java @@ -15,14 +15,15 @@ public class DistanceHandlerTest { DistanceHandler distanceHandler = new DistanceHandler(new Config(), mock(CacheManager.class)); - Position position = distanceHandler.handlePosition(new Position()); + Position position = new Position(); + distanceHandler.handlePosition(position, p -> {}); assertEquals(0.0, position.getAttributes().get(Position.KEY_DISTANCE)); assertEquals(0.0, position.getAttributes().get(Position.KEY_TOTAL_DISTANCE)); position.set(Position.KEY_DISTANCE, 100); - position = distanceHandler.handlePosition(position); + distanceHandler.handlePosition(position, p -> {}); assertEquals(100.0, position.getAttributes().get(Position.KEY_DISTANCE)); assertEquals(100.0, position.getAttributes().get(Position.KEY_TOTAL_DISTANCE)); diff --git a/src/test/java/org/traccar/handler/MotionHandlerTest.java b/src/test/java/org/traccar/handler/MotionHandlerTest.java index 10cdf6a90..927c803d9 100644 --- a/src/test/java/org/traccar/handler/MotionHandlerTest.java +++ b/src/test/java/org/traccar/handler/MotionHandlerTest.java @@ -26,7 +26,8 @@ public class MotionHandlerTest { MotionHandler motionHandler = new MotionHandler(cacheManager); - Position position = motionHandler.handlePosition(new Position()); + Position position = new Position(); + motionHandler.handlePosition(position, p -> {}); assertEquals(false, position.getAttributes().get(Position.KEY_MOTION)); diff --git a/src/test/java/org/traccar/handler/events/AlertEventHandlerTest.java b/src/test/java/org/traccar/handler/events/AlertEventHandlerTest.java index 66dc55c85..7b1991553 100644 --- a/src/test/java/org/traccar/handler/events/AlertEventHandlerTest.java +++ b/src/test/java/org/traccar/handler/events/AlertEventHandlerTest.java @@ -7,10 +7,11 @@ import org.traccar.model.Event; import org.traccar.model.Position; import org.traccar.session.cache.CacheManager; -import java.util.Map; +import java.util.ArrayList; +import java.util.List; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.mockito.Mockito.mock; public class AlertEventHandlerTest extends BaseTest { @@ -22,9 +23,10 @@ public class AlertEventHandlerTest extends BaseTest { Position position = new Position(); position.set(Position.KEY_ALARM, Position.ALARM_GENERAL); - Map<Event, Position> events = alertEventHandler.analyzePosition(position); - assertNotNull(events); - Event event = events.keySet().iterator().next(); + List<Event> events = new ArrayList<>(); + alertEventHandler.analyzePosition(position, events::add); + assertFalse(events.isEmpty()); + Event event = events.iterator().next(); assertEquals(Event.TYPE_ALARM, event.getType()); } diff --git a/src/test/java/org/traccar/handler/events/CommandResultEventHandlerTest.java b/src/test/java/org/traccar/handler/events/CommandResultEventHandlerTest.java index bc24e42f5..58e198b7f 100644 --- a/src/test/java/org/traccar/handler/events/CommandResultEventHandlerTest.java +++ b/src/test/java/org/traccar/handler/events/CommandResultEventHandlerTest.java @@ -5,9 +5,12 @@ import org.traccar.BaseTest; import org.traccar.model.Event; import org.traccar.model.Position; +import java.util.ArrayList; +import java.util.List; import java.util.Map; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; public class CommandResultEventHandlerTest extends BaseTest { @@ -19,9 +22,10 @@ public class CommandResultEventHandlerTest extends BaseTest { Position position = new Position(); position.set(Position.KEY_RESULT, "Test Result"); - Map<Event, Position> events = commandResultEventHandler.analyzePosition(position); - assertNotNull(events); - Event event = events.keySet().iterator().next(); + List<Event> events = new ArrayList<>(); + commandResultEventHandler.analyzePosition(position, events::add); + assertFalse(events.isEmpty()); + Event event = events.iterator().next(); assertEquals(Event.TYPE_COMMAND_RESULT, event.getType()); } diff --git a/src/test/java/org/traccar/handler/events/IgnitionEventHandlerTest.java b/src/test/java/org/traccar/handler/events/IgnitionEventHandlerTest.java index 972932df4..61ff50efa 100644 --- a/src/test/java/org/traccar/handler/events/IgnitionEventHandlerTest.java +++ b/src/test/java/org/traccar/handler/events/IgnitionEventHandlerTest.java @@ -1,14 +1,11 @@ package org.traccar.handler.events; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.traccar.BaseTest; -import org.traccar.model.Event; import org.traccar.model.Position; import org.traccar.session.cache.CacheManager; -import java.util.Map; - -import static org.junit.jupiter.api.Assertions.assertNull; import static org.mockito.Mockito.mock; public class IgnitionEventHandlerTest extends BaseTest { @@ -21,8 +18,7 @@ public class IgnitionEventHandlerTest extends BaseTest { Position position = new Position(); position.set(Position.KEY_IGNITION, true); position.setValid(true); - Map<Event, Position> events = ignitionEventHandler.analyzePosition(position); - assertNull(events); + ignitionEventHandler.analyzePosition(position, Assertions::assertNull); } } diff --git a/src/test/java/org/traccar/handler/events/MaintenanceEventHandlerTest.java b/src/test/java/org/traccar/handler/events/MaintenanceEventHandlerTest.java index 661336d76..f0bd4a3f9 100644 --- a/src/test/java/org/traccar/handler/events/MaintenanceEventHandlerTest.java +++ b/src/test/java/org/traccar/handler/events/MaintenanceEventHandlerTest.java @@ -2,11 +2,14 @@ package org.traccar.handler.events; import org.junit.jupiter.api.Test; import org.traccar.BaseTest; +import org.traccar.model.Event; import org.traccar.model.Maintenance; import org.traccar.model.Position; import org.traccar.session.cache.CacheManager; +import java.util.ArrayList; import java.util.Date; +import java.util.List; import java.util.Set; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -38,24 +41,30 @@ public class MaintenanceEventHandlerTest extends BaseTest { MaintenanceEventHandler eventHandler = new MaintenanceEventHandler(cacheManager); when(maintenance.getStart()).thenReturn(10000.0); - when(maintenance.getPeriod()).thenReturn(2000.0); + when(maintenance.getPeriod()).thenReturn(2000.0); + + List<Event> events = new ArrayList<>(); lastPosition.set(Position.KEY_TOTAL_DISTANCE, 1999); - position.set(Position.KEY_TOTAL_DISTANCE, 2001); - assertTrue(eventHandler.analyzePosition(position).isEmpty()); + position.set(Position.KEY_TOTAL_DISTANCE, 2001); + eventHandler.analyzePosition(position, events::add); + assertTrue(events.isEmpty()); lastPosition.set(Position.KEY_TOTAL_DISTANCE, 3999); - position.set(Position.KEY_TOTAL_DISTANCE, 4001); - assertTrue(eventHandler.analyzePosition(position).isEmpty()); + position.set(Position.KEY_TOTAL_DISTANCE, 4001); + eventHandler.analyzePosition(position, events::add); + assertTrue(events.isEmpty()); lastPosition.set(Position.KEY_TOTAL_DISTANCE, 9999); position.set(Position.KEY_TOTAL_DISTANCE, 10001); - assertEquals(1, eventHandler.analyzePosition(position).size()); + eventHandler.analyzePosition(position, events::add); + assertEquals(1, events.size()); lastPosition.set(Position.KEY_TOTAL_DISTANCE, 11999); position.set(Position.KEY_TOTAL_DISTANCE, 12001); - assertEquals(1, eventHandler.analyzePosition(position).size()); - + eventHandler.analyzePosition(position, events::add); + assertEquals(2, events.size()); + } } diff --git a/src/test/java/org/traccar/protocol/AtrackProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/AtrackProtocolDecoderTest.java index be6fb5c45..7115e0ea6 100644 --- a/src/test/java/org/traccar/protocol/AtrackProtocolDecoderTest.java +++ b/src/test/java/org/traccar/protocol/AtrackProtocolDecoderTest.java @@ -44,6 +44,9 @@ public class AtrackProtocolDecoderTest extends ProtocolTest { decoder.setCustom(true); verifyPositions(decoder, binary( + "405064e503092d8600000000000f54ab660786b0660786b0660a12c4fa2e202d01cfd32a005a02000022b700070000000000000007d007d000254349254d56254256254d5400007f002800660786ec660786ec660a12c4fa2e202d01cfd32a005a02000022b700070000000000000007d007d000254349254d56254256254d5400007f0028006607872866078728660a12c4fa2e202d01cfd32a005a02000022b700070000000000000007d007d000254349254d56254256254d5400007f0028006607876466078764660a12c4fa2e202d01cfd32a005a02000022b700070000000000000007d007d000254349254d56254256254d5400007f002800660787a0660787a0660a12c4fa2e202d01cfd32a005a02000022b700060000000000000007d007d000254349254d56254256254d5400007f002800660787dc660787dc660a12c4fa2e202d01cfd32a005a02000022b700060000000000000007d007d000254349254d56254256254d5400007f0028006607881866078818660a12c4fa2e202d01cfd32a005a02000022b700060000000000000007d007d000254349254d56254256254d5400007f0028006607885466078854660a12c4fa2e202d01cfd32a005a02000022b700060000000000000007d007d000254349254d56254256254d5400007f0028006607889066078890660a12c4fa2e202d01cfd32a005a02000022b700060000000000000007d007d000254349254d56254256254d5400007f002800660788cc660788cc660a12c4fa2e202d01cfd32a005a02000022b700060000000000000007d007d000254349254d56254256254d5400007f0028006607890866078908660a12c4fa2e202d01cfd32a005a02000022b700070000000000000007d007d000254349254d56254256254d5400007f0028006607894466078944660a12c4fa2e202d01cfd32a005a02000022b700070000000000000007d007d000254349254d56254256254d5400007f0028006607898066078980660a12c4fa2e202d01cfd32a005a02000022b700070000000000000007d007d000254349254d56254256254d5400007f002800")); + + verifyPositions(decoder, binary( "4050d78502e01d29000312fa45441d6d647d8e67647d8e67647eef190205437c021846e6001a020002d7f000070000000000000007d007d000254349254d5625525025564e254d4625454c25545225455425464c254d4c25464325534d25494e3025484125484225484325415400010c0000000000000000000000000000000000000000000000000e647d8e85647d8e86647eef190205437c021846e60019020002d7f000070000000000000007d007d000254349254d5625525025564e254d4625454c25545225455425464c254d4c25464325534d25494e3025484125484225484325415400010c0000000000000000000000000000000000000000000000000e647d8ea3647d8ea4647eef190205437c021846e60019020002d7f000070000000000000007d007d000254349254d5625525025564e254d4625454c25545225455425464c254d4c25464325534d25494e3025484125484225484325415400010b0000000000000000000000000000000000000000000000000e647d8ec1647d8ec2647eef190205437c021846e60019020002d7f000070000000000000007d007d000254349254d5625525025564e254d4625454c25545225455425464c254d4c25464325534d25494e3025484125484225484325415400010a0000000000000000000000000000000000000000000000000e647d8edf647d8ee0647eef190205437c021846e60019020002d7f000070000000000000007d007d000254349254d5625525025564e254d4625454c25545225455425464c254d4c25464325534d25494e3025484125484225484325415400010a0000000000000000000000000000000000000000000000000e647d8efd647d8efe647eef190205437c021846e60019020002d7f000070000000000000007d007d000254349254d5625525025564e254d4625454c25545225455425464c254d4c25464325534d25494e3025484125484225484325415400010a0000000000000000000000000000000000000000000000000e")); verifyPositions(decoder, binary( diff --git a/src/test/java/org/traccar/protocol/Gl200TextProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/Gl200TextProtocolDecoderTest.java index b6e50e07d..4cccf8fa2 100644 --- a/src/test/java/org/traccar/protocol/Gl200TextProtocolDecoderTest.java +++ b/src/test/java/org/traccar/protocol/Gl200TextProtocolDecoderTest.java @@ -11,6 +11,14 @@ public class Gl200TextProtocolDecoderTest extends ProtocolTest { var decoder = inject(new Gl200TextProtocolDecoder(null)); + verifyAttribute(decoder, buffer( + "+RESP:GTVGN,C2010D,869653060009939,GV600M,453966,0,0.0,0,163.9,10.239379,45.931389,20231210233723,0222,0010,2F31,006D7621,,,0.0,20240107182623,143F$"), + Position.KEY_IGNITION, true); + + verifyAttribute(decoder, buffer( + "+RESP:GTIGF,8020050502,867488060278727,,509,1,0.0,184,32.6,14.003446,42.654554,20240219124629,0222,0001,0FAD,0497F715,00,,0.0,20240219124630,FD2F$"), + Position.KEY_IGNITION, false); + verifyPositions(decoder, buffer( "+RESP:GTERI,C2010D,869653060009939,GV600M,00000100,,10,1,0,0.0,0,163.9,10.239379,45.931389,20231210233723,0222,0010,2F31,006D7621,,0.0,,,,3,410000,,1,0,6,4,0,,01BF,Sondaporte,EA7D0A3882F6,1,3484,21.81,43,00,00,0,0,0,20240114221802,1875$")); @@ -52,14 +60,14 @@ public class Gl200TextProtocolDecoderTest extends ProtocolTest { "+RESP:GTFRI,5E0100,862061048023666,,,12940,10,1,1,0.0,97,179.8,-90.366478,38.735379,20230616183231,0310,0410,6709,03ADF710,00,6223.7,,,,,110000,,,,202306161834$")); verifyAttribute(decoder, buffer( - "+BUFF:GTERI,410502,864802030794634,GV75W,00000001,,10,1,1,0.0,0,3027.8,-78.706612,-0.955699,20230418170736,0740,0002,A08C,2AB72D,00,0.0,,,,100,110000,1,0099,20230418171004,8B98$"), + "+BUFF:GTERI,410502,864802030794634,,00000001,,10,1,1,0.0,0,3027.8,-78.706612,-0.955699,20230418170736,0740,0002,A08C,2AB72D,00,0.0,,,,100,110000,1,0099,20230418171004,8B98$"), Position.KEY_FUEL_LEVEL, 153); verifyPositions(decoder, false, buffer( "+BUFF:GTFRI,2E0503,861106050005423,,,0,1,,,,,,,,,,,,0,0,,98,1,0,,,20200101000001,0083$")); verifyAttribute(decoder, buffer( - "+RESP:GTERI,271002,863457051562823,GV300,00000002,,10,1,1,0.0,15,28.2,-58.695253,-34.625413,20230119193305,0722,0007,1168,16B3BB,00,0.0,,,,99,210100,2,1,28F8A149F69A3C25,1,0190,20230119193314,07C7$"), + "+RESP:GTERI,271002,863457051562823,,00000002,,10,1,1,0.0,15,28.2,-58.695253,-34.625413,20230119193305,0722,0007,1168,16B3BB,00,0.0,,,,99,210100,2,1,28F8A149F69A3C25,1,0190,20230119193314,07C7$"), Position.PREFIX_TEMP + 1, 25.0); verifyAttribute(decoder, buffer( @@ -176,7 +184,7 @@ public class Gl200TextProtocolDecoderTest extends ProtocolTest { "+RESP:GTERI,310603,863286023345490,,00000002,,10,1,2,0.3,0,155.7,8.000000,52.000000,20171215213040,0262,0002,1450,9F13,00,1130.3,00539:27:19,,,110000,2,1,28FFD5239115034E,1,,20171215213041,27C7$")); verifyPositions(decoder, buffer( - "+RESP:GTERI,250C02,868789023691057,GV300,00000019,,10,1,1,0.0,196,2258.0,-99.201807,19.559242,20180214002957,0334,0003,235B,7F8D,00,6786.7,,,,100,110000,1,0394,1,4,100.0,100.0,20180214003006,C72B$")); + "+RESP:GTERI,250C02,868789023691057,,00000019,,10,1,1,0.0,196,2258.0,-99.201807,19.559242,20180214002957,0334,0003,235B,7F8D,00,6786.7,,,,100,110000,1,0394,1,4,100.0,100.0,20180214003006,C72B$")); verifyAttributes(decoder, buffer( "+RESP:GTCAN,310603,863286023335723,gv65,00,1,C03FFFFF,,0,,719601.00,,,,,,,,274.99,179.02,95.98,84761.00,,,0,,0,,,0,0.0,216,29.8,-2.155296,51.899400,20180209172714,0234,0010,53F3,8D38,00,20180211002128,E94E$")); @@ -203,7 +211,7 @@ public class Gl200TextProtocolDecoderTest extends ProtocolTest { "+RESP:GTOBD,360701,864251020253807,LSGTC58UX7Y067312,GV500,0,70FFFF,LSGTC58UX7Y067312,1,12309,983A8140,0,0,33,nan,,0,0,0,,10,0,,0,4.4,0,83.7,36.235142,49.967324,20170829112348,0255,0001,2760,9017,00,690.1,20170829112400,3456$")); verifyPositions(decoder, buffer( - "+RESP:GTERI,060502,861074023620928,GV300,00000002,27822,10,1,1,0.0,84,2870.9,-78.531796,-0.277329,20170825045344,,,,,,0.0,01138:30:24,,,83,220104,2,1,28FF2776A2150308,1,FFAD,0,20170825045348,A88C$")); + "+RESP:GTERI,060502,861074023620928,,00000002,27822,10,1,1,0.0,84,2870.9,-78.531796,-0.277329,20170825045344,,,,,,0.0,01138:30:24,,,83,220104,2,1,28FF2776A2150308,1,FFAD,0,20170825045348,A88C$")); verifyAttributes(decoder, buffer( "+RESP:GTINF,280500,A1000043D20139,GL300VC,41,,31,0,0,,,3.87,0,1,1,,,20170802150751,70,,48.0,,,20170802112145,03AC$")); @@ -218,7 +226,7 @@ public class Gl200TextProtocolDecoderTest extends ProtocolTest { "+RESP:GTTRI,862370030005908,1,0,99,1,0.0,354,18.5,18.821100,-34.084002,20170607152024,0655,0001,00DD,1CAE,00,0103010100,20170607172115,3E7D$")); verifyPositions(decoder, buffer( - "+RESP:GTERI,060800,861074023677175,GV300,00000002,12351,10,1,1,0.0,0,2862.4,-78.467273,-0.164998,20170529181717,,,,,,0.0,00259:11:50,,,0,210104,2,1,28E17436060000E2,1,015F,0,20170529181723,2824$")); + "+RESP:GTERI,060800,861074023677175,,00000002,12351,10,1,1,0.0,0,2862.4,-78.467273,-0.164998,20170529181717,,,,,,0.0,00259:11:50,,,0,210104,2,1,28E17436060000E2,1,015F,0,20170529181723,2824$")); verifyPosition(decoder, buffer( "+RESP:GTSWG,110100,358688000000158,,1,0,2.1,0,27.1,121.390717,31.164424,20110901073917,0460,0000,1878,0873,,20110901154653,0015$")); @@ -239,7 +247,7 @@ public class Gl200TextProtocolDecoderTest extends ProtocolTest { "+RESP:GTFRI,060228,862894020180553,,14827,10,1,1,3.4,199,409.6,-63.174466,-17.739317,20170407121823,0000,0000,0000,0000,00,15989.5,01070:43:13,13,180,0,220101,,,,20170407081824,9607$")); verifyPositions(decoder, buffer( - "+RESP:GTERI,060502,861074023376992,GV300,00000002,27239,10,1,1,0.2,312,183.3,-79.320820,-2.499110,20170401212005,0740,0000,EE4E,C98F,00,0.0,02114:36:35,,,90,220504,2,0,0,20170401212007,9E3D$")); + "+RESP:GTERI,060502,861074023376992,,00000002,27239,10,1,1,0.2,312,183.3,-79.320820,-2.499110,20170401212005,0740,0000,EE4E,C98F,00,0.0,02114:36:35,,,90,220504,2,0,0,20170401212007,9E3D$")); verifyPositions(decoder, buffer( "+RESP:GTFRI,060502,861074023689626,,25202,10,1,1,0.0,0,2744.1,-78.261047,0.023452,20170401211940,,,,,,0.0,00079:19:15,,,51,110000,,,,20170401212003,4DA7$")); @@ -251,7 +259,7 @@ public class Gl200TextProtocolDecoderTest extends ProtocolTest { "+RESP:GTERI,06020B,862170010196747,,00000000,,10,1,2,1.8,0,-2.5,117.198440,31.845219,20120802061037,0460,0000,5663,0358,00,0.0,,,,0,410000,20120802061040,0012$")); verifyPositions(decoder, buffer( - "+RESP:GTERI,060502,861074023692562,GV300,00000002,14197,10,1,1,0.2,220,491.8,-79.064212,-2.159754,20170401212007,0740,0000,EE49,CE25,00,0.0,01509:10:58,,,87,220104,2,0,0,20170401212010,D14D$")); + "+RESP:GTERI,060502,861074023692562,,00000002,14197,10,1,1,0.2,220,491.8,-79.064212,-2.159754,20170401212007,0740,0000,EE49,CE25,00,0.0,01509:10:58,,,87,220104,2,0,0,20170401212010,D14D$")); verifyPositions(decoder, buffer( "+RESP:GTFRI,210102,354524044925825,,1,1,1,29,2.8,0,133.7,-90.203063,32.265473,20170318005208,,,,,10800,4,20170318005208,0002$")); @@ -487,8 +495,17 @@ public class Gl200TextProtocolDecoderTest extends ProtocolTest { verifyAttributes(decoder, buffer( "+ACK:GTGEO,1A0102,135790246811220,,0,0008,20100310172830,11F0")); - decoder.setModelOverride("GV355CEU"); + verifyAttributes(decoder, buffer( + "+RESP:GTINF,6E0202,868589060187625,RA82,11,89883030000091225018,41,0,1,12349,,4.15,0,1,0,0,20240328231013,0,0,0,0,00,00,+0000,0,20240328231015,7D4F")); + + verifyAttribute(decoder, buffer( + "+RESP:GTINF,6E0202,868589060187625,RA82,11,89883030000091225018,41,0,1,12349,,4.15,0,1,0,0,20240328231013,0,0,0,0,00,00,+0000,0,20240328231015,7D4F"), + Position.KEY_POWER, 12.349); + verifyAttribute(decoder, buffer( + "+RESP:GTINF,6E0202,868589060187625,RA82,11,89883030000091225018,41,0,1,12349,,4.15,0,1,0,0,20240328231013,0,0,0,0,00,00,+0000,0,20240328231015,7D4F"), + Position.PREFIX_ADC + 3, "0"); + verifyAttributes(decoder, buffer( "+RESP:GTCAN,8020050605,867488060270575,,00,1,FFFFFFFF,8LBETF3W4N0001613,,,22.54,0,,,,,,,7.84,4.61,3.24,3.33,,8080,,,00,0.00,0.00,1,14,14,2371,0,001FFFFF,,,,,,,,,7158,9998,0,7.84,0.00,0.00,558,,,,,,,C0,,,,,0,0.0,346,2848.5,-78.592371,-0.968132,20240202083437,0740,0002,526C,00AE7907,00,20240202083440,3F6D$")); diff --git a/swagger.json b/swagger.json index b2209a20e..933652cb0 100644 --- a/swagger.json +++ b/swagger.json @@ -2,7 +2,7 @@ "openapi": "3.0.1", "info": { "title": "Traccar", - "version": "5.12", + "version": "6.0", "description": "Traccar GPS tracking server API documentation. To use the API you need to have a server instance. For testing purposes you can use one of free [demo servers](https://www.traccar.org/demo-server/). For production use you can install your own server or get a [subscription service](https://www.traccar.org/product/tracking-server/).", "contact": { "name": "Traccar Support", diff --git a/traccar-web b/traccar-web -Subproject c9da10062998a231c038cd3a519f72128fcea2b +Subproject a3e79c6572759f6c8f4544166ea34d0e6d789b4 |