diff --git a/ant/upload_to_s3.cfm b/ant/upload_to_s3.cfm
index f3f073719c..2502068b2d 100644
--- a/ant/upload_to_s3.cfm
+++ b/ant/upload_to_s3.cfm
@@ -53,22 +53,31 @@
trg.jar = trg.dir & src.jarName;
trg.core = trg.dir & src.coreName;
- if ( fileExists( trg.jar ) && fileExists( trg.core ) ){
- SystemOutput( "Build artifacts have already been uploaded for this version, nothing to do", 1, 1 );
- return;
- }
-
// copy jar
- SystemOutput( "upload #src.jarName# to S3",1,1 );
- if ( fileExists( trg.jar ) )
- fileDelete( trg.jar );
- fileCopy( src.jar, trg.jar );
+ SystemOutput( "upload #src.jar# to S3",1,1 );
+ copyIt=true;
+ if (fileExists( trg.jar ) ) {
+ try {
+ fileDelete( trg.jar );
+ }
+ catch(e){
+ copyIt=false;
+ }
+ }
+ if(copyIt) fileCopy( src.jar, trg.jar );
// copy core
- SystemOutput( "upload #src.coreName# to S3",1,1 );
- if ( fileExists( trg.core ) )
- fileDelete( trg.core );
- fileCopy( src.core, trg.core );
+ SystemOutput( "upload #src.core# to S3",1,1 );
+ copyIt=true;
+ if ( fileExists( trg.core ) ) {
+ try {
+ fileDelete( trg.core );
+ }
+ catch(e){
+ copyIt=false;
+ }
+ }
+ if(copyIt) fileCopy( src.core, trg.core );
}
/*
@@ -100,7 +109,7 @@
src.lightName = "lucee-light-" & src.version & ".jar";
src.light = src.dir & src.lightName;
if ( DO_DEPLOY )
- SystemOutput( "build and upload #src.lightName# to S3",1,1 );
+ SystemOutput( "build and upload #src.light# to S3",1,1 );
else
SystemOutput( "build #src.light#",1,1 );
@@ -108,22 +117,41 @@
createLight( src.jar,src.light,src.version, false );
if ( DO_DEPLOY ){
trg.light = trg.dir & src.lightName;
- fileCopy( src.light, trg.light );
+ copyIt=true;
+ if ( fileExists( trg.light ) ) {
+ try {
+ fileDelete( trg.light );
+ }
+ catch(e){
+ copyIt=false;
+ }
+ }
+ if(copyIt) fileCopy( src.light, trg.light );
}
// Lucee zero build, built from light but also no admin or docs
src.zeroName = "lucee-zero-" & src.version & ".jar";
src.zero = src.dir & src.zeroName;
- /*if ( DO_DEPLOY )
- SystemOutput( "build and upload #src.zeroName# to S3",1,1 );
+ if ( DO_DEPLOY )
+ SystemOutput( "build and upload #src.zero# to S3",1,1 );
else
SystemOutput( "build #src.zero#",1,1 );
- createLight( src.light, src.zero,src.version, true );
- */
+ createLight( src.light, src.zero,src.version, false );
+
if ( DO_DEPLOY ) {
trg.zero = trg.dir & src.zeroName;
- fileCopy( src.zero, trg.zero );
+
+ copyIt=true;
+ if ( fileExists( trg.zero ) ) {
+ try {
+ fileDelete( trg.zero );
+ }
+ catch(e){
+ copyIt=false;
+ }
+ }
+ if(copyIt) fileCopy( src.zero, trg.zero );
}
diff --git a/core/src/main/cfml/context/templates/error/error.cfm b/core/src/main/cfml/context/templates/error/error.cfm
index 4830556e03..2f0acbb1f8 100755
--- a/core/src/main/cfml/context/templates/error/error.cfm
+++ b/core/src/main/cfml/context/templates/error/error.cfm
@@ -60,6 +60,24 @@
#replace( HTMLEditFormat( trim( catch.detail ) ), chr(10), ' ', 'all' )# |
+
+
+
+
+
+ AI (#meta.label?:""#) |
+ #markdowntohtml(answer)# |
+
+
+
+
+
Error Code |
diff --git a/core/src/main/java/META-INF/MANIFEST.MF b/core/src/main/java/META-INF/MANIFEST.MF
index e991f0d9ff..d3a3e5601a 100644
--- a/core/src/main/java/META-INF/MANIFEST.MF
+++ b/core/src/main/java/META-INF/MANIFEST.MF
@@ -361,13 +361,13 @@ Require-Bundle: org.apache.commons.commons-codec;bundle-version=1.15.0,
com.github.f4b6a3.ulid;bundle-version=5.2.3,
org.lucee.janino;bundle-version=3.1.9,
org.lucee.janinocc;bundle-version=3.1.9
-Require-Extension: 7E673D15-D87C-41A6-8B5F1956528C605F;name=MySQL;label=MySQL;version=8.4.0,
- 99A4EF8D-F2FD-40C8-8FB8C2E67A4EEEB6;name=MSSQL;label=MS SQL Server;version=12.4.2.jre8,
+Require-Extension: 7E673D15-D87C-41A6-8B5F1956528C605F;name=MySQL;label=MySQL;version=9.0.0,
+ 99A4EF8D-F2FD-40C8-8FB8C2E67A4EEEB6;name=MSSQL;label=MS SQL Server;version=12.6.3.jre11,
671B01B8-B3B3-42B9-AC055A356BED5281;name=PostgreSQL;label=PostgreSQL;version=42.7.3,
2BCD080F-4E1E-48F5-BEFE794232A21AF6;name=JDTsSQL;label=jTDS (MSSQL);version=1.3.1,
CED6227E-0F49-6367-A68D21AACA6B07E8;name=Admin;label=Lucee Administrator;version=1.0.0.5,
D46D49C3-EB85-8D97-30BEC2F38561E985;name=Doc;label=Lucee Documentation;version=1.0.0.4,
- 17AB52DE-B300-A94B-E058BD978511E39E;name=S3;label=S3;version=2.0.1.25,
+ 17AB52DE-B300-A94B-E058BD978511E39E;name=S3;label=S3;version=2.0.2.19-SNAPSHOT,
87FE44E5-179C-43A3-A87B3D38BEF4652E;name=EHCache;label=EHCache;version=2.10.0.36,
66E312DD-D083-27C0-64189D16753FD6F0;name=PDF;label=PDF;version=1.2.0.10,
B737ABC4-D43F-4D91-8E8E973E37C40D1B;name=Image;label=Image;version=2.0.0.26;since=5.3.0.35-ALPHA,
diff --git a/core/src/main/java/lucee/aprint.java b/core/src/main/java/lucee/aprint.java
index 4a013836fc..dcc5fd9054 100755
--- a/core/src/main/java/lucee/aprint.java
+++ b/core/src/main/java/lucee/aprint.java
@@ -501,8 +501,12 @@ private static void _eo(PrintStream ps, Map map) {
ps.print(map.getClass().getName() + " {");
while (it.hasNext()) {
Object key = it.next();
-
- _eo(ps, key);
+ if (key instanceof CharSequence) {
+ ps.print("\"");
+ ps.print(key.toString());
+ ps.print("\" ");
+ }
+ else _eo(ps, key);
ps.print(":");
_eo(ps, map.get(key));
}
@@ -510,13 +514,20 @@ private static void _eo(PrintStream ps, Map map) {
}
else {
ps.println(map.getClass().getName() + " {");
+ char del = ' ';
while (it.hasNext()) {
Object key = it.next();
ps.print(" ");
- _eo(ps, key);
+ ps.print(del);
+ if (key instanceof CharSequence) {
+ ps.print("\"");
+ ps.print(key.toString());
+ ps.print("\" ");
+ }
+ else _eo(ps, key);
ps.print(":");
_eo(ps, map.get(key));
- ps.println(";");
+ del = ',';
}
ps.println("}");
}
diff --git a/core/src/main/java/lucee/commons/io/ModeUtil.java b/core/src/main/java/lucee/commons/io/ModeUtil.java
index d99745944b..80ce4a1724 100644
--- a/core/src/main/java/lucee/commons/io/ModeUtil.java
+++ b/core/src/main/java/lucee/commons/io/ModeUtil.java
@@ -20,6 +20,9 @@
import java.io.IOException;
+import lucee.commons.io.log.Log;
+import lucee.commons.io.log.LogUtil;
+
public final class ModeUtil {
public static final int PERM_READ = 04;
@@ -66,6 +69,23 @@ private static int _toOctalMode(String strMode) {
return mode;
}
+ /**
+ * Extracts the permission bits from the mode value. Logs a message if the file type bits are
+ * removed and logging is enabled.
+ *
+ * @param mode The mode value that includes file type and permission bits.
+ * @param log If true, log a message if file type bits are removed.
+ * @return The permission bits (e.g., 0700).
+ */
+ public static int extractPermissions(int mode, boolean log) {
+ int permissionBits = mode & 07777;
+
+ if (log && (mode != permissionBits)) {
+ LogUtil.log(Log.LEVEL_WARN, "mode", "File type bits removed from mode: original=" + toStringMode(mode) + ", extracted=" + toStringMode(permissionBits));
+ }
+ return permissionBits;
+ }
+
/**
* translate an octal mode value (73) to a string representation ("111")
*
diff --git a/core/src/main/java/lucee/commons/io/SystemUtil.java b/core/src/main/java/lucee/commons/io/SystemUtil.java
index de7f649566..ada33705bc 100644
--- a/core/src/main/java/lucee/commons/io/SystemUtil.java
+++ b/core/src/main/java/lucee/commons/io/SystemUtil.java
@@ -71,6 +71,7 @@
import lucee.commons.io.res.Resource;
import lucee.commons.io.res.ResourceProvider;
import lucee.commons.io.res.ResourcesImpl;
+import lucee.commons.io.res.util.CombinedClassLoader;
import lucee.commons.io.res.util.ResourceUtil;
import lucee.commons.lang.ArchiveClassLoader;
import lucee.commons.lang.CharSet;
@@ -163,6 +164,12 @@ public final class SystemUtil {
public static final int JAVA_VERSION_27 = 27; // FUTURE lucee.runtime.util.SystemUtil.JAVA_VERSION_14;
public static final int JAVA_VERSION_28 = 28; // FUTURE lucee.runtime.util.SystemUtil.JAVA_VERSION_14;
public static final int JAVA_VERSION_29 = 29; // FUTURE lucee.runtime.util.SystemUtil.JAVA_VERSION_14;
+ public static final int JAVA_VERSION_30 = 30;
+ public static final int JAVA_VERSION_31 = 31;
+ public static final int JAVA_VERSION_32 = 32;
+ public static final int JAVA_VERSION_33 = 33;
+ public static final int JAVA_VERSION_34 = 34;
+ public static final int JAVA_VERSION_35 = 35;
public static final int OUT = lucee.runtime.util.SystemUtil.OUT;
public static final int ERR = lucee.runtime.util.SystemUtil.ERR;
@@ -223,39 +230,37 @@ public final class SystemUtil {
MemoryPoolMXBean tmp = getPermGenSpaceBean();
if (tmp != permGenSpaceBean) permGenSpaceBean = null;
- if (JAVA_VERSION_STRING.startsWith("1.14.") || JAVA_VERSION_STRING.startsWith("14")) JAVA_VERSION = JAVA_VERSION_14;
+ if (JAVA_VERSION_STRING.startsWith("35.")) JAVA_VERSION = JAVA_VERSION_35;
+ else if (JAVA_VERSION_STRING.startsWith("34.")) JAVA_VERSION = JAVA_VERSION_34;
+ else if (JAVA_VERSION_STRING.startsWith("33.")) JAVA_VERSION = JAVA_VERSION_33;
+ else if (JAVA_VERSION_STRING.startsWith("32.")) JAVA_VERSION = JAVA_VERSION_32;
+ else if (JAVA_VERSION_STRING.startsWith("31.")) JAVA_VERSION = JAVA_VERSION_31;
+ else if (JAVA_VERSION_STRING.startsWith("30.")) JAVA_VERSION = JAVA_VERSION_30;
+ else if (JAVA_VERSION_STRING.startsWith("29.")) JAVA_VERSION = JAVA_VERSION_29;
+ else if (JAVA_VERSION_STRING.startsWith("28.")) JAVA_VERSION = JAVA_VERSION_28;
+ else if (JAVA_VERSION_STRING.startsWith("27.")) JAVA_VERSION = JAVA_VERSION_27;
+ else if (JAVA_VERSION_STRING.startsWith("26.")) JAVA_VERSION = JAVA_VERSION_26;
+ else if (JAVA_VERSION_STRING.startsWith("25.")) JAVA_VERSION = JAVA_VERSION_25;
+ else if (JAVA_VERSION_STRING.startsWith("24.")) JAVA_VERSION = JAVA_VERSION_24;
+ else if (JAVA_VERSION_STRING.startsWith("23.")) JAVA_VERSION = JAVA_VERSION_23;
+ else if (JAVA_VERSION_STRING.startsWith("22.")) JAVA_VERSION = JAVA_VERSION_22;
+ else if (JAVA_VERSION_STRING.startsWith("21.")) JAVA_VERSION = JAVA_VERSION_21;
+ else if (JAVA_VERSION_STRING.startsWith("20.")) JAVA_VERSION = JAVA_VERSION_20;
+ else if (JAVA_VERSION_STRING.startsWith("19.")) JAVA_VERSION = JAVA_VERSION_19;
+ else if (JAVA_VERSION_STRING.startsWith("18.")) JAVA_VERSION = JAVA_VERSION_18;
+ else if (JAVA_VERSION_STRING.startsWith("17.")) JAVA_VERSION = JAVA_VERSION_17;
+ else if (JAVA_VERSION_STRING.startsWith("16.")) JAVA_VERSION = JAVA_VERSION_16;
+ else if (JAVA_VERSION_STRING.startsWith("15.")) JAVA_VERSION = JAVA_VERSION_15;
+
+ else if (JAVA_VERSION_STRING.startsWith("1.14.") || JAVA_VERSION_STRING.startsWith("14")) JAVA_VERSION = JAVA_VERSION_14;
else if (JAVA_VERSION_STRING.startsWith("1.13.") || JAVA_VERSION_STRING.startsWith("13")) JAVA_VERSION = JAVA_VERSION_13;
else if (JAVA_VERSION_STRING.startsWith("1.12.") || JAVA_VERSION_STRING.startsWith("12")) JAVA_VERSION = JAVA_VERSION_12;
else if (JAVA_VERSION_STRING.startsWith("1.11.") || JAVA_VERSION_STRING.startsWith("11")) JAVA_VERSION = JAVA_VERSION_11;
else if (JAVA_VERSION_STRING.startsWith("1.10.") || JAVA_VERSION_STRING.startsWith("10")) JAVA_VERSION = JAVA_VERSION_10;
else if (JAVA_VERSION_STRING.startsWith("1.9.") || JAVA_VERSION_STRING.startsWith("9.")) JAVA_VERSION = JAVA_VERSION_9;
- else if (JAVA_VERSION_STRING.startsWith("1.8.")) JAVA_VERSION = JAVA_VERSION_8;
- else if (JAVA_VERSION_STRING.startsWith("1.7.")) JAVA_VERSION = JAVA_VERSION_7;
- else if (JAVA_VERSION_STRING.startsWith("1.6.")) JAVA_VERSION = JAVA_VERSION_6;
- else if (JAVA_VERSION_STRING.startsWith("1.6.")) JAVA_VERSION = JAVA_VERSION_6;
-
- else if (JAVA_VERSION_STRING.startsWith("8.")) JAVA_VERSION = JAVA_VERSION_8;
- else if (JAVA_VERSION_STRING.startsWith("9.")) JAVA_VERSION = JAVA_VERSION_9;
- else if (JAVA_VERSION_STRING.startsWith("10.")) JAVA_VERSION = JAVA_VERSION_10;
- else if (JAVA_VERSION_STRING.startsWith("11.")) JAVA_VERSION = JAVA_VERSION_11;
- else if (JAVA_VERSION_STRING.startsWith("12.")) JAVA_VERSION = JAVA_VERSION_12;
- else if (JAVA_VERSION_STRING.startsWith("13.")) JAVA_VERSION = JAVA_VERSION_13;
- else if (JAVA_VERSION_STRING.startsWith("14.")) JAVA_VERSION = JAVA_VERSION_14;
- else if (JAVA_VERSION_STRING.startsWith("15.")) JAVA_VERSION = JAVA_VERSION_15;
- else if (JAVA_VERSION_STRING.startsWith("16.")) JAVA_VERSION = JAVA_VERSION_16;
- else if (JAVA_VERSION_STRING.startsWith("17.")) JAVA_VERSION = JAVA_VERSION_17;
- else if (JAVA_VERSION_STRING.startsWith("18.")) JAVA_VERSION = JAVA_VERSION_18;
- else if (JAVA_VERSION_STRING.startsWith("19.")) JAVA_VERSION = JAVA_VERSION_19;
- else if (JAVA_VERSION_STRING.startsWith("20.")) JAVA_VERSION = JAVA_VERSION_20;
- else if (JAVA_VERSION_STRING.startsWith("21.")) JAVA_VERSION = JAVA_VERSION_21;
- else if (JAVA_VERSION_STRING.startsWith("22.")) JAVA_VERSION = JAVA_VERSION_22;
- else if (JAVA_VERSION_STRING.startsWith("23.")) JAVA_VERSION = JAVA_VERSION_23;
- else if (JAVA_VERSION_STRING.startsWith("24.")) JAVA_VERSION = JAVA_VERSION_24;
- else if (JAVA_VERSION_STRING.startsWith("25.")) JAVA_VERSION = JAVA_VERSION_25;
- else if (JAVA_VERSION_STRING.startsWith("26.")) JAVA_VERSION = JAVA_VERSION_26;
- else if (JAVA_VERSION_STRING.startsWith("27.")) JAVA_VERSION = JAVA_VERSION_27;
- else if (JAVA_VERSION_STRING.startsWith("28.")) JAVA_VERSION = JAVA_VERSION_28;
- else if (JAVA_VERSION_STRING.startsWith("29.")) JAVA_VERSION = JAVA_VERSION_29;
+ else if (JAVA_VERSION_STRING.startsWith("1.8.") || JAVA_VERSION_STRING.startsWith("8.")) JAVA_VERSION = JAVA_VERSION_8;
+ else if (JAVA_VERSION_STRING.startsWith("1.7.") || JAVA_VERSION_STRING.startsWith("7.")) JAVA_VERSION = JAVA_VERSION_7;
+ else if (JAVA_VERSION_STRING.startsWith("1.6.") || JAVA_VERSION_STRING.startsWith("6.")) JAVA_VERSION = JAVA_VERSION_6;
else JAVA_VERSION = 0;
}
@@ -263,17 +268,41 @@ public final class SystemUtil {
private static final Map tokens = Collections.synchronizedMap(new AccessOrderLimitedSizeMap(10000, 100));
private static ClassLoader loaderCL;
private static ClassLoader coreCL;
+ private static ClassLoader combCL;
public static ClassLoader getLoaderClassLoader() {
- if (loaderCL == null) loaderCL = new TP().getClass().getClassLoader();
+ if (loaderCL == null) {
+ synchronized (tokens) {
+ if (loaderCL == null) {
+ loaderCL = new TP().getClass().getClassLoader();
+ }
+ }
+ }
return loaderCL;
}
public static ClassLoader getCoreClassLoader() {
- if (coreCL == null) coreCL = new ClassLoaderHelper().getClass().getClassLoader();
+ if (coreCL == null) {
+ synchronized (tokens) {
+ if (coreCL == null) {
+ coreCL = new ClassLoaderHelper().getClass().getClassLoader();
+ }
+ }
+ }
return coreCL;
}
+ public static ClassLoader getCombinedClassLoader() {
+ if (combCL == null) {
+ synchronized (tokens) {
+ if (combCL == null) {
+ combCL = new CombinedClassLoader(SystemUtil.getLoaderClassLoader(), SystemUtil.getCoreClassLoader());
+ }
+ }
+ }
+ return combCL;
+ }
+
public static MemoryPoolMXBean getPermGenSpaceBean() {
java.util.List manager = ManagementFactory.getMemoryPoolMXBeans();
MemoryPoolMXBean bean;
@@ -1592,7 +1621,9 @@ public static List getClassLoaderContext(boolean unique, StringBuil
// check ClassLoader
if (cl == null) continue;
- if (cl instanceof PhysicalClassLoader) continue;
+ if (cl instanceof PhysicalClassLoader) {
+ if (!((PhysicalClassLoader) cl).isRPC()) continue;
+ }
if (cl instanceof ArchiveClassLoader) continue;
if (cl instanceof MemoryClassLoader) continue;
@@ -1610,6 +1641,7 @@ public static List getClassLoaderContext(boolean unique, StringBuil
}
last = ref.context[i];
}
+
return context;
}
diff --git a/core/src/main/java/lucee/commons/io/compress/CompressUtil.java b/core/src/main/java/lucee/commons/io/compress/CompressUtil.java
index 8233654eeb..b51a10f294 100644
--- a/core/src/main/java/lucee/commons/io/compress/CompressUtil.java
+++ b/core/src/main/java/lucee/commons/io/compress/CompressUtil.java
@@ -40,6 +40,7 @@
import org.apache.commons.compress.compressors.bzip2.BZip2CompressorOutputStream;
import lucee.commons.io.IOUtil;
+import lucee.commons.io.ModeUtil;
import lucee.commons.io.SystemUtil;
import lucee.commons.io.res.Resource;
import lucee.commons.io.res.ResourceProvider;
@@ -207,7 +208,6 @@ private static void extractTar(Resource tarFile, Resource targetDir) throws IOEx
try {
tis = new TarArchiveInputStream(IOUtil.toBufferedInputStream(tarFile.getInputStream()));
TarArchiveEntry entry;
- int mode;
while ((entry = tis.getNextTarEntry()) != null) {
// print.ln(entry);
Resource target = targetDir.getRealResource(entry.getName());
@@ -220,9 +220,6 @@ private static void extractTar(Resource tarFile, Resource targetDir) throws IOEx
IOUtil.copy(tis, target, false);
}
target.setLastModified(entry.getModTime().getTime());
- mode = entry.getMode();
- if (mode > 0) target.setMode(mode);
- // tis.closeEntry() ;
}
}
finally {
@@ -555,9 +552,8 @@ private static void compressTar(String parent, Resource source, TarArchiveOutput
entry.setName(parent);
// mode
- // 100777 TODO ist das so ok?
- if (mode > 0) entry.setMode(mode);
- else if ((mode = source.getMode()) > 0) entry.setMode(mode);
+ if (mode > 0) entry.setMode(ModeUtil.extractPermissions(mode, false));
+ else if ((mode = source.getMode()) > 0) entry.setMode(ModeUtil.extractPermissions(mode, false));
entry.setSize(source.length());
entry.setModTime(source.lastModified());
diff --git a/core/src/main/java/lucee/commons/io/log/LogUtil.java b/core/src/main/java/lucee/commons/io/log/LogUtil.java
index 2494481638..3af9f26a6f 100644
--- a/core/src/main/java/lucee/commons/io/log/LogUtil.java
+++ b/core/src/main/java/lucee/commons/io/log/LogUtil.java
@@ -27,10 +27,14 @@
import lucee.commons.io.res.Resource;
import lucee.commons.io.res.util.ResourceUtil;
import lucee.commons.lang.ExceptionUtil;
+import lucee.commons.lang.StringUtil;
import lucee.commons.lang.SystemOut;
import lucee.loader.engine.CFMLEngineFactory;
import lucee.runtime.PageContext;
+import lucee.runtime.PageContextImpl;
+import lucee.runtime.PageSource;
import lucee.runtime.config.Config;
+import lucee.runtime.config.ConfigWeb;
import lucee.runtime.config.ConfigWebUtil;
import lucee.runtime.engine.ThreadLocalPageContext;
@@ -148,10 +152,21 @@ public static void log(int level, String logName, String type, String msg) {
public static void log(Config config, int level, String logName, String type, String msg) {
Log log = ThreadLocalPageContext.getLog(config, logName);
- if (log != null) log.log(level, type, msg);
- else {
- logGlobal(ThreadLocalPageContext.getConfig(config), level, logName + ":" + type, msg);
+ if (log != null) {
+ log.log(level, type, msg);
+ return;
+ }
+ // fallback to application
+ if (!"application".equalsIgnoreCase(logName)) {
+ log = ThreadLocalPageContext.getLog(config, "application");
+ if (log != null) {
+ log.log(level, type, msg);
+ return;
+ }
}
+
+ logGlobal(ThreadLocalPageContext.getConfig(config), level, logName + ":" + type, msg);
+
}
public static void log(PageContext pc, int level, String logName, String type, String msg) {
@@ -207,4 +222,38 @@ public static boolean doesError(Log log) {
public static boolean doesFatal(Log log) {
return (log != null && log.getLogLevel() >= Log.LEVEL_FATAL);
}
+
+ public static String caller(PageContext pc, String defaultValue) {
+ Exception t = new Exception("Stack trace");
+ StackTraceElement[] traces = t.getStackTrace();
+
+ String template;
+ for (StackTraceElement trace: traces) {
+ template = trace.getFileName();
+ if (trace.getLineNumber() <= 0 || template == null || ResourceUtil.getExtension(template, "").equals("java")) continue;
+
+ return abs(pc, template) + ":" + trace.getLineNumber();
+ }
+ return defaultValue;
+ }
+
+ private static String abs(PageContext pc, String template) {
+ try {
+ ConfigWeb config = pc.getConfig();
+ Resource res = config.getResource(template);
+ if (res.exists()) return template;
+ String tmp;
+ PageSource ps = pc == null ? null : ((PageContextImpl) pc).getPageSource(template);
+ res = ps == null ? null : ps.getPhyscalFile();
+ if (res == null || !res.exists()) {
+ tmp = ps.getDisplayPath();
+ res = StringUtil.isEmpty(tmp) ? null : config.getResource(tmp);
+ if (res != null && res.exists()) return res.getAbsolutePath();
+ }
+ else return res.getAbsolutePath();
+ }
+ catch (Exception e) {
+ }
+ return template;
+ }
}
\ No newline at end of file
diff --git a/core/src/main/java/lucee/commons/io/res/type/file/FileResource.java b/core/src/main/java/lucee/commons/io/res/type/file/FileResource.java
index 425d70893d..2e4ec597e3 100644
--- a/core/src/main/java/lucee/commons/io/res/type/file/FileResource.java
+++ b/core/src/main/java/lucee/commons/io/res/type/file/FileResource.java
@@ -439,8 +439,9 @@ public static int getMode(Path path) {
@Override
public void setMode(int mode) throws IOException {
- // TODO unter windows mit setReadable usw.
+ // TODO for windows do it with help of NIO functions
if (!SystemUtil.isUnix()) return;
+ mode = ModeUtil.extractPermissions(mode, true); // we only need the permission part
provider.lock(this);
try {
// print.ln(ModeUtil.toStringMode(mode));
diff --git a/core/src/main/java/lucee/commons/io/res/type/ftp/FTPResource.java b/core/src/main/java/lucee/commons/io/res/type/ftp/FTPResource.java
index b15c4dcd90..c06b96cc97 100644
--- a/core/src/main/java/lucee/commons/io/res/type/ftp/FTPResource.java
+++ b/core/src/main/java/lucee/commons/io/res/type/ftp/FTPResource.java
@@ -114,7 +114,7 @@ private Boolean hasPermission(int permission) {
public void remove(boolean alsoRemoveChildren) throws IOException {
if (isRoot()) throw new FTPResoucreException("Can't delete root of ftp server");
- if (alsoRemoveChildren) ResourceUtil.removeChildren(this);
+ if (alsoRemoveChildren) ResourceUtil.removeChildren(this, false);
FTPResourceClient client = null;
try {
provider.lock(this);
diff --git a/core/src/main/java/lucee/commons/io/res/type/smb/SMBResource.java b/core/src/main/java/lucee/commons/io/res/type/smb/SMBResource.java
index 784a122daf..af168ae60f 100644
--- a/core/src/main/java/lucee/commons/io/res/type/smb/SMBResource.java
+++ b/core/src/main/java/lucee/commons/io/res/type/smb/SMBResource.java
@@ -234,7 +234,7 @@ private SmbFile _getTempFile(SmbFile directory, NtlmPasswordAuthentication auth)
@Override
public void remove(boolean alsoRemoveChildren) throws IOException {
- if (alsoRemoveChildren) ResourceUtil.removeChildren(this);
+ if (alsoRemoveChildren) ResourceUtil.removeChildren(this, false);
_delete();
}
diff --git a/core/src/main/java/lucee/commons/io/res/util/CombinedClassLoader.java b/core/src/main/java/lucee/commons/io/res/util/CombinedClassLoader.java
new file mode 100644
index 0000000000..d50c7bc17c
--- /dev/null
+++ b/core/src/main/java/lucee/commons/io/res/util/CombinedClassLoader.java
@@ -0,0 +1,69 @@
+package lucee.commons.io.res.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+
+public class CombinedClassLoader extends ClassLoader {
+
+ private final ClassLoader loader;
+ private final ClassLoader core;
+
+ public CombinedClassLoader(ClassLoader loader, ClassLoader core) {
+ super(null); // null means it doesn't have a parent itself
+ this.loader = loader;
+ this.core = core;
+ }
+
+ @Override
+ protected Class> findClass(String name) throws ClassNotFoundException {
+ try {
+ // Try loading with the primary (OSGi) class loader first
+ return core.loadClass(name);
+ }
+ catch (ClassNotFoundException e) {
+ // If not found, delegate to the secondary (main) class loader
+ return loader.loadClass(name);
+ }
+ }
+
+ @Override
+ public URL getResource(String name) {
+ URL resource = core.getResource(name);
+ if (resource == null) {
+ resource = loader.getResource(name);
+ }
+ return resource;
+ }
+
+ @Override
+ public InputStream getResourceAsStream(String name) {
+ InputStream stream = core.getResourceAsStream(name);
+ if (stream == null) {
+ stream = loader.getResourceAsStream(name);
+ }
+ return stream;
+ }
+
+ @Override
+ public Enumeration getResources(String name) throws IOException {
+ // Combine resources from both class loaders
+ Enumeration coreResources = core.getResources(name);
+ Enumeration loaderResources = loader.getResources(name);
+ URL url;
+ Map combinedResources = new HashMap<>();
+ while (loaderResources.hasMoreElements()) {
+ url = loaderResources.nextElement();
+ combinedResources.put(url.toExternalForm(), url);
+ }
+ while (coreResources.hasMoreElements()) {
+ url = coreResources.nextElement();
+ combinedResources.put(url.toExternalForm(), url);
+ }
+ return Collections.enumeration(combinedResources.values());
+ }
+}
diff --git a/core/src/main/java/lucee/commons/io/res/util/MavenClassLoader.java b/core/src/main/java/lucee/commons/io/res/util/MavenClassLoader.java
new file mode 100644
index 0000000000..9775c55cb6
--- /dev/null
+++ b/core/src/main/java/lucee/commons/io/res/util/MavenClassLoader.java
@@ -0,0 +1,25 @@
+package lucee.commons.io.res.util;
+
+public class MavenClassLoader {// extends ResourceClassLoader {
+ /*
+ * private static Map instances = new ConcurrentHashMap<>(); private POM
+ * pom;
+ *
+ * public MavenClassLoader(POM pom, ClassLoader parent) throws IOException { super(pom.getJars(),
+ * parent); this.pom = pom; }
+ *
+ * public static MavenClassLoader getInstance(POM pom, ClassLoader parent) throws IOException {
+ * MavenClassLoader mcl = instances.get(pom.hash()); if (mcl == null) { mcl = new
+ * MavenClassLoader(pom, parent); instances.put(pom.hash(), mcl); } return mcl; }
+ *
+ * public static MavenClassLoader getInstance(POM[] poms, ClassLoader parent) throws IOException {
+ * if (poms == null || poms.length == 0) throw new
+ * IOException("you need to define at least one POM.");
+ *
+ * Arrays.sort(poms, (pom1, pom2) -> pom1.id().compareTo(pom2.id()));
+ *
+ * for (POM pom: poms) { parent = getInstance(pom, parent); } return (MavenClassLoader) parent; }
+ *
+ * public POM getPOM() { return pom; }
+ */
+}
diff --git a/core/src/main/java/lucee/commons/io/res/util/ModeObjectWrap.java b/core/src/main/java/lucee/commons/io/res/util/ModeObjectWrap.java
index de50ff5c9c..9b39f2e5f7 100644
--- a/core/src/main/java/lucee/commons/io/res/util/ModeObjectWrap.java
+++ b/core/src/main/java/lucee/commons/io/res/util/ModeObjectWrap.java
@@ -32,7 +32,7 @@
import lucee.runtime.type.ObjectWrap;
import lucee.runtime.type.dt.DateTime;
-public final class ModeObjectWrap implements ObjectWrap, Castable {
+public final class ModeObjectWrap implements ObjectWrap, Castable, CharSequence {
private static final long serialVersionUID = -1630745501422006978L;
@@ -135,4 +135,19 @@ public int compareTo(DateTime dt) throws PageException {
return OpUtil.compare(ThreadLocalPageContext.get(), toString(), dt.castToString());
}
+ @Override
+ public int length() {
+ return toString().length();
+ }
+
+ @Override
+ public char charAt(int index) {
+ return toString().charAt(index);
+ }
+
+ @Override
+ public CharSequence subSequence(int start, int end) {
+ return toString().subSequence(end, end);
+ }
+
}
\ No newline at end of file
diff --git a/core/src/main/java/lucee/commons/io/res/util/ResourceClassLoader.java b/core/src/main/java/lucee/commons/io/res/util/ResourceClassLoader.java
deleted file mode 100755
index a9957c46f3..0000000000
--- a/core/src/main/java/lucee/commons/io/res/util/ResourceClassLoader.java
+++ /dev/null
@@ -1,146 +0,0 @@
-/**
- *
- * Copyright (c) 2014, the Railo Company Ltd. All rights reserved.
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library. If not, see .
- *
- **/
-package lucee.commons.io.res.util;
-
-import java.io.Closeable;
-import java.io.IOException;
-import java.lang.ref.SoftReference;
-import java.net.URL;
-import java.net.URLClassLoader;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-
-import lucee.commons.digest.MD5;
-import lucee.commons.io.res.Resource;
-import lucee.commons.io.res.type.file.FileResource;
-import lucee.runtime.exp.PageException;
-import lucee.runtime.type.util.ArrayUtil;
-
-/**
- * Classloader that load classes from resources
- */
-public final class ResourceClassLoader extends URLClassLoader implements Closeable {
-
- private List resources = new ArrayList();
- private Map> customCLs;
-
- static {
- boolean res = registerAsParallelCapable();
- }
-
- /**
- * Constructor of the class
- *
- * @param reses
- * @param parent
- * @throws PageException
- */
- public ResourceClassLoader(Resource[] resources, ClassLoader parent) throws IOException {
- super(doURLs(resources), parent);
- for (int i = 0; i < resources.length; i++) {
- if (resources[i] != null) this.resources.add(resources[i]);
- }
- }
-
- public ResourceClassLoader(ClassLoader parent) {
- super(new URL[0], parent);
- }
-
- /**
- * @return the resources
- */
- public Resource[] getResources() {
- return resources.toArray(new Resource[resources.size()]);
- }
-
- public boolean isEmpty() {
- return resources.isEmpty();
- }
-
- /**
- * translate resources to url Objects
- *
- * @param reses
- * @return
- * @throws PageException
- */
- public static URL[] doURLs(Resource[] reses) throws IOException {
- List list = new ArrayList();
- for (int i = 0; i < reses.length; i++) {
- if (reses[i].isDirectory() || "jar".equalsIgnoreCase(ResourceUtil.getExtension(reses[i], null))) list.add(doURL(reses[i]));
- }
- return list.toArray(new URL[list.size()]);
-
- }
-
- private static URL doURL(Resource res) throws IOException {
- if (!(res instanceof FileResource)) throw new IOException("resource [" + res.getPath() + "] must be a local file");
- return ((FileResource) res).toURL();
- }
-
- @Override
- public void close() {
- }
-
- public ResourceClassLoader getCustomResourceClassLoader(Resource[] resources) throws IOException {
-
- if (ArrayUtil.isEmpty(resources)) return this;
-
- String key = hash(resources);
- SoftReference tmp = customCLs == null ? null : customCLs.get(key);
- ResourceClassLoader rcl = tmp == null ? null : tmp.get();
-
- if (rcl != null) return rcl;
-
- resources = ResourceUtil.merge(this.getResources(), resources);
- rcl = new ResourceClassLoader(resources, getParent());
-
- if (customCLs == null) customCLs = new ConcurrentHashMap>();
-
- customCLs.put(key, new SoftReference(rcl));
- return rcl;
- }
-
- public ResourceClassLoader getCustomResourceClassLoader2(Resource[] resources) throws IOException {
- if (ArrayUtil.isEmpty(resources)) return this;
- String key = hash(resources);
- SoftReference tmp = customCLs == null ? null : customCLs.get(key);
- ResourceClassLoader rcl = tmp == null ? null : tmp.get();
- if (rcl != null) return rcl;
-
- rcl = new ResourceClassLoader(resources, this);
- if (customCLs == null) customCLs = new ConcurrentHashMap>();
- customCLs.put(key, new SoftReference(rcl));
- return rcl;
- }
-
- private String hash(Resource[] resources) {
- Arrays.sort(resources);
- StringBuilder sb = new StringBuilder();
- for (int i = 0; i < resources.length; i++) {
- sb.append(ResourceUtil.getCanonicalPathEL(resources[i]));
- sb.append(';');
- }
- return MD5.getDigestAsString(sb.toString(), null);
- }
-
-}
\ No newline at end of file
diff --git a/core/src/main/java/lucee/commons/io/res/util/ResourceUtil.java b/core/src/main/java/lucee/commons/io/res/util/ResourceUtil.java
index 7f159e6033..28bb8cf850 100755
--- a/core/src/main/java/lucee/commons/io/res/util/ResourceUtil.java
+++ b/core/src/main/java/lucee/commons/io/res/util/ResourceUtil.java
@@ -27,6 +27,7 @@
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutorService;
import lucee.commons.digest.Hash;
import lucee.commons.io.IOUtil;
@@ -63,6 +64,7 @@
import lucee.runtime.functions.system.ExpandPath;
import lucee.runtime.op.Caster;
import lucee.runtime.reflection.Reflector;
+import lucee.runtime.thread.ThreadUtil;
import lucee.runtime.type.util.ArrayUtil;
import lucee.runtime.type.util.ListUtil;
@@ -1039,11 +1041,28 @@ private static boolean parentExists(Resource res) {
return res != null && res.exists();
}
- public static void removeChildren(Resource res) throws IOException {
- removeChildren(res, (ResourceFilter) null);
+ public static void removeChildren(Resource res, boolean async) throws IOException {
+ removeChildren(res, (ResourceFilter) null, async);
}
- public static void removeChildren(Resource res, ResourceNameFilter filter) throws IOException {
+ public static void removeChildren(Resource res, ResourceNameFilter filter, boolean async) throws IOException {
+ if (async) {
+ ExecutorService executor = ThreadUtil.createExecutorService();
+ executor.submit(() -> {
+ try {
+ _removeChildren(res, filter);
+ }
+ catch (IOException e) {
+ LogUtil.log("file", e);
+ }
+ });
+ }
+ else {
+ _removeChildren(res, filter);
+ }
+ }
+
+ private static void _removeChildren(Resource res, ResourceNameFilter filter) throws IOException {
Resource[] children = filter == null ? res.listResources() : res.listResources(filter);
if (children == null) return;
@@ -1052,7 +1071,24 @@ public static void removeChildren(Resource res, ResourceNameFilter filter) throw
}
}
- public static void removeChildren(Resource res, ResourceFilter filter) throws IOException {
+ public static void removeChildren(Resource res, ResourceFilter filter, boolean async) throws IOException {
+ if (async) {
+ ExecutorService executor = ThreadUtil.createExecutorService();
+ executor.submit(() -> {
+ try {
+ _removeChildren(res, filter);
+ }
+ catch (IOException e) {
+ LogUtil.log("file", e);
+ }
+ });
+ }
+ else {
+ _removeChildren(res, filter);
+ }
+ }
+
+ private static void _removeChildren(Resource res, ResourceFilter filter) throws IOException {
Resource[] children = filter == null ? res.listResources() : res.listResources(filter);
if (children == null) return;
@@ -1061,27 +1097,27 @@ public static void removeChildren(Resource res, ResourceFilter filter) throws IO
}
}
- public static void removeChildrenEL(Resource res, ResourceNameFilter filter) {
+ public static void removeChildrenEL(Resource res, ResourceNameFilter filter, boolean async) {
try {
- removeChildren(res, filter);
+ removeChildren(res, filter, async);
}
catch (Throwable t) {
ExceptionUtil.rethrowIfNecessary(t);
}
}
- public static void removeChildrenEL(Resource res, ResourceFilter filter) {
+ public static void removeChildrenEL(Resource res, ResourceFilter filter, boolean async) {
try {
- removeChildren(res, filter);
+ removeChildren(res, filter, async);
}
catch (Throwable t) {
ExceptionUtil.rethrowIfNecessary(t);
}
}
- public static void removeChildrenEL(Resource res) {
+ public static void removeChildrenEL(Resource res, boolean async) {
try {
- removeChildren(res);
+ removeChildren(res, async);
}
catch (Throwable t) {
ExceptionUtil.rethrowIfNecessary(t);
diff --git a/core/src/main/java/lucee/commons/io/res/util/ResourceUtilImpl.java b/core/src/main/java/lucee/commons/io/res/util/ResourceUtilImpl.java
index c68f480c19..f7f53a51d1 100644
--- a/core/src/main/java/lucee/commons/io/res/util/ResourceUtilImpl.java
+++ b/core/src/main/java/lucee/commons/io/res/util/ResourceUtilImpl.java
@@ -163,17 +163,17 @@ public void moveTo(Resource src, Resource dest) throws IOException {
@Override
public void removeChildren(Resource res) throws IOException {
- ResourceUtil.removeChildren(res);
+ ResourceUtil.removeChildren(res, false); // FUTURE add argument async to interface
}
@Override
public void removeChildren(Resource res, ResourceNameFilter filter) throws IOException {
- ResourceUtil.removeChildren(res, filter);
+ ResourceUtil.removeChildren(res, filter, false); // FUTURE add argument async to interface
}
@Override
public void removeChildren(Resource res, ResourceFilter filter) throws IOException {
- ResourceUtil.removeChildren(res, filter);
+ ResourceUtil.removeChildren(res, filter, false); // FUTURE add argument async to interface
}
@Override
@@ -333,17 +333,17 @@ public void copy(Resource src, Resource trg) throws IOException {
@Override
public void removeChildrenSilent(Resource res, ResourceNameFilter filter) {
- ResourceUtil.removeChildrenEL(res, filter);
+ ResourceUtil.removeChildrenEL(res, filter, false);
}
@Override
public void removeChildrenSilent(Resource res, ResourceFilter filter) {
- ResourceUtil.removeChildrenEL(res, filter);
+ ResourceUtil.removeChildrenEL(res, filter, false);
}
@Override
public void removeChildrenSilent(Resource res) {
- ResourceUtil.removeChildrenEL(res);
+ ResourceUtil.removeChildrenEL(res, false);
}
@Override
diff --git a/core/src/main/java/lucee/commons/lang/ClassUtil.java b/core/src/main/java/lucee/commons/lang/ClassUtil.java
index feffa048d4..db7555af92 100644
--- a/core/src/main/java/lucee/commons/lang/ClassUtil.java
+++ b/core/src/main/java/lucee/commons/lang/ClassUtil.java
@@ -45,7 +45,8 @@
import lucee.commons.io.IOUtil;
import lucee.commons.io.SystemUtil;
import lucee.commons.io.res.Resource;
-import lucee.commons.io.res.util.ResourceClassLoader;
+import lucee.runtime.PageContext;
+import lucee.runtime.PageContextImpl;
import lucee.runtime.config.Config;
import lucee.runtime.config.ConfigPro;
import lucee.runtime.config.Identification;
@@ -65,8 +66,8 @@ public final class ClassUtil {
* @throws ClassException
* @throws PageException
*/
- public static Class toClass(String className) throws ClassException {
- return loadClass(className);
+ public static Class toClass(PageContext pc, String className) throws ClassException {
+ return loadClass(pc, className);
}
private static Class checkPrimaryTypesBytecodeDef(String className, Class defaultValue) {
@@ -231,6 +232,10 @@ public static Class loadClass(String className, Class defaultValue) {
return defaultValue;
}
+ public static Class loadClass(String className) throws ClassException {
+ return loadClass((PageContext) null, className);
+ }
+
/**
* loads a class from a String classname
*
@@ -238,28 +243,55 @@ public static Class loadClass(String className, Class defaultValue) {
* @return matching Class
* @throws ClassException
*/
- public static Class loadClass(String className) throws ClassException {
+ public static Class loadClass(PageContext pc, String className) throws ClassException {
Set exceptions = new HashSet();
// OSGI env
Class clazz = _loadClass(new OSGiBasedClassLoading(), className, null, exceptions);
if (clazz != null) {
return clazz;
}
+ // no ThreadLocalPageContext !!!
+ if (pc instanceof PageContextImpl) {
+ ClassLoader cl;
+ try {
+ cl = ((PageContextImpl) pc).getClassLoader(null);
+ }
+ catch (IOException e) {
+ ClassException ce = new ClassException("cannot load class through its string name");
+ ExceptionUtil.initCauseEL(ce, e);
+ throw ce;
+ }
- // core classloader
- clazz = _loadClass(new ClassLoaderBasedClassLoading(SystemUtil.getCoreClassLoader()), className, null, exceptions);
- if (clazz != null) {
- return clazz;
+ // core classloader
+ clazz = _loadClass(new ClassLoaderBasedClassLoading(cl), className, null, exceptions);
+ if (clazz != null) {
+ return clazz;
+ }
}
+ else {
+ // core classloader
+ clazz = _loadClass(new ClassLoaderBasedClassLoading(SystemUtil.getCoreClassLoader()), className, null, exceptions);
+ if (clazz != null) {
+ return clazz;
+ }
- // loader classloader
- clazz = _loadClass(new ClassLoaderBasedClassLoading(SystemUtil.getLoaderClassLoader()), className, null, exceptions);
- if (clazz != null) {
- return clazz;
+ // loader classloader
+ clazz = _loadClass(new ClassLoaderBasedClassLoading(SystemUtil.getLoaderClassLoader()), className, null, exceptions);
+ if (clazz != null) {
+ return clazz;
+ }
}
String msg = "cannot load class through its string name, because no definition for the class with the specified name [" + className + "] could be found";
- if (exceptions.size() > 0) {
+
+ if (exceptions.size() == 1) {
+ Throwable t = exceptions.iterator().next();
+ ClassException ce = new ClassException(msg);
+ ExceptionUtil.initCauseEL(ce, t);
+ throw ce;
+ }
+
+ else if (exceptions.size() > 0) {
StringBuilder detail = new StringBuilder();
Iterator it = exceptions.iterator();
Throwable t;
@@ -279,15 +311,12 @@ public static Class loadClass(ClassLoader cl, String className, Class defaultVal
private static Class loadClass(ClassLoader cl, String className, Class defaultValue, Set exceptions) {
if (cl != null) {
- // TODO do not produce a resource classloader in the first place if there are no resources
- if (cl instanceof ResourceClassLoader && ((ResourceClassLoader) cl).isEmpty()) {
- ClassLoader p = ((ResourceClassLoader) cl).getParent();
- if (p != null) cl = p;
- }
Class clazz = _loadClass(new ClassLoaderBasedClassLoading(cl), className, defaultValue, exceptions);
if (clazz != null) return clazz;
}
+ // MUST javasettings?
+
// OSGI env
Class clazz = _loadClass(new OSGiBasedClassLoading(), className, null, exceptions);
if (clazz != null) return clazz;
@@ -448,8 +477,8 @@ public static Object loadInstance(Class clazz) throws ClassException {
}
}
- public static Object loadInstance(String className) throws ClassException {
- return loadInstance(loadClass(className));
+ public static Object loadInstance(PageContext pc, String className) throws ClassException {
+ return loadInstance(loadClass(pc, className));
}
public static Object loadInstance(ClassLoader cl, String className) throws ClassException {
@@ -545,8 +574,8 @@ public static Object loadInstance(Class clazz, Object[] args) throws ClassExcept
}
}
- public static Object loadInstance(String className, Object[] args) throws ClassException, InvocationTargetException {
- return loadInstance(loadClass(className), args);
+ public static Object loadInstance(PageContext pc, String className, Object[] args) throws ClassException, InvocationTargetException {
+ return loadInstance(loadClass(pc, className), args);
}
public static Object loadInstance(ClassLoader cl, String className, Object[] args) throws ClassException, InvocationTargetException {
@@ -871,11 +900,11 @@ else if (file.isDirectory()) {
* @param defaultValue - a value to return in case the source could not be determined
* @return
*/
- public static String getSourcePathForClass(String className, String defaultValue) {
+ public static String getSourcePathForClass(PageContext pc, String className, String defaultValue) {
try {
- return getSourcePathForClass(ClassUtil.loadClass(className), defaultValue);
+ return getSourcePathForClass(ClassUtil.loadClass(pc, className), defaultValue);
}
catch (Throwable t) {
ExceptionUtil.rethrowIfNecessary(t);
@@ -921,8 +950,9 @@ public static String extractName(String className) {
* @throws ClassException
* @throws BundleException
*/
- public static Class loadClass(String className, String bundleName, String bundleVersion, Identification id, List addional) throws ClassException, BundleException {
- if (StringUtil.isEmpty(bundleName)) return loadClass(className);
+ public static Class loadClass(PageContext pc, String className, String bundleName, String bundleVersion, Identification id, List addional)
+ throws ClassException, BundleException {
+ if (StringUtil.isEmpty(bundleName)) return loadClass(pc, className);
return loadClassByBundle(className, bundleName, bundleVersion, id, addional);
}
@@ -991,13 +1021,16 @@ public Class> loadClass(String className, Class defaultValue, Set e
}
}
- public static ClassLoader getClassLoader(Class clazz) {
+ public static ClassLoader getClassLoader(PageContext pc, Class clazz) throws IOException {
ClassLoader cl = clazz.getClassLoader();
if (cl != null) return cl;
+ if (pc instanceof PageContextImpl) {
+ return ((PageContextImpl) pc).getClassLoader();
+ }
Config config = ThreadLocalPageContext.getConfig();
if (config instanceof ConfigPro) {
- return ((ConfigPro) config).getClassLoaderCore();
+ return ((ConfigPro) config).getRPCClassLoader(false, null);
}
return new lucee.commons.lang.ClassLoaderHelper().getClass().getClassLoader();
}
diff --git a/core/src/main/java/lucee/commons/lang/ExceptionUtil.java b/core/src/main/java/lucee/commons/lang/ExceptionUtil.java
index 5826638574..bc9ef20437 100644
--- a/core/src/main/java/lucee/commons/lang/ExceptionUtil.java
+++ b/core/src/main/java/lucee/commons/lang/ExceptionUtil.java
@@ -167,17 +167,17 @@ public static String similarKeyMessage(Collection.Key[] _keys, String keySearche
}
public static String similarKeyMessage(Collection.Key[] _keys, String keySearched, String keyLabel, String keyLabels, String in, boolean listAll) {
- String inThe = StringUtil.isEmpty(in, true) ? "" : " in the " + in;
+ String inThe = StringUtil.isEmpty(in, true) ? "." : (" in the " + in + ".");
boolean empty = _keys.length == 0;
if (listAll && (_keys.length > 50 || empty)) {
listAll = false;
}
- String list = null;
+ String list = "";
if (listAll) {
Arrays.sort(_keys);
- list = ListUtil.arrayToList(_keys, ", ");
+ list = " Available " + keyLabels + " are [" + ListUtil.arrayToList(_keys, ", ") + "].";
}
String keySearchedSoundex = StringUtil.soundex(keySearched);
@@ -193,7 +193,11 @@ public static String similarKeyMessage(Collection.Key[] _keys, String keySearche
+ "] available.";
}
}
- return "The " + keyLabel + " [" + keySearched + "] does not exist" + inThe;
+ return "The " + keyLabel + " [" + keySearched + "] does not exist" + inThe + list;
+ }
+
+ public static String similarKeyMessage(java.util.Collection coll, String keySearched, String keyLabel, String keyLabels, String in, boolean listAll) {
+ return similarKeyMessage(CollectionUtil.keysFromString(coll), keySearched, keyLabel, keyLabels, in, listAll);
}
public static String similarKeyMessage(Collection coll, String keySearched, String keyLabel, String keyLabels, String in, boolean listAll) {
diff --git a/core/src/main/java/lucee/commons/lang/ExtendableClassLoader.java b/core/src/main/java/lucee/commons/lang/ExtendableClassLoader.java
index 8386e189f6..6a2515231a 100644
--- a/core/src/main/java/lucee/commons/lang/ExtendableClassLoader.java
+++ b/core/src/main/java/lucee/commons/lang/ExtendableClassLoader.java
@@ -20,15 +20,7 @@
import java.lang.instrument.UnmodifiableClassException;
-public abstract class ExtendableClassLoader extends ClassLoader {
-
- public ExtendableClassLoader() {
- super();
- }
-
- public ExtendableClassLoader(ClassLoader parent) {
- super(parent);
- }
+public interface ExtendableClassLoader {
/**
* allow to define a new Class with help of the bytecode passed to the method
diff --git a/core/src/main/java/lucee/commons/lang/MemoryClassLoader.java b/core/src/main/java/lucee/commons/lang/MemoryClassLoader.java
index e70df8575b..1059d5ca47 100644
--- a/core/src/main/java/lucee/commons/lang/MemoryClassLoader.java
+++ b/core/src/main/java/lucee/commons/lang/MemoryClassLoader.java
@@ -27,7 +27,7 @@
/**
* ClassLoader that loads classes in memory that are not stored somewhere physically
*/
-public final class MemoryClassLoader extends ExtendableClassLoader {
+public final class MemoryClassLoader extends ClassLoader implements ExtendableClassLoader {
static {
boolean res = registerAsParallelCapable();
}
diff --git a/core/src/main/java/lucee/commons/lang/PhysicalClassLoader.java b/core/src/main/java/lucee/commons/lang/PhysicalClassLoader.java
index 474f21cf83..a2ef03bd97 100644
--- a/core/src/main/java/lucee/commons/lang/PhysicalClassLoader.java
+++ b/core/src/main/java/lucee/commons/lang/PhysicalClassLoader.java
@@ -22,47 +22,65 @@
import java.io.IOException;
import java.io.InputStream;
import java.lang.instrument.UnmodifiableClassException;
-import java.lang.ref.SoftReference;
import java.net.URL;
+import java.net.URLClassLoader;
import java.util.ArrayList;
-import java.util.Arrays;
+import java.util.Collection;
+import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
+import org.apache.felix.framework.BundleWiringImpl.BundleClassLoader;
+
import lucee.commons.digest.HashUtil;
+import lucee.commons.io.CharsetUtil;
import lucee.commons.io.IOUtil;
import lucee.commons.io.SystemUtil;
import lucee.commons.io.log.LogUtil;
import lucee.commons.io.res.Resource;
-import lucee.commons.io.res.util.ResourceClassLoader;
+import lucee.commons.io.res.type.file.FileResource;
import lucee.commons.io.res.util.ResourceUtil;
import lucee.runtime.PageSourcePool;
import lucee.runtime.config.Config;
import lucee.runtime.config.ConfigPro;
+import lucee.runtime.converter.ConverterException;
+import lucee.runtime.converter.JSONConverter;
+import lucee.runtime.converter.JSONDateFormat;
import lucee.runtime.exp.ApplicationException;
-import lucee.runtime.type.util.ArrayUtil;
+import lucee.runtime.listener.JavaSettings;
+import lucee.runtime.listener.JavaSettingsImpl;
+import lucee.runtime.listener.SerializationSettings;
+import lucee.runtime.type.Struct;
+import lucee.runtime.type.StructImpl;
+import lucee.runtime.type.util.KeyConstants;
import lucee.transformer.bytecode.util.ClassRenamer;
/**
* Directory ClassLoader
*/
-public final class PhysicalClassLoader extends ExtendableClassLoader {
+public final class PhysicalClassLoader extends URLClassLoader implements ExtendableClassLoader {
static {
boolean res = registerAsParallelCapable();
}
+ private static RC rc = new RC();
+
+ private static Map classLoaders = new ConcurrentHashMap<>();
+
private Resource directory;
private ConfigPro config;
- private final ClassLoader[] parents;
+ private final ClassLoader addionalClassLoader;
+ private final Collection resources;
private Map loadedClasses = new ConcurrentHashMap();
private Map allLoadedClasses = new ConcurrentHashMap(); // this includes all renames
private Map unavaiClasses = new ConcurrentHashMap();
- private Map> customCLs;
private PageSourcePool pageSourcePool;
+ private boolean rpc;
+
private static long counter = 0L;
private static long _start = 0L;
private static String start = Long.toString(_start, Character.MAX_RADIX);
@@ -80,43 +98,84 @@ public static String uid() {
}
}
- /**
- * Constructor of the class
- *
- * @param directory
- * @param parent
- * @throws IOException
- */
- public PhysicalClassLoader(Config c, Resource directory, PageSourcePool pageSourcePool) throws IOException {
- this(c, directory, (ClassLoader[]) null, true, pageSourcePool);
- }
+ public static PhysicalClassLoader getPhysicalClassLoader(Config c, Resource directory, boolean reload) throws IOException {
- public PhysicalClassLoader(Config c, Resource directory, ClassLoader[] parentClassLoaders, boolean includeCoreCL, PageSourcePool pageSourcePool) throws IOException {
- super(parentClassLoaders == null || parentClassLoaders.length == 0 ? c.getClassLoader() : parentClassLoaders[0]);
- config = (ConfigPro) c;
+ String key = HashUtil.create64BitHashAsString(directory.getAbsolutePath());
- this.pageSourcePool = pageSourcePool;
- // ClassLoader resCL = parent!=null?parent:config.getResourceClassLoader(null);
+ PhysicalClassLoader rpccl = reload ? null : classLoaders.get(key);
+ if (rpccl == null) {
+ synchronized (SystemUtil.createToken("PhysicalClassLoader", key)) {
+ rpccl = reload ? null : classLoaders.get(key);
+ if (rpccl == null) {
+ classLoaders.put(key, rpccl = new PhysicalClassLoader(c, new ArrayList(), directory, SystemUtil.getCombinedClassLoader(), null, null, false));
+ }
+ }
+ }
+ return rpccl;
+ }
- List tmp = new ArrayList();
- if (parentClassLoaders == null || parentClassLoaders.length == 0) {
- ResourceClassLoader _cl = config.getResourceClassLoader(null);
- if (_cl != null) tmp.add(_cl);
+ public static PhysicalClassLoader getRPCClassLoader(Config c, JavaSettings js, boolean reload) throws IOException {
+
+ String key = js == null ? "orphan" : ((JavaSettingsImpl) js).id();
+
+ PhysicalClassLoader rpccl = reload ? null : classLoaders.get(key);
+ if (rpccl == null) {
+ synchronized (SystemUtil.createToken("PhysicalClassLoader", key)) {
+ rpccl = reload ? null : classLoaders.get(key);
+ if (rpccl == null) {
+ List resources;
+ if (js == null) {
+ resources = new ArrayList();
+ }
+ else {
+ resources = toSortedList(JavaSettingsImpl.getAllResources(js));
+ }
+ Resource dir = storeResourceMeta(c, key, js, resources);
+ // (Config config, String key, JavaSettings js, Collection _resources)
+ classLoaders.put(key, rpccl = new PhysicalClassLoader(c, resources, dir, SystemUtil.getCombinedClassLoader(), null, null, true));
+ }
+ }
}
- else {
- for (ClassLoader p: parentClassLoaders) {
- tmp.add(p);
+ return rpccl;
+ }
+
+ public static PhysicalClassLoader getRPCClassLoader(Config c, BundleClassLoader bcl, boolean reload) throws IOException {
+ String key = HashUtil.create64BitHashAsString(bcl + "");
+ PhysicalClassLoader rpccl = reload ? null : classLoaders.get(key);
+ if (rpccl == null) {
+ synchronized (SystemUtil.createToken("PhysicalClassLoader", key)) {
+ rpccl = reload ? null : classLoaders.get(key);
+ if (rpccl == null) {
+ Resource dir = c.getClassDirectory().getRealResource("RPC/" + key);
+ if (!dir.exists()) ResourceUtil.createDirectoryEL(dir, true);
+ // (Config config, String key, JavaSettings js, Collection _resources)
+ classLoaders.put(key, rpccl = new PhysicalClassLoader(c, new ArrayList(), dir, SystemUtil.getCombinedClassLoader(), bcl, null, true));
+ }
}
}
+ return rpccl;
+ }
- if (includeCoreCL) tmp.add(config.getClassLoaderCore());
- parents = tmp.toArray(new ClassLoader[tmp.size()]);
+ private PhysicalClassLoader(Config c, List resources, Resource directory, ClassLoader parentClassLoader, ClassLoader addionalClassLoader,
+ PageSourcePool pageSourcePool, boolean rpc) throws IOException {
+ super(doURLs(resources), parentClassLoader == null ? (parentClassLoader = SystemUtil.getCombinedClassLoader()) : parentClassLoader);
+ this.resources = resources;
+ config = (ConfigPro) c;
+ this.addionalClassLoader = addionalClassLoader;
+
+ this.pageSourcePool = pageSourcePool;
+ // ClassLoader resCL = parent!=null?parent:config.getResourceClassLoader(null);
// check directory
if (!directory.exists()) directory.mkdirs();
if (!directory.isDirectory()) throw new IOException("Resource [" + directory + "] is not a directory");
if (!directory.canRead()) throw new IOException("Access denied to [" + directory + "] directory");
this.directory = directory;
+ this.rpc = rpc;
+ }
+
+ public boolean isRPC() {
+ return rpc;
}
@Override
@@ -135,14 +194,21 @@ private Class> loadClass(String name, boolean resolve, boolean loadFromFS) thr
// First, check if the class has already been loaded
Class> c = findLoadedClass(name);
if (c == null) {
- for (ClassLoader p: parents) {
+ try {
+ c = super.loadClass(name, resolve);
+ }
+ catch (Exception e) {
+ }
+
+ if (addionalClassLoader != null) {
try {
- c = p.loadClass(name);
- break;
+ c = addionalClassLoader.loadClass(name);
}
catch (Exception e) {
}
}
+
+ // }
if (c == null) {
if (loadFromFS) c = findClass(name);
else throw new ClassNotFoundException(name);
@@ -153,7 +219,22 @@ private Class> loadClass(String name, boolean resolve, boolean loadFromFS) thr
}
@Override
- protected Class> findClass(String name) throws ClassNotFoundException {// if(name.indexOf("sub")!=-1)print.ds(name);
+ protected Class> findClass(String name) throws ClassNotFoundException {
+
+ try {
+ return super.findClass(name);
+ }
+ catch (ClassNotFoundException cnfe) {
+ }
+
+ if (addionalClassLoader != null) {
+ try {
+ return addionalClassLoader.loadClass(name);
+ }
+ catch (ClassNotFoundException e) {
+ }
+ }
+
synchronized (SystemUtil.createToken("pcl", name)) {
Resource res = directory.getRealResource(name.replace('.', '/').concat(".class"));
@@ -218,6 +299,14 @@ public URL getResource(String name) {
return null;
}
+ public Resource[] getJarResources() {
+ return resources.toArray(new Resource[resources.size()]);
+ }
+
+ public boolean hasJarResources() {
+ return resources.isEmpty();
+ }
+
public int getSize(boolean includeAllRenames) {
return includeAllRenames ? allLoadedClasses.size() : loadedClasses.size();
}
@@ -269,31 +358,6 @@ public Resource getDirectory() {
return directory;
}
- public PhysicalClassLoader getCustomClassLoader(Resource[] resources, boolean reload) throws IOException {
- if (ArrayUtil.isEmpty(resources)) return this;
- String key = hash(resources);
-
- if (reload && customCLs != null) customCLs.remove(key);
-
- SoftReference tmp = customCLs == null ? null : customCLs.get(key);
- PhysicalClassLoader pcl = tmp == null ? null : tmp.get();
- if (pcl != null) return pcl;
- pcl = new PhysicalClassLoader(config, getDirectory(), new ClassLoader[] { new ResourceClassLoader(resources, getParent()) }, true, pageSourcePool);
- if (customCLs == null) customCLs = new ConcurrentHashMap>();
- customCLs.put(key, new SoftReference(pcl));
- return pcl;
- }
-
- private String hash(Resource[] resources) {
- Arrays.sort(resources);
- StringBuilder sb = new StringBuilder();
- for (int i = 0; i < resources.length; i++) {
- sb.append(ResourceUtil.getCanonicalPathEL(resources[i]));
- sb.append(';');
- }
- return HashUtil.create64BitHashAsString(sb.toString(), Character.MAX_RADIX);
- }
-
public void clear() {
clear(true);
}
@@ -305,6 +369,26 @@ public void clear(boolean clearPagePool) {
this.unavaiClasses.clear();
}
+ private static Resource storeResourceMeta(Config config, String key, JavaSettings js, Collection _resources) throws IOException {
+ Resource dir = config.getClassDirectory().getRealResource("RPC/" + key);
+ if (!dir.exists()) {
+ ResourceUtil.createDirectoryEL(dir, true);
+ Resource file = dir.getRealResource("classloader-resources.json");
+ Struct root = new StructImpl();
+ root.setEL(KeyConstants._resources, _resources);
+ JSONConverter json = new JSONConverter(true, CharsetUtil.UTF8, JSONDateFormat.PATTERN_CF, false);
+ try {
+ String str = json.serialize(null, root, SerializationSettings.SERIALIZE_AS_COLUMN, null);
+ IOUtil.write(file, str, CharsetUtil.UTF8, false);
+ }
+ catch (ConverterException e) {
+ throw ExceptionUtil.toIOException(e);
+ }
+
+ }
+ return dir;
+ }
+
/**
* removes memory based appendix from class name, for example it translates
* [test.test_cfc$sub2$cf$5] to [test.test_cfc$sub2$cf]
@@ -334,4 +418,49 @@ public void finalize() throws Throwable {
super.finalize();
}
+ public static List toSortedList(Collection resources) {
+ List list = new ArrayList();
+ if (resources != null) {
+ for (Resource r: resources) {
+ if (r != null) list.add(r);
+ }
+ }
+ java.util.Collections.sort(list, rc);
+ return list;
+ }
+
+ public static List toSortedList(Resource[] resources) {
+ List list = new ArrayList();
+ if (resources != null) {
+ for (Resource r: resources) {
+ if (r != null) list.add(r);
+ }
+ }
+ java.util.Collections.sort(list, rc);
+ return list;
+ }
+
+ private static URL[] doURLs(Collection reses) throws IOException {
+ List list = new ArrayList();
+ for (Resource r: reses) {
+ if (r.isDirectory() || "jar".equalsIgnoreCase(ResourceUtil.getExtension(r, null))) list.add(doURL(r));
+ }
+ return list.toArray(new URL[list.size()]);
+ }
+
+ private static URL doURL(Resource res) throws IOException {
+ if (!(res instanceof FileResource)) {
+ return ResourceUtil.toFile(res).toURL();
+ }
+ return ((FileResource) res).toURL();
+ }
+
+ private static class RC implements Comparator {
+
+ @Override
+ public int compare(Resource l, Resource r) {
+ return l.getAbsolutePath().compareTo(r.getAbsolutePath());
+ }
+ }
+
}
diff --git a/core/src/main/java/lucee/commons/tree/TreeNode.java b/core/src/main/java/lucee/commons/tree/TreeNode.java
new file mode 100644
index 0000000000..9b79954042
--- /dev/null
+++ b/core/src/main/java/lucee/commons/tree/TreeNode.java
@@ -0,0 +1,95 @@
+package lucee.commons.tree;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+public class TreeNode {
+ private T value;
+ private List> children;
+ private Set all;
+ private TreeNode parent;
+
+ public TreeNode(T value) {
+ this(value, false);
+ }
+
+ public TreeNode(T value, boolean allowIncest) {
+ this.value = value;
+ if (!allowIncest) all = new HashSet();
+ }
+
+ private TreeNode(T value, TreeNode parent) {
+ this.value = value;
+ all = parent.all;
+ }
+
+ public T getValue() {
+ return value;
+ }
+
+ public void setValue(T value) {
+ this.value = value;
+ }
+
+ public TreeNode getParent() {
+ return parent;
+ }
+
+ public List> getChildren() {
+ return children;
+ }
+
+ public boolean addChild(T child) {
+ if (all != null) {
+ if (all.contains(child)) return false;
+ all.add(child);
+ }
+ if (children == null) children = new ArrayList>();
+ children.add(new TreeNode(child, this));
+ return true;
+ }
+
+ public void removeChild(T child) {
+ children.remove(child);
+ if (all != null) all.remove(child);
+
+ }
+
+ public List asList() {
+ List list = new ArrayList();
+ list.add(getValue());
+ asList(list, getChildren());
+ return list;
+ }
+
+ private void asList(List list, List> children) {
+ if (children == null) return;
+
+ for (TreeNode n: children) {
+ list.add(n.getValue());
+ asList(list, n.getChildren());
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(value.toString()).append('\n');
+ toString(sb, getChildren(), 1);
+ return sb.toString();
+ }
+
+ private void toString(StringBuilder sb, List> children, int level) {
+ if (children == null) return;
+ for (TreeNode tn: children) {
+ for (int i = 0; i < level; i++) {
+ sb.append('-');
+ }
+ sb.append(' ').append(tn.getValue().toString()).append('\n');
+ toString(sb, tn.getChildren(), level + 1);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/core/src/main/java/lucee/runtime/CFMLFactoryImpl.java b/core/src/main/java/lucee/runtime/CFMLFactoryImpl.java
index ddaf1a2a98..c51c8a9ccf 100644
--- a/core/src/main/java/lucee/runtime/CFMLFactoryImpl.java
+++ b/core/src/main/java/lucee/runtime/CFMLFactoryImpl.java
@@ -73,6 +73,7 @@
import lucee.runtime.type.util.ArrayUtil;
import lucee.runtime.type.util.KeyConstants;
import lucee.runtime.type.util.ListUtil;
+import lucee.runtime.util.PageContextUtil;
import lucee.servlet.http.HTTPServletImpl;
/**
@@ -232,7 +233,7 @@ public PageContextImpl getPageContextImpl(HttpServlet servlet, HttpServletReques
pc = null;
}
}
- if (pc == null) pc = new PageContextImpl(scopeContext, config, servlet, ignoreScopes);
+ if (pc == null) pc = new PageContextImpl(scopeContext, config, servlet, tmplPC, ignoreScopes);
if (timeout > 0) pc.setRequestTimeout(timeout);
if (register2RunningThreads) {
@@ -580,7 +581,7 @@ public Array getInfo() {
data.setEL(KeyConstants._urltoken, pc.getURLToken());
try {
- if (pc.getConfig().debug()) data.setEL("debugger", pc.getDebugger().getDebuggingData(pc));
+ if (PageContextUtil.debug(pc)) data.setEL("debugger", pc.getDebugger().getDebuggingData(pc));
}
catch (PageException e2) {
}
diff --git a/core/src/main/java/lucee/runtime/ComponentPageImpl.java b/core/src/main/java/lucee/runtime/ComponentPageImpl.java
index cdd2ba2855..a7a05ef86a 100755
--- a/core/src/main/java/lucee/runtime/ComponentPageImpl.java
+++ b/core/src/main/java/lucee/runtime/ComponentPageImpl.java
@@ -85,6 +85,7 @@
import lucee.runtime.type.util.ListUtil;
import lucee.runtime.type.util.StructUtil;
import lucee.runtime.type.util.UDFUtil;
+import lucee.runtime.util.PageContextUtil;
/**
* A Page that can produce Components
@@ -174,7 +175,7 @@ public Object call(PageContext pc) throws PageException {
// METHOD INVOCATION
String qs = ReqRspUtil.getQueryString(pc.getHttpServletRequest());
- if (pc.getBasePageSource() == this.getPageSource() && pc.getConfig().debug()) pc.getDebugger().setOutput(false);
+ if (pc.getBasePageSource() == this.getPageSource() && PageContextUtil.show(pc)) pc.getDebugger().setOutput(false);
boolean isPost = pc.getHttpServletRequest().getMethod().equalsIgnoreCase("POST");
@@ -413,7 +414,7 @@ private void callRest(PageContext pc, Component component, String path, Result r
if (status == 404) {
String prefix = "no rest service for [" + path + "] found";
- if (pc.getConfig().debug()) {
+ if (PageContextUtil.show(pc)) {
msg = prefix + " in" + addDetail;
}
else {
@@ -424,7 +425,7 @@ private void callRest(PageContext pc, Component component, String path, Result r
}
else if (status == 405) {
String prefix = "Unsupported Media Type";
- if (pc.getConfig().debug()) {
+ if (PageContextUtil.show(pc)) {
msg = prefix + " for" + addDetail;
}
else {
@@ -435,7 +436,7 @@ else if (status == 405) {
}
else if (status == 406) {
String prefix = "Not Acceptable";
- if (pc.getConfig().debug()) {
+ if (PageContextUtil.show(pc)) {
msg = prefix + " for" + addDetail;
}
else {
@@ -547,7 +548,7 @@ else if (!"body".equalsIgnoreCase(restArgSource)) {
rtn = component.callWithNamedValues(pc, methodName, args);
}
catch (PageException e) {
- if (pc.getConfig().debug()) {
+ if (PageContextUtil.show(pc)) {
throw e;
}
else {
@@ -813,7 +814,7 @@ else if (Decision.isCastableToArray(args)) {
}
catch (Throwable t) {
PageException pe = Caster.toPageException(t);
- if (pc.getConfig().debug()) pe.setExposeMessage(true);
+ if (PageContextUtil.show(pc)) pe.setExposeMessage(true);
throw pe;
}
}
diff --git a/core/src/main/java/lucee/runtime/InterfacePageImpl.java b/core/src/main/java/lucee/runtime/InterfacePageImpl.java
index 213eab142e..175df5945d 100755
--- a/core/src/main/java/lucee/runtime/InterfacePageImpl.java
+++ b/core/src/main/java/lucee/runtime/InterfacePageImpl.java
@@ -26,6 +26,7 @@
import lucee.runtime.net.http.ReqRspUtil;
import lucee.runtime.op.Caster;
import lucee.runtime.type.util.KeyConstants;
+import lucee.runtime.util.PageContextUtil;
/**
* A Page that can produce Components
@@ -60,7 +61,7 @@ public Object call(PageContext pc) throws PageException {
}
String qs = ReqRspUtil.getQueryString(pc.getHttpServletRequest());
- if (pc.getBasePageSource() == this.getPageSource() && pc.getConfig().debug()) pc.getDebugger().setOutput(false);
+ if (pc.getBasePageSource() == this.getPageSource() && PageContextUtil.debug(pc)) pc.getDebugger().setOutput(false);
boolean isPost = pc.getHttpServletRequest().getMethod().equalsIgnoreCase("POST");
// POST
diff --git a/core/src/main/java/lucee/runtime/MappingImpl.java b/core/src/main/java/lucee/runtime/MappingImpl.java
index 717993b682..6c8c2d3c2c 100755
--- a/core/src/main/java/lucee/runtime/MappingImpl.java
+++ b/core/src/main/java/lucee/runtime/MappingImpl.java
@@ -23,7 +23,6 @@
import java.io.Serializable;
import java.lang.instrument.UnmodifiableClassException;
import java.lang.ref.SoftReference;
-import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@@ -72,7 +71,7 @@ public final class MappingImpl implements Mapping {
private final int inspectTemplateAutoIntervalFast;
private boolean physicalFirst;
- private transient Map loaders = new HashMap<>();
+ // private transient Map loaders = new HashMap<>();
private Resource archive;
private final Config config;
@@ -253,25 +252,27 @@ public Class> loadClass(String className) {
}
private Class> loadClass(String className, byte[] code) throws IOException, ClassNotFoundException {
-
- PhysicalClassLoaderReference pclr = loaders.get(className);
- PhysicalClassLoader pcl = pclr == null ? null : pclr.get();
- if (pcl == null || code != null) {// || pcl.getSize(true) > 3
- if (pcl != null) {
- pcl.clear();
- }
- pcl = new PhysicalClassLoader(config, getClassRootDirectory(), pageSourcePool);
- synchronized (loaders) {
- loaders.put(className, new PhysicalClassLoaderReference(pcl));
- }
- }
+ PhysicalClassLoader pcl = PhysicalClassLoader.getPhysicalClassLoader(config, getClassRootDirectory(), false);
+ /*
+ * PhysicalClassLoaderReference pclr = loaders.get(className); PhysicalClassLoader pcl = pclr ==
+ * null ? null : pclr.get(); if (pcl == null || code != null) {// || pcl.getSize(true) > 3 if (pcl
+ * != null) { pcl.clear(); } pcl = PhysicalClassLoader.getPhysicalClassLoader(config,
+ * getClassRootDirectory(), true); synchronized (loaders) { loaders.put(className, new
+ * PhysicalClassLoaderReference(pcl)); } }
+ */
if (code != null) {
try {
return pcl.loadClass(className, code);
}
catch (UnmodifiableClassException e) {
- throw ExceptionUtil.toIOException(e);
+ pcl = PhysicalClassLoader.getPhysicalClassLoader(config, getClassRootDirectory(), true);
+ try {
+ return pcl.loadClass(className, code);
+ }
+ catch (UnmodifiableClassException ex) {
+ throw ExceptionUtil.toIOException(ex);
+ }
}
}
return pcl.loadClass(className);
@@ -282,18 +283,10 @@ public void cleanLoaders() {
}
public void clear(String className) {
- PhysicalClassLoaderReference ref = loaders.remove(className);
- PhysicalClassLoader pcl;
- if (ref != null) {
- pcl = ref.get();
- if (pcl != null) {
- pcl.clear(false);
- }
- }
- }
-
- public int getSize() {
- return loaders.size();
+ /*
+ * PhysicalClassLoaderReference ref = loaders.remove(className); PhysicalClassLoader pcl; if (ref !=
+ * null) { pcl = ref.get(); if (pcl != null) { pcl.clear(false); } }
+ */
}
@Override
diff --git a/core/src/main/java/lucee/runtime/PageContextImpl.java b/core/src/main/java/lucee/runtime/PageContextImpl.java
index 30edaac100..9160c35be7 100644
--- a/core/src/main/java/lucee/runtime/PageContextImpl.java
+++ b/core/src/main/java/lucee/runtime/PageContextImpl.java
@@ -66,12 +66,10 @@
import lucee.commons.io.log.Log;
import lucee.commons.io.log.LogUtil;
import lucee.commons.io.res.Resource;
-import lucee.commons.io.res.util.ResourceClassLoader;
import lucee.commons.lang.ClassException;
import lucee.commons.lang.ClassUtil;
import lucee.commons.lang.ExceptionUtil;
import lucee.commons.lang.HTMLEntities;
-import lucee.commons.lang.PhysicalClassLoader;
import lucee.commons.lang.StringUtil;
import lucee.commons.lang.mimetype.MimeType;
import lucee.commons.lang.types.RefBoolean;
@@ -81,6 +79,8 @@
import lucee.commons.net.HTTPUtil;
import lucee.intergral.fusiondebug.server.FDSignal;
import lucee.loader.engine.CFMLEngine;
+import lucee.runtime.ai.AIEngine;
+import lucee.runtime.ai.AISession;
import lucee.runtime.cache.CacheConnection;
import lucee.runtime.cache.CacheUtil;
import lucee.runtime.cache.tag.CacheHandler;
@@ -133,6 +133,7 @@
import lucee.runtime.listener.ApplicationContextSupport;
import lucee.runtime.listener.ApplicationListener;
import lucee.runtime.listener.ClassicApplicationContext;
+import lucee.runtime.listener.JavaSettings;
import lucee.runtime.listener.JavaSettingsImpl;
import lucee.runtime.listener.ModernAppListenerException;
import lucee.runtime.listener.NoneAppListener;
@@ -228,7 +229,7 @@
public final class PageContextImpl extends PageContext {
private static final RefBoolean DUMMY_BOOL = new RefBooleanImpl(false);
-
+ private static final boolean JAVA_SETTING_CLASSIC_MODE = false;
private static int counter = 0;
/**
@@ -359,8 +360,6 @@ public final class PageContextImpl extends PageContext {
private StackTraceElement[] timeoutStacktrace;
- private boolean fullNullSupport;
-
private static final boolean READ_CFID_FROM_URL = Caster.toBooleanValue(SystemUtil.getSystemPropOrEnvVar("lucee.read.cfid.from.url", "true"), true);
private static AtomicInteger _idCounter = new AtomicInteger(1);
private long lastTimeoutNoAction;
@@ -374,22 +373,21 @@ public final class PageContextImpl extends PageContext {
* @param id identity of the pageContext
* @param servlet
*/
- public PageContextImpl(ScopeContext scopeContext, ConfigWebPro config, HttpServlet servlet, boolean jsr223) {
+ public PageContextImpl(ScopeContext scopeContext, ConfigWebPro config, HttpServlet servlet, PageContextImpl template, boolean jsr223) {
+
// must be first because is used after
tagHandlerPool = config.getTagHandlerPool();
this.servlet = servlet;
-
+ this.initApplicationContext = template != null ? template.initApplicationContext : new ClassicApplicationContext(config, "", true, null);
+ if (template != null) this.applicationContext = template.applicationContext;
bodyContentStack = new BodyContentStack();
devNull = bodyContentStack.getDevNullBodyContent();
-
this.config = config;
manager = new DatasourceManagerImpl(config);
this.scopeContext = scopeContext;
undefined = new UndefinedImpl(this, getScopeCascadingType());
server = ScopeContext.getServerScope(this, jsr223);
- initApplicationContext = new ClassicApplicationContext(config, "", true, null);
-
this.id = getIdCounter();
}
@@ -455,7 +453,6 @@ public PageContextImpl initialize(HttpServlet servlet, HttpServletRequest req, H
ReqRspUtil.setContentType(rsp, "text/html; charset=" + config.getWebCharset().name());
this.isChild = isChild;
- fullNullSupport = config.getFullNullSupport();
startTime = System.currentTimeMillis();
startTimeNS = System.nanoTime();
@@ -489,6 +486,12 @@ public PageContextImpl initialize(HttpServlet servlet, HttpServletRequest req, H
// Scopes
server = ScopeContext.getServerScope(this, ignoreScopes);
if (clone) {
+ this.cfid = tmplPC.cfid;
+ this.client = tmplPC.client;
+ this.session = tmplPC.session;
+ this.cgiR = tmplPC.cgiR;
+ this.cgiRW = tmplPC.cgiRW;
+ this.cookie = tmplPC.cookie;
this.form = tmplPC.form;
this.url = tmplPC.url;
this.urlForm = tmplPC.urlForm;
@@ -539,16 +542,19 @@ else if (variables == null) {
else {
_psq = null;
}
-
fdEnabled = !config.allowRequestTimeout();
-
if (config.getExecutionLogEnabled()) this.execLog = config.getExecutionLogFactory().getInstance(this);
if (debugger != null) debugger.init(config);
- undefined.initialize(this);
+ if (clone) {
+ ((UndefinedImpl) undefined).initialize(this, tmplPC.getScopeCascadingType(), tmplPC.hasDebugOptions(ConfigPro.DEBUG_IMPLICIT_ACCESS));
+ }
+ else {
+ undefined.initialize(this);
+ }
timeoutStacktrace = null;
if (clone) {
- getCFID();
+ tmplPC.getCFID();
this.cfid = tmplPC.cfid;
this.cftoken = tmplPC.cftoken;
@@ -576,9 +582,7 @@ else if (variables == null) {
}
}
tmplPC.children.add(this);
-
this.applicationContext = tmplPC.applicationContext;
- this.setFullNullSupport();
// path
this.base = tmplPC.base;
@@ -604,7 +608,7 @@ public void release() {
execLog = null;
}
- if (config.debug()) {
+ if (PageContextUtil.debug(this)) {
boolean skipLogThread = isChild;
if (skipLogThread && hasDebugOptions(ConfigPro.DEBUG_THREAD)) skipLogThread = false;
if (!skipLogThread && !gatewayContext) config.getDebuggerPool().store(this, debugger);
@@ -619,22 +623,24 @@ public void release() {
caller = null;
callerTemplate = null;
root = null;
- // Attention have to be before close
- if (client != null) {
- client.touchAfterRequest(this);
- client = null;
- }
-
- if (session != null) {
- session.touchAfterRequest(this);
- session = null;
- }
// ORM
// if(ormSession!=null)releaseORM();
// Scopes
if (hasFamily) {
+ boolean lastStanding = lastStanding();
+ // Attention have to be before close
+ if (client != null) {
+ if (lastStanding) client.touchAfterRequest(this);
+ client = null;
+ }
+
+ if (session != null) {
+ if (lastStanding) session.touchAfterRequest(this);
+ session = null;
+ }
+
if (hasFamily && !isChild) {
req.disconnect(this);
}
@@ -653,8 +659,22 @@ public void release() {
threads = null;
allThreads = null;
currentThread = null;
+ cgiR = new CGIImplReadOnly();
+ cgiRW = new CGIImpl();
}
else {
+
+ // Attention have to be before close
+ if (client != null) {
+ client.touchAfterRequest(this);
+ client = null;
+ }
+
+ if (session != null) {
+ session.touchAfterRequest(this);
+ session = null;
+ }
+
close();
base = null;
if (variables.isBind()) {
@@ -668,9 +688,9 @@ public void release() {
undefined.release(this);
urlForm.release(this);
request.release(this);
+ cgiR.release(this);
+ cgiRW.release(this);
}
- cgiR.release(this);
- cgiRW.release(this);
argument.release(this);
local = localUnsupportedScope;
@@ -751,6 +771,25 @@ public void release() {
catch (Exception e) {
}
}
+ startTime = 0L;
+ }
+
+ private boolean lastStanding() {
+ if (!hasFamily()) return true;
+ // active childern?
+ Queue tmp = this.children;
+ if (tmp != null) {
+ for (PageContext p: tmp) {
+ if (p.getStartTime() > 0) return false;
+ }
+ }
+ // active parent?
+ PageContext p = this;
+ while ((p = p.getParentPageContext()) != null) {
+ if (p.getStartTime() > 0) return false;
+ }
+
+ return false;
}
private void releaseORM() throws PageException {
@@ -2679,13 +2718,11 @@ else if (StringUtil.endsWithIgnoreCase(pathInfo, ".java")) {
@Override
public final void execute(String realPath, boolean throwExcpetion, boolean onlyTopLevel) throws PageException {
- fullNullSupport = getConfig().getFullNullSupport();
_execute(realPath, throwExcpetion, onlyTopLevel);
}
@Override
public final void executeCFML(String realPath, boolean throwExcpetion, boolean onlyTopLevel) throws PageException {
- fullNullSupport = getConfig().getFullNullSupport();
_execute(realPath, throwExcpetion, onlyTopLevel);
}
@@ -2771,7 +2808,7 @@ private final void execute(PageSource ps, boolean throwExcpetion, boolean onlyTo
setCFOutputOnly((short) 0);
}
if (!gatewayContext) {
- if (getConfig().debug()) {
+ if (show()) {
try {
listener.onDebug(this);
}
@@ -2821,6 +2858,10 @@ public boolean hasDebugOptions(int option) {
return getApplicationContext().hasDebugOptions(option);
}
+ public int getDebugOptions() {
+ return getApplicationContext().getDebugOptions();
+ }
+
private void initallog() {
if (!isGatewayContext() && config.isMonitoringEnabled()) {
RequestMonitor[] monitors = config.getRequestMonitors();
@@ -3346,13 +3387,11 @@ public void clearCatch() {
@Override
public void addPageSource(PageSource ps, boolean alsoInclude) {
- setFullNullSupport();
pathList.add(ps);
if (alsoInclude) includePathList.add(ps);
}
public void addPageSource(PageSource ps, PageSource psInc) {
- setFullNullSupport();
pathList.add(ps);
if (psInc != null) includePathList.add(psInc);
}
@@ -3360,9 +3399,6 @@ public void addPageSource(PageSource ps, PageSource psInc) {
@Override
public void removeLastPageSource(boolean alsoInclude) {
if (!pathList.isEmpty()) pathList.removeLast();
- if (!pathList.isEmpty()) {
- setFullNullSupport();
- }
if (alsoInclude && !includePathList.isEmpty()) includePathList.removeLast();
}
@@ -3410,7 +3446,6 @@ public void setApplicationContext(ApplicationContext ac) {
if (ac != null) this.applicationContext = (ApplicationContextSupport) ac;
else return;
- setFullNullSupport();
int scriptProtect = applicationContext.getScriptProtect();
// ScriptProtecting
@@ -3801,38 +3836,32 @@ public ORMSession getORMSession(boolean create) throws PageException {
return ormSession;
}
- public ClassLoader getClassLoader() throws IOException {
- return getResourceClassLoader();
- }
-
- public ClassLoader getClassLoader(Resource[] reses) throws IOException {
+ /*
+ * public ClassLoader getClassLoader() throws IOException { return getClassLoader(null); }
+ */
- ResourceClassLoader rcl = getResourceClassLoader();
- return rcl.getCustomResourceClassLoader(reses);
+ public JavaSettings getJavaSettings() {
+ return getApplicationContext().getJavaSettings();
}
- private ResourceClassLoader getResourceClassLoader() throws IOException {
- JavaSettingsImpl js = (JavaSettingsImpl) getApplicationContext().getJavaSettings();
+ public ClassLoader getRPCClassLoader(boolean reload) throws IOException {
+ return getRPCClassLoader(reload, (JavaSettings) null);
+ }
- if (js != null) {
- Resource[] jars = js.getResourcesTranslated();
- if (jars.length > 0) return config.getResourceClassLoader().getCustomResourceClassLoader(jars);
- }
- return config.getResourceClassLoader();
+ public ClassLoader getClassLoader() throws IOException {
+ return getRPCClassLoader(false, (JavaSettings) null);
}
- public ClassLoader getRPCClassLoader(boolean reload) throws IOException {
- return getRPCClassLoader(reload, null);
+ public ClassLoader getClassLoader(JavaSettings customJS) throws IOException {
+ return getRPCClassLoader(false, customJS);
}
- public ClassLoader getRPCClassLoader(boolean reload, ClassLoader[] parents) throws IOException {
- JavaSettingsImpl js = (JavaSettingsImpl) getApplicationContext().getJavaSettings();
- ClassLoader cl = config.getRPCClassLoader(reload, parents);
- if (js != null) {
- Resource[] jars = js.getResourcesTranslated();
- if (jars.length > 0) return ((PhysicalClassLoader) cl).getCustomClassLoader(jars, reload);
+ public ClassLoader getRPCClassLoader(boolean reload, JavaSettings customJS) throws IOException {
+ JavaSettings js = getJavaSettings();
+ if (customJS != null) {
+ js = JavaSettingsImpl.merge(config, js, customJS);
}
- return cl;
+ return ((ConfigPro) config).getRPCClassLoader(reload, js);
}
public void resetSession() {
@@ -3997,11 +4026,7 @@ public lucee.runtime.net.mail.Server[] getMailServers() {
// FUTURE add to interface
public boolean getFullNullSupport() {
- return fullNullSupport;
- }
-
- private void setFullNullSupport() {
- fullNullSupport = getApplicationContext().getFullNullSupport();
+ return getApplicationContext().getFullNullSupport();
}
public void registerLazyStatement(Statement s) {
@@ -4171,4 +4196,52 @@ public long timeoutNoAction() {
lastTimeoutNoAction = System.currentTimeMillis();
return tmp;
}
+
+ public AIEngine getAIEngine(String nameAI) throws PageException {
+ return ((ConfigPro) config).getAIEnginePool().getEngine(this, nameAI);
+ }
+
+ public AIEngine getAIEngine(String nameAI, AIEngine defaultValue) {
+ try {
+ return ((ConfigPro) config).getAIEnginePool().getEngine(this, nameAI);
+ }
+ catch (Exception e) {
+ return defaultValue;
+ }
+ }
+
+ public AISession createAISession(String nameAI, String initalMessage) throws PageException {
+ return getAIEngine(nameAI).createSession(initalMessage, -1);
+ }
+
+ public AISession createAISession(String nameAI, String initalMessage, long timeout) throws PageException {
+ return getAIEngine(nameAI).createSession(initalMessage, timeout);
+ }
+
+ public String getNameFromDefault(String defaultName) throws PageException {
+ if (StringUtil.isEmpty(defaultName, true)) throw new ApplicationException("default name cannot be empty.");
+ defaultName = defaultName.trim();
+
+ // TODO make a more direct way
+ ConfigPro cp = config;
+ for (String name: cp.getAIEngineFactoryNames()) {
+ if (defaultName.equalsIgnoreCase(cp.getAIEngineFactory(name).getDefault())) {
+ return name;
+ }
+ }
+ throw new ApplicationException("no match for default [" + defaultName + "] found.");
+ }
+
+ public String getNameFromDefault(String defaultName, String defaultValue) {
+ if (StringUtil.isEmpty(defaultName, true)) return defaultValue;
+
+ // TODO make a more direct way
+ ConfigPro cp = config;
+ for (String name: cp.getAIEngineFactoryNames()) {
+ if (defaultName.equalsIgnoreCase(cp.getAIEngineFactory(name).getDefault())) {
+ return name;
+ }
+ }
+ return defaultValue;
+ }
}
diff --git a/core/src/main/java/lucee/runtime/ai/AIEngine.java b/core/src/main/java/lucee/runtime/ai/AIEngine.java
index 6e0c105c33..4ac4255df7 100644
--- a/core/src/main/java/lucee/runtime/ai/AIEngine.java
+++ b/core/src/main/java/lucee/runtime/ai/AIEngine.java
@@ -1,11 +1,41 @@
package lucee.runtime.ai;
+import java.util.List;
+
import lucee.runtime.exp.PageException;
import lucee.runtime.type.Struct;
+/**
+ * The AIEngine interface defines the core functionalities required for an AI engine, including
+ * initialization, invocation, and lifecycle management methods.
+ */
public interface AIEngine {
- public AIEngine init(Struct properties) throws PageException;
+ /**
+ * Initializes the AI engine with the specified properties.
+ *
+ * @param properties a Struct containing the properties for initialization.
+ * @return an instance of the AIEngine after initialization.
+ * @throws PageException if an error occurs during initialization.
+ */
+ AIEngine init(AIEngineFactory factory, Struct properties) throws PageException;
+
+ public String getId();
+
+ /**
+ *
+ * @param inialMessage inital message to send to the AI, set null for no message
+ * @param timeout connection/read timeout for the calls to AI, set 0 for not timeout and -1 to use
+ * the default defined with the driver.
+ * @return the session created
+ */
+ public AISession createSession(String inialMessage, long timeout);
+
+ public AIEngineFactory getFactory();
+
+ public long getTimeout();
+
+ public String getLabel();
- public Response invoke(Request req) throws PageException;
+ public List getModels() throws PageException;
}
diff --git a/core/src/main/java/lucee/runtime/ai/AIEngineFactory.java b/core/src/main/java/lucee/runtime/ai/AIEngineFactory.java
new file mode 100644
index 0000000000..90a200f4ad
--- /dev/null
+++ b/core/src/main/java/lucee/runtime/ai/AIEngineFactory.java
@@ -0,0 +1,50 @@
+package lucee.runtime.ai;
+
+import org.osgi.framework.BundleException;
+
+import lucee.commons.io.log.Log;
+import lucee.commons.io.log.LogUtil;
+import lucee.commons.lang.ClassException;
+import lucee.commons.lang.ClassUtil;
+import lucee.commons.lang.StringUtil;
+import lucee.runtime.config.Config;
+import lucee.runtime.db.ClassDefinition;
+import lucee.runtime.exp.PageException;
+import lucee.runtime.type.Struct;
+import lucee.runtime.type.StructImpl;
+
+public class AIEngineFactory {
+
+ private ClassDefinition extends AIEngine> cd;
+ private Struct properties;
+ private String _default;
+ private String name;
+
+ public AIEngineFactory(ClassDefinition extends AIEngine> cd, Struct properties, String name, String _default) {
+ this.cd = cd;
+ this.properties = properties == null ? new StructImpl() : properties;
+ this.name = name.trim();
+ this._default = StringUtil.isEmpty(_default, true) ? null : _default.trim();
+ }
+
+ public static AIEngineFactory load(Config config, ClassDefinition extends AIEngine> cd, Struct custom, String name, String _default, boolean validate)
+ throws ClassException, BundleException {
+ // validate class
+ if (validate) cd.getClazz();
+ return new AIEngineFactory(cd, custom, name, _default);
+ }
+
+ public AIEngine createInstance(Config config) throws PageException, ClassException, BundleException {
+ AIEngine aie = (AIEngine) ClassUtil.loadInstance(cd.getClazz());
+ LogUtil.log(config, Log.LEVEL_TRACE, "ai", "ai-factory", "create AI instance [" + cd.toString() + "]");
+ return aie.init(this, properties);
+ }
+
+ public String getDefault() {
+ return _default;
+ }
+
+ public String getName() {
+ return name;
+ }
+}
diff --git a/core/src/main/java/lucee/runtime/ai/AIEngineFile.java b/core/src/main/java/lucee/runtime/ai/AIEngineFile.java
new file mode 100644
index 0000000000..a542f8873f
--- /dev/null
+++ b/core/src/main/java/lucee/runtime/ai/AIEngineFile.java
@@ -0,0 +1,24 @@
+package lucee.runtime.ai;
+
+import java.io.InputStream;
+import java.util.List;
+
+import lucee.commons.io.res.Resource;
+import lucee.runtime.exp.PageException;
+
+public interface AIEngineFile {
+
+ public static final String PURPOSE_ASSISTANTS = "assistants";
+ public static final String PURPOSE_VISION = "vision";
+ public static final String PURPOSE_FInE_TUNE = "fine-tune";
+
+ public List listFiles() throws PageException;
+
+ public String uploadFile(Resource jsonl, String purpose) throws PageException;
+
+ public AIFile getFile(String id) throws PageException;
+
+ public InputStream getFileContent(String id) throws PageException;
+
+ public boolean deleteFile(String id) throws PageException;
+}
diff --git a/core/src/main/java/lucee/runtime/ai/AIEnginePool.java b/core/src/main/java/lucee/runtime/ai/AIEnginePool.java
new file mode 100644
index 0000000000..95d66c78aa
--- /dev/null
+++ b/core/src/main/java/lucee/runtime/ai/AIEnginePool.java
@@ -0,0 +1,61 @@
+package lucee.runtime.ai;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import lucee.commons.lang.ExceptionUtil;
+import lucee.runtime.PageContext;
+import lucee.runtime.config.ConfigPro;
+import lucee.runtime.exp.ApplicationException;
+import lucee.runtime.exp.PageException;
+import lucee.runtime.op.Caster;
+
+public class AIEnginePool {
+
+ private Map instances = new ConcurrentHashMap<>();
+
+ public AIEngine getEngine(PageContext pc, String name, AIEngine defaultValue) {
+ // get existing instance
+ AIEngine aie = instances.get(name);
+ if (aie != null) return aie;
+
+ // loading new instance
+ AIEngineFactory factory = ((ConfigPro) pc.getConfig()).getAIEngineFactory(name.toLowerCase());
+ try {
+ return factory.createInstance(pc.getConfig());
+ }
+ catch (Exception e) {
+
+ }
+
+ return defaultValue;
+ }
+
+ public AIEngine getEngine(PageContext pc, String name) throws PageException {
+ // get existing instance
+ AIEngine aie = instances.get(name);
+ if (aie != null) return aie;
+
+ // loading new instance
+ AIEngineFactory factory = ((ConfigPro) pc.getConfig()).getAIEngineFactory(name.toLowerCase());
+ if (factory == null) {
+ throw new ApplicationException(ExceptionUtil.similarKeyMessage(((ConfigPro) pc.getConfig()).getAIEngineFactoryNames(), name, "source", "sources", "ai pool", true));
+ }
+
+ try {
+ aie = factory.createInstance(pc.getConfig());
+ if (aie != null) return aie;
+ }
+ catch (Exception e) {
+ throw Caster.toPageException(e);
+ }
+ throw new ApplicationException("there is no matching engine for the name [" + name + "] found");
+ }
+
+ /*
+ * private Map getCollection(String nameAI) { Map coll =
+ * instances.get(nameAI); if (coll == null) { synchronized (SystemUtil.createToken("ai-coll",
+ * nameAI)) { coll = instances.get(nameAI); if (coll == null) { coll = new ConcurrentHashMap<>();
+ * instances.put(nameAI, coll); } } } return coll; }
+ */
+}
\ No newline at end of file
diff --git a/core/src/main/java/lucee/runtime/ai/AIEngineSupport.java b/core/src/main/java/lucee/runtime/ai/AIEngineSupport.java
new file mode 100644
index 0000000000..5f95232f36
--- /dev/null
+++ b/core/src/main/java/lucee/runtime/ai/AIEngineSupport.java
@@ -0,0 +1,34 @@
+package lucee.runtime.ai;
+
+import lucee.commons.io.log.LogUtil;
+import lucee.runtime.functions.other.CreateUniqueId;
+
+public abstract class AIEngineSupport implements AIEngine {
+
+ public static final String DEFAULT_USERAGENT = "Lucee (AI Request)";
+ private String id;
+
+ private AIEngineFactory factory;
+
+ public AIEngine init(AIEngineFactory factory) {
+ this.factory = factory;
+ return this;
+ }
+
+ @Override
+ public AIEngineFactory getFactory() {
+ return factory;
+ }
+
+ @Override
+ public String getId() {
+ if (id == null) {
+ id = CreateUniqueId.invoke();
+ }
+ return id;
+ }
+
+ public static void log(Exception e) {
+ LogUtil.log("ai", "ai", e);
+ }
+}
diff --git a/core/src/main/java/lucee/runtime/ai/AIFile.java b/core/src/main/java/lucee/runtime/ai/AIFile.java
new file mode 100644
index 0000000000..80d1bcf392
--- /dev/null
+++ b/core/src/main/java/lucee/runtime/ai/AIFile.java
@@ -0,0 +1,21 @@
+package lucee.runtime.ai;
+
+import java.util.Date;
+
+public interface AIFile {
+ public String getObject();
+
+ public String getId();
+
+ public String getPurpose();
+
+ public String getFilename();
+
+ public long getBytes();
+
+ public Date getCreatedAt();
+
+ public String getStatus();
+
+ public String getStatusDetails();
+}
\ No newline at end of file
diff --git a/core/src/main/java/lucee/runtime/ai/AIFileSupport.java b/core/src/main/java/lucee/runtime/ai/AIFileSupport.java
new file mode 100644
index 0000000000..f22c77f39e
--- /dev/null
+++ b/core/src/main/java/lucee/runtime/ai/AIFileSupport.java
@@ -0,0 +1,74 @@
+package lucee.runtime.ai;
+
+import java.util.Date;
+
+public class AIFileSupport implements AIFile {
+
+ private String object;
+ private String id;
+ private String purpose;
+ private String filename;
+ private long bytes;
+ private Date createdAt;
+ private String status;
+ private String statusDetails;
+
+ public AIFileSupport(String object, String id, String purpose, String filename, long bytes, Date createdAt, String status, String statusDetails) {
+ this.object = object;
+ this.id = id;
+ this.purpose = purpose;
+ this.filename = filename;
+ this.bytes = bytes;
+ this.createdAt = createdAt;
+ this.status = status;
+ this.statusDetails = statusDetails;
+ }
+
+ @Override
+ public String getObject() {
+ return object;
+ }
+
+ @Override
+ public String getId() {
+ return id;
+ }
+
+ @Override
+ public String getPurpose() {
+ return purpose;
+ }
+
+ @Override
+ public String getFilename() {
+ return filename;
+ }
+
+ @Override
+ public long getBytes() {
+ return bytes;
+ }
+
+ @Override
+ public Date getCreatedAt() {
+ return createdAt;
+ }
+
+ @Override
+ public String getStatus() {
+ return status;
+ }
+
+ @Override
+ public String getStatusDetails() {
+ return statusDetails;
+ }
+
+ @Override
+ public String toString() {
+ // TODO Auto-generated method stub
+ return "object:" + object + ";id:" + id + ";purpose:" + purpose + ";filename:" + filename + ";bytes:" + bytes + ";createdAt:" + createdAt + ";status:" + status
+ + ";statusDetails:" + statusDetails + ";";
+ }
+
+}
diff --git a/core/src/main/java/lucee/runtime/ai/AIModel.java b/core/src/main/java/lucee/runtime/ai/AIModel.java
new file mode 100644
index 0000000000..60e8815cb3
--- /dev/null
+++ b/core/src/main/java/lucee/runtime/ai/AIModel.java
@@ -0,0 +1,14 @@
+package lucee.runtime.ai;
+
+import lucee.runtime.type.Struct;
+
+public interface AIModel {
+
+ public String getName();
+
+ public String getLabel();
+
+ public String getDescription();
+
+ public Struct asStruct();
+}
diff --git a/core/src/main/java/lucee/runtime/ai/AIModelSupport.java b/core/src/main/java/lucee/runtime/ai/AIModelSupport.java
new file mode 100644
index 0000000000..8bf7a0eacd
--- /dev/null
+++ b/core/src/main/java/lucee/runtime/ai/AIModelSupport.java
@@ -0,0 +1,49 @@
+package lucee.runtime.ai;
+
+import lucee.commons.io.CharsetUtil;
+import lucee.runtime.converter.ConverterException;
+import lucee.runtime.converter.JSONConverter;
+import lucee.runtime.converter.JSONDateFormat;
+import lucee.runtime.listener.SerializationSettings;
+import lucee.runtime.op.Caster;
+import lucee.runtime.type.Struct;
+import lucee.runtime.type.util.KeyConstants;
+
+public abstract class AIModelSupport implements AIModel {
+
+ private String name;
+ protected Struct raw;
+ private String charset;
+
+ public AIModelSupport(String name, Struct raw, String charset) {
+ this.name = name;
+ this.raw = raw;
+ this.charset = charset;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public String getDescription() {
+ return Caster.toString(raw.get(KeyConstants._description, null), null);
+ }
+
+ @Override
+ public String toString() {
+ try {
+ JSONConverter json = new JSONConverter(false, CharsetUtil.toCharset(charset), JSONDateFormat.PATTERN_CF, false);
+ return json.serialize(null, raw, SerializationSettings.SERIALIZE_AS_UNDEFINED, true);
+ }
+ catch (ConverterException e) {
+ return raw.toString();
+ }
+ }
+
+ @Override
+ public Struct asStruct() {
+ return raw;
+ }
+}
diff --git a/core/src/main/java/lucee/runtime/ai/AIResponseListener.java b/core/src/main/java/lucee/runtime/ai/AIResponseListener.java
new file mode 100644
index 0000000000..0e522bfa30
--- /dev/null
+++ b/core/src/main/java/lucee/runtime/ai/AIResponseListener.java
@@ -0,0 +1,5 @@
+package lucee.runtime.ai;
+
+public interface AIResponseListener {
+ public void listen(String part);
+}
diff --git a/core/src/main/java/lucee/runtime/ai/AISession.java b/core/src/main/java/lucee/runtime/ai/AISession.java
new file mode 100644
index 0000000000..6d68869422
--- /dev/null
+++ b/core/src/main/java/lucee/runtime/ai/AISession.java
@@ -0,0 +1,27 @@
+package lucee.runtime.ai;
+
+import lucee.runtime.exp.PageException;
+
+public interface AISession {
+
+ /**
+ * Invokes the AI session with the specified request.
+ *
+ * @param req the Request object containing the questions to be processed.
+ * @return a Response object containing the answers from the AI engine.
+ * @throws PageException if an error occurs during invocation.
+ */
+ Response inquiry(String message) throws PageException;
+
+ Response inquiry(String message, AIResponseListener listener) throws PageException;
+
+ public Conversation[] getHistory();
+
+ public String getId();
+
+ public AIEngine getEngine();
+
+ public long getTimeout();
+
+ public void release() throws PageException;
+}
diff --git a/core/src/main/java/lucee/runtime/ai/AISessionSupport.java b/core/src/main/java/lucee/runtime/ai/AISessionSupport.java
new file mode 100644
index 0000000000..9515ede635
--- /dev/null
+++ b/core/src/main/java/lucee/runtime/ai/AISessionSupport.java
@@ -0,0 +1,48 @@
+package lucee.runtime.ai;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import lucee.runtime.functions.other.CreateUniqueId;
+
+public abstract class AISessionSupport implements AISession {
+
+ private String id;
+ private AIEngine engine;
+ List history = new ArrayList<>();
+ private long timeout;
+
+ public AISessionSupport(AIEngine engine, long timeout) {
+ this.engine = engine;
+ if (timeout < 0) this.timeout = engine.getTimeout();
+ else this.timeout = timeout;
+ }
+
+ @Override
+ public final long getTimeout() {
+ return timeout;
+ }
+
+ @Override
+ public final AIEngine getEngine() {
+ return engine;
+ }
+
+ @Override
+ public final Conversation[] getHistory() {
+ return history.toArray(new Conversation[history.size()]);
+ }
+
+ protected final List getHistoryAsList() {
+ return history;
+ }
+
+ @Override
+ public final String getId() {
+ if (id == null) {
+ id = CreateUniqueId.invoke();
+ }
+ return id;
+ }
+
+}
diff --git a/core/src/main/java/lucee/runtime/ai/AIUtil.java b/core/src/main/java/lucee/runtime/ai/AIUtil.java
new file mode 100644
index 0000000000..4e769dbc05
--- /dev/null
+++ b/core/src/main/java/lucee/runtime/ai/AIUtil.java
@@ -0,0 +1,117 @@
+package lucee.runtime.ai;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import lucee.commons.io.res.ContentType;
+import lucee.commons.lang.StringUtil;
+import lucee.runtime.PageContext;
+import lucee.runtime.engine.ThreadLocalPageContext;
+import lucee.runtime.exp.ApplicationException;
+import lucee.runtime.exp.PageException;
+import lucee.runtime.type.Collection.Key;
+import lucee.runtime.type.KeyImpl;
+import lucee.runtime.type.Query;
+import lucee.runtime.type.QueryImpl;
+import lucee.runtime.type.Struct;
+import lucee.runtime.type.StructImpl;
+import lucee.runtime.type.util.KeyConstants;
+
+public class AIUtil {
+
+ private static final Key CREATED_AT = KeyImpl.init("createdAt");
+ private static final Key STATUS_DETAILS = KeyImpl.init("statusDetails");
+
+ public static PageException toException(AIEngine engine, String msg, String type, String code) {
+ String appendix = "";
+ if ("model_not_found".equals(code) || msg.indexOf("models") != -1) {
+ try {
+ appendix = " Available model names are [" + AIUtil.getModelNamesAsStringList(engine) + "]";
+ }
+ catch (PageException e) {
+ }
+ }
+
+ PageException ae = new ApplicationException(msg + appendix, "type:" + type + ";code:" + code);
+ ae.setErrorCode(code);
+ return ae;
+ }
+
+ public static List getModelNames(AIEngine aie) throws PageException {
+ List models = aie.getModels();
+ List names = new ArrayList<>();
+ for (AIModel m: models) {
+ names.add(m.getName());
+ }
+ Collections.sort(names);
+ return names;
+ }
+
+ public static String getModelNamesAsStringList(AIEngine aie) throws PageException {
+ StringBuilder sb = new StringBuilder();
+ for (String name: getModelNames(aie)) {
+ if (sb.length() > 0) sb.append(", ");
+ sb.append(name);
+ }
+ return sb.toString();
+ }
+
+ public static Struct getMetaData(AIEngine aie) throws PageException {
+
+ Struct meta = new StructImpl();
+
+ meta.set(KeyConstants._label, aie.getLabel());
+ AIEngineFactory factory = aie.getFactory();
+ if (factory != null) meta.set(KeyConstants._name, factory.getName());
+
+ // models
+ {
+ List models = aie.getModels();
+ Query qry = new QueryImpl(new Key[] { KeyConstants._name, KeyConstants._label, KeyConstants._description, KeyConstants._custom }, models.size(), "models");
+ int row = 0;
+ for (AIModel m: models) {
+ row++;
+ qry.setAt(KeyConstants._name, row, m.getName());
+ qry.setAt(KeyConstants._label, row, m.getLabel());
+ qry.setAt(KeyConstants._description, row, m.getDescription());
+ qry.setAt(KeyConstants._custom, row, m.asStruct());
+ }
+ meta.set(KeyConstants._models, qry);
+ }
+
+ // files
+ if (aie instanceof AIEngineFile) {
+ AIEngineFile aief = (AIEngineFile) aie;
+ List files = aief.listFiles();
+
+ // String status, String statusDetails
+ Query qry = new QueryImpl(new Key[] { KeyConstants._object, KeyConstants._id, KeyConstants._purpose, KeyConstants._filename, KeyConstants._bytes, CREATED_AT,
+ KeyConstants._status, STATUS_DETAILS }, files.size(), "files");
+ int row = 0;
+ for (AIFile f: files) {
+ row++;
+ qry.setAt(KeyConstants._object, row, f.getObject());
+ qry.setAt(KeyConstants._id, row, f.getId());
+ qry.setAt(KeyConstants._purpose, row, f.getPurpose());
+ qry.setAt(KeyConstants._filename, row, f.getFilename());
+ qry.setAt(KeyConstants._bytes, row, f.getBytes());
+ qry.setAt(CREATED_AT, row, f.getCreatedAt());
+ qry.setAt(KeyConstants._status, row, f.getStatus());
+ qry.setAt(STATUS_DETAILS, row, f.getStatusDetails());
+ }
+ meta.set(KeyConstants._files, qry);
+ }
+ return meta;
+ }
+
+ private static final String getCharset(ContentType ct) {
+ String charset = null;
+ if (ct != null) charset = ct.getCharset();
+ if (!StringUtil.isEmpty(charset)) return charset;
+
+ PageContext pc = ThreadLocalPageContext.get();
+ if (pc != null) return pc.getWebCharset().name();
+ return "ISO-8859-1";
+ }
+}
diff --git a/core/src/main/java/lucee/runtime/ai/CommandPromptAIResponseListener.java b/core/src/main/java/lucee/runtime/ai/CommandPromptAIResponseListener.java
new file mode 100644
index 0000000000..2e28a4eeda
--- /dev/null
+++ b/core/src/main/java/lucee/runtime/ai/CommandPromptAIResponseListener.java
@@ -0,0 +1,18 @@
+package lucee.runtime.ai;
+
+public class CommandPromptAIResponseListener implements AIResponseListener {
+
+ public static short OUT = 1;
+ public static short ERR = 2;
+ private short streamType;
+
+ public CommandPromptAIResponseListener(short streamType) {
+ this.streamType = streamType;
+ }
+
+ @Override
+ public void listen(String part) {
+ if (streamType == OUT) System.out.print(part);
+ else System.err.print(part);
+ }
+}
diff --git a/core/src/main/java/lucee/runtime/ai/Conversation.java b/core/src/main/java/lucee/runtime/ai/Conversation.java
new file mode 100644
index 0000000000..ebda7de8c8
--- /dev/null
+++ b/core/src/main/java/lucee/runtime/ai/Conversation.java
@@ -0,0 +1,9 @@
+package lucee.runtime.ai;
+
+// FUTURE move to interfac
+public interface Conversation {
+
+ public Request getRequest();
+
+ public Response getResponse();
+}
diff --git a/core/src/main/java/lucee/runtime/ai/ConversationImpl.java b/core/src/main/java/lucee/runtime/ai/ConversationImpl.java
new file mode 100644
index 0000000000..53ef78ccee
--- /dev/null
+++ b/core/src/main/java/lucee/runtime/ai/ConversationImpl.java
@@ -0,0 +1,23 @@
+package lucee.runtime.ai;
+
+public class ConversationImpl implements Conversation {
+
+ private Request req;
+ private Response rsp;
+
+ public ConversationImpl(Request req, Response rsp) {
+ this.req = req;
+ this.rsp = rsp;
+ }
+
+ @Override
+ public Request getRequest() {
+ return req;
+ }
+
+ @Override
+ public Response getResponse() {
+ return rsp;
+ }
+
+}
diff --git a/core/src/main/java/lucee/runtime/ai/DevNullAIResponseListener.java b/core/src/main/java/lucee/runtime/ai/DevNullAIResponseListener.java
new file mode 100644
index 0000000000..737ab76bfa
--- /dev/null
+++ b/core/src/main/java/lucee/runtime/ai/DevNullAIResponseListener.java
@@ -0,0 +1,12 @@
+package lucee.runtime.ai;
+
+public class DevNullAIResponseListener implements AIResponseListener {
+
+ public static final AIResponseListener INSTANCE = new DevNullAIResponseListener();
+
+ @Override
+ public void listen(String part) {
+ // do nothing
+ }
+
+}
diff --git a/core/src/main/java/lucee/runtime/ai/Request.java b/core/src/main/java/lucee/runtime/ai/Request.java
index 6d1c1deb11..18442cef44 100644
--- a/core/src/main/java/lucee/runtime/ai/Request.java
+++ b/core/src/main/java/lucee/runtime/ai/Request.java
@@ -1,14 +1,5 @@
package lucee.runtime.ai;
-public class Request {
-
- private String[] questions;
-
- public Request(String[] questions) {
- this.questions = questions;
- }
-
- public String[] getQuestions() {
- return questions;
- }
+public interface Request {
+ public String getQuestion();
}
diff --git a/core/src/main/java/lucee/runtime/ai/RequestSupport.java b/core/src/main/java/lucee/runtime/ai/RequestSupport.java
new file mode 100644
index 0000000000..4ae07af0fe
--- /dev/null
+++ b/core/src/main/java/lucee/runtime/ai/RequestSupport.java
@@ -0,0 +1,15 @@
+package lucee.runtime.ai;
+
+public class RequestSupport implements Request {
+
+ private String question;
+
+ public RequestSupport(String question) {
+ this.question = question;
+ }
+
+ @Override
+ public String getQuestion() {
+ return question;
+ }
+}
diff --git a/core/src/main/java/lucee/runtime/ai/google/GeminiEngine.java b/core/src/main/java/lucee/runtime/ai/google/GeminiEngine.java
new file mode 100644
index 0000000000..e7ce0aac25
--- /dev/null
+++ b/core/src/main/java/lucee/runtime/ai/google/GeminiEngine.java
@@ -0,0 +1,177 @@
+package lucee.runtime.ai.google;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import lucee.commons.io.res.ContentType;
+import lucee.commons.lang.StringUtil;
+import lucee.commons.net.http.HTTPResponse;
+import lucee.commons.net.http.Header;
+import lucee.commons.net.http.httpclient.HTTPEngine4Impl;
+import lucee.commons.net.http.httpclient.HeaderImpl;
+import lucee.loader.util.Util;
+import lucee.runtime.ai.AIEngine;
+import lucee.runtime.ai.AIEngineFactory;
+import lucee.runtime.ai.AIEngineSupport;
+import lucee.runtime.ai.AIModel;
+import lucee.runtime.ai.AISession;
+import lucee.runtime.ai.AIUtil;
+import lucee.runtime.exp.ApplicationException;
+import lucee.runtime.exp.PageException;
+import lucee.runtime.interpreter.JSONExpressionInterpreter;
+import lucee.runtime.net.proxy.ProxyData;
+import lucee.runtime.op.Caster;
+import lucee.runtime.type.Array;
+import lucee.runtime.type.Struct;
+import lucee.runtime.type.util.KeyConstants;
+
+public class GeminiEngine extends AIEngineSupport {
+ // https://ai.google.dev/gemini-api/docs/get-started/tutorial?lang=rest&hl=de
+ public static final String TYPE_REG = "generateContent?";
+ public static final String TYPE_STREAM = "streamGenerateContent?alt=sse&";
+
+ private static final String DEFAULT_URL = "https://generativelanguage.googleapis.com/v1/";
+
+ public static final String CHAT = "models/{model}:{cttype}key={apikey}";
+ public static final String MODELS = "models/?key={apikey}";
+
+ private static final long DEFAULT_TIMEOUT = 3000L;
+ private static final String DEFAULT_CHARSET = null;
+ private static final String DEFAULT_MIMETYPE = null;
+ // private static final String DEFAULT_MODEL = "gemini-1.5-flash";
+ private static final String DEFAULT_LOCATION = "us-central1";
+
+ Struct properties;
+ String apikey;
+ private long timeout;
+ String location;
+ String charset;
+ String mimetype;
+ ProxyData proxy = null;
+ Map formfields = null;
+ String model;
+ String systemMessage;
+ String baseURL = null;
+
+ @Override
+ public AIEngine init(AIEngineFactory factory, Struct properties) throws PageException {
+ super.init(factory);
+ this.properties = properties;
+
+ // base URL
+ String str = Caster.toString(properties.get(KeyConstants._URL, null), null);
+ if (!Util.isEmpty(str, true)) {
+ baseURL = str.trim();
+ if (!baseURL.endsWith("/")) baseURL += '/';
+ }
+ else baseURL = DEFAULT_URL;
+
+ // api key
+ str = Caster.toString(properties.get("apikey", null), null);
+ if (Util.isEmpty(str, true)) {
+ throw new ApplicationException("the property [apikey] is required for the AI Engine Gemini!");
+ }
+ apikey = str.trim();
+
+ // location
+ location = Caster.toString(properties.get(KeyConstants._location, null), DEFAULT_LOCATION);
+ if (Util.isEmpty(location, true)) location = DEFAULT_LOCATION;
+
+ // timeout
+ timeout = Caster.toLongValue(properties.get(KeyConstants._timeout, null), DEFAULT_TIMEOUT);
+
+ // charset
+ charset = Caster.toString(properties.get(KeyConstants._charset, null), DEFAULT_CHARSET);
+ if (Util.isEmpty(charset, true)) charset = null;
+
+ // mimetype
+ mimetype = Caster.toString(properties.get(KeyConstants._mimetype, null), DEFAULT_MIMETYPE);
+ if (Util.isEmpty(mimetype, true)) mimetype = null;
+
+ // model
+ model = Caster.toString(properties.get(KeyConstants._model, null), null);
+ if (Util.isEmpty(model, true)) {
+ // nice to have
+ String appendix = "";
+ try {
+ appendix = " Available models for this engine are [" + AIUtil.getModelNamesAsStringList(this) + "]";
+ }
+ catch (PageException pe) {
+ }
+
+ throw new ApplicationException("the property [model] is required for a OpenAI Engine!." + appendix);
+ }
+
+ // message
+ systemMessage = Caster.toString(properties.get(KeyConstants._message, null), null);
+
+ return this;
+
+ }
+
+ public URL toURL(String base, String scriptName, String cttype) throws PageException {
+ scriptName = StringUtil.replace(scriptName, "{location}", location, false);
+ scriptName = StringUtil.replace(scriptName, "{apikey}", apikey, false);
+ scriptName = StringUtil.replace(scriptName, "{model}", model, false);
+ if (cttype != null) scriptName = StringUtil.replace(scriptName, "{cttype}", cttype, false);
+ try {
+ return new URL(base + scriptName);
+ }
+ catch (MalformedURLException e) {
+ throw Caster.toPageException(e);
+ }
+ }
+
+ @Override
+ public AISession createSession(String inialMessage, long timeout) {
+ return new GeminiSession(this, StringUtil.isEmpty(inialMessage, true) ? systemMessage : inialMessage.trim(), timeout);
+ }
+
+ @Override
+ public String getLabel() {
+ return "Gemini";
+ }
+
+ @Override
+ public long getTimeout() {
+ return timeout;
+ }
+
+ @Override
+ public List getModels() throws PageException {
+ try {
+
+ HTTPResponse rsp = HTTPEngine4Impl.get(toURL(baseURL, MODELS, null), null, null, timeout, false, charset, AIEngineSupport.DEFAULT_USERAGENT, proxy,
+ new Header[] { new HeaderImpl("Content-Type", "application/json") });
+
+ ContentType ct = rsp.getContentType();
+ if ("application/json".equals(ct.getMimeType())) {
+ String cs = ct.getCharset();
+ if (Util.isEmpty(cs, true)) cs = charset;
+
+ Struct raw = Caster.toStruct(new JSONExpressionInterpreter().interpret(null, rsp.getContentAsString(cs)));
+ Struct err = Caster.toStruct(raw.get(KeyConstants._error, null), null);
+ if (err != null) {
+ throw AIUtil.toException(this, Caster.toString(err.get(KeyConstants._message)), Caster.toString(err.get(KeyConstants._type, null), null),
+ Caster.toString(err.get(KeyConstants._code, null), null));
+ }
+ Array data = Caster.toArray(raw.get("models"));
+ Iterator