99import java .io .IOException ;
1010import java .io .Serializable ;
1111import java .lang .invoke .MethodHandles ;
12+ import java .nio .ByteBuffer ;
1213import java .sql .SQLException ;
14+ import java .util .List ;
1315import java .util .Set ;
1416import java .util .UUID ;
1517import org .geotools .api .data .Query ;
@@ -74,7 +76,7 @@ public AttachmentsController(EditUtil editUtil, FeatureSourceFactoryHelper featu
7476 */
7577 @ PutMapping (
7678 path = {
77- "${tailormap-api.base-path}/{viewerKind}/{viewerName}/layer/{appLayerId}/feature/{featureId}/attachment "
79+ "${tailormap-api.base-path}/{viewerKind}/{viewerName}/layer/{appLayerId}/feature/{featureId}/attachments "
7880 },
7981 consumes = MediaType .MULTIPART_FORM_DATA_VALUE ,
8082 produces = MediaType .APPLICATION_JSON_VALUE )
@@ -92,10 +94,12 @@ public ResponseEntity<Serializable> addAttachment(
9294
9395 TMFeatureType tmFeatureType = editUtil .getEditableFeatureType (application , appTreeLayerNode , service , layer );
9496
97+ checkFeatureExists (tmFeatureType , featureId );
98+
9599 Set <@ Valid AttachmentAttributeType > attachmentAttrSet =
96100 tmFeatureType .getSettings ().getAttachmentAttributes ();
97101 if (attachmentAttrSet == null || attachmentAttrSet .isEmpty ()) {
98- throw new ResponseStatusException (HttpStatus .BAD_REQUEST , "Feature type does not support attachments" );
102+ throw new ResponseStatusException (HttpStatus .BAD_REQUEST , "Layer does not support attachments" );
99103 }
100104
101105 AttachmentAttributeType attachmentAttributeType = attachmentAttrSet .stream ()
@@ -107,18 +111,14 @@ public ResponseEntity<Serializable> addAttachment(
107111 .findFirst ()
108112 .orElseThrow (() -> new ResponseStatusException (
109113 HttpStatus .BAD_REQUEST ,
110- "Feature type does not support attachments for attribute "
114+ "Layer does not support attachments for attribute "
111115 + attachment .getAttributeName ()
112116 + " with mime type "
113117 + attachment .getMimeType ()
114118 + " and size "
115119 + fileData .length ));
116120 logger .debug ("Using attachment attribute {}" , attachmentAttributeType );
117121
118- if (!checkFeatureExists (tmFeatureType , featureId )) {
119- throw new ResponseStatusException (HttpStatus .NOT_FOUND , "Feature with id " + featureId + " does not exist" );
120- }
121-
122122 AttachmentMetadata response ;
123123 try {
124124 response = AttachmentsHelper .insertAttachment (tmFeatureType , attachment , featureId , fileData );
@@ -129,32 +129,120 @@ public ResponseEntity<Serializable> addAttachment(
129129 return new ResponseEntity <>(response , HttpStatus .CREATED );
130130 }
131131
132- @ DeleteMapping (path = "${tailormap-api.base-path}/attachment/{attachmentId}" )
133- public ResponseEntity <Serializable > deleteAttachment (@ PathVariable UUID attachmentId ) {
132+ /**
133+ * List attachments for a feature.
134+ *
135+ * @param appTreeLayerNode the application tree layer node
136+ * @param service the geo service
137+ * @param layer the geo service layer
138+ * @param application the application
139+ * @param featureId the feature id
140+ * @return the response entity containing a list of attachment metadata
141+ */
142+ @ GetMapping (
143+ path = {
144+ "${tailormap-api.base-path}/{viewerKind}/{viewerName}/layer/{appLayerId}/feature/{featureId}/attachments"
145+ },
146+ produces = MediaType .APPLICATION_JSON_VALUE )
147+ @ Transactional
148+ public ResponseEntity <List <AttachmentMetadata >> listAttachments (
149+ @ ModelAttribute AppTreeLayerNode appTreeLayerNode ,
150+ @ ModelAttribute GeoService service ,
151+ @ ModelAttribute GeoServiceLayer layer ,
152+ @ ModelAttribute Application application ,
153+ @ PathVariable String featureId ) {
154+
155+ TMFeatureType tmFeatureType = editUtil .getEditableFeatureType (application , appTreeLayerNode , service , layer );
156+
157+ checkFeatureTypeSupportsAttachments (tmFeatureType );
158+ checkFeatureExists (tmFeatureType , featureId );
159+
160+ List <AttachmentMetadata > response ;
161+ try {
162+ response = AttachmentsHelper .listAttachmentsForFeature (tmFeatureType , featureId );
163+ } catch (IOException | SQLException e ) {
164+ throw new ResponseStatusException (HttpStatus .INTERNAL_SERVER_ERROR , e .getMessage ());
165+ }
166+
167+ return new ResponseEntity <>(response , HttpStatus .OK );
168+ }
169+
170+ @ DeleteMapping (
171+ path = "${tailormap-api.base-path}/{viewerKind}/{viewerName}/layer/{appLayerId}/attachment/{attachmentId}" )
172+ @ Transactional
173+ public ResponseEntity <Serializable > deleteAttachment (
174+ @ ModelAttribute AppTreeLayerNode appTreeLayerNode ,
175+ @ ModelAttribute GeoService service ,
176+ @ ModelAttribute GeoServiceLayer layer ,
177+ @ ModelAttribute Application application ,
178+ @ PathVariable UUID attachmentId ) {
134179 editUtil .checkEditAuthorisation ();
135180
136- logger .debug ("TODO: Deleting attachment with id {}" , attachmentId );
137- throw new UnsupportedOperationException ("Not implemented yet" );
181+ TMFeatureType tmFeatureType = editUtil .getEditableFeatureType (application , appTreeLayerNode , service , layer );
182+
183+ checkFeatureTypeSupportsAttachments (tmFeatureType );
184+
185+ try {
186+ AttachmentsHelper .deleteAttachment (attachmentId , tmFeatureType );
187+ } catch (IOException | SQLException e ) {
188+ throw new ResponseStatusException (HttpStatus .INTERNAL_SERVER_ERROR , e .getMessage ());
189+ }
190+
191+ return new ResponseEntity <>(HttpStatus .NO_CONTENT );
138192 }
139193
194+ @ Transactional
140195 @ GetMapping (
141- path = "${tailormap-api.base-path}/attachment/{attachmentId}" ,
196+ path = "${tailormap-api.base-path}/{viewerKind}/{viewerName}/layer/{appLayerId}/ attachment/{attachmentId}" ,
142197 produces = {"application/octet-stream" })
143- // TODO determine return type: ResponseEntity<byte[]> or ResponseEntity<InputStreamResource>?
144- public ResponseEntity <byte []> getAttachment (@ PathVariable UUID attachmentId ) {
145- logger .debug ("TODO: Getting attachment with id {}" , attachmentId );
198+ public ResponseEntity <byte []> getAttachment (
199+ @ ModelAttribute AppTreeLayerNode appTreeLayerNode ,
200+ @ ModelAttribute GeoService service ,
201+ @ ModelAttribute GeoServiceLayer layer ,
202+ @ ModelAttribute Application application ,
203+ @ PathVariable UUID attachmentId ) {
146204
147- throw new UnsupportedOperationException ("Not implemented yet" );
205+ TMFeatureType tmFeatureType = editUtil .getEditableFeatureType (application , appTreeLayerNode , service , layer );
206+
207+ try {
208+ final AttachmentsHelper .AttachmentWithBinary attachmentWithBinary =
209+ AttachmentsHelper .getAttachment (tmFeatureType , attachmentId );
210+
211+ if (attachmentWithBinary == null ) {
212+ throw new ResponseStatusException (
213+ HttpStatus .NOT_FOUND , "Attachment %s not found" .formatted (attachmentId .toString ()));
214+ }
215+
216+ // the binary attachment() is a read-only ByteBuffer, so we cant use .array()
217+ final ByteBuffer bb = attachmentWithBinary .attachment ().asReadOnlyBuffer ();
218+ bb .rewind ();
219+ byte [] attachmentData = new byte [bb .remaining ()];
220+ bb .get (attachmentData );
221+
222+ return ResponseEntity .ok ()
223+ .header (
224+ "Content-Disposition" ,
225+ "inline; filename=\" "
226+ + attachmentWithBinary .attachmentMetadata ().getFileName () + "\" " )
227+ .contentType (MediaType .parseMediaType (
228+ attachmentWithBinary .attachmentMetadata ().getMimeType ()))
229+ .body (attachmentData );
230+ } catch (SQLException | IOException e ) {
231+ throw new ResponseStatusException (HttpStatus .INTERNAL_SERVER_ERROR , e .getMessage ());
232+ }
148233 }
149234
150- private boolean checkFeatureExists (TMFeatureType tmFeatureType , String featureId ) {
235+ private void checkFeatureExists (TMFeatureType tmFeatureType , String featureId ) throws ResponseStatusException {
151236 final Filter fidFilter = ff .id (ff .featureId (featureId ));
152237 SimpleFeatureSource fs = null ;
153238 try {
154239 fs = featureSourceFactoryHelper .openGeoToolsFeatureSource (tmFeatureType );
155240 Query query = new Query ();
156241 query .setFilter (fidFilter );
157- return fs .getCount (query ) > 0 ;
242+ if (fs .getCount (query ) < 1 ) {
243+ throw new ResponseStatusException (
244+ HttpStatus .NOT_FOUND , "Feature with id " + featureId + " does not exist" );
245+ }
158246 } catch (IOException e ) {
159247 throw new ResponseStatusException (HttpStatus .INTERNAL_SERVER_ERROR , e .getMessage ());
160248 } finally {
@@ -163,4 +251,12 @@ private boolean checkFeatureExists(TMFeatureType tmFeatureType, String featureId
163251 }
164252 }
165253 }
254+
255+ private void checkFeatureTypeSupportsAttachments (TMFeatureType tmFeatureType ) throws ResponseStatusException {
256+ Set <@ Valid AttachmentAttributeType > attachmentAttrSet =
257+ tmFeatureType .getSettings ().getAttachmentAttributes ();
258+ if (attachmentAttrSet == null || attachmentAttrSet .isEmpty ()) {
259+ throw new ResponseStatusException (HttpStatus .BAD_REQUEST , "Layer does not support attachments" );
260+ }
261+ }
166262}
0 commit comments