Skip to content
GitLab
Projects
Groups
Snippets
Help
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Wendys Racing Horses
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Analytics
Analytics
Repository
Value Stream
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Commits
Open sidebar
tu-wien
Wendys Racing Horses
Commits
a3b11854
Commit
a3b11854
authored
Mar 21, 2020
by
Ivaylo Ivanov
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
US04, TS20: Make it possible to delete a horse through the API, add better exception handling
parent
034837ce
Changes
11
Show whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
192 additions
and
24 deletions
+192
-24
backend/src/main/java/at/ac/tuwien/sepm/assignment/individual/endpoint/HorseEndpoint.java
...en/sepm/assignment/individual/endpoint/HorseEndpoint.java
+27
-3
backend/src/main/java/at/ac/tuwien/sepm/assignment/individual/persistence/FileDao.java
...uwien/sepm/assignment/individual/persistence/FileDao.java
+8
-0
backend/src/main/java/at/ac/tuwien/sepm/assignment/individual/persistence/HorseDao.java
...wien/sepm/assignment/individual/persistence/HorseDao.java
+13
-2
backend/src/main/java/at/ac/tuwien/sepm/assignment/individual/persistence/impl/HorseFileDao.java
.../assignment/individual/persistence/impl/HorseFileDao.java
+14
-0
backend/src/main/java/at/ac/tuwien/sepm/assignment/individual/persistence/impl/HorseJdbcDao.java
.../assignment/individual/persistence/impl/HorseJdbcDao.java
+36
-9
backend/src/main/java/at/ac/tuwien/sepm/assignment/individual/service/HorseService.java
...wien/sepm/assignment/individual/service/HorseService.java
+14
-5
backend/src/main/java/at/ac/tuwien/sepm/assignment/individual/service/impl/SimpleHorseService.java
...ssignment/individual/service/impl/SimpleHorseService.java
+8
-1
backend/src/test/java/at/ac/tuwien/sepm/assignment/individual/integration/HorseEndpointTest.java
.../assignment/individual/integration/HorseEndpointTest.java
+28
-2
backend/src/test/java/at/ac/tuwien/sepm/assignment/individual/unit/persistence/HorseDaoTestBase.java
...ignment/individual/unit/persistence/HorseDaoTestBase.java
+23
-1
backend/src/test/java/at/ac/tuwien/sepm/assignment/individual/unit/persistence/HorseFileDaoTest.java
...ignment/individual/unit/persistence/HorseFileDaoTest.java
+19
-0
backend/src/test/java/at/ac/tuwien/sepm/assignment/individual/unit/service/HorseServiceTest.java
.../assignment/individual/unit/service/HorseServiceTest.java
+2
-1
No files found.
backend/src/main/java/at/ac/tuwien/sepm/assignment/individual/endpoint/HorseEndpoint.java
View file @
a3b11854
...
...
@@ -57,7 +57,7 @@ public class HorseEndpoint {
}
catch
(
DataAccessException
e
)
{
LOGGER
.
error
(
e
.
getMessage
());
throw
new
ResponseStatusException
(
HttpStatus
.
UNPROCESSABLE_ENTITY
,
"Something went wrong during the communication with the database"
,
e
);
"Something went wrong during the communication with the database"
);
}
}
...
...
@@ -72,11 +72,35 @@ public class HorseEndpoint {
}
catch
(
ValidationException
e
)
{
LOGGER
.
error
(
e
.
getMessage
());
throw
new
ResponseStatusException
(
HttpStatus
.
BAD_REQUEST
,
"Error during updating horse with id "
+
id
+
": "
+
e
.
getMessage
()
,
e
);
"Error during updating horse with id "
+
id
+
": "
+
e
.
getMessage
());
}
catch
(
DataAccessException
e
)
{
LOGGER
.
error
(
e
.
getMessage
());
throw
new
ResponseStatusException
(
HttpStatus
.
UNPROCESSABLE_ENTITY
,
"Something went wrong during the communication with the database"
,
e
);
"Something went wrong during the communication with the database"
);
}
catch
(
IOException
e
)
{
LOGGER
.
error
(
e
.
getMessage
());
throw
new
ResponseStatusException
(
HttpStatus
.
PARTIAL_CONTENT
,
"Operation completed with errors: image could not be saved"
);
}
}
@DeleteMapping
(
value
=
"/{id}"
)
@ResponseStatus
(
HttpStatus
.
NO_CONTENT
)
public
void
deleteHorse
(
@PathVariable
(
"id"
)
Long
id
)
{
LOGGER
.
info
(
"DELETE "
+
BASE_URL
+
"/{}"
,
id
);
try
{
horseService
.
deleteHorse
(
id
);
}
catch
(
DataAccessException
e
)
{
LOGGER
.
error
(
e
.
getMessage
());
throw
new
ResponseStatusException
(
HttpStatus
.
UNPROCESSABLE_ENTITY
,
"Something went wrong during the communication with the database"
);
}
catch
(
NotFoundException
e
)
{
LOGGER
.
error
(
e
.
getMessage
());
throw
new
ResponseStatusException
(
HttpStatus
.
NOT_FOUND
,
"The requested horse has not been found"
);
}
catch
(
IOException
e
)
{
// Log the error and return as it does not concern the user
LOGGER
.
error
(
e
.
getMessage
());
}
}
...
...
backend/src/main/java/at/ac/tuwien/sepm/assignment/individual/persistence/FileDao.java
View file @
a3b11854
...
...
@@ -8,6 +8,14 @@ public interface FileDao {
/**
* Used for saving files on the local file system
* @param file file to save
* @throws IOException if something goes wrong with saving the file
*/
void
save
(
MultipartFile
file
)
throws
IOException
;
/**
* Used for deleting file from the local filesystem
* @param fileName file to delete
* @throws IOException if something goes wrong with deleting the file
*/
void
delete
(
String
fileName
)
throws
IOException
;
}
backend/src/main/java/at/ac/tuwien/sepm/assignment/individual/persistence/HorseDao.java
View file @
a3b11854
...
...
@@ -4,6 +4,8 @@ import at.ac.tuwien.sepm.assignment.individual.entity.Horse;
import
at.ac.tuwien.sepm.assignment.individual.exception.NotFoundException
;
import
org.springframework.dao.DataAccessException
;
import
java.io.IOException
;
public
interface
HorseDao
{
/**
...
...
@@ -25,6 +27,15 @@ public interface HorseDao {
* @param horse that specifies the new horse values alongside with the id of the horse to update
* @return the updated horse
* @throws DataAccessException will be thrown if something goes wrong during the database access.
* @throws IOException will be thrown if the old horse image could not be deleted
*/
Horse
updateHorse
(
Horse
horse
)
throws
DataAccessException
,
IOException
;
/**
* @param id of the horse to delete
* @throws DataAccessException will be thrown if something goes wrong during the database access.
* @throws NotFoundException will be thrown if the horse could not be found in the database.
* @throws IOException will be thrown if the horse image could not be deleted
*/
Horse
updateHorse
(
Horse
horse
)
;
void
deleteHorse
(
Long
id
)
throws
DataAccessException
,
NotFoundException
,
IOException
;
}
backend/src/main/java/at/ac/tuwien/sepm/assignment/individual/persistence/impl/HorseFileDao.java
View file @
a3b11854
...
...
@@ -49,4 +49,18 @@ public class HorseFileDao implements FileDao {
throw
new
IOException
(
"Saving the file failed"
);
}
}
@Override
public
void
delete
(
String
fileName
)
throws
IOException
{
if
(
fileName
==
null
||
fileName
.
equals
(
""
))
throw
new
IOException
(
"Cannot delete an unexisting file"
);
LOGGER
.
trace
(
"Deleting file from "
+
FILE_BASE_PATH
+
fileName
);
try
{
new
File
(
FILE_BASE_PATH
+
fileName
).
delete
();
}
catch
(
Exception
e
)
{
LOGGER
.
error
(
e
.
getMessage
());
throw
new
IOException
(
"Deleting the file specified failed"
);
}
}
}
backend/src/main/java/at/ac/tuwien/sepm/assignment/individual/persistence/impl/HorseJdbcDao.java
View file @
a3b11854
...
...
@@ -3,6 +3,7 @@ package at.ac.tuwien.sepm.assignment.individual.persistence.impl;
import
at.ac.tuwien.sepm.assignment.individual.entity.Horse
;
import
at.ac.tuwien.sepm.assignment.individual.enums.ERace
;
import
at.ac.tuwien.sepm.assignment.individual.exception.NotFoundException
;
import
at.ac.tuwien.sepm.assignment.individual.persistence.FileDao
;
import
at.ac.tuwien.sepm.assignment.individual.persistence.HorseDao
;
import
at.ac.tuwien.sepm.assignment.individual.util.ValidationException
;
import
org.slf4j.Logger
;
...
...
@@ -16,6 +17,8 @@ import org.springframework.jdbc.support.GeneratedKeyHolder;
import
org.springframework.jdbc.support.KeyHolder
;
import
org.springframework.stereotype.Repository
;
import
javax.xml.crypto.Data
;
import
java.io.IOException
;
import
java.lang.invoke.MethodHandles
;
import
java.sql.PreparedStatement
;
import
java.sql.ResultSet
;
...
...
@@ -30,6 +33,7 @@ public class HorseJdbcDao implements HorseDao {
private
static
final
Logger
LOGGER
=
LoggerFactory
.
getLogger
(
MethodHandles
.
lookup
().
lookupClass
());
private
final
JdbcTemplate
jdbcTemplate
;
private
final
NamedParameterJdbcTemplate
namedParameterJdbcTemplate
;
private
final
FileDao
fileDao
=
new
HorseFileDao
();
public
HorseJdbcDao
(
JdbcTemplate
jdbcTemplate
,
NamedParameterJdbcTemplate
namedParameterJdbcTemplate
)
{
this
.
jdbcTemplate
=
jdbcTemplate
;
...
...
@@ -87,21 +91,20 @@ public class HorseJdbcDao implements HorseDao {
},
keyHolder
);
if
(
changes
==
0
)
throw
new
NotFoundException
(
"Creating horse failed, no rows affected"
)
;
throw
new
DataAccessException
(
"Creating horse failed, no rows affected"
)
{}
;
horse
.
setId
(((
Number
)
keyHolder
.
getKeys
().
get
(
"id"
)).
longValue
());
return
horse
;
}
catch
(
DataAccessException
e
)
{
// We are doing this in order to not change the exception type
throw
new
DataAccessException
(
"Adding new records failed"
,
e
)
{};
}
catch
(
NotFoundException
e
){
throw
new
DataRetrievalFailureException
(
"No new records added"
,
e
);
throw
new
DataAccessException
(
"Adding new records failed"
,
e
)
{
};
}
}
@Override
public
Horse
updateHorse
(
Horse
horse
)
{
public
Horse
updateHorse
(
Horse
horse
)
throws
DataAccessException
,
IOException
{
LOGGER
.
trace
(
"Update horse {}"
,
horse
.
toString
());
String
sql
=
"UPDATE "
+
TABLE_NAME
+
" SET name=?, description=?, score=?, birthday=?, race=?, image_path=?, owner_id=?, updated_at=? WHERE id=?"
;
...
...
@@ -137,16 +140,40 @@ public class HorseJdbcDao implements HorseDao {
});
if
(
changes
==
0
)
throw
new
NotFoundException
(
"Updating horse failed, no rows affected"
);
throw
new
DataAccessException
(
"Updating horse failed, no rows affected"
)
{};
horse
.
setUpdatedAt
(
oldHorse
.
getUpdatedAt
());
fileDao
.
delete
(
oldHorse
.
getImagePath
());
horse
.
setCreatedAt
(
oldHorse
.
getCreatedAt
());
return
horse
;
}
catch
(
DataAccessException
e
)
{
// We are doing this in order to not change the exception type
throw
new
DataAccessException
(
"Updating records failed"
,
e
)
{};
}
catch
(
NotFoundException
e
){
throw
new
DataRetrievalFailureException
(
"No new records updated"
,
e
);
}
}
@Override
public
void
deleteHorse
(
Long
id
)
throws
DataAccessException
,
NotFoundException
,
IOException
{
Horse
horseToDelete
=
this
.
findOneById
(
id
);
LOGGER
.
trace
(
"Delete horse with id {}"
,
id
);
final
String
sql
=
"DELETE FROM "
+
TABLE_NAME
+
" WHERE id=?"
;
try
{
int
changes
=
jdbcTemplate
.
update
(
connection
->
{
PreparedStatement
ps
=
connection
.
prepareStatement
(
sql
);
ps
.
setLong
(
1
,
id
);
return
ps
;
});
if
(
changes
==
0
)
throw
new
DataAccessException
(
"Deleting horse failed, no rows affected"
)
{};
fileDao
.
delete
(
horseToDelete
.
getImagePath
());
}
catch
(
DataAccessException
e
){
// We are doing this in order to not change the exception type
throw
new
DataAccessException
(
"Deleting records failed"
,
e
)
{};
}
}
...
...
backend/src/main/java/at/ac/tuwien/sepm/assignment/individual/service/HorseService.java
View file @
a3b11854
...
...
@@ -30,13 +30,22 @@ public interface HorseService {
* @return the updated horse
* @throws ValidationException will be thrown if something goes wrong during verification.
* @throws DataAccessException will be thrown if the horse could not be saved in the database.
* @throws IOException will be thrown if the old horse image could not be deleted
*/
Horse
updateHorse
(
Horse
horse
);
Horse
updateHorse
(
Horse
horse
)
throws
ValidationException
,
DataAccessException
,
IOException
;
/**
* @param id of the horse to delete
* @throws NotFoundException will be thrown if the horse could not be found in the system
* @throws DataAccessException will be thrown if the horse could not be deleted from the database
* @throws IOException will be thrown if the horse image could not be deleted
*/
void
deleteHorse
(
Long
id
)
throws
NotFoundException
,
DataAccessException
,
IOException
;
/**
* @param img image to upload
* @throws IOException will be thrown if something goes wrong with saving the file
* @throws ValidationException will be thrown if the file is in the incorrect format
*/
void
saveImage
(
MultipartFile
img
)
throws
IOException
;
void
saveImage
(
MultipartFile
img
)
throws
IOException
,
ValidationException
;
}
backend/src/main/java/at/ac/tuwien/sepm/assignment/individual/service/impl/SimpleHorseService.java
View file @
a3b11854
package
at.ac.tuwien.sepm.assignment.individual.service.impl
;
import
at.ac.tuwien.sepm.assignment.individual.entity.Horse
;
import
at.ac.tuwien.sepm.assignment.individual.exception.NotFoundException
;
import
at.ac.tuwien.sepm.assignment.individual.persistence.FileDao
;
import
at.ac.tuwien.sepm.assignment.individual.persistence.HorseDao
;
import
at.ac.tuwien.sepm.assignment.individual.service.HorseService
;
...
...
@@ -44,12 +45,18 @@ public class SimpleHorseService implements HorseService {
}
@Override
public
Horse
updateHorse
(
Horse
horse
)
{
public
Horse
updateHorse
(
Horse
horse
)
throws
ValidationException
,
DataAccessException
,
IOException
{
this
.
validator
.
validateUpdateHorse
(
horse
);
LOGGER
.
trace
(
"updateHorse({})"
,
horse
.
toString
());
return
horseJdbcDao
.
updateHorse
(
horse
);
}
@Override
public
void
deleteHorse
(
Long
id
)
throws
NotFoundException
,
DataAccessException
,
IOException
{
LOGGER
.
trace
(
"deleteHorse({})"
,
id
);
horseJdbcDao
.
deleteHorse
(
id
);
}
@Override
public
void
saveImage
(
MultipartFile
img
)
throws
ValidationException
,
IOException
{
this
.
validator
.
validateHorseImage
(
img
);
...
...
backend/src/test/java/at/ac/tuwien/sepm/assignment/individual/integration/HorseEndpointTest.java
View file @
a3b11854
...
...
@@ -9,11 +9,11 @@ import org.junit.jupiter.api.Test;
import
org.springframework.beans.factory.annotation.Value
;
import
org.springframework.boot.test.context.SpringBootTest
;
import
org.springframework.boot.web.server.LocalServerPort
;
import
org.springframework.core.ParameterizedTypeReference
;
import
org.springframework.core.io.ClassPathResource
;
import
org.springframework.http.*
;
import
org.springframework.test.context.ActiveProfiles
;
import
org.springframework.test.context.event.annotation.AfterTestMethod
;
import
org.springframework.test.context.jdbc.Sql
;
import
org.springframework.web.client.HttpClientErrorException
;
import
org.springframework.util.LinkedMultiValueMap
;
import
org.springframework.util.MultiValueMap
;
import
org.springframework.web.client.RestTemplate
;
...
...
@@ -111,4 +111,30 @@ public class HorseEndpointTest {
new
File
(
FILE_BASE_PATH
+
"horse.jpg"
).
delete
();
}
}
@Test
@DisplayName
(
"Deleting an existing horse will return HTTP 204"
)
public
void
deletingHorse_existing_shouldReturnStatus204
()
{
// Create the horse
HorseDto
newHorse
=
new
HorseDto
(
"Zephyr"
,
"Nice horse"
,
(
short
)
4
,
Date
.
valueOf
(
"2020-01-01"
),
ERace
.
APPALOOSA
,
"files/test.png"
,
null
);
HttpEntity
<
HorseDto
>
request
=
new
HttpEntity
<>(
newHorse
);
ResponseEntity
<
HorseDto
>
response
=
REST_TEMPLATE
.
exchange
(
BASE_URL
+
port
+
HORSE_URL
,
HttpMethod
.
POST
,
request
,
HorseDto
.
class
);
// Delete and test if deleted
ResponseEntity
res
=
REST_TEMPLATE
.
exchange
(
BASE_URL
+
port
+
HORSE_URL
+
'/'
+
response
.
getBody
().
getId
(),
HttpMethod
.
DELETE
,
null
,
new
ParameterizedTypeReference
<
HorseDto
>()
{});
assertEquals
(
res
.
getStatusCode
(),
HttpStatus
.
NO_CONTENT
);
}
@Test
@DisplayName
(
"Deleting an nonexistent horse will return HTTP 404"
)
public
void
deletingHorse_nonexistent_shouldReturnStatus404
()
{
assertThrows
(
HttpClientErrorException
.
NotFound
.
class
,
()
->
REST_TEMPLATE
.
exchange
(
BASE_URL
+
port
+
HORSE_URL
+
"/0"
,
HttpMethod
.
DELETE
,
null
,
new
ParameterizedTypeReference
<
HorseDto
>()
{}));
}
}
backend/src/test/java/at/ac/tuwien/sepm/assignment/individual/unit/persistence/HorseDaoTestBase.java
View file @
a3b11854
...
...
@@ -4,12 +4,14 @@ import static org.junit.jupiter.api.Assertions.*;
import
at.ac.tuwien.sepm.assignment.individual.entity.Horse
;
import
at.ac.tuwien.sepm.assignment.individual.enums.ERace
;
import
at.ac.tuwien.sepm.assignment.individual.exception.NotFoundException
;
import
at.ac.tuwien.sepm.assignment.individual.persistence.HorseDao
;
import
org.junit.jupiter.api.DisplayName
;
import
org.junit.jupiter.api.Test
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.dao.DataAccessException
;
import
java.io.IOException
;
import
java.sql.Date
;
public
abstract
class
HorseDaoTestBase
{
...
...
@@ -34,7 +36,7 @@ public abstract class HorseDaoTestBase {
@Test
@DisplayName
(
"Updating a horse with the correct parameters should return the horse"
)
public
void
updatingHorse_correctParameters_shouldReturnHorse
()
{
public
void
updatingHorse_correctParameters_shouldReturnHorse
()
throws
IOException
{
// Create horse
Horse
newHorse
=
new
Horse
(
"Zephyr"
,
"Nice horse"
,
(
short
)
4
,
Date
.
valueOf
(
"2020-01-01"
),
ERace
.
APPALOOSA
,
"files/test.png"
,
null
);
Horse
savedHorse
=
horseDao
.
addHorse
(
newHorse
);
...
...
@@ -80,4 +82,24 @@ public abstract class HorseDaoTestBase {
assertThrows
(
DataAccessException
.
class
,
()
->
horseDao
.
addHorse
(
newHorse
));
}
@Test
@DisplayName
(
"Deleting an existing horse should delete the horse"
)
public
void
deletingHorse_existing_shouldDeleteHorse
()
throws
IOException
{
// Create the horse
Horse
newHorse
=
new
Horse
(
"Zephyr"
,
"Nice horse"
,
(
short
)
4
,
Date
.
valueOf
(
"2020-01-01"
),
ERace
.
APPALOOSA
,
"files/test.png"
,
null
);
Horse
savedHorse
=
horseDao
.
addHorse
(
newHorse
);
// Delete the horse
horseDao
.
deleteHorse
(
savedHorse
.
getId
());
// Check if deleted
assertThrows
(
NotFoundException
.
class
,
()
->
horseDao
.
findOneById
(
savedHorse
.
getId
()));
}
@Test
@DisplayName
(
"Deleting an nonexistent horse should throw NotFoundException"
)
public
void
deletingHorse_nonexistent_shouldThrowNotFound
()
throws
IOException
{
assertThrows
(
NotFoundException
.
class
,
()
->
horseDao
.
deleteHorse
(
null
));
}
}
backend/src/test/java/at/ac/tuwien/sepm/assignment/individual/unit/persistence/HorseFileDaoTest.java
View file @
a3b11854
...
...
@@ -43,6 +43,25 @@ public class HorseFileDaoTest {
assertThrows
(
IOException
.
class
,
()
->
horseFileDao
.
save
(
new
MockMultipartFile
(
"file"
,
image
.
getName
(),
MediaType
.
TEXT_HTML_VALUE
,
new
FileInputStream
(
image
))));
}
@Test
@DisplayName
(
"Deleting a file with a correct name should result in a deleted file"
)
public
void
deletingFile_correctName_shouldDeleteFile
()
throws
FileNotFoundException
,
IOException
{
// Save the file
File
image
=
new
File
(
"src/test/resources/at/ac/tuwien/sepm/assignment/individual/integration/horse.jpg"
);
horseFileDao
.
save
(
new
MockMultipartFile
(
"file"
,
image
.
getName
(),
MediaType
.
IMAGE_JPEG_VALUE
,
new
FileInputStream
(
image
)));
// Then delete it
horseFileDao
.
delete
(
"horse.jpg"
);
assertFalse
(
new
File
(
FILE_BASE_PATH
+
"horse.jpg"
).
exists
());
}
@Test
@DisplayName
(
"Deleting a file with an empty name should throw an Exception"
)
public
void
deletingFile_emptyName_shouldThrowIO
()
{
assertThrows
(
IOException
.
class
,
()
->
horseFileDao
.
delete
(
""
));
}
@AfterEach
public
void
cleanup
()
{
new
File
(
FILE_BASE_PATH
+
"horse.jpg"
).
delete
();
...
...
backend/src/test/java/at/ac/tuwien/sepm/assignment/individual/unit/service/HorseServiceTest.java
View file @
a3b11854
...
...
@@ -14,6 +14,7 @@ import org.springframework.boot.test.context.SpringBootTest;
import
org.springframework.test.context.ActiveProfiles
;
import
org.springframework.test.context.junit.jupiter.SpringExtension
;
import
java.io.IOException
;
import
java.sql.Date
;
import
static
org
.
junit
.
jupiter
.
api
.
Assertions
.
assertEquals
;
...
...
@@ -42,7 +43,7 @@ public class HorseServiceTest {
@Test
@DisplayName
(
"Updating a horse with the correct parameters will return the new horse"
)
public
void
updatingHorse_correctParameters_shouldReturnHorse
()
{
public
void
updatingHorse_correctParameters_shouldReturnHorse
()
throws
IOException
{
// Create horse
Horse
newHorse
=
new
Horse
(
"Zephyr"
,
"Nice horse"
,
(
short
)
4
,
Date
.
valueOf
(
"2020-01-01"
),
ERace
.
APPALOOSA
,
"files/test.png"
,
null
);
Horse
savedHorse
=
horseService
.
addHorse
(
newHorse
);
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment