Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
2cd20a4
HTM-1736 | HTM-1737: API and code to manage attachments
mprins Nov 6, 2025
6db2955
Add Transactional tp delete because we need the lazy-loaded attachmen…
mprins Nov 6, 2025
463b0aa
Change to PUT, remove mime type check
matthijsln Nov 6, 2025
85b7fbc
Temp workaround: type of foreign key is not always string (use Long f…
matthijsln Nov 6, 2025
e82fe2c
first check the type, then the feature, this could save one database …
mprins Nov 6, 2025
0dea8c0
HTM-1764: AttachmentsHelper should check the type of primary key of t…
mprins Nov 6, 2025
e940613
Merge branch 'attachments_api_2' of github.com:Tailormap/tailormap-ap…
mprins Nov 6, 2025
b900b16
Update src/main/resources/openapi/viewer-api.yaml
mprins Nov 6, 2025
1ae7db1
Update src/main/java/org/tailormap/api/controller/AttachmentsControll…
mprins Nov 6, 2025
6a63c91
Update src/main/resources/openapi/viewer-api.yaml
mprins Nov 6, 2025
89d52ec
Update src/test/java/org/tailormap/api/controller/AttachmentsControll…
mprins Nov 6, 2025
1909b85
Update src/test/java/org/tailormap/api/controller/AttachmentsControll…
mprins Nov 6, 2025
c447b59
Merge branch 'describe-attachment-attributes' into workaround-attachm…
matthijsln Nov 6, 2025
d4cf326
Merge branch 'main' into attachments_api_2
mprins Nov 7, 2025
6c32b4f
Merge branch 'describe-attachment-attributes' into workaround-attachm…
matthijsln Nov 7, 2025
caef7c8
Merge branch 'attachments_api_2' into workaround-attachment-upload
matthijsln Nov 7, 2025
94303f8
Use plurals for the attachments endpoint that related to a feature an…
mprins Nov 7, 2025
09d460d
Merge branch 'attachments_api_2' into workaround-attachment-upload
matthijsln Nov 7, 2025
072e04a
Fix attachment primary key param
matthijsln Nov 7, 2025
2a71c04
Fix attachment primary key param using primary key object from loadin…
matthijsln Nov 7, 2025
0527b55
Merge branch 'main' into workaround-attachment-upload
matthijsln Nov 7, 2025
7920636
Merge branch 'main' into workaround-attachment-upload
matthijsln Nov 11, 2025
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
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@
import java.nio.ByteBuffer;
import java.sql.SQLException;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.UUID;
import org.geotools.api.data.Query;
import org.geotools.api.data.SimpleFeatureSource;
import org.geotools.api.filter.Filter;
import org.geotools.api.filter.FilterFactory;
import org.geotools.data.simple.SimpleFeatureIterator;
import org.geotools.factory.CommonFactoryFinder;
import org.geotools.util.factory.GeoTools;
import org.slf4j.Logger;
Expand All @@ -31,7 +33,7 @@
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.server.ResponseStatusException;
import org.tailormap.api.annotation.AppRestController;
Expand Down Expand Up @@ -74,7 +76,7 @@
* @param fileData the attachment file data
* @return the response entity
*/
@PutMapping(
@PostMapping(
path = {
"${tailormap-api.base-path}/{viewerKind}/{viewerName}/layer/{appLayerId}/feature/{featureId}/attachments"
},
Expand All @@ -94,7 +96,7 @@

TMFeatureType tmFeatureType = editUtil.getEditableFeatureType(application, appTreeLayerNode, service, layer);

checkFeatureExists(tmFeatureType, featureId);
Object primaryKey = getFeaturePrimaryKeyByFid(tmFeatureType, featureId);

Check warning on line 99 in src/main/java/org/tailormap/api/controller/AttachmentsController.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/tailormap/api/controller/AttachmentsController.java#L99

Added line #L99 was not covered by tests

Set<@Valid AttachmentAttributeType> attachmentAttrSet =
tmFeatureType.getSettings().getAttachmentAttributes();
Expand All @@ -104,9 +106,8 @@

AttachmentAttributeType attachmentAttributeType = attachmentAttrSet.stream()
.filter(attr -> (attr.getAttributeName().equals(attachment.getAttributeName())
&& java.util.Arrays.stream(attr.getMimeType().split(","))
.map(String::trim)
.anyMatch(mime -> mime.equals(attachment.getMimeType()))
// TODO check attr.getMimeType() as "accept" attribute for HTML file input (null, mime types but
// also extensions)
&& (attr.getMaxAttachmentSize() == null || attr.getMaxAttachmentSize() >= fileData.length)))
.findFirst()
.orElseThrow(() -> new ResponseStatusException(
Expand All @@ -121,7 +122,7 @@

AttachmentMetadata response;
try {
response = AttachmentsHelper.insertAttachment(tmFeatureType, attachment, featureId, fileData);
response = AttachmentsHelper.insertAttachment(tmFeatureType, attachment, primaryKey, fileData);

Check warning on line 125 in src/main/java/org/tailormap/api/controller/AttachmentsController.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/tailormap/api/controller/AttachmentsController.java#L125

Added line #L125 was not covered by tests
} catch (IOException | SQLException e) {
throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage());
}
Expand Down Expand Up @@ -155,11 +156,11 @@
TMFeatureType tmFeatureType = editUtil.getEditableFeatureType(application, appTreeLayerNode, service, layer);

checkFeatureTypeSupportsAttachments(tmFeatureType);
checkFeatureExists(tmFeatureType, featureId);
Object primaryKey = getFeaturePrimaryKeyByFid(tmFeatureType, featureId);

Check warning on line 159 in src/main/java/org/tailormap/api/controller/AttachmentsController.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/tailormap/api/controller/AttachmentsController.java#L159

Added line #L159 was not covered by tests

List<AttachmentMetadata> response;
try {
response = AttachmentsHelper.listAttachmentsForFeature(tmFeatureType, featureId);
response = AttachmentsHelper.listAttachmentsForFeature(tmFeatureType, primaryKey);

Check warning on line 163 in src/main/java/org/tailormap/api/controller/AttachmentsController.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/tailormap/api/controller/AttachmentsController.java#L163

Added line #L163 was not covered by tests
} catch (IOException | SQLException e) {
throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage());
}
Expand Down Expand Up @@ -232,20 +233,27 @@
}
}

private void checkFeatureExists(TMFeatureType tmFeatureType, String featureId) throws ResponseStatusException {
private Object getFeaturePrimaryKeyByFid(TMFeatureType tmFeatureType, String featureId)
throws ResponseStatusException {
final Filter fidFilter = ff.id(ff.featureId(featureId));
SimpleFeatureSource fs = null;
SimpleFeatureIterator sfi = null;

Check warning on line 240 in src/main/java/org/tailormap/api/controller/AttachmentsController.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/tailormap/api/controller/AttachmentsController.java#L240

Added line #L240 was not covered by tests
try {
fs = featureSourceFactoryHelper.openGeoToolsFeatureSource(tmFeatureType);
Query query = new Query();
query.setFilter(fidFilter);
if (fs.getCount(query) < 1) {
throw new ResponseStatusException(
HttpStatus.NOT_FOUND, "Feature with id " + featureId + " does not exist");
}
query.setPropertyNames(tmFeatureType.getPrimaryKeyAttribute());
sfi = fs.getFeatures(query).features();
return sfi.next().getAttribute(tmFeatureType.getPrimaryKeyAttribute());
} catch (NoSuchElementException e) {
throw new ResponseStatusException(
HttpStatus.NOT_FOUND, "Feature with id %s does not exist".formatted(featureId));

Check warning on line 250 in src/main/java/org/tailormap/api/controller/AttachmentsController.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/tailormap/api/controller/AttachmentsController.java#L245-L250

Added lines #L245 - L250 were not covered by tests
} catch (IOException e) {
throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage());
throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage(), e);

Check warning on line 252 in src/main/java/org/tailormap/api/controller/AttachmentsController.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/tailormap/api/controller/AttachmentsController.java#L252

Added line #L252 was not covered by tests
} finally {
if (sfi != null) {
sfi.close();

Check warning on line 255 in src/main/java/org/tailormap/api/controller/AttachmentsController.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/tailormap/api/controller/AttachmentsController.java#L255

Added line #L255 was not covered by tests
}
if (fs != null) {
fs.getDataStore().dispose();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,7 @@
}

public static AttachmentMetadata insertAttachment(
TMFeatureType featureType, AttachmentMetadata attachment, String featureId, byte[] fileData)
TMFeatureType featureType, AttachmentMetadata attachment, Object primaryKey, byte[] fileData)
throws IOException, SQLException {

// create uuid here so we don't have to deal with DB-specific returning/generated key syntax
Expand All @@ -384,7 +384,7 @@
"Adding attachment {} for feature {}:{}, type {}: {} (bytes: {})",
attachment.getAttachmentId(),
featureType.getName(),
featureId,
primaryKey,
attachment.getMimeType(),
attachment,
fileData.length);
Expand All @@ -410,15 +410,11 @@
try {
ds = (JDBCDataStore) new JDBCFeatureSourceHelper().createDataStore(featureType.getFeatureSource());

Class<?> typeOfPK = ds.getSchema(featureType.getName())
.getDescriptor(featureType.getPrimaryKeyAttribute())
.getType()
.getBinding();

try (Connection conn = ds.getDataSource().getConnection();
PreparedStatement stmt = conn.prepareStatement(insertSql)) {

stmt.setObject(1, featureId, ds.getMapping(typeOfPK));
stmt.setObject(1, primaryKey);

Check warning on line 416 in src/main/java/org/tailormap/api/geotools/featuresources/AttachmentsHelper.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/tailormap/api/geotools/featuresources/AttachmentsHelper.java#L416

Added line #L416 was not covered by tests

if (featureType
.getFeatureSource()
.getJdbcConnection()
Expand Down Expand Up @@ -478,7 +474,7 @@
}
}

public static List<AttachmentMetadata> listAttachmentsForFeature(TMFeatureType featureType, String featureId)
public static List<AttachmentMetadata> listAttachmentsForFeature(TMFeatureType featureType, Object primaryKey)
throws IOException, SQLException {

String querySql = MessageFormat.format(
Expand All @@ -504,7 +500,7 @@
try (Connection conn = ds.getDataSource().getConnection();
PreparedStatement stmt = conn.prepareStatement(querySql)) {

stmt.setString(1, featureId);
stmt.setObject(1, primaryKey);

Check warning on line 503 in src/main/java/org/tailormap/api/geotools/featuresources/AttachmentsHelper.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/tailormap/api/geotools/featuresources/AttachmentsHelper.java#L503

Added line #L503 was not covered by tests

try (ResultSet rs = stmt.executeQuery()) {
while (rs.next()) {
Expand Down
Loading