Skip to content

Commit 194d500

Browse files
committed
fix(delete): do a cascade delete for project/feed source
Rather than simply deleting the single document when a feed source or project is deleted, use the object's delete method to delete related documents (for example, project feed sources or deployments) so that the database does not become cluttered with extra floating documents.
1 parent 4d38448 commit 194d500

File tree

8 files changed

+67
-62
lines changed

8 files changed

+67
-62
lines changed

src/main/java/com/conveyal/datatools/manager/controllers/api/DeploymentController.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ private static Deployment getDeployment (Request req, Response res) {
6868

6969
private static Deployment deleteDeployment (Request req, Response res) {
7070
Deployment deployment = checkDeploymentPermissions(req, res);
71-
Persistence.deployments.removeById(deployment.id);
71+
deployment.delete();
7272
return deployment;
7373
}
7474

src/main/java/com/conveyal/datatools/manager/controllers/api/FeedSourceController.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -207,15 +207,15 @@ public static FeedSource updateExternalFeedResource(Request req, Response res) t
207207
*
208208
* FIXME: Should this just set a "deleted" flag instead of removing from the database entirely?
209209
*/
210-
public static FeedSource deleteFeedSource(Request req, Response res) {
210+
private static FeedSource deleteFeedSource(Request req, Response res) {
211211
FeedSource source = requestFeedSourceById(req, "manage");
212212

213213
try {
214-
Persistence.feedSources.removeById(source.id);
214+
source.delete();
215215
return source;
216216
} catch (Exception e) {
217-
e.printStackTrace();
218-
halt(400, "Unknown error deleting feed source.");
217+
LOG.error("Could not delete feed source", e);
218+
haltWithMessage(400, "Unknown error deleting feed source.");
219219
return null;
220220
}
221221
}

src/main/java/com/conveyal/datatools/manager/controllers/api/FeedVersionController.java

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454

5555
import javax.servlet.ServletInputStream;
5656
import javax.servlet.ServletRequestWrapper;
57+
import javax.servlet.http.HttpServletResponse;
5758

5859
import static com.conveyal.datatools.common.status.MonitorableJob.JobType.BUILD_TRANSPORT_NETWORK;
5960
import static com.conveyal.datatools.common.utils.S3Utils.downloadFromS3;
@@ -80,19 +81,17 @@ enum Permission {
8081
* Grab the feed version for the ID supplied in the request.
8182
* If you pass in ?summarized=true, don't include the full tree of validation results, only the counts.
8283
*/
83-
public static FeedVersion getFeedVersion (Request req, Response res) {
84-
FeedVersion feedVersion = requestFeedVersion(req, "view");
85-
return feedVersion;
84+
private static FeedVersion getFeedVersion (Request req, Response res) {
85+
return requestFeedVersion(req, "view");
8686
}
8787

8888
/**
8989
* Get all feed versions for a given feedSource (whose ID is specified in the request).
9090
*/
91-
public static Collection<FeedVersion> getAllFeedVersionsForFeedSource(Request req, Response res) {
91+
private static Collection<FeedVersion> getAllFeedVersionsForFeedSource(Request req, Response res) {
9292
// Check permissions and get the FeedSource whose FeedVersions we want.
9393
FeedSource feedSource = requestFeedSourceById(req, "view");
94-
Collection<FeedVersion> feedVersions = feedSource.retrieveFeedVersions();
95-
return feedVersions;
94+
return feedSource.retrieveFeedVersions();
9695
}
9796

9897
public static FeedSource requestFeedSourceById(Request req, String action, String paramName) {
@@ -103,7 +102,7 @@ public static FeedSource requestFeedSourceById(Request req, String action, Strin
103102
return checkFeedSourcePermissions(req, Persistence.feedSources.getById(id), action);
104103
}
105104

106-
public static FeedSource requestFeedSourceById(Request req, String action) {
105+
private static FeedSource requestFeedSourceById(Request req, String action) {
107106
return requestFeedSourceById(req, action, "feedSourceId");
108107
}
109108

@@ -196,7 +195,7 @@ public static String createFeedVersionViaUpload(Request req, Response res) {
196195
*
197196
* OR we could just export the feed to a file and then re-import it per usual. This seems like it's wasting time/energy.
198197
*/
199-
public static boolean createFeedVersionFromSnapshot (Request req, Response res) {
198+
private static boolean createFeedVersionFromSnapshot (Request req, Response res) {
200199

201200
Auth0UserProfile userProfile = req.attribute("user");
202201
// TODO: Should the ability to create a feedVersion from snapshot be controlled by the 'edit-gtfs' privilege?
@@ -216,13 +215,13 @@ public static boolean createFeedVersionFromSnapshot (Request req, Response res)
216215
/**
217216
* Spark HTTP API handler that deletes a single feed version based on the ID in the request.
218217
*/
219-
public static FeedVersion deleteFeedVersion(Request req, Response res) {
218+
private static FeedVersion deleteFeedVersion(Request req, Response res) {
220219
FeedVersion version = requestFeedVersion(req, "manage");
221220
version.delete();
222221
return version;
223222
}
224223

225-
public static FeedVersion requestFeedVersion(Request req, String action) {
224+
private static FeedVersion requestFeedVersion(Request req, String action) {
226225
return requestFeedVersion(req, action, req.params("id"));
227226
}
228227

@@ -241,7 +240,7 @@ public static FeedVersion requestFeedVersion(Request req, String action, String
241240
* constructed in {@link #buildProfileRequest}). If a transport network does not exist for the feed version, an async
242241
* build job is kicked off. Otherwise, the transport network cache is checked for the network.
243242
*/
244-
public static String getIsochrones(Request req, Response res) {
243+
private static String getIsochrones(Request req, Response res) {
245244
if (!DataManager.isModuleEnabled("r5_network")) {
246245
haltWithMessage(400, "Isochrone generation not enabled in this application.");
247246
}
@@ -357,7 +356,7 @@ private static AnalystClusterRequest buildProfileRequest(Request req) {
357356
return clusterRequest;
358357
}
359358

360-
public static Boolean renameFeedVersion (Request req, Response res) {
359+
private static boolean renameFeedVersion (Request req, Response res) {
361360
FeedVersion v = requestFeedVersion(req, "manage");
362361

363362
String name = req.queryParams("name");
@@ -378,7 +377,7 @@ private static Object downloadFeedVersionDirectly(Request req, Response res) {
378377
* Returns credentials that a client may use to then download a feed version. Functionality
379378
* changes depending on whether application.data.use_s3_storage config property is true.
380379
*/
381-
public static Object getFeedDownloadCredentials(Request req, Response res) {
380+
private static Object getFeedDownloadCredentials(Request req, Response res) {
382381
FeedVersion version = requestFeedVersion(req, "view");
383382

384383
if (DataManager.useS3) {
@@ -398,7 +397,7 @@ public static Object getFeedDownloadCredentials(Request req, Response res) {
398397
*/
399398
private static JsonNode validate (Request req, Response res) {
400399
FeedVersion version = requestFeedVersion(req, "manage");
401-
400+
haltWithMessage(400, "Validate endpoint not currently configured!");
402401
// FIXME: Update for sql-loader validation process?
403402
return null;
404403
// return version.retrieveValidationResult(true);
@@ -426,7 +425,7 @@ private static FeedVersion publishToExternalResource (Request req, Response res)
426425
* Download locally stored feed version with token supplied by this application. This method is only used when
427426
* useS3 is set to false. Otherwise, a direct download from s3 should be used.
428427
*/
429-
private static Object downloadFeedVersionWithToken (Request req, Response res) {
428+
private static HttpServletResponse downloadFeedVersionWithToken (Request req, Response res) {
430429
String tokenValue = req.params("token");
431430
FeedDownloadToken token = Persistence.tokens.getById(tokenValue);
432431

src/main/java/com/conveyal/datatools/manager/controllers/api/ProjectController.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import com.conveyal.datatools.manager.jobs.MakePublicJob;
88
import com.conveyal.datatools.manager.jobs.MergeProjectFeedsJob;
99
import com.conveyal.datatools.manager.models.FeedDownloadToken;
10+
import com.conveyal.datatools.manager.models.FeedSource;
1011
import com.conveyal.datatools.manager.models.FeedVersion;
1112
import com.conveyal.datatools.manager.models.JsonViews;
1213
import com.conveyal.datatools.manager.models.Project;
@@ -135,10 +136,10 @@ private static Project updateProject(Request req, Response res) throws IOExcepti
135136
/**
136137
* Delete the project for the UUID given in the request.
137138
*/
138-
private static Project deleteProject(Request req, Response res) throws IOException {
139+
private static Project deleteProject(Request req, Response res) {
139140
// Fetch project first to check permissions, and so we can return the deleted project after deletion.
140141
Project project = requestProjectById(req, "manage");
141-
boolean successfullyDeleted = Persistence.projects.removeById(req.params("id"));
142+
boolean successfullyDeleted = project.delete();
142143
if (!successfullyDeleted) {
143144
haltWithMessage(400, "Did not delete project.");
144145
}

src/main/java/com/conveyal/datatools/manager/jobs/FeedUpdater.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,9 @@ private Map<String, String> checkForUpdatedFeeds() {
125125
properties.stream().map(p -> p.feedSourceId).collect(Collectors.joining(",")));
126126
}
127127
for (ExternalFeedSourceProperty prop : properties) {
128-
// FIXME: What if there are multiple props found for different feed sources.
128+
// FIXME: What if there are multiple props found for different feed sources. This could happen if
129+
// multiple projects have been synced with MTC or if the ExternalFeedSourceProperty for a feed
130+
// source is not deleted properly when the feed source is deleted.
129131
feedSource = Persistence.feedSources.getById(prop.feedSourceId);
130132
}
131133
if (feedSource == null) {

src/main/java/com/conveyal/datatools/manager/models/Deployment.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,10 @@ public String organizationId() {
434434
return project == null ? null : project.organizationId;
435435
}
436436

437+
public boolean delete() {
438+
return Persistence.deployments.removeById(this.id);
439+
}
440+
437441
/**
438442
* A summary of a FeedVersion, leaving out all of the individual validation errors.
439443
*/

src/main/java/com/conveyal/datatools/manager/models/FeedSource.java

Lines changed: 22 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -375,16 +375,6 @@ public Map<String, Map<String, String>> externalProperties() {
375375
return resourceTable;
376376
}
377377

378-
public static FeedSource retrieve(String id) {
379-
// return sourceStore.getById(id);
380-
return null;
381-
}
382-
383-
public static Collection<FeedSource> retrieveAll() {
384-
// return sourceStore.getAll();
385-
return null;
386-
}
387-
388378
/**
389379
* Get all of the feed versions for this source
390380
* @return collection of feed versions
@@ -517,35 +507,33 @@ public enum FeedRetrievalMethod {
517507

518508
/**
519509
* Delete this feed source and everything that it contains.
510+
*
511+
* FIXME: Use a Mongo transaction to handle the deletion of these related objects.
520512
*/
521-
public void delete() {
522-
retrieveFeedVersions().forEach(FeedVersion::delete);
513+
public boolean delete() {
514+
try {
515+
retrieveFeedVersions().forEach(FeedVersion::delete);
523516

524-
// delete latest copy of feed source
525-
if (DataManager.useS3) {
526-
DeleteObjectsRequest delete = new DeleteObjectsRequest(DataManager.feedBucket);
527-
delete.withKeys("public/" + this.name + ".zip", FeedStore.s3Prefix + this.id + ".zip");
528-
FeedStore.s3Client.deleteObjects(delete);
529-
}
517+
// Delete latest copy of feed source on S3.
518+
if (DataManager.useS3) {
519+
DeleteObjectsRequest delete = new DeleteObjectsRequest(DataManager.feedBucket);
520+
delete.withKeys("public/" + this.name + ".zip", FeedStore.s3Prefix + this.id + ".zip");
521+
FeedStore.s3Client.deleteObjects(delete);
522+
}
523+
// Remove all external properties for this feed source.
524+
Persistence.externalFeedSourceProperties.removeFiltered(eq("feedSourceId", this.id));
530525

531-
// Delete editor feed mapdb
532-
// TODO: does the mapdb folder need to be deleted separately?
533-
GlobalTx gtx = VersionedDataStore.getGlobalTx();
534-
if (!gtx.feeds.containsKey(id)) {
535-
gtx.rollback();
536-
}
537-
else {
538-
gtx.feeds.remove(id);
539-
gtx.commit();
540-
}
526+
// TODO: add delete for osm extract and r5 network (maybe that goes with version)
541527

542-
// FIXME use Mongo filters instead
543-
Persistence.externalFeedSourceProperties.getAll().stream()
544-
.filter(prop -> prop.feedSourceId.equals(this.id))
545-
.forEach(prop -> Persistence.externalFeedSourceProperties.removeById(prop.id));
528+
// FIXME: Should this delete related feed versions from the SQL database (for both published versions and
529+
// editor snapshots)?
546530

547-
// TODO: add delete for osm extract and r5 network (maybe that goes with version)
548-
Persistence.feedSources.removeById(this.id);
531+
// Finally, delete the feed source mongo document.
532+
return Persistence.feedSources.removeById(this.id);
533+
} catch (Exception e) {
534+
LOG.error("Could not delete feed source", e);
535+
return false;
536+
}
549537
}
550538

551539
public FeedSource clone () throws CloneNotSupportedException {

src/main/java/com/conveyal/datatools/manager/models/Project.java

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,24 @@
22

33
import com.conveyal.datatools.manager.persistence.Persistence;
44
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
5-
import com.fasterxml.jackson.annotation.JsonInclude;
6-
import com.fasterxml.jackson.annotation.JsonInclude.Include;
75
import org.slf4j.Logger;
86
import org.slf4j.LoggerFactory;
97

10-
import java.util.*;
8+
import java.util.Collection;
9+
import java.util.List;
1110
import java.util.stream.Collectors;
1211

1312
import static com.mongodb.client.model.Filters.eq;
1413

1514
/**
1615
* Represents a collection of feed sources that can be made into a deployment.
1716
* Generally, this would represent one agency that is managing the data.
18-
* For now, there is one FeedCollection per instance of GTFS data manager, but
17+
* For now, there is one Project per instance of GTFS data manager, but
1918
* we're trying to write the code in such a way that this is not necessary.
2019
*
2120
* @author mattwigway
2221
*
2322
*/
24-
@JsonInclude(Include.ALWAYS)
2523
@JsonIgnoreProperties(ignoreUnknown = true)
2624
public class Project extends Model {
2725
private static final long serialVersionUID = 1L;
@@ -101,4 +99,17 @@ public Organization retrieveOrganization() {
10199
return null;
102100
}
103101
}
102+
103+
public boolean delete() {
104+
// FIXME: Handle this in a Mongo transaction. See https://docs.mongodb.com/master/core/transactions/#transactions-and-mongodb-drivers
105+
// ClientSession clientSession = Persistence.startSession();
106+
// clientSession.startTransaction();
107+
108+
// Delete each feed source in the project (which in turn deletes each feed version).
109+
retrieveProjectFeedSources().forEach(FeedSource::delete);
110+
// Delete each deployment in the project.
111+
retrieveDeployments().forEach(Deployment::delete);
112+
// Finally, delete the project.
113+
return Persistence.projects.removeById(this.id);
114+
}
104115
}

0 commit comments

Comments
 (0)