Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge (current/future) feed versions for MTC #186

Merged
merged 29 commits into from
May 6, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
fa9ec49
initial work on merge feeds for MTC
landonreed Oct 11, 2018
8f8f6c4
Merge branch 'dev' into merge-feed-versions-mtc
landonreed Jan 28, 2019
9e76476
refactor(feed-merge): handle merging projects + two versions for feed…
landonreed Feb 1, 2019
cf7208c
Merge remote-tracking branch 'origin/fix-gtfs-plus-upload-job-fixes' …
landonreed Feb 5, 2019
b6d1058
feat(merge-feeds): add MTC merge feeds strategy
landonreed Feb 15, 2019
2fc62d1
refactor(job): remove unused job type
landonreed Feb 15, 2019
1e7014b
refactor(merge-feeds): improve comments, add job#validationResult
landonreed Feb 15, 2019
c4109c4
refactor: address PR comments
landonreed Feb 18, 2019
f84dd4c
build(pom): update gtfs-lib to snapshot version for testing
landonreed Feb 19, 2019
adb9b0d
Merge branch 'dev' into merge-feed-versions-mtc
Mar 1, 2019
bc0b450
test(merge-feeds): add tests for MTC and REGIONAL merge types
landonreed Mar 28, 2019
10c253f
Merge branch 'dev' into merge-feed-versions-mtc
Apr 1, 2019
fb3a2d8
refactor(junit): refactor tests to use junit4
landonreed Apr 1, 2019
72c57f6
build(pom): add junit 4
landonreed Apr 1, 2019
6b06107
test: create database if not exists
landonreed Apr 1, 2019
c3b9c9e
test: add log
landonreed Apr 1, 2019
b0f0260
test(ci): create database using travis
landonreed Apr 1, 2019
97e742f
build(travis): always create postgres db for tests
Apr 3, 2019
9860110
Merge branch 'dev' into merge-feed-versions-mtc
landonreed Apr 11, 2019
d94f40e
refactor(merge-feeds): address PR comments
landonreed Apr 23, 2019
126f61d
fix(merge-feeds): address remaining PR comments
landonreed Apr 24, 2019
d2276a2
build(pom): bump gtfs-lib to 4.3.3
Apr 25, 2019
6d9db08
fix(merge-feeds): add test for errors; fix missing stop_code bug
landonreed May 2, 2019
bcc5cbb
build(travis): remove oraclejdk8 to fix build
May 2, 2019
04c9c47
build(travis): use jdk 8 and linux trusty dist
landonreed May 2, 2019
7f69f67
refactor(merge-feeds): improve error message for agency_id mismatch
landonreed May 2, 2019
a590fc3
build(pom): update gtfs-lib to 4.3.4
landonreed May 3, 2019
f81ba52
Merge branch 'dev' into merge-feed-versions-mtc
landonreed May 3, 2019
b9e95a0
refactor(merge-feeds): add description for exception
landonreed May 6, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 13 additions & 5 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
dist: trusty # jdk 8 not available on xenial
language: java
jdk:
- oraclejdk8
language: java
java:
- oraclejdk8
install: true
sudo: false
# Install mongoDB to perform persistence tests
services: mongodb
services:
- mongodb
- postgresql
addons:
postgresql: 9.6
cache:
directories:
- "$HOME/.m2"
- "$HOME/.cache/yarn"
- $HOME/.m2
- $HOME/.cache/yarn
# Install semantic-release
before_script:
- yarn global add @conveyal/maven-semantic-release semantic-release@15
Expand All @@ -17,6 +23,8 @@ before_install:
# set region in AWS config for S3 setup
- mkdir ~/.aws && printf '%s\n' '[default]' 'aws_access_key_id=foo' 'aws_secret_access_key=bar' 'region=us-east-1' > ~/.aws/config
- cp configurations/default/server.yml.tmp configurations/default/server.yml
# create database for tests
- psql -U postgres -c 'CREATE DATABASE catalogue;'
script:
# package jar
- mvn package
Expand Down
33 changes: 23 additions & 10 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -243,11 +243,22 @@
<version>2.1.0</version>
</dependency>

<!-- Used for loading/fetching/writing GTFS entities (also provides access to commons-io and AWS S3 SDK). -->
<!-- Used for testing (note: this should match the version in gtfs-lib). -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>

<!-- Used for loading/fetching/writing GTFS entities. gtfs-lib also provides access to:
- commons-io - generic utilities
- AWS S3 SDK - putting/getting objects into/out of S3.
-->
<dependency>
<groupId>com.conveyal</groupId>
<artifactId>gtfs-lib</artifactId>
<version>4.3.2</version>
<version>4.3.4</version>
</dependency>

<!-- Used for data-tools application database -->
Expand Down Expand Up @@ -313,14 +324,6 @@
<version>19.2</version>
</dependency>

<!-- Unit testing -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>

<!-- Error reporting -->
<dependency>
<groupId>com.bugsnag</groupId>
Expand Down Expand Up @@ -363,6 +366,16 @@
<version>2.14.0</version>
<scope>test</scope>
</dependency>
<!-- Used for writing csv for merged feeds. Note: this appears to be one of the only
CSV libraries that will only quote values when necessary (e.g., there is a comma character
contained within the value) and that will work with an output stream writer when writing
directly to a zip output stream.
-->
<dependency>
<groupId>net.sf.supercsv</groupId>
<artifactId>super-csv</artifactId>
<version>2.4.0</version>
</dependency>
</dependencies>

</project>
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public enum JobType {
EXPORT_SNAPSHOT_TO_GTFS,
CONVERT_EDITOR_MAPDB_TO_SQL,
VALIDATE_ALL_FEEDS,
MERGE_PROJECT_FEEDS
MERGE_FEED_VERSIONS
}

public MonitorableJob(String owner, String name, JobType type) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package com.conveyal.datatools.manager.controllers.api;

import com.conveyal.datatools.common.utils.SparkUtils;
import com.conveyal.datatools.manager.DataManager;
import com.conveyal.datatools.manager.auth.Auth0UserProfile;
import com.conveyal.datatools.manager.jobs.CreateFeedVersionFromSnapshotJob;
import com.conveyal.datatools.manager.jobs.MergeFeedsJob;
import com.conveyal.datatools.manager.jobs.MergeFeedsType;
import com.conveyal.datatools.manager.jobs.ProcessSingleFeedJob;
import com.conveyal.datatools.manager.models.FeedDownloadToken;
import com.conveyal.datatools.manager.models.FeedSource;
Expand All @@ -13,6 +16,7 @@
import com.conveyal.datatools.manager.persistence.Persistence;
import com.conveyal.datatools.manager.utils.HashUtils;
import com.conveyal.datatools.manager.utils.json.JsonManager;

import com.fasterxml.jackson.databind.JsonNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -23,13 +27,16 @@
import java.io.File;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;

import static com.conveyal.datatools.common.utils.S3Utils.downloadFromS3;
import static com.conveyal.datatools.common.utils.SparkUtils.copyRequestStreamIntoFile;
import static com.conveyal.datatools.common.utils.SparkUtils.downloadFile;
import static com.conveyal.datatools.common.utils.SparkUtils.formatJobMessage;
import static com.conveyal.datatools.common.utils.SparkUtils.logMessageAndHalt;
import static com.conveyal.datatools.manager.controllers.api.FeedSourceController.checkFeedSourcePermissions;
import static com.conveyal.datatools.manager.jobs.MergeFeedsType.REGIONAL;
import static spark.Spark.delete;
import static spark.Spark.get;
import static spark.Spark.post;
Expand Down Expand Up @@ -262,6 +269,52 @@ private static FeedVersion publishToExternalResource (Request req, Response res)
}
}

/**
* HTTP controller that handles merging multiple feed versions for a given feed source, with version IDs specified
* in a comma-separated string in the feedVersionIds query parameter and merge type specified in mergeType query
* parameter. NOTE: REGIONAL merge type should only be handled through {@link ProjectController#mergeProjectFeeds(Request, Response)}.
*/
private static String mergeFeedVersions(Request req, Response res) {
String[] versionIds = req.queryParams("feedVersionIds").split(",");
// Try to parse merge type (null or bad value throws IllegalArgumentException).
MergeFeedsType mergeType;
try {
mergeType = MergeFeedsType.valueOf(req.queryParams("mergeType"));
if (mergeType.equals(REGIONAL)) {
throw new IllegalArgumentException("Regional merge type is not permitted for this endpoint.");
}
} catch (IllegalArgumentException e) {
logMessageAndHalt(req, 400, "Must provide valid merge type.", e);
return null;
}
// Collect versions to merge (must belong to same feed source).
Set<FeedVersion> versions = new HashSet<>();
String feedSourceId = null;
for (String id : versionIds) {
FeedVersion v = Persistence.feedVersions.getById(id);
if (v == null) {
logMessageAndHalt(req,
400,
String.format("Must provide valid version ID. (No version exists for id=%s.)", id)
);
}
// Store feed source id and check other versions for matching.
if (feedSourceId == null) feedSourceId = v.feedSourceId;
else if (!v.feedSourceId.equals(feedSourceId)) {
logMessageAndHalt(req, 400, "Cannot merge versions with different parent feed sources.");
}
versions.add(v);
}
if (versionIds.length != 2) {
logMessageAndHalt(req, 400, "Merging more than two versions is not currently supported.");
}
// Kick off merge feeds job.
Auth0UserProfile userProfile = req.attribute("user");
MergeFeedsJob mergeFeedsJob = new MergeFeedsJob(userProfile.getUser_id(), versions, "merged", mergeType);
DataManager.heavyExecutor.execute(mergeFeedsJob);
return SparkUtils.formatJobMessage(mergeFeedsJob.jobId, "Merging feed versions...");
}

/**
* Download locally stored feed version with token supplied by this application. This method is only used when
* useS3 is set to false. Otherwise, a direct download from s3 should be used.
Expand Down Expand Up @@ -300,6 +353,7 @@ public static void register (String apiPrefix) {
post(apiPrefix + "secure/feedversion", FeedVersionController::createFeedVersionViaUpload, json::write);
post(apiPrefix + "secure/feedversion/fromsnapshot", FeedVersionController::createFeedVersionFromSnapshot, json::write);
put(apiPrefix + "secure/feedversion/:id/rename", FeedVersionController::renameFeedVersion, json::write);
put(apiPrefix + "secure/feedversion/merge", FeedVersionController::mergeFeedVersions, json::write);
post(apiPrefix + "secure/feedversion/:id/publish", FeedVersionController::publishToExternalResource, json::write);
delete(apiPrefix + "secure/feedversion/:id", FeedVersionController::deleteFeedVersion, json::write);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@
import com.conveyal.datatools.manager.auth.Auth0UserProfile;
import com.conveyal.datatools.manager.jobs.FetchProjectFeedsJob;
import com.conveyal.datatools.manager.jobs.MakePublicJob;
import com.conveyal.datatools.manager.jobs.MergeProjectFeedsJob;
import com.conveyal.datatools.manager.jobs.MergeFeedsJob;
import com.conveyal.datatools.manager.models.FeedDownloadToken;
import com.conveyal.datatools.manager.models.FeedSource;
import com.conveyal.datatools.manager.models.FeedVersion;
import com.conveyal.datatools.manager.models.JsonViews;
import com.conveyal.datatools.manager.models.Project;
Expand All @@ -20,13 +21,16 @@
import spark.Response;

import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;

import static com.conveyal.datatools.common.utils.S3Utils.downloadFromS3;
import static com.conveyal.datatools.common.utils.SparkUtils.downloadFile;
import static com.conveyal.datatools.common.utils.SparkUtils.formatJobMessage;
import static com.conveyal.datatools.common.utils.SparkUtils.logMessageAndHalt;
import static com.conveyal.datatools.manager.DataManager.publicPath;
import static com.conveyal.datatools.manager.jobs.MergeFeedsType.REGIONAL;
import static spark.Spark.delete;
import static spark.Spark.get;
import static spark.Spark.post;
Expand Down Expand Up @@ -216,14 +220,28 @@ private static Project checkProjectPermissions(Request req, Project project, Str
* to getFeedDownloadCredentials with the project ID to obtain either temporary S3 credentials or a download token
* (depending on application configuration "application.data.use_s3_storage") to download the zip file.
*/
private static String downloadMergedFeed(Request req, Response res) {
static String mergeProjectFeeds(Request req, Response res) {
Project project = requestProjectById(req, "view");
Auth0UserProfile userProfile = req.attribute("user");
// TODO: make this an authenticated call?
MergeProjectFeedsJob mergeProjectFeedsJob = new MergeProjectFeedsJob(project, userProfile.getUser_id());
DataManager.heavyExecutor.execute(mergeProjectFeedsJob);
Set<FeedVersion> feedVersions = new HashSet<>();
// Get latest version for each feed source in project
Collection<FeedSource> feedSources = project.retrieveProjectFeedSources();
for (FeedSource fs : feedSources) {
// check if feed version exists
FeedVersion version = fs.retrieveLatest();
if (version == null) {
LOG.warn("Skipping {} because it has no feed versions", fs.name);
continue;
}
// modify feed version to use prepended feed id
LOG.info("Adding {} feed to merged zip", fs.name);
feedVersions.add(version);
}
MergeFeedsJob mergeFeedsJob = new MergeFeedsJob(userProfile.getUser_id(), feedVersions, project.id, REGIONAL);
DataManager.heavyExecutor.execute(mergeFeedsJob);
// Return job ID to requester for monitoring job status.
return formatJobMessage(mergeProjectFeedsJob.jobId, "Merge operation is processing.");
return formatJobMessage(mergeFeedsJob.jobId, "Merge operation is processing.");
}

/**
Expand Down Expand Up @@ -310,7 +328,7 @@ public static void register (String apiPrefix) {
post(apiPrefix + "secure/project/:id/fetch", ProjectController::fetch, json::write);
post(apiPrefix + "secure/project/:id/deployPublic", ProjectController::publishPublicFeeds, json::write);

get(apiPrefix + "secure/project/:id/download", ProjectController::downloadMergedFeed);
get(apiPrefix + "secure/project/:id/download", ProjectController::mergeProjectFeeds);
get(apiPrefix + "secure/project/:id/downloadtoken", ProjectController::getFeedDownloadCredentials, json::write);

get(apiPrefix + "public/project/:id", ProjectController::getProject, json::write);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.conveyal.datatools.manager.gtfsplus;

import com.conveyal.gtfs.model.Entity;

import javax.naming.OperationNotSupportedException;
import java.sql.PreparedStatement;
import java.sql.SQLException;

public class CalendarAttribute extends Entity {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs javadoc

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above comment: I'm not really sure what I would say for each of these tables. We don't really have javadoc for these entities on the GTFS side of things, so I wonder why we would need it here.


private static final long serialVersionUID = 1L;

public String service_id;
public String service_description;

@Override public void setStatementParameters(PreparedStatement statement, boolean setDefaultId) {
throw new UnsupportedOperationException(
"Cannot call setStatementParameters because loading a GTFS+ table into RDBMS is unsupported.");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.conveyal.datatools.manager.gtfsplus;

import com.conveyal.gtfs.model.Entity;

import java.sql.PreparedStatement;
import java.sql.SQLException;

public class Direction extends Entity {
landonreed marked this conversation as resolved.
Show resolved Hide resolved

private static final long serialVersionUID = 1L;

public String route_id;
public int direction_id;
public String direction;


@Override
public void setStatementParameters(PreparedStatement statement, boolean setDefaultId) throws SQLException {
throw new UnsupportedOperationException(
"Cannot call setStatementParameters because loading a GTFS+ table into RDBMS is unsupported.");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.conveyal.datatools.manager.gtfsplus;

import com.conveyal.gtfs.model.Entity;

import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.time.LocalDate;

public class FareRiderCategory extends Entity {
landonreed marked this conversation as resolved.
Show resolved Hide resolved

private static final long serialVersionUID = 1L;

public String fare_id;
public int rider_category_id;
public double price;
public LocalDate expiration_date;
public LocalDate commencement_date;

@Override
public void setStatementParameters(PreparedStatement statement, boolean setDefaultId) throws SQLException {
throw new UnsupportedOperationException(
"Cannot call setStatementParameters because loading a GTFS+ table into RDBMS is unsupported.");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.conveyal.datatools.manager.gtfsplus;

import com.conveyal.gtfs.model.Entity;

import java.sql.PreparedStatement;
import java.sql.SQLException;

public class FareZoneAttribute extends Entity {
landonreed marked this conversation as resolved.
Show resolved Hide resolved

private static final long serialVersionUID = 1L;

public String zone_id;
public String zone_name;

@Override
public void setStatementParameters(PreparedStatement statement, boolean setDefaultId) throws SQLException {
throw new UnsupportedOperationException(
"Cannot call setStatementParameters because loading a GTFS+ table into RDBMS is unsupported.");
}
}
Loading