org.apache.cxf
@@ -128,16 +115,13 @@
${project.build.directory}/generated-sources/cxf
- target/classes/wsdl/WebServices/dev/ECF-4.0-FilingReviewMDEService.wsdl
-
-
- target/classes/wsdl/WebServices/dev/ECF-4.0-CourtRecordMDEService.wsdl
+ src/main/resources/wsdl/WebServices/dev/ECF-4.0-FilingReviewMDEService.wsdl
- target/classes/wsdl/WebServices/dev/ECF-4.0-ServiceMDEService.wsdl
+ src/main/resources/wsdl/WebServices/dev/ECF-4.0-CourtRecordMDEService.wsdl
- target/classes/wsdl/WebServices/dev/ECF-4.0-FilingAssemblyMDEService.wsdl
+ src/main/resources/wsdl/WebServices/dev/ECF-4.0-FilingAssemblyMDEService.wsdl
-Djavax.xml.accessExternalDTD=all
diff --git a/proxyserver/src/main/java/edu/suffolk/litlab/efsp/server/ecf4/Ecf4Helper.java b/TylerEcf4/src/main/java/edu/suffolk/litlab/efsp/tyler/Ecf4Helper.java
similarity index 85%
rename from proxyserver/src/main/java/edu/suffolk/litlab/efsp/server/ecf4/Ecf4Helper.java
rename to TylerEcf4/src/main/java/edu/suffolk/litlab/efsp/tyler/Ecf4Helper.java
index 4671c59f1..fdcd6c42a 100644
--- a/proxyserver/src/main/java/edu/suffolk/litlab/efsp/server/ecf4/Ecf4Helper.java
+++ b/TylerEcf4/src/main/java/edu/suffolk/litlab/efsp/tyler/Ecf4Helper.java
@@ -1,4 +1,21 @@
-package edu.suffolk.litlab.efsp.server.ecf4;
+package edu.suffolk.litlab.efsp.tyler;
+
+import java.io.File;
+import java.io.StringWriter;
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.util.GregorianCalendar;
+import java.util.List;
+import java.util.Optional;
+
+import javax.xml.datatype.DatatypeConfigurationException;
+import javax.xml.datatype.DatatypeConstants;
+import javax.xml.datatype.DatatypeFactory;
+import javax.xml.datatype.XMLGregorianCalendar;
+import javax.xml.namespace.QName;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import ecf4.latest.gov.niem.niem.domains.jxdm._4.CourtType;
import ecf4.latest.gov.niem.niem.fips_10_4._2.CountryCodeType;
@@ -11,51 +28,27 @@
import ecf4.latest.oasis.names.tc.legalxml_courtfiling.schema.xsd.commontypes_4.CaseFilingType;
import ecf4.latest.oasis.names.tc.legalxml_courtfiling.schema.xsd.commontypes_4.PersonType;
import ecf4.latest.oasis.names.tc.legalxml_courtfiling.schema.xsd.commontypes_4.QueryMessageType;
-import ecf4.latest.oasis.names.tc.legalxml_courtfiling.schema.xsd.commontypes_4.QueryResponseMessageType;
-import edu.suffolk.litlab.efsp.server.utils.ServiceHelpers;
-import edu.suffolk.litlab.efsp.tyler.TylerErrorCodes;
-import jakarta.ws.rs.core.Response;
import jakarta.xml.bind.JAXBContext;
import jakarta.xml.bind.JAXBElement;
import jakarta.xml.bind.JAXBException;
import jakarta.xml.bind.Marshaller;
-import java.io.File;
-import java.io.StringWriter;
-import java.math.BigDecimal;
-import java.time.LocalDate;
-import java.util.GregorianCalendar;
-import java.util.List;
-import java.util.Optional;
-import java.util.function.Supplier;
-import javax.xml.datatype.DatatypeConfigurationException;
-import javax.xml.datatype.DatatypeConstants;
-import javax.xml.datatype.DatatypeFactory;
-import javax.xml.datatype.XMLGregorianCalendar;
-import javax.xml.namespace.QName;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-/**
- * Helper class that easily converts Java native types to Cumbersome XML Schema types, particularly
- * those from Oasis's ECF standard, and the National Information Exchange Model (NIEM).
- *
- * Some information about NIEM: Many of the types here all include the
- * "SimpleObjectAttributeGroup", which, "provides a collection of attributes which are appropriate
- * for definition of object types", including an ID attribute, a Metadata attribute, and a Link
- * Metadata attribute that directly reference objects via IDs instead of including the object as an
- * element. TBH, I'm not sure what the design decisions that dictate the use of them are, but they
- * exist.
- *
- * @author Bryce Willey
- */
public class Ecf4Helper {
private static final Logger log = LoggerFactory.getLogger(Ecf4Helper.class);
-
+
static final ecf4.latest.gov.niem.niem.proxy.xsd._2.ObjectFactory niemProxyObjFac;
static final ecf4.latest.gov.niem.niem.niem_core._2.ObjectFactory niemCoreObjFac;
static final ecf4.latest.gov.niem.niem.domains.jxdm._4.ObjectFactory jxObjFac;
static final DatatypeFactory datatypeFac;
+ /**
+ * One of the ways that you can communicate over ECF. For more information, see
+ * https://docs.oasis-open.org/legalxml-courtfiling/specs/ecf/v4.01/ecf-v4.01-spec/errata02/os/ecf-v4.01-spec-errata02-os-complete.html#_Toc425241629
+ */
+ public static final String MDE_PROFILE_CODE =
+ "urn:oasis:names:tc:legalxml-courtfiling:schema:xsd:WebServicesMessaging-2.0";
+
+
static {
niemProxyObjFac = new ecf4.latest.gov.niem.niem.proxy.xsd._2.ObjectFactory();
niemCoreObjFac = new ecf4.latest.gov.niem.niem.niem_core._2.ObjectFactory();
@@ -83,6 +76,11 @@ public static DateType convertDate(LocalDate date) {
dt.setDateRepresentation(niemCoreObjFac.createDate(d));
return dt;
}
+
+ public static LocalDate parseDate(DateType dt) {
+ var d = ((ecf4.latest.gov.niem.niem.proxy.xsd._2.Date) dt.getDateRepresentation().getValue()).getValue();
+ return LocalDate.of(d.getYear(), d.getMonth(), d.getDay());
+ }
/** Niem's Boolean type: either true or false. */
public static ecf4.latest.gov.niem.niem.proxy.xsd._2.Boolean convertBool(boolean bool) {
@@ -293,7 +291,7 @@ public static CountryCodeType strToCountryCode(String country) {
return cct;
}
- public static T prep(T newMsg, String courtId) {
+ public static T prep(T newMsg, String courtId, String locationId) {
EntityType typ = new EntityType();
var commonObjFac =
new ecf4.latest.oasis.names.tc.legalxml_courtfiling.schema.xsd.commontypes_4
@@ -305,16 +303,11 @@ public static T prep(T newMsg, String courtId) {
// Example in the ECF4 docs is "https://filingreviewmde.com", which matches best to
// EXTERNAL_URL.
// Doesn't seem to be used by Tyler however.
- newMsg.setSendingMDELocationID(Ecf4Helper.convertId(ServiceHelpers.EXTERNAL_URL));
- newMsg.setSendingMDEProfileCode(ServiceHelpers.MDE_PROFILE_CODE);
+ newMsg.setSendingMDELocationID(Ecf4Helper.convertId(locationId));
+ newMsg.setSendingMDEProfileCode(MDE_PROFILE_CODE);
return newMsg;
}
- public static Response makeResponse(
- QueryResponseMessageType resp, Supplier defaultRespFunc) {
- return mapTylerCodesToHttp(checkErrors(resp.getError()), defaultRespFunc);
- }
-
public record Error(String code, String text) {}
/**
@@ -337,23 +330,8 @@ public static List checkErrors(
return errors.stream().flatMap(err -> checkError(err).stream()).toList();
}
- public static Response mapTylerCodesToHttp(List err, Supplier defaultResp) {
- if (!err.isEmpty()) {
- var mainErr = err.getFirst();
- if (TylerErrorCodes.tylerToHttp.containsKey(mainErr.code)) {
- return Response.status(TylerErrorCodes.tylerToHttp.get(mainErr.code))
- .entity(mainErr.text)
- .build();
- }
-
- // 422 as semantic issues covers most of the error codes
- return Response.status(422).entity(mainErr.text).build();
- }
- return defaultResp.get();
- }
-
- public static void setupReplys(CaseFilingType reply) {
- reply.setSendingMDELocationID(Ecf4Helper.convertId(ServiceHelpers.SERVICE_URL));
- reply.setSendingMDEProfileCode(ServiceHelpers.MDE_PROFILE_CODE);
+ public static void setupReplys(CaseFilingType reply, String locationId) {
+ reply.setSendingMDELocationID(Ecf4Helper.convertId(locationId));
+ reply.setSendingMDEProfileCode(MDE_PROFILE_CODE);
}
}
diff --git a/TylerEfmClient/.gitignore b/TylerEfmClient/.gitignore
new file mode 100644
index 000000000..00d2ab71d
--- /dev/null
+++ b/TylerEfmClient/.gitignore
@@ -0,0 +1,2 @@
+/.apt_generated/
+/.apt_generated_tests/
diff --git a/TylerEfmClient/pom.xml b/TylerEfmClient/pom.xml
index 5f022b1b5..e16e5fa7e 100644
--- a/TylerEfmClient/pom.xml
+++ b/TylerEfmClient/pom.xml
@@ -7,13 +7,14 @@
efspserver-parent
1.4.0
- edu.suffolk.litlab
tyler-efm-client
Tyler EFM Services Client code
For Tyler's EFMUserService and EFMFirmService. Lets you choose the specific version that you want to interact with.
4.1.6
+
+ jdt_apt
diff --git a/pom.xml b/pom.xml
index 934112795..b272cc86f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -14,6 +14,7 @@
TylerEfmClient
TylerEcf4
TylerEcf5
+ TrueFilingEcf4
proxyserver
@@ -74,6 +75,11 @@
tyler-ecf5
${project.version}
+
+ edu.suffolk.litlab
+ truefiling-ecf4
+ ${project.version}
+
org.mapstruct
@@ -108,6 +114,7 @@
docs
+ site
enunciate.xml
${project.build.directory}/enunciate-docs
diff --git a/proxyserver/pom.xml b/proxyserver/pom.xml
index 75fb37f82..94a8abea0 100644
--- a/proxyserver/pom.xml
+++ b/proxyserver/pom.xml
@@ -53,6 +53,10 @@
edu.suffolk.litlab
tyler-ecf5
+
+ edu.suffolk.litlab
+ truefiling-ecf4
+
org.apache.commons
commons-lang3
diff --git a/proxyserver/src/main/java/edu/suffolk/litlab/efsp/ecf4/EcfCaseTypes.java b/proxyserver/src/main/java/edu/suffolk/litlab/efsp/ecf4/EcfCaseTypes.java
new file mode 100644
index 000000000..393ff046c
--- /dev/null
+++ b/proxyserver/src/main/java/edu/suffolk/litlab/efsp/ecf4/EcfCaseTypes.java
@@ -0,0 +1,17 @@
+package edu.suffolk.litlab.efsp.ecf4;
+
+/**
+ * Straight from ECF4 Spec:
+ * https://docs.oasis-open.org/legalxml-courtfiling/specs/ecf/v4.01/ecf-v4.01-spec/os/ecf-v4.01-spec-os.html#_Toc343512773
+ *
+ * Enumerated in a few other places, but none of the XML has a straight enum to use.
+ */
+public enum EcfCaseTypes {
+ AppellateCase,
+ BankruptcyCase,
+ CitationCase,
+ CivilCase,
+ CriminalCase,
+ DomesticCase,
+ JuvenileCase;
+}
diff --git a/proxyserver/src/main/java/edu/suffolk/litlab/efsp/ecfcodes/CodeDatabaseAPI.java b/proxyserver/src/main/java/edu/suffolk/litlab/efsp/ecfcodes/CodeDatabaseAPI.java
index 76f038840..41507c8b4 100644
--- a/proxyserver/src/main/java/edu/suffolk/litlab/efsp/ecfcodes/CodeDatabaseAPI.java
+++ b/proxyserver/src/main/java/edu/suffolk/litlab/efsp/ecfcodes/CodeDatabaseAPI.java
@@ -14,6 +14,8 @@
import java.util.List;
import java.util.Map;
import java.util.Optional;
+import java.util.Set;
+
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -40,6 +42,8 @@ public CodeDatabaseAPI(Connection conn) {
* Maps the name of an ECF element to be substituted by a court-specific code list or extension.
*/
public abstract Map xmlElemToTableName();
+
+ public abstract Set systemTables();
/**
* Gets all court location identifiers (CLI) stored in the database.
diff --git a/proxyserver/src/main/java/edu/suffolk/litlab/efsp/ecfcodes/CodeDatabaseUtils.java b/proxyserver/src/main/java/edu/suffolk/litlab/efsp/ecfcodes/CodeDatabaseUtils.java
index c79ccb30d..136b77e1b 100644
--- a/proxyserver/src/main/java/edu/suffolk/litlab/efsp/ecfcodes/CodeDatabaseUtils.java
+++ b/proxyserver/src/main/java/edu/suffolk/litlab/efsp/ecfcodes/CodeDatabaseUtils.java
@@ -10,7 +10,9 @@
public class CodeDatabaseUtils {
/** This exception is returned when a given table name isn't in the pre-approved list of names. */
public static class UnsupportedTableException extends SQLException {
- public UnsupportedTableException(String message) {
+ private static final long serialVersionUID = 42L;
+
+ public UnsupportedTableException(String message) {
super(message);
}
}
diff --git a/proxyserver/src/main/java/edu/suffolk/litlab/efsp/ecfcodes/CodeDocException.java b/proxyserver/src/main/java/edu/suffolk/litlab/efsp/ecfcodes/CodeDocException.java
index 77ce9888c..ef52857ad 100644
--- a/proxyserver/src/main/java/edu/suffolk/litlab/efsp/ecfcodes/CodeDocException.java
+++ b/proxyserver/src/main/java/edu/suffolk/litlab/efsp/ecfcodes/CodeDocException.java
@@ -7,6 +7,8 @@
* (instead of an ops issue).
*/
public class CodeDocException extends Exception {
+ private static final long serialVersionUID = 1L;
+
public CodeDocException(String message, Throwable ex) {
super(message, ex);
}
diff --git a/proxyserver/src/main/java/edu/suffolk/litlab/efsp/tyler/ecfcodes/CodeUpdater.java b/proxyserver/src/main/java/edu/suffolk/litlab/efsp/ecfcodes/CodeUpdater.java
similarity index 62%
rename from proxyserver/src/main/java/edu/suffolk/litlab/efsp/tyler/ecfcodes/CodeUpdater.java
rename to proxyserver/src/main/java/edu/suffolk/litlab/efsp/ecfcodes/CodeUpdater.java
index f95a8b98c..f0f13581d 100644
--- a/proxyserver/src/main/java/edu/suffolk/litlab/efsp/tyler/ecfcodes/CodeUpdater.java
+++ b/proxyserver/src/main/java/edu/suffolk/litlab/efsp/ecfcodes/CodeUpdater.java
@@ -1,26 +1,12 @@
-package edu.suffolk.litlab.efsp.tyler.ecfcodes;
+package edu.suffolk.litlab.efsp.ecfcodes;
-import ecf4.latest.oasis.names.tc.legalxml_courtfiling.schema.xsd.courtpolicyquerymessage_4.CourtPolicyQueryMessageType;
-import ecf4.latest.oasis.names.tc.legalxml_courtfiling.schema.xsd.courtpolicyresponsemessage_4.CourtPolicyResponseMessageType;
-import ecf4.latest.oasis.names.tc.legalxml_courtfiling.wsdl.webservicesprofile_definitions_4_0.FilingReviewMDEPort;
-import ecf4.latest.tyler.efm.wsdl.webservicesprofile_implementation_4_0.FilingReviewMDEService;
import edu.suffolk.litlab.efsp.Jurisdiction;
import edu.suffolk.litlab.efsp.db.DatabaseCreator;
-import edu.suffolk.litlab.efsp.ecfcodes.CodeDatabaseAPI;
import edu.suffolk.litlab.efsp.ecfcodes.CodeDatabaseUtils.UnsupportedTableException;
-import edu.suffolk.litlab.efsp.server.ecf4.Ecf4Helper;
import edu.suffolk.litlab.efsp.server.logging.MDCWrappers;
import edu.suffolk.litlab.efsp.server.utils.HeaderSigner;
-import edu.suffolk.litlab.efsp.server.utils.ServiceHelpers;
-import edu.suffolk.litlab.efsp.server.utils.SoapX509CallbackHandler;
-import edu.suffolk.litlab.efsp.tyler.SoapClientChooser;
-import edu.suffolk.litlab.efsp.tyler.TylerClients;
-import edu.suffolk.litlab.efsp.tyler.TylerUserClient;
-import edu.suffolk.litlab.efsp.tyler.TylerUserFactory;
-import edu.suffolk.litlab.efsp.tyler.TylerUserNamePassword;
+import edu.suffolk.litlab.efsp.tyler.ecfcodes.CodeDatabase;
import jakarta.xml.bind.JAXBException;
-import jakarta.xml.ws.BindingProvider;
-import jakarta.xml.ws.soap.SOAPFaultException;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
@@ -33,6 +19,7 @@
import java.sql.Savepoint;
import java.time.Duration;
import java.time.Instant;
+import java.time.LocalDate;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -40,24 +27,21 @@
import java.util.Map.Entry;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.zip.ZipInputStream;
import javax.sql.DataSource;
-import org.apache.cxf.headers.Header;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
-import tyler.efm.latest.services.schema.authenticaterequest.AuthenticateRequestType;
-import tyler.efm.latest.services.schema.authenticateresponse.AuthenticateResponseType;
/**
* Updates the Tyler "Codes" for each court in a jurisdiction.
*
- * There are 3 main steps to download a code for a Tyler court: 1. calling `getPolicy` (see
- * {@link FilingReviewMDEPort}) for a court to get the URLs that the codes are available at. - this
+ *
There are 3 main steps to download a code for a Tyler court: 1. calling `FilingReview::getPolicy` for a court to get the URLs that the codes are available at. - this
* URL can change, but tbh doesn't change often. It takes approximately 1 for a call to getPolicy to
* complete. 2. downloading the codes, which are zip files available at the above URL. - You need
* the court itself, the table that you are downloading. Currently, we take the input stream and
@@ -119,10 +103,11 @@ public static InputStream getCodesZip(String toRead, String authHeader)
* @throws URISyntaxException
*/
private boolean downloadAndProcessZip(
- String toRead, String signedTime, Function process)
+ Supplier urlStreamMaker, Function process)
throws URISyntaxException {
+ //String toRead, String signedTime,
Instant startTable = Instant.now();
- try (InputStream urlStream = getCodesZip(toRead, signedTime)) {
+ try (InputStream urlStream = urlStreamMaker.get()) { //getCodesZip(toRead, signedTime)) {
// Write out the zip file
downloadDuration = downloadDuration.plus(Duration.between(startTable, Instant.now()));
@@ -137,27 +122,18 @@ private boolean downloadAndProcessZip(
} catch (IOException ex) {
// Some system codes (everything but "country", "state", "filingstatus", "datafieldconfig",
// and "servicetype") are expected to 500. Not really sure why they give us bad URLs.
- log.warn("Skipping {}, got exception accessing zip: ", toRead, ex);
+ log.warn("Skipping, got exception accessing zip: ", ex);
return false;
}
}
- private boolean downloadSystemTables(String baseUrl, CodeDatabaseAPI cd, HeaderSigner signer)
+ private boolean downloadSystemTables(Map> codelistUrls, CodeDatabaseAPI cd, HeaderSigner signer)
throws SQLException, IOException, JAXBException, URISyntaxException {
MDC.put(MDCWrappers.SESSION_ID, "system");
- Map codeUrls =
- Map.of(
- "version",
- "/CodeService/codes/version/",
- "location",
- "/CodeService/codes/location/",
- // NOTE: the Tyler docs say "error" is available from `GetPolicy'. That is wrong.
- "error",
- "/CodeService/codes/error");
-
- for (Map.Entry urlSuffix : codeUrls.entrySet()) {
- MDC.put(MDCWrappers.REQUEST_ID, urlSuffix.getKey());
- cd.createTableIfAbsent(urlSuffix.getKey());
+
+ for (String tableName: codelistUrls.keySet()) {
+ MDC.put(MDCWrappers.REQUEST_ID, tableName);
+ cd.createTableIfAbsent(tableName);
}
MDC.put(MDCWrappers.REQUEST_ID, "installedversion");
@@ -176,16 +152,18 @@ private boolean downloadSystemTables(String baseUrl, CodeDatabaseAPI cd, HeaderS
return false;
}
- for (Map.Entry urlSuffix : codeUrls.entrySet()) {
+ for (var tableAndUrl : codelistUrls.entrySet()) {
// Let SQL exceptions through here; table names are hard coded, so if they break
// we need to know.
- cd.deleteFromTable(urlSuffix.getKey());
+ var tableName = tableAndUrl.getKey();
+ var urlGetter = tableAndUrl.getValue();
+ cd.deleteFromTable(tableName);
final Function process =
(is) -> {
- MDC.put(MDCWrappers.REQUEST_ID, urlSuffix.getKey());
+ MDC.put(MDCWrappers.REQUEST_ID, tableName);
try {
// "0" is used as the system court location.
- cd.updateTable(urlSuffix.getKey(), "0", is);
+ cd.updateTable(tableName, "0", is);
return true;
} catch (final Exception e1) {
log.error("Error when updating table", e1);
@@ -193,7 +171,7 @@ private boolean downloadSystemTables(String baseUrl, CodeDatabaseAPI cd, HeaderS
}
};
boolean updateSuccess =
- downloadAndProcessZip(baseUrl + urlSuffix.getValue(), signedTime.get(), process);
+ downloadAndProcessZip(urlGetter, signedTime.get(), process);
MDC.remove(MDCWrappers.REQUEST_ID);
if (!updateSuccess) {
cd.rollback(sp);
@@ -209,15 +187,7 @@ private boolean downloadSystemTables(String baseUrl, CodeDatabaseAPI cd, HeaderS
* Internal class, meant to pass around information between the "getPolicy URL" stage and the
* "download zip" stage.
*/
- private static class CodeToDownload {
- final String tableName;
- final String url;
-
- public CodeToDownload(String tableName, String url) {
- this.tableName = tableName;
- this.url = url;
- }
- }
+ private static record CodeToDownload(String tableName, String url) {}
/**
* Internal class, meant to pass around information between the "download zip" stage and the
@@ -255,26 +225,20 @@ private static Map streamDownload(
return codeLists;
}
- private static Map streamPolicies(
- Stream locations, Jurisdiction jurisdiction, FilingReviewMDEPort filingPort) {
- var policies = new ConcurrentHashMap();
+ private static Map> streamPolicies(
+ Stream locations, Jurisdiction jurisdiction, Function> courtCodeGetter) {
+ var policies = new ConcurrentHashMap>();
locations.forEach(
- location -> {
- var m = Ecf4Helper.prep(new CourtPolicyQueryMessageType(), location);
- try {
- CourtPolicyResponseMessageType p = filingPort.getPolicy(m);
- policies.put(location, p);
- } catch (SOAPFaultException ex) {
- log.warn(
- "Got a SOAP excption getting policy for {} in {}: ", location, jurisdiction, ex);
- }
+ location -> {
+ policies.put(location, courtCodeGetter.apply(location));
});
return policies;
}
- private String makeCodeUrl(String baseUrl, String tableName, String location) {
- return baseUrl + "CodeService/codes/" + tableName + "/" + location.replace(" ", "%20");
- }
+
+ // An agnostic type for CourtCodelistType
+ public record CourtCodelistInfo(String ecfElement, String uri, LocalDate effectiveDate) {}
+
/**
* @param tables If empty, all versions will be downloaded
@@ -284,8 +248,7 @@ private boolean downloadCourtTables(
Optional> tables,
CodeDatabaseAPI cd,
HeaderSigner signer,
- CourtPolicyResponseMessageType policyResp,
- String baseUrl)
+ List policyResp)
throws JAXBException, IOException, SQLException {
MDC.put(MDCWrappers.SESSION_ID, location);
log.info("Doing updates for: {},\ttables: {}", location, tables);
@@ -293,16 +256,12 @@ private boolean downloadCourtTables(
// TODO(brycew-later): check that the effective date is later than today
// JAXBElement> obj = ccl.getEffectiveDate().getDateRepresentation();
Map urlMap =
- policyResp.getRuntimePolicyParameters().getCourtCodelist().stream()
+ policyResp.stream()
.collect(
Collectors.toMap(
- (cc1) -> cd.xmlElemToTableName().get(cc1.getECFElementName().getValue()),
+ (cc1) -> cd.xmlElemToTableName().get(cc1.ecfElement()),
// Tyler gives us URLs w/ spaces, which aren't valid. This makes them valid
- (cc1) ->
- cc1.getCourtCodelistURI()
- .getIdentificationID()
- .getValue()
- .replace(" ", "%20")));
+ (cc1) -> cc1.uri().replace(" ", "%20")));
Stream toDownload;
if (tables.isEmpty()) {
toDownload =
@@ -310,11 +269,8 @@ private boolean downloadCourtTables(
} else {
toDownload =
tables.get().stream()
- .map(
- tableName -> {
- String guessUrl = makeCodeUrl(baseUrl, tableName, location);
- return new CodeToDownload(tableName, urlMap.getOrDefault(tableName, guessUrl));
- });
+ .filter(tableName -> urlMap.containsKey(tableName))
+ .map(tableName -> new CodeToDownload(tableName, urlMap.get(tableName)));
}
Optional signedTime = signer.signedCurrentTime();
if (signedTime.isEmpty()) {
@@ -394,11 +350,11 @@ private Map> makeOrRemoveUnsupportedTables(
*
* @throws URISyntaxException
*/
- public boolean updateAll(String baseUrl, FilingReviewMDEPort filingPort, CodeDatabaseAPI cd)
+ public boolean updateAll(Map systemUrls, Function> codeInfoGetter, CodeUrlGetter urlGetter, CodeDatabaseAPI cd)
throws SQLException, IOException, JAXBException, URISyntaxException {
cd.setAutoCommit(false);
HeaderSigner signer = new HeaderSigner(this.pathToKeystore, this.x509Password);
- if (!downloadSystemTables(baseUrl, cd, signer)) {
+ if (!downloadSystemTables(systemUrls, cd, signer)) {
log.warn(
"System tables didn't update, but we needed them "
+ " to actually figure out new versions");
@@ -434,9 +390,9 @@ public boolean updateAll(String baseUrl, FilingReviewMDEPort filingPort, CodeDat
}
log.info("Took {} to remove existing tables", Duration.between(startDel, Instant.now()));
Instant startPolicy = Instant.now();
- Map policies =
+ Map> policies =
streamPolicies(
- versionsToUpdate.keySet().stream().parallel(), cd.getJurisdiction(), filingPort);
+ versionsToUpdate.keySet().stream().parallel(), cd.getJurisdiction(), codeInfoGetter);
var soapInc = Duration.between(startPolicy, Instant.now());
soapDuration = soapDuration.plus(soapInc);
log.info("Soaps took: {} (total: {})", soapInc, soapDuration);
@@ -445,7 +401,7 @@ public boolean updateAll(String baseUrl, FilingReviewMDEPort filingPort, CodeDat
final String courtLocation = policy.getKey();
final List tables = versionsToUpdate.get(courtLocation);
if (!downloadCourtTables(
- courtLocation, Optional.of(tables), cd, signer, policy.getValue(), baseUrl)) {
+ courtLocation, Optional.of(tables), cd, signer, policy.getValue())) {
log.warn("Failed updating court {}'s tables {}", courtLocation, tables);
cd.rollback(sp);
return false;
@@ -462,18 +418,18 @@ public boolean updateAll(String baseUrl, FilingReviewMDEPort filingPort, CodeDat
*
* @throws URISyntaxException
*/
- public boolean replaceAll(String baseUrl, FilingReviewMDEPort filingPort, CodeDatabaseAPI cd)
+ public boolean replaceAll(Map systemUrls, Function> codeInfoGetter, CodeDatabaseAPI cd)
throws SQLException, IOException, JAXBException, URISyntaxException {
- return replaceSome(baseUrl, filingPort, cd, List.of());
+ return replaceSome(systemUrls, codeInfoGetter, cd, List.of());
}
public boolean replaceSome(
- String baseUrl, FilingReviewMDEPort filingPort, CodeDatabaseAPI cd, List locs)
+ Map systemUrls, Function> codeInfoGetter, CodeDatabaseAPI cd, List locs)
throws SQLException, IOException, JAXBException, URISyntaxException {
cd.setAutoCommit(false);
HeaderSigner signer = new HeaderSigner(this.pathToKeystore, this.x509Password);
log.info("Downloading system tables for {}", cd.getJurisdiction());
- boolean success = downloadSystemTables(baseUrl, cd, signer);
+ boolean success = downloadSystemTables(systemUrls, cd, signer);
var tablesToDeleteDomain = cd.xmlElemToTableName().values();
for (String table : tablesToDeleteDomain) {
@@ -491,15 +447,14 @@ public boolean replaceSome(
// Remove the "0" or top level court, which doesn't usually have individual court tables
locs.remove("0");
Instant startPolicy = Instant.now();
- Map policies =
- streamPolicies(locs.parallelStream(), cd.getJurisdiction(), filingPort);
+ var policies = streamPolicies(locs.parallelStream(), cd.getJurisdiction(), codeInfoGetter);
soapDuration = soapDuration.plus(Duration.between(startPolicy, Instant.now()));
log.info("Soaps: {}", soapDuration);
for (var policy : policies.entrySet()) {
final String location = policy.getKey();
log.info("Downloading tables for {}", location);
success &=
- downloadCourtTables(location, Optional.empty(), cd, signer, policy.getValue(), baseUrl);
+ downloadCourtTables(location, Optional.empty(), cd, signer, policy.getValue());
}
log.info(
"Downloads took: {}, updates took: {}, soaps took: {}",
@@ -512,39 +467,12 @@ public boolean replaceSome(
return success;
}
- /** Sets up the WSDL connection to Tyler, used for `getPolicy` to get the URL. */
- private static FilingReviewMDEPort loginWithTyler(
- Jurisdiction jurisdiction, String userEmail, String userPassword) {
- Optional userFactory = TylerClients.getEfmUserFactory(jurisdiction);
- if (userFactory.isEmpty()) {
- throw new RuntimeException("Can't find " + jurisdiction + " in Soap chooser for EFMUser");
- }
- log.info("Getting filing factory for {}", jurisdiction);
- Optional filingFactory =
- SoapClientChooser.getFilingReviewFactory(jurisdiction);
- if (filingFactory.isEmpty()) {
- throw new RuntimeException(
- "Can't find " + jurisdiction + " in Soap Chooser for filing review factory");
- }
- TylerUserClient userPort = userFactory.get().makeUserClient(ServiceHelpers::setupServicePort);
- AuthenticateRequestType authReq = new AuthenticateRequestType();
- authReq.setEmail(userEmail);
- authReq.setPassword(userPassword);
- AuthenticateResponseType authRes = userPort.authenticateUser(authReq);
- List headersList = TylerUserNamePassword.makeHeaderList(authRes);
- FilingReviewMDEPort filingPort = filingFactory.get().getFilingReviewMDEPort();
- ServiceHelpers.setupServicePort((BindingProvider) filingPort);
- Map ctx = ((BindingProvider) filingPort).getRequestContext();
- ctx.put(Header.HEADER_LIST, headersList);
- return filingPort;
- }
-
/**
* Downloads a single codes zip. For Debugging.
*
* @throws URISyntaxException
*/
- public boolean downloadIndiv(List args) throws URISyntaxException {
+ public boolean downloadIndiv(List args, BiFunction makeUrl) throws URISyntaxException {
if (args.size() < 3) {
log.error(
"Need to pass in args: downloadIndiv args) throws URISyntaxException {
return false;
}
- var jurisdictionArg = Jurisdiction.parse(args.get(1));
-
String table = args.get(2);
String location = (args.size() == 4) ? args.get(3) : "";
HeaderSigner hs = new HeaderSigner(this.pathToKeystore, this.x509Password);
- String endpoint = TylerClients.getTylerServerRootUrl(jurisdictionArg);
return downloadAndProcessZip(
- makeCodeUrl(endpoint, table, location),
+ makeUrl.apply(table, location),
hs.signedCurrentTime().get(),
(in) -> {
String newFile = location.replace(':', '_') + "_" + table + "_test.xml";
@@ -573,42 +498,8 @@ public boolean downloadIndiv(List args) throws URISyntaxException {
});
}
- public static boolean executeCommand(
- Supplier cdSupplier,
- Jurisdiction jurisdiction,
- List args,
- String x509Password) {
- SoapX509CallbackHandler.setX509Password(x509Password);
- String command = args.get(0);
- try {
- String codesSite = TylerClients.getTylerServerRootUrl(jurisdiction);
- FilingReviewMDEPort filingPort =
- loginWithTyler(
- jurisdiction,
- System.getenv("TYLER_USER_EMAIL"),
- System.getenv("TYLER_USER_PASSWORD"));
- CodeUpdater cu = new CodeUpdater(System.getenv("PATH_TO_KEYSTORE"), x509Password);
- if (command.equalsIgnoreCase("replaceall")) {
- return cu.replaceAll(codesSite, filingPort, cdSupplier.get());
- } else if (command.equalsIgnoreCase("replacesome")) {
- return cu.replaceSome(
- codesSite, filingPort, cdSupplier.get(), args.subList(1, args.size()));
- } else if (command.equalsIgnoreCase("refresh")) {
- return cu.updateAll(codesSite, filingPort, cdSupplier.get());
- } else if (command.equalsIgnoreCase("downloadIndiv")) {
- return cu.downloadIndiv(args);
- } else {
- log.error("Command {} isn't a real command", command);
- return false;
- }
- } catch (SQLException | IOException | JAXBException | URISyntaxException e) {
- log.error("Exception when doing code updating! ", e);
- return false;
- }
- }
-
/** Should just be called from main. */
- private static CodeDatabaseAPI makeCodeDatabase(Jurisdiction jurisdiction) {
+ public static CodeDatabaseAPI makeCodeDatabase(Function dbMaker) {
try {
DataSource ds =
DatabaseCreator.makeDataSource(
@@ -620,45 +511,9 @@ private static CodeDatabaseAPI makeCodeDatabase(Jurisdiction jurisdiction) {
10,
100);
- return switch (jurisdiction.getVendor()) {
- case Jurisdiction.Vendor.TYLER -> CodeDatabase.fromDS(jurisdiction, ds);
- default -> throw new RuntimeException("Other databases not yet supported");
- };
+ return dbMaker.apply(ds);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
-
- /**
- * Run with:
- *
- *
- * java -cp efspserver-with-deps.jar edu.suffolk.litlab.efsp.ecfcodes.CodeUpdater refresh
- *
- *
- * TODO(#111): use with this System property and class to try to fix parallel unmarshalling
- * -Djava.util.concurrent.ForkJoinPool.common.threadFactory=edu.suffolk.litlab.efsp.server.JAXBForkJoinWorkerThreadFactory
- * \ https://stackoverflow.com/a/57551188/11416267
- */
- public static void main(String[] args) throws Exception {
- if (args.length < 1) {
- log.error("Need to pass in a subprogram: downloadIndiv, or refresh");
- System.exit(1);
- }
-
- List jurisdictions =
- Stream.of(System.getenv("TYLER_JURISDICTIONS").split(" "))
- .map(Jurisdiction::parse)
- .toList();
- for (var jurisdiction : jurisdictions) {
- MDC.put(MDCWrappers.USER_ID, jurisdiction.getName());
- // Reusing USER for Jurisdiction, SESSION for the court / location, and REQUEST for the table
- // name.
- executeCommand(
- () -> makeCodeDatabase(jurisdiction),
- jurisdiction,
- List.of(args),
- System.getenv("X509_PASSWORD"));
- }
- }
}
diff --git a/proxyserver/src/main/java/edu/suffolk/litlab/efsp/ecfcodes/CodeUrlGetter.java b/proxyserver/src/main/java/edu/suffolk/litlab/efsp/ecfcodes/CodeUrlGetter.java
new file mode 100644
index 000000000..22277d76b
--- /dev/null
+++ b/proxyserver/src/main/java/edu/suffolk/litlab/efsp/ecfcodes/CodeUrlGetter.java
@@ -0,0 +1,10 @@
+package edu.suffolk.litlab.efsp.ecfcodes;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URISyntaxException;
+
+public interface CodeUrlGetter {
+ public InputStream get(String url) throws IOException, URISyntaxException;
+ public boolean refresh();
+}
diff --git a/proxyserver/src/main/java/edu/suffolk/litlab/efsp/server/ecf4/TylerEcf4Helper.java b/proxyserver/src/main/java/edu/suffolk/litlab/efsp/server/ecf4/TylerEcf4Helper.java
new file mode 100644
index 000000000..72f7e7bc6
--- /dev/null
+++ b/proxyserver/src/main/java/edu/suffolk/litlab/efsp/server/ecf4/TylerEcf4Helper.java
@@ -0,0 +1,56 @@
+package edu.suffolk.litlab.efsp.server.ecf4;
+
+import ecf4.latest.oasis.names.tc.legalxml_courtfiling.schema.xsd.commontypes_4.CaseFilingType;
+import ecf4.latest.oasis.names.tc.legalxml_courtfiling.schema.xsd.commontypes_4.QueryMessageType;
+import ecf4.latest.oasis.names.tc.legalxml_courtfiling.schema.xsd.commontypes_4.QueryResponseMessageType;
+import edu.suffolk.litlab.efsp.server.utils.ServiceHelpers;
+import edu.suffolk.litlab.efsp.tyler.Ecf4Helper;
+//import edu.suffolk.litlab.efsp.server.utils.ServiceHelpers;
+import edu.suffolk.litlab.efsp.tyler.TylerErrorCodes;
+import jakarta.ws.rs.core.Response;
+import java.util.List;
+import java.util.function.Supplier;
+
+/**
+ * Helper class that easily converts Java native types to Cumbersome XML Schema types, particularly
+ * those from Oasis's ECF standard, and the National Information Exchange Model (NIEM).
+ *
+ * Some information about NIEM: Many of the types here all include the
+ * "SimpleObjectAttributeGroup", which, "provides a collection of attributes which are appropriate
+ * for definition of object types", including an ID attribute, a Metadata attribute, and a Link
+ * Metadata attribute that directly reference objects via IDs instead of including the object as an
+ * element. TBH, I'm not sure what the design decisions that dictate the use of them are, but they
+ * exist.
+ *
+ * @author Bryce Willey
+ */
+public class TylerEcf4Helper {
+
+ public static T prep(T newMsg, String courtId) {
+ return Ecf4Helper.prep(newMsg, courtId, ServiceHelpers.EXTERNAL_URL);
+ }
+
+ public static Response makeResponse(
+ QueryResponseMessageType resp, Supplier defaultRespFunc) {
+ return mapTylerCodesToHttp(Ecf4Helper.checkErrors(resp.getError()), defaultRespFunc);
+ }
+
+ public static Response mapTylerCodesToHttp(List err, Supplier defaultResp) {
+ if (!err.isEmpty()) {
+ var mainErr = err.getFirst();
+ if (TylerErrorCodes.tylerToHttp.containsKey(mainErr.code())) {
+ return Response.status(TylerErrorCodes.tylerToHttp.get(mainErr.code()))
+ .entity(mainErr.text())
+ .build();
+ }
+
+ // 422 as semantic issues covers most of the error codes
+ return Response.status(422).entity(mainErr.text()).build();
+ }
+ return defaultResp.get();
+ }
+
+ public static void setupReplys(CaseFilingType reply) {
+ Ecf4Helper.setupReplys(reply, ServiceHelpers.SERVICE_URL);
+ }
+}
diff --git a/proxyserver/src/main/java/edu/suffolk/litlab/efsp/server/setup/tyler/TylerModuleSetup.java b/proxyserver/src/main/java/edu/suffolk/litlab/efsp/server/setup/tyler/TylerModuleSetup.java
index 0b493144b..ad2853f0b 100644
--- a/proxyserver/src/main/java/edu/suffolk/litlab/efsp/server/setup/tyler/TylerModuleSetup.java
+++ b/proxyserver/src/main/java/edu/suffolk/litlab/efsp/server/setup/tyler/TylerModuleSetup.java
@@ -27,8 +27,8 @@
import edu.suffolk.litlab.efsp.server.utils.UpdateCodeVersions;
import edu.suffolk.litlab.efsp.stdlib.StdLib;
import edu.suffolk.litlab.efsp.tyler.ecfcodes.CodeDatabase;
-import edu.suffolk.litlab.efsp.tyler.ecfcodes.CodeUpdater;
import edu.suffolk.litlab.efsp.tyler.ecfcodes.DataFieldRow;
+import edu.suffolk.litlab.efsp.tyler.ecfcodes.TylerCodeUpdater;
import edu.suffolk.litlab.efsp.utils.InterviewToFilingInformationConverter;
import java.sql.SQLException;
import java.time.LocalTime;
@@ -186,11 +186,11 @@ public void preSetup() {
"Downloading just codes for {} in {}: please wait a bit",
testOnlyLocation,
jurisdiction);
- CodeUpdater.executeCommand(
+ TylerCodeUpdater.executeCommand(
() -> cd, jurisdiction, List.of("replacesome", testOnlyLocation), this.x509Password);
} else {
log.info("Downloading all codes for {}: please wait a bit", jurisdiction);
- CodeUpdater.executeCommand(
+ TylerCodeUpdater.executeCommand(
() -> cd, jurisdiction, List.of("replaceall"), this.x509Password);
}
}
diff --git a/proxyserver/src/main/java/edu/suffolk/litlab/efsp/server/utils/HeaderSigner.java b/proxyserver/src/main/java/edu/suffolk/litlab/efsp/server/utils/HeaderSigner.java
index 08f0ce132..280c79d75 100644
--- a/proxyserver/src/main/java/edu/suffolk/litlab/efsp/server/utils/HeaderSigner.java
+++ b/proxyserver/src/main/java/edu/suffolk/litlab/efsp/server/utils/HeaderSigner.java
@@ -37,6 +37,9 @@ public class HeaderSigner {
private final String pathToKeystore;
private final String x509Password;
+ // Can be refreshed
+ private String signedTime = "";
+
/**
* The string password to the x509 certificate. This password is given by Tyler, and will also
* unlock the .pfx file.
@@ -128,9 +131,21 @@ public Optional signedBase64(String content) {
*
* @return signed bytes of the timestamp string that has been base 64 encoded
*/
- public Optional signedCurrentTime() {
+ private Optional signTime() {
Instant now = Instant.now(Clock.systemUTC());
String currentTimestamp = now.toString();
return signedBase64(currentTimestamp);
}
+
+ public boolean refresh() {
+ var maybeTime = signTime();
+ maybeTime.ifPresent(time -> {
+ this.signedTime = time;
+ });
+ return maybeTime.isPresent();
+ }
+
+ public String signedCurrentTime() {
+ return this.signedTime;
+ }
}
diff --git a/proxyserver/src/main/java/edu/suffolk/litlab/efsp/server/utils/ServiceHelpers.java b/proxyserver/src/main/java/edu/suffolk/litlab/efsp/server/utils/ServiceHelpers.java
index 8944fef04..14b678d5e 100644
--- a/proxyserver/src/main/java/edu/suffolk/litlab/efsp/server/utils/ServiceHelpers.java
+++ b/proxyserver/src/main/java/edu/suffolk/litlab/efsp/server/utils/ServiceHelpers.java
@@ -26,13 +26,6 @@
public class ServiceHelpers {
private static final Logger log = LoggerFactory.getLogger(ServiceHelpers.class);
- /**
- * One of the ways that you can communicate over ECF. For more information, see
- * https://docs.oasis-open.org/legalxml-courtfiling/specs/ecf/v4.01/ecf-v4.01-spec/errata02/os/ecf-v4.01-spec-errata02-os-complete.html#_Toc425241629
- */
- public static final String MDE_PROFILE_CODE =
- "urn:oasis:names:tc:legalxml-courtfiling:schema:xsd:WebServicesMessaging-2.0";
-
public static final String MDE_PROFILE_CODE_5 =
"urn:oasis:names:tc:legalxml-courtfiling:schema:xsd:WebServicesMessaging-5.0";
diff --git a/proxyserver/src/main/java/edu/suffolk/litlab/efsp/server/utils/UpdateCodeVersions.java b/proxyserver/src/main/java/edu/suffolk/litlab/efsp/server/utils/UpdateCodeVersions.java
index 65d6ea77f..d59aafe6d 100644
--- a/proxyserver/src/main/java/edu/suffolk/litlab/efsp/server/utils/UpdateCodeVersions.java
+++ b/proxyserver/src/main/java/edu/suffolk/litlab/efsp/server/utils/UpdateCodeVersions.java
@@ -4,7 +4,8 @@
import edu.suffolk.litlab.efsp.db.DatabaseCreator;
import edu.suffolk.litlab.efsp.server.logging.MDCWrappers;
import edu.suffolk.litlab.efsp.tyler.ecfcodes.CodeDatabase;
-import edu.suffolk.litlab.efsp.tyler.ecfcodes.CodeUpdater;
+import edu.suffolk.litlab.efsp.tyler.ecfcodes.TylerCodeUpdater;
+
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
@@ -40,7 +41,7 @@ public void execute(JobExecutionContext context) throws JobExecutionException {
DatabaseCreator.makeSingleConnection(pgDb, pgFullUrl, pgUser, pgPassword);
CodeDatabase cd = new CodeDatabase(jurisdiction, conn)) {
success =
- CodeUpdater.executeCommand(() -> cd, jurisdiction, List.of("refresh"), x509Password);
+ TylerCodeUpdater.executeCommand(() -> cd, jurisdiction, List.of("refresh"), x509Password);
} catch (SQLException e) {
log.error("Couldn't connect to Codes db from Job Executor: ", e);
success = false;
diff --git a/proxyserver/src/main/java/edu/suffolk/litlab/efsp/truefiling/Ecf4Helper.java b/proxyserver/src/main/java/edu/suffolk/litlab/efsp/truefiling/Ecf4Helper.java
new file mode 100644
index 000000000..cf019a9fc
--- /dev/null
+++ b/proxyserver/src/main/java/edu/suffolk/litlab/efsp/truefiling/Ecf4Helper.java
@@ -0,0 +1,338 @@
+package edu.suffolk.litlab.efsp.truefiling;
+
+import java.io.File;
+import java.io.StringWriter;
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.util.GregorianCalendar;
+import java.util.List;
+import java.util.Optional;
+
+import javax.xml.datatype.DatatypeConfigurationException;
+import javax.xml.datatype.DatatypeConstants;
+import javax.xml.datatype.DatatypeFactory;
+import javax.xml.datatype.XMLGregorianCalendar;
+import javax.xml.namespace.QName;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import gov.niem.niem.domains.jxdm._4.CourtType;
+import gov.niem.niem.fips_10_4._2.CountryCodeType;
+import gov.niem.niem.iso_4217._2.CurrencyCodeSimpleType;
+import gov.niem.niem.niem_core._2.AmountType;
+import gov.niem.niem.niem_core._2.DateType;
+import gov.niem.niem.niem_core._2.EntityType;
+import gov.niem.niem.niem_core._2.MeasureType;
+import gov.niem.niem.niem_core._2.TextType;
+import gov.niem.niem.proxy.xsd._2.Decimal;
+import jakarta.xml.bind.JAXBContext;
+import jakarta.xml.bind.JAXBElement;
+import jakarta.xml.bind.JAXBException;
+import jakarta.xml.bind.Marshaller;
+import oasis.names.tc.legalxml_courtfiling.schema.xsd.commontypes_4.CaseFilingType;
+import oasis.names.tc.legalxml_courtfiling.schema.xsd.commontypes_4.PersonType;
+import oasis.names.tc.legalxml_courtfiling.schema.xsd.commontypes_4.QueryMessageType;
+
+public class Ecf4Helper {
+ private static final Logger log = LoggerFactory.getLogger(Ecf4Helper.class);
+
+ static final gov.niem.niem.proxy.xsd._2.ObjectFactory niemProxyObjFac;
+ static final gov.niem.niem.niem_core._2.ObjectFactory niemCoreObjFac;
+ static final gov.niem.niem.domains.jxdm._4.ObjectFactory jxObjFac;
+ static final DatatypeFactory datatypeFac;
+
+ /**
+ * One of the ways that you can communicate over ECF. For more information, see
+ * https://docs.oasis-open.org/legalxml-courtfiling/specs/ecf/v4.01/ecf-v4.01-spec/errata02/os/ecf-v4.01-spec-errata02-os-complete.html#_Toc425241629
+ */
+ public static final String MDE_PROFILE_CODE =
+ "urn:oasis:names:tc:legalxml-courtfiling:schema:xsd:WebServicesMessaging-2.0";
+
+
+ static {
+ niemProxyObjFac = new gov.niem.niem.proxy.xsd._2.ObjectFactory();
+ niemCoreObjFac = new gov.niem.niem.niem_core._2.ObjectFactory();
+ jxObjFac = new gov.niem.niem.domains.jxdm._4.ObjectFactory();
+ try {
+ datatypeFac = DatatypeFactory.newInstance();
+ } catch (DatatypeConfigurationException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /** Creates a date from a java date. Doesn't have time associated with it. */
+ public static DateType convertDate(LocalDate date) {
+ GregorianCalendar cal = new GregorianCalendar();
+ // TODO(#47): DEFAULT TIMEZONE IS WRONG: how should LocalDate +
+ // GregorianCalendar operate?
+ cal.set(date.getYear(), date.getMonthValue() - 1, date.getDayOfMonth(), 0, 0, 0);
+
+ gov.niem.niem.proxy.xsd._2.Date d = niemProxyObjFac.createDate();
+ XMLGregorianCalendar x = datatypeFac.newXMLGregorianCalendar(cal);
+ x.setTimezone(DatatypeConstants.FIELD_UNDEFINED);
+ d.setValue(x);
+
+ DateType dt = niemCoreObjFac.createDateType();
+ dt.setDateRepresentation(niemCoreObjFac.createDate(d));
+ return dt;
+ }
+
+ public static LocalDate parseDate(DateType dt) {
+ var d = ((gov.niem.niem.proxy.xsd._2.Date) dt.getDateRepresentation().getValue()).getValue();
+ return LocalDate.of(d.getYear(), d.getMonth(), d.getDay());
+ }
+
+ /** Niem's Boolean type: either true or false. */
+ public static gov.niem.niem.proxy.xsd._2.Boolean convertBool(boolean bool) {
+ gov.niem.niem.proxy.xsd._2.Boolean val =
+ new gov.niem.niem.proxy.xsd._2.Boolean();
+ val.setValue(bool);
+ return val;
+ }
+
+ public static Optional getNonEmptyText(TextType text) {
+ if (text != null && text.getValue() != null && !text.getValue().isBlank()) {
+ return Optional.of(text.getValue());
+ }
+ return Optional.empty();
+ }
+
+ /**
+ * Converts a Java string to NIEM "Text", a wrapper around the NIEM String.
+ *
+ * "A data type for a character string"
+ */
+ public static TextType convertText(String str) {
+ TextType tt = niemCoreObjFac.createTextType();
+ tt.setValue(str);
+ return tt;
+ }
+
+ /**
+ * Converts a Java string to NIEM's XML String. "A datatype for character strings in XML."
+ * Practically, just a xsd:string with the extra SimpleObjectAttributeGroup attributes.
+ */
+ public static gov.niem.niem.proxy.xsd._2.String convertString(String str) {
+ var outStr = niemProxyObjFac.createString();
+ outStr.setValue(str);
+ return outStr;
+ }
+
+ /**
+ * @param type will always be in bytes, but this is for backup.
+ */
+ public static long sizeMeasureAsBytes(MeasureType type) {
+ String measureText = ((TextType) type.getMeasureValue().getValue()).getValue();
+ long measureNum = Long.parseLong(measureText);
+ String measureUnit = type.getMeasureUnitText().getValue();
+ if (measureUnit.equalsIgnoreCase("byte")) {
+ return measureNum;
+ } else if (measureUnit.equalsIgnoreCase("kilobyte") || measureUnit.equals("kB")) {
+ return measureNum * 1000;
+ } else if (measureUnit.equals("KB") || measureUnit.equals("KiB")) {
+ return measureNum * 1024;
+ } else if (measureUnit.equals("MB")) {
+ return measureNum * 1000 * 1000;
+ } else {
+ return measureNum;
+ }
+ }
+
+ public static gov.niem.niem.niem_core._2.IdentificationType convertId(String idStr) {
+ var id = niemCoreObjFac.createIdentificationType();
+ id.setIdentificationID(convertString(idStr));
+ return id;
+ }
+
+ public static gov.niem.niem.niem_core._2.IdentificationType convertId(
+ String idStr, String category) {
+ var id = niemCoreObjFac.createIdentificationType();
+ id.setIdentificationID(convertString(idStr));
+ id.setIdentificationCategory(
+ niemCoreObjFac.createIdentificationCategoryText(convertText(category)));
+ return id;
+ }
+
+ public static Decimal convertDecimal(BigDecimal deci) {
+ Decimal xml = new Decimal();
+ xml.setValue(deci);
+ return xml;
+ }
+
+ /**
+ * Convert an int to a BigDecimal to an XML Decimal
+ *
+ *
Weird conversion, but the multiplier on optional services is a Decimal for some reason? "I
+ * need 2.3 copies of this sent to my office", lmao.
+ *
+ * @param val
+ * @return
+ */
+ public static Decimal convertDecimal(int val) {
+ return convertDecimal(new BigDecimal(val));
+ }
+
+ /** Converts a Java string to a URI. */
+ public static gov.niem.niem.proxy.xsd._2.AnyURI convertUri(String uri) {
+ var anyUri = niemProxyObjFac.createAnyURI();
+ anyUri.setValue(uri);
+ return anyUri;
+ }
+
+ public static gov.niem.niem.proxy.xsd._2.Base64Binary convertBase64(
+ final byte[] rawContent) {
+ var binaryString = niemProxyObjFac.createBase64Binary();
+ // We don't need to encode Base64? For some strange reason, JAXB does it for us.
+ // See https://stackoverflow.com/a/7224025
+ // binaryString.setValue(Base64.getEncoder().encode(rawContent));
+ binaryString.setValue(rawContent);
+ return binaryString;
+ }
+
+ public static CourtType convertCourtType(String courtId) {
+ CourtType court = jxObjFac.createCourtType();
+ court.setOrganizationIdentification(Ecf4Helper.convertId(courtId));
+ return court;
+ }
+
+ public static String amountToString(AmountType amt) {
+ if (amt == null) {
+ return "";
+ }
+ var currencyCode = amt.getCurrencyCode();
+ String valStr = amt.getValue().toString();
+ if (currencyCode == null) {
+ return valStr;
+ }
+ // TODO(brycew): more internationalization
+ if (currencyCode.equals(CurrencyCodeSimpleType.USD)) {
+ return "$" + valStr;
+ } else {
+ return valStr + " " + currencyCode.toString();
+ }
+ }
+
+ /**
+ * Converts any XML annotated object (from CXF) to a string. Useful for debugging. Doesn't throw,
+ * but does return the string of the exception instead.
+ *
+ * @param the type of object being passed in
+ * @param toXml The object to do things with
+ * @return the XML string to do what you want with
+ */
+ public static String objectToXmlStrOrError(T toXml, Class toXmlClazz) {
+ try {
+ return objectToXmlStr(toXml, toXmlClazz);
+ } catch (JAXBException | NullPointerException ex) {
+ return ex.toString() + "(original obj was : " + toXml.toString() + ")";
+ }
+ }
+
+ /**
+ * Converts any XML annotated object (from CXF) to a string. Useful for testing. Will throw an
+ * exception if there's a JAXB error.
+ *
+ * @param the type of object being passed in
+ * @param toXml The object to do things with
+ * @return the XML string to do what you want with
+ */
+ public static String objectToXmlStr(T toXml, Class toXmlClazz) throws JAXBException {
+ JAXBContext jaxContext =
+ JAXBContext.newInstance(
+ toXmlClazz,
+ gov.niem.niem.niem_core._2.ObjectFactory.class,
+ gov.niem.niem.structures._2.ObjectFactory.class,
+ oasis.names.tc.legalxml_courtfiling.schema.xsd.corefilingmessage_4
+ .ObjectFactory.class,
+ oasis.names.tc.legalxml_courtfiling.schema.xsd.commontypes_4.ObjectFactory
+ .class
+ /* ecfv5.https.docs_oasis_open_org.legalxml_courtfiling.ns.v5_0.ecf.ObjectFactory.class,
+ ecfv5.https.docs_oasis_open_org.legalxml_courtfiling.ns.v5_0.civil.ObjectFactory.class,
+ ecfv5.gov.niem.release.niem.niem_core._4.ObjectFactory.class,
+ ecfv5.gov.niem.release.niem.domains.jxdm._6.ObjectFactory.class*/ );
+ Marshaller mar = jaxContext.createMarshaller();
+ mar.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
+ QName qname = new QName("suffolk.test.objectToXml", "objectToXml");
+ JAXBElement wrappedRoot = new JAXBElement(qname, toXmlClazz, toXml);
+ StringWriter sw = new StringWriter();
+ mar.marshal(wrappedRoot, sw);
+ return sw.toString();
+ }
+
+ /**
+ * Converts any XML annotated object (from CXF) to a file. Useful for testing larger XML objects
+ * that are unwieldly to print. Will throw an exception if there's a JAXB error.
+ *
+ * @param the type of object being passed in
+ * @param toXml The object to do things with
+ */
+ public static void objectToXmlFile(T toXml, Class toXmlClazz, File outfile)
+ throws JAXBException {
+ JAXBContext jc =
+ JAXBContext.newInstance(
+ gov.niem.niem.niem_core._2.ObjectFactory.class,
+ gov.niem.niem.structures._2.ObjectFactory.class,
+ oasis.names.tc.legalxml_courtfiling.schema.xsd.corefilingmessage_4
+ .ObjectFactory.class,
+ oasis.names.tc.legalxml_courtfiling.schema.xsd.commontypes_4.ObjectFactory
+ .class);
+ Marshaller mar = jc.createMarshaller();
+ mar.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
+ QName qname = new QName("suffolk.test.objectToXml", "objectToXml");
+ JAXBElement pp = new JAXBElement(qname, toXmlClazz, toXml);
+ mar.marshal(pp, outfile);
+ }
+
+ /** Turn a given string into a country type. */
+ public static CountryCodeType strToCountryCode(String country) {
+ CountryCodeType cct = new CountryCodeType();
+ cct.setValue(country);
+ return cct;
+ }
+
+ public static T prep(T newMsg, String courtId, String locationId) {
+ EntityType typ = new EntityType();
+ var commonObjFac =
+ new oasis.names.tc.legalxml_courtfiling.schema.xsd.commontypes_4
+ .ObjectFactory();
+ JAXBElement elem2 = commonObjFac.createEntityPerson(new PersonType());
+ typ.setEntityRepresentation(elem2);
+ newMsg.setQuerySubmitter(typ);
+ newMsg.setCaseCourt(Ecf4Helper.convertCourtType(courtId));
+ // Example in the ECF4 docs is "https://filingreviewmde.com", which matches best to
+ // EXTERNAL_URL.
+ // Doesn't seem to be used by Tyler however.
+ newMsg.setSendingMDELocationID(Ecf4Helper.convertId(locationId));
+ newMsg.setSendingMDEProfileCode(MDE_PROFILE_CODE);
+ return newMsg;
+ }
+
+ public record Error(String code, String text) {}
+
+ /**
+ * Returns the error type on errors from the ECF side of the API. They work the same as the Tyler
+ * ones.
+ */
+ public static Optional checkError(
+ oasis.names.tc.legalxml_courtfiling.schema.xsd.commontypes_4.ErrorType error) {
+ var errCode = error.getErrorCode();
+ if (errCode != null && !errCode.getValue().equals("0")) {
+ log.error("Error!: {}: {}", errCode.getValue(), error.getErrorText().getValue());
+ return Optional.of(new Error(errCode.getValue(), error.getErrorText().getValue()));
+ }
+ return Optional.empty();
+ }
+
+ public static List checkErrors(
+ List
+ errors) {
+ return errors.stream().flatMap(err -> checkError(err).stream()).toList();
+ }
+
+ public static void setupReplys(CaseFilingType reply, String locationId) {
+ reply.setSendingMDELocationID(Ecf4Helper.convertId(locationId));
+ reply.setSendingMDEProfileCode(MDE_PROFILE_CODE);
+ }
+
+}
diff --git a/proxyserver/src/main/java/edu/suffolk/litlab/efsp/truefiling/ecfcodes/AllowanceChargeReason.java b/proxyserver/src/main/java/edu/suffolk/litlab/efsp/truefiling/ecfcodes/AllowanceChargeReason.java
new file mode 100644
index 000000000..9d4dc37d8
--- /dev/null
+++ b/proxyserver/src/main/java/edu/suffolk/litlab/efsp/truefiling/ecfcodes/AllowanceChargeReason.java
@@ -0,0 +1,6 @@
+package edu.suffolk.litlab.efsp.truefiling.ecfcodes;
+
+public record AllowanceChargeReason(String code, String description) {
+ // TODO: finish
+
+}
diff --git a/proxyserver/src/main/java/edu/suffolk/litlab/efsp/truefiling/ecfcodes/CaseCategory.java b/proxyserver/src/main/java/edu/suffolk/litlab/efsp/truefiling/ecfcodes/CaseCategory.java
new file mode 100644
index 000000000..f43c8d58a
--- /dev/null
+++ b/proxyserver/src/main/java/edu/suffolk/litlab/efsp/truefiling/ecfcodes/CaseCategory.java
@@ -0,0 +1,130 @@
+package edu.suffolk.litlab.efsp.truefiling.ecfcodes;
+
+import edu.suffolk.litlab.efsp.ecf4.EcfCaseTypes;
+import edu.suffolk.litlab.efsp.ecfcodes.CodeDatabaseUtils.Column;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.List;
+
+/**
+ * Heavily based on {@link edu.suffolk.litlab.efsp.tyler.ecfcodes.CaseCategory}, but replacing name
+ * with description.
+ */
+public record CaseCategory(
+ /** Actually a string, different from Tyler's numerical codes. */
+ String code,
+ /* Description to show user*/
+ String description,
+ /* which ECF Case Type to make for this Case Category */
+ EcfCaseTypes ecfCaseType) {
+
+ public CaseCategory(String code, String description, String ecfCaseType) {
+ this(code, description, EcfCaseTypes.valueOf(ecfCaseType));
+ }
+
+ public CaseCategory(ResultSet rs) throws SQLException {
+ this(rs.getString(2), rs.getString(3), EcfCaseTypes.valueOf(rs.getString(4)));
+ }
+
+ public static final String TABLE_NAME = "truefiling_casecategory";
+ public static final List columns =
+ List.of(
+ new Column("code", "text"),
+ new Column("descirption", "text"),
+ new Column("ecfcasetype", "text"));
+
+ public static final String CREATE_QUERY =
+ """
+ CREATE TABLE truefiling_casecategory (
+ "code" text PRIMARY KEY,
+ "description" text,
+ "ecfcasetype" text,
+ "location" varchar(80),
+ "jurisdiction" varchar(80)
+ )
+ """;
+
+ public static final String INSERT_QUERY =
+ """
+ INSERT INTO truefiling_casecategory (
+ "code", "description", "ecfcasetype", "location", "jurisdiction"
+ ) VALUES (
+ ?, ?, ?, ?, ?
+ )
+ """;
+
+ public static final String DELETE_FROM_QUERY =
+ """
+ DELETE FROM truefiling_casecategory WHERE jurisdiction=? AND location=?
+ """;
+
+ public static final String DELETE_ALL_COURTS_FROM_QUERY =
+ """
+ DELETE FROM truefiling_casecategory WHERE jurisdiction=?
+ """;
+
+ public static String searchCaseCategories() {
+ return """
+ SELECT DISTINCT description
+ FROM truefiling_casecategory
+ WHERE jurisdiction=? AND description ILIKE ?
+ ORDER BY description
+ """;
+ }
+
+ public static String courtCoverageCaseCategories() {
+ return """
+ SELECT DISTINCT location
+ FROM truefiling_casecategory
+ WHERE jurisdiction=? AND description ILIKE ?
+ ORDER BY location
+ """;
+ }
+
+ public static String retrieveCaseCategoryForName() {
+ return """
+ SELECT DISTINCT code, location
+ FROM truefiling_casecategory
+ WHERE jurisdiction=? AND description=?
+ ORDER BY location
+ """;
+ }
+
+ // TODO(#86): stop filtering out criminal categories
+ public static String getCaseCategoriesForLoc() {
+ return """
+ SELECT code, description, ecfcasetype
+ FROM truefiling_casecategory
+ WHERE jurisdiction=? AND location=? AND ecfcasetype !='CriminalCase'
+ """;
+ }
+
+ public static String getFileableCaseCategoryForLoc() {
+ return """
+ SELECT cat.code, cat.description, cat.ecfcasetype
+ FROM (
+ SELECT cate.code, cate.description, cate.ecfcasetype,
+ type.id AS type_id,
+ ROW_NUMBER() OVER(PARTITION BY cate.code ORDER BY type.id) AS RN
+ FROM truefiling_casecategory AS cate
+ INNER JOIN truefiling_casetype AS type
+ ON cate.code = type.casecategory AND cate.location = type.location AND cate.jurisdiction = type.jurisdiction
+ WHERE cate.jurisdiction=? AND cate.location=? AND cate.ecfcasetype != 'CriminalCase'
+ ) cat
+ WHERE cat.RN = 1
+ """;
+ }
+
+ public static String getFileableCaseCategoryForTiming() {
+ // TODO: just ignore the timing param higher up
+ return getFileableCaseCategoryForLoc();
+ }
+
+ public static String getCaseCategoryWithCode() {
+ return """
+ SELECT code, description, ecfcasetype
+ FROM truefiling_casecategory
+ WHERE jurisdiction=? AND location=? AND code=?
+ """;
+ }
+}
diff --git a/proxyserver/src/main/java/edu/suffolk/litlab/efsp/truefiling/ecfcodes/CaseType.java b/proxyserver/src/main/java/edu/suffolk/litlab/efsp/truefiling/ecfcodes/CaseType.java
new file mode 100644
index 000000000..46ea6629b
--- /dev/null
+++ b/proxyserver/src/main/java/edu/suffolk/litlab/efsp/truefiling/ecfcodes/CaseType.java
@@ -0,0 +1,128 @@
+package edu.suffolk.litlab.efsp.truefiling.ecfcodes;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.UUID;
+
+public record CaseType(UUID id, String code, String description, String caseCategoryCode) {
+ public CaseType(String id, String code, String description, String caseCategoryCode) {
+ this(UUID.fromString(id), code, description, caseCategoryCode);
+ }
+
+ public CaseType(ResultSet rs) throws SQLException {
+ this((UUID) rs.getObject(1), rs.getString(2), rs.getString(3), rs.getString(4));
+ }
+
+ public static final String TABLE_NAME = "truefiling_casetype";
+
+ public static final String CREATE_QUERY =
+ """
+ CREATE TABLE truefiling_casetype (
+ "id" uuid PRIMARY KEY,
+ "code" text,
+ "description" text,
+ "casecategory" text,
+ "location" varchar(80),
+ "jurisdiction" varchar(80)
+ )
+ """;
+
+ public static final String INSERT_QUERY =
+ """
+ INSERT INTO truefiling_casetype (
+ "id", "code", "description", "casecategory", "location", "jurisdiciton"
+ ) VALUES (
+ ?, ?, ?, ?, ?
+ )
+ """;
+
+ public static final String DELETE_FROM_QUERY =
+ """
+ DELETE FROM truefiling_casetype WHERE jurisdiction=? AND location=?
+ """;
+
+ public static final String DELETE_ALL_COURTS_FROM_QUERY =
+ """
+ DELETE FROM truefiling_casetype WHERE jurisdiction=?
+ """;
+
+ public static PreparedStatement prepQuery(
+ Connection conn, String jurisdiction, String courtLocationId, String caseCategoryCode)
+ throws SQLException {
+ String query =
+ """
+ SELECT id, code, description, casecategory
+ FROM truefiling_casetype
+ WHERE jurisdiction=? AND location=? AND casecategory=?
+ """;
+ PreparedStatement st = conn.prepareStatement(query);
+ st.setString(1, jurisdiction);
+ st.setString(2, courtLocationId);
+ st.setString(3, caseCategoryCode);
+ return st;
+ }
+
+ public static PreparedStatement prepQueryWithCode(
+ Connection conn, String jurisdiction, String courtLocationId, UUID caseTypeId)
+ throws SQLException {
+ String withcode =
+ """
+ SELECT id, code, description, casecategory
+ FROM truefiling_casetype
+ WHERE jurisdiction=? AND location=? AND id=?
+ """;
+ PreparedStatement st = conn.prepareStatement(withcode);
+ st.setString(1, jurisdiction);
+ st.setString(2, courtLocationId);
+ st.setObject(3, caseTypeId);
+
+ return st;
+ }
+
+ public static PreparedStatement prepSearchQuery(
+ Connection conn, String jurisdiction, String searchTerm) throws SQLException {
+ String search =
+ """
+ SELECT DISTINCT description
+ FROM casetype
+ WHERE jurisdiction=? AND description ILIKE ?
+ ORDER BY description
+ """;
+ PreparedStatement st = conn.prepareStatement(search);
+ st.setString(1, jurisdiction);
+ st.setString(2, searchTerm);
+ return st;
+ }
+
+ public static PreparedStatement prepCourtCoverage(
+ Connection conn, String jurisdiction, String searchTerm) throws SQLException {
+ String search =
+ """
+ SELECT DISTINCT location
+ FROM casetype
+ WHERE jurisdiction=? AND name ILIKE ?
+ ORDER BY location
+ """;
+ PreparedStatement st = conn.prepareStatement(search);
+ st.setString(1, jurisdiction);
+ st.setString(2, searchTerm);
+ return st;
+ }
+
+ public static PreparedStatement prepRetrieveQuery(
+ Connection conn, String jurisdiction, String caseTypeName) throws SQLException {
+ String retrieve =
+ """
+ SELECT DISTINCT code, location
+ FROM casetype
+ WHERE jurisdiction=? AND name=?
+ ORDER BY location
+ """;
+ PreparedStatement st = conn.prepareStatement(retrieve);
+ st.setString(1, jurisdiction);
+ st.setString(2, caseTypeName);
+ return st;
+ }
+}
diff --git a/proxyserver/src/main/java/edu/suffolk/litlab/efsp/truefiling/ecfcodes/CodeTableConstants.java b/proxyserver/src/main/java/edu/suffolk/litlab/efsp/truefiling/ecfcodes/CodeTableConstants.java
new file mode 100644
index 000000000..4ddd87951
--- /dev/null
+++ b/proxyserver/src/main/java/edu/suffolk/litlab/efsp/truefiling/ecfcodes/CodeTableConstants.java
@@ -0,0 +1,73 @@
+package edu.suffolk.litlab.efsp.truefiling.ecfcodes;
+
+import java.util.List;
+import java.util.Map;
+
+import edu.suffolk.litlab.efsp.ecfcodes.CodeDatabaseUtils.TableColumns;
+import edu.suffolk.litlab.efsp.ecfcodes.CodeDatabaseUtils.UnsupportedTableException;
+
+/**
+ * Court codes in Alaska are very long lived and don't change often.
+ * Keeping them here for the moment. Marking other places where this
+ * matters with TODO(ak-no-tables).
+ */
+public class CodeTableConstants {
+
+ private static final Map tableColumns;
+
+ static {
+ tableColumns = Map.ofEntries(
+ Map.entry(CaseCategory.TABLE_NAME, new TableColumns(CaseCategory.columns, List.of(), false))
+ );
+ }
+
+ public static final List categories = List.of(
+ new CaseCategory("appellate", "Appellate", "AppellateCase"),
+ new CaseCategory("bankruptcy", "Bankruptcy", "BankruptcyCase"),
+ new CaseCategory("citation", "Citation", "CitationCase"),
+ new CaseCategory("civil", "Civil", "CivilCase"),
+ new CaseCategory("criminal", "Criminal", "CriminalCase"),
+ new CaseCategory("domestic", "Domestic", "DomesticCase"),
+ new CaseCategory("juvenile", "Juvenile", "JuvenileCase")
+ );
+
+ public static String getCreateTable(String tableName) throws UnsupportedTableException {
+ if (createQueries.containsKey(tableName)) {
+ return createQueries.get(tableName);
+ }
+ throw new UnsupportedTableException("No create query for table " + tableName);
+ }
+
+ private static final Map createQueries = Map.of(
+ CaseCategory.TABLE_NAME, CaseCategory.CREATE_QUERY,
+ CaseType.TABLE_NAME, CaseType.CREATE_QUERY
+ );
+
+
+ private static final Map insertQueries = Map.of(
+ CaseCategory.TABLE_NAME, CaseCategory.INSERT_QUERY,
+ CaseType.TABLE_NAME, CaseType.INSERT_QUERY
+ );
+
+ private static final Map deleteFromQueries = Map.of(
+ CaseCategory.TABLE_NAME, CaseCategory.DELETE_FROM_QUERY,
+ CaseType.TABLE_NAME, CaseType.DELETE_FROM_QUERY
+ );
+
+ private static final Map deleteAllCourtsFromQueries = Map.of(
+ CaseCategory.TABLE_NAME, CaseCategory.DELETE_ALL_COURTS_FROM_QUERY,
+ CaseType.TABLE_NAME, CaseType.DELETE_ALL_COURTS_FROM_QUERY
+ );
+
+ public static String getInsertInto(String tableName) throws UnsupportedTableException {
+ if (!insertQueries.containsKey(tableName)) {
+ throw new UnsupportedTableException("No insert query for table " + tableName);
+ }
+ return insertQueries.get(tableName);
+ }
+
+ public static TableColumns getTableColumns(String tableName) {
+ return tableColumns.getOrDefault(tableName, new TableColumns(List.of(), List.of(), false));
+ }
+
+}
diff --git a/proxyserver/src/main/java/edu/suffolk/litlab/efsp/truefiling/ecfcodes/DocumentStatus.java b/proxyserver/src/main/java/edu/suffolk/litlab/efsp/truefiling/ecfcodes/DocumentStatus.java
new file mode 100644
index 000000000..ee9aaebca
--- /dev/null
+++ b/proxyserver/src/main/java/edu/suffolk/litlab/efsp/truefiling/ecfcodes/DocumentStatus.java
@@ -0,0 +1,9 @@
+package edu.suffolk.litlab.efsp.truefiling.ecfcodes;
+
+// TODO: what to do with this one? no description or other
+// information that isn't present in the code itself? might
+// be able to just ignore
+public record DocumentStatus(String code) {
+ // TODO: finish
+
+}
diff --git a/proxyserver/src/main/java/edu/suffolk/litlab/efsp/truefiling/ecfcodes/DocumentType.java b/proxyserver/src/main/java/edu/suffolk/litlab/efsp/truefiling/ecfcodes/DocumentType.java
new file mode 100644
index 000000000..abf1a697b
--- /dev/null
+++ b/proxyserver/src/main/java/edu/suffolk/litlab/efsp/truefiling/ecfcodes/DocumentType.java
@@ -0,0 +1,15 @@
+package edu.suffolk.litlab.efsp.truefiling.ecfcodes;
+
+import java.util.UUID;
+
+// is per court
+public record DocumentType(
+ UUID id,
+ UUID caseTypeId,
+ String description,
+ // TODO: idk what this is used for, not referenced elsewhere in the docs
+ String filingServiceType,
+ boolean courtUseOnly) {
+
+ // TODO: finish
+}
diff --git a/proxyserver/src/main/java/edu/suffolk/litlab/efsp/truefiling/ecfcodes/ErrorCode.java b/proxyserver/src/main/java/edu/suffolk/litlab/efsp/truefiling/ecfcodes/ErrorCode.java
new file mode 100644
index 000000000..418302049
--- /dev/null
+++ b/proxyserver/src/main/java/edu/suffolk/litlab/efsp/truefiling/ecfcodes/ErrorCode.java
@@ -0,0 +1,7 @@
+package edu.suffolk.litlab.efsp.truefiling.ecfcodes;
+
+// System-wide code list
+public record ErrorCode(String code, String name) {
+ // TODO: finish
+
+}
diff --git a/proxyserver/src/main/java/edu/suffolk/litlab/efsp/truefiling/ecfcodes/FeeExceptionReason.java b/proxyserver/src/main/java/edu/suffolk/litlab/efsp/truefiling/ecfcodes/FeeExceptionReason.java
new file mode 100644
index 000000000..4abe22e9a
--- /dev/null
+++ b/proxyserver/src/main/java/edu/suffolk/litlab/efsp/truefiling/ecfcodes/FeeExceptionReason.java
@@ -0,0 +1,13 @@
+package edu.suffolk.litlab.efsp.truefiling.ecfcodes;
+
+import java.util.UUID;
+
+// Is per court
+public record FeeExceptionReason(
+ UUID id,
+ String description,
+ // TODO: make this a URL? will that be too strict?
+ String formLink) {
+ // TODO: finish class
+
+}
diff --git a/proxyserver/src/main/java/edu/suffolk/litlab/efsp/truefiling/ecfcodes/TFCodeDatabase.java b/proxyserver/src/main/java/edu/suffolk/litlab/efsp/truefiling/ecfcodes/TFCodeDatabase.java
new file mode 100644
index 000000000..e5e45f55e
--- /dev/null
+++ b/proxyserver/src/main/java/edu/suffolk/litlab/efsp/truefiling/ecfcodes/TFCodeDatabase.java
@@ -0,0 +1,269 @@
+package edu.suffolk.litlab.efsp.truefiling.ecfcodes;
+
+import edu.suffolk.litlab.efsp.Jurisdiction;
+import edu.suffolk.litlab.efsp.ecfcodes.CodeAndLocation;
+import edu.suffolk.litlab.efsp.ecfcodes.CodeDatabaseAPI;
+import edu.suffolk.litlab.efsp.ecfcodes.CodeDatabaseUtils;
+import edu.suffolk.litlab.efsp.ecfcodes.CodeDocException;
+import edu.suffolk.litlab.efsp.ecfcodes.CodeDocIterator;
+import edu.suffolk.litlab.efsp.ecfcodes.NameAndCode;
+import java.io.InputStream;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import javax.sql.DataSource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class TFCodeDatabase extends CodeDatabaseAPI {
+ private static final Logger log = LoggerFactory.getLogger(TFCodeDatabase.class);
+
+ private final Jurisdiction jurisdiction;
+
+ public TFCodeDatabase(Jurisdiction jurisdiction, Connection conn) {
+ super(conn);
+ this.jurisdiction = jurisdiction;
+ }
+
+ public static TFCodeDatabase fromDS(Jurisdiction jurisdiction, DataSource ds) {
+ try {
+ TFCodeDatabase cd = new TFCodeDatabase(jurisdiction, ds.getConnection());
+ return cd;
+ } catch (SQLException e) {
+ log.error("In TFCodeDatabase constructor, can't get connection: ", e);
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public Jurisdiction getJurisdiction() {
+ return jurisdiction;
+ }
+
+ // TODO: should this be in the parent class?
+ private String jurisStr() {
+ return jurisdiction.getName();
+ }
+
+ private static final Map ecf4Map =
+ Map.of(
+ "nc:CaseCategoryText", "truefiling_casecategory",
+ "nc:DocumentCategoryText", "truefiling_documentcategory",
+ "j:RegisterActionDescriptionText", "truefiling_registeraction",
+ "nc:BinaryFormatStandardName", "truefiling_binaryformat",
+ "cbc:AccountingCostCode", "truefiling_accountingcost",
+ "FeeExceptionReasonCode", "truefiling_feeexception",
+ "ecf:FilingStatusCode", "truefiling_filingstatus");
+
+ @Override
+ public Map xmlElemToTableName() {
+ return ecf4Map;
+ }
+
+ @Override
+ public Set systemTables() {
+ return Set.of();
+ }
+
+ @Override
+ public List getAllLocations() {
+ // Only AK Trial Court
+ return List.of("55da5b11-2bc4-4881-abd1-b0dfdb506bb1");
+ }
+
+ @Override
+ public void updateTable(String tableName, String courtName, InputStream is)
+ throws SQLException, CodeDocException {
+ if (conn == null) {
+ throw new SQLException("Null connection!");
+ }
+ validTable(tableName);
+ createTableIfAbsent(tableName);
+ // TODO: also handle indicies
+ // createIndicesIfAbsent(tableName);
+
+ CodeDocIterator iterator = CodeDocIterator.setup(is);
+ updateTable(tableName, courtName, iterator.getVersion(), iterator);
+ }
+
+ @Override
+ public void updateTable(
+ String tableName, String courtName, String newVersion, Iterator