diff --git a/photon-client/src/components/settings/ObjectDetectionCard.vue b/photon-client/src/components/settings/ObjectDetectionCard.vue index 7b9a2b9877..db6e9d419e 100644 --- a/photon-client/src/components/settings/ObjectDetectionCard.vue +++ b/photon-client/src/components/settings/ObjectDetectionCard.vue @@ -86,7 +86,7 @@ const renameModel = async (model: ObjectDetectionModelProperties, newName: strin }); axiosPost("/objectdetection/rename", "rename an object detection model", { - modelPath: model.modelPath.replace("file:", ""), + modelPath: model.modelPath, newName: newName }); showRenameDialog.value.show = false; @@ -418,7 +418,7 @@ const handleBulkImport = () => { diff --git a/photon-core/src/main/java/org/photonvision/common/util/file/JacksonUtils.java b/photon-core/src/main/java/org/photonvision/common/util/file/JacksonUtils.java index d979060311..40d8e6a4ff 100644 --- a/photon-core/src/main/java/org/photonvision/common/util/file/JacksonUtils.java +++ b/photon-core/src/main/java/org/photonvision/common/util/file/JacksonUtils.java @@ -17,15 +17,9 @@ package org.photonvision.common.util.file; -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.json.JsonReadFeature; -import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.deser.std.StdDeserializer; import com.fasterxml.jackson.databind.json.JsonMapper; import com.fasterxml.jackson.databind.jsontype.BasicPolymorphicTypeValidator; @@ -37,7 +31,6 @@ import java.io.FileOutputStream; import java.io.IOException; import java.nio.file.Path; -import java.nio.file.Paths; import java.util.HashMap; import java.util.Map; import org.eclipse.jetty.io.EofException; @@ -45,89 +38,41 @@ public class JacksonUtils { public static class UIMap extends HashMap {} - // Custom Path serializer that outputs just the path string without file:/ prefix - public static class PathSerializer extends JsonSerializer { - @Override - public void serialize(Path value, JsonGenerator gen, SerializerProvider serializers) - throws IOException { - if (value == null) { - gen.writeNull(); - } else { - gen.writeString(value.toString()); - } - } - } - - // Custom Path deserializer that reads path strings - public static class PathDeserializer extends JsonDeserializer { - @Override - public Path deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { - String pathString = p.getValueAsString(); - if (pathString == null || pathString.isEmpty()) { - return null; - } - - // Handle case where old serialized data might still have file:/ prefix - if (pathString.startsWith("file:/")) { - pathString = pathString.substring(6); // Remove "file:/" prefix - } - - return Paths.get(pathString); - } - } - - // Custom Path key deserializer for Maps with Path keys - public static class PathKeyDeserializer extends com.fasterxml.jackson.databind.KeyDeserializer { - @Override - public Object deserializeKey(String key, DeserializationContext ctxt) throws IOException { - if (key == null || key.isEmpty()) { - return null; - } - - // Handle case where old serialized data might still have file:/ prefix - if (key.startsWith("file:/")) { - key = key.substring(6); // Remove "file:/" prefix - } - - return Paths.get(key); - } - } - - // Helper method to create ObjectMapper with Path serialization support - private static ObjectMapper createObjectMapperWithPathSupport(Class baseType) { - PolymorphicTypeValidator ptv = - BasicPolymorphicTypeValidator.builder().allowIfBaseType(baseType).build(); - - SimpleModule pathModule = new SimpleModule(); - pathModule.addSerializer(Path.class, new PathSerializer()); - pathModule.addDeserializer(Path.class, new PathDeserializer()); - pathModule.addKeyDeserializer(Path.class, new PathKeyDeserializer()); - - return JsonMapper.builder() - .configure(JsonReadFeature.ALLOW_JAVA_COMMENTS, true) - .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) - .activateDefaultTyping(ptv, ObjectMapper.DefaultTyping.JAVA_LANG_OBJECT) - .addModule(pathModule) - .build(); - } - public static void serialize(Path path, T object) throws IOException { serialize(path, object, true); } public static String serializeToString(T object) throws IOException { - ObjectMapper objectMapper = createObjectMapperWithPathSupport(object.getClass()); + PolymorphicTypeValidator ptv = + BasicPolymorphicTypeValidator.builder().allowIfBaseType(object.getClass()).build(); + ObjectMapper objectMapper = + JsonMapper.builder() + .activateDefaultTyping(ptv, ObjectMapper.DefaultTyping.JAVA_LANG_OBJECT) + .build(); return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(object); } public static void serialize(Path path, T object, boolean forceSync) throws IOException { - ObjectMapper objectMapper = createObjectMapperWithPathSupport(object.getClass()); + PolymorphicTypeValidator ptv = + BasicPolymorphicTypeValidator.builder().allowIfBaseType(object.getClass()).build(); + ObjectMapper objectMapper = + JsonMapper.builder() + .activateDefaultTyping(ptv, ObjectMapper.DefaultTyping.JAVA_LANG_OBJECT) + .build(); String json = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(object); saveJsonString(json, path, forceSync); } public static T deserialize(Map s, Class ref) throws IOException { - ObjectMapper objectMapper = createObjectMapperWithPathSupport(ref); + PolymorphicTypeValidator ptv = + BasicPolymorphicTypeValidator.builder().allowIfBaseType(ref).build(); + ObjectMapper objectMapper = + JsonMapper.builder() + .configure(JsonReadFeature.ALLOW_JAVA_COMMENTS, true) + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + .activateDefaultTyping(ptv, ObjectMapper.DefaultTyping.JAVA_LANG_OBJECT) + .build(); + return objectMapper.convertValue(s, ref); } @@ -136,14 +81,28 @@ public static T deserialize(String s, Class ref) throws IOException { throw new EofException("Provided empty string for class " + ref.getName()); } - ObjectMapper objectMapper = createObjectMapperWithPathSupport(ref); - objectMapper.enable(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL); + PolymorphicTypeValidator ptv = + BasicPolymorphicTypeValidator.builder().allowIfBaseType(ref).build(); + ObjectMapper objectMapper = + JsonMapper.builder() + .configure(JsonReadFeature.ALLOW_JAVA_COMMENTS, true) + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + .enable(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL) + .activateDefaultTyping(ptv, ObjectMapper.DefaultTyping.JAVA_LANG_OBJECT) + .build(); return objectMapper.readValue(s, ref); } public static T deserialize(Path path, Class ref) throws IOException { - ObjectMapper objectMapper = createObjectMapperWithPathSupport(ref); + PolymorphicTypeValidator ptv = + BasicPolymorphicTypeValidator.builder().allowIfBaseType(ref).build(); + ObjectMapper objectMapper = + JsonMapper.builder() + .configure(JsonReadFeature.ALLOW_JAVA_COMMENTS, true) + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + .activateDefaultTyping(ptv, ObjectMapper.DefaultTyping.JAVA_LANG_OBJECT) + .build(); File jsonFile = new File(path.toString()); if (jsonFile.exists() && jsonFile.length() > 0) { return objectMapper.readValue(jsonFile, ref); @@ -156,12 +115,6 @@ public static T deserialize(Path path, Class ref, StdDeserializer dese ObjectMapper objectMapper = new ObjectMapper(); SimpleModule module = new SimpleModule(); module.addDeserializer(ref, deserializer); - - // Add Path support to custom deserializer case as well - module.addSerializer(Path.class, new PathSerializer()); - module.addDeserializer(Path.class, new PathDeserializer()); - module.addKeyDeserializer(Path.class, new PathKeyDeserializer()); - objectMapper.registerModule(module); File jsonFile = new File(path.toString()); @@ -182,12 +135,6 @@ public static void serialize( ObjectMapper objectMapper = new ObjectMapper(); SimpleModule module = new SimpleModule(); module.addSerializer(ref, serializer); - - // Add Path support to custom serializer case as well - module.addSerializer(Path.class, new PathSerializer()); - module.addDeserializer(Path.class, new PathDeserializer()); - module.addKeyDeserializer(Path.class, new PathKeyDeserializer()); - objectMapper.registerModule(module); String json = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(object); saveJsonString(json, path, forceSync); diff --git a/photon-server/src/main/java/org/photonvision/server/RequestHandler.java b/photon-server/src/main/java/org/photonvision/server/RequestHandler.java index b655cb6566..a462154625 100644 --- a/photon-server/src/main/java/org/photonvision/server/RequestHandler.java +++ b/photon-server/src/main/java/org/photonvision/server/RequestHandler.java @@ -850,33 +850,30 @@ public static void onBulkImportObjectDetectionModelRequest(Context ctx) { } } - private record DeleteObjectDetectionModelRequest(String modelPath) {} + private record DeleteObjectDetectionModelRequest(Path modelPath) {} public static void onDeleteObjectDetectionModelRequest(Context ctx) { logger.info("Deleting object detection model"); - Path modelPath; try { DeleteObjectDetectionModelRequest request = JacksonUtils.deserialize(ctx.body(), DeleteObjectDetectionModelRequest.class); - modelPath = Path.of(request.modelPath.substring(7)); - - if (modelPath == null) { + if (request.modelPath == null) { ctx.status(400); ctx.result("The provided model path was malformed"); logger.error("The provided model path was malformed"); return; } - if (!modelPath.toFile().exists()) { + if (!request.modelPath.toFile().exists()) { ctx.status(400); ctx.result("The provided model path does not exist"); logger.error("The provided model path does not exist"); return; } - if (!modelPath.toFile().delete()) { + if (!request.modelPath.toFile().delete()) { ctx.status(500); ctx.result("Unable to delete the model file"); logger.error("Unable to delete the model file"); @@ -886,7 +883,7 @@ public static void onDeleteObjectDetectionModelRequest(Context ctx) { if (!ConfigManager.getInstance() .getConfig() .neuralNetworkPropertyManager() - .removeModel(modelPath)) { + .removeModel(request.modelPath)) { ctx.status(400); ctx.result("The model's information was not found in the config"); logger.error("The model's information was not found in the config"); @@ -910,26 +907,24 @@ public static void onDeleteObjectDetectionModelRequest(Context ctx) { UIPhotonConfiguration.programStateToUi(ConfigManager.getInstance().getConfig()))); } - private record RenameObjectDetectionModelRequest(String modelPath, String newName) {} + private record RenameObjectDetectionModelRequest(Path modelPath, String newName) {} public static void onRenameObjectDetectionModelRequest(Context ctx) { try { RenameObjectDetectionModelRequest request = JacksonUtils.deserialize(ctx.body(), RenameObjectDetectionModelRequest.class); - Path modelPath = Path.of(request.modelPath); - - if (modelPath == null) { + if (request.modelPath == null) { ctx.status(400); ctx.result("The provided model path was malformed"); logger.error("The provided model path was malformed"); return; } - if (!modelPath.toFile().exists()) { + if (!request.modelPath.toFile().exists()) { ctx.status(400); ctx.result("The provided model path does not exist"); - logger.error("The model path: " + modelPath + " does not exist"); + logger.error("The model path: " + request.modelPath + " does not exist"); return; } @@ -943,7 +938,7 @@ public static void onRenameObjectDetectionModelRequest(Context ctx) { if (!ConfigManager.getInstance() .getConfig() .neuralNetworkPropertyManager() - .renameModel(modelPath, request.newName)) { + .renameModel(request.modelPath, request.newName)) { ctx.status(400); ctx.result("The model's information was not found in the config"); logger.error("The model's information was not found in the config");