diff --git a/build.number b/build.number index 948df68..695c088 100644 --- a/build.number +++ b/build.number @@ -1,3 +1,3 @@ #Build Number for ANT. Do not edit! -#Tue Oct 31 17:27:17 CET 2023 -build.number=4 +#Tue Oct 31 20:27:20 CET 2023 +build.number=5 diff --git a/source/java/src/org/lucee/extension/resource/s3/function/S3Download.java b/source/java/src/org/lucee/extension/resource/s3/function/S3Download.java index f7cc089..4c88ac3 100644 --- a/source/java/src/org/lucee/extension/resource/s3/function/S3Download.java +++ b/source/java/src/org/lucee/extension/resource/s3/function/S3Download.java @@ -5,19 +5,22 @@ import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.io.InputStreamReader; +import java.lang.reflect.Method; import java.nio.charset.Charset; import java.util.Arrays; import org.lucee.extension.resource.s3.S3; -import org.lucee.extension.resource.s3.util.print; import com.amazonaws.services.s3.model.S3Object; import lucee.commons.io.res.Resource; +import lucee.commons.lang.types.RefInteger; import lucee.loader.engine.CFMLEngine; import lucee.loader.engine.CFMLEngineFactory; +import lucee.runtime.Component; import lucee.runtime.PageContext; import lucee.runtime.exp.PageException; +import lucee.runtime.type.Collection.Key; import lucee.runtime.type.FunctionArgument; import lucee.runtime.type.UDF; import lucee.runtime.util.Cast; @@ -54,51 +57,29 @@ public Object invoke(PageContext pc, Object[] args) throws PageException { // validate UDF targetUDF = null; + Component targetCFC = null; Resource targetRes = null; - int mode = 0; - int blockSize = 0; + RefInteger mode = eng.getCreationUtil().createRefInteger(0); + RefInteger blockSize = eng.getCreationUtil().createRefInteger(0); + Key INVOKE = eng.getCastUtil().toKey("invoke"); + Key BEFORE = eng.getCastUtil().toKey("before"); + Key AFTER = eng.getCastUtil().toKey("after"); + if (target != null) { if (target instanceof UDF) { targetUDF = (UDF) target; + validateInvoke(pc, targetUDF, mode, blockSize, false); - // function return type - if (!(targetUDF.getReturnType() == TYPE_ANY || targetUDF.getReturnType() == TYPE_BOOLEAN)) throw eng.getExceptionUtil().createFunctionException(pc, "S3Download", 3, - "target", "the function invoke of the component listener must have the return type boolean.", ""); - - // function invoke arguments - FunctionArgument[] udfArgs = targetUDF.getFunctionArguments(); - if (udfArgs.length < 1 || udfArgs.length > 1) throw eng.getExceptionUtil().createFunctionException(pc, "S3Download", 3, "target", - "you need to define an argument for the closure/function passed in following this pattern (string line|binary{number}|string{number})", ""); - - FunctionArgument arg = udfArgs[0]; - if (!(arg.getType() == TYPE_ANY || arg.getType() == TYPE_STRING)) throw eng.getExceptionUtil().createFunctionException(pc, "S3Download", 3, "target", - "the first argument of the closuere/function need to be defined as a string or no defintion at all", ""); - print.e("name:" + arg.getName()); - - String name = (arg.getName().getString() + "").toLowerCase().trim(); - if ("line".equals(name)) { - mode = MODE_LINE; - } - else if (name.startsWith("binary")) { - mode = MODE_BINARY; - blockSize = eng.getCastUtil().toIntValue(name.substring(6)); - if (blockSize <= 0) throw eng.getExceptionUtil().createFunctionException(pc, "S3Download", 3, "target", - "invalid block size defintion with the argument [binary{Number}], blocksize need to be a positive number, so the argument name should for example look like this [binary1000] to get block size 1000", - ""); - } - else if (name.startsWith("string")) { - mode = MODE_STRING; - blockSize = eng.getCastUtil().toIntValue(name.substring(6)); - if (blockSize <= 0) throw eng.getExceptionUtil().createFunctionException(pc, "S3Download", 3, "target", - "invalid block size defintion with the argument [string{Number}], blocksize need to be a positive number, so the argument name should for example look like this [string1000] to get block size 1000", - ""); - } - else { - throw eng.getExceptionUtil().createFunctionException(pc, "S3Download", 3, "target", - "the first argument of the closuere/function need to be define an argument where the name does follow one of this patterns [line, binary(Number), string(Number) ]", - ""); - } - + } + else if (target instanceof Component) { + targetCFC = (Component) target; + Component csa = toComponentSpecificAccess(Component.ACCESS_PRIVATE, targetCFC); + boolean hasBefore = toFunction(csa.get(BEFORE), null) != null; + boolean hasAfter = toFunction(csa.get(AFTER), null) != null; + UDF invoke = toFunction(csa.get(INVOKE), null); + if (invoke == null) throw eng.getExceptionUtil().createFunctionException(pc, "DirectoryEvery", 2, "component", + "the listener component does not contain a instance function with name [invoke] that is required", null); + validateInvoke(pc, invoke, mode, blockSize, false); } else if ((targetRes = S3Write.toResource(pc, target, false, null)) == null) { // can also be a charset defintion @@ -115,16 +96,32 @@ else if ((targetRes = S3Write.toResource(pc, target, false, null)) == null) { S3Object obj = s3.getData(bucketName, objectName); Cast caster = eng.getCastUtil(); // stream to UDF - if (targetUDF != null) { + boolean isUDF; + if ((isUDF = (targetUDF != null)) || targetCFC != null) { + // LINE - if (MODE_LINE == mode) { + if (MODE_LINE == mode.toInt()) { BufferedReader reader = null; try { if (charset == null) charset = pc.getConfig().getResourceCharset(); reader = new BufferedReader(new InputStreamReader(obj.getObjectContent(), charset)); String line; + if (!isUDF) { + targetCFC.call(pc, BEFORE, new Object[] {}); + } while ((line = reader.readLine()) != null) { - if (!caster.toBooleanValue(targetUDF.call(pc, new Object[] { line }, true))) return null; + if (isUDF) { + if (!caster.toBooleanValue(targetUDF.call(pc, new Object[] { line }, true))) return null; + } + else { + if (!caster.toBooleanValue(targetCFC.call(pc, INVOKE, new Object[] { line }))) { + targetCFC.call(pc, AFTER, new Object[] {}); + return null; + } + } + } + if (!isUDF) { + targetCFC.call(pc, AFTER, new Object[] {}); } return null; } @@ -133,16 +130,30 @@ else if ((targetRes = S3Write.toResource(pc, target, false, null)) == null) { } } // STRING - else if (MODE_STRING == mode) { + else if (MODE_STRING == mode.toInt()) { BufferedReader reader = null; - char[] buffer = new char[blockSize]; + char[] buffer = new char[blockSize.toInt()]; try { + if (!isUDF) { + targetCFC.call(pc, BEFORE, new Object[] {}); + } if (charset == null) charset = pc.getConfig().getResourceCharset(); reader = new BufferedReader(new InputStreamReader(obj.getObjectContent(), charset)); int numCharsRead; while ((numCharsRead = reader.read(buffer, 0, buffer.length)) != -1) { String block = new String(buffer, 0, numCharsRead); - if (!caster.toBooleanValue(targetUDF.call(pc, new Object[] { block }, true))) return null; + if (isUDF) { + if (!caster.toBooleanValue(targetUDF.call(pc, new Object[] { block }, true))) return null; + } + else { + if (!caster.toBooleanValue(targetCFC.call(pc, INVOKE, new Object[] { block }))) { + targetCFC.call(pc, AFTER, new Object[] {}); + return null; + } + } + } + if (!isUDF) { + targetCFC.call(pc, AFTER, new Object[] {}); } return null; } @@ -153,20 +164,31 @@ else if (MODE_STRING == mode) { // BINARY else { InputStream input = null; - byte[] buffer = new byte[blockSize]; + byte[] buffer = new byte[blockSize.toInt()]; + byte[] arg; try { + if (!isUDF) { + targetCFC.call(pc, BEFORE, new Object[] {}); + } input = new BufferedInputStream(obj.getObjectContent()); int bytesRead; while ((bytesRead = input.read(buffer)) != -1) { - if (bytesRead == buffer.length) { - if (!caster.toBooleanValue(targetUDF.call(pc, new Object[] { buffer }, true))) return null; + + if (bytesRead == buffer.length) arg = buffer; + else arg = Arrays.copyOf(buffer, bytesRead); + if (isUDF) { + if (!caster.toBooleanValue(targetUDF.call(pc, new Object[] { arg }, true))) return null; } else { - // create a smaller array for the last block, which might not be a full 1000 bytes - byte[] lastBlock = Arrays.copyOf(buffer, bytesRead); - targetUDF.call(pc, new Object[] { lastBlock }, true); + if (!caster.toBooleanValue(targetCFC.call(pc, INVOKE, new Object[] { arg }))) { + targetCFC.call(pc, AFTER, new Object[] {}); + return null; + } } } + if (!isUDF) { + targetCFC.call(pc, AFTER, new Object[] {}); + } return null; } finally { @@ -195,4 +217,62 @@ else if (targetRes != null) { throw eng.getCastUtil().toPageException(e); } } + + private UDF toFunction(Object obj, UDF defaultValue) { + if (obj instanceof UDF) return (UDF) obj; + return defaultValue; + } + + private void validateInvoke(PageContext pc, UDF targetUDF, RefInteger mode, RefInteger blockSize, boolean member) throws PageException { + + String nameDesc = member ? "function [invoke] of the listener component" : "closure/function"; + + CFMLEngine eng = CFMLEngineFactory.getInstance(); + // function return type + if (!(targetUDF.getReturnType() == TYPE_ANY || targetUDF.getReturnType() == TYPE_BOOLEAN)) + throw eng.getExceptionUtil().createFunctionException(pc, "S3Download", 3, "target", "the " + nameDesc + " must have the return type boolean.", ""); + + // function invoke arguments + FunctionArgument[] udfArgs = targetUDF.getFunctionArguments(); + if (udfArgs.length < 1 || udfArgs.length > 1) throw eng.getExceptionUtil().createFunctionException(pc, "S3Download", 3, "target", + "you need to define an argument for the " + nameDesc + " passed in following this pattern (string line|binary{number}|string{number})", ""); + + FunctionArgument arg = udfArgs[0]; + if (!(arg.getType() == TYPE_ANY || arg.getType() == TYPE_STRING)) throw eng.getExceptionUtil().createFunctionException(pc, "S3Download", 3, "target", + "the first argument of the " + nameDesc + " need to be defined as a string or no defintion at all", ""); + + String name = (arg.getName().getString() + "").toLowerCase().trim(); + if ("line".equals(name)) { + mode.setValue(MODE_LINE); + } + else if (name.startsWith("binary")) { + mode.setValue(MODE_BINARY); + blockSize.setValue(eng.getCastUtil().toIntValue(name.substring(6))); + if (blockSize.toInt() <= 0) throw eng.getExceptionUtil().createFunctionException(pc, "S3Download", 3, "target", + "invalid block size defintion with the argument [binary{Number}], blocksize need to be a positive number, so the argument name should for example look like this [binary1000] to get block size 1000", + ""); + } + else if (name.startsWith("string")) { + mode.setValue(MODE_STRING); + blockSize.setValue(eng.getCastUtil().toIntValue(name.substring(6))); + if (blockSize.toInt() <= 0) throw eng.getExceptionUtil().createFunctionException(pc, "S3Download", 3, "target", + "invalid block size defintion with the argument [string{Number}], blocksize need to be a positive number, so the argument name should for example look like this [string1000] to get block size 1000", + ""); + } + else { + throw eng.getExceptionUtil().createFunctionException(pc, "S3Download", 3, "target", "the first argument of the " + nameDesc + + " need to be define an argument where the name does follow one of this patterns [line, binary(Number), string(Number) ]", ""); + } + } + + public static Component toComponentSpecificAccess(int access, Component component) throws PageException { + try { + Class clazz = CFMLEngineFactory.getInstance().getClassUtil().loadClass("lucee.runtime.ComponentSpecificAccess"); + Method m = clazz.getMethod("toComponentSpecificAccess", new Class[] { int.class, Component.class }); + return (Component) m.invoke(null, new Object[] { access, component }); + } + catch (Exception e) { + throw CFMLEngineFactory.getInstance().getCastUtil().toPageException(e); + } + } } \ No newline at end of file diff --git a/tests/functions/BinaryListener.cfc b/tests/functions/BinaryListener.cfc new file mode 100644 index 0000000..9bb3ffa --- /dev/null +++ b/tests/functions/BinaryListener.cfc @@ -0,0 +1,20 @@ +component { + + variables.listener=""; + + + function before() { + variables.listener="before;"; + } + function invoke(binary4) { + variables.listener&=len(binary4)&";"; + return true; + } + function after() { + variables.listener&="after;"; + } + + function getData() { + return variables.listener; + } +} \ No newline at end of file diff --git a/tests/functions/LineListener.cfc b/tests/functions/LineListener.cfc new file mode 100644 index 0000000..3ed3178 --- /dev/null +++ b/tests/functions/LineListener.cfc @@ -0,0 +1,20 @@ +component { + + variables.lineListener=""; + + + function before() { + variables.lineListener="before;"; + } + function invoke(line) { + variables.lineListener&=line; + return true; + } + function after() { + variables.lineListener&="after;"; + } + + function getData() { + return variables.lineListener; + } +} \ No newline at end of file diff --git a/tests/functions/S3Download.cfc b/tests/functions/S3Download.cfc index 615c512..7f9f7ea 100644 --- a/tests/functions/S3Download.cfc +++ b/tests/functions/S3Download.cfc @@ -44,6 +44,7 @@ Sorglos"; } }); +//////// UDF /////////// it(title="download to UDF:line", body = function( currentSpec ) { var data=""; s3Download(bucket:bucketName,object:objectName,target:function(line){ @@ -105,6 +106,43 @@ Sor:4;glos:4;"); assertEquals(len(data),6); assertEquals(data, "5;5;2;"); }); + + + +//////// COMPONENT /////////// + it(title="download to component:line", body = function( currentSpec ) { + var listener=new LineListener(); + s3Download(bucket:bucketName,object:objectName,target:listener,accessKeyId:cred.ACCESS_KEY_ID,secretAccessKey:cred.SECRET_KEY); + var data=listener.getData(); + assertTrue(isSimpleValue(data)); + assertEquals(len(data),12); + assertEquals(data, content); + }); + + it(title="download to component:line with charset", body = function( currentSpec ) { + var listener=new LineListener(); + s3Download(bucket:bucketName,object:objectName,charset:"UTF-8",target:listener,accessKeyId:cred.ACCESS_KEY_ID,secretAccessKey:cred.SECRET_KEY); + var data=listener.getData(); + assertTrue(isSimpleValue(data)); + assertEquals(len(data),12); + assertEquals(data, content); + }); + + it(title="download to component:string with charset", body = function( currentSpec ) { + var listener=new StringListener(); + s3Download(bucket:bucketName,object:objectName,charset:"UTF-8",target:listener,accessKeyId:cred.ACCESS_KEY_ID,secretAccessKey:cred.SECRET_KEY); + assertTrue(isSimpleValue(data)); + assertEquals(len(data),12); + assertEquals(data, content); + }); + + it(title="download to component:binary", body = function( currentSpec ) { + var listener=new BinaryListener(); + s3Download(bucket:bucketName,object:objectName,target:listener,accessKeyId:cred.ACCESS_KEY_ID,secretAccessKey:cred.SECRET_KEY); + assertTrue(isSimpleValue(data)); + assertEquals(len(data),12); + assertEquals(data, content); + }); }); } diff --git a/tests/functions/StringListener.cfc b/tests/functions/StringListener.cfc new file mode 100644 index 0000000..64b57bf --- /dev/null +++ b/tests/functions/StringListener.cfc @@ -0,0 +1,20 @@ +component { + + variables.listener=""; + + + function before() { + variables.listener="before;"; + } + function invoke(string4) { + variables.listener&=string4; + return true; + } + function after() { + variables.listener&="after;"; + } + + function getData() { + return variables.listener; + } +} \ No newline at end of file