diff --git a/src/main/java/com/c4181/camel/CamelConfiguration.java b/src/main/java/com/c4181/camel/CamelConfiguration.java index 5a8b6c7..ce37719 100644 --- a/src/main/java/com/c4181/camel/CamelConfiguration.java +++ b/src/main/java/com/c4181/camel/CamelConfiguration.java @@ -25,6 +25,10 @@ public class CamelConfiguration extends RouteBuilder { @Override public void configure() { + errorHandler(deadLetterChannel(appProperties.deadLetterRoute()) + .log("Failed to process message. Sending to dead letter queue") + .useOriginalMessage()); + from(appProperties.jsoCadUpdateRouteIn()) .filter(exchange -> StringUtils.isNotBlank(exchange.getIn().getBody(String.class))) .process((exchange -> { diff --git a/src/main/java/com/c4181/model/JsoCallDecoder.java b/src/main/java/com/c4181/model/JsoCallDecoder.java index ff64784..d5895e1 100644 --- a/src/main/java/com/c4181/model/JsoCallDecoder.java +++ b/src/main/java/com/c4181/model/JsoCallDecoder.java @@ -17,6 +17,8 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Locale; +import java.util.regex.Matcher; +import java.util.regex.Pattern; @ApplicationScoped public class JsoCallDecoder { @@ -32,16 +34,23 @@ public class JsoCallDecoder { .map(String::trim) .toList(); + String pattern = "(\\d+)\\s+(\\d{1,2}-\\d{1,2} \\d{1,2}:\\d{1,2})\\s+(.+)\\s+( \\d+\\s?[\\w\\d]*)\\s+(\\D+)"; + Pattern p = Pattern.compile(pattern); + List jsoCalls = new ArrayList<>(); for (String call : newCalls) { + Matcher m = p.matcher(call); + if (!m.matches() && m.groupCount() != 5) { + Log.warnf("Failed to parse call\n%s", call); + continue; + } + JsoCall jsoCall = new JsoCall(); - String[] workingString = call.split(" ", 5); - workingString[4] = workingString[4].trim(); - jsoCall.setIncidentNumber(workingString[0]); - jsoCall.setDispatchedTime(parseTimeWithoutYear(workingString[2] + " " + workingString[3].trim())); - jsoCall.setAddress(workingString[4].substring(0, 61).trim()); - jsoCall.setSignal(workingString[4].substring(61, 69).trim()); - jsoCall.setCallDescription(workingString[4].substring(69).trim()); + jsoCall.setIncidentNumber(m.group(1).trim()); + jsoCall.setDispatchedTime(parseTimeWithoutYear(m.group(2).trim())); + jsoCall.setAddress(m.group(3).trim()); + jsoCall.setSignal(m.group(4).trim()); + jsoCall.setCallDescription(m.group(5).trim()); if (!jsoCall.getAddress().contains("I95") && !jsoCall.getAddress().contains("I295") && !jsoCall.getAddress().contains("I10")) { jsoCall.setPoint(geoCodeAddress(jsoCall.getAddress())); diff --git a/src/main/java/com/c4181/properties/AppProperties.java b/src/main/java/com/c4181/properties/AppProperties.java index d5e3aa6..f71d643 100644 --- a/src/main/java/com/c4181/properties/AppProperties.java +++ b/src/main/java/com/c4181/properties/AppProperties.java @@ -7,6 +7,7 @@ public interface AppProperties { String jsoCadUpdateRouteIn(); String jsoCadUpdateRouteOut(); + String deadLetterRoute(); String telegramRoute(); String googleApiKey(); double myLat(); diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index d3c06fd..7aef220 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,5 +1,6 @@ app.jso-cad-update-route-in=rabbitmq:${RABBITMQ_IP}/jso.cad.updates.to.postgres?queue=jso.cad.update.received&declare=false&vhost=jso&username=${RABBITMQ_USER}&password=${RABBITMQ_PASSWORD}&autoDelete=false app.jso-cad-update-route-out=sql:INSERT INTO calls(incident_number, dispatched_time, address, signal, call_description, point) VALUES (:#incident_number, :#dispatched_time, :#address, :#signal, :#call_description, point(:#x, :#y)) +app.dead-letter-route=rabbitmq:${RABBITMQ_IP}/failed.updates?declare=false&vhost=jso&username=${RABBITMQ_USER}&password=${RABBITMQ_PASSWORD}&autoDelete=false app.telegram-route=telegram:bots?authorizationToken=${TELEGRAM_BOT_ID}&chatId=${CHAT_ID} app.google-api-key=${GOOGLE_API_KEY} diff --git a/src/test/java/com/c4181/model/JsoCallDecoderTest.java b/src/test/java/com/c4181/model/JsoCallDecoderTest.java index 771cdad..0b300e4 100644 --- a/src/test/java/com/c4181/model/JsoCallDecoderTest.java +++ b/src/test/java/com/c4181/model/JsoCallDecoderTest.java @@ -8,6 +8,7 @@ import java.nio.file.Paths; import java.util.List; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; class JsoCallDecoderTest { @@ -19,4 +20,48 @@ class JsoCallDecoderTest { List calls = decoder.decodeJsoCallUpdates(data); assertEquals(52, calls.size()); } + + @Test + void testEdgeCaseSignals() throws IOException { + String data = Files.readString(Paths.get("src/test/resources/edge-cases.txt")); + + JsoCallDecoder decoder = new JsoCallDecoder(); + List calls = decoder.decodeJsoCallUpdates(data); + assertEquals(5, calls.size()); + + JsoCall firstCall = calls.get(0); + assertEquals("202200769492", firstCall.getIncidentNumber()); + assertNotNull(firstCall.getDispatchedTime()); + assertEquals("5200 RAMONA BLVD", firstCall.getAddress()); + assertEquals("13", firstCall.getSignal()); + assertEquals("SUSPICIOUS PERSON", firstCall.getCallDescription()); + + JsoCall secondCall = calls.get(1); + assertEquals("202200769474", secondCall.getIncidentNumber()); + assertNotNull(secondCall.getDispatchedTime()); + assertEquals("9100 MERRILL RD", secondCall.getAddress()); + assertEquals("4", secondCall.getSignal()); + assertEquals("AUTO CRASH", secondCall.getCallDescription()); + + JsoCall thirdCall = calls.get(2); + assertEquals("202200769409", thirdCall.getIncidentNumber()); + assertNotNull(thirdCall.getDispatchedTime()); + assertEquals("1000 ST CLAIR ST", thirdCall.getAddress()); + assertEquals("21CT", thirdCall.getSignal()); + assertEquals("BURGLARY CONVEYANCE TELESERVE", thirdCall.getCallDescription()); + + JsoCall fourthCall = calls.get(3); + assertEquals("202300004101", fourthCall.getIncidentNumber()); + assertNotNull(fourthCall.getDispatchedTime()); + assertEquals("12000 ATLANTIC BLVD", fourthCall.getAddress()); + assertEquals("1050", fourthCall.getSignal()); + assertEquals("TRAFFIC STOP", fourthCall.getCallDescription()); + + JsoCall fifthCall = calls.get(4); + assertEquals("202300004011", fifthCall.getIncidentNumber()); + assertNotNull(fifthCall.getDispatchedTime()); + assertEquals("BULLS BAY HWY / BEAVER ST W", fifthCall.getAddress()); + assertEquals("0 13", fifthCall.getSignal()); + assertEquals("ARMED SUSPICIOUS PERSON", fifthCall.getCallDescription()); + } } diff --git a/src/test/resources/edge-cases.txt b/src/test/resources/edge-cases.txt new file mode 100644 index 0000000..60e2a07 --- /dev/null +++ b/src/test/resources/edge-cases.txt @@ -0,0 +1,22 @@ +JSO CAD + + JACKSONVILLE SHERIFF'S OFFICE + JSO Calls for Service + + COMPLETED DISPATCHED CALLS FOR SERVICE + + Welcome to the Jacksonville Sheriff’s Office Completed Dispatched Calls for Service webpage. This page displays calls for service made to the Jacksonville Sheriff's Office that have recently been completed. The data on this page is refreshed automatically. + + This information is not intended to be used as official crime data. This program does not provide information about all crimes, and excludes specific incidents such as sexual assaults and child abuse. + + Disclaimer: The Jacksonville Sheriff's Office makes every effort to produce and publish current and accurate information. No warranties, expressed or implied, are provided for the data herein, its use, or its interpretation. The services provided are for informational purposes only and should not be relied on for any type of legal action. + +(changed) Last refreshed 12/31 12:49:22 +(into ) Last refreshed 12/31 13:45:54 + + Incident # Dispatched Block Address Signal Call Description +(added ) 202200769492 12-31 12:26 5200 RAMONA BLVD 13 SUSPICIOUS PERSON +(added ) 202200769474 12-31 12:19 9100 MERRILL RD 4 AUTO CRASH +(added ) 202200769409 12-31 11:18 1000 ST CLAIR ST 21CT BURGLARY CONVEYANCE TELESERVE +(added ) 202300004101 1-3 08:29 12000 ATLANTIC BLVD 1050 TRAFFIC STOP +(added ) 202300004011 1-3 07:37 BULLS BAY HWY / BEAVER ST W 0 13 ARMED SUSPICIOUS PERSON \ No newline at end of file