Skip to content

Commit f5dd91f

Browse files
authored
Image API External Storage Support (#554)
* Support for ImageHeight and Width when Storage is Enabled (cherry picked from commit 7be4e0e) * Illigal Argument Exception catch (cherry picked from commit 96bef67) * Support GetFileSize (cherry picked from commit 92119c1) * Crop, Resize, Scale, Rotate support for S3. (cherry picked from commit 8132c77) * Remove unused code (cherry picked from commit 935af8c) * Crop, Resize, Scale, Rotate support for S3. (cherry picked from commit b2005c7) * Add UnitTests (cherry picked from commit 414d246) * GXFile should detect if it is already a absolute file path. (cherry picked from commit 158099a) * Fix * Fix Code Quality (cherry picked from commit 58c42fc) * Not working in some scenarios for external storage urls (cherry picked from commit 99da7fc) * GxFile more defensive code (cherry picked from commit c42979d) * Compile fixes * Compile error
1 parent 6c2dc88 commit f5dd91f

File tree

15 files changed

+322
-73
lines changed

15 files changed

+322
-73
lines changed

common/src/main/java/com/genexus/util/GXExternalFileInfo.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import com.genexus.CommonUtil;
99
import com.genexus.db.driver.*;
1010
import com.genexus.common.interfaces.SpecificImplementation;
11+
import com.genexus.db.driver.ResourceAccessControlList;
1112

1213
import java.util.Date;
1314

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package com.genexus.util;
2+
3+
public enum GxFileInfoSourceType {
4+
Unknown,
5+
LocalFile,
6+
ExternalFile
7+
}

gxexternalproviders/src/test/java/com/genexus/db/driver/TestExternalProvider.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.genexus.db.driver;
22

3+
import com.genexus.specific.java.Connect;
34
import org.junit.Assume;
45
import org.junit.Before;
56
import org.junit.Test;
@@ -15,6 +16,7 @@
1516
import java.util.List;
1617
import java.util.Random;
1718

19+
import com.genexus.db.driver.ResourceAccessControlList;
1820
import static org.junit.Assert.*;
1921
import static org.junit.Assume.assumeTrue;
2022

@@ -36,7 +38,7 @@ public boolean supportsObjectAcls() {
3638

3739
@Before
3840
public void beforeEachTestMethod() {
39-
41+
Connect.init();
4042
boolean testEnabled = false;
4143
try {
4244
testEnabled = ExternalProviderHelper.getEnvironmentVariable(getProviderName() + "_TEST_ENABLED", false).equals("true");
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.genexus.gxoffice;
22

3+
import com.genexus.db.driver.ResourceAccessControlList;
4+
35
public class Constants {
4-
public static boolean EXTERNAL_PRIVATE_UPLOAD = true;
6+
public static ResourceAccessControlList EXTERNAL_UPLOAD_ACL = ResourceAccessControlList.Private;
57
}

gxoffice/src/main/java/com/genexus/gxoffice/poi/hssf/ExcelDocument.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ public short Save()
9090
workBook.write(fs);
9191
ByteArrayInputStream in = new ByteArrayInputStream(fs.toByteArray());
9292
fs.close();
93-
GXFile file = new GXFile(xlsFileName, Constants.EXTERNAL_PRIVATE_UPLOAD);
93+
GXFile file = new GXFile(xlsFileName, Constants.EXTERNAL_UPLOAD_ACL);
9494
file.create(in, true);
9595
}
9696
catch(Exception e)

gxoffice/src/main/java/com/genexus/gxoffice/poi/sxssf/ExcelDocument.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package com.genexus.gxoffice.poi.sxssf;
22

3+
import com.genexus.util.GxFileInfoSourceType;
34
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
45
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
56

7+
import com.genexus.gxoffice.Constants;
68
import com.genexus.gxoffice.IExcelCells;
79
import com.genexus.gxoffice.IGxError;
810
import com.genexus.gxoffice.poi.xssf.StylesCache;
@@ -27,7 +29,7 @@ public short Open(String fileName) {
2729
return errCod;
2830
}
2931
} else {
30-
GXFile file = new GXFile(fileName);
32+
GXFile file = new GXFile("", fileName, Constants.EXTERNAL_UPLOAD_ACL, GxFileInfoSourceType.Unknown);
3133
if (file.exists()) {
3234
// System.out.println("Opening..");
3335
workBook = new SXSSFWorkbook(new XSSFWorkbook(file.getStream()));

gxoffice/src/main/java/com/genexus/gxoffice/poi/xssf/ExcelDocument.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import java.io.ByteArrayInputStream;
44
import java.io.ByteArrayOutputStream;
55

6+
import com.genexus.util.GxFileInfoSourceType;
67
import org.apache.poi.ss.usermodel.Row;
78
import org.apache.poi.ss.usermodel.Sheet;
89
import org.apache.poi.ss.usermodel.Workbook;
@@ -36,7 +37,7 @@ public short Open(String fileName) {
3637
return errCod;
3738
}
3839
} else {
39-
GXFile file = new GXFile(fileName, Constants.EXTERNAL_PRIVATE_UPLOAD);
40+
GXFile file = new GXFile("", fileName, Constants.EXTERNAL_UPLOAD_ACL, GxFileInfoSourceType.Unknown);
4041
if (file.exists()) {
4142
// System.out.println("Opening..");
4243
workBook = new XSSFWorkbook(file.getStream());
@@ -74,7 +75,8 @@ public short Save() {
7475
workBook.write(fs);
7576
ByteArrayInputStream in = new ByteArrayInputStream(fs.toByteArray());
7677
fs.close();
77-
GXFile file = new GXFile(xlsFileName, Constants.EXTERNAL_PRIVATE_UPLOAD);
78+
boolean isAbsolute = new java.io.File(xlsFileName).isAbsolute();
79+
GXFile file = new GXFile(xlsFileName, Constants.EXTERNAL_UPLOAD_ACL, isAbsolute);
7880
file.create(in, true);
7981
saved = true;
8082
} catch (Exception e) {

java/src/main/java/com/genexus/GxImageUtil.java

Lines changed: 99 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -5,99 +5,146 @@
55
import java.awt.geom.AffineTransform;
66
import java.awt.image.AffineTransformOp;
77
import java.awt.image.BufferedImage;
8-
import java.io.File;
9-
import java.io.FileOutputStream;
10-
import java.io.IOException;
8+
import java.io.*;
119

10+
import com.genexus.db.driver.ResourceAccessControlList;
11+
import com.genexus.util.GxFileInfoSourceType;
1212
import com.genexus.util.GXFile;
1313
import org.apache.logging.log4j.Logger;
1414

1515
public class GxImageUtil {
1616
private static Logger log = org.apache.logging.log4j.LogManager.getLogger(GxImageUtil.class);
17+
private static int INVALID_CODE = -1;
1718

18-
private static String getImageAbsolutePath(String imageFile){
19-
if (CommonUtil.isUploadPrefix(imageFile)) {
20-
return new GXFile(imageFile).getAbsolutePath();
19+
private static InputStream getInputStream(String filePathOrUrl) throws IOException {
20+
return getGXFile(filePathOrUrl).getStream();
21+
}
22+
23+
private static BufferedImage createBufferedImageFromURI(String filePathOrUrl) throws IOException
24+
{
25+
try (InputStream is = getGXFile(filePathOrUrl).getStream()) {
26+
return ImageIO.read(is);
27+
}
28+
catch (IOException e) {
29+
log.error("Failed to read image stream: " + filePathOrUrl);
30+
throw e;
2131
}
22-
String defaultPath = com.genexus.ModelContext.getModelContext().getHttpContext().getDefaultPath();
23-
return imageFile.startsWith(defaultPath)? imageFile : defaultPath + imageFile.replace("/", File.separator);
32+
}
33+
34+
private static GXFile getGXFile(String filePathOrUrl) {
35+
String basePath = (com.genexus.ModelContext.getModelContext() != null) ? com.genexus.ModelContext.getModelContext().getHttpContext().getDefaultPath(): "";
36+
return new GXFile(basePath, filePathOrUrl, ResourceAccessControlList.Default, GxFileInfoSourceType.Unknown);
2437
}
2538

2639
public static long getFileSize(String imageFile){
27-
return new File(getImageAbsolutePath(imageFile)).length();
40+
if (!isValidInput(imageFile))
41+
return INVALID_CODE;
42+
43+
return new GXFile(imageFile).getLength();
2844
}
2945

3046
public static int getImageHeight(String imageFile) {
47+
if (!isValidInput(imageFile))
48+
return INVALID_CODE;
49+
3150
try {
32-
BufferedImage image = ImageIO.read(new File(getImageAbsolutePath(imageFile)));
33-
return image.getHeight();
51+
return createBufferedImageFromURI(imageFile).getHeight();
3452
}
35-
catch (IOException e) {
53+
catch (Exception e) {
3654
log.error("getImageHeight " + imageFile + " failed" , e);
37-
return 0;
3855
}
56+
return INVALID_CODE;
57+
}
58+
59+
private static boolean isValidInput(String imageFile) {
60+
boolean isValid = imageFile != null && imageFile.length() > 0;
61+
if (!isValid) {
62+
log.debug("Image Api - FileName cannot be empty");
63+
}
64+
return isValid;
3965
}
4066

4167
public static int getImageWidth(String imageFile) {
68+
if (!isValidInput(imageFile))
69+
return INVALID_CODE;
70+
4271
try {
43-
BufferedImage image = ImageIO.read(new File(getImageAbsolutePath(imageFile)));
44-
return image.getWidth();
72+
return createBufferedImageFromURI(imageFile).getWidth();
4573
}
46-
catch (IOException e) {
74+
catch (Exception e) {
4775
log.error("getImageWidth " + imageFile + " failed" , e);
48-
return 0;
4976
}
77+
return INVALID_CODE;
5078
}
5179

52-
public static String crop(String imageFile, int x, int y, int width, int height){
80+
public static String crop(String imageFile, int x, int y, int width, int height) {
81+
if (!isValidInput(imageFile))
82+
return "";
83+
5384
try {
54-
String absolutePath = getImageAbsolutePath(imageFile);
55-
BufferedImage image = ImageIO.read(new File(absolutePath));
56-
BufferedImage cropedImage = image.getSubimage(x, y, width, height);
57-
ImageIO.write(cropedImage, CommonUtil.getFileType(absolutePath), new FileOutputStream(absolutePath));
85+
BufferedImage image = createBufferedImageFromURI(imageFile);
86+
BufferedImage croppedImage = image.getSubimage(x, y, width, height);
87+
writeImage(croppedImage, imageFile);
5888
}
59-
catch (IOException e) {
89+
catch (Exception e) {
6090
log.error("crop " + imageFile + " failed" , e);
6191
}
6292
return imageFile;
6393
}
6494

65-
public static String flipHorizontally(String imageFile){
95+
private static void writeImage(BufferedImage croppedImage, String destinationFilePathOrUrl) throws IOException {
96+
try (ByteArrayOutputStream outStream = new ByteArrayOutputStream()) {
97+
ImageIO.write(croppedImage, CommonUtil.getFileType(destinationFilePathOrUrl), outStream);
98+
try (ByteArrayInputStream inStream = new ByteArrayInputStream(outStream.toByteArray())) {
99+
GXFile file = getGXFile(destinationFilePathOrUrl);
100+
file.create(inStream, true);
101+
file.close();
102+
}
103+
}
104+
}
105+
106+
public static String flipHorizontally(String imageFile) {
107+
if (!isValidInput(imageFile))
108+
return "";
109+
66110
try {
67-
String absolutePath = getImageAbsolutePath(imageFile);
68-
BufferedImage image = ImageIO.read(new File(absolutePath));
111+
BufferedImage image = createBufferedImageFromURI(imageFile);
69112
AffineTransform tx = AffineTransform.getScaleInstance(-1, 1);
70113
tx.translate(-image.getWidth(null), 0);
71114
AffineTransformOp op = new AffineTransformOp(tx, AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
72-
BufferedImage flipedImage = op.filter(image, null);
73-
ImageIO.write(flipedImage, CommonUtil.getFileType(absolutePath), new FileOutputStream(absolutePath));
115+
BufferedImage flippedImage = op.filter(image, null);
116+
writeImage(flippedImage, imageFile);
74117
}
75-
catch (IOException e) {
118+
catch (Exception e) {
76119
log.error("flip horizontal " + imageFile + " failed" , e);
77120
}
78121
return imageFile;
79122
}
80123

81-
public static String flipVertically(String imageFile){
124+
public static String flipVertically(String imageFile) {
125+
if (!isValidInput(imageFile))
126+
return "";
127+
82128
try {
83-
String absolutePath = getImageAbsolutePath(imageFile);
84-
BufferedImage image = ImageIO.read(new File(absolutePath));
129+
BufferedImage image = createBufferedImageFromURI(imageFile);
85130
AffineTransform tx = AffineTransform.getScaleInstance(1, -1);
86131
tx.translate(0, -image.getHeight(null));
87132
AffineTransformOp op = new AffineTransformOp(tx, AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
88-
BufferedImage flipedImage = op.filter(image, null);
89-
ImageIO.write(flipedImage, CommonUtil.getFileType(absolutePath), new FileOutputStream(absolutePath));
133+
BufferedImage flippedImage = op.filter(image, null);
134+
writeImage(flippedImage, imageFile);
90135
}
91-
catch (IOException e) {
136+
catch (Exception e) {
92137
log.error("flip vertical " + imageFile + " failed" , e);
93138
}
94139
return imageFile;
95140
}
96141

97-
public static String resize(String imageFile, int width, int height, boolean keepAspectRatio){
142+
public static String resize(String imageFile, int width, int height, boolean keepAspectRatio) {
143+
if (!isValidInput(imageFile))
144+
return "";
145+
98146
try {
99-
String absolutePath = getImageAbsolutePath(imageFile);
100-
BufferedImage image = ImageIO.read(new File(absolutePath));
147+
BufferedImage image = createBufferedImageFromURI(imageFile);
101148
if (keepAspectRatio) {
102149
double imageHeight = image.getHeight();
103150
double imageWidth = image.getWidth();
@@ -112,34 +159,37 @@ public static String resize(String imageFile, int width, int height, boolean kee
112159
Graphics2D g2d = resizedImage.createGraphics();
113160
g2d.drawImage(image, 0, 0, width, height, null);
114161
g2d.dispose();
115-
ImageIO.write(resizedImage, CommonUtil.getFileType(absolutePath), new FileOutputStream(absolutePath));
162+
writeImage(resizedImage, imageFile);
116163
}
117-
catch (IOException e) {
164+
catch (Exception e) {
118165
log.error("resize " + imageFile + " failed" , e);
119166
}
120167
return imageFile;
121168
}
122169

123-
public static String scale(String imageFile, short percent){
170+
public static String scale(String imageFile, short percent) {
171+
if (!isValidInput(imageFile))
172+
return "";
173+
124174
try {
125-
String absolutePath = getImageAbsolutePath(imageFile);
126-
BufferedImage image = ImageIO.read(new File(absolutePath));
175+
BufferedImage image = createBufferedImageFromURI(imageFile);
127176
imageFile = resize(imageFile, image.getWidth() * percent / 100, image.getHeight() * percent / 100,true);
128177
}
129-
catch (IOException e) {
178+
catch (Exception e) {
130179
log.error("scale " + imageFile + " failed" , e);
131180
}
132181
return imageFile;
133182
}
134183

135-
public static String rotate(String imageFile, short angle){
184+
public static String rotate(String imageFile, short angle) {
185+
if (!isValidInput(imageFile))
186+
return "";
136187
try {
137-
String absolutePath = getImageAbsolutePath(imageFile);
138-
BufferedImage image = ImageIO.read(new File(absolutePath));
188+
BufferedImage image = createBufferedImageFromURI(imageFile);
139189
BufferedImage rotatedImage = rotateImage(image, angle);
140-
ImageIO.write(rotatedImage, CommonUtil.getFileType(absolutePath), new FileOutputStream(absolutePath));
190+
writeImage(rotatedImage, imageFile);
141191
}
142-
catch (IOException e) {
192+
catch (Exception e) {
143193
log.error("rotate " + imageFile + " failed" , e);
144194
}
145195
return imageFile;

java/src/main/java/com/genexus/PrivateUtilities.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
import java.lang.reflect.Field;
2020
import java.net.HttpURLConnection;
2121
import java.net.URL;
22+
import java.nio.file.InvalidPathException;
23+
import java.nio.file.Paths;
2224
import java.util.Properties;
2325
import java.util.Random;
2426
import java.util.zip.DeflaterOutputStream;
@@ -608,6 +610,20 @@ private static File parent(File f) {
608610
return new File(dirname);
609611
}
610612

613+
/**
614+
* <pre>
615+
* Checks if a string is an absolute path to local file system.
616+
* Null safe.
617+
* </pre>
618+
*/
619+
public static boolean isAbsoluteFilePath(String path) {
620+
try {
621+
return Paths.get(path).isAbsolute();
622+
} catch (InvalidPathException | NullPointerException ex) {
623+
return false;
624+
}
625+
}
626+
611627
public static String addLastPathSeparator(String dir)
612628
{
613629
return addLastChar(dir, File.separator);

0 commit comments

Comments
 (0)