5
5
import com .conveyal .datatools .manager .auth .Auth0UserProfile ;
6
6
import com .conveyal .datatools .manager .gtfsplus .tables .GtfsPlusTable ;
7
7
import com .conveyal .datatools .manager .models .FeedSource ;
8
+ import com .conveyal .datatools .manager .models .FeedSource .FeedRetrievalMethod ;
8
9
import com .conveyal .datatools .manager .models .FeedVersion ;
10
+ import com .conveyal .datatools .manager .models .Project ;
9
11
import com .conveyal .datatools .manager .persistence .FeedStore ;
12
+ import com .conveyal .datatools .manager .persistence .Persistence ;
10
13
import com .conveyal .gtfs .error .NewGTFSError ;
11
14
import com .conveyal .gtfs .error .NewGTFSErrorType ;
12
15
import com .conveyal .gtfs .loader .Field ;
42
45
import java .util .zip .ZipFile ;
43
46
import java .util .zip .ZipOutputStream ;
44
47
45
- import static com .conveyal .datatools .manager .jobs .MergeFeedsType .MTC ;
48
+ import static com .conveyal .datatools .manager .jobs .MergeFeedsType .SERVICE_PERIOD ;
46
49
import static com .conveyal .datatools .manager .jobs .MergeFeedsType .REGIONAL ;
50
+ import static com .conveyal .datatools .manager .models .FeedSource .FeedRetrievalMethod .REGIONAL_MERGE ;
47
51
import static com .conveyal .datatools .manager .utils .StringUtils .getCleanName ;
48
52
import static com .conveyal .gtfs .loader .DateField .GTFS_DATE_FORMATTER ;
49
53
import static com .conveyal .gtfs .loader .Field .getFieldIndex ;
56
60
* found in any other feed version. Note: There is absolutely no attempt to merge
57
61
* entities based on either expected shared IDs or entity location (e.g., stop
58
62
* coordinates).
59
- * - {@link MergeFeedsType#MTC }: this strategy is defined in detail at https://github.com/conveyal/datatools-server/issues/185,
63
+ * - {@link MergeFeedsType#SERVICE_PERIOD }: this strategy is defined in detail at https://github.com/conveyal/datatools-server/issues/185,
60
64
* but in essence, this strategy attempts to merge a current and future feed into
61
65
* a combined file. For certain entities (specifically stops and routes) it uses
62
66
* alternate fields as primary keys (stop_code and route_short_name) if they are
@@ -143,18 +147,39 @@ public MergeFeedsJob(Auth0UserProfile owner, Set<FeedVersion> feedVersions, Stri
143
147
super (owner , mergeType .equals (REGIONAL ) ? "Merging project feeds" : "Merging feed versions" ,
144
148
JobType .MERGE_FEED_VERSIONS );
145
149
this .feedVersions = feedVersions ;
146
- // Grab parent feed source if performing non-regional merge (each version should share the
147
- // same feed source).
148
- this .feedSource =
149
- mergeType .equals (REGIONAL ) ? null : feedVersions .iterator ().next ().parentFeedSource ();
150
150
// Construct full filename with extension
151
151
this .filename = String .format ("%s.zip" , file );
152
152
// If the merge type is regional, the file string should be equivalent to projectId, which
153
153
// is used by the client to download the merged feed upon job completion.
154
154
this .projectId = mergeType .equals (REGIONAL ) ? file : null ;
155
155
this .mergeType = mergeType ;
156
156
// Assuming job is successful, mergedVersion will contain the resulting feed version.
157
- this .mergedVersion = mergeType .equals (REGIONAL ) ? null : new FeedVersion (this .feedSource );
157
+ Project project = Persistence .projects .getById (projectId );
158
+ // Grab parent feed source depending on merge type.
159
+ FeedSource regionalFeedSource = null ;
160
+ // If storing a regional merge as a new version, find the feed source designated by the project.
161
+ if (mergeType .equals (REGIONAL )) {
162
+ regionalFeedSource = Persistence .feedSources .getById (project .regionalFeedSourceId );
163
+ // Create new feed source if this is the first regional merge.
164
+ if (regionalFeedSource == null ) {
165
+ regionalFeedSource = new FeedSource ("REGIONAL MERGE" , project .id , REGIONAL_MERGE );
166
+ // Store new feed source.
167
+ Persistence .feedSources .create (regionalFeedSource );
168
+ // Update regional feed source ID on project.
169
+ project .regionalFeedSourceId = regionalFeedSource .id ;
170
+ Persistence .projects .replace (project .id , project );
171
+ }
172
+ }
173
+ // Assign regional feed source or simply the first parent feed source found in the feed version list (these
174
+ // should all belong to the same feed source if the merge is not regional).
175
+ this .feedSource = mergeType .equals (REGIONAL )
176
+ ? regionalFeedSource
177
+ : feedVersions .iterator ().next ().parentFeedSource ();
178
+ // Set feed source for merged version.
179
+ this .mergedVersion = new FeedVersion (this .feedSource );
180
+ this .mergedVersion .retrievalMethod = mergeType .equals (REGIONAL )
181
+ ? FeedRetrievalMethod .REGIONAL_MERGE
182
+ : FeedRetrievalMethod .SERVICE_PERIOD_MERGE ;
158
183
this .mergeFeedsResult = new MergeFeedsResult (mergeType );
159
184
}
160
185
@@ -226,21 +251,22 @@ public void jobFinished() {
226
251
}
227
252
// Close output stream for zip file.
228
253
out .close ();
229
- // Handle writing file to storage (local or s3).
230
254
if (mergeFeedsResult .failed ) {
255
+ // Fail job if the merge result indicates something went wrong.
231
256
status .fail ("Merging feed versions failed." );
232
257
} else {
258
+ // Store feed locally and (if applicable) upload regional feed to S3.
233
259
storeMergedFeed ();
234
260
status .update (false , "Merged feed created successfully." , 100 , true );
235
261
}
236
262
LOG .info ("Feed merge is complete." );
237
- if (!mergeType .equals (REGIONAL ) && !status .error && !mergeFeedsResult .failed ) {
238
- // Handle the processing of the new version for non-regional merges (note: s3 upload is handled within this job).
263
+ if (mergedVersion != null && !status .error && !mergeFeedsResult .failed ) {
264
+ mergedVersion .hash ();
265
+ mergedVersion .inputVersions = feedVersions .stream ().map (FeedVersion ::retrieveId ).collect (Collectors .toSet ());
266
+ // Handle the processing of the new version when storing new version (note: s3 upload is handled within this job).
239
267
// We must add this job in jobLogic (rather than jobFinished) because jobFinished is called after this job's
240
268
// subJobs are run.
241
- ProcessSingleFeedJob processSingleFeedJob =
242
- new ProcessSingleFeedJob (mergedVersion , owner , true );
243
- addNextJob (processSingleFeedJob );
269
+ addNextJob (new ProcessSingleFeedJob (mergedVersion , owner , true ));
244
270
}
245
271
}
246
272
@@ -254,8 +280,7 @@ private List<FeedToMerge> collectAndSortFeeds(Set<FeedVersion> feedVersions) {
254
280
try {
255
281
return new FeedToMerge (version );
256
282
} catch (Exception e ) {
257
- LOG .error ("Could not create zip file for version {}:" , version .parentFeedSource (),
258
- version .version );
283
+ LOG .error ("Could not create zip file for version: {}" , version .version );
259
284
return null ;
260
285
}
261
286
}).filter (Objects ::nonNull ).filter (entry -> entry .version .validationResult != null
@@ -271,6 +296,16 @@ private List<FeedToMerge> collectAndSortFeeds(Set<FeedVersion> feedVersions) {
271
296
* Otherwise, it will write to a new version.
272
297
*/
273
298
private void storeMergedFeed () throws IOException {
299
+ if (mergedVersion != null ) {
300
+ // Store the zip file for the merged feed version.
301
+ try {
302
+ FeedVersion .feedStore .newFeed (mergedVersion .id , new FileInputStream (mergedTempFile ), feedSource );
303
+ } catch (IOException e ) {
304
+ LOG .error ("Could not store merged feed for new version" );
305
+ throw e ;
306
+ }
307
+ }
308
+ // Write the new latest regional merge file to s3://$BUCKET/project/$PROJECT_ID.zip
274
309
if (mergeType .equals (REGIONAL )) {
275
310
status .update (false , "Saving merged feed." , 95 );
276
311
// Store the project merged zip locally or on s3
@@ -289,15 +324,6 @@ private void storeMergedFeed() throws IOException {
289
324
throw e ;
290
325
}
291
326
}
292
- } else {
293
- // Store the zip file for the merged feed version.
294
- try {
295
- FeedVersion .feedStore
296
- .newFeed (mergedVersion .id , new FileInputStream (mergedTempFile ), feedSource );
297
- } catch (IOException e ) {
298
- LOG .error ("Could not store merged feed for new version" );
299
- throw e ;
300
- }
301
327
}
302
328
}
303
329
@@ -315,7 +341,7 @@ private int constructMergedTable(Table table, List<FeedToMerge> feedsToMerge,
315
341
CsvListWriter writer = new CsvListWriter (new OutputStreamWriter (out ), CsvPreference .STANDARD_PREFERENCE );
316
342
String keyField = table .getKeyFieldName ();
317
343
String orderField = table .getOrderFieldName ();
318
- if (mergeType .equals (MTC )) {
344
+ if (mergeType .equals (SERVICE_PERIOD ) && DataManager . isExtensionEnabled ( "mtc" )) {
319
345
// MTC requires that the stop and route records be merged based on different key fields.
320
346
switch (table .name ) {
321
347
case "stops" :
@@ -389,7 +415,7 @@ private int constructMergedTable(Table table, List<FeedToMerge> feedsToMerge,
389
415
// Iterate over rows in table, writing them to the out file.
390
416
while (csvReader .readRecord ()) {
391
417
String keyValue = csvReader .get (keyFieldIndex );
392
- if (feedIndex > 0 && mergeType .equals (MTC )) {
418
+ if (feedIndex > 0 && mergeType .equals (SERVICE_PERIOD )) {
393
419
// Always prefer the "future" file for the feed_info table, which means
394
420
// we can skip any iterations following the first one. If merging the agency
395
421
// table, we should only skip the following feeds if performing an MTC merge
@@ -454,7 +480,7 @@ private int constructMergedTable(Table table, List<FeedToMerge> feedsToMerge,
454
480
}
455
481
fieldsFoundList = Arrays .asList (fieldsFoundInZip );
456
482
}
457
- if (mergeType .equals (MTC ) && table .name .equals ("stops" )) {
483
+ if (mergeType .equals (SERVICE_PERIOD ) && table .name .equals ("stops" )) {
458
484
// For the first line of the stops table, check that the alt. key
459
485
// field (stop_code) is present. If it is not, revert to the original
460
486
// key field. This is only pertinent for the MTC merge type.
@@ -541,7 +567,7 @@ private int constructMergedTable(Table table, List<FeedToMerge> feedsToMerge,
541
567
// reference tracker will get far too large if we attempt to use it to
542
568
// track references for a large number of feeds (e.g., every feed in New
543
569
// York State).
544
- if (mergeType .equals (MTC )) {
570
+ if (mergeType .equals (SERVICE_PERIOD )) {
545
571
Set <NewGTFSError > idErrors ;
546
572
// If analyzing the second feed (non-future feed), the service_id always gets feed scoped.
547
573
// See https://github.com/ibi-group/datatools-server/issues/244
@@ -834,7 +860,7 @@ private int constructMergedTable(Table table, List<FeedToMerge> feedsToMerge,
834
860
rowValues [specFieldIndex ] = valueToWrite ;
835
861
} // End of iteration over each field for a row.
836
862
// Do not write rows that are designated to be skipped.
837
- if (skipRecord && this .mergeType .equals (MTC )) {
863
+ if (skipRecord && this .mergeType .equals (SERVICE_PERIOD )) {
838
864
mergeFeedsResult .recordsSkipCount ++;
839
865
continue ;
840
866
}
0 commit comments