diff options
20 files changed, 446 insertions, 139 deletions
diff --git a/src/main/java/org/traccar/api/MediaFilter.java b/src/main/java/org/traccar/api/MediaFilter.java index ab75bdc5d..e6556189a 100644 --- a/src/main/java/org/traccar/api/MediaFilter.java +++ b/src/main/java/org/traccar/api/MediaFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 - 2022 Anton Tananaev (anton@traccar.org) + * Copyright 2018 - 2023 Anton Tananaev (anton@traccar.org) * Copyright 2018 Andrey Kunitsyn (andrey@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -32,7 +32,6 @@ import javax.inject.Inject; import javax.inject.Singleton; import javax.servlet.Filter; import javax.servlet.FilterChain; -import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; @@ -58,10 +57,6 @@ public class MediaFilter implements Filter { } @Override - public void init(FilterConfig filterConfig) throws ServletException { - } - - @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { @@ -99,8 +94,4 @@ public class MediaFilter implements Filter { } } - @Override - public void destroy() { - } - } diff --git a/src/main/java/org/traccar/config/Keys.java b/src/main/java/org/traccar/config/Keys.java index c69289403..162a3fe13 100644 --- a/src/main/java/org/traccar/config/Keys.java +++ b/src/main/java/org/traccar/config/Keys.java @@ -373,14 +373,15 @@ public final class Keys { */ public static final ConfigKey<Boolean> EVENT_MOTION_PROCESS_INVALID_POSITIONS = new BooleanConfigKey( "event.motion.processInvalidPositions", - List.of(KeyType.CONFIG)); + List.of(KeyType.CONFIG, KeyType.DEVICE), + false); /** * If the speed is above specified value, the object is considered to be in motion. Default value is 0.01 knots. */ public static final ConfigKey<Double> EVENT_MOTION_SPEED_THRESHOLD = new DoubleConfigKey( "event.motion.speedThreshold", - List.of(KeyType.CONFIG), + List.of(KeyType.CONFIG, KeyType.DEVICE), 0.01); /** @@ -1167,7 +1168,7 @@ public final class Keys { */ public static final ConfigKey<Long> REPORT_TRIP_MINIMAL_TRIP_DISTANCE = new LongConfigKey( "report.trip.minimalTripDistance", - List.of(KeyType.CONFIG), + List.of(KeyType.CONFIG, KeyType.DEVICE), 500L); /** @@ -1175,7 +1176,7 @@ public final class Keys { */ public static final ConfigKey<Long> REPORT_TRIP_MINIMAL_TRIP_DURATION = new LongConfigKey( "report.trip.minimalTripDuration", - List.of(KeyType.CONFIG), + List.of(KeyType.CONFIG, KeyType.DEVICE), 300L); /** @@ -1183,7 +1184,7 @@ public final class Keys { */ public static final ConfigKey<Long> REPORT_TRIP_MINIMAL_PARKING_DURATION = new LongConfigKey( "report.trip.minimalParkingDuration", - List.of(KeyType.CONFIG), + List.of(KeyType.CONFIG, KeyType.DEVICE), 300L); /** @@ -1191,7 +1192,7 @@ public final class Keys { */ public static final ConfigKey<Long> REPORT_TRIP_MINIMAL_NO_DATA_DURATION = new LongConfigKey( "report.trip.minimalNoDataDuration", - List.of(KeyType.CONFIG), + List.of(KeyType.CONFIG, KeyType.DEVICE), 3600L); /** @@ -1199,7 +1200,8 @@ public final class Keys { */ public static final ConfigKey<Boolean> REPORT_TRIP_USE_IGNITION = new BooleanConfigKey( "report.trip.useIgnition", - List.of(KeyType.CONFIG)); + List.of(KeyType.CONFIG, KeyType.DEVICE), + false); /** * Ignore odometer value reported by the device and use server-calculated total distance instead. This is useful @@ -1207,7 +1209,8 @@ public final class Keys { */ public static final ConfigKey<Boolean> REPORT_IGNORE_ODOMETER = new BooleanConfigKey( "report.ignoreOdometer", - List.of(KeyType.CONFIG)); + List.of(KeyType.CONFIG), + false); /** * Boolean flag to enable or disable position filtering. diff --git a/src/main/java/org/traccar/handler/MotionHandler.java b/src/main/java/org/traccar/handler/MotionHandler.java index 10312f9b3..297527b4d 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 - 2022 Anton Tananaev (anton@traccar.org) + * Copyright 2017 - 2023 Anton Tananaev (anton@traccar.org) * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,8 +18,10 @@ package org.traccar.handler; import io.netty.channel.ChannelHandler; import org.traccar.BaseDataHandler; +import org.traccar.config.Keys; +import org.traccar.helper.model.AttributeUtil; import org.traccar.model.Position; -import org.traccar.reports.common.TripsConfig; +import org.traccar.session.cache.CacheManager; import javax.inject.Inject; import javax.inject.Singleton; @@ -28,17 +30,19 @@ import javax.inject.Singleton; @ChannelHandler.Sharable public class MotionHandler extends BaseDataHandler { - private final double speedThreshold; + private final CacheManager cacheManager; @Inject - public MotionHandler(TripsConfig tripsConfig) { - speedThreshold = tripsConfig.getSpeedThreshold(); + public MotionHandler(CacheManager cacheManager) { + this.cacheManager = cacheManager; } @Override protected Position handlePosition(Position position) { if (!position.hasAttribute(Position.KEY_MOTION)) { - position.set(Position.KEY_MOTION, position.getSpeed() > speedThreshold); + double threshold = AttributeUtil.lookup( + cacheManager, Keys.EVENT_MOTION_SPEED_THRESHOLD, position.getDeviceId()); + position.set(Position.KEY_MOTION, position.getSpeed() > threshold); } return position; } diff --git a/src/main/java/org/traccar/handler/events/MotionEventHandler.java b/src/main/java/org/traccar/handler/events/MotionEventHandler.java index c406bd935..d2c54ccf3 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 - 2022 Anton Tananaev (anton@traccar.org) + * Copyright 2016 - 2023 Anton Tananaev (anton@traccar.org) * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,6 +19,8 @@ package org.traccar.handler.events; import io.netty.channel.ChannelHandler; 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; @@ -46,14 +48,11 @@ public class MotionEventHandler extends BaseEventHandler { private final CacheManager cacheManager; private final Storage storage; - private final TripsConfig tripsConfig; @Inject - public MotionEventHandler( - CacheManager cacheManager, Storage storage, TripsConfig tripsConfig) { + public MotionEventHandler(CacheManager cacheManager, Storage storage) { this.cacheManager = cacheManager; this.storage = storage; - this.tripsConfig = tripsConfig; } @Override @@ -61,14 +60,16 @@ public class MotionEventHandler extends BaseEventHandler { long deviceId = position.getDeviceId(); Device device = cacheManager.getObject(Device.class, deviceId); - if (device == null) { + if (device == null || !PositionUtil.isLatest(cacheManager, position)) { return null; } - if (!PositionUtil.isLatest(cacheManager, position) - || !tripsConfig.getProcessInvalidPositions() && !position.getValid()) { + boolean processInvalid = AttributeUtil.lookup( + cacheManager, Keys.EVENT_MOTION_PROCESS_INVALID_POSITIONS, deviceId); + if (!processInvalid && !position.getValid()) { return null; } + TripsConfig tripsConfig = new TripsConfig(new AttributeUtil.CacheProvider(cacheManager, deviceId)); MotionState state = MotionState.fromDevice(device); MotionProcessor.updateState(state, position, position.getBoolean(Position.KEY_MOTION), tripsConfig); if (state.isChanged()) { diff --git a/src/main/java/org/traccar/helper/model/AttributeUtil.java b/src/main/java/org/traccar/helper/model/AttributeUtil.java index 43558e8f7..2630f64f0 100644 --- a/src/main/java/org/traccar/helper/model/AttributeUtil.java +++ b/src/main/java/org/traccar/helper/model/AttributeUtil.java @@ -1,5 +1,5 @@ /* - * Copyright 2022 Anton Tananaev (anton@traccar.org) + * Copyright 2022 - 2023 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,25 +15,44 @@ */ package org.traccar.helper.model; +import org.traccar.api.security.PermissionsService; +import org.traccar.config.Config; import org.traccar.config.ConfigKey; import org.traccar.config.KeyType; import org.traccar.config.Keys; import org.traccar.model.Device; import org.traccar.model.Group; +import org.traccar.model.Server; 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 final class AttributeUtil { private AttributeUtil() { } - @SuppressWarnings({ "deprecation", "unchecked" }) + public interface Provider { + Device getDevice(); + Group getGroup(long groupId); + Server getServer(); + Config getConfig(); + } + public static <T> T lookup(CacheManager cacheManager, ConfigKey<T> key, long deviceId) { - Device device = cacheManager.getObject(Device.class, deviceId); + return lookup(new CacheProvider(cacheManager, deviceId), key); + } + + @SuppressWarnings({ "deprecation", "unchecked" }) + public static <T> T lookup(Provider provider, ConfigKey<T> key) { + Device device = provider.getDevice(); Object result = device.getAttributes().get(key.getKey()); long groupId = device.getGroupId(); while (result == null && groupId > 0) { - Group group = cacheManager.getObject(Group.class, groupId); + Group group = provider.getGroup(groupId); if (group != null) { result = group.getAttributes().get(key.getKey()); groupId = group.getGroupId(); @@ -42,10 +61,10 @@ public final class AttributeUtil { } } if (result == null && key.hasType(KeyType.SERVER)) { - result = cacheManager.getServer().getAttributes().get(key.getKey()); + result = provider.getServer().getAttributes().get(key.getKey()); } if (result == null && key.hasType(KeyType.CONFIG)) { - result = cacheManager.getConfig().getString(key.getKey()); + result = provider.getConfig().getString(key.getKey()); } if (result != null) { @@ -91,4 +110,79 @@ public final class AttributeUtil { return defaultPassword; } + public static class CacheProvider implements Provider { + + private final CacheManager cacheManager; + private final long deviceId; + + public CacheProvider(CacheManager cacheManager, long deviceId) { + this.cacheManager = cacheManager; + this.deviceId = deviceId; + } + + @Override + public Device getDevice() { + return cacheManager.getObject(Device.class, deviceId); + } + + @Override + public Group getGroup(long groupId) { + return cacheManager.getObject(Group.class, groupId); + } + + @Override + public Server getServer() { + return cacheManager.getServer(); + } + + @Override + public Config getConfig() { + return cacheManager.getConfig(); + } + } + + public static class StorageProvider implements Provider { + + private final Config config; + private final Storage storage; + private final PermissionsService permissionsService; + private final Device device; + + public StorageProvider(Config config, Storage storage, PermissionsService permissionsService, Device device) { + this.config = config; + this.storage = storage; + this.permissionsService = permissionsService; + this.device = device; + } + + @Override + public Device getDevice() { + return device; + } + + @Override + public Group getGroup(long groupId) { + try { + return storage.getObject( + Group.class, new Request(new Columns.All(), new Condition.Equals("id", groupId))); + } catch (StorageException e) { + throw new RuntimeException(e); + } + } + + @Override + public Server getServer() { + try { + return permissionsService.getServer(); + } catch (StorageException e) { + throw new RuntimeException(e); + } + } + + @Override + public Config getConfig() { + return config; + } + } + } diff --git a/src/main/java/org/traccar/model/ExtendedModel.java b/src/main/java/org/traccar/model/ExtendedModel.java index 7a61eda8c..6a4f502f7 100644 --- a/src/main/java/org/traccar/model/ExtendedModel.java +++ b/src/main/java/org/traccar/model/ExtendedModel.java @@ -89,14 +89,18 @@ public class ExtendedModel extends BaseModel { } } - public String getString(String key) { + public String getString(String key, String defaultValue) { if (attributes.containsKey(key)) { return attributes.get(key).toString(); } else { - return null; + return defaultValue; } } + public String getString(String key) { + return getString(key, null); + } + public double getDouble(String key) { if (attributes.containsKey(key)) { Object value = attributes.get(key); diff --git a/src/main/java/org/traccar/protocol/HuabaoProtocolDecoder.java b/src/main/java/org/traccar/protocol/HuabaoProtocolDecoder.java index 05e2fb8cc..4beee7696 100644 --- a/src/main/java/org/traccar/protocol/HuabaoProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/HuabaoProtocolDecoder.java @@ -333,10 +333,6 @@ public class HuabaoProtocolDecoder extends BaseProtocolDecoder { buf.readUnsignedByte(); // power level position.set(Position.KEY_BATTERY_LEVEL, buf.readUnsignedByte()); break; - case 0x60: - position.set(Position.KEY_EVENT, buf.readUnsignedShort()); - buf.skipBytes(length - 2); - break; case 0x61: position.set(Position.KEY_POWER, buf.readUnsignedShort() * 0.01); break; @@ -488,6 +484,13 @@ public class HuabaoProtocolDecoder extends BaseProtocolDecoder { position.set(Position.KEY_BATTERY, Integer.parseInt(lockStatus.substring(2, 5)) * 0.01); } break; + case 0x60: + position.set(Position.KEY_EVENT, buf.readUnsignedShort()); + buf.skipBytes(length - 2); + break; + case 0x69: + position.set(Position.KEY_BATTERY, buf.readUnsignedShort() * 0.01); + break; case 0x80: buf.readUnsignedByte(); // content endIndex = buf.writerIndex() - 2; @@ -934,6 +937,12 @@ public class HuabaoProtocolDecoder extends BaseProtocolDecoder { int id = buf.readUnsignedByte(); int length = buf.readUnsignedByte(); switch (id) { + case 0x01: + position.set(Position.KEY_ALARM, Position.ALARM_POWER_RESTORED); + break; + case 0x02: + position.set(Position.KEY_ALARM, Position.ALARM_POWER_CUT); + break; case 0x1A: position.set(Position.KEY_ALARM, Position.ALARM_ACCELERATION); break; @@ -951,6 +960,15 @@ public class HuabaoProtocolDecoder extends BaseProtocolDecoder { case 0x23: position.set(Position.KEY_ALARM, Position.ALARM_FATIGUE_DRIVING); break; + case 0x26: + case 0x27: + case 0x28: + position.set(Position.KEY_ALARM, Position.ALARM_ACCIDENT); + break; + case 0x31: + case 0x32: + position.set(Position.KEY_ALARM, Position.ALARM_DOOR); + break; default: break; } diff --git a/src/main/java/org/traccar/protocol/StartekProtocol.java b/src/main/java/org/traccar/protocol/StartekProtocol.java index d010df858..1b1c93e33 100644 --- a/src/main/java/org/traccar/protocol/StartekProtocol.java +++ b/src/main/java/org/traccar/protocol/StartekProtocol.java @@ -1,5 +1,5 @@ /* - * Copyright 2021 Anton Tananaev (anton@traccar.org) + * Copyright 2021 - 2023 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -38,7 +38,7 @@ public class StartekProtocol extends BaseProtocol { addServer(new TrackerServer(config, getName(), false) { @Override protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { - pipeline.addLast(new LineBasedFrameDecoder(1024)); + pipeline.addLast(new LineBasedFrameDecoder(1100)); pipeline.addLast(new StringEncoder()); pipeline.addLast(new StringDecoder()); pipeline.addLast(new StartekProtocolEncoder(StartekProtocol.this)); diff --git a/src/main/java/org/traccar/reports/common/ReportUtils.java b/src/main/java/org/traccar/reports/common/ReportUtils.java index 0c168e4ef..f5aa6d040 100644 --- a/src/main/java/org/traccar/reports/common/ReportUtils.java +++ b/src/main/java/org/traccar/reports/common/ReportUtils.java @@ -31,6 +31,7 @@ import org.traccar.config.Config; import org.traccar.config.Keys; import org.traccar.geocoder.Geocoder; import org.traccar.helper.UnitsConverter; +import org.traccar.helper.model.AttributeUtil; import org.traccar.helper.model.PositionUtil; import org.traccar.helper.model.UserUtil; import org.traccar.model.BaseModel; @@ -67,18 +68,16 @@ public class ReportUtils { private final Config config; private final Storage storage; private final PermissionsService permissionsService; - private final TripsConfig tripsConfig; private final VelocityEngine velocityEngine; private final Geocoder geocoder; @Inject public ReportUtils( Config config, Storage storage, PermissionsService permissionsService, - TripsConfig tripsConfig, VelocityEngine velocityEngine, @Nullable Geocoder geocoder) { + VelocityEngine velocityEngine, @Nullable Geocoder geocoder) { this.config = config; this.storage = storage; this.permissionsService = permissionsService; - this.tripsConfig = tripsConfig; this.velocityEngine = velocityEngine; this.geocoder = geocoder; } @@ -306,6 +305,8 @@ public class ReportUtils { Class<T> reportClass) throws StorageException { Collection<T> result = new ArrayList<>(); + TripsConfig tripsConfig = new TripsConfig( + new AttributeUtil.StorageProvider(config, storage, permissionsService, device)); ArrayList<Position> positions = new ArrayList<>(positionCollection); if (!positions.isEmpty()) { diff --git a/src/main/java/org/traccar/reports/common/TripsConfig.java b/src/main/java/org/traccar/reports/common/TripsConfig.java index 52db97b74..2792114d4 100644 --- a/src/main/java/org/traccar/reports/common/TripsConfig.java +++ b/src/main/java/org/traccar/reports/common/TripsConfig.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 - 2022 Anton Tananaev (anton@traccar.org) + * Copyright 2017 - 2023 Anton Tananaev (anton@traccar.org) * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,37 +16,28 @@ */ package org.traccar.reports.common; -import org.traccar.config.Config; import org.traccar.config.Keys; +import org.traccar.helper.model.AttributeUtil; -import javax.inject.Inject; -import javax.inject.Singleton; - -@Singleton public class TripsConfig { - @Inject - public TripsConfig(Config config) { + public TripsConfig(AttributeUtil.Provider attributeProvider) { this( - config.getLong(Keys.REPORT_TRIP_MINIMAL_TRIP_DISTANCE), - config.getLong(Keys.REPORT_TRIP_MINIMAL_TRIP_DURATION) * 1000, - config.getLong(Keys.REPORT_TRIP_MINIMAL_PARKING_DURATION) * 1000, - config.getLong(Keys.REPORT_TRIP_MINIMAL_NO_DATA_DURATION) * 1000, - config.getBoolean(Keys.REPORT_TRIP_USE_IGNITION), - config.getBoolean(Keys.EVENT_MOTION_PROCESS_INVALID_POSITIONS), - config.getDouble(Keys.EVENT_MOTION_SPEED_THRESHOLD)); + AttributeUtil.lookup(attributeProvider, Keys.REPORT_TRIP_MINIMAL_TRIP_DISTANCE), + AttributeUtil.lookup(attributeProvider, Keys.REPORT_TRIP_MINIMAL_TRIP_DURATION) * 1000, + AttributeUtil.lookup(attributeProvider, Keys.REPORT_TRIP_MINIMAL_PARKING_DURATION) * 1000, + AttributeUtil.lookup(attributeProvider, Keys.REPORT_TRIP_MINIMAL_NO_DATA_DURATION) * 1000, + AttributeUtil.lookup(attributeProvider, Keys.REPORT_TRIP_USE_IGNITION)); } public TripsConfig( double minimalTripDistance, long minimalTripDuration, long minimalParkingDuration, - long minimalNoDataDuration, boolean useIgnition, boolean processInvalidPositions, double speedThreshold) { + long minimalNoDataDuration, boolean useIgnition) { this.minimalTripDistance = minimalTripDistance; this.minimalTripDuration = minimalTripDuration; this.minimalParkingDuration = minimalParkingDuration; this.minimalNoDataDuration = minimalNoDataDuration; this.useIgnition = useIgnition; - this.processInvalidPositions = processInvalidPositions; - this.speedThreshold = speedThreshold; } private final double minimalTripDistance; @@ -79,16 +70,4 @@ public class TripsConfig { return useIgnition; } - private final boolean processInvalidPositions; - - public boolean getProcessInvalidPositions() { - return processInvalidPositions; - } - - private final double speedThreshold; - - public double getSpeedThreshold() { - return speedThreshold; - } - } diff --git a/src/main/java/org/traccar/web/CharResponseWrapper.java b/src/main/java/org/traccar/web/CharResponseWrapper.java new file mode 100644 index 000000000..477fe7928 --- /dev/null +++ b/src/main/java/org/traccar/web/CharResponseWrapper.java @@ -0,0 +1,82 @@ +/* + * Copyright 2023 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.web; + +import javax.servlet.ServletOutputStream; +import javax.servlet.WriteListener; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpServletResponseWrapper; +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +public class CharResponseWrapper extends HttpServletResponseWrapper { + + private final ByteArrayOutputStream capture; + private ServletOutputStream output; + + public CharResponseWrapper(HttpServletResponse response) { + super(response); + capture = new ByteArrayOutputStream(response.getBufferSize()); + } + + @Override + public ServletOutputStream getOutputStream() { + if (output == null) { + output = new ServletOutputStream() { + @Override + public boolean isReady() { + return true; + } + + @Override + public void setWriteListener(WriteListener writeListener) { + } + + @Override + public void write(int b) { + capture.write(b); + } + + @Override + public void flush() throws IOException { + capture.flush(); + } + + @Override + public void close() throws IOException { + capture.close(); + } + }; + } + return output; + } + + @Override + public void flushBuffer() throws IOException { + super.flushBuffer(); + if (output != null) { + output.flush(); + } + } + + public byte[] getCapture() throws IOException { + if (output != null) { + output.close(); + } + return capture.toByteArray(); + } + +} diff --git a/src/main/java/org/traccar/web/ModernDefaultServlet.java b/src/main/java/org/traccar/web/ModernDefaultServlet.java new file mode 100644 index 000000000..bae089d6c --- /dev/null +++ b/src/main/java/org/traccar/web/ModernDefaultServlet.java @@ -0,0 +1,33 @@ +/* + * Copyright 2023 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.web; + +import org.eclipse.jetty.servlet.DefaultServlet; +import org.eclipse.jetty.util.resource.Resource; + +public class ModernDefaultServlet extends DefaultServlet { + + @Override + public Resource getResource(String pathInContext) { + return super.getResource(pathInContext.indexOf('.') < 0 ? "/" : pathInContext); + } + + @Override + public String getWelcomeFile(String pathInContext) { + return super.getWelcomeFile("/"); + } + +} diff --git a/src/main/java/org/traccar/web/OverrideFilter.java b/src/main/java/org/traccar/web/OverrideFilter.java new file mode 100644 index 000000000..708632bc1 --- /dev/null +++ b/src/main/java/org/traccar/web/OverrideFilter.java @@ -0,0 +1,80 @@ +/* + * Copyright 2023 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.web; + +import com.google.inject.Provider; +import org.traccar.api.security.PermissionsService; +import org.traccar.model.Server; +import org.traccar.storage.StorageException; + +import javax.inject.Inject; +import javax.inject.Singleton; +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +@Singleton +public class OverrideFilter implements Filter { + + private final Provider<PermissionsService> permissionsServiceProvider; + + @Inject + public OverrideFilter(Provider<PermissionsService> permissionsServiceProvider) { + this.permissionsServiceProvider = permissionsServiceProvider; + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + + CharResponseWrapper wrappedResponse = new CharResponseWrapper((HttpServletResponse) response); + + chain.doFilter(request, wrappedResponse); + + byte[] bytes = wrappedResponse.getCapture(); + if (wrappedResponse.getContentType().contains("text/html") + || ((HttpServletRequest) request).getPathInfo().endsWith("manifest.json")) { + + Server server; + try { + server = permissionsServiceProvider.get().getServer(); + } catch (StorageException e) { + throw new RuntimeException(e); + } + + String title = server.getString("title", "Traccar"); + String description = server.getString("description", "Traccar GPS Tracking System"); + String colorPrimary = server.getString("colorPrimary", "#1a237e"); + + String alteredContent = new String(wrappedResponse.getCapture()) + .replace("${title}", title) + .replace("${description}", description) + .replace("${colorPrimary}", colorPrimary); + + response.setContentLength(alteredContent.length()); + response.getOutputStream().write(alteredContent.getBytes()); + + } else { + response.getOutputStream().write(bytes); + } + } + +} diff --git a/src/main/java/org/traccar/web/WebModule.java b/src/main/java/org/traccar/web/WebModule.java index 0722c5d1e..a32a6f447 100644 --- a/src/main/java/org/traccar/web/WebModule.java +++ b/src/main/java/org/traccar/web/WebModule.java @@ -1,5 +1,5 @@ /* - * Copyright 2022 Anton Tananaev (anton@traccar.org) + * Copyright 2022 - 2023 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,6 +23,7 @@ public class WebModule extends ServletModule { @Override protected void configureServlets() { + filter("/*").through(OverrideFilter.class); filter("/api/*").through(ThrottlingFilter.class); filter("/api/media/*").through(MediaFilter.class); serve("/api/socket").with(AsyncSocketServlet.class); diff --git a/src/main/java/org/traccar/web/WebServer.java b/src/main/java/org/traccar/web/WebServer.java index 79d19cc9b..ce1220157 100644 --- a/src/main/java/org/traccar/web/WebServer.java +++ b/src/main/java/org/traccar/web/WebServer.java @@ -62,9 +62,6 @@ import java.io.File; import java.io.IOException; import java.io.Writer; import java.net.InetSocketAddress; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; import java.util.EnumSet; public class WebServer implements LifecycleObject { @@ -103,14 +100,8 @@ public class WebServer implements LifecycleObject { @Override protected void handleErrorPage( HttpServletRequest request, Writer writer, int code, String message) throws IOException { - Path index = Paths.get(config.getString(Keys.WEB_PATH), "index.html"); - if (code == HttpStatus.NOT_FOUND_404 - && !request.getPathInfo().startsWith("/api/") && Files.exists(index)) { - writer.write(Files.readString(index)); - } else { - writer.write("<!DOCTYPE><html><head><title>Error</title></head><html><body>" - + code + " - " + HttpStatus.getMessage(code) + "</body></html>"); - } + writer.write("<!DOCTYPE><html><head><title>Error</title></head><html><body>" + + code + " - " + HttpStatus.getMessage(code) + "</body></html>"); } }); @@ -150,7 +141,7 @@ public class WebServer implements LifecycleObject { } private void initWebApp(ServletContextHandler servletHandler) { - ServletHolder servletHolder = new ServletHolder(DefaultServlet.class); + ServletHolder servletHolder = new ServletHolder(ModernDefaultServlet.class); servletHolder.setInitParameter("resourceBase", new File(config.getString(Keys.WEB_PATH)).getAbsolutePath()); servletHolder.setInitParameter("dirAllowed", "false"); if (config.getBoolean(Keys.WEB_DEBUG)) { diff --git a/src/test/java/org/traccar/handler/MotionHandlerTest.java b/src/test/java/org/traccar/handler/MotionHandlerTest.java index 2a7af23ba..10cdf6a90 100644 --- a/src/test/java/org/traccar/handler/MotionHandlerTest.java +++ b/src/test/java/org/traccar/handler/MotionHandlerTest.java @@ -1,10 +1,15 @@ package org.traccar.handler; import org.junit.jupiter.api.Test; +import org.traccar.config.Config; +import org.traccar.config.Keys; +import org.traccar.model.Device; import org.traccar.model.Position; -import org.traccar.reports.common.TripsConfig; +import org.traccar.session.cache.CacheManager; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -13,10 +18,13 @@ public class MotionHandlerTest { @Test public void testCalculateMotion() { - TripsConfig tripsConfig = mock(TripsConfig.class); - when(tripsConfig.getSpeedThreshold()).thenReturn(0.01); + var cacheManager = mock(CacheManager.class); + when(cacheManager.getObject(eq(Device.class), anyLong())).thenReturn(mock(Device.class)); + var config = mock(Config.class); + when(config.getString(Keys.EVENT_MOTION_SPEED_THRESHOLD.getKey())).thenReturn("0.01"); + when(cacheManager.getConfig()).thenReturn(config); - MotionHandler motionHandler = new MotionHandler(tripsConfig); + MotionHandler motionHandler = new MotionHandler(cacheManager); Position position = motionHandler.handlePosition(new Position()); diff --git a/src/test/java/org/traccar/handler/events/MotionEventHandlerTest.java b/src/test/java/org/traccar/handler/events/MotionEventHandlerTest.java index f2d858656..c61ae913d 100644 --- a/src/test/java/org/traccar/handler/events/MotionEventHandlerTest.java +++ b/src/test/java/org/traccar/handler/events/MotionEventHandlerTest.java @@ -36,7 +36,7 @@ public class MotionEventHandlerTest extends BaseTest { @Test public void testMotionWithPosition() throws ParseException { - TripsConfig tripsConfig = new TripsConfig(500, 300000, 300000, 0, false, false, 0.01); + TripsConfig tripsConfig = new TripsConfig(500, 300000, 300000, 0, false); MotionState state = new MotionState(); @@ -63,7 +63,7 @@ public class MotionEventHandlerTest extends BaseTest { @Test public void testMotionFluctuation() throws ParseException { - TripsConfig tripsConfig = new TripsConfig(500, 300000, 300000, 0, false, false, 0.01); + TripsConfig tripsConfig = new TripsConfig(500, 300000, 300000, 0, false); MotionState state = new MotionState(); @@ -94,7 +94,7 @@ public class MotionEventHandlerTest extends BaseTest { @Test public void testStopWithPositionIgnition() throws ParseException { - TripsConfig tripsConfig = new TripsConfig(500, 300000, 300000, 0, true, false, 0.01); + TripsConfig tripsConfig = new TripsConfig(500, 300000, 300000, 0, true); MotionState state = new MotionState(); state.setMotionStreak(true); diff --git a/src/test/java/org/traccar/protocol/HuabaoProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/HuabaoProtocolDecoderTest.java index 67f214563..2f317d049 100644 --- a/src/test/java/org/traccar/protocol/HuabaoProtocolDecoderTest.java +++ b/src/test/java/org/traccar/protocol/HuabaoProtocolDecoderTest.java @@ -15,6 +15,14 @@ public class HuabaoProtocolDecoderTest extends ProtocolTest { "7e010200204f07788ef67601824f4459344f544d314d4459774d4441314d444977626d5633553235536457786cba7e")); verifyAttribute(decoder, binary( + "7e0200006476806111898300710000000000100046005d3156065f7128000000000000230511165956660b01fe000001031a5d1a8101670831333231343332326902018b6a01166b01006c0f323034303830393230373533363735711438393434343738383030303030323131303030464b7e"), + Position.KEY_BATTERY, 3.95); + + verifyAttribute(decoder, binary( + "7e02000071768060874297002d0000000000208022015a30b006c869f8000000000000230505062034600b0004003930303235343939660b01cc0000000c40f89f27b067083133323134333232690201936a01116b01006c0f34363030383138353937303632343071143839383630343938313032313930353835373430607e"), + Position.KEY_EVENT, 4); + + verifyAttribute(decoder, binary( "7e0200002f017028775424038d000000000000000a0189dbeb04ca653a00000012014723040700074401040000000030011b31010ee1012dea020001dc7e"), Position.KEY_BATTERY_LEVEL, 45); diff --git a/src/test/java/org/traccar/reports/ReportUtilsTest.java b/src/test/java/org/traccar/reports/ReportUtilsTest.java index 09e5b3e27..22d70c93a 100644 --- a/src/test/java/org/traccar/reports/ReportUtilsTest.java +++ b/src/test/java/org/traccar/reports/ReportUtilsTest.java @@ -2,15 +2,16 @@ package org.traccar.reports; import org.apache.velocity.app.VelocityEngine; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.traccar.BaseTest; import org.traccar.api.security.PermissionsService; import org.traccar.config.Config; +import org.traccar.config.Keys; import org.traccar.helper.model.PositionUtil; import org.traccar.model.Device; import org.traccar.model.Position; import org.traccar.reports.common.ReportUtils; -import org.traccar.reports.common.TripsConfig; import org.traccar.reports.model.StopReportItem; import org.traccar.reports.model.TripReportItem; import org.traccar.storage.Storage; @@ -24,6 +25,7 @@ import java.util.Collection; import java.util.Date; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.TimeZone; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -62,6 +64,19 @@ public class ReportUtilsTest extends BaseTest { return position; } + + private Device mockDevice( + double minimalTripDistance, long minimalTripDuration, long minimalParkingDuration, + long minimalNoDataDuration, boolean useIgnition) { + Device device = mock(Device.class); + when(device.getAttributes()).thenReturn(Map.of( + Keys.REPORT_TRIP_MINIMAL_TRIP_DISTANCE.getKey(), minimalTripDistance, + Keys.REPORT_TRIP_MINIMAL_TRIP_DURATION.getKey(), minimalTripDuration, + Keys.REPORT_TRIP_MINIMAL_PARKING_DURATION.getKey(), minimalParkingDuration, + Keys.REPORT_TRIP_MINIMAL_NO_DATA_DURATION.getKey(), minimalNoDataDuration, + Keys.REPORT_TRIP_USE_IGNITION.getKey(), useIgnition)); + return device; + } @Test public void testCalculateDistance() { @@ -78,8 +93,7 @@ public class ReportUtilsTest extends BaseTest { @Test public void testCalculateSpentFuel() { ReportUtils reportUtils = new ReportUtils( - mock(Config.class), storage, mock(PermissionsService.class), - mock(TripsConfig.class), mock(VelocityEngine.class), null); + mock(Config.class), storage, mock(PermissionsService.class), mock(VelocityEngine.class), null); Position startPosition = new Position(); Position endPosition = new Position(); assertEquals(reportUtils.calculateFuel(startPosition, endPosition), 0.0, 0.01); @@ -101,12 +115,11 @@ public class ReportUtilsTest extends BaseTest { position("2016-01-01 00:15:00.000", 0, 3000), position("2016-01-01 00:25:00.000", 0, 3000)); - TripsConfig tripsConfig = new TripsConfig(500, 300000, 180000, 900000, false, false, 0.01); + Device device = mockDevice(500, 300, 180, 900, false); ReportUtils reportUtils = new ReportUtils( - mock(Config.class), storage, mock(PermissionsService.class), - tripsConfig, mock(VelocityEngine.class), null); + mock(Config.class), storage, mock(PermissionsService.class), mock(VelocityEngine.class), null); - var trips = reportUtils.detectTripsAndStops(mock(Device.class), data, false, TripReportItem.class); + var trips = reportUtils.detectTripsAndStops(device, data, false, TripReportItem.class); assertNotNull(trips); assertFalse(trips.isEmpty()); @@ -120,7 +133,7 @@ public class ReportUtilsTest extends BaseTest { assertEquals(10, itemTrip.getMaxSpeed(), 0.01); assertEquals(3000, itemTrip.getDistance(), 0.01); - var stops = reportUtils.detectTripsAndStops(mock(Device.class) ,data, false, StopReportItem.class); + var stops = reportUtils.detectTripsAndStops(device, data, false, StopReportItem.class); assertNotNull(stops); assertFalse(stops.isEmpty()); @@ -156,12 +169,11 @@ public class ReportUtilsTest extends BaseTest { data.get(5).set(Position.KEY_IGNITION, false); - TripsConfig tripsConfig = new TripsConfig(500, 300000, 180000, 900000, true, false, 0.01); + Device device = mockDevice(500, 300, 180, 900, true); ReportUtils reportUtils = new ReportUtils( - mock(Config.class), storage, mock(PermissionsService.class), - tripsConfig, mock(VelocityEngine.class), null); + mock(Config.class), storage, mock(PermissionsService.class), mock(VelocityEngine.class), null); - var trips = reportUtils.detectTripsAndStops(mock(Device.class) ,data, false, TripReportItem.class); + var trips = reportUtils.detectTripsAndStops(device, data, false, TripReportItem.class); assertNotNull(trips); assertFalse(trips.isEmpty()); @@ -175,7 +187,7 @@ public class ReportUtilsTest extends BaseTest { assertEquals(10, itemTrip.getMaxSpeed(), 0.01); assertEquals(3000, itemTrip.getDistance(), 0.01); - trips = reportUtils.detectTripsAndStops(mock(Device.class) ,data, false, TripReportItem.class); + trips = reportUtils.detectTripsAndStops(device, data, false, TripReportItem.class); assertNotNull(trips); assertFalse(trips.isEmpty()); @@ -189,7 +201,7 @@ public class ReportUtilsTest extends BaseTest { assertEquals(10, itemTrip.getMaxSpeed(), 0.01); assertEquals(3000, itemTrip.getDistance(), 0.01); - var stops = reportUtils.detectTripsAndStops(mock(Device.class) ,data, false, StopReportItem.class); + var stops = reportUtils.detectTripsAndStops(device, data, false, StopReportItem.class); assertNotNull(stops); assertFalse(stops.isEmpty()); @@ -227,12 +239,11 @@ public class ReportUtilsTest extends BaseTest { position("2016-01-01 00:19:00.000", 0, 7000), position("2016-01-01 00:29:00.000", 0, 7000)); - TripsConfig tripsConfig = new TripsConfig(500, 300000, 180000, 900000, false, false, 0.01); + Device device = mockDevice(500, 300, 180, 900, false); ReportUtils reportUtils = new ReportUtils( - mock(Config.class), storage, mock(PermissionsService.class), - tripsConfig, mock(VelocityEngine.class), null); + mock(Config.class), storage, mock(PermissionsService.class), mock(VelocityEngine.class), null); - var trips = reportUtils.detectTripsAndStops(mock(Device.class) ,data, false, TripReportItem.class); + var trips = reportUtils.detectTripsAndStops(device, data, false, TripReportItem.class); assertNotNull(trips); assertFalse(trips.isEmpty()); @@ -246,7 +257,7 @@ public class ReportUtilsTest extends BaseTest { assertEquals(10, itemTrip.getMaxSpeed(), 0.01); assertEquals(7000, itemTrip.getDistance(), 0.01); - var stops = reportUtils.detectTripsAndStops(mock(Device.class) ,data, false, StopReportItem.class); + var stops = reportUtils.detectTripsAndStops(device, data, false, StopReportItem.class); assertNotNull(stops); assertFalse(stops.isEmpty()); @@ -278,12 +289,11 @@ public class ReportUtilsTest extends BaseTest { position("2016-01-01 00:04:00.000", 1, 0), position("2016-01-01 00:05:00.000", 0, 0)); - TripsConfig tripsConfig = new TripsConfig(500, 300000, 200000, 900000, false, false, 0.01); + Device device = mockDevice(500, 300, 200, 900, false); ReportUtils reportUtils = new ReportUtils( - mock(Config.class), storage, mock(PermissionsService.class), - tripsConfig, mock(VelocityEngine.class), null); + mock(Config.class), storage, mock(PermissionsService.class), mock(VelocityEngine.class), null); - var result = reportUtils.detectTripsAndStops(mock(Device.class) ,data, false, StopReportItem.class); + var result = reportUtils.detectTripsAndStops(device, data, false, StopReportItem.class); assertNotNull(result); assertFalse(result.isEmpty()); @@ -307,12 +317,11 @@ public class ReportUtilsTest extends BaseTest { position("2016-01-01 00:04:00.000", 1, 0), position("2016-01-01 00:05:00.000", 2, 0)); - TripsConfig tripsConfig = new TripsConfig(500, 300000, 200000, 900000, false, false, 0.01); + Device device = mockDevice(500, 300, 200, 900, false); ReportUtils reportUtils = new ReportUtils( - mock(Config.class), storage, mock(PermissionsService.class), - tripsConfig, mock(VelocityEngine.class), null); + mock(Config.class), storage, mock(PermissionsService.class), mock(VelocityEngine.class), null); - var result = reportUtils.detectTripsAndStops(mock(Device.class) ,data, false, StopReportItem.class); + var result = reportUtils.detectTripsAndStops(device, data, false, StopReportItem.class); assertNotNull(result); assertFalse(result.isEmpty()); @@ -336,12 +345,11 @@ public class ReportUtilsTest extends BaseTest { position("2016-01-01 00:22:00.000", 0, 0), position("2016-01-01 00:32:00.000", 0, 0)); - TripsConfig tripsConfig = new TripsConfig(500, 300000, 200000, 900000, false, false, 0.01); + Device device = mockDevice(500, 300, 200, 900, false); ReportUtils reportUtils = new ReportUtils( - mock(Config.class), storage, mock(PermissionsService.class), - tripsConfig, mock(VelocityEngine.class), null); + mock(Config.class), storage, mock(PermissionsService.class), mock(VelocityEngine.class), null); - var result = reportUtils.detectTripsAndStops(mock(Device.class) ,data, false, StopReportItem.class); + var result = reportUtils.detectTripsAndStops(device, data, false, StopReportItem.class); assertNotNull(result); assertFalse(result.isEmpty()); @@ -365,12 +373,11 @@ public class ReportUtilsTest extends BaseTest { position("2016-01-01 00:04:00.000", 5, 0), position("2016-01-01 00:05:00.000", 5, 0)); - TripsConfig tripsConfig = new TripsConfig(500, 300000, 200000, 900000, false, false, 0.01); + Device device = mockDevice(500, 300, 200, 900, false); ReportUtils reportUtils = new ReportUtils( - mock(Config.class), storage, mock(PermissionsService.class), - tripsConfig, mock(VelocityEngine.class), null); + mock(Config.class), storage, mock(PermissionsService.class), mock(VelocityEngine.class), null); - var result = reportUtils.detectTripsAndStops(mock(Device.class) ,data, false, StopReportItem.class); + var result = reportUtils.detectTripsAndStops(device, data, false, StopReportItem.class); assertNotNull(result); assertTrue(result.isEmpty()); @@ -390,12 +397,11 @@ public class ReportUtilsTest extends BaseTest { position("2016-01-01 00:24:00.000", 5, 800), position("2016-01-01 00:25:00.000", 5, 900)); - TripsConfig tripsConfig = new TripsConfig(500, 200000, 200000, 900000, false, false, 0.01); + Device device = mockDevice(500, 200, 200, 900, false); ReportUtils reportUtils = new ReportUtils( - mock(Config.class), storage, mock(PermissionsService.class), - tripsConfig, mock(VelocityEngine.class), null); + mock(Config.class), storage, mock(PermissionsService.class), mock(VelocityEngine.class), null); - var trips = reportUtils.detectTripsAndStops(mock(Device.class) ,data, false, TripReportItem.class); + var trips = reportUtils.detectTripsAndStops(device, data, false, TripReportItem.class); assertNotNull(trips); assertFalse(trips.isEmpty()); @@ -409,7 +415,7 @@ public class ReportUtilsTest extends BaseTest { assertEquals(7, itemTrip.getMaxSpeed(), 0.01); assertEquals(600, itemTrip.getDistance(), 0.01); - var stops = reportUtils.detectTripsAndStops(mock(Device.class) ,data, false, StopReportItem.class); + var stops = reportUtils.detectTripsAndStops(device, data, false, StopReportItem.class); assertNotNull(stops); assertFalse(stops.isEmpty()); diff --git a/swagger.json b/swagger.json index 3d3842b67..889728d33 100644 --- a/swagger.json +++ b/swagger.json @@ -2799,6 +2799,9 @@ "limitCommands": { "type": "boolean" }, + "fixedEmail": { + "type": "boolean" + }, "poiLayer": { "type": "string" }, |