Make height/width nullable; add support for reading svgs; better error handling.
This commit is contained in:
parent
0a619c5d41
commit
3166f1dd5d
@ -69,6 +69,12 @@ dependencies {
|
||||
// https://mvnrepository.com/artifact/com.twelvemonkeys.imageio/imageio-webp
|
||||
runtimeOnly 'com.twelvemonkeys.imageio:imageio-webp:3.12.0'
|
||||
|
||||
// https://mvnrepository.com/artifact/com.twelvemonkeys.imageio/imageio-batik
|
||||
runtimeOnly 'com.twelvemonkeys.imageio:imageio-batik:3.12.0'
|
||||
|
||||
// https://mvnrepository.com/artifact/org.apache.xmlgraphics/batik-all
|
||||
runtimeOnly 'org.apache.xmlgraphics:batik-all:1.19'
|
||||
|
||||
compileOnly 'org.jetbrains:annotations:24.1.0'
|
||||
|
||||
// Custom testing
|
||||
|
||||
@ -16,7 +16,7 @@ public interface Image {
|
||||
@Nullable String getCaption();
|
||||
User getOwner();
|
||||
boolean isPublic();
|
||||
int getHeight();
|
||||
int getWidth();
|
||||
@Nullable Integer getHeight();
|
||||
@Nullable Integer getWidth();
|
||||
Set<User> getViewers();
|
||||
}
|
||||
|
||||
@ -6,7 +6,7 @@ public class ImageException extends Exception {
|
||||
INVALID_ID,
|
||||
INVALID_USERNAME_OR_FILENAME,
|
||||
IMAGE_NOT_FOUND,
|
||||
UNKNOWN_MIME_TYPE
|
||||
UNSUPPORTED_IMAGE_TYPE,
|
||||
}
|
||||
|
||||
private final Type type;
|
||||
|
||||
@ -35,11 +35,9 @@ public class S3ImageEntity implements Image {
|
||||
@Column(nullable = false)
|
||||
private String objectName;
|
||||
|
||||
@Column(nullable = false)
|
||||
private int height;
|
||||
private Integer height;
|
||||
|
||||
@Column(nullable = false)
|
||||
private int width;
|
||||
private Integer width;
|
||||
|
||||
@ManyToOne(optional = false)
|
||||
@JoinColumn(name = "owner_id", nullable = false)
|
||||
@ -123,20 +121,20 @@ public class S3ImageEntity implements Image {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHeight() {
|
||||
public @Nullable Integer getHeight() {
|
||||
return this.height;
|
||||
}
|
||||
|
||||
public void setHeight(int height) {
|
||||
public void setHeight(Integer height) {
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWidth() {
|
||||
public @Nullable Integer getWidth() {
|
||||
return this.width;
|
||||
}
|
||||
|
||||
public void setWidth(int width) {
|
||||
public void setWidth(Integer width) {
|
||||
this.width = width;
|
||||
}
|
||||
|
||||
|
||||
@ -7,8 +7,6 @@ import app.mealsmadeeasy.api.s3.S3Manager;
|
||||
import app.mealsmadeeasy.api.user.User;
|
||||
import app.mealsmadeeasy.api.user.UserEntity;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.security.access.prepost.PostAuthorize;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
@ -16,6 +14,8 @@ import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.time.LocalDateTime;
|
||||
@ -27,7 +27,11 @@ import java.util.regex.Pattern;
|
||||
public class S3ImageService implements ImageService {
|
||||
|
||||
private static final Pattern extensionPattern = Pattern.compile(".+\\.(.+)$");
|
||||
private static final Logger logger = LoggerFactory.getLogger(S3ImageService.class);
|
||||
|
||||
private static final String IMAGE_JPEG = "image/jpeg";
|
||||
private static final String IMAGE_PNG = "image/png";
|
||||
private static final String IMAGE_SVG = "image/svg+xml";
|
||||
private static final String IMAGE_WEBP = "image/webp";
|
||||
|
||||
private final S3Manager s3Manager;
|
||||
private final S3ImageRepository imageRepository;
|
||||
@ -51,10 +55,10 @@ public class S3ImageService implements ImageService {
|
||||
if (m.matches()) {
|
||||
final String extension = m.group(1);
|
||||
return switch (extension) {
|
||||
case "jpg", "jpeg" -> "image/jpeg";
|
||||
case "png" -> "image/png";
|
||||
case "svg" -> "image/svg+xml";
|
||||
case "webp" -> "image/webp";
|
||||
case "jpg", "jpeg" -> IMAGE_JPEG;
|
||||
case "png" -> IMAGE_PNG;
|
||||
case "svg" -> IMAGE_SVG;
|
||||
case "webp" -> IMAGE_WEBP;
|
||||
default -> throw new IllegalArgumentException("Cannot determine mime type for extension: " + extension);
|
||||
};
|
||||
} else {
|
||||
@ -64,13 +68,13 @@ public class S3ImageService implements ImageService {
|
||||
|
||||
private String getExtension(String mimeType) throws ImageException {
|
||||
return switch (mimeType) {
|
||||
case "image/jpeg" -> "jpg";
|
||||
case "image/png" -> "png";
|
||||
case "image/svg+xml" -> "svg";
|
||||
case "image/webp" -> "webp";
|
||||
case IMAGE_JPEG -> "jpg";
|
||||
case IMAGE_PNG -> "png";
|
||||
case IMAGE_SVG -> "svg";
|
||||
case IMAGE_WEBP -> "webp";
|
||||
default -> throw new ImageException(
|
||||
ImageException.Type.UNKNOWN_MIME_TYPE,
|
||||
"Unknown mime type: " + mimeType
|
||||
ImageException.Type.UNSUPPORTED_IMAGE_TYPE,
|
||||
"Unsupported mime type: " + mimeType
|
||||
);
|
||||
};
|
||||
}
|
||||
@ -101,6 +105,18 @@ public class S3ImageService implements ImageService {
|
||||
return didTransfer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @apiNote Consumes and closes the {@link java.io.InputStream}.
|
||||
*
|
||||
* @param owner the User owner
|
||||
* @param userFilename the name of the uploaded file from the user
|
||||
* @param inputStream the image content
|
||||
* @param objectSize the size of the image, in bytes
|
||||
* @param createSpec the metadata for the image
|
||||
* @return an {@link app.mealsmadeeasy.api.image.Image} representing the stored image
|
||||
* @throws IOException if there are any errors related to IO while storing the image
|
||||
* @throws ImageException if there are any errors processing the image
|
||||
*/
|
||||
@Override
|
||||
public Image create(
|
||||
User owner,
|
||||
@ -113,19 +129,29 @@ public class S3ImageService implements ImageService {
|
||||
final String uuid = UUID.randomUUID().toString();
|
||||
final String extension = this.getExtension(mimeType);
|
||||
final String filename = uuid + "." + extension;
|
||||
|
||||
final var baos = new ByteArrayOutputStream();
|
||||
inputStream.transferTo(baos);
|
||||
final InputStream toStore = new ByteArrayInputStream(baos.toByteArray());
|
||||
final InputStream toRead = new ByteArrayInputStream(baos.toByteArray());
|
||||
|
||||
final String objectName = this.s3Manager.store(
|
||||
this.imageBucketName, filename, mimeType, inputStream, objectSize
|
||||
this.imageBucketName, filename, mimeType, toStore, objectSize
|
||||
);
|
||||
|
||||
final int height, width;
|
||||
try (final InputStream imageContent = this.s3Manager.load(this.imageBucketName, objectName)) {
|
||||
final BufferedImage bufferedImage = ImageIO.read(imageContent);
|
||||
final BufferedImage bufferedImage = ImageIO.read(toRead);
|
||||
if (bufferedImage == null) {
|
||||
logger.error("ImageIO could not read image: {} ({})", userFilename, objectName);
|
||||
}
|
||||
height = bufferedImage.getHeight();
|
||||
width = bufferedImage.getWidth();
|
||||
throw new ImageException(
|
||||
ImageException.Type.UNSUPPORTED_IMAGE_TYPE,
|
||||
"ImageIO could not read image: " + userFilename
|
||||
);
|
||||
}
|
||||
final int height = bufferedImage.getHeight();
|
||||
final int width = bufferedImage.getWidth();
|
||||
|
||||
toRead.close();
|
||||
toStore.close();
|
||||
inputStream.close();
|
||||
|
||||
final S3ImageEntity draft = new S3ImageEntity();
|
||||
draft.setOwner((UserEntity) owner);
|
||||
|
||||
@ -41,8 +41,8 @@ public class ImageView {
|
||||
private @Nullable String caption;
|
||||
private UserInfoView owner;
|
||||
private boolean isPublic;
|
||||
private int height;
|
||||
private int width;
|
||||
private @Nullable Integer height;
|
||||
private @Nullable Integer width;
|
||||
private @Nullable Set<UserInfoView> viewers;
|
||||
|
||||
public String getUrl() {
|
||||
@ -117,19 +117,19 @@ public class ImageView {
|
||||
this.isPublic = isPublic;
|
||||
}
|
||||
|
||||
public int getHeight() {
|
||||
public @Nullable Integer getHeight() {
|
||||
return this.height;
|
||||
}
|
||||
|
||||
public void setHeight(int height) {
|
||||
public void setHeight(Integer height) {
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
public int getWidth() {
|
||||
public @Nullable Integer getWidth() {
|
||||
return this.width;
|
||||
}
|
||||
|
||||
public void setWidth(int width) {
|
||||
public void setWidth(Integer width) {
|
||||
this.width = width;
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user