Repository: ibi-group/datatools-server
Issue: New MTC feed source does not contain nulled out MTC external feed source properties

## Observed behavior

Creating a new feed source for MTC results in no external feed source properties being available in the UI (`Feed Source > Settings > MTC Properties`). The workflow for creating a new feed requires that these props be available because in order to register a new feed with MTC's external database, the `AgencyId` prop must be changed from null to a two letter string value.

## Expected behavior

MTC properties with null values should be initialized.

## Steps to reproduce the problem

Ensure MTC config is enabled. Create feed source. Go to `Feed Source > Settings > MTC Properties`. No properties are visible.

## Any special notes on configuration used

MTC extension required

Changed files:
src/main/java/com/conveyal/datatools/manager/extensions/mtc/RtdCarrier.java
src/main/java/com/conveyal/datatools/manager/extensions/ExternalFeedResource.java
src/main/java/com/conveyal/datatools/manager/extensions/mtc/MtcFeedResource.java
src/main/java/com/conveyal/datatools/manager/models/FeedSource.java

diff --git a/src/main/java/com/conveyal/datatools/manager/extensions/ExternalFeedResource.java b/src/main/java/com/conveyal/datatools/manager/extensions/ExternalFeedResource.java
index d3c0c61b..c761449d 100644
--- a/src/main/java/com/conveyal/datatools/manager/extensions/ExternalFeedResource.java
+++ b/src/main/java/com/conveyal/datatools/manager/extensions/ExternalFeedResource.java
@@ -16,7 +16,7 @@ public interface ExternalFeedResource {
 
     public void importFeedsForProject(Project project, String authHeader) throws Exception;
 
-    public void feedSourceCreated(FeedSource source, String authHeader);
+    public void feedSourceCreated(FeedSource source, String authHeader) throws Exception;
 
     public void propertyUpdated(ExternalFeedSourceProperty property, String previousValue, String authHeader) throws IOException;
 
diff --git a/src/main/java/com/conveyal/datatools/manager/extensions/mtc/MtcFeedResource.java b/src/main/java/com/conveyal/datatools/manager/extensions/mtc/MtcFeedResource.java
index 5b7eb075..99e2125b 100644
--- a/src/main/java/com/conveyal/datatools/manager/extensions/mtc/MtcFeedResource.java
+++ b/src/main/java/com/conveyal/datatools/manager/extensions/mtc/MtcFeedResource.java
@@ -120,16 +120,7 @@ public class MtcFeedResource implements ExternalFeedResource {
 
                 // Create / update the properties
                 LOG.info("Updating props for {}", source.name);
-                for(Field carrierField : carrier.getClass().getDeclaredFields()) {
-                    String fieldName = carrierField.getName();
-                    String fieldValue = carrierField.get(carrier) != null ? carrierField.get(carrier).toString() : null;
-                    ExternalFeedSourceProperty prop = new ExternalFeedSourceProperty(source, this.getResourceType(), fieldName, fieldValue);
-                    if (Persistence.externalFeedSourceProperties.getById(prop.id) == null) {
-                        Persistence.externalFeedSourceProperties.create(prop);
-                    } else {
-                        Persistence.externalFeedSourceProperties.updateField(prop.id, fieldName, fieldValue);
-                    }
-                }
+                carrier.updateFields(source);
             }
         } catch(Exception ex) {
             LOG.error("Could not read feeds from MTC RTD API");
@@ -138,12 +129,15 @@ public class MtcFeedResource implements ExternalFeedResource {
     }
 
     /**
-     * Do nothing for now. Creating a new agency for RTD requires adding the AgencyId property (when it was previously
-     * null. See {@link #propertyUpdated(ExternalFeedSourceProperty, String, String)}.
+     * Generate blank external feed resource properties when a new feed source is created. Creating a new agency for RTD
+     * requires adding the AgencyId property (when it was previously null. See {@link #propertyUpdated(ExternalFeedSourceProperty, String, String)}.
      */
     @Override
-    public void feedSourceCreated(FeedSource source, String authHeader) {
-        LOG.info("Processing new FeedSource {} for RTD. (No action taken.)", source.name);
+    public void feedSourceCreated(FeedSource source, String authHeader) throws IllegalAccessException {
+        LOG.info("Processing new FeedSource {} for RTD. Empty external feed properties being generated.", source.name);
+        // Create a blank carrier and update fields (will initialize all fields to null).
+        RtdCarrier carrier = new RtdCarrier();
+        carrier.updateFields(source);
     }
 
     /**
diff --git a/src/main/java/com/conveyal/datatools/manager/extensions/mtc/RtdCarrier.java b/src/main/java/com/conveyal/datatools/manager/extensions/mtc/RtdCarrier.java
index 768acdf9..920d7ee3 100644
--- a/src/main/java/com/conveyal/datatools/manager/extensions/mtc/RtdCarrier.java
+++ b/src/main/java/com/conveyal/datatools/manager/extensions/mtc/RtdCarrier.java
@@ -1,12 +1,17 @@
 package com.conveyal.datatools.manager.extensions.mtc;
 
+import com.conveyal.datatools.manager.models.ExternalFeedSourceProperty;
 import com.conveyal.datatools.manager.models.FeedSource;
 import com.conveyal.datatools.manager.persistence.Persistence;
 import com.fasterxml.jackson.annotation.JsonProperty;
 
+import java.lang.reflect.Field;
+
 import static com.conveyal.datatools.manager.models.ExternalFeedSourceProperty.constructId;
 
 /**
+ * Represents all of the properties persisted on a carrier record by the external MTC database known as RTD.
+ *
  * Created by demory on 3/30/16.
  */
 
@@ -63,11 +68,12 @@ public class RtdCarrier {
     @JsonProperty
     String EditedDate;
 
+    /** Empty constructor needed for serialization (also used to create empty carrier). */
     public RtdCarrier() {
     }
 
     /**
-     * Construct an RtdCarrier given the provided feed source.
+     * Construct an RtdCarrier given the provided feed source and initialize all field values from MongoDB.
      * @param source
      */
     public RtdCarrier(FeedSource source) {
@@ -93,9 +99,38 @@ public class RtdCarrier {
     }
 
     /**
-     * FIXME: Are there cases where this might throw NPEs?
+     * Get the value stored in the database for a particular field.
+     *
+     * TODO: Are there cases where this might throw NPEs?
      */
     private String getValueForField (FeedSource source, String fieldName) {
         return Persistence.externalFeedSourceProperties.getById(getPropId(source, fieldName)).value;
     }
+
+    /**
+     * Use reflection to update (or create if field does not exist) all fields for a carrier instance and provided feed
+     * source.
+     *
+     * TODO: Perhaps we should not be using reflection, but it works pretty well here.
+     */
+    public void updateFields(FeedSource feedSource) throws IllegalAccessException {
+        // Using reflection, iterate over every field in the class.
+        for(Field carrierField : this.getClass().getDeclaredFields()) {
+            String fieldName = carrierField.getName();
+            String fieldValue = carrierField.get(this) != null ? carrierField.get(this).toString() : null;
+            // Construct external feed source property for field with value from carrier.
+            ExternalFeedSourceProperty prop = new ExternalFeedSourceProperty(
+                feedSource,
+                MtcFeedResource.RESOURCE_TYPE,
+                fieldName,
+                fieldValue
+            );
+            // If field does not exist, create it. Otherwise, update value.
+            if (Persistence.externalFeedSourceProperties.getById(prop.id) == null) {
+                Persistence.externalFeedSourceProperties.create(prop);
+            } else {
+                Persistence.externalFeedSourceProperties.updateField(prop.id, fieldName, fieldValue);
+            }
+        }
+    }
 }
\\ No newline at end of file
diff --git a/src/main/java/com/conveyal/datatools/manager/models/FeedSource.java b/src/main/java/com/conveyal/datatools/manager/models/FeedSource.java
index 3f428a0b..fa18e3aa 100644
--- a/src/main/java/com/conveyal/datatools/manager/models/FeedSource.java
+++ b/src/main/java/com/conveyal/datatools/manager/models/FeedSource.java
@@ -30,6 +30,7 @@ import java.util.HashMap;
 import java.util.Map;
 
 import static com.conveyal.datatools.manager.utils.StringUtils.getCleanName;
+import static com.mongodb.client.model.Filters.and;
 import static com.mongodb.client.model.Filters.eq;
 
 /**
@@ -391,10 +392,10 @@ public class FeedSource extends Model implements Cloneable {
         for(String resourceType : DataManager.feedResources.keySet()) {
             Map<String, String> propTable = new HashMap<>();
 
-            // FIXME: use mongo filters instead
-            Persistence.externalFeedSourceProperties.getAll().stream()
-                    .filter(prop -> prop.feedSourceId.equals(this.id))
-                    .forEach(prop -> propTable.put(prop.name, prop.value));
+            // Get all external properties for the feed source/resource type and fill prop table.
+            Persistence.externalFeedSourceProperties
+                .getFiltered(and(eq("feedSourceId", this.id), eq("resourceType", resourceType)))
+                .forEach(prop -> propTable.put(prop.name, prop.value));
 
             resourceTable.put(resourceType, propTable);
         }