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
|
// https://mvnrepository.com/artifact/com.twelvemonkeys.imageio/imageio-webp
|
||||||
runtimeOnly 'com.twelvemonkeys.imageio:imageio-webp:3.12.0'
|
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'
|
compileOnly 'org.jetbrains:annotations:24.1.0'
|
||||||
|
|
||||||
// Custom testing
|
// Custom testing
|
||||||
|
|||||||
@ -16,7 +16,7 @@ public interface Image {
|
|||||||
@Nullable String getCaption();
|
@Nullable String getCaption();
|
||||||
User getOwner();
|
User getOwner();
|
||||||
boolean isPublic();
|
boolean isPublic();
|
||||||
int getHeight();
|
@Nullable Integer getHeight();
|
||||||
int getWidth();
|
@Nullable Integer getWidth();
|
||||||
Set<User> getViewers();
|
Set<User> getViewers();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,7 +6,7 @@ public class ImageException extends Exception {
|
|||||||
INVALID_ID,
|
INVALID_ID,
|
||||||
INVALID_USERNAME_OR_FILENAME,
|
INVALID_USERNAME_OR_FILENAME,
|
||||||
IMAGE_NOT_FOUND,
|
IMAGE_NOT_FOUND,
|
||||||
UNKNOWN_MIME_TYPE
|
UNSUPPORTED_IMAGE_TYPE,
|
||||||
}
|
}
|
||||||
|
|
||||||
private final Type type;
|
private final Type type;
|
||||||
|
|||||||
@ -35,11 +35,9 @@ public class S3ImageEntity implements Image {
|
|||||||
@Column(nullable = false)
|
@Column(nullable = false)
|
||||||
private String objectName;
|
private String objectName;
|
||||||
|
|
||||||
@Column(nullable = false)
|
private Integer height;
|
||||||
private int height;
|
|
||||||
|
|
||||||
@Column(nullable = false)
|
private Integer width;
|
||||||
private int width;
|
|
||||||
|
|
||||||
@ManyToOne(optional = false)
|
@ManyToOne(optional = false)
|
||||||
@JoinColumn(name = "owner_id", nullable = false)
|
@JoinColumn(name = "owner_id", nullable = false)
|
||||||
@ -123,20 +121,20 @@ public class S3ImageEntity implements Image {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getHeight() {
|
public @Nullable Integer getHeight() {
|
||||||
return this.height;
|
return this.height;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setHeight(int height) {
|
public void setHeight(Integer height) {
|
||||||
this.height = height;
|
this.height = height;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getWidth() {
|
public @Nullable Integer getWidth() {
|
||||||
return this.width;
|
return this.width;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setWidth(int width) {
|
public void setWidth(Integer width) {
|
||||||
this.width = 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.User;
|
||||||
import app.mealsmadeeasy.api.user.UserEntity;
|
import app.mealsmadeeasy.api.user.UserEntity;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.security.access.prepost.PostAuthorize;
|
import org.springframework.security.access.prepost.PostAuthorize;
|
||||||
import org.springframework.security.access.prepost.PreAuthorize;
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
@ -16,6 +14,8 @@ import org.springframework.stereotype.Service;
|
|||||||
|
|
||||||
import javax.imageio.ImageIO;
|
import javax.imageio.ImageIO;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
@ -27,7 +27,11 @@ import java.util.regex.Pattern;
|
|||||||
public class S3ImageService implements ImageService {
|
public class S3ImageService implements ImageService {
|
||||||
|
|
||||||
private static final Pattern extensionPattern = Pattern.compile(".+\\.(.+)$");
|
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 S3Manager s3Manager;
|
||||||
private final S3ImageRepository imageRepository;
|
private final S3ImageRepository imageRepository;
|
||||||
@ -51,10 +55,10 @@ public class S3ImageService implements ImageService {
|
|||||||
if (m.matches()) {
|
if (m.matches()) {
|
||||||
final String extension = m.group(1);
|
final String extension = m.group(1);
|
||||||
return switch (extension) {
|
return switch (extension) {
|
||||||
case "jpg", "jpeg" -> "image/jpeg";
|
case "jpg", "jpeg" -> IMAGE_JPEG;
|
||||||
case "png" -> "image/png";
|
case "png" -> IMAGE_PNG;
|
||||||
case "svg" -> "image/svg+xml";
|
case "svg" -> IMAGE_SVG;
|
||||||
case "webp" -> "image/webp";
|
case "webp" -> IMAGE_WEBP;
|
||||||
default -> throw new IllegalArgumentException("Cannot determine mime type for extension: " + extension);
|
default -> throw new IllegalArgumentException("Cannot determine mime type for extension: " + extension);
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
@ -64,13 +68,13 @@ public class S3ImageService implements ImageService {
|
|||||||
|
|
||||||
private String getExtension(String mimeType) throws ImageException {
|
private String getExtension(String mimeType) throws ImageException {
|
||||||
return switch (mimeType) {
|
return switch (mimeType) {
|
||||||
case "image/jpeg" -> "jpg";
|
case IMAGE_JPEG -> "jpg";
|
||||||
case "image/png" -> "png";
|
case IMAGE_PNG -> "png";
|
||||||
case "image/svg+xml" -> "svg";
|
case IMAGE_SVG -> "svg";
|
||||||
case "image/webp" -> "webp";
|
case IMAGE_WEBP -> "webp";
|
||||||
default -> throw new ImageException(
|
default -> throw new ImageException(
|
||||||
ImageException.Type.UNKNOWN_MIME_TYPE,
|
ImageException.Type.UNSUPPORTED_IMAGE_TYPE,
|
||||||
"Unknown mime type: " + mimeType
|
"Unsupported mime type: " + mimeType
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -101,6 +105,18 @@ public class S3ImageService implements ImageService {
|
|||||||
return didTransfer;
|
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
|
@Override
|
||||||
public Image create(
|
public Image create(
|
||||||
User owner,
|
User owner,
|
||||||
@ -113,19 +129,29 @@ public class S3ImageService implements ImageService {
|
|||||||
final String uuid = UUID.randomUUID().toString();
|
final String uuid = UUID.randomUUID().toString();
|
||||||
final String extension = this.getExtension(mimeType);
|
final String extension = this.getExtension(mimeType);
|
||||||
final String filename = uuid + "." + extension;
|
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(
|
final String objectName = this.s3Manager.store(
|
||||||
this.imageBucketName, filename, mimeType, inputStream, objectSize
|
this.imageBucketName, filename, mimeType, toStore, objectSize
|
||||||
);
|
);
|
||||||
|
|
||||||
final int height, width;
|
final BufferedImage bufferedImage = ImageIO.read(toRead);
|
||||||
try (final InputStream imageContent = this.s3Manager.load(this.imageBucketName, objectName)) {
|
|
||||||
final BufferedImage bufferedImage = ImageIO.read(imageContent);
|
|
||||||
if (bufferedImage == null) {
|
if (bufferedImage == null) {
|
||||||
logger.error("ImageIO could not read image: {} ({})", userFilename, objectName);
|
throw new ImageException(
|
||||||
}
|
ImageException.Type.UNSUPPORTED_IMAGE_TYPE,
|
||||||
height = bufferedImage.getHeight();
|
"ImageIO could not read image: " + userFilename
|
||||||
width = bufferedImage.getWidth();
|
);
|
||||||
}
|
}
|
||||||
|
final int height = bufferedImage.getHeight();
|
||||||
|
final int width = bufferedImage.getWidth();
|
||||||
|
|
||||||
|
toRead.close();
|
||||||
|
toStore.close();
|
||||||
|
inputStream.close();
|
||||||
|
|
||||||
final S3ImageEntity draft = new S3ImageEntity();
|
final S3ImageEntity draft = new S3ImageEntity();
|
||||||
draft.setOwner((UserEntity) owner);
|
draft.setOwner((UserEntity) owner);
|
||||||
|
|||||||
@ -41,8 +41,8 @@ public class ImageView {
|
|||||||
private @Nullable String caption;
|
private @Nullable String caption;
|
||||||
private UserInfoView owner;
|
private UserInfoView owner;
|
||||||
private boolean isPublic;
|
private boolean isPublic;
|
||||||
private int height;
|
private @Nullable Integer height;
|
||||||
private int width;
|
private @Nullable Integer width;
|
||||||
private @Nullable Set<UserInfoView> viewers;
|
private @Nullable Set<UserInfoView> viewers;
|
||||||
|
|
||||||
public String getUrl() {
|
public String getUrl() {
|
||||||
@ -117,19 +117,19 @@ public class ImageView {
|
|||||||
this.isPublic = isPublic;
|
this.isPublic = isPublic;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getHeight() {
|
public @Nullable Integer getHeight() {
|
||||||
return this.height;
|
return this.height;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setHeight(int height) {
|
public void setHeight(Integer height) {
|
||||||
this.height = height;
|
this.height = height;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getWidth() {
|
public @Nullable Integer getWidth() {
|
||||||
return this.width;
|
return this.width;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setWidth(int width) {
|
public void setWidth(Integer width) {
|
||||||
this.width = width;
|
this.width = width;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user